1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::{CompiledLanguage, CppCompileContext};
3use crate::declarations::cpp_displaced_preprocessor_branch;
4#[cfg(test)]
5use crate::declarations::cpp_displaced_preprocessor_terminator;
6use crate::declarations::{
7 CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
8 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term,
9 enclosing_cpp_namespace_nodes, macro_modifier_identifier, node_text, normalize_cpp_whitespace,
10 recovered_class_body_at, recovered_function_like_field_declarator,
11 recovered_pyobject_head_field,
12};
13use crate::graph::CppGraphSource;
14use crate::graph::extractor::ScanCtx;
15use crate::graph::syntax::{
16 SentinelInput, function_macro_replacement_span, object_macro_replacement_type_references,
17 parse_sentinel, qualified_name_parts,
18};
19use crate::graph_support::CppSource;
20use crate::imports::{
21 IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
22};
23use crate::source_context::cpp_guard_set_to_runtime;
24use crate::structural::is_cpp_preprocessor_conditional;
25use crate::template_heads::{
26 CppTemplateBinding, CppTemplateContext, CppTemplateReconciliation,
27 cpp_declaration_template_context,
28};
29pub use brokk_bifrost_core::analyzer::cpp_facts::{
30 CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
31};
32use brokk_bifrost_core::analyzer::cpp_facts::{
33 CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
34 CppStructuredAliasTarget as StructuredAliasTarget,
35};
36use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
37use brokk_bifrost_core::analyzer::memo_attribution::{ReplayingMemo, ReplayingMemoEntry};
38use brokk_bifrost_core::analyzer::model::{
39 CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
40 CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
41 SignatureMetadata, StructuredTypeName,
42};
43use brokk_bifrost_core::analyzer::pool_memo::{KeyedPoolSafeMemo, PoolSafeMemo};
44use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
45#[cfg(test)]
46use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
47use brokk_bifrost_core::analyzer::project_reads::{OpaqueReadReceipt, ReadCapability};
48use brokk_bifrost_core::analyzer::query_token::QueryToken;
49use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
50use brokk_bifrost_core::analyzer::source_facts::SourceDeclarationId;
51use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
52use brokk_bifrost_core::analyzer::tree_walk::{
53 ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
54 named_children_iter, node_for_exact_range, push_named_children_reversed,
55 single_named_child_matching,
56};
57use brokk_bifrost_core::analyzer::usages::common::same_node;
58use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
59use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
60use brokk_bifrost_core::cancellation::CancellationToken;
61use brokk_bifrost_core::hash::{HashMap, HashSet};
62#[cfg(test)]
63use brokk_bifrost_core::text_utils::compute_line_starts;
64use std::borrow::Cow;
65#[cfg(any(test, feature = "test-support"))]
66use std::cell::Cell;
67use std::cell::OnceCell;
68use std::cmp::Ordering as CmpOrdering;
69use std::collections::BTreeSet;
70use std::hash::Hash;
71use std::sync::atomic::{AtomicUsize, Ordering};
72use std::sync::{Arc, Mutex, OnceLock};
73use std::time::{Duration, Instant};
74use tree_sitter::{Node, Parser, Tree};
75
76const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
77const CALLABLE_METADATA_CACHE_CAP: usize = 512;
78const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
79
80#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
81struct PreprocessorContextNodeKey {
82 id: usize,
83 start_byte: usize,
84 end_byte: usize,
85}
86
87#[derive(Default)]
96pub(crate) struct PreprocessorContextCache {
97 displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
98 displaced_branches: HashMap<PreprocessorContextNodeKey, Option<DisplacedBranchDirective>>,
99}
100
101#[derive(Clone, Copy)]
104struct DisplacedBranchDirective {
105 end_byte: usize,
106 is_else: bool,
107}
108
109impl PreprocessorContextCache {
110 fn displaced_branch(&mut self, conditional: Node<'_>) -> Option<DisplacedBranchDirective> {
111 let key = PreprocessorContextNodeKey {
112 id: conditional.id(),
113 start_byte: conditional.start_byte(),
114 end_byte: conditional.end_byte(),
115 };
116 if let Some(directive) = self.displaced_branches.get(&key) {
117 return *directive;
118 }
119 let directive = cpp_displaced_preprocessor_branch(conditional).map(|directive| {
120 DisplacedBranchDirective {
121 end_byte: directive.end_byte(),
122 is_else: directive.kind() == "#else",
123 }
124 });
125 if self.displaced_branches.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
126 self.displaced_branches.insert(key, directive);
127 }
128 directive
129 }
130
131 fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
132 let key = PreprocessorContextNodeKey {
133 id: conditional.id(),
134 start_byte: conditional.start_byte(),
135 end_byte: conditional.end_byte(),
136 };
137 if let Some(boundary) = self.displaced_boundaries.get(&key) {
138 return *boundary;
139 }
140 let boundary =
141 cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
142 if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
143 self.displaced_boundaries.insert(key, boundary);
144 }
145 boundary
146 }
147}
148
149#[cfg(any(test, feature = "test-support"))]
150thread_local! {
151 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
152 static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
153}
154
155#[derive(Clone, Copy, PartialEq, Eq)]
156pub enum TargetKind {
157 Type,
158 Constructor,
159 FreeFunction,
160 Method,
161 GlobalField,
162 MemberField,
163 Macro,
164}
165
166pub enum LexicalTypeResolution {
167 Resolved {
168 unit: CodeUnit,
169 components: Vec<String>,
170 candidates: Vec<CodeUnit>,
171 },
172 Ambiguous,
173 Missing,
174}
175
176pub enum GuardedAliasSelection {
179 Proven {
182 unit: CodeUnit,
183 candidates: Vec<CodeUnit>,
184 },
185 Alternatives(Vec<CodeUnit>),
187}
188
189#[derive(Clone, Copy, PartialEq, Eq)]
190enum AliasArmEvidence {
191 Implied,
192 Compatible,
193 Contradicted,
194}
195
196#[derive(Clone, Copy)]
197enum TypeCandidateResolution<'a> {
198 Canonical,
199 PreserveAlias,
200 PreserveTarget(&'a CodeUnit),
201}
202
203#[derive(Clone, Copy, Debug, PartialEq, Eq)]
212enum TypeCandidateFailure {
213 Ambiguous,
214 Unresolvable,
215}
216
217impl TypeCandidateFailure {
218 fn lexical_resolution(self) -> LexicalTypeResolution {
219 match self {
220 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
221 Self::Unresolvable => LexicalTypeResolution::Missing,
222 }
223 }
224}
225
226pub enum LexicalCallableValueResolution {
227 Type(CodeUnit),
228 FreeFunction(CodeUnit),
229 Ambiguous,
230 Missing,
231}
232
233pub enum UsingEnumMemberResolution {
234 Resolved { owner: CodeUnit, member: CodeUnit },
235 Ambiguous,
236 Missing,
237}
238
239pub enum NamespaceValueResolution {
240 Resolved,
241 Ambiguous,
242 Missing,
243}
244
245#[derive(Clone, Debug, PartialEq, Eq)]
246pub enum OrdinaryMacroReferenceResolution {
247 Resolved(CodeUnit),
248 Ambiguous,
249 Missing,
250}
251
252#[derive(Clone, Debug, PartialEq, Eq)]
253pub enum RecoveredCReferenceRanges {
254 Complete(Vec<Range>),
255 LimitExceeded,
256}
257
258pub fn resolve_namespace_value(
259 analyzer: &CppGraphSource<'_>,
260 visibility: &VisibilityIndex<'_>,
261 file: &ProjectFile,
262 namespace: &str,
263 name: &str,
264 before_byte: usize,
265) -> NamespaceValueResolution {
266 let mut matches = Vec::new();
267 for candidate in visibility.visible_identifier_candidates(file, name) {
268 if type_owner_of(analyzer, candidate).is_some()
269 || candidate.package_name() != namespace
270 || (candidate.source() == file
271 && !analyzer
272 .ranges(candidate)
273 .iter()
274 .any(|range| range.start_byte < before_byte))
275 || matches
276 .iter()
277 .any(|existing| same_visible_symbol(existing, candidate))
278 {
279 continue;
280 }
281 matches.push(candidate.clone());
282 if matches.len() > 1 {
283 return NamespaceValueResolution::Ambiguous;
284 }
285 }
286 matches
287 .pop()
288 .map(|_| NamespaceValueResolution::Resolved)
289 .unwrap_or(NamespaceValueResolution::Missing)
290}
291
292pub(crate) struct ScopedUsingEnumOwners {
293 scopes: Vec<Vec<CodeUnit>>,
294}
295
296pub(crate) struct SemanticUsingEnumOwners {
301 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
302 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
303}
304
305pub(crate) enum SemanticUsingEnumMemberResolution {
306 Class(UsingEnumMemberResolution),
307 Namespace(UsingEnumMemberResolution),
308 Missing,
309}
310
311impl SemanticUsingEnumOwners {
312 pub(crate) fn new() -> Self {
313 Self {
314 class_imports: HashMap::default(),
315 namespace_imports: HashMap::default(),
316 }
317 }
318
319 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
320 let imports = self.class_imports.entry(class).or_default();
321 if !imports
322 .iter()
323 .any(|existing| same_visible_symbol(existing, &enum_owner))
324 {
325 imports.push(enum_owner);
326 }
327 }
328
329 pub fn import_namespace(
330 &mut self,
331 namespace: Vec<String>,
332 declaration_byte: usize,
333 enum_owner: CodeUnit,
334 ) {
335 let imports = self.namespace_imports.entry(namespace).or_default();
336 if !imports
337 .iter()
338 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
339 {
340 imports.push((declaration_byte, enum_owner));
341 }
342 }
343
344 pub fn resolve_member(
345 &self,
346 visibility: &VisibilityIndex<'_>,
347 file: &ProjectFile,
348 class: Option<&CodeUnit>,
349 namespace: &[String],
350 before_byte: usize,
351 name: &str,
352 ) -> SemanticUsingEnumMemberResolution {
353 if let Some(class) = class
354 && let Some((_, imports)) = self
355 .class_imports
356 .iter()
357 .find(|(owner, _)| same_visible_symbol(owner, class))
358 {
359 let resolution =
360 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
361 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
362 return SemanticUsingEnumMemberResolution::Class(resolution);
363 }
364 }
365 for prefix_len in (0..=namespace.len()).rev() {
366 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
367 continue;
368 };
369 let owners = imports
370 .iter()
371 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
372 .map(|(_, owner)| owner);
373 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
374 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
375 return SemanticUsingEnumMemberResolution::Namespace(resolution);
376 }
377 }
378 SemanticUsingEnumMemberResolution::Missing
379 }
380}
381
382fn resolve_using_enum_member_for_owners<'a>(
383 visibility: &VisibilityIndex<'_>,
384 file: &ProjectFile,
385 owners: impl IntoIterator<Item = &'a CodeUnit>,
386 name: &str,
387) -> UsingEnumMemberResolution {
388 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
389 for owner in owners {
390 for member in visibility.visible_members_for_owner_name(file, owner, name) {
391 if !member.is_field()
392 || matches.iter().any(|(existing_owner, existing_member)| {
393 same_visible_symbol(existing_owner, owner)
394 && same_visible_symbol(existing_member, member)
395 })
396 {
397 continue;
398 }
399 matches.push((owner.clone(), member.clone()));
400 }
401 }
402 match matches.len() {
403 0 => UsingEnumMemberResolution::Missing,
404 1 => {
405 let (owner, member) = matches.pop().expect("one using-enum match");
406 UsingEnumMemberResolution::Resolved { owner, member }
407 }
408 _ => UsingEnumMemberResolution::Ambiguous,
409 }
410}
411
412impl ScopedUsingEnumOwners {
413 pub(crate) fn new() -> Self {
414 Self {
415 scopes: vec![Vec::new()],
416 }
417 }
418
419 pub fn enter_scope(&mut self) {
420 self.scopes.push(Vec::new());
421 }
422
423 pub fn exit_scope(&mut self) {
424 brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
425 }
426
427 pub fn import(&mut self, owner: CodeUnit) {
428 let scope = self
429 .scopes
430 .last_mut()
431 .expect("using-enum scope stack is never empty");
432 if !scope
433 .iter()
434 .any(|existing| same_visible_symbol(existing, &owner))
435 {
436 scope.push(owner);
437 }
438 }
439
440 pub fn resolve_member(
441 &self,
442 visibility: &VisibilityIndex<'_>,
443 file: &ProjectFile,
444 name: &str,
445 ) -> UsingEnumMemberResolution {
446 for scope in self.scopes.iter().rev() {
447 let resolution =
448 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
449 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
450 return resolution;
451 }
452 }
453 UsingEnumMemberResolution::Missing
454 }
455}
456
457#[derive(Clone)]
458pub struct TargetSpec {
459 pub target: CodeUnit,
460 pub kind: TargetKind,
461 pub owner: Option<CodeUnit>,
462 pub member_name: String,
463 pub callable_arity: Option<CallableArity>,
464 pub activated_callable_arities: Vec<ActivatedCallableArity>,
465 pub param_types: Option<Vec<String>>,
466 pub enum_owner_kind: EnumOwnerKind,
467 pub owner_is_forward_declaration: bool,
468 pub callable_has_definition_body: bool,
469}
470
471#[derive(Clone, Copy)]
472pub struct ActivatedCallableArity {
473 pub activation_byte: usize,
474 pub arity: CallableArity,
475}
476
477#[derive(Debug, PartialEq, Eq, Hash)]
478pub struct TypeScanKey {
479 target: LogicalSymbolKey,
480 member_name: String,
481}
482
483#[derive(Clone, Debug, PartialEq, Eq, Hash)]
484struct LogicalSymbolKey {
485 kind: CodeUnitType,
486 fq_name: String,
487 signature: Option<String>,
488}
489
490struct ResolvedTypeOwner {
491 unit: CodeUnit,
492 is_forward_declaration: bool,
493}
494
495impl TargetSpec {
496 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
497 (self.kind == TargetKind::Type).then(|| TypeScanKey {
498 target: logical_symbol_key(&self.target),
499 member_name: self.member_name.clone(),
500 })
501 }
502
503 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
504 if target.is_class() {
505 return Some(Self::new(
506 target.clone(),
507 TargetKind::Type,
508 Some(target.clone()),
509 target.identifier().to_string(),
510 None,
511 None,
512 ));
513 }
514
515 if target.is_field() {
516 let owner = type_owner_of(analyzer, target);
522 let kind = if owner.is_some() {
523 TargetKind::MemberField
524 } else {
525 TargetKind::GlobalField
526 };
527 let enum_owner_kind = owner
528 .as_ref()
529 .map(|owner| classify_enum_owner(analyzer, owner))
530 .unwrap_or(EnumOwnerKind::NonEnum);
531 let mut spec = Self::new(
532 target.clone(),
533 kind,
534 owner,
535 target.identifier().to_string(),
536 None,
537 None,
538 );
539 spec.enum_owner_kind = enum_owner_kind;
540 return Some(spec);
541 }
542
543 if target.is_function() {
544 let owner_resolution = target_type_owner_resolution(analyzer, target);
547 let owner_is_forward_declaration = owner_resolution
548 .as_ref()
549 .is_some_and(|owner| owner.is_forward_declaration);
550 let owner = owner_resolution.map(|owner| owner.unit);
551 let kind = if owner.as_ref().is_some_and(|owner| {
552 target.identifier() == owner.identifier()
553 || analyzer
554 .cpp
555 .and_then(|cpp| cpp.template_metadata(owner))
556 .is_some_and(|metadata| metadata.primary_name == target.identifier())
557 }) {
558 TargetKind::Constructor
559 } else if owner.is_some() {
560 TargetKind::Method
561 } else {
562 TargetKind::FreeFunction
563 };
564 let mut spec = Self::new(
565 target.clone(),
566 kind,
567 owner,
568 target.identifier().to_string(),
569 cpp_callable_arity(analyzer, target),
570 cpp_callable_parameter_types(analyzer, target),
571 );
572 spec.owner_is_forward_declaration = owner_is_forward_declaration;
573 spec.callable_has_definition_body =
574 callable_target_has_definition_body(analyzer, target);
575 return Some(spec);
576 }
577
578 if target.is_macro() {
579 return Some(Self::new(
580 target.clone(),
581 TargetKind::Macro,
582 None,
583 target.identifier().to_string(),
584 None,
585 None,
586 ));
587 }
588
589 None
590 }
591
592 pub fn with_visible_callable_arities<'a>(
593 &'a self,
594 analyzer: &CppGraphSource<'_>,
595 cpp: &dyn CppSource,
596 visibility: &VisibilityIndex<'_>,
597 file: &ProjectFile,
598 prepared: &PreparedSyntaxTree,
599 ) -> Cow<'a, Self> {
600 let macro_parameter_arity =
601 visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
602 let activated_callable_arities =
603 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
604 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
605 return Cow::Borrowed(self);
606 }
607 let mut effective = self.clone();
608 if let Some(macro_parameter_arity) = macro_parameter_arity {
609 effective.callable_arity = Some(macro_parameter_arity);
610 }
611 effective.activated_callable_arities = activated_callable_arities;
612 Cow::Owned(effective)
613 }
614
615 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
616 let base = self.callable_arity?;
617 Some(
618 self.activated_callable_arities
619 .iter()
620 .filter(|candidate| candidate.activation_byte <= byte)
621 .fold(base, |arity, candidate| {
622 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
623 }),
624 )
625 }
626
627 pub fn new(
628 target: CodeUnit,
629 kind: TargetKind,
630 owner: Option<CodeUnit>,
631 member_name: String,
632 callable_arity: Option<CallableArity>,
633 param_types: Option<Vec<String>>,
634 ) -> Self {
635 Self {
636 target,
637 kind,
638 owner,
639 member_name,
640 callable_arity,
641 activated_callable_arities: Vec::new(),
642 param_types,
643 enum_owner_kind: EnumOwnerKind::NonEnum,
644 owner_is_forward_declaration: false,
645 callable_has_definition_body: false,
646 }
647 }
648}
649
650fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
651 analyzer
652 .signature_metadata(target)
653 .iter()
654 .any(|metadata| !metadata.is_declaration_only())
655}
656
657fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
658 LogicalSymbolKey {
659 kind: unit.kind(),
660 fq_name: unit.fq_name(),
661 signature: unit.signature().map(str::to_string),
662 }
663}
664
665fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
666 let Some(facts) = analyzer.declaration_source_properties(owner) else {
667 return EnumOwnerKind::NonEnum;
668 };
669 let Some(first) = facts.first() else {
670 return EnumOwnerKind::NonEnum;
671 };
672 if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
673 first.enum_kind
674 } else {
675 EnumOwnerKind::NonEnum
676 }
677}
678
679#[derive(Clone, PartialEq, Eq, Hash)]
680pub struct CppScanBinding {
681 pub unit: Option<CodeUnit>,
682 pub type_name: Option<String>,
683 pub indirection: i32,
684}
685
686impl CppScanBinding {
687 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
688 Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
689 }
690
691 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
692 Self {
693 type_name: Some(type_name),
694 unit,
695 indirection,
696 }
697 }
698
699 pub fn as_arg_type(&self) -> Option<CppArgType> {
700 let name = self
701 .type_name
702 .clone()
703 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
704 Some(CppArgType {
705 name,
706 unit: self.unit.clone(),
707 indirection: self.indirection,
708 pointee_const: false,
709 })
710 }
711}
712
713type VisibilityEntry<T> = Arc<ReplayingMemoEntry<T, OpaqueReadReceipt>>;
716type VisibilityMemo<K, V> =
717 ReplayingMemo<KeyedPoolSafeMemo<K, ReplayingMemoEntry<V, OpaqueReadReceipt>>>;
718type BoundedVisibilityMemo<K, V> = ReplayingMemo<Mutex<HashMap<K, VisibilityEntry<V>>>>;
719type IncludeProtectionMemo = ReplayingMemo<
721 Mutex<
722 HashMap<
723 ProjectFile,
724 Arc<PoolSafeMemo<ReplayingMemoEntry<MacroIncludeProtection, OpaqueReadReceipt>>>,
725 >,
726 >,
727>;
728type VisibilityOnce<T> = ReplayingMemo<OnceLock<VisibilityEntry<T>>>;
729
730fn try_visibility_memo_value<K: Eq + Hash + Clone, V: Clone, E>(
731 memo: &VisibilityMemo<K, V>,
732 reads: &ReadCapability,
733 key: K,
734 build: impl FnOnce() -> Result<V, E>,
735) -> Result<V, E> {
736 let value = memo.read(&reads.memo_attribution(), |entries, capture| {
737 entries
738 .cell(&key)
739 .get_or_try_build_pool_independent(|| capture.try_build(build))
740 })?;
741 Ok((*value).clone())
742}
743
744pub(crate) fn visibility_memo_value<K: Eq + Hash + Clone, V: Clone>(
745 memo: &VisibilityMemo<K, V>,
746 reads: &ReadCapability,
747 key: K,
748 build: impl FnOnce() -> V,
749) -> V {
750 try_visibility_memo_value(memo, reads, key, || {
751 Ok::<_, std::convert::Infallible>(build())
752 })
753 .expect("infallible C++ visibility build")
754}
755
756fn bounded_visibility_memo_value<K: Eq + Hash + Clone, V: Clone>(
757 memo: &BoundedVisibilityMemo<K, V>,
758 reads: &ReadCapability,
759 key: &K,
760 capacity: usize,
761 build: impl FnOnce() -> V,
762) -> V {
763 let value = memo
764 .read(&reads.memo_attribution(), |entries, capture| {
765 if let Some(value) = entries
766 .lock()
767 .expect("C++ bounded visibility memo poisoned")
768 .get(key)
769 .cloned()
770 {
771 return Ok::<_, std::convert::Infallible>(value);
772 }
773 let value = Arc::new(capture.build(build));
774 let mut entries = entries
775 .lock()
776 .expect("C++ bounded visibility memo poisoned");
777 if let Some(existing) = entries.get(key).cloned() {
778 return Ok(existing);
779 }
780 if entries.len() < capacity {
781 entries.insert(key.clone(), Arc::clone(&value));
782 }
783 Ok(value)
784 })
785 .expect("infallible bounded C++ visibility build");
786 (*value).clone()
787}
788
789fn visibility_once_value<T: Clone>(
790 memo: &VisibilityOnce<T>,
791 reads: &ReadCapability,
792 build: impl FnOnce() -> T,
793) -> T {
794 let value = memo
795 .read(&reads.memo_attribution(), |cell, capture| {
796 Ok::<_, std::convert::Infallible>(Arc::clone(
797 cell.get_or_init(|| Arc::new(capture.build(build))),
798 ))
799 })
800 .expect("infallible C++ visibility cell build");
801 (*value).clone()
802}
803
804pub struct VisibilityCell<T> {
805 memo: VisibilityOnce<T>,
806 reads: ReadCapability,
807}
808impl<T: Clone> VisibilityCell<T> {
809 fn new(reads: ReadCapability) -> Self {
810 Self {
811 memo: VisibilityOnce::default(),
812 reads,
813 }
814 }
815 pub fn get_or_init(&self, build: impl FnOnce() -> T) -> T {
816 visibility_once_value(&self.memo, &self.reads, build)
817 }
818 pub fn get(&self) -> Option<T> {
819 self.memo
820 .read(&self.reads.memo_attribution(), |cell, _| {
821 cell.get().cloned().ok_or(())
822 })
823 .ok()
824 .map(|value| (*value).clone())
825 }
826}
827
828type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
829pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
830pub type MacroEventCell = Arc<VisibilityCell<Arc<[MacroEvent]>>>;
831type MacroEnvironmentCheckpointCell = Arc<VisibilityCell<Arc<MacroEnvironmentCheckpoints>>>;
832
833struct StructuredIncludeFact {
834 start_byte: usize,
835 end_byte: usize,
836 path: String,
837}
838
839#[derive(Clone, Default)]
840pub struct MacroEnvironment {
841 bindings: HashMap<String, MacroBinding>,
842 known_undefined_names: HashSet<String>,
843 build_proven_defines: HashSet<String>,
848 unknown_names: bool,
849 applied_pragma_once_files: HashSet<ProjectFile>,
850 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
851}
852
853pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
862
863struct MacroEnvironmentCheckpoint {
865 frontier: usize,
867 environment: Arc<MacroEnvironment>,
868}
869
870struct MacroEnvironmentCheckpoints {
879 checkpoints: Vec<MacroEnvironmentCheckpoint>,
880}
881
882impl MacroEnvironmentCheckpoints {
883 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
885 let index = self
886 .checkpoints
887 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
888 assert!(
889 index > 0,
890 "a checkpoint vector starts at frontier zero, which precedes every request"
891 );
892 &self.checkpoints[index - 1]
893 }
894}
895
896impl MacroEnvironment {
897 fn binding(&self, name: &str) -> Option<&MacroBinding> {
898 self.bindings.get(name)
899 }
900
901 fn may_bind(&self, name: &str) -> bool {
902 self.bindings.contains_key(name) || self.unknown_names
903 }
904
905 fn insert(&mut self, name: String, binding: MacroBinding) {
906 self.known_undefined_names.remove(&name);
907 self.bindings.insert(name, binding);
908 }
909
910 fn remove(&mut self, name: &str) {
911 self.bindings.remove(name);
912 self.known_undefined_names.insert(name.to_string());
913 }
914
915 fn remove_known_undefined(&mut self, name: &str) {
916 self.known_undefined_names.remove(name);
917 }
918
919 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
920 for binding in self.bindings.values_mut() {
921 *binding = MacroBinding::uncertain_from(binding, source, byte);
922 }
923 self.known_undefined_names.clear();
924 self.build_proven_defines.clear();
929 self.unknown_names = true;
930 }
931
932 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
933 guards.iter().all(|guard| self.guard_may_hold(guard))
934 }
935
936 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
937 self.boolean_guard_may_hold(&guard.as_boolean_expression())
938 }
939
940 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
941 match expression {
942 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
943 BooleanGuardExpression::Undefined(name) => {
944 self.bindings
945 .get(name)
946 .is_none_or(|binding| !binding.is_exact())
947 && (!self.build_proven_defines.contains(name)
948 || self.known_undefined_names.contains(name))
949 }
950 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
951 BooleanGuardExpression::Opaque(_)
952 | BooleanGuardExpression::NegatedOpaque(_)
953 | BooleanGuardExpression::Constant(true) => true,
954 BooleanGuardExpression::Constant(false) => false,
955 BooleanGuardExpression::All(expressions) => expressions
956 .iter()
957 .all(|expression| self.boolean_guard_may_hold(expression)),
958 BooleanGuardExpression::Any(expressions) => expressions
959 .iter()
960 .any(|expression| self.boolean_guard_may_hold(expression)),
961 }
962 }
963}
964
965fn any_visible_source_file_matches(
966 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
967 file: &ProjectFile,
968 mut predicate: impl FnMut(&ProjectFile) -> bool,
969) -> bool {
970 match visible_source_files_by_root.get(file) {
971 Some(files) => files.iter().any(predicate),
972 None => predicate(file),
973 }
974}
975
976#[derive(Clone)]
977pub enum EffectiveUsingTarget {
978 Ordinary {
979 name: String,
980 target_components: Vec<String>,
981 global: bool,
982 },
983 Namespace {
984 namespace_components: Vec<String>,
985 global: bool,
986 },
987}
988
989#[derive(Clone)]
990pub struct OrdinaryTypeImport {
991 pub target: EffectiveUsingTarget,
992 pub source: ProjectFile,
993 pub declaration_byte: usize,
994 pub scope_start: usize,
995 pub scope_end: usize,
996 pub scope_depth: usize,
997 pub block_scope: bool,
998 pub lexical_depth: usize,
999 pub declaration_namespace: Vec<String>,
1000 pub namespace_scope: Option<Vec<String>>,
1001 pub resolved_target_components: Option<Vec<String>>,
1002 pub required_guards: HashSet<PreprocessorGuard>,
1003}
1004
1005#[derive(Clone)]
1006pub struct ConditionalIncludeProjection {
1007 pub activation_byte: usize,
1008 pub required_guards: HashSet<PreprocessorGuard>,
1009 pub partial_guards: HashSet<PreprocessorGuard>,
1013}
1014
1015#[derive(Default)]
1016pub struct SourceUsingIndex {
1017 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
1018 pub directives: Vec<OrdinaryTypeImport>,
1019}
1020
1021#[derive(Default)]
1022pub struct ProjectUsingIndex {
1023 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
1024 pub directives: Vec<OrdinaryTypeImport>,
1025}
1026
1027type EffectiveUsingProjectionCell = Arc<VisibilityCell<Arc<[OrdinaryTypeImport]>>>;
1028
1029pub struct EffectiveUsingIndex {
1030 reads: ReadCapability,
1031 projected_by_name: VisibilityMemo<String, EffectiveUsingProjectionCell>,
1032}
1033
1034impl EffectiveUsingIndex {
1035 fn new(_root: ProjectFile, reads: ReadCapability) -> Self {
1036 Self {
1037 reads,
1038 projected_by_name: VisibilityMemo::default(),
1039 }
1040 }
1041
1042 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
1043 visibility_memo_value(
1044 &self.projected_by_name,
1045 &self.reads,
1046 name.to_owned(),
1047 || Arc::new(VisibilityCell::new(self.reads.clone())),
1048 )
1049 }
1050}
1051
1052pub enum OrdinaryTypeImportResolution {
1053 Resolved {
1054 target: CodeUnit,
1055 target_components: Vec<String>,
1056 lexical_depth: usize,
1057 is_direct: bool,
1058 },
1059 Ambiguous {
1060 lexical_depth: usize,
1061 },
1062 Missing,
1063}
1064
1065type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
1066
1067struct ExtractedComparable {
1072 shapes: Vec<CppComparableSlot>,
1073 suffix: String,
1074 template: Option<CppTemplateContext>,
1077}
1078
1079const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
1083
1084#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1093enum IncludePathAdmission {
1094 Proven,
1095 Compatible,
1096}
1097
1098impl IncludePathAdmission {
1099 fn admits(
1100 self,
1101 required: &HashSet<PreprocessorGuard>,
1102 partial: &HashSet<PreprocessorGuard>,
1103 reference_guards: Option<&HashSet<PreprocessorGuard>>,
1104 ) -> bool {
1105 match self {
1106 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
1107 Self::Compatible => {
1108 guard_requirements_hold_at_reference(partial, reference_guards)
1109 && guards_compatible_at_reference(required, reference_guards)
1110 }
1111 }
1112 }
1113}
1114
1115pub struct VisibilityIndex<'a> {
1119 cpp: &'a dyn CppSource,
1120 token: QueryToken<'a>,
1121 data: Arc<VisibilityIndexData>,
1122}
1123
1124impl std::ops::Deref for VisibilityIndex<'_> {
1125 type Target = VisibilityIndexData;
1126
1127 fn deref(&self) -> &Self::Target {
1128 &self.data
1129 }
1130}
1131
1132pub struct VisibilityIndexData {
1135 pub(crate) reads: ReadCapability,
1136 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1137 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1138 global_field_internal_linkage: HashMap<CodeUnit, bool>,
1139 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1140 alias_cells: VisibilityMemo<ProjectFile, AliasCell>,
1141 visible_parser_alias_name_sets: VisibilityMemo<ProjectFile, Arc<HashSet<String>>>,
1142 parser_alias_target_matches: VisibilityMemo<(ProjectFile, String, LogicalSymbolKey), bool>,
1143 ordinary_type_import_cells: VisibilityMemo<ProjectFile, OrdinaryTypeImportCell>,
1144 project_using_index: VisibilityOnce<Arc<ProjectUsingIndex>>,
1145 namespace_alias_sites: VisibilityMemo<ProjectFile, super::namespace_alias::NamespaceAliasCell>,
1146 callable_reference_specs:
1147 VisibilityMemo<(ProjectFile, LogicalSymbolKey), Option<Arc<TargetSpec>>>,
1148 structured_include_fact_cells: VisibilityMemo<ProjectFile, Arc<[StructuredIncludeFact]>>,
1149 include_activation_cells: VisibilityMemo<(ProjectFile, ProjectFile), Option<usize>>,
1150 compile_proven_guard_cells: VisibilityMemo<ProjectFile, Arc<HashSet<PreprocessorGuard>>>,
1151 include_path_admission_cells: VisibilityMemo<ProjectFile, IncludePathAdmission>,
1152 conditional_include_projection_cells:
1153 VisibilityMemo<ProjectFile, Arc<ConditionalIncludeProjectionIndex>>,
1154 #[cfg(any(test, feature = "test-support"))]
1155 conditional_include_projection_index_build_count: AtomicUsize,
1156 #[cfg(any(test, feature = "test-support"))]
1157 conditional_include_projection_state_count: AtomicUsize,
1158 #[cfg(any(test, feature = "test-support"))]
1159 conditional_include_target_state_count: AtomicUsize,
1160 #[cfg(any(test, feature = "test-support"))]
1161 include_activation_build_count: AtomicUsize,
1162 #[cfg(any(test, feature = "test-support"))]
1163 using_donor_activation_count: AtomicUsize,
1164 #[cfg(any(test, feature = "test-support"))]
1165 using_namespace_lookup_count: AtomicUsize,
1166 #[cfg(any(test, feature = "test-support"))]
1167 using_name_candidate_inspection_count: AtomicUsize,
1168 #[cfg(any(test, feature = "test-support"))]
1169 callable_reference_spec_build_count: AtomicUsize,
1170 #[cfg(any(test, feature = "test-support"))]
1171 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1172 #[cfg(any(test, feature = "test-support"))]
1173 visible_parser_alias_name_set_build_count: AtomicUsize,
1174 parser_alias_fallback_calls: AtomicUsize,
1175 parser_alias_fallback_files: AtomicUsize,
1176 parser_alias_source_parses: AtomicUsize,
1177 parser_alias_fallback_elapsed_micros: AtomicUsize,
1178 field_type_facts: VisibilityMemo<CodeUnit, Option<DeclaredFieldTypeFact>>,
1179 structured_alias_targets: VisibilityMemo<CodeUnit, Option<StructuredAliasTarget>>,
1180 callable_comparables: VisibilityMemo<CodeUnit, Option<Arc<ExtractedComparable>>>,
1181 class_definition_agreements: VisibilityMemo<(CodeUnit, CodeUnit), bool>,
1182 callable_metadata: BoundedVisibilityMemo<CodeUnit, Arc<Vec<SignatureMetadata>>>,
1188 comparable_name_declarations: VisibilityMemo<StructuredTypeName, Option<CodeUnit>>,
1189 indexed_structural_class_scopes:
1190 VisibilityMemo<(ProjectFile, usize, usize), Option<Vec<String>>>,
1191 indexed_enclosing_owner_scopes:
1192 VisibilityMemo<(ProjectFile, usize, usize), Option<Vec<String>>>,
1193 precise_parent_cache: VisibilityMemo<CodeUnit, Option<CodeUnit>>,
1194 c_tag_kind_cache: VisibilityMemo<CodeUnit, Option<CppCTagKind>>,
1195 c_tag_complete_definition_cache: VisibilityMemo<CodeUnit, Option<CodeUnit>>,
1196 macro_event_cells: VisibilityMemo<ProjectFile, MacroEventCell>,
1197 macro_event_name_sets: VisibilityMemo<ProjectFile, Arc<HashSet<String>>>,
1198 pub macro_include_protection_cells: IncludeProtectionMemo,
1199 macro_environment_checkpoints: VisibilityMemo<ProjectFile, MacroEnvironmentCheckpointCell>,
1206 macro_replacements: VisibilityMemo<(ProjectFile, usize), Arc<ParsedMacroReplacement>>,
1207 macro_local_binding_templates:
1208 VisibilityMemo<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>,
1209 pub(crate) macro_lexical_templates:
1210 VisibilityMemo<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>,
1211 macro_lexical_indexes:
1212 BoundedVisibilityMemo<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>,
1213 macro_replacement_bodies:
1214 VisibilityMemo<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>,
1215 macro_type_parameters: VisibilityMemo<(ProjectFile, usize), Option<Arc<[usize]>>>,
1216 callable_parameter_macro_arities: VisibilityMemo<(ProjectFile, String), Option<CallableArity>>,
1217 #[cfg(any(test, feature = "test-support"))]
1218 pub macro_replacement_parse_count: AtomicUsize,
1219 #[cfg(any(test, feature = "test-support"))]
1220 pub macro_event_application_count: AtomicUsize,
1221 #[cfg(any(test, feature = "test-support"))]
1224 pub macro_environment_checkpoint_build_count: AtomicUsize,
1225 #[cfg(any(test, feature = "test-support"))]
1228 pub macro_environment_copy_count: AtomicUsize,
1229 #[cfg(any(test, feature = "test-support"))]
1230 pub macro_environment_request_count: AtomicUsize,
1231 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1232 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1233 #[cfg(any(test, feature = "test-support"))]
1234 qualified_candidate_inspections: AtomicUsize,
1235 #[cfg(any(test, feature = "test-support"))]
1236 target_preserving_type_resolution_count: AtomicUsize,
1237 #[cfg(any(test, feature = "test-support"))]
1238 visibility_identifier_lookup_count: usize,
1239 #[cfg(any(test, feature = "test-support"))]
1240 visibility_identifier_batch_count: usize,
1241}
1242
1243impl Drop for VisibilityIndexData {
1244 fn drop(&mut self) {
1245 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1246 return;
1247 }
1248 #[cfg(any(test, feature = "test-support"))]
1249 eprintln!(
1250 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1251 self.macro_environment_request_count.load(Ordering::Relaxed),
1252 self.macro_environment_copy_count.load(Ordering::Relaxed),
1253 self.macro_environment_checkpoint_build_count
1254 .load(Ordering::Relaxed),
1255 self.macro_event_application_count.load(Ordering::Relaxed),
1256 );
1257 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1258 if calls == 0 {
1259 return;
1260 }
1261 eprintln!(
1262 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1263 calls,
1264 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1265 self.parser_alias_source_parses.load(Ordering::Relaxed),
1266 self.parser_alias_fallback_elapsed_micros
1267 .load(Ordering::Relaxed)
1268 / 1_000,
1269 );
1270 }
1271}
1272
1273#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1274pub enum PreprocessorGuard {
1275 Defined(String),
1276 Undefined(String),
1277 Boolean(BooleanGuardExpression),
1278 Expression(String),
1279 NegatedExpression(String),
1280 Constant(bool),
1281}
1282
1283#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1284pub enum BooleanGuardExpression {
1285 Defined(String),
1286 Undefined(String),
1287 Truthy(String),
1288 Falsy(String),
1289 Opaque(String),
1290 NegatedOpaque(String),
1291 All(Vec<BooleanGuardExpression>),
1292 Any(Vec<BooleanGuardExpression>),
1293 Constant(bool),
1294}
1295
1296impl BooleanGuardExpression {
1297 fn negated(&self) -> Self {
1298 match self {
1299 Self::Defined(name) => Self::Undefined(name.clone()),
1300 Self::Undefined(name) => Self::Defined(name.clone()),
1301 Self::Truthy(name) => Self::Falsy(name.clone()),
1302 Self::Falsy(name) => Self::Truthy(name.clone()),
1303 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1304 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1305 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1306 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1307 Self::Constant(value) => Self::Constant(!value),
1308 }
1309 }
1310
1311 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1312 Self::normalized(expressions, true)
1313 }
1314
1315 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1316 Self::normalized(expressions, false)
1317 }
1318
1319 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1320 let mut normalized = Vec::new();
1321 for expression in expressions {
1322 match expression {
1323 Self::All(nested) if conjunction => normalized.extend(nested),
1324 Self::Any(nested) if !conjunction => normalized.extend(nested),
1325 Self::Constant(value) if value == conjunction => {}
1326 Self::Constant(value) => return Self::Constant(value),
1327 expression => normalized.push(expression),
1328 }
1329 }
1330 normalized.sort_unstable();
1331 normalized.dedup();
1332 match normalized.len() {
1333 0 => Self::Constant(conjunction),
1334 1 => normalized.pop().expect("one Boolean guard expression"),
1335 _ if conjunction => Self::All(normalized),
1336 _ => Self::Any(normalized),
1337 }
1338 }
1339
1340 fn implies(&self, required: &Self) -> bool {
1341 if self == required
1342 || matches!(self, Self::Constant(false))
1343 || matches!(required, Self::Constant(true))
1344 {
1345 return true;
1346 }
1347 if matches!(
1348 (self, required),
1349 (Self::Truthy(active), Self::Defined(required))
1350 | (Self::Undefined(active), Self::Falsy(required))
1351 if active == required
1352 ) {
1353 return true;
1354 }
1355 match self {
1356 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1357 Self::All(active) => match required {
1358 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1359 _ => active.iter().any(|expression| expression.implies(required)),
1360 },
1361 _ => match required {
1362 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1363 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1364 _ => false,
1365 },
1366 }
1367 }
1368
1369 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1370 match self {
1371 Self::Defined(name)
1372 | Self::Undefined(name)
1373 | Self::Truthy(name)
1374 | Self::Falsy(name) => name == macro_name,
1375 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1378 Self::All(expressions) | Self::Any(expressions) => expressions
1379 .iter()
1380 .any(|expression| expression.may_depend_on_macro(macro_name)),
1381 Self::Constant(_) => false,
1382 }
1383 }
1384
1385 pub fn heap_size(&self) -> usize {
1386 match self {
1387 Self::Defined(value)
1388 | Self::Undefined(value)
1389 | Self::Truthy(value)
1390 | Self::Falsy(value)
1391 | Self::Opaque(value)
1392 | Self::NegatedOpaque(value) => value.len(),
1393 Self::All(expressions) | Self::Any(expressions) => {
1394 expressions
1395 .iter()
1396 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1397 size.saturating_add(std::mem::size_of::<Self>())
1398 .saturating_add(expression.heap_size())
1399 })
1400 }
1401 Self::Constant(_) => 0,
1402 }
1403 }
1404}
1405
1406impl PreprocessorGuard {
1407 fn as_boolean_expression(&self) -> BooleanGuardExpression {
1408 match self {
1409 Self::Defined(name) => BooleanGuardExpression::Defined(name.clone()),
1410 Self::Undefined(name) => BooleanGuardExpression::Undefined(name.clone()),
1411 Self::Boolean(expression) => expression.clone(),
1412 Self::Constant(value) => BooleanGuardExpression::Constant(*value),
1413 Self::Expression(expression) => BooleanGuardExpression::Opaque(expression.clone()),
1414 Self::NegatedExpression(expression) => {
1415 BooleanGuardExpression::NegatedOpaque(expression.clone())
1416 }
1417 }
1418 }
1419
1420 fn negated(&self) -> Self {
1421 match self {
1422 Self::Defined(name) => Self::Undefined(name.clone()),
1423 Self::Undefined(name) => Self::Defined(name.clone()),
1424 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1425 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1426 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1427 Self::Constant(value) => Self::Constant(!value),
1428 }
1429 }
1430
1431 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1432 match self {
1433 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1434 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1435 Self::Expression(_) | Self::NegatedExpression(_) => true,
1438 Self::Constant(_) => false,
1439 }
1440 }
1441}
1442
1443#[derive(Clone, PartialEq, Eq)]
1444pub enum MacroDefinition {
1445 Object {
1446 replacement: String,
1447 },
1448 Function {
1449 parameters: Vec<String>,
1450 replacement: String,
1451 },
1452 VariadicFunction {
1453 parameters: Vec<String>,
1454 replacement: String,
1455 },
1456 Unsupported,
1457}
1458
1459#[derive(Clone, Debug, PartialEq, Eq)]
1460pub enum MacroIncludeProtection {
1461 MacroGuard(String),
1462 PragmaOnce,
1463 None,
1464}
1465
1466enum ParsedMacroReplacement {
1467 Parsed { source: String, tree: Tree },
1468 Unsupported,
1469}
1470
1471const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1475const MACRO_BODY_SENTINEL_SUFFIX: &str = "; }";
1476
1477type MacroOriginMap = Box<[std::ops::Range<usize>]>;
1480
1481pub struct ParsedReplacementBody {
1490 pub source: String,
1491 pub tree: Tree,
1492 pub body_offset: usize,
1493 pub parameters: Vec<String>,
1494 original_offsets: MacroOriginMap,
1498}
1499
1500impl ParsedReplacementBody {
1501 pub fn statements(&self) -> Option<Node<'_>> {
1503 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1504 .child_by_field_name("body")
1505 }
1506
1507 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1513 assert!(
1514 node.start_byte() >= self.body_offset,
1515 "synthetic sentinel node cannot be mapped to a macro replacement"
1516 );
1517 assert!(
1518 node.end_byte() >= self.body_offset,
1519 "synthetic sentinel node cannot be mapped to a macro replacement"
1520 );
1521 let start_offset = node.start_byte() - self.body_offset;
1522 let end_offset = node.end_byte() - self.body_offset;
1523 assert!(start_offset <= end_offset);
1524 let start_origin = self
1525 .original_offsets
1526 .get(start_offset)
1527 .expect("replacement node start must have a source mapping")
1528 .start;
1529 let end_origin = if start_offset == end_offset {
1530 start_origin
1531 } else {
1532 self.original_offsets
1533 .get(end_offset - 1)
1534 .expect("replacement node end must have a source mapping")
1535 .end
1536 };
1537 assert!(start_origin <= end_origin);
1538 let start = replacement_start + start_origin;
1539 let end = replacement_start + end_origin;
1540 start..end
1541 }
1542
1543 fn expands_variadic_arguments(&self) -> bool {
1550 let mut stack = vec![self.tree.root_node()];
1551 while let Some(node) = stack.pop() {
1552 if matches!(
1553 node.kind(),
1554 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1555 ) && node_text(node, &self.source) == "__VA_ARGS__"
1556 {
1557 return true;
1558 }
1559 push_named_children_reversed(node, &mut stack);
1560 }
1561 false
1562 }
1563}
1564
1565pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1567 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1568 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1569 if compact.starts_with("0x") || compact.starts_with("0X") {
1570 (16, 2, |ch| ch.is_ascii_hexdigit())
1571 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1572 (2, 2, |ch| matches!(ch, '0' | '1'))
1573 } else if compact.starts_with('0') && compact.len() > 1 {
1574 (8, 0, |ch| matches!(ch, '0'..='7'))
1575 } else {
1576 (10, 0, |ch| ch.is_ascii_digit())
1577 };
1578 let digit_len = compact[digits_start..]
1579 .chars()
1580 .take_while(|ch| digit_matches(*ch))
1581 .map(char::len_utf8)
1582 .sum::<usize>();
1583 if digit_len == 0 {
1584 return None;
1585 }
1586 let digits_end = digits_start + digit_len;
1587 if !compact[digits_end..]
1588 .chars()
1589 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1590 {
1591 return None;
1592 }
1593 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1594}
1595
1596#[derive(Clone)]
1597enum MacroLocalBindingTypeTemplate {
1598 Parameter(usize),
1599 Fixed(String),
1600}
1601
1602#[derive(Clone)]
1603struct MacroLocalBindingTemplate {
1604 name: String,
1605 declared_type: MacroLocalBindingTypeTemplate,
1606 pointer_depth: i32,
1607}
1608
1609pub struct MacroLocalBinding<'tree> {
1617 pub name: String,
1618 pub type_name: String,
1619 pub type_node: Option<Node<'tree>>,
1620 pub pointer_depth: i32,
1621 pub proven_unit: Option<CodeUnit>,
1622}
1623
1624#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1630pub enum MacroLexicalBindingKind {
1631 Parameter,
1632 Local,
1633}
1634
1635#[derive(Clone, Debug, PartialEq, Eq)]
1636pub struct MacroLexicalBinding {
1637 pub definition: ProjectFile,
1638 pub kind: MacroLexicalBindingKind,
1639 pub name: String,
1640 pub name_range: std::ops::Range<usize>,
1641 pub declaration_range: std::ops::Range<usize>,
1642}
1643
1644#[derive(Clone, Debug, Default, PartialEq, Eq)]
1648pub struct MacroLexicalReferences {
1649 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1650 pub truncated: bool,
1651 pub cancelled: bool,
1652}
1653
1654fn macro_replacement_type_parameters(
1655 body: &ParsedReplacementBody,
1656 parameters: &[String],
1657) -> Option<Vec<usize>> {
1658 let mut found = Vec::new();
1659 let mut stack = vec![body.tree.root_node()];
1660 while let Some(node) = stack.pop() {
1661 let type_position = node.kind() == "type_identifier"
1662 || (node.kind() == "identifier"
1663 && node.parent().is_some_and(|parent| {
1664 parent.kind() == "type_descriptor"
1665 && parent.child_by_field_name("type") == Some(node)
1666 }));
1667 let offsetof_type_position = node.kind() == "identifier"
1668 && node
1669 .parent()
1670 .filter(|parent| parent.kind() == "argument_list")
1671 .and_then(|arguments| arguments.parent())
1672 .is_some_and(|call| {
1673 call.kind() == "call_expression"
1674 && call
1675 .child_by_field_name("function")
1676 .is_some_and(|function| {
1677 function.kind() == "identifier"
1678 && node_text(function, &body.source) == "offsetof"
1679 })
1680 && call
1681 .child_by_field_name("arguments")
1682 .is_some_and(|arguments| {
1683 argument_children(arguments).next() == Some(node)
1684 })
1685 });
1686 if (type_position || offsetof_type_position)
1687 && let Some(index) = parameters
1688 .iter()
1689 .position(|parameter| parameter == node_text(node, &body.source))
1690 && !found.contains(&index)
1691 {
1692 found.push(index);
1693 }
1694 push_named_children_reversed(node, &mut stack);
1695 }
1696 (!found.is_empty()).then_some(found)
1697}
1698
1699fn macro_replacement_type_parameter(
1700 body: &ParsedReplacementBody,
1701 parameters: &[String],
1702) -> Option<usize> {
1703 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1704 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1705}
1706
1707pub(crate) fn function_macro_invocation_parts(node: Node<'_>) -> Option<(Node<'_>, Vec<Node<'_>>)> {
1711 let (name, arguments) = match node.kind() {
1712 "call_expression" => (
1713 node.child_by_field_name("function")?,
1714 argument_children(node.child_by_field_name("arguments")?).collect(),
1715 ),
1716 "macro_type_specifier" => (
1717 node.child_by_field_name("name")?,
1718 vec![node.child_by_field_name("type")?],
1719 ),
1720 _ => return None,
1721 };
1722 (name.kind() == "identifier").then_some((name, arguments))
1723}
1724
1725pub(crate) fn macro_type_argument_node<'tree>(
1726 node: Node<'tree>,
1727 source: &str,
1728) -> Option<Node<'tree>> {
1729 match node.kind() {
1730 "type_descriptor" => {
1731 let type_child = node
1732 .child_by_field_name("type")
1733 .or_else(|| first_type_child(node))?;
1734 for index in (0..node.named_child_count()).rev() {
1735 let child = node.named_child(index)?;
1736 if child != type_child
1737 && matches!(
1738 child.kind(),
1739 "identifier"
1740 | "type_identifier"
1741 | "qualified_identifier"
1742 | "scoped_type_identifier"
1743 )
1744 {
1745 return Some(child);
1746 }
1747 }
1748 macro_type_argument_node(type_child, source)
1749 }
1750 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1751 node.child_by_field_name("name")
1752 }
1753 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1754 .next_named_sibling()
1755 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1756 .and_then(|error| error.named_child(0))
1757 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1758 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1759 }
1760}
1761
1762fn c_function_macro_argument<'tree>(
1763 node: Node<'tree>,
1764) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1765 let mut current = node;
1766 while let Some(parent) = current.parent() {
1767 if parent.kind() == "argument_list" {
1768 let call = parent.parent().filter(|call| {
1769 call.kind() == "call_expression"
1770 && call.child_by_field_name("arguments") == Some(parent)
1771 && call
1772 .child_by_field_name("function")
1773 .is_some_and(|function| {
1774 function.kind() == "identifier"
1775 && !node_range_contains(function, current)
1776 })
1777 })?;
1778 let mut actuals = argument_children(parent).enumerate();
1779 let (index, argument) = actuals.find(|(_, argument)| {
1780 node_range_contains(*argument, node)
1781 && (argument.start_byte() == current.start_byte()
1782 || argument.end_byte() == current.end_byte())
1783 })?;
1784 return Some((call, index, argument));
1785 }
1786 current = parent;
1787 }
1788 None
1789}
1790
1791fn recognized_c_macro_declarator_binding<'tree>(
1799 statement: Node<'tree>,
1800 source: &str,
1801) -> Option<MacroLocalBinding<'tree>> {
1802 if statement.kind() == "declaration" {
1803 let specifier = statement
1804 .child_by_field_name("type")
1805 .filter(|type_node| type_node.kind() == "macro_type_specifier")?;
1806 let macro_name = specifier
1807 .child_by_field_name("name")
1808 .filter(|name| name.kind() == "identifier")?;
1809 if node_text(macro_name, source) != "g_autoptr" {
1810 return None;
1811 }
1812 let type_node = specifier
1813 .child_by_field_name("type")
1814 .and_then(|descriptor| descriptor.child_by_field_name("type"))?;
1815 let declarator = statement
1816 .child_by_field_name("declarator")
1817 .filter(|declarator| declarator.kind() == "init_declarator")?;
1818 let name_node = declarator
1819 .child_by_field_name("declarator")
1820 .filter(|name| name.kind() == "identifier")?;
1821 declarator.child_by_field_name("value")?;
1822 let name = node_text(name_node, source).trim();
1823 let type_name = node_text(type_node, source).trim();
1824 if name.is_empty() || type_name.is_empty() {
1825 return None;
1826 }
1827 return Some(MacroLocalBinding {
1828 name: name.to_string(),
1829 type_name: type_name.to_string(),
1830 type_node: Some(type_node),
1831 pointer_depth: 1,
1832 proven_unit: None,
1833 });
1834 }
1835 let assignment = match statement.kind() {
1836 "assignment_expression" => statement,
1837 "expression_statement" => {
1838 single_named_child_matching(statement, |child| !child.is_extra())?
1839 }
1840 _ => return None,
1841 };
1842 if assignment.kind() != "assignment_expression" {
1843 return None;
1844 }
1845 let call = assignment.child_by_field_name("left")?;
1846 if call.kind() != "call_expression" {
1847 return None;
1848 }
1849 let function = call.child_by_field_name("function")?;
1850 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1851 return None;
1852 }
1853 let arguments = call.child_by_field_name("arguments")?;
1854 let mut actuals = argument_children(arguments);
1855 let type_node = actuals.next()?;
1856 if actuals.next().is_some()
1857 || !matches!(
1858 type_node.kind(),
1859 "identifier"
1860 | "type_identifier"
1861 | "qualified_identifier"
1862 | "scoped_type_identifier"
1863 | "template_type"
1864 )
1865 {
1866 return None;
1867 }
1868 let name_node = (0..assignment.named_child_count())
1869 .filter_map(|index| assignment.named_child(index))
1870 .filter(|child| child.kind() == "ERROR")
1871 .filter_map(|error| {
1872 (error.named_child_count() == 1)
1873 .then(|| error.named_child(0))
1874 .flatten()
1875 })
1876 .find(|node| node.kind() == "identifier")?;
1877 let name = node_text(name_node, source).trim();
1878 let type_name = node_text(type_node, source).trim();
1879 if name.is_empty() || type_name.is_empty() {
1880 return None;
1881 }
1882 Some(MacroLocalBinding {
1883 name: name.to_string(),
1884 type_name: type_name.to_string(),
1885 type_node: Some(type_node),
1886 pointer_depth: 1,
1887 proven_unit: None,
1888 })
1889}
1890
1891#[derive(Clone, PartialEq, Eq)]
1892pub struct MacroBinding {
1893 source: ProjectFile,
1894 declaration_byte: usize,
1895 definition: MacroDefinition,
1896 exact: bool,
1897}
1898
1899impl MacroBinding {
1900 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1901 Self {
1902 source: source.clone(),
1903 declaration_byte,
1904 definition: MacroDefinition::Unsupported,
1905 exact: false,
1906 }
1907 }
1908
1909 fn is_exact(&self) -> bool {
1910 self.exact
1911 }
1912
1913 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1914 Self {
1915 source: source.clone(),
1916 declaration_byte,
1917 definition: current.definition.clone(),
1918 exact: false,
1919 }
1920 }
1921}
1922
1923type OwningPreprocessorConditionals = Box<[usize]>;
1935
1936#[derive(Clone)]
1937pub enum MacroEvent {
1938 Define {
1939 name: String,
1940 binding: MacroBinding,
1941 byte: usize,
1942 conditionals: OwningPreprocessorConditionals,
1943 },
1944 Undef {
1945 name: String,
1946 byte: usize,
1947 conditionals: OwningPreprocessorConditionals,
1948 },
1949 Include {
1950 targets: Vec<ProjectFile>,
1951 byte: usize,
1952 conditionals: OwningPreprocessorConditionals,
1953 },
1954 Invalidate {
1955 byte: usize,
1956 },
1957}
1958
1959impl MacroEvent {
1960 pub fn byte(&self) -> usize {
1961 match self {
1962 Self::Define { byte, .. }
1963 | Self::Undef { byte, .. }
1964 | Self::Include { byte, .. }
1965 | Self::Invalidate { byte } => *byte,
1966 }
1967 }
1968}
1969
1970#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1971pub enum CallArityEvidence {
1972 Exact(usize),
1973 Unknown,
1974}
1975
1976impl CallArityEvidence {
1977 pub fn exact(self) -> Option<usize> {
1978 match self {
1979 Self::Exact(arity) => Some(arity),
1980 Self::Unknown => None,
1981 }
1982 }
1983
1984 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1985 self.exact().map(|arity| expected.accepts(arity))
1986 }
1987}
1988
1989struct CppAlias {
1990 name: String,
1991 target: String,
1992 namespace: Option<String>,
1993}
1994
1995type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1996
1997#[derive(Debug, Clone, PartialEq, Eq)]
2001pub enum CppTemplateResolutionError {
2002 AliasCycle { alias: CodeUnit },
2004 ArgumentBinding,
2006 Substitution,
2008 PrimarySelection,
2011 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
2014}
2015
2016fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
2019 let mut distinct: Vec<CodeUnit> = Vec::new();
2020 for unit in units {
2021 if !distinct
2022 .iter()
2023 .any(|existing| same_visible_symbol(existing, unit))
2024 {
2025 distinct.push(unit.clone());
2026 }
2027 }
2028 distinct
2029}
2030
2031pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
2034 visibility: Factory,
2035 file: &ProjectFile,
2036 root: Node<'_>,
2037 source: &str,
2038 max_references: usize,
2039 cancelled: impl FnMut() -> bool,
2040) -> MacroLexicalReferences
2041where
2042 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
2043{
2044 crate::graph::macro_lexical::all_references(
2045 visibility,
2046 file,
2047 root,
2048 source,
2049 max_references,
2050 cancelled,
2051 )
2052}
2053
2054impl<'a> VisibilityIndex<'a> {
2055 pub fn data(&self) -> Arc<VisibilityIndexData> {
2056 Arc::clone(&self.data)
2057 }
2058
2059 pub fn from_data(
2060 cpp: &'a dyn CppSource,
2061 token: QueryToken<'a>,
2062 data: Arc<VisibilityIndexData>,
2063 ) -> Self {
2064 Self { cpp, token, data }
2065 }
2066
2067 pub fn cpp(&self) -> &'a dyn CppSource {
2068 self.cpp
2069 }
2070
2071 pub fn token(&self) -> QueryToken<'a> {
2073 self.token
2074 }
2075
2076 pub(crate) fn macro_lexical_index(
2080 &self,
2081 file: &ProjectFile,
2082 root: Node<'_>,
2083 source: &str,
2084 ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
2085 let index = bounded_visibility_memo_value(
2086 &self.macro_lexical_indexes,
2087 &self.reads,
2088 file,
2089 MACRO_LEXICAL_INDEX_CACHE_CAP,
2090 || {
2091 Arc::new(crate::graph::macro_lexical::build_index(
2092 self, file, root, source,
2093 ))
2094 },
2095 );
2096 debug_assert_eq!(
2097 index.source_len,
2098 source.len(),
2099 "macro lexical index for {file:?} was built from a different source"
2100 );
2101 index
2102 }
2103
2104 fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
2110 bounded_visibility_memo_value(
2111 &self.callable_metadata,
2112 &self.reads,
2113 unit,
2114 CALLABLE_METADATA_CACHE_CAP,
2115 || Arc::new(self.cpp.stored_signature_metadata(unit)),
2116 )
2117 }
2118
2119 pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
2121 crate::identity::cpp_callable_linkage_from_metadata(
2122 self.callable_metadata(unit).iter().cloned(),
2123 )
2124 }
2125
2126 pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
2128 brokk_bifrost_core::analyzer::model::first_callable_arity(
2129 self.callable_metadata(unit).iter().cloned(),
2130 )
2131 }
2132
2133 pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
2138 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
2139 self.callable_metadata(unit).iter().cloned(),
2140 )
2141 }
2142
2143 pub fn has_unresolved_include_visible_before(
2151 &self,
2152 file: &ProjectFile,
2153 before_byte: usize,
2154 ) -> bool {
2155 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2156 return false;
2157 };
2158 let facts = self.structured_include_facts(file, prepared.as_ref());
2159 has_unresolved_include_visible_before_in_prepared(
2160 file,
2161 prepared.as_ref(),
2162 &self.cpp.include_target_index(),
2163 &facts,
2164 before_byte,
2165 &|guards: Option<&HashSet<PreprocessorGuard>>| {
2166 self.effective_reference_guards(file, guards)
2167 },
2168 )
2169 }
2170
2171 fn structured_include_facts(
2172 &self,
2173 file: &ProjectFile,
2174 prepared: &PreparedSyntaxTree,
2175 ) -> Arc<[StructuredIncludeFact]> {
2176 visibility_memo_value(
2177 &self.structured_include_fact_cells,
2178 &self.reads,
2179 file.clone(),
2180 || collect_structured_include_facts(prepared),
2181 )
2182 }
2183
2184 #[cfg(any(test, feature = "test-support"))]
2192 pub fn from_visible_files_for_test(
2193 reads: ReadCapability,
2194 cpp: &'a dyn CppSource,
2195 token: QueryToken<'a>,
2196 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2197 ) -> Self {
2198 let visible_source_files_by_root = visible_by_file
2199 .iter()
2200 .map(|(file, visible)| {
2201 (
2202 file.clone(),
2203 visible
2204 .iter()
2205 .map(|unit| unit.source().clone())
2206 .chain(std::iter::once(file.clone()))
2207 .collect(),
2208 )
2209 })
2210 .collect();
2211 let mut global_field_internal_linkage = HashMap::default();
2212 Self {
2213 cpp,
2214 token,
2215 data: Arc::new(VisibilityIndexData {
2216 reads,
2217 visible_by_identifier: build_visible_identifier_index(
2218 &CppGraphSource::from_source(cpp, token),
2219 &visible_by_file,
2220 &visible_source_files_by_root,
2221 &mut global_field_internal_linkage,
2222 ),
2223 global_field_internal_linkage,
2224 visible_by_file,
2225 visible_source_files_by_root,
2226 alias_cells: VisibilityMemo::default(),
2227 visible_parser_alias_name_sets: VisibilityMemo::default(),
2228 parser_alias_target_matches: VisibilityMemo::default(),
2229 ordinary_type_import_cells: VisibilityMemo::default(),
2230 project_using_index: ReplayingMemo::new(OnceLock::new()),
2231 namespace_alias_sites: VisibilityMemo::default(),
2232 callable_reference_specs: VisibilityMemo::default(),
2233 structured_include_fact_cells: VisibilityMemo::default(),
2234 include_activation_cells: VisibilityMemo::default(),
2235 compile_proven_guard_cells: VisibilityMemo::default(),
2236 include_path_admission_cells: VisibilityMemo::default(),
2237 conditional_include_projection_cells: VisibilityMemo::default(),
2238 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2239 conditional_include_projection_state_count: AtomicUsize::new(0),
2240 conditional_include_target_state_count: AtomicUsize::new(0),
2241 include_activation_build_count: AtomicUsize::new(0),
2242 using_donor_activation_count: AtomicUsize::new(0),
2243 using_namespace_lookup_count: AtomicUsize::new(0),
2244 using_name_candidate_inspection_count: AtomicUsize::new(0),
2245 callable_reference_spec_build_count: AtomicUsize::new(0),
2246 alias_source_parse_counts: Mutex::new(HashMap::default()),
2247 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2248 parser_alias_fallback_calls: AtomicUsize::new(0),
2249 parser_alias_fallback_files: AtomicUsize::new(0),
2250 parser_alias_source_parses: AtomicUsize::new(0),
2251 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2252 field_type_facts: VisibilityMemo::default(),
2253 structured_alias_targets: VisibilityMemo::default(),
2254 callable_comparables: VisibilityMemo::default(),
2255 class_definition_agreements: VisibilityMemo::default(),
2256 callable_metadata: ReplayingMemo::new(Mutex::new(HashMap::default())),
2257 comparable_name_declarations: VisibilityMemo::default(),
2258 indexed_structural_class_scopes: VisibilityMemo::default(),
2259 indexed_enclosing_owner_scopes: VisibilityMemo::default(),
2260 precise_parent_cache: VisibilityMemo::default(),
2261 c_tag_kind_cache: VisibilityMemo::default(),
2262 c_tag_complete_definition_cache: VisibilityMemo::default(),
2263 macro_event_cells: VisibilityMemo::default(),
2264 macro_event_name_sets: VisibilityMemo::default(),
2265 macro_include_protection_cells: IncludeProtectionMemo::default(),
2266 macro_environment_checkpoints: VisibilityMemo::default(),
2267 macro_replacements: VisibilityMemo::default(),
2268 macro_local_binding_templates: VisibilityMemo::default(),
2269 macro_lexical_templates: VisibilityMemo::default(),
2270 macro_lexical_indexes: ReplayingMemo::new(Mutex::new(HashMap::default())),
2271 macro_replacement_bodies: VisibilityMemo::default(),
2272 macro_type_parameters: VisibilityMemo::default(),
2273 callable_parameter_macro_arities: VisibilityMemo::default(),
2274 macro_replacement_parse_count: AtomicUsize::new(0),
2275 macro_event_application_count: AtomicUsize::new(0),
2276 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2277 macro_environment_copy_count: AtomicUsize::new(0),
2278 macro_environment_request_count: AtomicUsize::new(0),
2279 cpp_template_metadata: HashMap::default(),
2280 cpp_template_families: HashMap::default(),
2281 qualified_candidate_inspections: AtomicUsize::new(0),
2282 target_preserving_type_resolution_count: AtomicUsize::new(0),
2283 visibility_identifier_lookup_count: 0,
2284 visibility_identifier_batch_count: 0,
2285 }),
2286 }
2287 }
2288
2289 fn cpp_source(&self) -> CppGraphSource<'a> {
2296 CppGraphSource::from_source(self.cpp, self.token)
2297 }
2298
2299 pub fn build(
2300 reads: ReadCapability,
2301 cpp: &'a dyn CppSource,
2302 token: QueryToken<'a>,
2303 analyzer: &CppGraphSource<'_>,
2304 roots: &HashSet<ProjectFile>,
2305 ) -> Self {
2306 Self::build_with_cancellation(reads, cpp, token, analyzer, roots, None)
2307 }
2308
2309 pub fn build_with_cancellation(
2310 reads: ReadCapability,
2311 cpp: &'a dyn CppSource,
2312 token: QueryToken<'a>,
2313 analyzer: &CppGraphSource<'_>,
2314 roots: &HashSet<ProjectFile>,
2315 cancellation: Option<&CancellationToken>,
2316 ) -> Self {
2317 let visibility_started = Instant::now();
2318 let include_targets = cpp.include_target_index();
2319 let includes_started = Instant::now();
2320 let mut include_graph = IncludeGraph::default();
2321 for root in roots {
2322 include_graph.extend_with(root, cancellation, &mut |file| {
2323 cpp.canonical_include_paths(token, file)
2324 .unwrap_or_default()
2325 .into_iter()
2326 .flat_map(|include| {
2327 resolve_include_targets_with_index(file, &include, &include_targets)
2328 })
2329 .collect()
2330 });
2331 }
2332 let include_elapsed = includes_started.elapsed();
2333 let include_file_count = include_graph.files().count();
2334 let visible_source_files_by_root = roots
2335 .iter()
2336 .map(|root| {
2337 (
2338 root.clone(),
2339 include_graph.reachable_files(root, cancellation),
2340 )
2341 })
2342 .collect::<HashMap<_, _>>();
2343 let mut visibility_stats = BoundedVisibilityStats::default();
2344 let mut visible_by_file = build_bounded_visible_declarations(
2345 cpp,
2346 token,
2347 analyzer,
2348 roots,
2349 &visible_source_files_by_root,
2350 cancellation,
2351 &mut visibility_stats,
2352 );
2353 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2354 eprintln!(
2355 "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={}",
2356 visibility_started.elapsed().as_millis(),
2357 include_elapsed.as_millis(),
2358 include_file_count,
2359 visibility_stats.rounds,
2360 visibility_stats.root_names,
2361 visibility_stats.identifier_lookups,
2362 visibility_stats.identifier_batches,
2363 visibility_stats.candidate_units,
2364 visibility_stats.candidate_sources,
2365 visibility_stats.declaration_reads,
2366 visibility_stats.declaration_units,
2367 visibility_stats.selected_units,
2368 visibility_stats.dependency_source_names,
2369 visibility_stats.dependency_names,
2370 visibility_stats.lookup_elapsed.as_millis(),
2371 visibility_stats.declaration_elapsed.as_millis(),
2372 visibility_stats.dependency_source_elapsed.as_millis(),
2373 );
2374 }
2375 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2376 let finalize_started = Instant::now();
2377 if report_stats {
2378 eprintln!(
2379 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2380 visible_by_file.len(),
2381 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2382 );
2383 }
2384 let owner_started = Instant::now();
2385 if report_stats {
2386 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2387 }
2388 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2389 if report_stats {
2390 eprintln!(
2391 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2392 owner_stats.unseen_owners,
2393 owner_stats.definition_lookups,
2394 owner_stats.admitted,
2395 owner_started.elapsed().as_millis(),
2396 );
2397 }
2398 let mut global_field_internal_linkage = HashMap::default();
2399 let identifier_started = Instant::now();
2400 if report_stats {
2401 eprintln!(
2402 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2403 );
2404 }
2405 let visible_by_identifier = build_visible_identifier_index(
2406 analyzer,
2407 &visible_by_file,
2408 &visible_source_files_by_root,
2409 &mut global_field_internal_linkage,
2410 );
2411 if report_stats {
2412 eprintln!(
2413 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2414 visible_by_identifier.len(),
2415 visible_by_identifier
2416 .values()
2417 .map(HashMap::len)
2418 .sum::<usize>(),
2419 visible_by_identifier
2420 .values()
2421 .flat_map(HashMap::values)
2422 .map(Vec::len)
2423 .sum::<usize>(),
2424 identifier_started.elapsed().as_millis(),
2425 );
2426 }
2427 let mut cpp_template_metadata = HashMap::default();
2428 let metadata_started = Instant::now();
2429 let mut template_classes = 0usize;
2430 if report_stats {
2431 eprintln!(
2432 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2433 );
2434 }
2435 for unit in visible_by_file
2436 .values()
2437 .flatten()
2438 .filter(|unit| unit.is_class())
2439 {
2440 template_classes += 1;
2441 if cpp_template_metadata.contains_key(unit) {
2442 continue;
2443 }
2444 if let Some(metadata) = cpp.template_metadata(unit) {
2445 cpp_template_metadata.insert(unit.clone(), metadata);
2446 }
2447 }
2448 if report_stats {
2449 eprintln!(
2450 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2451 template_classes,
2452 cpp_template_metadata.len(),
2453 metadata_started.elapsed().as_millis(),
2454 );
2455 }
2456 let families_started = Instant::now();
2457 if report_stats {
2458 eprintln!(
2459 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2460 );
2461 }
2462 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2463 for (unit, metadata) in &cpp_template_metadata {
2464 cpp_template_families
2465 .entry(metadata.primary_fq_name.clone())
2466 .or_default()
2467 .push(unit.clone());
2468 }
2469 for family in cpp_template_families.values_mut() {
2478 sort_lookup_units(family);
2479 }
2480 if report_stats {
2481 eprintln!(
2482 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2483 cpp_template_families.len(),
2484 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2485 families_started.elapsed().as_millis(),
2486 );
2487 eprintln!(
2488 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2489 visible_by_file.len(),
2490 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2491 finalize_started.elapsed().as_millis(),
2492 visibility_started.elapsed().as_millis(),
2493 );
2494 }
2495 Self {
2496 cpp,
2497 token,
2498 data: Arc::new(VisibilityIndexData {
2499 reads,
2500 visible_by_file,
2501 visible_by_identifier,
2502 global_field_internal_linkage,
2503 visible_source_files_by_root,
2504 alias_cells: VisibilityMemo::default(),
2505 visible_parser_alias_name_sets: VisibilityMemo::default(),
2506 parser_alias_target_matches: VisibilityMemo::default(),
2507 ordinary_type_import_cells: VisibilityMemo::default(),
2508 project_using_index: ReplayingMemo::new(OnceLock::new()),
2509 namespace_alias_sites: VisibilityMemo::default(),
2510 callable_reference_specs: VisibilityMemo::default(),
2511 structured_include_fact_cells: VisibilityMemo::default(),
2512 include_activation_cells: VisibilityMemo::default(),
2513 compile_proven_guard_cells: VisibilityMemo::default(),
2514 include_path_admission_cells: VisibilityMemo::default(),
2515 conditional_include_projection_cells: VisibilityMemo::default(),
2516 #[cfg(any(test, feature = "test-support"))]
2517 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2518 #[cfg(any(test, feature = "test-support"))]
2519 conditional_include_projection_state_count: AtomicUsize::new(0),
2520 #[cfg(any(test, feature = "test-support"))]
2521 conditional_include_target_state_count: AtomicUsize::new(0),
2522 #[cfg(any(test, feature = "test-support"))]
2523 include_activation_build_count: AtomicUsize::new(0),
2524 #[cfg(any(test, feature = "test-support"))]
2525 using_donor_activation_count: AtomicUsize::new(0),
2526 #[cfg(any(test, feature = "test-support"))]
2527 using_namespace_lookup_count: AtomicUsize::new(0),
2528 #[cfg(any(test, feature = "test-support"))]
2529 using_name_candidate_inspection_count: AtomicUsize::new(0),
2530 #[cfg(any(test, feature = "test-support"))]
2531 callable_reference_spec_build_count: AtomicUsize::new(0),
2532 #[cfg(any(test, feature = "test-support"))]
2533 alias_source_parse_counts: Mutex::new(HashMap::default()),
2534 #[cfg(any(test, feature = "test-support"))]
2535 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2536 parser_alias_fallback_calls: AtomicUsize::new(0),
2537 parser_alias_fallback_files: AtomicUsize::new(0),
2538 parser_alias_source_parses: AtomicUsize::new(0),
2539 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2540 field_type_facts: VisibilityMemo::default(),
2541 structured_alias_targets: VisibilityMemo::default(),
2542 callable_comparables: VisibilityMemo::default(),
2543 class_definition_agreements: VisibilityMemo::default(),
2544 callable_metadata: ReplayingMemo::new(Mutex::new(HashMap::default())),
2545 comparable_name_declarations: VisibilityMemo::default(),
2546 indexed_structural_class_scopes: VisibilityMemo::default(),
2547 indexed_enclosing_owner_scopes: VisibilityMemo::default(),
2548 precise_parent_cache: VisibilityMemo::default(),
2549 c_tag_kind_cache: VisibilityMemo::default(),
2550 c_tag_complete_definition_cache: VisibilityMemo::default(),
2551 macro_event_cells: VisibilityMemo::default(),
2552 macro_event_name_sets: VisibilityMemo::default(),
2553 macro_include_protection_cells: IncludeProtectionMemo::default(),
2554 macro_environment_checkpoints: VisibilityMemo::default(),
2555 macro_replacements: VisibilityMemo::default(),
2556 macro_local_binding_templates: VisibilityMemo::default(),
2557 macro_lexical_templates: VisibilityMemo::default(),
2558 macro_lexical_indexes: ReplayingMemo::new(Mutex::new(HashMap::default())),
2559 macro_replacement_bodies: VisibilityMemo::default(),
2560 macro_type_parameters: VisibilityMemo::default(),
2561 callable_parameter_macro_arities: VisibilityMemo::default(),
2562 #[cfg(any(test, feature = "test-support"))]
2563 macro_replacement_parse_count: AtomicUsize::new(0),
2564 #[cfg(any(test, feature = "test-support"))]
2565 macro_event_application_count: AtomicUsize::new(0),
2566 #[cfg(any(test, feature = "test-support"))]
2567 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2568 #[cfg(any(test, feature = "test-support"))]
2569 macro_environment_copy_count: AtomicUsize::new(0),
2570 #[cfg(any(test, feature = "test-support"))]
2571 macro_environment_request_count: AtomicUsize::new(0),
2572 cpp_template_metadata,
2573 cpp_template_families,
2574 #[cfg(any(test, feature = "test-support"))]
2575 qualified_candidate_inspections: AtomicUsize::new(0),
2576 #[cfg(any(test, feature = "test-support"))]
2577 target_preserving_type_resolution_count: AtomicUsize::new(0),
2578 #[cfg(any(test, feature = "test-support"))]
2579 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2580 #[cfg(any(test, feature = "test-support"))]
2581 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2582 }),
2583 }
2584 }
2585
2586 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2587 if file == target.source() {
2588 return true;
2589 }
2590 if self.global_field_has_internal_linkage(target) {
2591 return self
2592 .visible_source_files_by_root
2593 .get(file)
2594 .is_some_and(|sources| sources.contains(target.source()));
2595 }
2596 self.visible_by_file
2597 .get(file)
2598 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2599 }
2600
2601 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2602 self.global_field_internal_linkage
2603 .get(unit)
2604 .copied()
2605 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2606 }
2607
2608 pub fn call_arity_evidence(
2609 &self,
2610 file: &ProjectFile,
2611 call: Node<'_>,
2612 source: &str,
2613 ) -> CallArityEvidence {
2614 self.call_arity_evidence_at(file, call, source, call.start_byte())
2615 }
2616
2617 pub fn call_arity_evidence_at(
2625 &self,
2626 file: &ProjectFile,
2627 call: Node<'_>,
2628 source: &str,
2629 environment_byte: usize,
2630 ) -> CallArityEvidence {
2631 let Some(arguments) = call
2632 .child_by_field_name("arguments")
2633 .or_else(|| call.child_by_field_name("parameters"))
2634 .or_else(|| call.child_by_field_name("value"))
2635 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2636 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2637 else {
2638 return CallArityEvidence::Exact(0);
2639 };
2640 let recovered_c_keyword_arguments =
2641 recovered_c_keyword_argument_count(file, call, arguments, source);
2642 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2643 let arguments = argument_children(arguments)
2644 .flat_map(|argument| {
2645 recovered_c_new_expression_arguments(argument, c_semantics)
2646 .map(Vec::from)
2647 .unwrap_or_else(|| vec![argument])
2648 })
2649 .collect::<Vec<_>>();
2650 if arguments
2651 .iter()
2652 .all(|argument| !argument_shape_may_change_arity(*argument))
2653 {
2654 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2655 }
2656 let environment = self.macro_environment(file, environment_byte);
2657 let mut stack = Vec::new();
2658 let mut total = recovered_c_keyword_arguments;
2659 for argument in arguments {
2660 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2661 return CallArityEvidence::Unknown;
2662 }
2663 let CallArityEvidence::Exact(spread) =
2664 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2665 else {
2666 return CallArityEvidence::Unknown;
2667 };
2668 total += spread;
2669 }
2670 CallArityEvidence::Exact(total)
2671 }
2672
2673 fn argument_arity_evidence(
2674 &self,
2675 argument: Node<'_>,
2676 source: &str,
2677 environment: &MacroEnvironment,
2678 stack: &mut Vec<(ProjectFile, usize)>,
2679 ) -> CallArityEvidence {
2680 let (name, invocation_arguments, function_like) = match argument.kind() {
2681 "identifier" => (node_text(argument, source), None, false),
2682 "call_expression" => {
2683 let Some(function) = argument.child_by_field_name("function") else {
2684 return CallArityEvidence::Exact(1);
2685 };
2686 if function.kind() != "identifier" {
2687 return CallArityEvidence::Exact(1);
2688 }
2689 let Some(arguments) = argument.child_by_field_name("arguments") else {
2690 return CallArityEvidence::Exact(1);
2691 };
2692 (node_text(function, source), Some(arguments), true)
2693 }
2694 _ => return CallArityEvidence::Exact(1),
2695 };
2696 let Some(binding) = environment.binding(name) else {
2697 return if environment.unknown_names {
2698 CallArityEvidence::Unknown
2699 } else {
2700 CallArityEvidence::Exact(1)
2701 };
2702 };
2703 if !binding.is_exact() {
2704 return CallArityEvidence::Unknown;
2705 }
2706 match (&binding.definition, invocation_arguments, function_like) {
2707 (MacroDefinition::Object { replacement }, None, false) => self
2708 .replacement_arity_evidence(
2709 replacement,
2710 &[],
2711 &[],
2712 source,
2713 environment,
2714 stack,
2715 binding,
2716 ),
2717 (
2718 MacroDefinition::Function {
2719 parameters,
2720 replacement,
2721 },
2722 Some(arguments),
2723 true,
2724 ) => {
2725 let actuals = argument_children(arguments).collect::<Vec<_>>();
2726 if actuals.len() != parameters.len() {
2727 CallArityEvidence::Unknown
2728 } else {
2729 self.replacement_arity_evidence(
2730 replacement,
2731 parameters,
2732 &actuals,
2733 source,
2734 environment,
2735 stack,
2736 binding,
2737 )
2738 }
2739 }
2740 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2741 _ => CallArityEvidence::Unknown,
2742 }
2743 }
2744
2745 #[allow(clippy::too_many_arguments)]
2746 fn replacement_arity_evidence(
2747 &self,
2748 replacement: &str,
2749 parameters: &[String],
2750 actuals: &[Node<'_>],
2751 actual_source: &str,
2752 environment: &MacroEnvironment,
2753 stack: &mut Vec<(ProjectFile, usize)>,
2754 binding: &MacroBinding,
2755 ) -> CallArityEvidence {
2756 let identity = (binding.source.clone(), binding.declaration_byte);
2757 if stack.contains(&identity) || replacement.trim().is_empty() {
2758 return CallArityEvidence::Unknown;
2759 }
2760 stack.push(identity);
2761 let parsed = self.parsed_macro_replacement(binding, replacement);
2762 let evidence = (|| {
2763 let ParsedMacroReplacement::Parsed {
2764 source: sentinel,
2765 tree,
2766 } = parsed.as_ref()
2767 else {
2768 return None;
2769 };
2770 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2771 let arguments = call.child_by_field_name("arguments")?;
2772 let mut total = 0usize;
2773 for argument in argument_children(arguments) {
2774 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2775 return None;
2776 }
2777 if argument.kind() == "identifier"
2778 && let Some(parameter_index) = parameters
2779 .iter()
2780 .position(|parameter| parameter == node_text(argument, sentinel))
2781 {
2782 if !macro_expansion_shape_is_safe(
2783 actuals[parameter_index],
2784 actual_source,
2785 &[],
2786 environment,
2787 ) {
2788 return None;
2789 }
2790 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2791 actuals[parameter_index],
2792 actual_source,
2793 environment,
2794 stack,
2795 ) else {
2796 return None;
2797 };
2798 total += spread;
2799 continue;
2800 }
2801 let CallArityEvidence::Exact(spread) =
2802 self.argument_arity_evidence(argument, sentinel, environment, stack)
2803 else {
2804 return None;
2805 };
2806 total += spread;
2807 }
2808 Some(CallArityEvidence::Exact(total))
2809 })()
2810 .unwrap_or(CallArityEvidence::Unknown);
2811 stack.pop();
2812 evidence
2813 }
2814
2815 fn parsed_macro_replacement(
2816 &self,
2817 binding: &MacroBinding,
2818 replacement: &str,
2819 ) -> Arc<ParsedMacroReplacement> {
2820 visibility_memo_value(
2821 &self.macro_replacements,
2822 &self.reads,
2823 (binding.source.clone(), binding.declaration_byte),
2824 || {
2825 #[cfg(any(test, feature = "test-support"))]
2826 self.macro_replacement_parse_count
2827 .fetch_add(1, Ordering::Relaxed);
2828 let parsed = parse_sentinel(
2829 "void __bifrost_macro_arity() { __bifrost_macro_call(",
2830 SentinelInput::replacement(replacement),
2831 "); }",
2832 );
2833 let parsed = if parsed.tree.root_node().has_error() {
2834 ParsedMacroReplacement::Unsupported
2835 } else {
2836 ParsedMacroReplacement::Parsed {
2837 source: parsed.source,
2838 tree: parsed.tree,
2839 }
2840 };
2841 Arc::new(parsed)
2842 },
2843 )
2844 }
2845
2846 pub fn function_macro_local_binding<'tree>(
2856 &self,
2857 file: &ProjectFile,
2858 statement: Node<'tree>,
2859 source: &str,
2860 ) -> Option<MacroLocalBinding<'tree>> {
2861 if !is_c_source_file(file) {
2862 return None;
2863 }
2864 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2865 return Some(binding);
2866 }
2867 let call = match statement.kind() {
2868 "expression_statement" => {
2869 single_named_child_matching(statement, |child| !child.is_extra())?
2870 }
2871 _ => statement,
2872 };
2873 let (function, actuals) = function_macro_invocation_parts(call)?;
2874 let environment = self.macro_environment(file, call.start_byte());
2875 let function_name = node_text(function, source);
2876 let binding = environment.binding(function_name)?;
2877 let MacroDefinition::Function {
2878 parameters,
2879 replacement,
2880 } = &binding.definition
2881 else {
2882 return None;
2883 };
2884 if actuals.len() != parameters.len() {
2885 return None;
2886 }
2887 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2888 let (type_name, type_node) = match &template.declared_type {
2889 MacroLocalBindingTypeTemplate::Parameter(index) => {
2890 let actual = *actuals.get(*index)?;
2891 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2892 return None;
2893 }
2894 let type_node = macro_type_argument_node(actual, source).unwrap_or(actual);
2895 (
2896 node_text(actual, source).trim().to_string(),
2897 Some(type_node),
2898 )
2899 }
2900 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2901 };
2902 if type_name.is_empty() {
2903 return None;
2904 }
2905 Some(MacroLocalBinding {
2906 name: template.name.clone(),
2907 type_name,
2908 type_node,
2909 pointer_depth: template.pointer_depth,
2910 proven_unit: None,
2911 })
2912 }
2913
2914 pub fn function_macro_container_binding<'tree>(
2923 &self,
2924 analyzer: &CppGraphSource<'_>,
2925 file: &ProjectFile,
2926 assignment: Node<'tree>,
2927 source: &str,
2928 ) -> Option<MacroLocalBinding<'tree>> {
2929 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2930 return None;
2931 }
2932 let name_node = assignment.child_by_field_name("left")?;
2933 if name_node.kind() != "identifier" {
2934 return None;
2935 }
2936 let call = assignment.child_by_field_name("right")?;
2937 if call.kind() != "call_expression" {
2938 return None;
2939 }
2940 let function = call.child_by_field_name("function")?;
2941 if function.kind() != "identifier" {
2942 return None;
2943 }
2944 let arguments = call.child_by_field_name("arguments")?;
2945 let actuals = argument_children(arguments).collect::<Vec<_>>();
2946 let function_name = node_text(function, source);
2947 let environment = self.macro_environment(file, call.start_byte());
2948 let binding = environment.binding(function_name)?;
2949 if !binding.is_exact() {
2950 return None;
2951 }
2952 let MacroDefinition::Function {
2953 parameters,
2954 replacement,
2955 } = &binding.definition
2956 else {
2957 return None;
2958 };
2959 if actuals.len() != parameters.len() {
2960 return None;
2961 }
2962 let body = self.parsed_macro_replacement_body(
2963 &(binding.source.clone(), binding.declaration_byte),
2964 parameters,
2965 replacement,
2966 )?;
2967 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2968 let type_argument = *actuals.get(type_parameter)?;
2969 let type_node = macro_type_argument_node(type_argument, source)?;
2970 let type_name = node_text(type_node, source).trim().to_string();
2971 if type_name.is_empty() {
2972 return None;
2973 }
2974 let explicit_tag = match node_text(type_argument, source) {
2975 "struct" => Some(CppCTagKind::Struct),
2976 "union" => Some(CppCTagKind::Union),
2977 _ => None,
2978 };
2979 let resolved_type = if let Some(tag) = explicit_tag {
2980 let candidates = self
2981 .visible_identifier_candidates(file, &type_name)
2982 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2983 .collect::<Vec<_>>();
2984 self.resolve_type_candidates(
2985 analyzer,
2986 file,
2987 &candidates,
2988 TypeCandidateResolution::Canonical,
2989 )
2990 .ok()
2991 } else {
2992 self.resolve_type_node_result(file, type_node, source)
2993 .ok()
2994 .flatten()
2995 };
2996 let proven_unit = resolved_type?;
2997 Some(MacroLocalBinding {
2998 name: node_text(name_node, source).to_string(),
2999 type_name,
3000 type_node: Some(type_node),
3001 pointer_depth: 1,
3002 proven_unit: Some(proven_unit),
3003 })
3004 }
3005
3006 pub fn macro_local_binding_at<'tree>(
3009 &self,
3010 file: &ProjectFile,
3011 root: Node<'tree>,
3012 source: &str,
3013 start_byte: usize,
3014 end_byte: usize,
3015 ) -> Option<MacroLocalBinding<'tree>> {
3016 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
3017 }
3018
3019 pub fn macro_lexical_binding(
3023 &self,
3024 file: &ProjectFile,
3025 root: Node<'_>,
3026 source: &str,
3027 start_byte: usize,
3028 end_byte: usize,
3029 ) -> Option<MacroLexicalBinding> {
3030 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
3031 }
3032
3033 pub fn macro_lexical_references(
3038 &self,
3039 file: &ProjectFile,
3040 root: Node<'_>,
3041 source: &str,
3042 max_references: usize,
3043 cancelled: impl FnMut() -> bool,
3044 ) -> MacroLexicalReferences {
3045 crate::graph::macro_lexical::all_references(
3046 || self,
3047 file,
3048 root,
3049 source,
3050 max_references,
3051 cancelled,
3052 )
3053 }
3054
3055 pub(crate) fn function_macro_binding_at(
3060 &self,
3061 file: &ProjectFile,
3062 name: &str,
3063 before_byte: usize,
3064 ) -> Option<(ProjectFile, usize)> {
3065 let environment = self.macro_environment(file, before_byte);
3066 let binding = environment.binding(name)?;
3067 if binding.is_exact()
3068 && matches!(
3069 binding.definition,
3070 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
3071 )
3072 {
3073 return Some((binding.source.clone(), binding.declaration_byte));
3074 }
3075 if binding.source != *file {
3081 return None;
3082 }
3083 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3084 let root = prepared.tree().root_node();
3085 let source = prepared.source();
3086 let reference = root.descendant_for_byte_range(
3087 before_byte,
3088 before_byte.saturating_add(1).min(source.len()),
3089 )?;
3090 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
3091 let cell = self.macro_event_cell(file);
3092 let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
3093 let event = events
3094 .iter()
3095 .find(|event| event.byte() == binding.declaration_byte)?;
3096 let MacroEvent::Define {
3097 name: defined_name,
3098 binding: definition,
3099 conditionals,
3100 ..
3101 } = event
3102 else {
3103 return None;
3104 };
3105 if defined_name != name
3106 || conditionals.is_empty()
3107 || !conditionals
3108 .iter()
3109 .all(|condition| reference_conditions.contains(condition))
3110 || !matches!(
3111 definition.definition,
3112 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
3113 )
3114 {
3115 return None;
3116 }
3117 Some((definition.source.clone(), definition.declaration_byte))
3118 }
3119
3120 pub fn function_macro_type_argument<'tree>(
3127 &self,
3128 file: &ProjectFile,
3129 node: Node<'tree>,
3130 source: &str,
3131 ) -> Option<Node<'tree>> {
3132 if !is_c_source_file(file) {
3133 return None;
3134 }
3135 let (call, argument_index, argument) = c_function_macro_argument(node)?;
3136 let function = call.child_by_field_name("function")?;
3137 let function_name = node_text(function, source);
3138 if function_name.is_empty() {
3139 return None;
3140 }
3141 if !self.file_defines_macro_name(file, function_name)
3142 && !self
3143 .visible_identifier_candidates(file, function_name)
3144 .any(|candidate| candidate.is_macro())
3145 {
3146 return None;
3147 }
3148 let environment = self.macro_environment(file, call.start_byte());
3149 let binding = environment.binding(function_name)?;
3150 if !binding.is_exact() {
3151 return None;
3152 }
3153 let (parameters, replacement, variadic) = match &binding.definition {
3154 MacroDefinition::Function {
3155 parameters,
3156 replacement,
3157 } => (parameters, replacement, false),
3158 MacroDefinition::VariadicFunction {
3159 parameters,
3160 replacement,
3161 } => (parameters, replacement, true),
3162 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3163 };
3164 let actuals = call
3165 .child_by_field_name("arguments")
3166 .map(argument_children)
3167 .into_iter()
3168 .flatten()
3169 .collect::<Vec<_>>();
3170 let arity_matches = if variadic {
3171 actuals.len() >= parameters.len()
3172 } else {
3173 actuals.len() == parameters.len()
3174 };
3175 let type_parameters = self.macro_type_parameter_indices(
3176 &(binding.source.clone(), binding.declaration_byte),
3177 parameters,
3178 replacement,
3179 )?;
3180 if !arity_matches || !type_parameters.contains(&argument_index) {
3181 return None;
3182 }
3183 let type_node = macro_type_argument_node(argument, source)?;
3184 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
3185 return None;
3186 }
3187 Some(type_node)
3188 }
3189
3190 fn macro_local_binding_template(
3191 &self,
3192 binding: &MacroBinding,
3193 parameters: &[String],
3194 replacement: &str,
3195 ) -> Option<Arc<MacroLocalBindingTemplate>> {
3196 visibility_memo_value(
3197 &self.macro_local_binding_templates,
3198 &self.reads,
3199 (binding.source.clone(), binding.declaration_byte),
3200 || {
3201 let key = (binding.source.clone(), binding.declaration_byte);
3202 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3203 let sentinel = body.source.as_str();
3204 let statements = body.statements()?;
3205 let declaration =
3206 single_named_child_matching(statements, |child| !child.is_extra())?;
3207 if declaration.kind() != "declaration" {
3208 return None;
3209 }
3210 let type_node = declaration
3211 .child_by_field_name("type")
3212 .or_else(|| first_type_child(declaration))?;
3213 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3214 let mut cursor = declaration.walk();
3215 declaration.named_children(&mut cursor).find_map(|child| {
3216 if child.kind() == "init_declarator" {
3217 child.child_by_field_name("declarator")
3218 } else {
3219 is_declarator_node(child).then_some(child)
3220 }
3221 })
3222 })?;
3223 let name = extract_variable_name(declarator, sentinel)?;
3224 let pointer_depth =
3225 declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3226 let type_text = node_text(type_node, sentinel).trim();
3227 let declared_type = parameters
3228 .iter()
3229 .position(|parameter| parameter == type_text)
3230 .map(MacroLocalBindingTypeTemplate::Parameter)
3231 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3232 Some(Arc::new(MacroLocalBindingTemplate {
3233 name,
3234 declared_type,
3235 pointer_depth,
3236 }))
3237 },
3238 )
3239 }
3240
3241 pub fn function_macro_replacement_body(
3248 &self,
3249 file: &ProjectFile,
3250 definition: Node<'_>,
3251 source: &str,
3252 ) -> Option<Arc<ParsedReplacementBody>> {
3253 debug_assert_eq!(definition.kind(), "preproc_function_def");
3254 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3255 MacroDefinition::Function {
3256 parameters,
3257 replacement,
3258 }
3259 | MacroDefinition::VariadicFunction {
3260 parameters,
3261 replacement,
3262 } => (parameters, replacement),
3263 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3264 };
3265 self.parsed_macro_replacement_body(
3266 &(file.clone(), definition.start_byte()),
3267 ¶meters,
3268 &replacement,
3269 )
3270 }
3271
3272 fn parsed_macro_replacement_body(
3280 &self,
3281 key: &(ProjectFile, usize),
3282 parameters: &[String],
3283 replacement: &str,
3284 ) -> Option<Arc<ParsedReplacementBody>> {
3285 visibility_memo_value(
3286 &self.macro_replacement_bodies,
3287 &self.reads,
3288 key.clone(),
3289 || {
3290 if replacement.trim().is_empty() {
3291 return None;
3292 }
3293 let (source, tree, original_offsets) =
3294 Self::parse_macro_replacement_body(replacement, parameters)?;
3295 let body = ParsedReplacementBody {
3296 source,
3297 tree,
3298 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3299 parameters: parameters.to_vec(),
3300 original_offsets,
3301 };
3302 body.statements()?;
3303 if body.expands_variadic_arguments() {
3304 return None;
3305 }
3306 Some(Arc::new(body))
3307 },
3308 )
3309 }
3310
3311 fn parse_macro_replacement_body(
3319 replacement: &str,
3320 parameters: &[String],
3321 ) -> Option<(String, Tree, MacroOriginMap)> {
3322 let parsed = parse_sentinel(
3323 MACRO_BODY_SENTINEL_PREFIX,
3324 SentinelInput::replacement(replacement),
3325 MACRO_BODY_SENTINEL_SUFFIX,
3326 );
3327 let normalized_end = parsed.source.len() - MACRO_BODY_SENTINEL_SUFFIX.len() - 1;
3328 let normalized = &parsed.source[MACRO_BODY_SENTINEL_PREFIX.len()..normalized_end];
3329 let normalized_offsets = &parsed.original_offsets;
3330 let source = parsed.source.as_str();
3331 let tree = &parsed.tree;
3332 if !tree.root_node().has_error() {
3333 return Some((
3334 parsed.source,
3335 parsed.tree,
3336 parsed.original_offsets.into_boxed_slice(),
3337 ));
3338 }
3339
3340 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3341 let mut insertion_points = Vec::new();
3342 let mut stack = vec![tree.root_node()];
3343 while let Some(node) = stack.pop() {
3344 if matches!(
3345 node.kind(),
3346 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3347 ) && parameters
3348 .iter()
3349 .any(|parameter| parameter.as_str() == node_text(node, source))
3350 && Self::macro_formal_needs_statement_separator(node)
3351 {
3352 let point = node.end_byte().saturating_sub(body_offset);
3353 if point <= normalized.len() && !insertion_points.contains(&point) {
3354 insertion_points.push(point);
3355 }
3356 }
3357 push_named_children_reversed(node, &mut stack);
3358 }
3359 if insertion_points.is_empty() {
3360 return None;
3361 }
3362 insertion_points.sort_unstable();
3363 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3364 let mut original_offsets =
3365 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3366 let mut next_insertion = 0;
3367 for (index, byte) in normalized.bytes().enumerate() {
3368 recovered.push(byte);
3369 original_offsets.push(normalized_offsets[index].clone());
3370 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3371 recovered.push(b';');
3372 let boundary = normalized_offsets[index].end;
3373 original_offsets.push(boundary..boundary);
3374 next_insertion += 1;
3375 }
3376 }
3377 original_offsets.push(replacement.len()..replacement.len());
3378 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3379 let parsed = parse_sentinel(
3380 MACRO_BODY_SENTINEL_PREFIX,
3381 SentinelInput {
3382 source: recovered,
3383 original_offsets,
3384 },
3385 MACRO_BODY_SENTINEL_SUFFIX,
3386 );
3387 let (source, tree, original_offsets) =
3388 (parsed.source, parsed.tree, parsed.original_offsets);
3389 if tree.root_node().has_error() {
3390 return None;
3391 }
3392 Some((source, tree, original_offsets.into_boxed_slice()))
3393 }
3394
3395 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3400 let mut current = node;
3401 let mut crossed_recovery = false;
3402 while let Some(parent) = current.parent() {
3403 if parent.is_error() {
3404 crossed_recovery = true;
3405 current = parent;
3406 continue;
3407 }
3408 if matches!(
3409 parent.kind(),
3410 "call_expression"
3411 | "argument_list"
3412 | "field_expression"
3413 | "binary_expression"
3414 | "unary_expression"
3415 | "assignment_expression"
3416 | "conditional_expression"
3417 | "parenthesized_expression"
3418 | "subscript_expression"
3419 ) {
3420 return false;
3421 }
3422 if parent.kind() == "expression_statement" {
3423 return single_named_child_matching(parent, |child| !child.is_extra()).is_some();
3424 }
3425 if parent.kind() == "compound_statement" && current == node {
3426 return true;
3427 }
3428 if matches!(
3429 parent.kind(),
3430 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3431 ) {
3432 return crossed_recovery
3433 || parent.child_by_field_name("consequence") == Some(current);
3434 }
3435 if matches!(parent.kind(), "declaration" | "init_declarator") {
3436 return false;
3437 }
3438 current = parent;
3439 }
3440 false
3441 }
3442
3443 fn macro_type_parameter_indices(
3451 &self,
3452 key: &(ProjectFile, usize),
3453 parameters: &[String],
3454 replacement: &str,
3455 ) -> Option<Arc<[usize]>> {
3456 visibility_memo_value(
3457 &self.macro_type_parameters,
3458 &self.reads,
3459 key.clone(),
3460 || {
3461 #[cfg(any(test, feature = "test-support"))]
3462 self.macro_replacement_parse_count
3463 .fetch_add(1, Ordering::Relaxed);
3464 if replacement.trim().is_empty() {
3465 return None;
3466 }
3467 let parsed = parse_sentinel(
3471 MACRO_BODY_SENTINEL_PREFIX,
3472 SentinelInput::replacement(replacement),
3473 MACRO_BODY_SENTINEL_SUFFIX,
3474 );
3475 let (source, tree, original_offsets) =
3476 (parsed.source, parsed.tree, parsed.original_offsets);
3477 let body = ParsedReplacementBody {
3478 source,
3479 tree,
3480 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3481 parameters: parameters.to_vec(),
3482 original_offsets: original_offsets.into_boxed_slice(),
3483 };
3484 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3485 },
3486 )
3487 }
3488
3489 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3490 let replacement = if node.kind() == "preproc_function_def" {
3491 function_macro_replacement_span(node, source)
3492 .and_then(|span| source.get(span))
3493 .map(str::to_owned)
3494 .or_else(|| {
3495 node.child_by_field_name("value")
3496 .map(|value| node_text(value, source).to_string())
3497 })
3498 .unwrap_or_default()
3499 } else {
3500 node.child_by_field_name("value")
3501 .map(|value| node_text(value, source).to_string())
3502 .unwrap_or_default()
3503 };
3504 if node.kind() == "preproc_def" {
3505 return MacroDefinition::Object { replacement };
3506 }
3507 let Some(parameters) = node.child_by_field_name("parameters") else {
3508 return MacroDefinition::Unsupported;
3509 };
3510 let variadic = (0..parameters.child_count()).any(|index| {
3511 parameters
3512 .child(index)
3513 .is_some_and(|child| child.kind() == "...")
3514 });
3515 let parameters = (0..parameters.named_child_count())
3516 .filter_map(|index| parameters.named_child(index))
3517 .map(|parameter| node_text(parameter, source).to_string())
3518 .collect::<Vec<_>>();
3519 if variadic {
3520 MacroDefinition::VariadicFunction {
3521 parameters,
3522 replacement,
3523 }
3524 } else {
3525 MacroDefinition::Function {
3526 parameters,
3527 replacement,
3528 }
3529 }
3530 }
3531
3532 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3533 visibility_memo_value(&self.macro_event_cells, &self.reads, file.clone(), || {
3534 Arc::new(VisibilityCell::new(self.reads.clone()))
3535 })
3536 }
3537
3538 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3539 let names = visibility_memo_value(
3540 &self.macro_event_name_sets,
3541 &self.reads,
3542 file.clone(),
3543 || {
3544 let cell = self.macro_event_cell(file);
3545 let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
3546 Arc::new(
3547 events
3548 .iter()
3549 .filter_map(|event| match event {
3550 MacroEvent::Define { name, .. } => Some(name.clone()),
3551 MacroEvent::Undef { .. }
3552 | MacroEvent::Include { .. }
3553 | MacroEvent::Invalidate { .. } => None,
3554 })
3555 .collect(),
3556 )
3557 },
3558 );
3559 names.contains(name)
3560 }
3561
3562 fn macro_environment_checkpoint_cell(
3563 &self,
3564 file: &ProjectFile,
3565 ) -> MacroEnvironmentCheckpointCell {
3566 visibility_memo_value(
3567 &self.macro_environment_checkpoints,
3568 &self.reads,
3569 file.clone(),
3570 || Arc::new(VisibilityCell::new(self.reads.clone())),
3571 )
3572 }
3573
3574 pub fn macro_environment(
3576 &self,
3577 file: &ProjectFile,
3578 before_byte: usize,
3579 ) -> Arc<MacroEnvironment> {
3580 #[cfg(any(test, feature = "test-support"))]
3581 self.macro_environment_request_count
3582 .fetch_add(1, Ordering::Relaxed);
3583 let cell = self.macro_event_cell(file);
3584 let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
3585 let frontier = events.partition_point(|event| event.byte() < before_byte);
3586 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3587 let checkpoints = checkpoint_cell
3588 .get_or_init(|| Arc::new(self.build_macro_environment_checkpoints(file, &events)));
3589 let checkpoint = checkpoints.at_or_before(frontier);
3590 if checkpoint.frontier == frontier {
3591 return Arc::clone(&checkpoint.environment);
3592 }
3593 #[cfg(any(test, feature = "test-support"))]
3594 self.macro_environment_copy_count
3595 .fetch_add(1, Ordering::Relaxed);
3596 let mut environment = checkpoint.environment.as_ref().clone();
3597 let mut include_stack = HashSet::from_iter([file.clone()]);
3598 for event in &events[checkpoint.frontier..frontier] {
3599 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3600 }
3601 Arc::new(environment)
3602 }
3603
3604 fn build_macro_environment_checkpoints(
3607 &self,
3608 file: &ProjectFile,
3609 events: &[MacroEvent],
3610 ) -> MacroEnvironmentCheckpoints {
3611 #[cfg(any(test, feature = "test-support"))]
3612 self.macro_environment_checkpoint_build_count
3613 .fetch_add(1, Ordering::Relaxed);
3614 let mut environment = MacroEnvironment {
3619 build_proven_defines: self
3620 .compile_proven_guards(file)
3621 .iter()
3622 .filter_map(|guard| match guard {
3623 PreprocessorGuard::Defined(name) => Some(name.clone()),
3624 _ => None,
3625 })
3626 .collect(),
3627 ..MacroEnvironment::default()
3628 };
3629 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3630 frontier: 0,
3631 environment: Arc::new(environment.clone()),
3632 }];
3633 let checkpoint_stride = events
3640 .len()
3641 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3642 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3643 let mut include_stack = HashSet::from_iter([file.clone()]);
3644 for (index, event) in events.iter().enumerate() {
3645 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3646 let frontier = index + 1;
3647 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3648 checkpoints.push(MacroEnvironmentCheckpoint {
3649 frontier,
3650 environment: Arc::new(environment.clone()),
3651 });
3652 }
3653 }
3654 MacroEnvironmentCheckpoints { checkpoints }
3655 }
3656
3657 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3666 self.macro_environment(file, before_byte)
3667 .binding(name)
3668 .is_some()
3669 }
3670
3671 pub fn macro_name_may_be_bound_at(
3672 &self,
3673 file: &ProjectFile,
3674 name: &str,
3675 before_byte: usize,
3676 ) -> bool {
3677 self.macro_environment(file, before_byte).may_bind(name)
3678 }
3679
3680 pub fn macro_binding_matches_target_at(
3684 &self,
3685 analyzer: &CppGraphSource<'_>,
3686 file: &ProjectFile,
3687 name: &str,
3688 before_byte: usize,
3689 target: &CodeUnit,
3690 ) -> bool {
3691 let ranges = analyzer.ranges(target);
3692 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3693 self.macro_binding_matches_target_declaration_at(
3694 file,
3695 name,
3696 before_byte,
3697 target.source(),
3698 &declaration_bytes,
3699 )
3700 }
3701
3702 pub(crate) fn macro_declaration_bytes(
3707 &self,
3708 target: &CodeUnit,
3709 ranges: &[Range],
3710 ) -> Vec<usize> {
3711 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3712 return Vec::new();
3713 };
3714 ranges
3715 .iter()
3716 .filter_map(|range| {
3717 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3718 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3719 node = node.parent()?;
3720 }
3721 Some(node.start_byte())
3722 })
3723 .collect()
3724 }
3725
3726 pub(crate) fn macro_binding_matches_target_declaration_at(
3727 &self,
3728 file: &ProjectFile,
3729 name: &str,
3730 before_byte: usize,
3731 target_source: &ProjectFile,
3732 target_declaration_bytes: &[usize],
3733 ) -> bool {
3734 let environment = self.macro_environment(file, before_byte);
3735 let Some(binding) = environment.binding(name) else {
3736 return false;
3737 };
3738 if binding.definition == MacroDefinition::Unsupported {
3739 return false;
3740 }
3741 if binding.source != *target_source {
3745 return false;
3746 }
3747 target_declaration_bytes.contains(&binding.declaration_byte)
3748 }
3749
3750 pub fn resolve_ordinary_macro_reference(
3757 &self,
3758 analyzer: &CppGraphSource<'_>,
3759 file: &ProjectFile,
3760 node: Node<'_>,
3761 source: &str,
3762 ) -> OrdinaryMacroReferenceResolution {
3763 if !is_ordinary_macro_reference_node(node) {
3764 return OrdinaryMacroReferenceResolution::Missing;
3765 }
3766 let name = node_text(node, source);
3767 if name.is_empty() {
3768 return OrdinaryMacroReferenceResolution::Missing;
3769 }
3770 let visible = self
3771 .visible_identifier_candidates(file, name)
3772 .filter(|candidate| candidate.is_macro())
3773 .cloned()
3774 .collect::<Vec<_>>();
3775 let mut exact = Vec::new();
3776 for candidate in &visible {
3777 if self.macro_binding_matches_target_at(
3778 analyzer,
3779 file,
3780 name,
3781 node.start_byte(),
3782 candidate,
3783 ) && !exact
3784 .iter()
3785 .any(|existing| same_visible_symbol(existing, candidate))
3786 {
3787 exact.push(candidate.clone());
3788 }
3789 }
3790 match exact.len() {
3791 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3792 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3793 0 if !visible.is_empty()
3794 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3795 {
3796 OrdinaryMacroReferenceResolution::Ambiguous
3797 }
3798 0 => OrdinaryMacroReferenceResolution::Missing,
3799 }
3800 }
3801
3802 pub fn recovered_c_reference_ranges(
3810 &self,
3811 file: &ProjectFile,
3812 root: Node<'_>,
3813 source: &str,
3814 limit: usize,
3815 ) -> RecoveredCReferenceRanges {
3816 if !is_c_source_file(file) {
3817 return RecoveredCReferenceRanges::Complete(Vec::new());
3818 }
3819 let mut ranges = Vec::new();
3820 let mut seen = HashSet::default();
3821 let mut stack = vec![(root, root.is_error())];
3822 while let Some((node, inside_error)) = stack.pop() {
3823 let inside_error =
3828 inside_error || node.is_error() || node.kind() == "macro_type_specifier";
3829 if node.kind() == "preproc_arg" {
3830 let macro_value_kind = node.parent().and_then(|parent| {
3835 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3836 });
3837 if matches!(
3838 macro_value_kind,
3839 Some("preproc_def" | "preproc_function_def")
3840 ) {
3841 let name = node_text(node, source);
3842 if !name.is_empty()
3843 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3844 && !push_recovered_c_range(
3845 &mut ranges,
3846 &mut seen,
3847 node.start_byte(),
3848 node.end_byte(),
3849 node,
3850 limit,
3851 )
3852 {
3853 return RecoveredCReferenceRanges::LimitExceeded;
3854 }
3855 }
3856 if macro_value_kind == Some("preproc_def") {
3857 for reference in object_macro_replacement_type_references(node, source) {
3858 for range in reference.component_ranges {
3859 let visible = self
3860 .visible_identifier_candidates(file, &source[range.clone()])
3861 .any(|candidate| candidate.is_class() || candidate.is_module());
3862 if visible
3863 && !push_recovered_c_range(
3864 &mut ranges,
3865 &mut seen,
3866 range.start,
3867 range.end,
3868 node,
3869 limit,
3870 )
3871 {
3872 return RecoveredCReferenceRanges::LimitExceeded;
3873 }
3874 }
3875 }
3876 }
3877 }
3878 if inside_error
3879 && recovered_c_reference_node(self, file, node, source)
3880 && !push_recovered_c_range(
3881 &mut ranges,
3882 &mut seen,
3883 node.start_byte(),
3884 node.end_byte(),
3885 node,
3886 limit,
3887 )
3888 {
3889 return RecoveredCReferenceRanges::LimitExceeded;
3890 }
3891 let mut cursor = node.walk();
3892 for child in node.named_children(&mut cursor) {
3893 stack.push((child, inside_error));
3894 }
3895 }
3896 ranges.sort_unstable();
3897 RecoveredCReferenceRanges::Complete(ranges)
3898 }
3899
3900 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3906 self.visible_identifier_candidates(file, target.identifier())
3907 .filter(|candidate| candidate.is_macro())
3908 .any(|candidate| {
3909 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3910 })
3911 }
3912
3913 pub fn object_macro_replacement_at(
3914 &self,
3915 file: &ProjectFile,
3916 name: &str,
3917 before_byte: usize,
3918 ) -> Option<String> {
3919 let environment = self.macro_environment(file, before_byte);
3920 let binding = environment.binding(name)?;
3921 if !binding.exact {
3922 return None;
3923 }
3924 match &binding.definition {
3925 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3926 MacroDefinition::Function { .. }
3927 | MacroDefinition::VariadicFunction { .. }
3928 | MacroDefinition::Unsupported => None,
3929 }
3930 }
3931
3932 fn apply_macro_events(
3933 &self,
3934 file: &ProjectFile,
3935 environment: &mut MacroEnvironment,
3936 include_stack: &mut HashSet<ProjectFile>,
3937 ) {
3938 if !include_stack.insert(file.clone()) {
3939 return;
3940 }
3941 if self.cpp.prepared_syntax(self.token, file).is_none() {
3942 environment.mark_unknown_names(file, 0);
3943 include_stack.remove(file);
3944 return;
3945 }
3946 match self.macro_include_protection(file) {
3947 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3948 Some(binding) if binding.is_exact() => {
3949 include_stack.remove(file);
3950 return;
3951 }
3952 Some(_) | None if environment.unknown_names => {
3953 let mut ambiguous_seen = HashSet::default();
3954 self.mark_macro_events_ambiguous(
3955 file,
3956 environment,
3957 &mut ambiguous_seen,
3958 file,
3959 0,
3960 );
3961 include_stack.remove(file);
3962 return;
3963 }
3964 Some(_) => {
3965 let mut ambiguous_seen = HashSet::default();
3966 self.mark_macro_events_ambiguous(
3967 file,
3968 environment,
3969 &mut ambiguous_seen,
3970 file,
3971 0,
3972 );
3973 include_stack.remove(file);
3974 return;
3975 }
3976 None => {}
3977 },
3978 MacroIncludeProtection::PragmaOnce => {
3979 if !environment.applied_pragma_once_files.insert(file.clone()) {
3980 include_stack.remove(file);
3981 return;
3982 }
3983 if environment.maybe_applied_pragma_once_files.remove(file) {
3984 let mut ambiguous_seen = HashSet::default();
3989 environment.applied_pragma_once_files.remove(file);
3990 self.mark_macro_events_ambiguous(
3991 file,
3992 environment,
3993 &mut ambiguous_seen,
3994 file,
3995 0,
3996 );
3997 environment.maybe_applied_pragma_once_files.remove(file);
3998 environment.applied_pragma_once_files.insert(file.clone());
3999 include_stack.remove(file);
4000 return;
4001 }
4002 }
4003 MacroIncludeProtection::None => {}
4004 }
4005 let cell = self.macro_event_cell(file);
4006 let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
4007 for event in events.iter() {
4008 self.apply_macro_event(file, event, environment, include_stack);
4009 }
4010 include_stack.remove(file);
4011 }
4012
4013 fn apply_macro_event(
4014 &self,
4015 file: &ProjectFile,
4016 event: &MacroEvent,
4017 environment: &mut MacroEnvironment,
4018 include_stack: &mut HashSet<ProjectFile>,
4019 ) {
4020 #[cfg(any(test, feature = "test-support"))]
4021 self.macro_event_application_count
4022 .fetch_add(1, Ordering::Relaxed);
4023 match event {
4024 MacroEvent::Define {
4025 name,
4026 binding,
4027 conditionals,
4028 byte,
4029 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
4030 Some(true) => environment.insert(name.clone(), binding.clone()),
4031 Some(false) => {}
4032 None => Self::merge_conditional_macro_definition(
4033 environment,
4034 name,
4035 binding,
4036 file,
4037 *byte,
4038 ),
4039 },
4040 MacroEvent::Undef {
4041 name,
4042 conditionals,
4043 byte,
4044 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
4045 Some(true) => environment.remove(name),
4046 Some(false) => {}
4047 None => {
4048 if environment.binding(name).is_some() {
4049 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
4050 }
4051 }
4052 },
4053 MacroEvent::Include {
4054 targets,
4055 conditionals,
4056 byte,
4057 } => {
4058 let condition =
4059 self.macro_event_condition_value(file, *byte, environment, conditionals);
4060 if condition == Some(false) {
4061 return;
4062 }
4063 if targets.is_empty() {
4064 environment.mark_unknown_names(file, *byte);
4065 return;
4066 }
4067 if condition.is_none() || targets.len() > 1 {
4068 let mut ambiguous_seen = HashSet::default();
4069 for target in targets {
4070 self.mark_macro_events_ambiguous(
4071 target,
4072 environment,
4073 &mut ambiguous_seen,
4074 file,
4075 *byte,
4076 );
4077 }
4078 } else if let Some(target) = targets.first() {
4079 self.apply_macro_events(target, environment, include_stack);
4080 }
4081 }
4082 MacroEvent::Invalidate { byte } => {
4083 for binding in environment.bindings.values_mut() {
4084 *binding = MacroBinding::uncertain_from(binding, file, *byte);
4085 }
4086 }
4087 }
4088 }
4089
4090 fn macro_event_condition_value(
4101 &self,
4102 file: &ProjectFile,
4103 event_byte: usize,
4104 environment: &MacroEnvironment,
4105 conditionals: &OwningPreprocessorConditionals,
4106 ) -> Option<bool> {
4107 if conditionals.is_empty() {
4108 return Some(true);
4109 }
4110 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4111 let source = prepared.source();
4112 let root = prepared.tree().root_node();
4113 let descendant = root.descendant_for_byte_range(
4114 event_byte,
4115 event_byte.saturating_add(1).min(source.len()),
4116 )?;
4117 let mut unknown = false;
4118 let mut current = descendant.parent();
4119 while let Some(conditional) = current {
4120 if matches!(
4121 conditional.kind(),
4122 "preproc_if" | "preproc_ifdef" | "preproc_elif"
4123 ) && conditionals.contains(&conditional.start_byte())
4124 {
4125 match self.preprocessor_conditional_branch_value(
4126 conditional,
4127 descendant,
4128 source,
4129 environment,
4130 ) {
4131 Some(true) => {}
4132 Some(false) => return Some(false),
4133 None => unknown = true,
4134 }
4135 }
4136 current = conditional.parent();
4137 }
4138 (!unknown).then_some(true)
4139 }
4140
4141 fn preprocessor_conditional_branch_value(
4142 &self,
4143 conditional: Node<'_>,
4144 descendant: Node<'_>,
4145 source: &str,
4146 environment: &MacroEnvironment,
4147 ) -> Option<bool> {
4148 let value = match conditional.kind() {
4149 "preproc_ifdef" => {
4150 let name = conditional.child_by_field_name("name")?;
4151 let defined = self.macro_name_defined_value(environment, node_text(name, source));
4152 match conditional.child(0)?.kind() {
4153 "#ifdef" => defined,
4154 "#ifndef" => defined.map(|defined| !defined),
4155 _ => None,
4156 }
4157 }
4158 "preproc_if" | "preproc_elif" => conditional
4159 .child_by_field_name("condition")
4160 .and_then(|condition| {
4161 self.preprocessor_integer_value(
4162 condition,
4163 source,
4164 environment,
4165 &mut Vec::new(),
4166 0,
4167 )
4168 })
4169 .map(|value| value != 0),
4170 _ => unreachable!(),
4171 };
4172 match preprocessor_branch_of_descendant(
4173 conditional,
4174 descendant,
4175 &mut PreprocessorContextCache::default(),
4176 ) {
4177 PreprocessorBranch::Consequence => value,
4178 PreprocessorBranch::Structured(_) | PreprocessorBranch::DisplacedElse => {
4179 value.map(|value| !value)
4180 }
4181 PreprocessorBranch::Unreadable => None,
4182 }
4183 }
4184
4185 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4186 if environment.known_undefined_names.contains(name) {
4187 return Some(false);
4188 }
4189 if let Some(binding) = environment.binding(name) {
4190 return binding.is_exact().then_some(true);
4191 }
4192 environment
4193 .build_proven_defines
4194 .contains(name)
4195 .then_some(true)
4196 }
4197
4198 fn preprocessor_integer_value(
4199 &self,
4200 expression: Node<'_>,
4201 source: &str,
4202 environment: &MacroEnvironment,
4203 expansion_stack: &mut Vec<(ProjectFile, usize)>,
4204 depth: usize,
4205 ) -> Option<i128> {
4206 if depth >= 64 {
4209 return None;
4210 }
4211 match expression.kind() {
4212 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4213 "identifier" | "type_identifier" => {
4214 let binding = environment.binding(node_text(expression, source))?;
4215 if !binding.is_exact() {
4216 return None;
4217 }
4218 let MacroDefinition::Object { replacement } = &binding.definition else {
4219 return None;
4220 };
4221 let identity = (binding.source.clone(), binding.declaration_byte);
4222 if expansion_stack.contains(&identity) {
4223 return None;
4224 }
4225 expansion_stack.push(identity);
4226 let parsed = self.parsed_macro_replacement(binding, replacement);
4227 let value = match parsed.as_ref() {
4228 ParsedMacroReplacement::Parsed {
4229 source: replacement_source,
4230 tree,
4231 } => first_descendant_of_kind(tree.root_node(), "call_expression")
4232 .and_then(|call| call.child_by_field_name("arguments"))
4233 .and_then(|arguments| argument_children(arguments).next())
4234 .and_then(|argument| {
4235 self.preprocessor_integer_value(
4236 argument,
4237 replacement_source,
4238 environment,
4239 expansion_stack,
4240 depth + 1,
4241 )
4242 }),
4243 ParsedMacroReplacement::Unsupported => None,
4244 };
4245 expansion_stack.pop();
4246 value
4247 }
4248 "preproc_defined" => {
4249 let mut cursor = expression.walk();
4250 let name = expression
4251 .named_children(&mut cursor)
4252 .find(|child| child.kind() == "identifier")?;
4253 self.macro_name_defined_value(environment, node_text(name, source))
4254 .map(i128::from)
4255 }
4256 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4257 self.preprocessor_integer_value(
4258 child,
4259 source,
4260 environment,
4261 expansion_stack,
4262 depth + 1,
4263 )
4264 }),
4265 "unary_expression" => {
4266 let operator = expression.child_by_field_name("operator")?.kind();
4267 let argument = expression.child_by_field_name("argument")?;
4268 let value = self.preprocessor_integer_value(
4269 argument,
4270 source,
4271 environment,
4272 expansion_stack,
4273 depth + 1,
4274 )?;
4275 match operator {
4276 "+" => Some(value),
4277 "-" => value.checked_neg(),
4278 "!" => Some(i128::from(value == 0)),
4279 "~" => Some(!value),
4280 _ => None,
4281 }
4282 }
4283 "binary_expression" => {
4284 let left = self.preprocessor_integer_value(
4285 expression.child_by_field_name("left")?,
4286 source,
4287 environment,
4288 expansion_stack,
4289 depth + 1,
4290 )?;
4291 let right = self.preprocessor_integer_value(
4292 expression.child_by_field_name("right")?,
4293 source,
4294 environment,
4295 expansion_stack,
4296 depth + 1,
4297 )?;
4298 match expression.child_by_field_name("operator")?.kind() {
4299 "+" => left.checked_add(right),
4300 "-" => left.checked_sub(right),
4301 "*" => left.checked_mul(right),
4302 "/" => left.checked_div(right),
4303 "%" => left.checked_rem(right),
4304 "<<" => u32::try_from(right)
4305 .ok()
4306 .and_then(|shift| left.checked_shl(shift)),
4307 ">>" => u32::try_from(right)
4308 .ok()
4309 .and_then(|shift| left.checked_shr(shift)),
4310 "<" => Some(i128::from(left < right)),
4311 "<=" => Some(i128::from(left <= right)),
4312 ">" => Some(i128::from(left > right)),
4313 ">=" => Some(i128::from(left >= right)),
4314 "==" => Some(i128::from(left == right)),
4315 "!=" => Some(i128::from(left != right)),
4316 "&" => Some(left & right),
4317 "|" => Some(left | right),
4318 "^" => Some(left ^ right),
4319 "&&" => Some(i128::from(left != 0 && right != 0)),
4320 "||" => Some(i128::from(left != 0 || right != 0)),
4321 _ => None,
4322 }
4323 }
4324 _ => None,
4325 }
4326 }
4327
4328 fn mark_macro_events_ambiguous(
4329 &self,
4330 file: &ProjectFile,
4331 environment: &mut MacroEnvironment,
4332 include_stack: &mut HashSet<ProjectFile>,
4333 conditional_file: &ProjectFile,
4334 conditional_byte: usize,
4335 ) {
4336 if !include_stack.insert(file.clone()) {
4337 return;
4338 }
4339 if self.cpp.prepared_syntax(self.token, file).is_none() {
4340 environment.mark_unknown_names(conditional_file, conditional_byte);
4341 return;
4342 }
4343 match self.macro_include_protection(file) {
4344 MacroIncludeProtection::MacroGuard(guard) => {
4345 if environment
4346 .binding(&guard)
4347 .is_some_and(MacroBinding::is_exact)
4348 {
4349 return;
4350 }
4351 }
4352 MacroIncludeProtection::PragmaOnce => {
4353 if environment.applied_pragma_once_files.contains(file) {
4354 return;
4355 }
4356 environment
4357 .maybe_applied_pragma_once_files
4358 .insert(file.clone());
4359 }
4360 MacroIncludeProtection::None => {}
4361 }
4362 let cell = self.macro_event_cell(file);
4363 let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
4364 for event in events.iter() {
4365 #[cfg(any(test, feature = "test-support"))]
4366 self.macro_event_application_count
4367 .fetch_add(1, Ordering::Relaxed);
4368 match event {
4369 MacroEvent::Define { name, binding, .. } => {
4370 Self::merge_conditional_macro_definition(
4371 environment,
4372 name,
4373 binding,
4374 conditional_file,
4375 conditional_byte,
4376 );
4377 }
4378 MacroEvent::Undef { name, .. } => {
4379 if environment.binding(name).is_some() {
4380 environment.insert(
4381 name.clone(),
4382 MacroBinding::ambiguous(conditional_file, conditional_byte),
4383 );
4384 } else {
4385 environment.remove_known_undefined(name);
4386 }
4387 }
4388 MacroEvent::Include { targets, .. } => {
4389 if targets.is_empty() {
4390 environment.mark_unknown_names(conditional_file, conditional_byte);
4391 continue;
4392 }
4393 for target in targets {
4394 self.mark_macro_events_ambiguous(
4395 target,
4396 environment,
4397 include_stack,
4398 conditional_file,
4399 conditional_byte,
4400 );
4401 }
4402 }
4403 MacroEvent::Invalidate { .. } => {
4404 for binding in environment.bindings.values_mut() {
4405 *binding = MacroBinding::uncertain_from(
4406 binding,
4407 conditional_file,
4408 conditional_byte,
4409 );
4410 }
4411 }
4412 }
4413 }
4414 }
4415
4416 fn merge_conditional_macro_definition(
4417 environment: &mut MacroEnvironment,
4418 name: &str,
4419 possible_binding: &MacroBinding,
4420 conditional_file: &ProjectFile,
4421 conditional_byte: usize,
4422 ) {
4423 if environment.binding(name).is_some_and(|current| {
4428 current.definition != MacroDefinition::Unsupported
4429 && current.definition == possible_binding.definition
4430 }) {
4431 return;
4432 }
4433 environment.insert(
4434 name.to_string(),
4435 MacroBinding::ambiguous(conditional_file, conditional_byte),
4436 );
4437 }
4438
4439 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4440 let value = self
4441 .macro_include_protection_cells
4442 .read(&self.reads.memo_attribution(), |cells, capture| {
4443 let cell = cells
4444 .lock()
4445 .expect("C++ include protection cache poisoned")
4446 .entry(file.clone())
4447 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4448 .clone();
4449 Ok::<_, std::convert::Infallible>(cell.get_or_build_pool_independent(|| {
4450 capture.build(|| {
4451 self.cpp.prepared_syntax(self.token, file).map_or(
4452 MacroIncludeProtection::None,
4453 |prepared| {
4454 top_level_macro_include_protection(
4455 prepared.tree().root_node(),
4456 prepared.source(),
4457 )
4458 },
4459 )
4460 })
4461 }))
4462 })
4463 .expect("infallible include protection build");
4464 (*value).clone()
4465 }
4466
4467 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4468 collect_macro_events(self.cpp, self.token, file)
4469 }
4470
4471 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4472 visibility_memo_value(
4473 &self.ordinary_type_import_cells,
4474 &self.reads,
4475 file.clone(),
4476 || Arc::new(EffectiveUsingIndex::new(file.clone(), self.reads.clone())),
4477 )
4478 }
4479
4480 pub fn project_using_index(
4481 &self,
4482 build: impl FnOnce() -> ProjectUsingIndex,
4483 ) -> Arc<ProjectUsingIndex> {
4484 visibility_once_value(&self.project_using_index, &self.reads, || Arc::new(build()))
4485 }
4486
4487 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4488 let mut files = self
4489 .visible_source_files_by_root
4490 .values()
4491 .flatten()
4492 .cloned()
4493 .collect::<HashSet<_>>()
4494 .into_iter()
4495 .collect::<Vec<_>>();
4496 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4497 files
4498 }
4499
4500 pub(super) fn namespace_alias_cell(
4502 &self,
4503 root: &ProjectFile,
4504 ) -> super::namespace_alias::NamespaceAliasCell {
4505 visibility_memo_value(
4506 &self.namespace_alias_sites,
4507 &self.reads,
4508 root.clone(),
4509 || Arc::new(VisibilityCell::new(self.reads.clone())),
4510 )
4511 }
4512
4513 pub(super) fn visible_source_files(&self, root: &ProjectFile) -> Vec<ProjectFile> {
4515 self.visible_source_files_by_root.get(root).map_or_else(
4516 || vec![root.clone()],
4517 |files| {
4518 files
4519 .iter()
4520 .chain([root])
4521 .cloned()
4522 .collect::<HashSet<_>>()
4523 .into_iter()
4524 .collect()
4525 },
4526 )
4527 }
4528
4529 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4530 self.visible_source_files_by_root
4531 .get(root)
4532 .is_some_and(|files| files.contains(source))
4533 }
4534
4535 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4536 try_visibility_memo_value(
4537 &self.visible_parser_alias_name_sets,
4538 &self.reads,
4539 file.clone(),
4540 || {
4541 let mut names = HashSet::default();
4542 let visible_files = self
4543 .visible_source_files_by_root
4544 .get(file)
4545 .cloned()
4546 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4547 for visible_file in visible_files {
4548 let aliases = self
4549 .canonical_alias_cell(self.cpp, &visible_file)
4550 .ok_or(())?;
4551 for alias in aliases.get().expect("published canonical aliases").iter() {
4552 names.insert(alias.name.clone());
4553 }
4554 }
4555 #[cfg(any(test, feature = "test-support"))]
4556 self.visible_parser_alias_name_set_build_count
4557 .fetch_add(1, Ordering::Relaxed);
4558 Ok::<_, ()>(Arc::new(names))
4559 },
4560 )
4561 .ok()
4562 .is_some_and(|names| names.contains(name))
4563 }
4564
4565 pub fn parser_alias_name_may_resolve_to_target(
4566 &self,
4567 file: &ProjectFile,
4568 alias_name: &str,
4569 target: &CodeUnit,
4570 ) -> bool {
4571 let started = std::time::Instant::now();
4572 self.parser_alias_fallback_calls
4573 .fetch_add(1, Ordering::Relaxed);
4574 let matched = visibility_memo_value(
4575 &self.parser_alias_target_matches,
4576 &self.reads,
4577 (
4578 file.clone(),
4579 alias_name.to_string(),
4580 logical_symbol_key(target),
4581 ),
4582 || {
4583 any_visible_source_file_matches(
4584 &self.visible_source_files_by_root,
4585 file,
4586 |visible_file| {
4587 self.parser_alias_fallback_files
4588 .fetch_add(1, Ordering::Relaxed);
4589 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4590 },
4591 )
4592 },
4593 );
4594 self.parser_alias_fallback_elapsed_micros.fetch_add(
4595 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4596 Ordering::Relaxed,
4597 );
4598 matched
4599 }
4600
4601 fn callable_arities_for_target(
4602 &self,
4603 analyzer: &CppGraphSource<'_>,
4604 cpp: &dyn CppSource,
4605 file: &ProjectFile,
4606 prepared: &PreparedSyntaxTree,
4607 spec: &TargetSpec,
4608 ) -> Vec<ActivatedCallableArity> {
4609 let Some(signature) = spec.target.signature() else {
4610 return Vec::new();
4611 };
4612 let Some(candidates) = self
4613 .visible_by_identifier
4614 .get(file)
4615 .and_then(|by_name| by_name.get(&spec.member_name))
4616 else {
4617 return Vec::new();
4618 };
4619 let differing_candidates = candidates
4620 .iter()
4621 .filter(|candidate| {
4622 candidate.is_function()
4623 && candidate.fq_name() == spec.target.fq_name()
4624 && candidate.signature() == Some(signature)
4625 })
4626 .filter_map(|candidate| {
4627 analyzer
4628 .signature_metadata(candidate)
4629 .into_iter()
4630 .find_map(|metadata| metadata.callable_arity())
4631 .filter(|arity| Some(*arity) != spec.callable_arity)
4632 .map(|arity| (candidate, arity))
4633 })
4634 .collect::<Vec<_>>();
4635 if differing_candidates.is_empty() {
4636 return Vec::new();
4637 }
4638 let mut arities = Vec::with_capacity(differing_candidates.len());
4639 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
4642 self.effective_reference_guards(file, guards)
4643 };
4644 let reference = CallableReferenceContext {
4645 effective_guards: &effective_guards,
4646 file_guards: OnceCell::new(),
4647 file,
4648 position: None,
4649 };
4650 for (candidate, candidate_arity) in differing_candidates {
4651 let declaration_activation =
4652 callable_declaration_activation_in_file(analyzer, candidate, &reference);
4653 let Some(declaration_activation) = declaration_activation else {
4654 continue;
4655 };
4656 let activation_byte = if candidate.source() == file {
4657 Some(declaration_activation)
4658 } else {
4659 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4660 };
4661 if let Some(activation_byte) = activation_byte {
4662 arities.push(ActivatedCallableArity {
4663 activation_byte,
4664 arity: candidate_arity,
4665 });
4666 }
4667 }
4668 arities
4669 }
4670
4671 fn callable_parameter_macro_arity(
4672 &self,
4673 target: &CodeUnit,
4674 parameter_types: Option<&[String]>,
4675 ) -> Option<CallableArity> {
4676 let parameter_types = parameter_types?;
4677 let [macro_name] = parameter_types else {
4678 return None;
4679 };
4680 if macro_name.is_empty()
4681 || !macro_name
4682 .chars()
4683 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4684 {
4685 return None;
4686 }
4687 let cache_key = (target.source().clone(), macro_name.clone());
4688 visibility_memo_value(
4689 &self.callable_parameter_macro_arities,
4690 &self.reads,
4691 cache_key,
4692 || {
4693 let mut visible_files = HashSet::default();
4694 collect_include_closure(
4695 &self.cpp_source(),
4696 &self.cpp.include_target_index(),
4697 target.source(),
4698 &mut visible_files,
4699 None,
4700 );
4701 let mut arities = Vec::new();
4702 for visible_file in visible_files {
4703 let cell = self.macro_event_cell(&visible_file);
4704 for event in cell
4705 .get_or_init(|| Arc::from(self.collect_macro_events(&visible_file)))
4706 .iter()
4707 {
4708 let MacroEvent::Define { name, binding, .. } = event else {
4709 continue;
4710 };
4711 if name != macro_name {
4712 continue;
4713 }
4714 let MacroDefinition::Object { replacement } = &binding.definition else {
4715 continue;
4716 };
4717 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4718 continue;
4719 };
4720 if !arities.contains(&arity) {
4721 arities.push(arity);
4722 }
4723 }
4724 }
4725 let required = arities
4726 .iter()
4727 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4728 .min()?;
4729 let total = arities.iter().map(|arity| arity.total()).max()?;
4730 let repeated = arities
4731 .iter()
4732 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4733 Some(CallableArity::new(required, total, repeated))
4738 },
4739 )
4740 }
4741
4742 pub fn include_activation_for_source(
4743 &self,
4744 cpp: &dyn CppSource,
4745 file: &ProjectFile,
4746 prepared: &PreparedSyntaxTree,
4747 donor_source: &ProjectFile,
4748 ) -> Option<usize> {
4749 visibility_memo_value(
4750 &self.include_activation_cells,
4751 &self.reads,
4752 (file.clone(), donor_source.clone()),
4753 || {
4754 #[cfg(any(test, feature = "test-support"))]
4755 self.include_activation_build_count
4756 .fetch_add(1, Ordering::Relaxed);
4757 let effective = OnceCell::new();
4758 find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4759 effective
4760 .get_or_init(|| {
4761 self.effective_reference_guards(file, Some(&HashSet::default()))
4762 })
4763 .as_ref()
4764 })
4765 },
4766 )
4767 }
4768
4769 pub fn conditional_include_projections_for_source(
4770 &self,
4771 file: &ProjectFile,
4772 prepared: &PreparedSyntaxTree,
4773 donor_source: &ProjectFile,
4774 ) -> Arc<[ConditionalIncludeProjection]> {
4775 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4776 let index = visibility_memo_value(
4777 &self.conditional_include_projection_cells,
4778 &self.reads,
4779 file.clone(),
4780 || {
4781 #[cfg(any(test, feature = "test-support"))]
4782 self.conditional_include_projection_index_build_count
4783 .fetch_add(1, Ordering::Relaxed);
4784 let effective = OnceCell::new();
4785 Arc::new(find_conditional_include_projection_index(
4786 self.cpp,
4787 self.token,
4788 file,
4789 prepared,
4790 &|| {
4791 effective
4792 .get_or_init(|| {
4793 self.effective_reference_guards(file, Some(&HashSet::default()))
4794 })
4795 .as_ref()
4796 },
4797 &|| {
4798 #[cfg(any(test, feature = "test-support"))]
4799 self.conditional_include_projection_state_count
4800 .fetch_add(1, Ordering::Relaxed);
4801 },
4802 ))
4803 },
4804 );
4805 index
4806 .get(donor_source)
4807 .cloned()
4808 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4809 }
4810
4811 fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4812 let mut macro_events = |file: &ProjectFile| {
4813 let cell = self.macro_event_cell(file);
4814 cell.get_or_init(|| Arc::from(self.collect_macro_events(file)))
4815 .as_ref()
4816 .to_vec()
4817 .into_boxed_slice()
4818 };
4819 include_language_guard_is_stable_with(
4820 self.cpp,
4821 self.token,
4822 file,
4823 &mut macro_events,
4824 &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4825 self.structured_include_facts(file, prepared)
4826 },
4827 )
4828 }
4829
4830 #[cfg(any(test, feature = "test-support"))]
4831 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4832 (
4833 self.conditional_include_projection_index_build_count
4834 .load(Ordering::Relaxed),
4835 self.conditional_include_projection_state_count
4836 .load(Ordering::Relaxed),
4837 )
4838 }
4839
4840 #[cfg(any(test, feature = "test-support"))]
4841 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4842 self.conditional_include_target_state_count
4843 .load(Ordering::Relaxed)
4844 }
4845
4846 #[cfg(any(test, feature = "test-support"))]
4847 pub fn include_activation_build_count_for_test(&self) -> usize {
4848 self.include_activation_build_count.load(Ordering::Relaxed)
4849 }
4850
4851 #[cfg(any(test, feature = "test-support"))]
4852 pub fn note_using_donor_activation_for_test(&self) {
4853 self.using_donor_activation_count
4854 .fetch_add(1, Ordering::Relaxed);
4855 }
4856
4857 #[cfg(not(any(test, feature = "test-support")))]
4858 pub fn note_using_donor_activation_for_test(&self) {}
4859
4860 #[cfg(any(test, feature = "test-support"))]
4861 pub fn note_using_namespace_lookup_for_test(&self) {
4862 self.using_namespace_lookup_count
4863 .fetch_add(1, Ordering::Relaxed);
4864 }
4865
4866 #[cfg(not(any(test, feature = "test-support")))]
4867 pub fn note_using_namespace_lookup_for_test(&self) {}
4868
4869 #[cfg(any(test, feature = "test-support"))]
4870 pub fn note_using_name_candidate_inspection_for_test(&self) {
4871 self.using_name_candidate_inspection_count
4872 .fetch_add(1, Ordering::Relaxed);
4873 }
4874
4875 #[cfg(not(any(test, feature = "test-support")))]
4876 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4877
4878 #[cfg(any(test, feature = "test-support"))]
4879 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4880 (
4881 self.using_donor_activation_count.load(Ordering::Relaxed),
4882 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4883 self.callable_reference_spec_build_count
4884 .load(Ordering::Relaxed),
4885 self.using_name_candidate_inspection_count
4886 .load(Ordering::Relaxed),
4887 )
4888 }
4889
4890 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4891 file == target.source()
4892 || self
4893 .visible_by_file
4894 .get(file)
4895 .is_some_and(|visible| visible.contains(target))
4896 }
4897
4898 pub fn declaration_visible_at(
4910 &self,
4911 analyzer: &CppGraphSource<'_>,
4912 file: &ProjectFile,
4913 declaration: &CodeUnit,
4914 reference_byte: usize,
4915 ) -> bool {
4916 let reference_guards = OnceCell::new();
4917 if declaration.is_callable() && !callable_target_has_definition_body(analyzer, declaration)
4920 {
4921 return self.physical_declaration_visible_at(
4922 analyzer,
4923 file,
4924 declaration,
4925 reference_byte,
4926 &reference_guards,
4927 );
4928 }
4929 self.visible_identifier_candidates(file, declaration.identifier())
4930 .filter(|candidate| {
4931 (if declaration.is_callable()
4936 && reference_uses_c_semantics(self.cpp, file)
4937 != reference_uses_c_semantics(self.cpp, declaration.source())
4938 {
4939 crate::identity::cpp_callable_definitions_share_identity_evidence_with_visibility(
4940 analyzer,
4941 self,
4942 candidate,
4943 declaration,
4944 |left, right| {
4945 crate::identity::cpp_header_body_files_are_related(
4946 self.cpp, self.token, left, right,
4947 )
4948 },
4949 )
4950 } else {
4951 self.same_logical_callable(analyzer, candidate, declaration)
4952 })
4953 || flattened_macro_namespace_declaration_matches(
4954 analyzer,
4955 self.cpp,
4956 file,
4957 candidate,
4958 declaration,
4959 reference_byte,
4960 )
4961 })
4962 .any(|candidate| {
4963 self.physical_declaration_visible_at(
4964 analyzer,
4965 file,
4966 candidate,
4967 reference_byte,
4968 &reference_guards,
4969 )
4970 })
4971 }
4972
4973 pub fn declaration_guards_compatible_at_reference(
4977 &self,
4978 analyzer: &CppGraphSource<'_>,
4979 file: &ProjectFile,
4980 declaration: &CodeUnit,
4981 reference: Node<'_>,
4982 ) -> bool {
4983 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4984 return false;
4985 };
4986 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4987 let requirements = declaration_guard_requirements(analyzer, self.cpp, declaration);
4988 if reference_guards.as_ref().is_some_and(HashSet::is_empty)
4992 && requirements.iter().any(|(_, required)| required.is_empty())
4993 {
4994 return true;
4995 }
4996 let reference_guards = self.effective_reference_guards(file, reference_guards.as_ref());
4997 requirements.iter().any(|(_, required)| {
4998 guards_compatible_at_reference(required, reference_guards.as_ref())
4999 })
5000 }
5001
5002 pub fn declaration_guards_implied_at_reference(
5006 &self,
5007 analyzer: &CppGraphSource<'_>,
5008 file: &ProjectFile,
5009 declaration: &CodeUnit,
5010 reference: Node<'_>,
5011 ) -> bool {
5012 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5013 return false;
5014 };
5015 let raw = preprocessor_guard_environment(reference, prepared.source());
5016 let requirements = declaration_guard_requirements(analyzer, self.cpp, declaration);
5017 if raw.is_some() && requirements.iter().any(|(_, required)| required.is_empty()) {
5018 return true;
5019 }
5020 let effective = self.effective_reference_guards(file, raw.as_ref());
5021 requirements.iter().any(|(byte, required)| {
5022 if guard_requirements_hold_at_reference(required, effective.as_ref())
5023 && (declaration.source() != file
5024 || self.preprocessor_guards_stable_between(
5025 file,
5026 (*byte).min(reference.start_byte()),
5027 (*byte).max(reference.start_byte()),
5028 required,
5029 ))
5030 {
5031 return true;
5032 }
5033 if declaration.source() != file {
5034 return false;
5035 }
5036 let root = prepared.tree().root_node();
5041 let Some(node) = root.descendant_for_byte_range(
5042 *byte,
5043 byte.saturating_add(1).min(prepared.source().len()),
5044 ) else {
5045 return false;
5046 };
5047 let conditionals = owning_preprocessor_conditionals(root, node, prepared.source());
5048 if conditionals.is_empty() {
5049 return false;
5050 }
5051 let mut pending = conditionals.iter().copied().peekable();
5052 let mut ancestor = node.parent();
5053 let mut evaluated = false;
5054 while let Some(conditional) = ancestor {
5055 if pending.peek() == Some(&conditional.start_byte()) {
5056 pending.next();
5057 match conditional.kind() {
5058 "preproc_if" | "preproc_ifdef" | "preproc_elif" => {
5059 let environment =
5060 self.macro_environment(file, conditional.start_byte());
5061 if self.preprocessor_conditional_branch_value(
5062 conditional,
5063 node,
5064 prepared.source(),
5065 &environment,
5066 ) != Some(true)
5067 {
5068 return false;
5069 }
5070 evaluated = true;
5071 }
5072 "preproc_else" => {}
5073 _ => return false,
5074 }
5075 }
5076 ancestor = conditional.parent();
5077 }
5078 evaluated && pending.peek().is_none()
5079 })
5080 }
5081
5082 pub fn declaration_visible_at_reference(
5089 &self,
5090 analyzer: &CppGraphSource<'_>,
5091 file: &ProjectFile,
5092 declaration: &CodeUnit,
5093 reference: Node<'_>,
5094 ) -> bool {
5095 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5096 return false;
5097 };
5098 if !self.declaration_guards_compatible_at_reference(analyzer, file, declaration, reference)
5099 {
5100 return false;
5101 }
5102 if declaration.source() == file
5103 && !analyzer.reference_uses_c_semantics(file)
5104 && (declaration.is_field() || declaration.is_callable())
5105 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
5106 self.indexed_enclosing_owner_scope(analyzer, file, reference)
5107 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
5108 })
5109 {
5110 return true;
5114 }
5115 if declaration.is_field() && declaration.source() == file {
5116 let reference_byte = reference.start_byte();
5117 let reference_function =
5118 real_function_definition_ancestor(reference, prepared.source());
5119 let guards = OnceCell::new();
5120 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5121 self.effective_reference_guards(file, guards)
5122 };
5123 let field_reference = CallableReferenceContext {
5124 effective_guards: &effective_guards,
5125 file_guards: OnceCell::new(),
5126 file,
5127 position: Some(CallableReferencePosition {
5128 prepared: prepared.as_ref(),
5129 byte: reference_byte,
5130 guards: &guards,
5131 }),
5132 };
5133 let mut has_local_declaration = false;
5134 let mut local_declaration_visible = false;
5135 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
5136 {
5137 let Some(declaration_function) =
5138 real_function_definition_ancestor(declaration, prepared.source())
5139 else {
5140 continue;
5141 };
5142 has_local_declaration = true;
5143 if reference_function.is_some_and(|reference_function| {
5144 reference_function.start_byte() == declaration_function.start_byte()
5145 && reference_function.end_byte() == declaration_function.end_byte()
5146 }) && callable_preprocessor_context_is_visible_for_reference(
5147 declaration,
5148 prepared.source(),
5149 &field_reference,
5150 ) && declaration.end_byte() < reference_byte
5151 {
5152 local_declaration_visible = true;
5153 break;
5154 }
5155 }
5156 if has_local_declaration {
5157 return local_declaration_visible;
5158 }
5159 }
5160 let guards = OnceCell::new();
5161 self.physical_declaration_visible_at(
5162 analyzer,
5163 file,
5164 declaration,
5165 reference.start_byte(),
5166 &guards,
5167 )
5168 }
5169
5170 pub fn declaration_visible_for_c_forward_call(
5176 &self,
5177 analyzer: &CppGraphSource<'_>,
5178 file: &ProjectFile,
5179 declaration: &CodeUnit,
5180 reference_byte: usize,
5181 ) -> bool {
5182 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
5183 return true;
5184 }
5185 if declaration.source() != file {
5186 return false;
5187 }
5188 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5189 return false;
5190 };
5191 let reference_guards = prepared
5192 .tree()
5193 .root_node()
5194 .descendant_for_byte_range(reference_byte, reference_byte)
5195 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
5196 declaration_guard_requirements(analyzer, self.cpp, declaration)
5197 .into_iter()
5198 .any(|(_, required)| {
5199 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
5200 })
5201 }
5202
5203 pub fn callable_arity_at_reference(
5204 &self,
5205 analyzer: &CppGraphSource<'_>,
5206 file: &ProjectFile,
5207 candidate: &CodeUnit,
5208 reference_byte: usize,
5209 ) -> Option<CallableArity> {
5210 let spec = visibility_memo_value(
5211 &self.callable_reference_specs,
5212 &self.reads,
5213 (file.clone(), logical_symbol_key(candidate)),
5214 || {
5215 let prepared = self.cpp.prepared_syntax(self.token, file)?;
5216 let spec = TargetSpec::from_target(analyzer, candidate)?;
5217 let spec = spec
5218 .with_visible_callable_arities(
5219 analyzer,
5220 self.cpp,
5221 self,
5222 file,
5223 prepared.as_ref(),
5224 )
5225 .into_owned();
5226 #[cfg(any(test, feature = "test-support"))]
5227 self.callable_reference_spec_build_count
5228 .fetch_add(1, Ordering::Relaxed);
5229 Some(Arc::new(spec))
5230 },
5231 );
5232 spec.as_ref()?.callable_arity_at(reference_byte)
5233 }
5234
5235 fn physical_declaration_visible_at(
5236 &self,
5237 analyzer: &CppGraphSource<'_>,
5238 file: &ProjectFile,
5239 declaration: &CodeUnit,
5240 reference_byte: usize,
5241 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
5242 ) -> bool {
5243 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5244 return false;
5245 };
5246 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5247 self.effective_reference_guards(file, guards)
5248 };
5249 let reference = CallableReferenceContext {
5250 effective_guards: &effective_guards,
5251 file_guards: OnceCell::new(),
5252 file,
5253 position: Some(CallableReferencePosition {
5254 prepared: prepared.as_ref(),
5255 byte: reference_byte,
5256 guards: reference_guards,
5257 }),
5258 };
5259 if declaration.source() == file {
5260 return callable_declaration_activation_in_file(analyzer, declaration, &reference)
5261 .or_else(|| {
5262 self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
5263 })
5264 .is_some_and(|activation| activation < reference_byte);
5265 }
5266 if self
5267 .exhaustive_guard_family_activation(analyzer, declaration, &reference)
5268 .is_some()
5269 {
5270 return self.foreign_declaration_reachable_at_reference(
5273 file,
5274 prepared.as_ref(),
5275 declaration.source(),
5276 &HashSet::default(),
5277 reference.guards(),
5278 reference_byte,
5279 );
5280 }
5281 if !self.foreign_callable_declaration_is_active(analyzer, declaration, &reference) {
5282 return false;
5283 }
5284 declaration_guard_requirements(analyzer, self.cpp, declaration)
5285 .into_iter()
5286 .any(|(_, declaration_guards)| {
5287 self.foreign_declaration_reachable_at_reference(
5288 file,
5289 prepared.as_ref(),
5290 declaration.source(),
5291 &declaration_guards,
5292 reference.guards(),
5293 reference_byte,
5294 )
5295 })
5296 }
5297
5298 fn foreign_callable_declaration_is_active(
5303 &self,
5304 analyzer: &CppGraphSource<'_>,
5305 declaration: &CodeUnit,
5306 reference: &CallableReferenceContext<'_>,
5307 ) -> bool {
5308 let build_decides = !self.compile_context_is_absent(reference.file);
5309 let active = reference.guards();
5310 analyzer
5311 .declaration_source_occurrences(declaration)
5312 .into_iter()
5313 .flatten()
5314 .any(|occurrence| {
5315 if occurrence.fact.callable_activation.is_none() {
5316 return false;
5317 }
5318 let Some(required) = occurrence
5319 .fact
5320 .callable_guards
5321 .as_ref()
5322 .and_then(cpp_guard_set_to_runtime)
5323 else {
5324 return false;
5325 };
5326 required.is_empty()
5327 || if build_decides {
5328 guard_requirements_hold_at_reference(&required, active)
5329 } else {
5330 guards_compatible_at_reference(&required, active)
5331 }
5332 })
5333 }
5334
5335 pub fn external_type_candidate_visible_at(
5336 &self,
5337 file: &ProjectFile,
5338 candidate: &CodeUnit,
5339 reference_byte: usize,
5340 ) -> bool {
5341 if candidate.source() == file {
5342 return true;
5343 }
5344 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5345 return false;
5346 };
5347 self.visible_identifier_candidates(file, candidate.identifier())
5348 .filter(|peer| same_logical_symbol(candidate, peer))
5349 .any(|peer| {
5350 peer.source() == file
5351 || self
5352 .include_activation_for_source(
5353 self.cpp,
5354 file,
5355 prepared.as_ref(),
5356 peer.source(),
5357 )
5358 .is_some_and(|activation| activation <= reference_byte)
5359 })
5360 }
5361
5362 pub fn external_type_declaration_visible_at(
5363 &self,
5364 file: &ProjectFile,
5365 candidate: &CodeUnit,
5366 reference_byte: usize,
5367 ) -> bool {
5368 if candidate.source() == file {
5369 return true;
5370 }
5371 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5372 return false;
5373 };
5374 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5375 .is_some_and(|activation| activation <= reference_byte)
5376 }
5377
5378 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5397 visibility_memo_value(
5398 &self.compile_proven_guard_cells,
5399 &self.reads,
5400 file.clone(),
5401 || Arc::new(compile_proven_guard_facts(self.cpp, file)),
5402 )
5403 }
5404
5405 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5415 visibility_memo_value(
5416 &self.include_path_admission_cells,
5417 &self.reads,
5418 file.clone(),
5419 || {
5420 if self.compile_context_is_absent(file) {
5421 IncludePathAdmission::Compatible
5422 } else {
5423 IncludePathAdmission::Proven
5424 }
5425 },
5426 )
5427 }
5428
5429 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5436 if !self.cpp.compile_contexts_for(file).is_empty() {
5437 return false;
5438 }
5439 let translation_units = self.cpp.reaching_translation_units(file);
5440 translation_units.is_empty()
5441 || translation_units
5442 .iter()
5443 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5444 }
5445
5446 pub fn miss_requires_compile_context(
5458 &self,
5459 file: &ProjectFile,
5460 identifier: &str,
5461 reference: Node<'_>,
5462 ) -> bool {
5463 if !self.compile_context_is_absent(file) {
5464 return false;
5465 }
5466 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5467 return false;
5468 };
5469 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5470 let reference_byte = reference.start_byte();
5471 let mut sources = self
5472 .visible_identifier_candidates(file, identifier)
5473 .map(CodeUnit::source)
5474 .filter(|source| *source != file)
5475 .collect::<Vec<_>>();
5476 sources.sort();
5477 sources.dedup();
5478 sources.into_iter().any(|declaration_source| {
5479 self.conditional_include_projections_for_source(
5480 file,
5481 prepared.as_ref(),
5482 declaration_source,
5483 )
5484 .iter()
5485 .any(|projection| {
5486 projection.activation_byte <= reference_byte
5487 && !guard_requirements_hold_at_reference(
5488 &projection.required_guards,
5489 reference_guards.as_ref(),
5490 )
5491 && guards_compatible_at_reference(
5492 &projection.required_guards,
5493 reference_guards.as_ref(),
5494 )
5495 })
5496 })
5497 }
5498
5499 fn effective_reference_guards(
5501 &self,
5502 file: &ProjectFile,
5503 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5504 ) -> Option<HashSet<PreprocessorGuard>> {
5505 effective_reference_guards(
5506 self.cpp,
5507 file,
5508 reference_guards,
5509 &self.compile_proven_guards(file),
5510 &|| self.include_language_guard_is_stable(file),
5511 )
5512 }
5513
5514 fn foreign_declaration_reachable_at_reference(
5522 &self,
5523 file: &ProjectFile,
5524 prepared: &PreparedSyntaxTree,
5525 declaration_source: &ProjectFile,
5526 declaration_guards: &HashSet<PreprocessorGuard>,
5527 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5528 reference_byte: usize,
5529 ) -> bool {
5530 let effective = self.effective_reference_guards(file, reference_guards);
5531 let reference_guards = effective.as_ref();
5532 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5533 eprintln!(
5534 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5535 declaration_source.rel_path().display(),
5536 );
5537 }
5538 if !guard_requirements_hold_at_reference(declaration_guards, reference_guards) {
5539 return false;
5540 }
5541 if self
5542 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5543 .is_some_and(|activation| activation <= reference_byte)
5544 {
5545 return true;
5546 }
5547 let projections =
5548 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5549 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5550 eprintln!(
5551 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5552 declaration_source.rel_path().display(),
5553 declaration_guards.len(),
5554 self.compile_proven_guards(file).len(),
5555 projections.len(),
5556 );
5557 }
5558 let admission = IncludePathAdmission::Proven;
5559 projections.iter().any(|projection| {
5560 projection.activation_byte <= reference_byte
5561 && admission.admits(
5562 &projection.required_guards,
5563 &projection.partial_guards,
5564 reference_guards,
5565 )
5566 && self.preprocessor_guards_stable_between(
5567 file,
5568 projection.activation_byte,
5569 reference_byte,
5570 &projection.required_guards,
5571 )
5572 })
5573 }
5574
5575 fn foreign_declaration_may_be_reachable_from_raw_guards(
5576 &self,
5577 file: &ProjectFile,
5578 prepared: &PreparedSyntaxTree,
5579 declaration_source: &ProjectFile,
5580 declaration_guards: &HashSet<PreprocessorGuard>,
5581 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5582 reference_byte: usize,
5583 ) -> bool {
5584 let proven = self.compile_proven_guards(file);
5585 let augmented;
5586 let reference_guards = match reference_guards {
5587 Some(active) if !proven.is_empty() => {
5588 augmented = active.union(&proven).cloned().collect();
5589 Some(&augmented)
5590 }
5591 other => other,
5592 };
5593 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5594 return false;
5595 }
5596 if self
5597 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5598 .is_some_and(|activation| activation <= reference_byte)
5599 {
5600 return true;
5601 }
5602 let effective = OnceCell::new();
5603 let reachable = find_conditional_include_projection_for_source(
5604 self.cpp,
5605 self.token,
5606 file,
5607 prepared,
5608 declaration_source,
5609 self.include_path_admission(file),
5610 reference_guards,
5611 reference_byte,
5612 &|| {
5613 effective
5614 .get_or_init(|| {
5615 self.effective_reference_guards(file, Some(&HashSet::default()))
5616 })
5617 .as_ref()
5618 },
5619 &|| {
5620 #[cfg(any(test, feature = "test-support"))]
5621 self.conditional_include_target_state_count
5622 .fetch_add(1, Ordering::Relaxed);
5623 },
5624 );
5625 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5626 eprintln!(
5627 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5628 declaration_source.rel_path().display(),
5629 declaration_guards.len(),
5630 proven.len(),
5631 reference_guards.map_or(0, HashSet::len),
5632 );
5633 }
5634 reachable
5635 }
5636
5637 fn foreign_declaration_reachable_from_compile_proven_guards(
5638 &self,
5639 file: &ProjectFile,
5640 prepared: &PreparedSyntaxTree,
5641 declaration_source: &ProjectFile,
5642 declaration_guards: &HashSet<PreprocessorGuard>,
5643 reference_byte: usize,
5644 ) -> bool {
5645 let proven = self.compile_proven_guards(file);
5646 if proven.is_empty()
5647 || !guard_requirements_hold_at_reference(declaration_guards, Some(proven.as_ref()))
5648 {
5649 return false;
5650 }
5651 let projections =
5652 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5653 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5654 eprintln!(
5655 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5656 declaration_source.rel_path().display(),
5657 declaration_guards.len(),
5658 proven.len(),
5659 projections.len(),
5660 );
5661 }
5662 projections.iter().any(|projection| {
5663 projection.activation_byte <= reference_byte
5664 && guard_requirements_hold_at_reference(
5665 &projection.required_guards,
5666 Some(proven.as_ref()),
5667 )
5668 && self.preprocessor_guards_stable_between(
5673 file,
5674 0,
5675 projection.activation_byte,
5676 &projection.required_guards,
5677 )
5678 })
5679 }
5680
5681 fn declaration_site_visible_without_reference_environment(
5687 &self,
5688 file: &ProjectFile,
5689 prepared: &PreparedSyntaxTree,
5690 site_source: &ProjectFile,
5691 site_byte: usize,
5692 site_guards: &HashSet<PreprocessorGuard>,
5693 reference_byte: usize,
5694 ) -> bool {
5695 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5696 if site_source == file {
5697 let visible = site_byte < reference_byte && site_guards.is_empty();
5698 if report_stats {
5699 eprintln!(
5700 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5701 site_source.rel_path().display(),
5702 site_guards.len(),
5703 );
5704 }
5705 return visible;
5706 }
5707 let direct = site_guards.is_empty()
5708 && self
5709 .include_activation_for_source(self.cpp, file, prepared, site_source)
5710 .is_some_and(|activation| activation <= reference_byte);
5711 let compile_proven = !direct
5712 && self.foreign_declaration_reachable_from_compile_proven_guards(
5713 file,
5714 prepared,
5715 site_source,
5716 site_guards,
5717 reference_byte,
5718 );
5719 if report_stats {
5720 eprintln!(
5721 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5722 site_source.rel_path().display(),
5723 site_guards.len(),
5724 );
5725 }
5726 direct || compile_proven
5727 }
5728
5729 #[allow(clippy::too_many_arguments)]
5734 fn declaration_site_visible_at_reference<'g>(
5735 &self,
5736 file: &ProjectFile,
5737 prepared: &PreparedSyntaxTree,
5738 site_source: &ProjectFile,
5739 site: &DeclarationGuardSite,
5740 reference_byte: usize,
5741 raw_reference_guards: Option<&HashSet<PreprocessorGuard>>,
5742 reference_guards_at_site: impl Fn() -> Option<&'g HashSet<PreprocessorGuard>>,
5743 ) -> bool {
5744 if site_source == file {
5745 if site.byte >= reference_byte {
5746 return false;
5747 }
5748 let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5753 guard_requirements_hold_at_reference(&site.guards, guards)
5754 || (site.completed_family_before_reference
5755 && guards_compatible_at_reference(&site.guards, guards))
5756 };
5757 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5758 self.effective_reference_guards(file, guards)
5759 };
5760 let raw_reference_guards = effective_guards(raw_reference_guards);
5761 if !active(raw_reference_guards.as_ref()) {
5762 return false;
5763 }
5764 let reference_guards_at_site = effective_guards(reference_guards_at_site());
5765 return active(reference_guards_at_site.as_ref())
5766 && self.preprocessor_guards_stable_between(
5767 file,
5768 site.byte,
5769 reference_byte,
5770 &site.guards,
5771 );
5772 }
5773 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5774 file,
5775 prepared,
5776 site_source,
5777 &site.guards,
5778 raw_reference_guards,
5779 reference_byte,
5780 );
5781 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5782 eprintln!(
5783 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5784 site_source.rel_path().display(),
5785 site.guards.len(),
5786 );
5787 }
5788 raw_feasible
5789 && self.foreign_declaration_reachable_at_reference(
5790 file,
5791 prepared,
5792 site_source,
5793 &site.guards,
5794 reference_guards_at_site(),
5795 reference_byte,
5796 )
5797 }
5798
5799 pub fn binding_site_visible_at_reference(
5804 &self,
5805 file: &ProjectFile,
5806 site_source: &ProjectFile,
5807 site_byte: usize,
5808 site_guards: &HashSet<PreprocessorGuard>,
5809 reference: Node<'_>,
5810 ) -> bool {
5811 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5812 return false;
5813 };
5814 if self.declaration_site_visible_without_reference_environment(
5815 file,
5816 prepared.as_ref(),
5817 site_source,
5818 site_byte,
5819 site_guards,
5820 reference.start_byte(),
5821 ) {
5822 return true;
5823 }
5824 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5825 let reference_guards = OnceCell::new();
5826 self.declaration_site_visible_at_reference(
5827 file,
5828 prepared.as_ref(),
5829 site_source,
5830 &DeclarationGuardSite {
5831 byte: site_byte,
5832 guards: site_guards.clone(),
5833 completed_family_before_reference: false,
5834 },
5835 reference.start_byte(),
5836 raw_reference_guards.as_ref(),
5837 || {
5838 reference_guards
5839 .get_or_init(|| {
5840 let environment = self.macro_environment(file, reference.start_byte());
5841 raw_reference_guards
5842 .clone()
5843 .filter(|guards| environment.guard_requirements_may_hold(guards))
5844 })
5845 .as_ref()
5846 },
5847 )
5848 }
5849
5850 pub fn external_type_candidate_visible_in_context(
5851 &self,
5852 analyzer: &CppGraphSource<'_>,
5853 file: &ProjectFile,
5854 candidate: &CodeUnit,
5855 reference: Node<'_>,
5856 ) -> bool {
5857 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5858 if report_stats {
5859 eprintln!(
5860 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5861 candidate.fq_name(),
5862 candidate.source().rel_path().display(),
5863 file.rel_path().display(),
5864 reference.start_byte(),
5865 );
5866 }
5867 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5868 return false;
5869 };
5870 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5871 let reference_guards = OnceCell::new();
5872 let reference_guards_at_site = || {
5873 reference_guards.get_or_init(|| {
5874 let started = Instant::now();
5875 if report_stats {
5876 eprintln!(
5877 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5878 file.rel_path().display(),
5879 reference.start_byte(),
5880 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5881 );
5882 }
5883 let macro_environment = self.macro_environment(file, reference.start_byte());
5884 let filtered = raw_reference_guards
5885 .clone()
5886 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5887 if report_stats {
5888 eprintln!(
5889 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5890 filtered.is_some(),
5891 started.elapsed().as_millis(),
5892 );
5893 }
5894 filtered
5895 })
5896 };
5897
5898 let peers = self
5899 .visible_identifier_candidates(file, candidate.identifier())
5900 .filter(|peer| same_logical_symbol(candidate, peer))
5901 .collect::<Vec<_>>();
5902 if report_stats {
5903 let peer_sources = peers
5904 .iter()
5905 .map(|peer| peer.source().rel_path().display().to_string())
5906 .collect::<Vec<_>>();
5907 eprintln!(
5908 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5909 candidate.fq_name(),
5910 );
5911 }
5912 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5913 declaration_guard_requirements(analyzer, self.cpp, peer)
5914 .into_iter()
5915 .any(|(declaration_byte, declaration_guards)| {
5916 self.declaration_site_visible_without_reference_environment(
5917 file,
5918 prepared.as_ref(),
5919 peer.source(),
5920 declaration_byte,
5921 &declaration_guards,
5922 reference.start_byte(),
5923 )
5924 })
5925 });
5926 if directly_visible_without_reference_environment {
5927 if report_stats {
5928 eprintln!(
5929 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5930 candidate.fq_name(),
5931 );
5932 }
5933 return true;
5934 }
5935 let directly_visible = peers.iter().any(|peer| {
5936 declaration_guard_sites(analyzer, self.cpp, peer, reference.start_byte())
5937 .into_iter()
5938 .any(|site| {
5939 self.declaration_site_visible_at_reference(
5940 file,
5941 prepared.as_ref(),
5942 peer.source(),
5943 &site,
5944 reference.start_byte(),
5945 raw_reference_guards.as_ref(),
5946 || reference_guards_at_site().as_ref(),
5947 )
5948 })
5949 });
5950 if directly_visible {
5951 if report_stats {
5952 eprintln!(
5953 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5954 candidate.fq_name(),
5955 );
5956 }
5957 return true;
5958 }
5959 let complementary = self
5960 .visible_identifier_candidates(file, candidate.identifier())
5961 .filter(|peer| {
5962 peer.kind() == candidate.kind()
5963 && peer.fq_name() == candidate.fq_name()
5964 && peer.source() == candidate.source()
5965 })
5966 .collect::<Vec<_>>();
5967 let complementary_family =
5972 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5973 let raw_candidate_branch_compatible = complementary_family
5974 && raw_reference_guards.as_ref().is_some_and(|active| {
5975 declaration_guard_requirements(analyzer, self.cpp, candidate)
5976 .iter()
5977 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5978 });
5979 if report_stats {
5980 eprintln!(
5981 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5982 candidate.fq_name(),
5983 complementary.len(),
5984 complementary_family,
5985 raw_candidate_branch_compatible,
5986 );
5987 }
5988 let candidate_branch_compatible = raw_candidate_branch_compatible
5989 && reference_guards_at_site().as_ref().is_some_and(|active| {
5990 declaration_guard_requirements(analyzer, self.cpp, candidate)
5991 .iter()
5992 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5993 });
5994 let complementary_visible = candidate_branch_compatible
5995 && if candidate.source() == file {
5996 declaration_guard_requirements(analyzer, self.cpp, candidate)
5997 .iter()
5998 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5999 } else {
6000 self.include_activation_for_source(
6001 self.cpp,
6002 file,
6003 prepared.as_ref(),
6004 candidate.source(),
6005 )
6006 .is_some_and(|activation| activation <= reference.start_byte())
6007 };
6008 if report_stats {
6009 eprintln!(
6010 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
6011 if complementary_visible {
6012 "complementary"
6013 } else {
6014 "missing"
6015 },
6016 candidate.fq_name(),
6017 );
6018 }
6019 complementary_visible
6020 }
6021
6022 pub fn is_exhaustive_same_fqn_type_declaration_family(
6023 &self,
6024 analyzer: &CppGraphSource<'_>,
6025 file: &ProjectFile,
6026 candidate: &CodeUnit,
6027 ) -> bool {
6028 let candidates = self
6029 .visible_identifier_candidates(file, candidate.identifier())
6030 .filter(|peer| {
6031 peer.kind() == candidate.kind()
6032 && peer.fq_name() == candidate.fq_name()
6033 && peer.source() == candidate.source()
6034 })
6035 .collect::<Vec<_>>();
6036 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
6037 }
6038
6039 pub fn dependent_member_pointer_alias_visible_in_context(
6054 &self,
6055 analyzer: &CppGraphSource<'_>,
6056 file: &ProjectFile,
6057 candidate: &CodeUnit,
6058 owner_components: &[String],
6059 reference: Node<'_>,
6060 ) -> bool {
6061 if !analyzer
6062 .type_alias_provider()
6063 .is_some_and(|provider| provider.is_type_alias(candidate))
6064 {
6065 return false;
6066 }
6067 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
6068 return false;
6069 };
6070 if terminal != candidate.identifier()
6071 || canonical_cpp_scope_components(candidate) != owner_components
6072 {
6073 return false;
6074 }
6075 let Some(expected_parent_fq_name) =
6076 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
6077 else {
6078 return false;
6079 };
6080 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
6081 return false;
6082 };
6083 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
6084 || parent_anchor.source() != candidate.source()
6085 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
6086 {
6087 return false;
6088 }
6089
6090 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
6096 || candidate.source() == file
6097 && !analyzer
6098 .ranges(candidate)
6099 .iter()
6100 .any(|range| range.start_byte < reference.start_byte())
6101 {
6102 return false;
6103 }
6104
6105 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
6106 if candidate_guards.is_empty() {
6107 return false;
6108 }
6109 let same_guard_sets =
6110 |left: &[(usize, HashSet<PreprocessorGuard>)],
6111 right: &[(usize, HashSet<PreprocessorGuard>)]| {
6112 left.iter().all(|(_, left_guards)| {
6113 right
6114 .iter()
6115 .any(|(_, right_guards)| left_guards == right_guards)
6116 })
6117 };
6118 let parent_candidates = self
6119 .visible_identifier_candidates(file, parent_anchor.identifier())
6120 .filter(|peer| {
6121 peer.kind() == parent_anchor.kind()
6122 && peer.fq_name() == expected_parent_fq_name.as_str()
6123 && peer.source() == parent_anchor.source()
6124 && canonical_cpp_scope_components(peer) == owner_prefix
6125 })
6126 .filter_map(|peer| {
6127 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
6128 (candidate_guards.len() == parent_guards.len()
6129 && same_guard_sets(&candidate_guards, &parent_guards)
6130 && same_guard_sets(&parent_guards, &candidate_guards))
6131 .then(|| (peer.clone(), parent_guards))
6132 })
6133 .collect::<Vec<_>>();
6134 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
6135 return false;
6136 };
6137
6138 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6139 return false;
6140 };
6141 let raw = preprocessor_guard_environment(reference, prepared.source());
6142 let Some(reference_guards) = self.effective_reference_guards(file, raw.as_ref()) else {
6143 return false;
6144 };
6145 if !candidate_guards.iter().any(|(_, target_guards)| {
6150 guards_compatible_at_reference(target_guards, Some(&reference_guards))
6151 && (candidate.source() != file
6152 || self.preprocessor_guards_stable_between(
6153 file,
6154 0,
6155 reference.start_byte(),
6156 target_guards,
6157 ))
6158 }) {
6159 return false;
6160 }
6161
6162 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
6163 }
6164
6165 pub fn external_type_candidate_guard_compatible_in_context(
6175 &self,
6176 analyzer: &CppGraphSource<'_>,
6177 file: &ProjectFile,
6178 candidate: &CodeUnit,
6179 reference: Node<'_>,
6180 ) -> bool {
6181 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6182 return false;
6183 };
6184 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
6185
6186 self.visible_identifier_candidates(file, candidate.identifier())
6187 .filter(|peer| same_logical_symbol(candidate, peer))
6188 .any(|peer| {
6189 declaration_guard_requirements(analyzer, self.cpp, peer)
6190 .into_iter()
6191 .any(|(declaration_byte, declaration_guards)| {
6192 if peer.source() == file {
6193 let (start, end) = if declaration_byte <= reference.start_byte() {
6194 (declaration_byte, reference.start_byte())
6195 } else {
6196 (reference.start_byte(), declaration_byte)
6197 };
6198 return guard_requirements_hold_at_reference(
6199 &declaration_guards,
6200 reference_guards.as_ref(),
6201 ) && self.preprocessor_guards_stable_between(
6202 file,
6203 start,
6204 end,
6205 &declaration_guards,
6206 );
6207 }
6208 self.foreign_declaration_reachable_at_reference(
6209 file,
6210 prepared.as_ref(),
6211 peer.source(),
6212 &declaration_guards,
6213 reference_guards.as_ref(),
6214 reference.start_byte(),
6215 )
6216 })
6217 })
6218 }
6219
6220 pub fn same_file_callable_guard_compatible_ignoring_order(
6228 &self,
6229 analyzer: &CppGraphSource<'_>,
6230 file: &ProjectFile,
6231 candidate: &CodeUnit,
6232 reference: Node<'_>,
6233 ) -> bool {
6234 if candidate.source() != file || !candidate.is_callable() {
6235 return false;
6236 }
6237 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6238 return false;
6239 };
6240 let guards = OnceCell::new();
6241 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
6242 self.effective_reference_guards(file, guards)
6243 };
6244 let context = CallableReferenceContext {
6245 effective_guards: &effective_guards,
6246 file_guards: OnceCell::new(),
6247 file,
6248 position: Some(CallableReferencePosition {
6249 prepared: prepared.as_ref(),
6250 byte: reference.start_byte(),
6251 guards: &guards,
6252 }),
6253 };
6254 analyzer
6255 .declaration_source_occurrences(candidate)
6256 .into_iter()
6257 .flatten()
6258 .filter_map(|occurrence| {
6259 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
6260 Some(callable_guard_requirements_are_visible(
6261 &guards,
6262 occurrence.fact.callable_guard_completion_byte,
6263 &context,
6264 ))
6265 })
6266 .any(|visible| visible)
6267 }
6268
6269 pub fn type_candidate_may_be_visible_before_reference(
6270 &self,
6271 analyzer: &CppGraphSource<'_>,
6272 file: &ProjectFile,
6273 candidate: &CodeUnit,
6274 reference_byte: usize,
6275 ) -> bool {
6276 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6277 return false;
6278 };
6279 let root = prepared.tree().root_node();
6280 let end_byte = reference_byte
6281 .saturating_add(1)
6282 .min(prepared.source().len());
6283 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
6284 return false;
6285 };
6286 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
6287 }
6288
6289 pub fn preprocessor_guards_stable_between(
6290 &self,
6291 file: &ProjectFile,
6292 start_byte: usize,
6293 end_byte: usize,
6294 guards: &HashSet<PreprocessorGuard>,
6295 ) -> bool {
6296 if guards.is_empty() || start_byte >= end_byte {
6297 return true;
6298 }
6299 let cell = self.macro_event_cell(file);
6300 let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
6301 let mut visited = HashSet::from_iter([file.clone()]);
6302 !events.iter().any(|event| {
6303 event.byte() >= start_byte
6304 && event.byte() < end_byte
6305 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
6306 })
6307 }
6308
6309 fn macro_event_may_mutate_guards(
6310 &self,
6311 event: &MacroEvent,
6312 guards: &HashSet<PreprocessorGuard>,
6313 visited: &mut HashSet<ProjectFile>,
6314 ) -> bool {
6315 match event {
6316 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
6317 guards.iter().any(|guard| guard.may_depend_on_macro(name))
6318 }
6319 MacroEvent::Include { targets, .. } => {
6320 targets.is_empty()
6321 || targets
6322 .iter()
6323 .any(|target| self.source_may_mutate_guards(target, guards, visited))
6324 }
6325 MacroEvent::Invalidate { .. } => true,
6326 }
6327 }
6328
6329 fn source_may_mutate_guards(
6330 &self,
6331 file: &ProjectFile,
6332 guards: &HashSet<PreprocessorGuard>,
6333 visited: &mut HashSet<ProjectFile>,
6334 ) -> bool {
6335 if !visited.insert(file.clone()) {
6336 return false;
6337 }
6338 let cell = self.macro_event_cell(file);
6339 let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
6340 events
6341 .iter()
6342 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
6343 }
6344
6345 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
6346 let normalized = normalize_reference_name(raw_name)?;
6347 self.type_candidates(file, &normalized)
6348 .into_iter()
6349 .next()
6350 .cloned()
6351 }
6352
6353 pub fn unique_visible_parameter_type_fallback(
6362 &self,
6363 analyzer: &CppGraphSource<'_>,
6364 file: &ProjectFile,
6365 node: Node<'_>,
6366 source: &str,
6367 ) -> Option<CodeUnit> {
6368 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
6369 return None;
6370 }
6371 let name = node_text(node, source);
6372 let candidates = self
6373 .visible_identifier_candidates(file, name)
6374 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6375 .filter(|candidate| {
6376 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6377 })
6378 .collect::<Vec<_>>();
6379 self.unique_canonical_type_candidate(analyzer, file, &candidates)
6380 }
6381
6382 pub fn resolve_type_node_result(
6383 &self,
6384 file: &ProjectFile,
6385 node: Node<'_>,
6386 source: &str,
6387 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6388 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6389 return Ok(None);
6390 };
6391 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6392 return Ok(Some(primary));
6393 };
6394 self.resolve_template_arguments(file, primary, &arguments)
6395 .map(Some)
6396 }
6397
6398 pub fn resolve_type_node_primary(
6399 &self,
6400 file: &ProjectFile,
6401 node: Node<'_>,
6402 source: &str,
6403 ) -> Option<CodeUnit> {
6404 let components = cpp_type_name_components(node, source)?;
6405 if !is_globally_qualified_cpp_name(node)
6406 && let Some(resolution) =
6407 function_local_type_resolution(&self.cpp_source(), file, node, &components, source)
6408 {
6409 return match resolution {
6410 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
6411 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
6412 };
6413 }
6414 self.resolve_type(file, &components.join("::"))
6415 }
6416
6417 pub fn resolve_template_arguments(
6418 &self,
6419 file: &ProjectFile,
6420 primary: CodeUnit,
6421 arguments: &[CppTemplateExpression],
6422 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6423 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6424 }
6425
6426 fn resolve_template_arguments_inner(
6427 &self,
6428 file: &ProjectFile,
6429 primary: CodeUnit,
6430 arguments: &[CppTemplateExpression],
6431 seen_aliases: &mut HashSet<CodeUnit>,
6432 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6433 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6434 && let Some(alias_target) = &metadata.alias_target
6435 {
6436 if !seen_aliases.insert(primary.clone()) {
6437 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6438 }
6439 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6440 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6441 let target_name = alias_target.components.join("::");
6442 let target_primary = if alias_target.global {
6443 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6444 } else {
6445 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6446 };
6447 let Some(target_primary) = target_primary else {
6448 return Ok(primary);
6452 };
6453 let Some(target_arguments) = &alias_target.arguments else {
6454 return Ok(target_primary);
6455 };
6456 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6457 .ok_or(CppTemplateResolutionError::Substitution)?;
6458 return self.resolve_template_arguments_inner(
6459 file,
6460 target_primary,
6461 &target_arguments,
6462 seen_aliases,
6463 );
6464 }
6465
6466 let primary_fq_name = self
6467 .cpp_template_metadata
6468 .get(&primary)
6469 .map(|metadata| metadata.primary_fq_name.clone())
6470 .unwrap_or_else(|| primary.fq_name());
6471 let has_specialization_metadata = self
6472 .cpp_template_families
6473 .get(&primary_fq_name)
6474 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6475 if !has_specialization_metadata {
6476 return Ok(primary);
6477 }
6478 self.select_template_specialization(file, &primary, arguments)
6479 }
6480
6481 fn select_template_specialization(
6482 &self,
6483 file: &ProjectFile,
6484 resolved: &CodeUnit,
6485 explicit_arguments: &[CppTemplateExpression],
6486 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6487 let primary_fq_name = self
6488 .cpp_template_metadata
6489 .get(resolved)
6490 .map(|metadata| metadata.primary_fq_name.clone())
6491 .unwrap_or_else(|| resolved.fq_name());
6492 let family = self
6493 .cpp_template_families
6494 .get(&primary_fq_name)
6495 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6496 let primary_candidates = family
6497 .iter()
6498 .filter_map(|unit| {
6499 let metadata = self.cpp_template_metadata.get(unit)?;
6500 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6501 })
6502 .collect::<Vec<_>>();
6503 let primary_unit = primary_candidates
6504 .iter()
6505 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6506 .or_else(|| {
6507 primary_candidates
6508 .iter()
6509 .map(|(unit, _)| *unit)
6510 .min_by_key(|unit| {
6511 (
6512 unit.source().to_string(),
6513 unit.signature().unwrap_or_default(),
6514 )
6515 })
6516 })
6517 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6518 let primary_parameters =
6519 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6520 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6521 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6522 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6523
6524 let mut applicable = Vec::new();
6525 for unit in family {
6526 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6527 continue;
6528 };
6529 if metadata.is_primary() || !self.is_visible(file, unit) {
6530 continue;
6531 }
6532 if !cpp_specialization_matches(metadata, &expanded) {
6533 continue;
6534 }
6535 applicable.push((unit, metadata));
6536 }
6537 if applicable.is_empty() {
6538 return Ok(primary_unit.clone());
6539 }
6540
6541 let winners = applicable
6546 .iter()
6547 .filter(|(candidate, candidate_metadata)| {
6548 applicable.iter().all(|(other, other_metadata)| {
6549 same_visible_symbol(candidate, other)
6550 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6551 })
6552 })
6553 .copied()
6554 .collect::<Vec<_>>();
6555 let Some((selected, _)) = winners.first() else {
6556 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6559 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6560 });
6561 };
6562 if winners
6563 .iter()
6564 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6565 {
6566 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6567 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6568 });
6569 }
6570 Ok((*selected).clone())
6571 }
6572
6573 pub fn resolve_type_components_lexically(
6574 &self,
6575 analyzer: &CppGraphSource<'_>,
6576 file: &ProjectFile,
6577 components: &[String],
6578 global: bool,
6579 lexical_scope: &[String],
6580 ) -> LexicalTypeResolution {
6581 self.resolve_type_components_lexically_inner(
6582 analyzer,
6583 file,
6584 components,
6585 global,
6586 lexical_scope,
6587 TypeCandidateResolution::Canonical,
6588 )
6589 }
6590
6591 pub fn resolve_type_components_lexically_for_forward(
6592 &self,
6593 analyzer: &CppGraphSource<'_>,
6594 file: &ProjectFile,
6595 components: &[String],
6596 global: bool,
6597 lexical_scope: &[String],
6598 ) -> LexicalTypeResolution {
6599 self.resolve_type_components_lexically_inner(
6600 analyzer,
6601 file,
6602 components,
6603 global,
6604 lexical_scope,
6605 TypeCandidateResolution::PreserveAlias,
6606 )
6607 }
6608
6609 pub fn resolve_type_components_lexically_for_target(
6610 &self,
6611 analyzer: &CppGraphSource<'_>,
6612 file: &ProjectFile,
6613 components: &[String],
6614 global: bool,
6615 lexical_scope: &[String],
6616 target: &CodeUnit,
6617 ) -> LexicalTypeResolution {
6618 #[cfg(any(test, feature = "test-support"))]
6619 self.target_preserving_type_resolution_count
6620 .fetch_add(1, Ordering::Relaxed);
6621 self.resolve_type_components_lexically_inner(
6622 analyzer,
6623 file,
6624 components,
6625 global,
6626 lexical_scope,
6627 TypeCandidateResolution::PreserveTarget(target),
6628 )
6629 }
6630
6631 pub fn coarse_unqualified_type_reference_may_resolve(
6632 &self,
6633 file: &ProjectFile,
6634 name: &str,
6635 ) -> bool {
6636 if name.is_empty() {
6637 return true;
6638 }
6639 self.visible_identifier_candidates(file, name)
6640 .any(|candidate| {
6641 candidate.kind() == CodeUnitType::Class
6642 || declared_type_alias(&self.cpp_source(), candidate)
6643 })
6644 || self.visible_parser_alias_name_is_visible(file, name)
6645 }
6646
6647 #[allow(clippy::too_many_arguments)]
6648 pub fn structured_type_reference_may_resolve_to_target(
6649 &self,
6650 analyzer: &CppGraphSource<'_>,
6651 file: &ProjectFile,
6652 components: &[String],
6653 global: bool,
6654 lexical_scope: &[String],
6655 target: &CodeUnit,
6656 ) -> bool {
6657 if components.is_empty() {
6658 return true;
6659 }
6660 let Some(terminal) = components.last() else {
6661 return true;
6662 };
6663 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6664 .map(|qualified| qualified.join("::"))
6665 .collect::<Vec<_>>();
6666 let target_name = cpp_name_for(target);
6667 if qualified_tiers
6668 .iter()
6669 .any(|qualified| qualified == &target_name)
6670 {
6671 return true;
6672 }
6673
6674 let mut saw_shape_candidate = false;
6675 for candidate in self.visible_identifier_candidates(file, terminal) {
6676 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6677 {
6678 continue;
6679 }
6680 let candidate_name = cpp_name_for(candidate);
6681 let shape_matches = if global || components.len() > 1 {
6682 qualified_tiers
6683 .iter()
6684 .any(|qualified| qualified == &candidate_name)
6685 } else {
6686 true
6687 };
6688 if !shape_matches {
6689 continue;
6690 }
6691 saw_shape_candidate = true;
6692 if same_visible_symbol(candidate, target)
6693 || self.c_tag_declaration_family_matches_target(
6694 analyzer,
6695 file,
6696 std::slice::from_ref(&candidate),
6697 target,
6698 )
6699 || self.compatible_primary_template_redeclarations(candidate, target)
6700 || (declared_type_alias(analyzer, candidate)
6701 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6702 {
6703 return true;
6704 }
6705 }
6706
6707 !saw_shape_candidate
6708 }
6709
6710 fn cached_c_tag_kind(
6717 &self,
6718 analyzer: &CppGraphSource<'_>,
6719 candidate: &CodeUnit,
6720 ) -> Option<CppCTagKind> {
6721 visibility_memo_value(
6722 &self.c_tag_kind_cache,
6723 &self.reads,
6724 candidate.clone(),
6725 || indexed_c_tag_kind(analyzer, candidate),
6726 )
6727 }
6728
6729 fn cached_unique_c_tag_complete_definition(
6730 &self,
6731 analyzer: &CppGraphSource<'_>,
6732 target: &CodeUnit,
6733 target_tag: CppCTagKind,
6734 ) -> Option<CodeUnit> {
6735 visibility_memo_value(
6736 &self.c_tag_complete_definition_cache,
6737 &self.reads,
6738 target.clone(),
6739 || {
6740 let complete_definitions = analyzer
6741 .definitions(&target.fq_name())
6742 .filter(|candidate| {
6743 candidate.is_class()
6744 && !declared_type_alias(analyzer, candidate)
6745 && is_c_source_file(candidate.source())
6746 && analyzer.parent_of(candidate).is_none()
6747 && cpp_class_declaration_strength(analyzer, candidate)
6748 == CppClassDeclarationStrength::Full
6749 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6750 })
6751 .collect::<HashSet<_>>();
6752 (complete_definitions.len() == 1)
6753 .then(|| complete_definitions.into_iter().next())
6754 .flatten()
6755 .filter(|candidate| same_visible_symbol(candidate, target))
6756 },
6757 )
6758 }
6759
6760 pub fn c_tag_declaration_family_matches_target(
6761 &self,
6762 analyzer: &CppGraphSource<'_>,
6763 visible_from: &ProjectFile,
6764 candidates: &[&CodeUnit],
6765 target: &CodeUnit,
6766 ) -> bool {
6767 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6768 let candidate_evidence = candidates
6769 .iter()
6770 .map(|candidate| {
6771 (
6772 candidate.fq_name(),
6773 candidate.source().rel_path().to_path_buf(),
6774 cpp_class_declaration_strength(analyzer, candidate),
6775 indexed_c_tag_kind(analyzer, candidate),
6776 self.is_physically_visible(visible_from, candidate),
6777 )
6778 })
6779 .collect::<Vec<_>>();
6780 eprintln!(
6781 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6782 visible_from.rel_path().display(),
6783 target.fq_name(),
6784 target.source().rel_path().display(),
6785 cpp_class_declaration_strength(analyzer, target),
6786 indexed_c_tag_kind(analyzer, target),
6787 );
6788 }
6789 if candidates.is_empty()
6790 || !target.is_class()
6791 || declared_type_alias(analyzer, target)
6792 || !is_c_source_file(target.source())
6793 || analyzer.parent_of(target).is_some()
6794 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6795 {
6796 return false;
6797 }
6798 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6799 return false;
6800 };
6801 if self
6802 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6803 .is_none()
6804 {
6805 return false;
6806 }
6807 let mut saw_visible_forward = false;
6808 for candidate in candidates.iter().copied() {
6809 if candidate == target {
6810 continue;
6811 }
6812 if !candidate.is_class()
6813 || declared_type_alias(analyzer, candidate)
6814 || candidate.fq_name() != target.fq_name()
6815 || analyzer.parent_of(candidate).is_some()
6816 || cpp_class_declaration_strength(analyzer, candidate)
6817 != CppClassDeclarationStrength::Forward
6818 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6819 || !self.is_physically_visible(visible_from, candidate)
6820 {
6821 return false;
6822 }
6823 saw_visible_forward = true;
6824 }
6825 saw_visible_forward
6826 }
6827
6828 fn unique_c_tag_declaration_family(
6833 &self,
6834 analyzer: &CppGraphSource<'_>,
6835 visible_from: &ProjectFile,
6836 candidates: &[&CodeUnit],
6837 ) -> Option<CodeUnit> {
6838 let first = candidates.first()?;
6839 let target_fq_name = first.fq_name();
6840 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6841 let mut full = None;
6842 let mut saw_forward = false;
6843 for candidate in candidates.iter().copied() {
6844 if !candidate.is_class()
6845 || declared_type_alias(analyzer, candidate)
6846 || candidate.fq_name() != target_fq_name
6847 || analyzer.parent_of(candidate).is_some()
6848 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6849 {
6850 return None;
6851 }
6852 match cpp_class_declaration_strength(analyzer, candidate) {
6853 CppClassDeclarationStrength::Full
6854 if is_c_source_file(candidate.source())
6855 && full.replace(candidate.clone()).is_none() => {}
6856 CppClassDeclarationStrength::Forward
6857 if self.is_physically_visible(visible_from, candidate) =>
6858 {
6859 saw_forward = true
6860 }
6861 _ => return None,
6862 }
6863 }
6864 if saw_forward { full } else { None }
6865 }
6866
6867 pub fn target_preserving_reference_namespace(
6868 &self,
6869 analyzer: &CppGraphSource<'_>,
6870 file: &ProjectFile,
6871 identifier: &str,
6872 target: &CodeUnit,
6873 ) -> Option<Vec<String>> {
6874 let mut namespace = None;
6875 for candidate in self.visible_identifier_candidates(file, identifier) {
6876 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6877 {
6878 continue;
6879 }
6880 if !(same_visible_symbol(candidate, target)
6881 || self.compatible_primary_template_redeclarations(candidate, target)
6882 || declared_type_alias(analyzer, candidate)
6883 && self.structured_alias_primary_preserves_target(
6884 analyzer, file, candidate, target,
6885 ))
6886 {
6887 continue;
6888 }
6889 if namespace
6890 .as_ref()
6891 .is_some_and(|existing| existing != candidate.package_name())
6892 {
6893 return None;
6894 }
6895 namespace = Some(candidate.package_name().to_string());
6896 }
6897 let namespace = namespace?;
6898 Some(
6899 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6900 brokk_bifrost_core::analyzer::Language::Cpp,
6901 &namespace,
6902 ),
6903 )
6904 }
6905
6906 pub fn resolve_imported_type_candidate(
6907 &self,
6908 analyzer: &CppGraphSource<'_>,
6909 file: &ProjectFile,
6910 target: &CodeUnit,
6911 target_components: &[String],
6912 direct_target: Option<&CodeUnit>,
6913 preserve_alias: bool,
6914 ) -> LexicalTypeResolution {
6915 let candidates = [target];
6916 let resolution = if preserve_alias {
6917 TypeCandidateResolution::PreserveAlias
6918 } else {
6919 direct_target.map_or(
6920 TypeCandidateResolution::Canonical,
6921 TypeCandidateResolution::PreserveTarget,
6922 )
6923 };
6924 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6928 Ok(unit) => LexicalTypeResolution::Resolved {
6929 unit,
6930 components: target_components.to_vec(),
6931 candidates: vec![target.clone()],
6932 },
6933 Err(failure) => failure.lexical_resolution(),
6934 }
6935 }
6936
6937 fn resolve_type_components_lexically_inner(
6938 &self,
6939 analyzer: &CppGraphSource<'_>,
6940 file: &ProjectFile,
6941 components: &[String],
6942 global: bool,
6943 lexical_scope: &[String],
6944 resolution: TypeCandidateResolution<'_>,
6945 ) -> LexicalTypeResolution {
6946 if components.is_empty() {
6947 return LexicalTypeResolution::Missing;
6948 }
6949 let mut injected = self.resolve_injected_class_name(
6959 analyzer,
6960 file,
6961 components,
6962 global,
6963 lexical_scope,
6964 resolution,
6965 );
6966 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6967 let prefix_len = qualified.len().saturating_sub(components.len());
6968 if injected
6969 .as_ref()
6970 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6971 {
6972 return injected
6973 .take()
6974 .expect("injected class resolution was just present")
6975 .1;
6976 }
6977 let qualified_name = qualified.join("::");
6978 let candidates = self
6979 .type_candidates(file, &qualified_name)
6980 .into_iter()
6981 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6982 .collect::<Vec<_>>();
6983 if candidates.is_empty() {
6984 if !global && components.len() == 1 {
6985 match self.resolve_inherited_type_for_lexical_scope(
6986 analyzer,
6987 file,
6988 &qualified[..prefix_len],
6989 &components[0],
6990 resolution,
6991 ) {
6992 LexicalTypeResolution::Missing => {}
6993 inherited => return inherited,
6994 }
6995 }
6996 continue;
6997 }
6998 let candidates =
6999 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
7000 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
7001 Ok(unit) => unit,
7002 Err(failure) => return failure.lexical_resolution(),
7003 };
7004 return LexicalTypeResolution::Resolved {
7005 unit,
7006 components: qualified,
7007 candidates: candidates.into_iter().cloned().collect(),
7008 };
7009 }
7010 LexicalTypeResolution::Missing
7011 }
7012
7013 fn resolve_injected_class_name(
7014 &self,
7015 analyzer: &CppGraphSource<'_>,
7016 file: &ProjectFile,
7017 components: &[String],
7018 global: bool,
7019 lexical_scope: &[String],
7020 resolution: TypeCandidateResolution<'_>,
7021 ) -> Option<(usize, LexicalTypeResolution)> {
7022 if global
7023 || components.len() != 1
7024 || file.rel_path().extension().is_some_and(|ext| ext == "c")
7025 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
7026 {
7027 return None;
7028 }
7029 let name = components.first()?;
7030 let mut matches: Vec<&CodeUnit> = Vec::new();
7031 let mut owner_len = 0;
7032 for candidate in self.visible_identifier_candidates(file, name) {
7033 if !candidate.is_class()
7034 || declared_type_alias(analyzer, candidate)
7035 || candidate.identifier() != name
7036 {
7037 continue;
7038 }
7039 let candidate_scope = canonical_cpp_scope_components(candidate);
7040 if candidate_scope.len() > lexical_scope.len()
7041 || !lexical_scope.starts_with(&candidate_scope)
7042 || candidate_scope.last().is_none_or(|last| last != name)
7043 {
7044 continue;
7045 }
7046 if candidate_scope.len() > owner_len {
7047 owner_len = candidate_scope.len();
7048 matches.clear();
7049 }
7050 if candidate_scope.len() == owner_len
7051 && !matches
7052 .iter()
7053 .any(|existing| same_logical_symbol(existing, candidate))
7054 {
7055 matches.push(candidate);
7056 }
7057 }
7058 if matches.is_empty() {
7059 return None;
7060 }
7061 let owner_components = lexical_scope[..owner_len].to_vec();
7068 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
7069 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
7070 Ok(unit) => LexicalTypeResolution::Resolved {
7071 unit,
7072 components: owner_components,
7073 candidates: matches.into_iter().cloned().collect(),
7074 },
7075 Err(failure) => failure.lexical_resolution(),
7076 };
7077 Some((owner_len, resolution))
7078 }
7079
7080 fn resolve_inherited_type_for_lexical_scope(
7081 &self,
7082 analyzer: &CppGraphSource<'_>,
7083 file: &ProjectFile,
7084 lexical_scope: &[String],
7085 name: &str,
7086 resolution: TypeCandidateResolution<'_>,
7087 ) -> LexicalTypeResolution {
7088 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7089 return LexicalTypeResolution::Missing;
7090 };
7091 let lexical_owner_name = lexical_scope.join("::");
7092 if lexical_owner_name.is_empty() {
7093 return LexicalTypeResolution::Missing;
7094 }
7095 let owner_candidates = self
7096 .type_candidates(file, &lexical_owner_name)
7097 .into_iter()
7098 .filter(|candidate| {
7099 canonical_cpp_name_matches(candidate, &lexical_owner_name)
7100 && !declared_type_alias(analyzer, candidate)
7101 })
7102 .collect::<Vec<_>>();
7103 if owner_candidates.is_empty() {
7104 return LexicalTypeResolution::Missing;
7105 }
7106 let physical_owner_candidates = owner_candidates
7111 .iter()
7112 .copied()
7113 .filter(|candidate| candidate.source() == file)
7114 .collect::<Vec<_>>();
7115 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
7116 owner_candidates
7117 } else {
7118 physical_owner_candidates
7119 };
7120 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
7121 return LexicalTypeResolution::Ambiguous;
7122 };
7123
7124 let mut frontier = hierarchy
7125 .get_direct_ancestors(&lexical_owner)
7126 .definite()
7127 .to_vec();
7128 let mut visited_owners = HashSet::default();
7129 while !frontier.is_empty() {
7130 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
7131 let mut next_frontier = Vec::new();
7132 for owner in frontier {
7133 if !visited_owners.insert(owner.fq_name()) {
7134 continue;
7135 }
7136 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
7137 let candidates = self
7138 .type_candidates(file, &qualified_name)
7139 .into_iter()
7140 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
7141 .collect::<Vec<_>>();
7142 if candidates.is_empty() {
7143 for ancestor in hierarchy.get_direct_ancestors(&owner).definite().to_vec() {
7144 if !next_frontier
7145 .iter()
7146 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
7147 {
7148 next_frontier.push(ancestor);
7149 }
7150 }
7151 continue;
7152 }
7153 let candidates =
7154 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
7155 let unit =
7156 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
7157 Ok(unit) => unit,
7158 Err(failure) => return failure.lexical_resolution(),
7159 };
7160 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
7161 }
7162 if let Some((unit, candidates)) = level_matches.first().cloned() {
7163 let Some(first_declaration) = candidates.first() else {
7164 return LexicalTypeResolution::Ambiguous;
7165 };
7166 if !level_matches.iter().all(|(_, declarations)| {
7167 declarations
7168 .iter()
7169 .all(|declaration| same_logical_symbol(first_declaration, declaration))
7170 }) {
7171 return LexicalTypeResolution::Ambiguous;
7172 }
7173 let mut components = lexical_scope.to_vec();
7174 components.push(name.to_string());
7175 return LexicalTypeResolution::Resolved {
7176 unit,
7177 components,
7178 candidates,
7179 };
7180 }
7181 frontier = next_frontier;
7182 }
7183 LexicalTypeResolution::Missing
7184 }
7185
7186 pub fn inherited_injected_class_owner(
7203 &self,
7204 analyzer: &CppGraphSource<'_>,
7205 file: &ProjectFile,
7206 enclosing_owner: &CodeUnit,
7207 injected_name: &str,
7208 ) -> Option<CodeUnit> {
7209 let hierarchy = analyzer.type_hierarchy_provider()?;
7210 let mut frontier = hierarchy
7211 .get_direct_ancestors(enclosing_owner)
7212 .definite()
7213 .to_vec();
7214 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7215 while !frontier.is_empty() {
7216 let mut level_matches = Vec::new();
7217 let mut next_frontier = Vec::new();
7218 for raw_owner in frontier {
7219 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7220 else {
7221 if raw_owner.identifier() == injected_name {
7222 return None;
7223 }
7224 continue;
7225 };
7226 let propagated = propagated_counts.entry(owner.clone()).or_default();
7227 if *propagated == 2 {
7228 continue;
7229 }
7230 *propagated += 1;
7231 if owner.identifier() == injected_name {
7232 level_matches.push(owner.clone());
7233 }
7234 next_frontier.extend(hierarchy.get_direct_ancestors(&owner).definite().to_vec());
7235 }
7236 match level_matches.as_slice() {
7237 [owner] => return Some(owner.clone()),
7238 [_, ..] => return None,
7239 [] => {}
7240 }
7241 frontier = next_frontier;
7242 }
7243 None
7244 }
7245
7246 fn resolve_type_candidates(
7251 &self,
7252 analyzer: &CppGraphSource<'_>,
7253 file: &ProjectFile,
7254 candidates: &[&CodeUnit],
7255 resolution: TypeCandidateResolution<'_>,
7256 ) -> Result<CodeUnit, TypeCandidateFailure> {
7257 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
7258 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
7259 {
7260 return Ok(unit);
7261 }
7262 match resolution {
7263 TypeCandidateResolution::Canonical => {
7264 self.canonical_type_candidate_resolution(analyzer, file, candidates)
7265 }
7266 TypeCandidateResolution::PreserveAlias => {
7267 let same_fqn_alias_family = candidates.len() > 1
7274 && candidates.iter().all(|candidate| {
7275 declared_type_alias(analyzer, candidate)
7276 && same_logical_symbol(candidates[0], candidate)
7277 })
7278 && candidates
7279 .iter()
7280 .any(|candidate| candidate.source() != candidates[0].source());
7281 if same_fqn_alias_family {
7282 let physically_visible = candidates
7283 .iter()
7284 .copied()
7285 .filter(|candidate| self.is_physically_visible(file, candidate))
7286 .collect::<Vec<_>>();
7287 if let Some(alias) = select_alias_spelling_with_agreed_targets(
7297 &physically_visible,
7298 |candidate| self.structured_alias_target(analyzer, candidate),
7299 ) {
7300 return Ok(alias.clone());
7301 }
7302 }
7303 unique_type_candidate_preserving_alias(analyzer, file, candidates)
7304 .ok_or(TypeCandidateFailure::Ambiguous)
7305 }
7306 TypeCandidateResolution::PreserveTarget(target) => self
7307 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
7308 .ok_or(TypeCandidateFailure::Ambiguous),
7309 }
7310 }
7311
7312 fn candidates_for_type_resolution<'b>(
7313 &self,
7314 analyzer: &CppGraphSource<'_>,
7315 file: &ProjectFile,
7316 candidates: &[&'b CodeUnit],
7317 resolution: TypeCandidateResolution<'_>,
7318 ) -> Vec<&'b CodeUnit> {
7319 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
7320 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
7321 if compile_proven.len() == 1 {
7322 return compile_proven;
7323 }
7324 }
7325 candidates.to_vec()
7326 }
7327
7328 fn compile_proven_type_candidates<'b>(
7335 &self,
7336 analyzer: &CppGraphSource<'_>,
7337 file: &ProjectFile,
7338 candidates: &[&'b CodeUnit],
7339 ) -> Vec<&'b CodeUnit> {
7340 let proven = self.compile_proven_guards(file);
7341 if proven.is_empty() {
7342 return Vec::new();
7343 }
7344 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7345 return Vec::new();
7346 };
7347 candidates
7348 .iter()
7349 .copied()
7350 .filter(|candidate| {
7351 let declaration_guards =
7352 declaration_guard_requirements(analyzer, self.cpp, candidate);
7353 if declaration_guards.is_empty() {
7354 return false;
7355 }
7356 if candidate.source() == file {
7357 return declaration_guards.iter().any(|(_, required)| {
7358 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
7359 });
7360 }
7361 declaration_guards.iter().any(|(_, required)| {
7362 self.foreign_declaration_reachable_from_compile_proven_guards(
7363 file,
7364 prepared.as_ref(),
7365 candidate.source(),
7366 required,
7367 usize::MAX,
7368 )
7369 })
7370 })
7371 .collect()
7372 }
7373
7374 pub fn resolve_callable_value_components_lexically(
7375 &self,
7376 analyzer: &CppGraphSource<'_>,
7377 file: &ProjectFile,
7378 owner_components: &[String],
7379 member_name: &str,
7380 global: bool,
7381 lexical_scope: &[String],
7382 ) -> LexicalCallableValueResolution {
7383 if owner_components.is_empty() || member_name.is_empty() {
7384 return LexicalCallableValueResolution::Missing;
7385 }
7386 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7387 let owner_name = qualified_owner.join("::");
7388 let type_candidates = self
7389 .type_candidates(file, &owner_name)
7390 .into_iter()
7391 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7392 .collect::<Vec<_>>();
7393 let resolved_type = if type_candidates.is_empty() {
7394 None
7395 } else {
7396 let Some(unit) =
7397 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7398 else {
7399 return LexicalCallableValueResolution::Ambiguous;
7400 };
7401 Some(unit)
7402 };
7403
7404 let mut qualified_callable = qualified_owner;
7405 qualified_callable.push(member_name.to_string());
7406 let callable_name = qualified_callable.join("::");
7407 let free_function = self
7408 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7409 .into_iter()
7410 .find(|candidate| {
7411 canonical_cpp_name_matches(candidate, &callable_name)
7412 && type_owner_of(analyzer, candidate).is_none()
7413 })
7414 .cloned();
7415
7416 match (resolved_type, free_function) {
7417 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7418 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7419 (None, Some(function)) => {
7420 return LexicalCallableValueResolution::FreeFunction(function);
7421 }
7422 (None, None) => {}
7423 }
7424 }
7425 LexicalCallableValueResolution::Missing
7426 }
7427
7428 fn resolve_type_for_declaration(
7429 &self,
7430 visible_from: &ProjectFile,
7431 declaration: &CodeUnit,
7432 raw_name: &str,
7433 ) -> Option<CodeUnit> {
7434 let normalized = normalize_reference_name(raw_name)?;
7435 if !normalized.contains("::")
7436 && let Some(namespace) = cpp_namespace_for(declaration)
7437 {
7438 for prefix in namespace_prefixes(&namespace) {
7439 let qualified = format!("{prefix}::{normalized}");
7440 if let Some(unit) = self
7441 .type_candidates(visible_from, &qualified)
7442 .into_iter()
7443 .next()
7444 {
7445 return Some(unit.clone());
7446 }
7447 }
7448 }
7449 self.resolve_type(visible_from, raw_name)
7450 }
7451
7452 fn resolve_unique_canonical_type_for_declaration(
7453 &self,
7454 analyzer: &CppGraphSource<'_>,
7455 visible_from: &ProjectFile,
7456 declaration: &CodeUnit,
7457 raw_name: &str,
7458 ) -> Option<CodeUnit> {
7459 let current = self.resolve_defining_type_for_declaration(
7460 analyzer,
7461 visible_from,
7462 declaration,
7463 raw_name,
7464 )?;
7465 self.canonical_type_resolution(analyzer, visible_from, ¤t)
7466 .ok()
7467 }
7468
7469 pub fn canonical_type_unit(
7470 &self,
7471 analyzer: &CppGraphSource<'_>,
7472 visible_from: &ProjectFile,
7473 unit: &CodeUnit,
7474 ) -> Option<CodeUnit> {
7475 self.canonical_type_resolution(analyzer, visible_from, unit)
7476 .ok()
7477 }
7478
7479 pub fn canonical_type_unit_in_context(
7487 &self,
7488 analyzer: &CppGraphSource<'_>,
7489 visible_from: &ProjectFile,
7490 reference: Node<'_>,
7491 unit: &CodeUnit,
7492 ) -> Option<CodeUnit> {
7493 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7494 {
7495 return None;
7496 }
7497 self.canonical_type_resolution(analyzer, visible_from, unit)
7498 .ok()
7499 }
7500
7501 pub fn guarded_alias_selection_at_reference(
7517 &self,
7518 analyzer: &CppGraphSource<'_>,
7519 file: &ProjectFile,
7520 reference: Node<'_>,
7521 candidates: &[CodeUnit],
7522 ) -> Option<GuardedAliasSelection> {
7523 const MAX_GUARDED_ALIAS_STEPS: usize = 64;
7524 let mut conditional = false;
7525 let mut complete = true;
7526 let mut selected = Vec::new();
7527 let mut terminals: Vec<(CodeUnit, bool)> = Vec::new();
7528 let mut visited = HashSet::default();
7529 let mut pending = candidates
7532 .iter()
7533 .map(|candidate| (candidate.clone(), true, 0, true))
7534 .collect::<Vec<_>>();
7535 let mut steps = 0;
7536 while let Some((unit, proven, depth, top_level)) = pending.pop() {
7537 if !visited.insert((unit.clone(), proven)) {
7538 continue;
7539 }
7540 steps += 1;
7541 if steps > MAX_GUARDED_ALIAS_STEPS || depth > MAX_COMPARABLE_ALIAS_HOPS {
7542 return None;
7543 }
7544 let arms = match self.conditional_alias_arms(analyzer, file, &unit) {
7545 Some(arms) => {
7546 conditional = true;
7547 let evidence = arms
7548 .into_iter()
7549 .map(|arm| {
7550 let evidence = self.alias_arm_evidence(analyzer, file, &arm, reference);
7551 (arm, evidence)
7552 })
7553 .collect::<Vec<_>>();
7554 let implied = evidence
7558 .iter()
7559 .any(|(_, evidence)| *evidence == AliasArmEvidence::Implied);
7560 for (arm, _) in &evidence {
7561 visited.insert((arm.clone(), proven));
7562 }
7563 evidence
7564 .into_iter()
7565 .filter_map(|(arm, evidence)| match evidence {
7566 AliasArmEvidence::Implied => Some((arm, proven)),
7567 AliasArmEvidence::Compatible if !implied => Some((arm, false)),
7568 AliasArmEvidence::Compatible | AliasArmEvidence::Contradicted => None,
7569 })
7570 .collect::<Vec<_>>()
7571 }
7572 None => vec![(unit, proven)],
7573 };
7574 for (arm, proven) in arms {
7575 if top_level {
7576 selected.push(arm.clone());
7577 }
7578 match self.structured_alias_target(analyzer, &arm) {
7579 None | Some(StructuredAliasTarget::Builtin) if arm.is_class() => {
7580 terminals.push((arm, proven));
7581 }
7582 None | Some(StructuredAliasTarget::Builtin) => complete = false,
7583 Some(target) => {
7584 match self.structured_alias_target_resolution(file, &arm, &target) {
7585 Ok(next) => pending.push((next, proven, depth + 1, false)),
7586 Err(_) => complete = false,
7587 }
7588 }
7589 }
7590 }
7591 }
7592 if !conditional {
7593 return None;
7594 }
7595 let mut targets: Vec<CodeUnit> = Vec::new();
7596 for (terminal, _) in &terminals {
7597 if !targets
7598 .iter()
7599 .any(|existing| same_visible_symbol(existing, terminal))
7600 {
7601 targets.push(terminal.clone());
7602 }
7603 }
7604 if complete
7605 && let [unit] = targets.as_slice()
7606 && terminals.iter().all(|(_, proven)| *proven)
7607 {
7608 return Some(GuardedAliasSelection::Proven {
7609 unit: unit.clone(),
7610 candidates: selected,
7611 });
7612 }
7613 Some(GuardedAliasSelection::Alternatives(targets))
7614 }
7615
7616 fn conditional_alias_arms(
7619 &self,
7620 analyzer: &CppGraphSource<'_>,
7621 file: &ProjectFile,
7622 unit: &CodeUnit,
7623 ) -> Option<Vec<CodeUnit>> {
7624 if !declared_type_alias(analyzer, unit) || self.cpp_template_metadata.contains_key(unit) {
7625 return None;
7626 }
7627 let mut arms = Vec::new();
7628 for candidate in self
7629 .visible_identifier_candidates(file, unit.identifier())
7630 .filter(|candidate| {
7631 candidate.kind() == unit.kind()
7632 && candidate.fq_name() == unit.fq_name()
7633 && declared_type_alias(analyzer, candidate)
7634 })
7635 {
7636 if self.cpp_template_metadata.contains_key(candidate) {
7637 return None;
7638 }
7639 if !arms.contains(candidate) {
7640 arms.push(candidate.clone());
7641 }
7642 }
7643 let refs = arms.iter().collect::<Vec<_>>();
7644 (arms.len() > 1
7645 && select_alias_spelling_with_agreed_targets(&refs, |candidate| {
7646 self.structured_alias_target(analyzer, candidate)
7647 })
7648 .is_none())
7649 .then_some(arms)
7650 }
7651
7652 fn alias_arm_evidence(
7656 &self,
7657 analyzer: &CppGraphSource<'_>,
7658 file: &ProjectFile,
7659 arm: &CodeUnit,
7660 reference: Node<'_>,
7661 ) -> AliasArmEvidence {
7662 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7663 return AliasArmEvidence::Compatible;
7664 };
7665 let effective = self.effective_reference_guards(
7666 file,
7667 preprocessor_guard_environment(reference, prepared.source()).as_ref(),
7668 );
7669 let requirements = declaration_guard_requirements(analyzer, self.cpp, arm);
7670 if requirements
7671 .iter()
7672 .any(|(_, required)| guard_requirements_hold_at_reference(required, effective.as_ref()))
7673 && self.external_type_candidate_visible_in_context(analyzer, file, arm, reference)
7674 {
7675 AliasArmEvidence::Implied
7676 } else if requirements.is_empty()
7677 || requirements
7678 .iter()
7679 .any(|(_, required)| guards_compatible_at_reference(required, effective.as_ref()))
7680 {
7681 AliasArmEvidence::Compatible
7682 } else {
7683 AliasArmEvidence::Contradicted
7684 }
7685 }
7686
7687 fn canonical_type_resolution(
7695 &self,
7696 analyzer: &CppGraphSource<'_>,
7697 visible_from: &ProjectFile,
7698 unit: &CodeUnit,
7699 ) -> Result<CodeUnit, TypeCandidateFailure> {
7700 let mut current = unit.clone();
7701 let mut seen_aliases = HashSet::default();
7702 loop {
7703 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7704 return current
7705 .is_class()
7706 .then_some(current)
7707 .ok_or(TypeCandidateFailure::Unresolvable);
7708 };
7709 if matches!(target, StructuredAliasTarget::Builtin) {
7710 return current
7711 .is_class()
7712 .then_some(current)
7713 .ok_or(TypeCandidateFailure::Unresolvable);
7714 }
7715 if !seen_aliases.insert(current.clone()) {
7716 return Err(TypeCandidateFailure::Unresolvable);
7717 }
7718 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7719 }
7720 }
7721
7722 pub fn canonical_visible_full_type_unit(
7723 &self,
7724 analyzer: &CppGraphSource<'_>,
7725 visible_from: &ProjectFile,
7726 unit: &CodeUnit,
7727 ) -> Option<CodeUnit> {
7728 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7729 if cpp_class_declaration_strength(analyzer, &canonical)
7730 != CppClassDeclarationStrength::Forward
7731 {
7732 return Some(canonical);
7733 }
7734 let mut full = Vec::new();
7735 for candidate in self
7736 .visible_identifier_candidates(visible_from, canonical.identifier())
7737 .filter(|candidate| {
7738 candidate.is_class()
7739 && candidate.fq_name() == canonical.fq_name()
7740 && cpp_class_declaration_strength(analyzer, candidate)
7741 == CppClassDeclarationStrength::Full
7742 })
7743 {
7744 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7745 full.push(candidate.clone());
7746 }
7747 }
7748 match full.len() {
7749 0 => Some(canonical),
7750 1 => full.pop(),
7751 _ => None,
7752 }
7753 }
7754
7755 fn resolve_structured_alias_target(
7756 &self,
7757 visible_from: &ProjectFile,
7758 declaration: &CodeUnit,
7759 target: &StructuredAliasTarget,
7760 ) -> Option<CodeUnit> {
7761 self.structured_alias_target_resolution(visible_from, declaration, target)
7762 .ok()
7763 }
7764
7765 fn structured_alias_target_resolution(
7766 &self,
7767 visible_from: &ProjectFile,
7768 declaration: &CodeUnit,
7769 target: &StructuredAliasTarget,
7770 ) -> Result<CodeUnit, TypeCandidateFailure> {
7771 let primary =
7772 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7773 let StructuredAliasTarget::Named { arguments, .. } = target else {
7774 return Err(TypeCandidateFailure::Unresolvable);
7775 };
7776 match arguments {
7777 Some(arguments) => self
7778 .resolve_template_arguments(visible_from, primary, arguments)
7779 .map_err(|error| match error {
7780 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7781 TypeCandidateFailure::Ambiguous
7782 }
7783 _ => TypeCandidateFailure::Unresolvable,
7784 }),
7785 None => Ok(primary),
7786 }
7787 }
7788
7789 fn resolve_structured_alias_primary(
7790 &self,
7791 visible_from: &ProjectFile,
7792 declaration: &CodeUnit,
7793 target: &StructuredAliasTarget,
7794 ) -> Option<CodeUnit> {
7795 self.structured_alias_primary_resolution(visible_from, declaration, target)
7796 .ok()
7797 }
7798
7799 fn structured_alias_primary_resolution(
7800 &self,
7801 visible_from: &ProjectFile,
7802 declaration: &CodeUnit,
7803 target: &StructuredAliasTarget,
7804 ) -> Result<CodeUnit, TypeCandidateFailure> {
7805 let StructuredAliasTarget::Named {
7806 components, global, ..
7807 } = target
7808 else {
7809 return Err(TypeCandidateFailure::Unresolvable);
7810 };
7811 let qualified = components.join("::");
7812 let candidates = if *global {
7813 let mut candidates = self.type_candidates(visible_from, &qualified);
7820 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7821 candidates
7822 } else {
7823 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7824 };
7825 logical_type_candidate(candidates)
7826 }
7827
7828 pub fn structured_alias_primary_preserves_target(
7829 &self,
7830 analyzer: &CppGraphSource<'_>,
7831 visible_from: &ProjectFile,
7832 candidate: &CodeUnit,
7833 target: &CodeUnit,
7834 ) -> bool {
7835 let mut current = candidate.clone();
7836 let mut seen = HashSet::default();
7837 let mut matched_target = false;
7838 loop {
7839 if same_visible_symbol(¤t, target)
7840 || self.compatible_primary_template_redeclarations(¤t, target)
7841 {
7842 matched_target = true;
7843 }
7844 if !seen.insert(current.clone()) {
7845 return false;
7846 }
7847 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7848 return matched_target;
7849 };
7850 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7851 return matched_target;
7852 };
7853 let Some(primary) =
7854 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7855 else {
7856 return matched_target;
7862 };
7863 current = primary;
7864 }
7865 }
7866
7867 fn lexical_type_candidates_match_target(
7868 &self,
7869 analyzer: &CppGraphSource<'_>,
7870 unit: &CodeUnit,
7871 candidates: &[CodeUnit],
7872 target: &CodeUnit,
7873 ) -> bool {
7874 same_visible_symbol(unit, target)
7875 || self.same_template_member_identity(analyzer, unit, target)
7876 || candidates.iter().any(|candidate| {
7877 same_visible_symbol(candidate, target)
7878 || self.same_template_member_identity(analyzer, candidate, target)
7879 })
7880 }
7881
7882 pub fn structured_class_alias_resolves_to_target(
7883 &self,
7884 analyzer: &CppGraphSource<'_>,
7885 visible_from: &ProjectFile,
7886 alias: &CodeUnit,
7887 target: &CodeUnit,
7888 ) -> bool {
7889 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7890 return false;
7891 };
7892 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7893 return false;
7894 };
7895 let StructuredAliasTarget::Named {
7896 components, global, ..
7897 } = &alias_target
7898 else {
7899 return false;
7900 };
7901 let lexical_scope = canonical_cpp_scope_components(&owner);
7902 match self.resolve_type_components_lexically_for_target(
7903 analyzer,
7904 visible_from,
7905 components,
7906 *global,
7907 &lexical_scope,
7908 target,
7909 ) {
7910 LexicalTypeResolution::Resolved {
7911 unit, candidates, ..
7912 } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7913 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7914 self.structured_alias_primary_preserves_target(
7915 analyzer,
7916 visible_from,
7917 alias,
7918 target,
7919 ) || self.flattened_macro_namespace_alias_target_matches(
7920 analyzer,
7921 visible_from,
7922 alias,
7923 &alias_target,
7924 target,
7925 )
7926 }
7927 }
7928 }
7929
7930 pub fn structured_class_alias_path_preserves_target(
7938 &self,
7939 analyzer: &CppGraphSource<'_>,
7940 visible_from: &ProjectFile,
7941 alias: &CodeUnit,
7942 target: &CodeUnit,
7943 ) -> bool {
7944 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7945 return false;
7946 };
7947 let Some(StructuredAliasTarget::Named {
7948 components, global, ..
7949 }) = self.structured_alias_target(analyzer, alias)
7950 else {
7951 return false;
7952 };
7953 let lexical_scope = canonical_cpp_scope_components(&owner);
7954 (1..components.len()).rev().any(|component_count| {
7955 matches!(
7956 self.resolve_type_components_lexically_for_target(
7957 analyzer,
7958 visible_from,
7959 &components[..component_count],
7960 global,
7961 &lexical_scope,
7962 target,
7963 ),
7964 LexicalTypeResolution::Resolved {
7965 ref unit,
7966 ref candidates,
7967 ..
7968 } if self.lexical_type_candidates_match_target(
7969 analyzer,
7970 unit,
7971 candidates,
7972 target,
7973 )
7974 )
7975 })
7976 }
7977
7978 fn flattened_macro_namespace_alias_target_matches(
7979 &self,
7980 analyzer: &CppGraphSource<'_>,
7981 visible_from: &ProjectFile,
7982 alias: &CodeUnit,
7983 alias_target: &StructuredAliasTarget,
7984 target: &CodeUnit,
7985 ) -> bool {
7986 let StructuredAliasTarget::Named {
7987 components,
7988 global: false,
7989 arguments: None,
7990 } = alias_target
7991 else {
7992 return false;
7993 };
7994 let Some((target_name, namespace_components)) = components.split_last() else {
7995 return false;
7996 };
7997 if namespace_components.is_empty()
7998 || target_name != target.identifier()
7999 || alias.source() != target.source()
8000 || alias.source() != visible_from
8001 || !target.is_class()
8002 || declared_type_alias(analyzer, target)
8003 {
8004 return false;
8005 }
8006 if self
8007 .resolve_structured_alias_target(visible_from, alias, alias_target)
8008 .is_some()
8009 {
8010 return false;
8011 }
8012
8013 let Some(alias_start) = analyzer
8014 .declaration_source_occurrences(alias)
8015 .into_iter()
8016 .flatten()
8017 .map(|occurrence| occurrence.range.start_byte)
8018 .min()
8019 else {
8020 return false;
8021 };
8022 let has_matching_declaration = analyzer
8023 .declaration_source_occurrences(target)
8024 .into_iter()
8025 .flatten()
8026 .any(|occurrence| {
8027 occurrence.range.end_byte <= alias_start
8028 && occurrence.fact.flattened_macro_namespace.as_deref()
8029 == Some(namespace_components)
8030 });
8031 if !has_matching_declaration {
8032 return false;
8033 }
8034
8035 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
8036 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
8037 guard_requirement_sets_match(&alias_guards, &target_guards)
8038 }
8039
8040 pub fn template_alias_arguments_preserve_target(
8041 &self,
8042 analyzer: &CppGraphSource<'_>,
8043 visible_from: &ProjectFile,
8044 alias: &CodeUnit,
8045 arguments: &[CppTemplateExpression],
8046 target: &CodeUnit,
8047 ) -> bool {
8048 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
8049 return false;
8050 };
8051 if metadata.alias_target.is_none()
8052 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
8053 {
8054 return false;
8055 }
8056 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
8057 }
8058
8059 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
8060 self.cpp_template_metadata
8061 .get(unit)
8062 .is_some_and(CppTemplateMetadata::is_primary)
8063 }
8064
8065 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
8066 self.cpp_template_metadata
8067 .get(unit)
8068 .is_some_and(CppTemplateMetadata::is_specialization)
8069 }
8070
8071 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
8072 same_visible_symbol(left, right)
8073 || self.compatible_primary_template_redeclarations(left, right)
8074 }
8075
8076 pub fn same_template_member_identity(
8077 &self,
8078 analyzer: &CppGraphSource<'_>,
8079 left: &CodeUnit,
8080 right: &CodeUnit,
8081 ) -> bool {
8082 if same_visible_symbol(left, right) {
8083 return true;
8084 }
8085 if left.kind() != right.kind()
8086 || left.identifier() != right.identifier()
8087 || left.signature() != right.signature()
8088 {
8089 return false;
8090 }
8091 let (Some(left_owner), Some(right_owner)) =
8092 (analyzer.parent_of(left), analyzer.parent_of(right))
8093 else {
8094 return false;
8095 };
8096 left_owner.is_class()
8097 && right_owner.is_class()
8098 && self.same_template_owner_identity(&left_owner, &right_owner)
8099 }
8100
8101 fn unique_canonical_type_candidate(
8102 &self,
8103 analyzer: &CppGraphSource<'_>,
8104 visible_from: &ProjectFile,
8105 candidates: &[&CodeUnit],
8106 ) -> Option<CodeUnit> {
8107 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
8108 .ok()
8109 }
8110
8111 fn canonical_type_candidate_resolution(
8112 &self,
8113 analyzer: &CppGraphSource<'_>,
8114 visible_from: &ProjectFile,
8115 candidates: &[&CodeUnit],
8116 ) -> Result<CodeUnit, TypeCandidateFailure> {
8117 let mut canonical = Vec::new();
8118 for candidate in candidates {
8119 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
8120 if canonical
8121 .iter()
8122 .any(|existing| same_visible_symbol(existing, &resolved))
8123 {
8124 continue;
8125 }
8126 if let Some(existing) = canonical.iter_mut().find(|existing| {
8127 self.compatible_primary_template_redeclarations(existing, &resolved)
8128 }) {
8129 if matches!(
8138 (
8139 cpp_class_declaration_strength(analyzer, existing),
8140 cpp_class_declaration_strength(analyzer, &resolved),
8141 ),
8142 (
8143 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
8144 CppClassDeclarationStrength::Full,
8145 ) | (
8146 CppClassDeclarationStrength::Unknown,
8147 CppClassDeclarationStrength::Forward,
8148 )
8149 ) {
8150 *existing = resolved;
8151 }
8152 continue;
8153 }
8154 canonical.push(resolved);
8155 if canonical.len() > 1 {
8156 return Err(TypeCandidateFailure::Ambiguous);
8157 }
8158 }
8159 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
8160 }
8161
8162 pub fn unique_type_candidate_preserving_target(
8163 &self,
8164 analyzer: &CppGraphSource<'_>,
8165 visible_from: &ProjectFile,
8166 candidates: &[&CodeUnit],
8167 target: &CodeUnit,
8168 ) -> Option<CodeUnit> {
8169 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
8180 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8181 {
8182 return Some(target.clone());
8183 }
8184 let mut resolved_candidates = Vec::new();
8185 for candidate in candidates {
8186 let Some(resolved) =
8192 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
8193 else {
8194 continue;
8195 };
8196 if resolved_candidates
8197 .iter()
8198 .any(|existing| same_visible_symbol(existing, &resolved))
8199 {
8200 continue;
8201 }
8202 resolved_candidates.push(resolved);
8203 }
8204 match resolved_candidates.as_slice() {
8205 [] => None,
8206 [single] => Some(single.clone()),
8207 _ => self
8212 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
8213 .map(|_| target.clone()),
8214 }
8215 }
8216
8217 pub fn same_fqn_type_spelling_for_target<'b>(
8234 &self,
8235 analyzer: &CppGraphSource<'_>,
8236 visible_from: &ProjectFile,
8237 candidates: &[&'b CodeUnit],
8238 target: &CodeUnit,
8239 ) -> Option<&'b CodeUnit> {
8240 let [first, rest @ ..] = candidates else {
8241 return None;
8242 };
8243 if rest.is_empty()
8244 || !rest.iter().all(|candidate| {
8245 candidate.kind() == first.kind()
8246 && candidate.fq_name() == first.fq_name()
8247 && candidate.source() == first.source()
8248 })
8249 {
8250 return None;
8251 }
8252 candidates
8253 .iter()
8254 .copied()
8255 .find(|candidate| same_symbol(candidate, target))
8256 .or_else(|| {
8257 candidates.iter().copied().find(|candidate| {
8258 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
8259 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
8260 })
8261 })
8262 }
8263
8264 pub fn alternate_same_fqn_type_declarations(
8265 &self,
8266 analyzer: &CppGraphSource<'_>,
8267 candidates: &[&CodeUnit],
8268 target: &CodeUnit,
8269 ) -> bool {
8270 let Some(first) = candidates.first() else {
8271 return false;
8272 };
8273 let same_api = first.kind() == target.kind()
8274 && first.fq_name() == target.fq_name()
8275 && first.source() == target.source()
8276 && candidates.iter().all(|candidate| {
8277 candidate.kind() == target.kind()
8278 && candidate.fq_name() == target.fq_name()
8279 && candidate.source() == target.source()
8280 })
8281 && candidates
8282 .iter()
8283 .any(|candidate| same_symbol(candidate, target))
8284 && candidates
8285 .iter()
8286 .any(|candidate| !same_logical_symbol(candidate, target));
8287 if !same_api {
8288 return false;
8289 }
8290
8291 let requirements = candidates
8292 .iter()
8293 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8294 .collect::<Vec<_>>();
8295 requirements.len() > 1
8296 && requirements
8297 .iter()
8298 .all(|requirement| !requirement.is_empty())
8299 && requirements.iter().enumerate().all(|(index, left)| {
8300 requirements[index + 1..].iter().all(|right| {
8301 left.iter().all(|(_, left_guards)| {
8302 right.iter().all(|(_, right_guards)| {
8303 merge_preprocessor_guards(left_guards, right_guards).is_none()
8304 })
8305 })
8306 })
8307 })
8308 }
8309
8310 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
8311 let mut pending = vec![terms.to_vec()];
8312 while let Some(branch_terms) = pending.pop() {
8313 let mut normalized = Vec::new();
8314 let mut covers_branch = false;
8315 for term in branch_terms {
8316 if term.iter().any(|guard| term.contains(&guard.negated())) {
8317 continue;
8318 }
8319 if term.is_empty() {
8320 covers_branch = true;
8321 break;
8322 }
8323 if !normalized.iter().any(|existing| existing == &term) {
8324 normalized.push(term);
8325 }
8326 }
8327 if covers_branch {
8328 continue;
8329 }
8330 let Some(split_guard) = normalized
8331 .iter()
8332 .flat_map(|term| term.iter())
8333 .next()
8334 .cloned()
8335 else {
8336 return false;
8337 };
8338 let negated_guard = split_guard.negated();
8339 let mut when_defined = Vec::new();
8340 let mut when_undefined = Vec::new();
8341 for term in normalized {
8342 if term.contains(&negated_guard) {
8343 } else if term.contains(&split_guard) {
8345 let mut reduced = term.clone();
8346 reduced.remove(&split_guard);
8347 when_defined.push(reduced);
8348 } else {
8349 when_defined.push(term.clone());
8350 }
8351 if term.contains(&split_guard) {
8352 } else if term.contains(&negated_guard) {
8354 let mut reduced = term;
8355 reduced.remove(&negated_guard);
8356 when_undefined.push(reduced);
8357 } else {
8358 when_undefined.push(term);
8359 }
8360 }
8361 pending.push(when_defined);
8362 pending.push(when_undefined);
8363 }
8364 true
8365 }
8366
8367 fn declarations_share_exhaustive_conditional_family(
8376 &self,
8377 analyzer: &CppGraphSource<'_>,
8378 candidates: &[&CodeUnit],
8379 ) -> Option<(usize, usize)> {
8380 let mut family_range = None;
8381 for candidate in candidates {
8382 let mut candidate_family = None;
8383 for occurrence in analyzer.declaration_source_occurrences(candidate)? {
8384 let key = occurrence.exhaustive_family?;
8385 if candidate_family.is_some_and(|existing| existing != key) {
8386 return None;
8387 }
8388 candidate_family = Some(key);
8389 }
8390 let candidate_family = candidate_family?;
8391 if family_range.is_some_and(|existing| existing != candidate_family) {
8392 return None;
8393 }
8394 family_range = Some(candidate_family);
8395 }
8396 family_range
8397 }
8398
8399 pub fn complementary_same_fqn_type_declarations(
8400 &self,
8401 analyzer: &CppGraphSource<'_>,
8402 candidates: &[&CodeUnit],
8403 target: &CodeUnit,
8404 ) -> bool {
8405 if candidates.len() < 2
8406 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8407 || self
8408 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
8409 .is_none()
8410 {
8411 return false;
8412 }
8413 Self::preprocessor_guard_terms_cover_all_paths(
8414 &self.declaration_family_guard_terms(analyzer, candidates),
8415 )
8416 }
8417
8418 fn declaration_family_guard_terms(
8419 &self,
8420 analyzer: &CppGraphSource<'_>,
8421 candidates: &[&CodeUnit],
8422 ) -> Vec<HashSet<PreprocessorGuard>> {
8423 candidates
8424 .iter()
8425 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8426 .map(|(_, guards)| guards)
8427 .collect()
8428 }
8429
8430 fn exhaustive_guard_family_activation(
8446 &self,
8447 analyzer: &CppGraphSource<'_>,
8448 candidate: &CodeUnit,
8449 reference: &CallableReferenceContext<'_>,
8450 ) -> Option<usize> {
8451 if !analyzer
8454 .declaration_source_occurrences(candidate)?
8455 .iter()
8456 .any(|occurrence| occurrence.fact.callable_activation.is_some())
8457 {
8458 return None;
8459 }
8460 let family = self
8461 .visible_identifier_candidates(reference.file, candidate.identifier())
8462 .filter(|peer| {
8463 peer.kind() == candidate.kind()
8464 && peer.fq_name() == candidate.fq_name()
8465 && peer.source() == candidate.source()
8466 })
8467 .collect::<Vec<_>>();
8468 let (_, family_end) =
8469 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
8470 if !Self::preprocessor_guard_terms_cover_all_paths(
8471 &self.declaration_family_guard_terms(analyzer, &family),
8472 ) {
8473 return None;
8474 }
8475 let effective = self.effective_reference_guards(reference.file, reference.guards());
8483 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
8484 .iter()
8485 .any(|(_, guards)| guards_compatible_at_reference(guards, effective.as_ref()))
8486 {
8487 return None;
8488 }
8489 (first_declaration_byte(analyzer, candidate)?
8490 == family
8491 .iter()
8492 .filter_map(|peer| first_declaration_byte(analyzer, peer))
8493 .min()?)
8494 .then_some(family_end)
8495 }
8496
8497 fn type_candidate_preserving_target(
8498 &self,
8499 analyzer: &CppGraphSource<'_>,
8500 visible_from: &ProjectFile,
8501 candidate: &CodeUnit,
8502 target: &CodeUnit,
8503 ) -> Option<CodeUnit> {
8504 let mut current = candidate.clone();
8505 let mut matched_target = same_visible_symbol(¤t, target)
8506 || self.compatible_primary_template_redeclarations(¤t, target);
8507 let mut seen = HashSet::default();
8508 loop {
8509 if !seen.insert(current.clone()) {
8510 return None;
8511 }
8512 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8513 return matched_target
8514 .then(|| target.clone())
8515 .or_else(|| current.is_class().then_some(current));
8516 };
8517 if self.flattened_macro_namespace_alias_target_matches(
8518 analyzer,
8519 visible_from,
8520 ¤t,
8521 &alias_target,
8522 target,
8523 ) {
8524 return Some(target.clone());
8525 }
8526 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8527 return matched_target
8528 .then(|| target.clone())
8529 .or_else(|| current.is_class().then_some(current));
8530 }
8531 if !self.cpp_template_metadata.contains_key(¤t)
8539 && let Some(primary) =
8540 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8541 && (same_visible_symbol(&primary, target)
8542 || self.compatible_primary_template_redeclarations(&primary, target))
8543 {
8544 return Some(target.clone());
8545 }
8546 if same_visible_symbol(¤t, target) {
8547 return Some(target.clone());
8548 }
8549 if self.cpp_template_metadata.contains_key(¤t) {
8550 return None;
8551 }
8552 let Some(next) =
8553 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
8554 else {
8555 return matched_target.then(|| target.clone());
8556 };
8557 current = next;
8558 matched_target |= same_visible_symbol(¤t, target)
8559 || self.compatible_primary_template_redeclarations(¤t, target);
8560 }
8561 }
8562
8563 fn compatible_primary_template_redeclarations(
8564 &self,
8565 left: &CodeUnit,
8566 right: &CodeUnit,
8567 ) -> bool {
8568 let (Some(left_metadata), Some(right_metadata)) = (
8569 self.cpp_template_metadata.get(left),
8570 self.cpp_template_metadata.get(right),
8571 ) else {
8572 return false;
8573 };
8574 left_metadata.primary_fq_name == right_metadata.primary_fq_name
8575 && left_metadata.is_primary()
8576 && right_metadata.is_primary()
8577 && cpp_reconcile_primary_template_parameters(
8578 &[(left, left_metadata), (right, right_metadata)],
8579 right,
8580 )
8581 .is_some()
8582 }
8583
8584 fn alias_candidate_may_preserve_target(
8585 &self,
8586 analyzer: &CppGraphSource<'_>,
8587 visible_from: &ProjectFile,
8588 candidate: &CodeUnit,
8589 target: &CodeUnit,
8590 ) -> bool {
8591 let mut pending = vec![candidate.clone()];
8595 let mut seen = HashSet::default();
8596 while let Some(current) = pending.pop() {
8597 if same_visible_symbol(¤t, target)
8598 || self.compatible_primary_template_redeclarations(¤t, target)
8599 {
8600 return true;
8601 }
8602 if self.cpp_template_metadata.contains_key(¤t) {
8603 return true;
8604 }
8605 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8606 continue;
8607 };
8608 let StructuredAliasTarget::Named {
8609 components,
8610 global,
8611 arguments,
8612 } = alias_target
8613 else {
8614 continue;
8615 };
8616 if arguments.is_some() || !seen.insert(current.clone()) {
8617 return true;
8618 }
8619 let qualified = components.join("::");
8620 let next = if global {
8621 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8622 } else {
8623 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8624 };
8625 let Some(next) = next else {
8626 return true;
8627 };
8628 match self.conditional_alias_arms(analyzer, visible_from, &next) {
8629 Some(arms) => pending.extend(arms),
8630 None => pending.push(next),
8631 }
8632 }
8633 false
8634 }
8635
8636 fn alias_candidate_structurally_reaches_target(
8637 &self,
8638 analyzer: &CppGraphSource<'_>,
8639 visible_from: &ProjectFile,
8640 candidate: &CodeUnit,
8641 target: &CodeUnit,
8642 ) -> bool {
8643 let mut pending = vec![candidate.clone()];
8646 let mut seen = HashSet::default();
8647 while let Some(current) = pending.pop() {
8648 if same_visible_symbol(¤t, target)
8649 || self.compatible_primary_template_redeclarations(¤t, target)
8650 || self
8651 .cpp_template_metadata
8652 .get(¤t)
8653 .zip(self.cpp_template_metadata.get(target))
8654 .is_some_and(|(current, target)| {
8655 current.primary_fq_name == target.primary_fq_name
8656 })
8657 {
8658 return true;
8659 }
8660 if !seen.insert(current.clone()) {
8661 continue;
8662 }
8663 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8664 continue;
8665 };
8666 if let StructuredAliasTarget::Named { components, .. } = &alias_target
8667 && components[..components.len().saturating_sub(1)]
8668 .iter()
8669 .any(|component| {
8670 self.type_reference_component_directly_names_target(component, target)
8671 })
8672 && self.structured_class_alias_path_preserves_target(
8673 analyzer,
8674 visible_from,
8675 ¤t,
8676 target,
8677 )
8678 {
8679 return true;
8680 }
8681 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8682 continue;
8683 }
8684 let Some(primary) =
8685 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8686 else {
8687 continue;
8688 };
8689 match self.conditional_alias_arms(analyzer, visible_from, &primary) {
8690 Some(arms) => pending.extend(arms),
8691 None => pending.push(primary),
8692 }
8693 }
8694 false
8695 }
8696
8697 pub fn qualified_alias_reference_may_reach_target(
8709 &self,
8710 analyzer: &CppGraphSource<'_>,
8711 file: &ProjectFile,
8712 components: &[String],
8713 global: bool,
8714 target: &CodeUnit,
8715 ) -> bool {
8716 for end in 0..components.len() {
8717 let spelled = &components[..=end];
8718 for candidate in self
8719 .visible_identifier_candidates(file, &components[end])
8720 .filter(|candidate| declared_type_alias(analyzer, candidate))
8721 {
8722 let candidate_components = canonical_cpp_scope_components(candidate);
8723 let shape_matches = if global {
8724 candidate_components == spelled
8725 } else {
8726 candidate_components.ends_with(spelled)
8727 };
8728 if shape_matches
8729 && self.alias_candidate_structurally_reaches_target(
8730 analyzer, file, candidate, target,
8731 )
8732 {
8733 return true;
8734 }
8735 }
8736 }
8737 false
8738 }
8739
8740 fn type_candidates_for_declaration<'b>(
8744 &'b self,
8745 visible_from: &ProjectFile,
8746 declaration: &CodeUnit,
8747 raw_name: &str,
8748 ) -> Vec<&'b CodeUnit> {
8749 let Some(normalized) = normalize_reference_name(raw_name) else {
8750 return Vec::new();
8751 };
8752 if let Some(namespace) = cpp_namespace_for(declaration) {
8753 for prefix in namespace_prefixes(&namespace) {
8754 let qualified = format!("{prefix}::{normalized}");
8755 let candidates = self.type_candidates(visible_from, &qualified);
8756 if !candidates.is_empty() {
8757 return candidates;
8758 }
8759 }
8760 }
8761 self.type_candidates(visible_from, &normalized)
8762 }
8763
8764 fn resolve_unique_type_for_declaration(
8765 &self,
8766 visible_from: &ProjectFile,
8767 declaration: &CodeUnit,
8768 raw_name: &str,
8769 ) -> Option<CodeUnit> {
8770 unique_logical_type_candidate(self.type_candidates_for_declaration(
8771 visible_from,
8772 declaration,
8773 raw_name,
8774 ))
8775 }
8776
8777 fn resolve_defining_type_for_declaration(
8795 &self,
8796 analyzer: &CppGraphSource<'_>,
8797 visible_from: &ProjectFile,
8798 declaration: &CodeUnit,
8799 raw_name: &str,
8800 ) -> Option<CodeUnit> {
8801 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8802 let logical = unique_logical_type_candidate(candidates.clone())?;
8803 let mut defining = candidates.into_iter().filter(|candidate| {
8804 candidate.is_class()
8805 && cpp_class_declaration_strength(analyzer, candidate)
8806 == CppClassDeclarationStrength::Full
8807 });
8808 match (defining.next(), defining.next()) {
8809 (Some(unique_definition), None) => Some(unique_definition.clone()),
8810 _ => Some(logical),
8811 }
8812 }
8813
8814 pub fn resolves_to_type(
8815 &self,
8816 analyzer: &CppGraphSource<'_>,
8817 file: &ProjectFile,
8818 raw_name: &str,
8819 target: &CodeUnit,
8820 ) -> bool {
8821 let Some(normalized) = normalize_reference_name(raw_name) else {
8822 return false;
8823 };
8824 let candidates = self.type_candidates(file, &normalized);
8825 if candidates.is_empty() {
8826 return self.parser_alias_resolves_to_type(file, raw_name, target);
8827 }
8828 let Some(resolved) =
8829 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8830 else {
8831 return false;
8832 };
8833 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8834 }
8835
8836 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8837 let mut current = alias.clone();
8838 let mut visited = HashSet::default();
8839 while visited.insert(current.clone()) {
8840 let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), ¤t)?;
8841 let resolved =
8842 self.resolve_type_for_declaration(current.source(), ¤t, &raw_target)?;
8843 if resolved.is_class() {
8844 return Some(resolved);
8845 }
8846 if !declared_type_alias(&self.cpp_source(), &resolved) {
8847 return None;
8848 }
8849 current = resolved;
8850 }
8851 None
8852 }
8853
8854 pub fn same_logical_callable(
8869 &self,
8870 analyzer: &CppGraphSource<'_>,
8871 left: &CodeUnit,
8872 right: &CodeUnit,
8873 ) -> bool {
8874 if same_logical_symbol(left, right) {
8875 return true;
8876 }
8877 if left.kind() != right.kind()
8878 || !left.is_callable()
8879 || !right.is_callable()
8880 || left.fq_name() != right.fq_name()
8881 {
8882 return false;
8883 }
8884 let (Some(left_comparable), Some(right_comparable)) = (
8885 self.callable_comparable(analyzer, left),
8886 self.callable_comparable(analyzer, right),
8887 ) else {
8888 return false;
8889 };
8890 let binding = match (&left_comparable.template, &right_comparable.template) {
8898 (Some(left_template), Some(right_template))
8899 if left_template.is_templated() || right_template.is_templated() =>
8900 {
8901 match left_template.reconcile(right_template) {
8902 CppTemplateReconciliation::Equivalent(binding) => binding,
8903 CppTemplateReconciliation::Distinct
8904 | CppTemplateReconciliation::Unsupported => return false,
8905 }
8906 }
8907 (Some(_), Some(_)) => CppTemplateBinding::default(),
8908 _ if self.callable_is_template_declaration(analyzer, left)
8909 || self.callable_is_template_declaration(analyzer, right) =>
8910 {
8911 return false;
8912 }
8913 _ => CppTemplateBinding::default(),
8914 };
8915 if left_comparable.suffix != right_comparable.suffix
8920 || left_comparable.shapes.len() != right_comparable.shapes.len()
8921 {
8922 return false;
8923 }
8924 left_comparable
8925 .shapes
8926 .iter()
8927 .zip(right_comparable.shapes.iter())
8928 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8929 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8930 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8931 self.comparable_shapes_agree(analyzer, &binding, left_shape, right_shape)
8932 }
8933 _ => false,
8937 })
8938 }
8939
8940 pub fn same_logical_class_definition(
8953 &self,
8954 analyzer: &CppGraphSource<'_>,
8955 left: &CodeUnit,
8956 right: &CodeUnit,
8957 ) -> bool {
8958 if same_symbol(left, right) {
8959 return true;
8960 }
8961 if !left.is_class() || !right.is_class() || left.fq_name() != right.fq_name() {
8962 return false;
8963 }
8964 let key = if left <= right {
8965 (left.clone(), right.clone())
8966 } else {
8967 (right.clone(), left.clone())
8968 };
8969 visibility_memo_value(&self.class_definition_agreements, &self.reads, key, || {
8970 self.class_definitions_agree(analyzer, left, right)
8971 })
8972 }
8973
8974 fn class_definitions_agree(
8975 &self,
8976 analyzer: &CppGraphSource<'_>,
8977 left: &CodeUnit,
8978 right: &CodeUnit,
8979 ) -> bool {
8980 let complete_definition = |class: &CodeUnit| {
8981 let mut facts = analyzer
8982 .declaration_source_properties(class)?
8983 .into_iter()
8984 .filter(|fact| fact.class_strength == CppClassDeclarationStrength::Full);
8985 let fact = facts.next()?;
8986 facts.next().is_none().then_some(fact)
8989 };
8990 let (Some(left_fact), Some(right_fact)) =
8991 (complete_definition(left), complete_definition(right))
8992 else {
8993 return false;
8994 };
8995 if left_fact.bases != right_fact.bases {
8996 return false;
8997 }
8998 let (Some(left_template), Some(right_template)) = (
8999 self.declaration_template_context(left, left_fact.declaration),
9000 self.declaration_template_context(right, right_fact.declaration),
9001 ) else {
9002 return false;
9003 };
9004 if !matches!(
9005 left_template.reconcile(&right_template),
9006 CppTemplateReconciliation::Equivalent(_)
9007 ) {
9008 return false;
9009 }
9010 let members = |class: &CodeUnit| {
9011 let mut members = analyzer
9012 .direct_children(class)
9013 .into_iter()
9014 .filter(|member| member.source() == class.source())
9015 .map(|member| {
9016 (
9017 member.kind(),
9018 member.identifier().to_owned(),
9019 member.signature().map(str::to_owned),
9020 )
9021 })
9022 .collect::<Vec<_>>();
9023 members.sort();
9024 members
9025 };
9026 members(left) == members(right)
9027 }
9028
9029 pub fn class_definition_bridges_to_file(
9039 &self,
9040 analyzer: &CppGraphSource<'_>,
9041 class: &CodeUnit,
9042 file: &ProjectFile,
9043 header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
9044 ) -> bool {
9045 class.is_class()
9046 && class.source() != file
9047 && crate::identity::cpp_source_path_is_header(class.source())
9048 && analyzer
9049 .index
9050 .definitions_by_structured_name(class.fq(), Language::Cpp)
9051 .into_iter()
9052 .any(|copy| {
9053 copy.is_class()
9057 && copy.source() != class.source()
9058 && copy.source() != file
9059 && crate::identity::cpp_source_path_is_header(copy.source())
9060 && header_body_related(copy.source(), file)
9061 && self.same_logical_class_definition(analyzer, class, ©)
9062 })
9063 }
9064
9065 pub fn member_declaration_bridges_to_body(
9069 &self,
9070 analyzer: &CppGraphSource<'_>,
9071 declaration: &CodeUnit,
9072 body: &CodeUnit,
9073 header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
9074 ) -> bool {
9075 declaration.is_callable()
9076 && self
9077 .cpp
9078 .structural_parent_of(declaration)
9079 .is_some_and(|owner| {
9080 self.class_definition_bridges_to_file(
9081 analyzer,
9082 &owner,
9083 body.source(),
9084 header_body_related,
9085 )
9086 })
9087 }
9088
9089 fn comparable_shapes_agree(
9095 &self,
9096 analyzer: &CppGraphSource<'_>,
9097 binding: &CppTemplateBinding,
9098 left: &CppComparableParameter,
9099 right: &CppComparableParameter,
9100 ) -> bool {
9101 let mut stack = vec![(left.root(), right.root())];
9102 while let Some((left_index, right_index)) = stack.pop() {
9103 match (left.node(left_index), right.node(right_index)) {
9104 (
9105 CppComparableNode::Named {
9106 name: left_name,
9107 primitive: left_primitive,
9108 konst: left_konst,
9109 volatil: left_volatil,
9110 },
9111 CppComparableNode::Named {
9112 name: right_name,
9113 primitive: right_primitive,
9114 konst: right_konst,
9115 volatil: right_volatil,
9116 },
9117 ) => {
9118 if left_konst != right_konst
9119 || left_volatil != right_volatil
9120 || left_primitive != right_primitive
9121 || !self.comparable_names_agree(
9122 analyzer,
9123 binding,
9124 left_name,
9125 right_name,
9126 *left_primitive,
9127 )
9128 {
9129 return false;
9130 }
9131 }
9132 (
9133 CppComparableNode::Pointer {
9134 inner: left_inner,
9135 konst: left_konst,
9136 volatil: left_volatil,
9137 },
9138 CppComparableNode::Pointer {
9139 inner: right_inner,
9140 konst: right_konst,
9141 volatil: right_volatil,
9142 },
9143 ) => {
9144 if left_konst != right_konst || left_volatil != right_volatil {
9145 return false;
9146 }
9147 stack.push((*left_inner, *right_inner));
9148 }
9149 (
9150 CppComparableNode::Reference { inner: left_inner },
9151 CppComparableNode::Reference { inner: right_inner },
9152 )
9153 | (
9154 CppComparableNode::Array { inner: left_inner },
9155 CppComparableNode::Array { inner: right_inner },
9156 ) => stack.push((*left_inner, *right_inner)),
9157 (
9158 CppComparableNode::Generic {
9159 base: left_base,
9160 arguments: left_arguments,
9161 },
9162 CppComparableNode::Generic {
9163 base: right_base,
9164 arguments: right_arguments,
9165 },
9166 ) => {
9167 if left_arguments.len() != right_arguments.len() {
9168 return false;
9169 }
9170 stack.push((*left_base, *right_base));
9171 stack.extend(
9172 left_arguments.iter().zip(right_arguments.iter()).map(
9173 |(left_argument, right_argument)| (*left_argument, *right_argument),
9174 ),
9175 );
9176 }
9177 _ => return false,
9178 }
9179 }
9180 true
9181 }
9182
9183 fn comparable_names_agree(
9198 &self,
9199 analyzer: &CppGraphSource<'_>,
9200 binding: &CppTemplateBinding,
9201 left: &StructuredTypeName,
9202 right: &StructuredTypeName,
9203 primitive: bool,
9204 ) -> bool {
9205 if primitive {
9206 return left.path() == right.path();
9207 }
9208 if !binding.is_empty() {
9209 let (Some(left_head), Some(right_head)) = (left.path().first(), right.path().first())
9210 else {
9211 return false;
9212 };
9213 match (
9214 binding.is_left_parameter(left_head),
9215 binding.is_right_parameter(right_head),
9216 ) {
9217 (true, true) => {
9218 return !left.is_absolute()
9219 && !right.is_absolute()
9220 && binding.binds(right_head, left_head)
9221 && left.path()[1..] == right.path()[1..];
9222 }
9223 (false, false) => {}
9224 _ => return false,
9225 }
9226 }
9227 match (
9228 self.comparable_name_terminal(analyzer, left),
9229 self.comparable_name_terminal(analyzer, right),
9230 ) {
9231 (Some(left_terminal), Some(right_terminal)) => {
9232 same_logical_symbol(&left_terminal, &right_terminal)
9233 }
9234 (None, None) => {
9235 left.path() == right.path() && left.is_absolute() == right.is_absolute()
9236 }
9237 _ => false,
9238 }
9239 }
9240
9241 fn comparable_name_terminal(
9251 &self,
9252 analyzer: &CppGraphSource<'_>,
9253 name: &StructuredTypeName,
9254 ) -> Option<CodeUnit> {
9255 let mut current = self.comparable_name_declaration(analyzer, name)?;
9256 let mut visited = HashSet::default();
9257 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
9258 if !declared_type_alias(analyzer, ¤t) {
9265 return current.is_class().then_some(current);
9266 }
9267 if !visited.insert(current.clone()) {
9268 return None;
9269 }
9270 let facts = analyzer.declaration_source_properties(¤t)?;
9271 if facts.iter().any(|fact| fact.adds_indirection) {
9272 return None;
9273 }
9274 let raw_target = cpp_canonical_alias_target_text(analyzer, ¤t)?;
9275 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
9276 }
9277 None
9278 }
9279
9280 fn comparable_alias_target(
9291 &self,
9292 analyzer: &CppGraphSource<'_>,
9293 alias: &CodeUnit,
9294 raw_target: &str,
9295 ) -> Option<CodeUnit> {
9296 let absolute = raw_target.trim_start().starts_with("::");
9301 let normalized = normalize_reference_name(raw_target)?;
9302 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9303 brokk_bifrost_core::analyzer::Language::Cpp,
9304 &normalized,
9305 );
9306 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
9307 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9308 brokk_bifrost_core::analyzer::Language::Cpp,
9309 &namespace,
9310 )
9311 });
9312 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
9313 self.comparable_name_declaration(analyzer, &name)
9314 }
9315
9316 fn comparable_name_declaration(
9324 &self,
9325 analyzer: &CppGraphSource<'_>,
9326 name: &StructuredTypeName,
9327 ) -> Option<CodeUnit> {
9328 visibility_memo_value(
9329 &self.comparable_name_declarations,
9330 &self.reads,
9331 name.clone(),
9332 || {
9333 let interner = segment_interner();
9334 let identifier = name.path().last()?;
9335 let candidates_by_identifier =
9336 self.cpp.visibility_identifier_candidates(identifier);
9337 let first_depth = if name.is_absolute() {
9338 0
9339 } else {
9340 name.lexical_scope().len()
9341 };
9342 let mut resolved = None;
9343 for depth in (0..=first_depth).rev() {
9344 let mut structured = FqName::new();
9345 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
9346 structured.push(interner.intern(component, SegmentKind::Unknown));
9347 }
9348 let mut candidates = candidates_by_identifier
9349 .iter()
9350 .filter(|unit| unit.fq().same_segment_texts(&structured))
9351 .filter(|unit| {
9352 unit.kind() == CodeUnitType::Class
9353 || declared_type_alias(analyzer, unit)
9354 })
9355 .cloned();
9356 let Some(first) = candidates.next() else {
9357 continue;
9358 };
9359 resolved = candidates
9360 .all(|unit| same_logical_symbol(&unit, &first))
9361 .then_some(first);
9362 break;
9363 }
9364 resolved
9365 },
9366 )
9367 }
9368
9369 fn callable_comparable(
9375 &self,
9376 analyzer: &CppGraphSource<'_>,
9377 unit: &CodeUnit,
9378 ) -> Option<Arc<ExtractedComparable>> {
9379 visibility_memo_value(
9380 &self.callable_comparables,
9381 &self.reads,
9382 unit.clone(),
9383 || {
9384 self.extract_callable_comparable(analyzer, unit)
9385 .map(Arc::new)
9386 },
9387 )
9388 }
9389
9390 fn extract_callable_comparable(
9391 &self,
9392 analyzer: &CppGraphSource<'_>,
9393 unit: &CodeUnit,
9394 ) -> Option<ExtractedComparable> {
9395 let fact = analyzer
9396 .declaration_source_properties(unit)?
9397 .into_iter()
9398 .find(|fact| fact.callable_comparable_shapes.is_some())?;
9399 let template = self.declaration_template_context(unit, fact.declaration);
9400 Some(ExtractedComparable {
9401 shapes: fact.callable_comparable_shapes?,
9402 suffix: fact.callable_identity_suffix?,
9403 template,
9404 })
9405 }
9406
9407 pub fn declaration_template_context(
9410 &self,
9411 unit: &CodeUnit,
9412 declaration: SourceDeclarationId,
9413 ) -> Option<CppTemplateContext> {
9414 let facts = self
9415 .cpp
9416 .declaration_source_facts(self.token, unit.source())?;
9417 let occurrence = facts.source.declaration(declaration).occurrence;
9418 let range = facts.source.occurrence(occurrence).range;
9419 let syntax = self.cpp.prepared_syntax(self.token, unit.source())?;
9420 let mut node = node_for_exact_range(syntax.tree().root_node(), &range)?;
9421 while node.kind() == "template_declaration" {
9424 node = node.named_child(node.named_child_count().checked_sub(1)?)?;
9425 }
9426 cpp_declaration_template_context(node, syntax.source())
9427 }
9428
9429 pub fn canonical_type_for_reference(
9430 &self,
9431 file: &ProjectFile,
9432 raw_name: &str,
9433 ) -> Option<CodeUnit> {
9434 let resolved = self.resolve_type(file, raw_name)?;
9435 self.alias_target(&resolved).or(Some(resolved))
9436 }
9437
9438 pub fn parser_alias_resolves_to_type(
9439 &self,
9440 file: &ProjectFile,
9441 raw_name: &str,
9442 target: &CodeUnit,
9443 ) -> bool {
9444 let Some(alias_name) = normalize_reference_name(raw_name) else {
9445 return false;
9446 };
9447 any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
9448 self.file_alias_matches(self.cpp, source_file, &alias_name, target)
9449 })
9450 }
9451
9452 fn file_alias_matches(
9453 &self,
9454 cpp: &dyn CppSource,
9455 file: &ProjectFile,
9456 alias_name: &str,
9457 target: &CodeUnit,
9458 ) -> bool {
9459 self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
9460 cell.get()
9461 .expect("published canonical aliases")
9462 .iter()
9463 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
9464 })
9465 }
9466
9467 fn canonical_alias_cell(
9468 &self,
9469 cpp: &dyn CppSource,
9470 file: &ProjectFile,
9471 ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
9472 try_visibility_memo_value(&self.alias_cells, &self.reads, file.clone(), || {
9473 let aliases = aliases_from_canonical_source(cpp, self.token, file).ok_or(())?;
9474 self.parser_alias_source_parses
9475 .fetch_add(1, Ordering::Relaxed);
9476 #[cfg(any(test, feature = "test-support"))]
9477 {
9478 *self
9479 .alias_source_parse_counts
9480 .lock()
9481 .expect("alias source parse count lock")
9482 .entry(file.clone())
9483 .or_default() += 1;
9484 }
9485 let cell = Arc::new(OnceLock::new());
9486 cell.get_or_init(|| aliases.into_boxed_slice());
9487 Ok::<_, ()>(cell)
9488 })
9489 .ok()
9490 }
9491
9492 #[cfg(any(test, feature = "test-support"))]
9493 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
9494 self.visible_source_files_by_root
9495 .get(file)
9496 .cloned()
9497 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
9498 }
9499
9500 #[cfg(any(test, feature = "test-support"))]
9501 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
9502 self.alias_source_parse_counts
9503 .lock()
9504 .expect("alias source parse count lock")
9505 .get(file)
9506 .copied()
9507 .unwrap_or(0)
9508 }
9509
9510 #[cfg(any(test, feature = "test-support"))]
9511 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
9512 self.parser_alias_fallback_files.load(Ordering::Relaxed)
9513 }
9514
9515 pub fn resolve_named(
9516 &self,
9517 file: &ProjectFile,
9518 raw_name: &str,
9519 kind: TargetKind,
9520 ) -> Option<CodeUnit> {
9521 let normalized = normalize_reference_name(raw_name)?;
9522 self.named_candidates_for_normalized(file, &normalized, kind)
9523 .into_iter()
9524 .next()
9525 .cloned()
9526 }
9527
9528 pub fn contains_named_symbol(
9529 &self,
9530 file: &ProjectFile,
9531 raw_name: &str,
9532 kind: TargetKind,
9533 target: &CodeUnit,
9534 ) -> bool {
9535 let Some(normalized) = normalize_reference_name(raw_name) else {
9536 return false;
9537 };
9538 self.named_candidates_for_normalized(file, &normalized, kind)
9539 .into_iter()
9540 .any(|unit| {
9541 matches_kind_for_lookup(unit, kind)
9542 && reference_matches_unit(&normalized, unit)
9543 && same_visible_symbol(unit, target)
9544 })
9545 }
9546
9547 pub fn named_candidates(
9548 &self,
9549 file: &ProjectFile,
9550 raw_name: &str,
9551 kind: TargetKind,
9552 ) -> Vec<CodeUnit> {
9553 let Some(normalized) = normalize_reference_name(raw_name) else {
9554 return Vec::new();
9555 };
9556 self.named_candidates_for_normalized(file, &normalized, kind)
9557 .into_iter()
9558 .cloned()
9559 .collect()
9560 }
9561
9562 pub fn resolve_known_non_target(
9563 &self,
9564 file: &ProjectFile,
9565 raw_name: &str,
9566 kind: TargetKind,
9567 target: &CodeUnit,
9568 ) -> bool {
9569 let Some(normalized) = normalize_reference_name(raw_name) else {
9570 return false;
9571 };
9572 normalized.contains("::")
9573 && self
9574 .named_candidates_for_normalized(file, &normalized, kind)
9575 .into_iter()
9576 .any(|unit| {
9577 matches_kind_for_lookup(unit, kind)
9578 && reference_matches_unit(&normalized, unit)
9579 && !same_visible_symbol(unit, target)
9580 })
9581 }
9582
9583 pub fn resolve_call_return_binding(
9584 &self,
9585 analyzer: &CppGraphSource<'_>,
9586 file: &ProjectFile,
9587 raw_name: &str,
9588 arity: usize,
9589 lexical_namespace: Option<&str>,
9590 direct_type: Option<&CodeUnit>,
9591 ) -> Option<CppScanBinding> {
9592 let normalized = normalize_reference_name(raw_name)?;
9593 let mut candidates = Vec::new();
9594 for function in
9595 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9596 {
9597 if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
9598 && !direct_type.is_some_and(|direct_type| {
9599 self.callable_is_constructor_declaration(analyzer, function)
9600 && type_owner_of(analyzer, function)
9601 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9602 })
9603 {
9604 candidates.push(function.clone());
9605 }
9606 }
9607 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9608 unanimous_return_binding(analyzer, self, file, &candidates)
9609 }
9610
9611 pub fn resolve_call_return_binding_without_arity(
9612 &self,
9613 analyzer: &CppGraphSource<'_>,
9614 file: &ProjectFile,
9615 raw_name: &str,
9616 lexical_namespace: Option<&str>,
9617 direct_type: Option<&CodeUnit>,
9618 ) -> (bool, Option<CppScanBinding>) {
9619 let Some(normalized) = normalize_reference_name(raw_name) else {
9620 return (false, None);
9621 };
9622 let mut candidates = self
9623 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9624 .into_iter()
9625 .filter(|function| {
9626 function.is_function()
9627 && !direct_type.is_some_and(|direct_type| {
9628 self.callable_is_constructor_declaration(analyzer, function)
9629 && type_owner_of(analyzer, function)
9630 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9631 })
9632 })
9633 .cloned()
9634 .collect::<Vec<_>>();
9635 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9636 let has_candidates = !candidates.is_empty();
9637 (
9638 has_candidates,
9639 unanimous_return_binding(analyzer, self, file, &candidates),
9640 )
9641 }
9642
9643 pub fn visible_identifier_candidates<'b>(
9644 &'b self,
9645 file: &ProjectFile,
9646 identifier: &str,
9647 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
9648 self.visible_by_identifier
9649 .get(file)
9650 .and_then(|by_name| by_name.get(identifier))
9651 .into_iter()
9652 .flatten()
9653 }
9654
9655 pub fn type_reference_component_directly_names_target(
9661 &self,
9662 component: &str,
9663 target: &CodeUnit,
9664 ) -> bool {
9665 component == target.identifier()
9666 || self
9667 .cpp_template_metadata
9668 .get(target)
9669 .is_some_and(|metadata| component == metadata.primary_name)
9670 }
9671
9672 pub fn visible_type_reference_component_names_for_target(
9680 &self,
9681 analyzer: &CppGraphSource<'_>,
9682 file: &ProjectFile,
9683 target: &CodeUnit,
9684 ) -> HashSet<String> {
9685 let mut names = HashSet::from_iter([target.identifier().to_string()]);
9686 if let Some(metadata) = self.cpp_template_metadata.get(target) {
9687 names.insert(metadata.primary_name.clone());
9688 }
9689
9690 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
9691 for (identifier, candidates) in by_identifier {
9692 if candidates.iter().any(|candidate| {
9693 (candidate.is_class()
9694 && (same_visible_symbol(candidate, target)
9695 || self.compatible_primary_template_redeclarations(candidate, target)))
9696 || (declared_type_alias(analyzer, candidate)
9697 && self.alias_candidate_structurally_reaches_target(
9698 analyzer, file, candidate, target,
9699 ))
9700 }) {
9701 names.insert(identifier.clone());
9702 }
9703 }
9704 }
9705
9706 names
9707 }
9708
9709 pub fn indexed_structural_class_scope(
9710 &self,
9711 file: &ProjectFile,
9712 class: Node<'_>,
9713 source: &str,
9714 ) -> Option<Vec<String>> {
9715 let key = (file.clone(), class.start_byte(), class.end_byte());
9716 visibility_memo_value(
9717 &self.indexed_structural_class_scopes,
9718 &self.reads,
9719 key,
9720 || {
9721 let name = class.child_by_field_name("name")?;
9722 let identifier = if name.kind() == "template_type" {
9723 node_text(name.child_by_field_name("name")?, source).to_string()
9724 } else {
9725 let mut components = Vec::new();
9726 append_cpp_name_components(name, source, &mut components)?;
9727 components.last()?.clone()
9728 };
9729 let visible = self
9730 .visible_identifier_candidates(file, &identifier)
9731 .cloned()
9732 .collect::<Vec<_>>();
9733 let mut visible = visible;
9734 for candidate in
9735 self.visible_by_file
9736 .get(file)
9737 .into_iter()
9738 .flatten()
9739 .filter(|candidate| {
9740 self.cpp_template_metadata
9741 .get(candidate)
9742 .is_some_and(|metadata| metadata.primary_name == identifier)
9743 })
9744 {
9745 if !visible
9746 .iter()
9747 .any(|existing| same_logical_symbol(existing, candidate))
9748 {
9749 visible.push(candidate.clone());
9750 }
9751 }
9752 let cpp_source = self.cpp_source();
9755 let candidates = visible
9756 .iter()
9757 .filter(|candidate| {
9758 candidate.source() == file
9759 && candidate.is_class()
9760 && !declared_type_alias(&cpp_source, candidate)
9761 && self.cpp.ranges(candidate).iter().any(|range| {
9762 range.start_byte <= class.start_byte()
9763 && class.end_byte() <= range.end_byte
9764 })
9765 })
9766 .collect::<Vec<_>>();
9767 let owner = if name.kind() == "template_type" {
9768 let expected = normalize_cpp_whitespace(node_text(name, source));
9769 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9770 let exact = candidates
9771 .iter()
9772 .copied()
9773 .filter(|candidate| {
9774 candidate
9775 .fq()
9776 .segments()
9777 .iter()
9778 .rev()
9779 .find_map(|&segment| {
9780 let (text, kind) = interner.resolve(segment);
9781 matches!(
9782 kind,
9783 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9784 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9785 )
9786 .then_some(text)
9787 })
9788 .is_some_and(|text| text == expected)
9789 })
9790 .collect::<Vec<_>>();
9791 unique_logical_type_candidate(exact)
9792 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9793 } else {
9794 unique_logical_type_candidate(candidates)?
9795 };
9796 Some(canonical_cpp_scope_components(&owner))
9797 },
9798 )
9799 }
9800
9801 pub fn indexed_enclosing_owner_scope(
9802 &self,
9803 analyzer: &CppGraphSource<'_>,
9804 file: &ProjectFile,
9805 node: Node<'_>,
9806 ) -> Option<Vec<String>> {
9807 let anchor = std::iter::successors(Some(node), |current| current.parent())
9808 .find(|current| {
9809 matches!(
9810 current.kind(),
9811 "function_definition"
9812 | "class_specifier"
9813 | "struct_specifier"
9814 | "union_specifier"
9815 )
9816 })
9817 .unwrap_or(node);
9818 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9819 visibility_memo_value(
9820 &self.indexed_enclosing_owner_scopes,
9821 &self.reads,
9822 key,
9823 || {
9824 let range = Range {
9825 start_byte: node.start_byte(),
9826 end_byte: node.end_byte(),
9827 start_line: node.start_position().row,
9828 end_line: node.end_position().row,
9829 };
9830 let start = analyzer.enclosing_code_unit(file, &range)?;
9831 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9832 start,
9833 |unit| self.cached_precise_parent_of(analyzer, unit),
9834 )
9835 .find(|unit| {
9836 unit.is_class()
9837 && !analyzer
9838 .type_alias_provider()
9839 .is_some_and(|provider| provider.is_type_alias(unit))
9840 })?;
9841 Some(canonical_cpp_scope_components(&owner))
9842 },
9843 )
9844 }
9845
9846 fn cached_precise_parent_of(
9847 &self,
9848 analyzer: &CppGraphSource<'_>,
9849 code_unit: &CodeUnit,
9850 ) -> Option<CodeUnit> {
9851 visibility_memo_value(
9852 &self.precise_parent_cache,
9853 &self.reads,
9854 code_unit.clone(),
9855 || precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit),
9856 )
9857 }
9858
9859 pub fn callable_is_constructor_declaration(
9860 &self,
9861 analyzer: &CppGraphSource<'_>,
9862 candidate: &CodeUnit,
9863 ) -> bool {
9864 if !candidate.is_function() {
9865 return false;
9866 }
9867 if analyzer
9868 .declaration_source_properties(candidate)
9869 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9870 {
9871 return true;
9872 }
9873 let metadata = analyzer.signature_metadata(candidate);
9874 !metadata.is_empty()
9875 && metadata
9876 .iter()
9877 .all(|signature| signature.return_type_text().is_none())
9878 }
9879
9880 pub fn callable_is_deduction_guide_declaration(
9888 &self,
9889 analyzer: &CppGraphSource<'_>,
9890 candidate: &CodeUnit,
9891 ) -> bool {
9892 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9893 }
9894
9895 pub fn callable_is_template_declaration(
9899 &self,
9900 analyzer: &CppGraphSource<'_>,
9901 candidate: &CodeUnit,
9902 ) -> bool {
9903 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9904 }
9905
9906 pub fn type_name_candidates<'b>(
9907 &'b self,
9908 file: &ProjectFile,
9909 normalized: &str,
9910 ) -> Vec<&'b CodeUnit> {
9911 self.candidate_units(file, normalized, TargetKind::Type)
9912 }
9913
9914 pub fn visible_members_for_owner_name<'b>(
9915 &'b self,
9916 file: &ProjectFile,
9917 owner: &CodeUnit,
9918 name: &str,
9919 ) -> Vec<&'b CodeUnit> {
9920 self.visible_identifier_candidates(file, name)
9921 .filter(|unit| {
9922 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9926 .is_some_and(|parent| parent == owner.fq_name())
9927 })
9928 .collect()
9929 }
9930
9931 pub fn visible_member_for_owner_name(
9932 &self,
9933 file: &ProjectFile,
9934 owner: &CodeUnit,
9935 name: &str,
9936 ) -> VisibleMemberResolution {
9937 let candidates = self.visible_members_for_owner_name(file, owner, name);
9938 let mut callables = Vec::new();
9939 let mut non_callable = None;
9940 for candidate in candidates {
9941 if candidate.is_function() {
9942 callables.push(candidate.clone());
9943 } else if non_callable.is_none() {
9944 non_callable = Some(candidate.clone());
9945 }
9946 }
9947 match (callables.is_empty(), non_callable) {
9948 (false, None) => VisibleMemberResolution::Callable(callables),
9949 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9950 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9951 (true, None) => VisibleMemberResolution::Missing,
9952 }
9953 }
9954
9955 fn field_declared_type_fact(
9956 &self,
9957 analyzer: &CppGraphSource<'_>,
9958 field: &CodeUnit,
9959 ) -> Option<DeclaredFieldTypeFact> {
9960 visibility_memo_value(&self.field_type_facts, &self.reads, field.clone(), || {
9961 decode_field_declared_type_fact(analyzer, field)
9962 })
9963 }
9964
9965 fn structured_alias_target(
9966 &self,
9967 analyzer: &CppGraphSource<'_>,
9968 unit: &CodeUnit,
9969 ) -> Option<StructuredAliasTarget> {
9970 visibility_memo_value(
9971 &self.structured_alias_targets,
9972 &self.reads,
9973 unit.clone(),
9974 || decode_structured_alias_target(analyzer, unit),
9975 )
9976 }
9977
9978 pub fn type_candidates<'b>(
9979 &'b self,
9980 file: &ProjectFile,
9981 normalized: &str,
9982 ) -> Vec<&'b CodeUnit> {
9983 let qualified = normalized.contains("::");
9988 let mut candidates = self
9989 .candidate_units(file, normalized, TargetKind::Type)
9990 .into_iter()
9991 .filter(|unit| {
9992 unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9993 })
9994 .filter(|unit| qualified || !is_function_local_unit(unit))
9995 .collect::<Vec<_>>();
9996 dedup_unit_refs(&mut candidates);
9997 candidates
9998 }
9999
10000 pub fn named_candidates_for_normalized<'b>(
10001 &'b self,
10002 file: &ProjectFile,
10003 normalized: &str,
10004 kind: TargetKind,
10005 ) -> Vec<&'b CodeUnit> {
10006 let mut candidates = self
10007 .candidate_units(file, normalized, kind)
10008 .into_iter()
10009 .filter(|unit| {
10010 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
10011 })
10012 .collect::<Vec<_>>();
10013 dedup_unit_refs(&mut candidates);
10014 candidates
10015 }
10016
10017 pub fn candidate_units<'b>(
10018 &'b self,
10019 file: &ProjectFile,
10020 normalized: &str,
10021 kind: TargetKind,
10022 ) -> Vec<&'b CodeUnit> {
10023 if normalized.contains("::") {
10024 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10033 brokk_bifrost_core::analyzer::Language::Cpp,
10034 normalized,
10035 );
10036 let Some(identifier) = parts.last() else {
10037 return Vec::new();
10038 };
10039 let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
10040 return self
10041 .visible_identifier_candidates(file, identifier)
10042 .filter(|unit| {
10043 #[cfg(any(test, feature = "test-support"))]
10044 self.qualified_candidate_inspections
10045 .fetch_add(1, Ordering::Relaxed);
10046 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
10047 || canonical_cpp_name_matches(unit, normalized)
10048 })
10049 .collect();
10050 }
10051 self.visible_identifier_candidates(file, normalized)
10052 .collect()
10053 }
10054
10055 #[cfg(any(test, feature = "test-support"))]
10056 pub fn reset_qualified_candidate_inspections(&self) {
10057 self.qualified_candidate_inspections
10058 .store(0, Ordering::Relaxed);
10059 }
10060
10061 #[cfg(any(test, feature = "test-support"))]
10062 pub fn qualified_candidate_inspections(&self) -> usize {
10063 self.qualified_candidate_inspections.load(Ordering::Relaxed)
10064 }
10065
10066 #[cfg(any(test, feature = "test-support"))]
10067 pub fn visibility_identifier_lookup_count(&self) -> usize {
10068 self.visibility_identifier_lookup_count
10069 }
10070
10071 #[cfg(any(test, feature = "test-support"))]
10072 pub fn visibility_identifier_batch_count(&self) -> usize {
10073 self.visibility_identifier_batch_count
10074 }
10075
10076 #[cfg(any(test, feature = "test-support"))]
10077 pub fn reset_target_preserving_type_resolution_count(&self) {
10078 self.target_preserving_type_resolution_count
10079 .store(0, Ordering::Relaxed);
10080 }
10081
10082 #[cfg(any(test, feature = "test-support"))]
10083 pub fn target_preserving_type_resolution_count(&self) -> usize {
10084 self.target_preserving_type_resolution_count
10085 .load(Ordering::Relaxed)
10086 }
10087
10088 #[cfg(any(test, feature = "test-support"))]
10089 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
10090 self.visible_parser_alias_name_set_build_count
10091 .load(Ordering::Relaxed)
10092 }
10093}
10094
10095fn callable_declaration_has_fact(
10096 analyzer: &CppGraphSource<'_>,
10097 candidate: &CodeUnit,
10098 matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
10099) -> bool {
10100 candidate.is_function()
10101 && analyzer
10102 .declaration_source_properties(candidate)
10103 .is_some_and(|facts| facts.iter().any(matches))
10104}
10105
10106#[derive(Default)]
10107struct IncludeGraph {
10108 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
10109}
10110
10111impl IncludeGraph {
10112 fn extend_with<F>(
10113 &mut self,
10114 root: &ProjectFile,
10115 cancellation: Option<&CancellationToken>,
10116 targets_for: &mut F,
10117 ) where
10118 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
10119 {
10120 let mut stack = vec![root.clone()];
10121 while let Some(file) = stack.pop() {
10122 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10123 break;
10124 }
10125 if self.targets_by_file.contains_key(&file) {
10126 continue;
10127 }
10128 let targets = targets_for(&file);
10129 stack.extend(targets.iter().cloned());
10130 self.targets_by_file.insert(file, targets);
10131 }
10132 }
10133
10134 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
10135 self.targets_by_file.keys()
10136 }
10137
10138 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
10139 self.targets_by_file
10140 .get(file)
10141 .map(Vec::as_slice)
10142 .unwrap_or_default()
10143 }
10144
10145 fn reachable_files(
10146 &self,
10147 root: &ProjectFile,
10148 cancellation: Option<&CancellationToken>,
10149 ) -> HashSet<ProjectFile> {
10150 let mut pending = vec![root.clone()];
10151 let mut visited = HashSet::default();
10152 while let Some(file) = pending.pop() {
10153 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10154 break;
10155 }
10156 if visited.insert(file.clone()) {
10157 pending.extend(self.targets(&file).iter().cloned());
10158 }
10159 }
10160 visited
10161 }
10162}
10163
10164fn build_bounded_visible_declarations(
10165 cpp: &dyn CppSource,
10166 token: QueryToken<'_>,
10167 analyzer: &CppGraphSource<'_>,
10168 roots: &HashSet<ProjectFile>,
10169 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10170 cancellation: Option<&CancellationToken>,
10171 stats: &mut BoundedVisibilityStats,
10172) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
10173 let mut candidates_by_identifier = HashMap::default();
10174 let mut declarations_by_source_and_reading: HashMap<
10175 (ProjectFile, bool),
10176 BoundedVisibilityDeclarations,
10177 > = HashMap::default();
10178 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
10179 roots
10180 .iter()
10181 .map(|root| {
10182 let reading_is_c = analyzer.reference_uses_c_semantics(root);
10183 let root_declarations = declarations_by_source_and_reading
10184 .entry((root.clone(), reading_is_c))
10185 .or_insert_with(|| {
10186 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
10187 });
10188 let mut visible = root_declarations
10189 .all
10190 .iter()
10191 .cloned()
10192 .collect::<HashSet<_>>();
10193 let mut pending_names = HashSet::default();
10194 if let Some(prepared) = cpp.prepared_syntax(token, root) {
10195 let mut cursor = prepared.tree().walk();
10200 let mut pending_nodes = vec![prepared.tree().root_node()];
10201 while let Some(node) = pending_nodes.pop() {
10202 if matches!(
10203 node.kind(),
10204 "identifier"
10205 | "type_identifier"
10206 | "field_identifier"
10207 | "namespace_identifier"
10208 ) {
10209 pending_names.insert(node_text(node, prepared.source()).to_string());
10210 }
10211 if node.kind() == "preproc_arg" {
10212 for reference in
10213 object_macro_replacement_type_references(node, prepared.source())
10214 {
10215 pending_names.extend(reference.components);
10216 }
10217 }
10218 pending_nodes.extend(node.named_children(&mut cursor));
10219 }
10220 }
10221 stats.root_names += pending_names.len();
10222 let mut completed_names = HashSet::default();
10223 while !pending_names.is_empty() {
10224 stats.rounds += 1;
10225 let round_names = std::mem::take(&mut pending_names);
10226 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
10227 HashMap::default();
10228 let mut identifiers = Vec::new();
10229 for identifier in round_names {
10230 if !completed_names.insert(identifier.clone())
10231 || cancellation.is_some_and(CancellationToken::is_cancelled)
10232 {
10233 continue;
10234 }
10235 identifiers.push(identifier);
10236 }
10237 let missing_identifiers = identifiers
10238 .iter()
10239 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
10240 .cloned()
10241 .collect::<HashSet<_>>();
10242 if !missing_identifiers.is_empty() {
10243 let lookup_started = Instant::now();
10244 let mut candidates = cpp
10245 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
10246 stats.lookup_elapsed += lookup_started.elapsed();
10247 stats.identifier_lookups += missing_identifiers.len();
10248 stats.identifier_batches += 1;
10249 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10250 break;
10251 }
10252 for identifier in missing_identifiers {
10253 let units = candidates.remove(&identifier).unwrap_or_default();
10254 let candidate_count = units.len();
10255 let candidate_sources = units
10256 .into_iter()
10257 .map(|unit| unit.source().clone())
10258 .collect::<HashSet<_>>();
10259 candidates_by_identifier
10260 .insert(identifier, (candidate_sources, candidate_count));
10261 }
10262 }
10263 for identifier in identifiers {
10264 let (candidate_sources, candidate_count) = candidates_by_identifier
10265 .get(&identifier)
10266 .expect("every missing identifier was inserted after the batch lookup");
10267 stats.candidate_units += *candidate_count;
10268 for source in candidate_sources.iter().cloned() {
10269 if source != *root
10270 && visible_sources
10271 .get(root)
10272 .is_some_and(|files| files.contains(&source))
10273 {
10274 requested_names_by_source
10275 .entry(source)
10276 .or_default()
10277 .insert(identifier.clone());
10278 }
10279 }
10280 }
10281 stats.candidate_sources += requested_names_by_source.len();
10282 for (source, requested_names) in requested_names_by_source {
10283 let declarations = declarations_by_source_and_reading
10284 .entry((source.clone(), reading_is_c))
10285 .or_insert_with(|| {
10286 bounded_visibility_declarations(
10287 cpp,
10288 analyzer,
10289 &source,
10290 reading_is_c,
10291 stats,
10292 )
10293 });
10294 let mut selected = HashSet::default();
10295 for name in requested_names {
10296 if let Some(units) = declarations.by_identifier.get(&name) {
10297 selected.extend(units.iter().cloned());
10298 }
10299 }
10300 for unit in selected {
10301 stats.selected_units += 1;
10302 let dependency_names = dependency_names_by_unit
10303 .entry(unit.clone())
10304 .or_insert_with(|| {
10305 let facts_started = Instant::now();
10306 let mut dependency_names = HashSet::default();
10307 if let Some(facts) = analyzer.declaration_source_properties(&unit) {
10308 for fact in facts {
10309 for name in fact.dependency_type_names {
10310 stats.dependency_source_names += 1;
10311 #[cfg(any(test, feature = "test-support"))]
10312 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
10313 .with(|count| count.set(count.get() + 1));
10314 dependency_names.insert(name);
10315 }
10316 }
10317 stats.dependency_source_elapsed += facts_started.elapsed();
10318 }
10319 dependency_names
10320 });
10321 for name in dependency_names.iter() {
10322 if !completed_names.contains(name) && pending_names.insert(name.clone())
10323 {
10324 stats.dependency_names += 1;
10325 }
10326 }
10327 visible.insert(unit);
10328 }
10329 }
10330 }
10331 (root.clone(), visible)
10332 })
10333 .collect()
10334}
10335
10336struct BoundedVisibilityDeclarations {
10337 all: Vec<CodeUnit>,
10338 by_identifier: HashMap<String, Vec<CodeUnit>>,
10339}
10340
10341fn bounded_visibility_declarations(
10342 cpp: &dyn CppSource,
10343 analyzer: &CppGraphSource<'_>,
10344 file: &ProjectFile,
10345 c_semantics: bool,
10346 stats: &mut BoundedVisibilityStats,
10347) -> BoundedVisibilityDeclarations {
10348 let declarations_started = Instant::now();
10349 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
10350 .into_iter()
10351 .collect::<Vec<_>>();
10352 stats.declaration_elapsed += declarations_started.elapsed();
10353 stats.declaration_reads += 1;
10354 stats.declaration_units += all.len();
10355
10356 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10357 for unit in &all {
10358 by_identifier
10359 .entry(unit.identifier().to_string())
10360 .or_default()
10361 .push(unit.clone());
10362 if unit.is_class()
10363 && let Some(metadata) = cpp.template_metadata(unit)
10364 && metadata.primary_name != unit.identifier()
10365 {
10366 by_identifier
10367 .entry(metadata.primary_name)
10368 .or_default()
10369 .push(unit.clone());
10370 }
10371 }
10372 BoundedVisibilityDeclarations { all, by_identifier }
10373}
10374
10375#[derive(Default)]
10376struct BoundedVisibilityStats {
10377 rounds: usize,
10378 root_names: usize,
10379 identifier_lookups: usize,
10380 identifier_batches: usize,
10381 candidate_units: usize,
10382 candidate_sources: usize,
10383 declaration_reads: usize,
10384 declaration_units: usize,
10385 selected_units: usize,
10386 dependency_source_names: usize,
10387 dependency_names: usize,
10388 lookup_elapsed: Duration,
10389 declaration_elapsed: Duration,
10390 dependency_source_elapsed: Duration,
10391}
10392
10393fn bounded_visibility_declarations_in_reading(
10394 analyzer: &CppGraphSource<'_>,
10395 file: &ProjectFile,
10396 c_semantics: bool,
10397) -> BTreeSet<CodeUnit> {
10398 #[cfg(any(test, feature = "test-support"))]
10399 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
10400 analyzer.declarations_in_reading(file, c_semantics)
10401}
10402
10403#[cfg(any(test, feature = "test-support"))]
10404pub fn reset_bounded_visibility_declaration_read_count_for_test() {
10405 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
10406}
10407
10408#[cfg(any(test, feature = "test-support"))]
10409pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
10410 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
10411}
10412
10413#[cfg(any(test, feature = "test-support"))]
10414pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
10415 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
10416}
10417
10418#[cfg(any(test, feature = "test-support"))]
10419pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
10420 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
10421}
10422
10423#[derive(Default)]
10442struct OutOfLineOwnerBindingStats {
10443 unseen_owners: usize,
10444 definition_lookups: usize,
10445 admitted: usize,
10446}
10447
10448fn extend_with_out_of_line_owner_bindings(
10449 cpp: &dyn CppSource,
10450 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
10451) -> OutOfLineOwnerBindingStats {
10452 let mut stats = OutOfLineOwnerBindingStats::default();
10453 for (file, visible) in visible_by_file.iter_mut() {
10454 let mut unseen_owners: HashSet<String> = visible
10458 .iter()
10459 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
10460 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
10461 .collect();
10462 if unseen_owners.is_empty() {
10463 continue;
10464 }
10465 for unit in visible.iter().filter(|unit| unit.is_class()) {
10466 unseen_owners.remove(&unit.fq_name());
10467 }
10468 stats.unseen_owners += unseen_owners.len();
10469 stats.definition_lookups += unseen_owners.len();
10470 let admitted = unseen_owners
10471 .iter()
10472 .flat_map(|owner| cpp.definitions(owner))
10473 .filter(CodeUnit::is_class)
10474 .collect::<Vec<_>>();
10475 stats.admitted += admitted.len();
10476 visible.extend(admitted);
10477 }
10478 stats
10479}
10480
10481pub enum VisibleMemberResolution {
10482 Callable(Vec<CodeUnit>),
10483 NonCallable,
10484 AmbiguousKind,
10485 Missing,
10486}
10487
10488#[derive(Clone)]
10489pub enum EnclosingMemberOwnerResolution {
10490 Owner(CodeUnit),
10491 Ambiguous,
10492 Missing,
10493}
10494
10495pub fn resolve_declaring_member_owner(
10496 analyzer: &CppGraphSource<'_>,
10497 visibility: &VisibilityIndex<'_>,
10498 file: &ProjectFile,
10499 receiver_owner: &CodeUnit,
10500 member_name: &str,
10501) -> EnclosingMemberOwnerResolution {
10502 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10503 return EnclosingMemberOwnerResolution::Missing;
10504 };
10505 let Some(receiver_owner) =
10506 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
10507 else {
10508 return EnclosingMemberOwnerResolution::Ambiguous;
10509 };
10510 let resolve_level = |frontier: &[CodeUnit]| {
10511 let mut member_owners = Vec::new();
10512 for raw_owner in frontier {
10513 let Some(owner) =
10514 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
10515 else {
10516 return EnclosingMemberOwnerResolution::Ambiguous;
10517 };
10518 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
10519 if !member.is_field() && !member.is_function() {
10524 continue;
10525 }
10526 let Some(member_owner) = type_owner_of(analyzer, member) else {
10527 return EnclosingMemberOwnerResolution::Ambiguous;
10528 };
10529 if !member_owners
10530 .iter()
10531 .any(|existing| same_visible_symbol(existing, &member_owner))
10532 {
10533 member_owners.push(member_owner);
10534 }
10535 }
10536 }
10537 match member_owners.len() {
10538 0 => EnclosingMemberOwnerResolution::Missing,
10539 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
10540 _ => EnclosingMemberOwnerResolution::Ambiguous,
10541 }
10542 };
10543 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
10547 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
10548 return direct;
10549 }
10550 let mut stack = hierarchy
10551 .get_direct_ancestors(&receiver_owner)
10552 .definite()
10553 .to_vec();
10554 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
10555 let mut path_matches = Vec::new();
10556 while let Some(raw_owner) = stack.pop() {
10557 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
10558 else {
10559 return EnclosingMemberOwnerResolution::Ambiguous;
10560 };
10561 let propagated = propagated_counts.entry(owner.clone()).or_default();
10565 if *propagated == 2 {
10566 continue;
10567 }
10568 *propagated += 1;
10569 match resolve_level(std::slice::from_ref(&owner)) {
10570 EnclosingMemberOwnerResolution::Owner(owner) => {
10571 path_matches.push(owner);
10572 if path_matches.len() == 2 {
10573 return EnclosingMemberOwnerResolution::Ambiguous;
10574 }
10575 }
10576 EnclosingMemberOwnerResolution::Ambiguous => {
10577 return EnclosingMemberOwnerResolution::Ambiguous;
10578 }
10579 EnclosingMemberOwnerResolution::Missing => {
10580 stack.extend(hierarchy.get_direct_ancestors(&owner).definite().to_vec());
10581 }
10582 }
10583 }
10584 match path_matches.len() {
10585 0 => EnclosingMemberOwnerResolution::Missing,
10586 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
10587 _ => unreachable!("base-path matches are capped at one before returning"),
10588 }
10589}
10590
10591pub fn resolve_declaring_callable_owner(
10606 analyzer: &CppGraphSource<'_>,
10607 visibility: &VisibilityIndex<'_>,
10608 file: &ProjectFile,
10609 ordinary: EnclosingMemberOwnerResolution,
10610 member_name: &str,
10611 call_arity: usize,
10612) -> EnclosingMemberOwnerResolution {
10613 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10614 return ordinary;
10615 };
10616 if visibility
10617 .visible_members_for_owner_name(file, ordinary_owner, member_name)
10618 .into_iter()
10619 .any(|unit| {
10620 unit.is_function()
10621 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
10622 })
10623 {
10624 return ordinary;
10625 }
10626
10627 let mut pending = match member_using_declaration_bases(
10628 analyzer,
10629 visibility,
10630 file,
10631 ordinary_owner,
10632 member_name,
10633 ) {
10634 Ok(bases) => bases,
10635 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10636 };
10637 let mut visited = HashSet::default();
10638 let mut introduced_owners = Vec::new();
10639 while let Some(owner) = pending.pop() {
10640 if !visited.insert(owner.clone()) {
10641 continue;
10642 }
10643 let accepts_arity = visibility
10644 .visible_members_for_owner_name(file, &owner, member_name)
10645 .into_iter()
10646 .any(|unit| {
10647 unit.is_function()
10648 && cpp_callable_arity(analyzer, unit)
10649 .is_none_or(|known| known.accepts(call_arity))
10650 });
10651 if accepts_arity {
10652 if !introduced_owners
10653 .iter()
10654 .any(|existing| same_visible_symbol(existing, &owner))
10655 {
10656 introduced_owners.push(owner);
10657 }
10658 continue;
10659 }
10660 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10661 Ok(bases) => pending.extend(bases),
10662 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10663 }
10664 }
10665 match introduced_owners.as_slice() {
10666 [] => ordinary,
10667 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10668 _ => EnclosingMemberOwnerResolution::Ambiguous,
10669 }
10670}
10671
10672fn member_using_declaration_bases(
10673 analyzer: &CppGraphSource<'_>,
10674 visibility: &VisibilityIndex<'_>,
10675 file: &ProjectFile,
10676 owner: &CodeUnit,
10677 member_name: &str,
10678) -> Result<Vec<CodeUnit>, ()> {
10679 let Some(facts) = analyzer.declaration_source_properties(owner) else {
10680 return Err(());
10681 };
10682 let scopes: Vec<_> = facts
10683 .iter()
10684 .flat_map(|fact| &fact.member_usings)
10685 .filter(|using| using.member == member_name)
10686 .map(|using| using.scope.join("::"))
10687 .collect();
10688 if scopes.is_empty() {
10689 return Ok(Vec::new());
10690 }
10691 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10692 return Ok(Vec::new());
10693 };
10694 let mut bases = Vec::new();
10695 for raw_ancestor in hierarchy.get_ancestors(owner).definite().to_vec() {
10696 let Some(ancestor) =
10697 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10698 else {
10699 return Err(());
10700 };
10701 let qualified = cpp_name_for(&ancestor);
10702 if scopes
10703 .iter()
10704 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10705 && !bases
10706 .iter()
10707 .any(|existing| same_visible_symbol(existing, &ancestor))
10708 {
10709 bases.push(ancestor);
10710 }
10711 }
10712 Ok(bases)
10713}
10714
10715pub fn lexical_component_tiers<'a>(
10716 components: &'a [String],
10717 global: bool,
10718 lexical_scope: &'a [String],
10719) -> impl Iterator<Item = Vec<String>> + 'a {
10720 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10721 (0..=first_prefix_len).rev().map(move |prefix_len| {
10722 let mut qualified = Vec::with_capacity(prefix_len + components.len());
10723 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10724 qualified.extend_from_slice(components);
10725 qualified
10726 })
10727}
10728
10729pub fn build_visible_identifier_index(
10730 analyzer: &CppGraphSource<'_>,
10731 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10732 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10733 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10734) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10735 let mut out = HashMap::default();
10736 for (file, visible) in visible_by_file {
10737 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10738 for unit in visible {
10739 if unit.is_field()
10740 && !visible_source_files_by_root
10741 .get(file)
10742 .is_some_and(|sources| sources.contains(unit.source()))
10743 && cpp_global_field_has_internal_linkage_cached(
10744 analyzer,
10745 global_field_internal_linkage,
10746 unit,
10747 )
10748 {
10749 continue;
10750 }
10751 by_identifier
10752 .entry(unit.identifier().to_string())
10753 .or_default()
10754 .push(unit.clone());
10755 }
10756 for units in by_identifier.values_mut() {
10757 sort_lookup_units(units);
10758 units.dedup();
10759 }
10760 out.insert(file.clone(), by_identifier);
10761 }
10762 out
10763}
10764
10765fn sort_lookup_units(units: &mut [CodeUnit]) {
10766 units.sort_by(|left, right| {
10767 left.fq_name()
10768 .cmp(&right.fq_name())
10769 .then_with(|| left.signature().cmp(&right.signature()))
10770 .then_with(|| left.source().cmp(right.source()))
10771 .then_with(|| left.kind().cmp(&right.kind()))
10772 .then_with(|| {
10773 left.package_segment_count()
10774 .cmp(&right.package_segment_count())
10775 })
10776 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10777 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10778 });
10779}
10780
10781fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10782 let interner = segment_interner();
10783 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10784 let (left_text, left_kind) = interner.resolve(left_id);
10785 let (right_text, right_kind) = interner.resolve(right_id);
10786 let order = left_text
10787 .cmp(right_text)
10788 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10789 if order != CmpOrdering::Equal {
10790 return order;
10791 }
10792 }
10793 left.len().cmp(&right.len())
10794}
10795
10796const fn segment_kind_order(kind: SegmentKind) -> u8 {
10797 match kind {
10798 SegmentKind::Path => 0,
10799 SegmentKind::Package => 1,
10800 SegmentKind::Type => 2,
10801 SegmentKind::Companion => 3,
10802 SegmentKind::Nested => 4,
10803 SegmentKind::Member => 5,
10804 SegmentKind::Unknown => 6,
10805 }
10806}
10807
10808fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10809 *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10810 std::mem::take(units),
10811 );
10812}
10813
10814pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10815 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10819 brokk_bifrost_core::analyzer::Language::Cpp,
10820 reference,
10821 );
10822 cpp_reference_fqn_candidates_from_parts(&parts, kind)
10823}
10824
10825fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10826 if parts.is_empty() {
10827 return Vec::new();
10828 }
10829
10830 let mut candidates = Vec::new();
10831 for package_len in 0..parts.len() {
10832 let package = parts[..package_len].join("::");
10833 let rest = &parts[package_len..];
10834 if rest.is_empty() {
10835 continue;
10836 }
10837 match kind {
10838 TargetKind::Type | TargetKind::Constructor => {
10839 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10840 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10841 }
10842 TargetKind::FreeFunction
10843 | TargetKind::Method
10844 | TargetKind::GlobalField
10845 | TargetKind::MemberField
10846 | TargetKind::Macro => {
10847 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10848 if rest.len() > 1 {
10849 let owner = rest[..rest.len() - 1].join("$");
10850 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10851 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10852 }
10853 }
10854 }
10855 }
10856 candidates
10857}
10858
10859fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10860 let fqn = if package.is_empty() {
10861 short.to_string()
10862 } else {
10863 format!("{package}.{short}")
10864 };
10865 if !out.contains(&fqn) {
10866 out.push(fqn);
10867 }
10868}
10869
10870pub fn infer_cpp_initializer_type(
10871 analyzer: &CppGraphSource<'_>,
10872 visibility: &VisibilityIndex<'_>,
10873 file: &ProjectFile,
10874 source: &str,
10875 node: Node<'_>,
10876) -> Option<CodeUnit> {
10877 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10878 .and_then(|binding| binding.unit)
10879}
10880
10881pub fn infer_cpp_initializer_binding(
10882 analyzer: &CppGraphSource<'_>,
10883 visibility: &VisibilityIndex<'_>,
10884 file: &ProjectFile,
10885 source: &str,
10886 node: Node<'_>,
10887 receiver_resolver: Option<&ReceiverResolver<'_>>,
10888) -> Option<CppScanBinding> {
10889 match node.kind() {
10890 "new_expression" => {
10891 let type_node = cpp_new_expression_type_node(node)?;
10892 let type_text = node_text(type_node, source);
10893 let name = normalize_cpp_type_name(type_text);
10894 Some(CppScanBinding::from_type_name(
10895 name.clone(),
10896 visibility.resolve_type(file, &name),
10897 1,
10898 ))
10899 }
10900 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10901 if function.kind() == "field_expression" {
10909 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10910 return resolve_field_method_call_return_binding(
10911 analyzer,
10912 visibility,
10913 file,
10914 source,
10915 function,
10916 arity,
10917 receiver_resolver,
10918 );
10919 }
10920 let function_text = node_text(function, source);
10921 let direct_type_binding = visibility
10922 .resolve_type(file, function_text)
10923 .map(|unit| CppScanBinding::from_unit(unit, 0));
10924 if function.kind() == "template_function" && direct_type_binding.is_some() {
10925 let lexical_namespace = enclosing_namespace_context(node, source);
10926 let arity = visibility.call_arity_evidence(file, node, source).exact();
10927 if let Some(arity) = arity
10928 && let Some(binding) = visibility.resolve_call_return_binding(
10929 analyzer,
10930 file,
10931 function_text,
10932 arity,
10933 lexical_namespace.as_deref(),
10934 direct_type_binding
10935 .as_ref()
10936 .and_then(|binding| binding.unit.as_ref()),
10937 )
10938 {
10939 return Some(binding);
10940 }
10941 let (has_callable, callable_binding) = visibility
10942 .resolve_call_return_binding_without_arity(
10943 analyzer,
10944 file,
10945 function_text,
10946 lexical_namespace.as_deref(),
10947 direct_type_binding
10948 .as_ref()
10949 .and_then(|binding| binding.unit.as_ref()),
10950 );
10951 if let Some(binding) = callable_binding {
10952 return Some(binding);
10953 }
10954 if has_callable {
10955 return None;
10956 }
10957 return direct_type_binding;
10958 }
10959 let arity = visibility.call_arity_evidence(file, node, source).exact();
10964 if let Some(arity) = arity {
10965 let direct_type_binding_for_call = direct_type_binding.clone();
10966 if let Some(binding) = resolve_static_method_call_return_binding(
10967 analyzer, visibility, file, source, function, arity,
10968 )
10969 .or_else(|| {
10970 visibility.resolve_call_return_binding(
10975 analyzer,
10976 file,
10977 function_text,
10978 arity,
10979 enclosing_namespace_context(node, source).as_deref(),
10980 direct_type_binding_for_call
10981 .as_ref()
10982 .and_then(|binding| binding.unit.as_ref()),
10983 )
10984 }) {
10985 return Some(binding);
10986 }
10987 }
10988 direct_type_binding
10989 }),
10990 _ => None,
10991 }
10992}
10993
10994pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
11000 debug_assert_eq!(expression.kind(), "new_expression");
11001 if let Some(type_node) = expression.child_by_field_name("type") {
11002 return Some(type_node);
11003 }
11004
11005 let mut pending = Vec::new();
11006 let mut cursor = expression.walk();
11007 pending.extend(
11008 expression
11009 .named_children(&mut cursor)
11010 .filter(|child| {
11011 !matches!(
11012 child.kind(),
11013 "argument_list" | "initializer_list" | "new_declarator"
11014 )
11015 })
11016 .collect::<Vec<_>>()
11017 .into_iter()
11018 .rev(),
11019 );
11020 while let Some(node) = pending.pop() {
11021 if is_cpp_type_specifier_node(node) {
11022 return Some(node);
11023 }
11024 if node.kind() == "ERROR" {
11025 let mut cursor = node.walk();
11026 pending.extend(
11027 node.named_children(&mut cursor)
11028 .collect::<Vec<_>>()
11029 .into_iter()
11030 .rev(),
11031 );
11032 }
11033 }
11034 None
11035}
11036
11037fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
11038 matches!(
11039 node.kind(),
11040 "class_specifier"
11041 | "decltype"
11042 | "dependent_type"
11043 | "enum_specifier"
11044 | "placeholder_type_specifier"
11045 | "primitive_type"
11046 | "qualified_identifier"
11047 | "sized_type_specifier"
11048 | "struct_specifier"
11049 | "template_type"
11050 | "type_identifier"
11051 | "union_specifier"
11052 )
11053}
11054
11055fn resolve_static_method_call_return_binding(
11056 analyzer: &CppGraphSource<'_>,
11057 visibility: &VisibilityIndex<'_>,
11058 file: &ProjectFile,
11059 source: &str,
11060 function: Node<'_>,
11061 arity: usize,
11062) -> Option<CppScanBinding> {
11063 if function.kind() != "qualified_identifier" {
11064 return None;
11065 }
11066 let qualified = normalize_cpp_reference_text(node_text(function, source));
11067 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11074 brokk_bifrost_core::analyzer::Language::Cpp,
11075 &qualified,
11076 );
11077 let (owner_text, member_name) = match parts.split_last() {
11078 Some((member, owner_parts)) if !owner_parts.is_empty() => {
11079 (owner_parts.join("::"), member.clone())
11080 }
11081 _ => {
11082 let scope = function.child_by_field_name("scope")?;
11083 let name = function.child_by_field_name("name")?;
11084 (
11085 node_text(scope, source).to_string(),
11086 node_text(name, source).to_string(),
11087 )
11088 }
11089 };
11090 let owner = visibility.resolve_type(file, &owner_text)?;
11091 let candidates =
11092 callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
11093 unanimous_return_binding(analyzer, visibility, file, &candidates)
11094}
11095
11096fn resolve_field_method_call_return_binding(
11097 analyzer: &CppGraphSource<'_>,
11098 visibility: &VisibilityIndex<'_>,
11099 file: &ProjectFile,
11100 source: &str,
11101 function: Node<'_>,
11102 arity: usize,
11103 receiver_resolver: Option<&ReceiverResolver<'_>>,
11104) -> Option<CppScanBinding> {
11105 debug_assert_eq!(
11106 function.kind(),
11107 "field_expression",
11108 "the member-call return binding answers only for a field-expression callee"
11109 );
11110 let receiver_resolver = receiver_resolver?;
11111 let field = function.child_by_field_name("field")?;
11112 let member_name = node_text(function_terminal_node(field), source);
11113 let receiver = function
11114 .child_by_field_name("argument")
11115 .or_else(|| function.named_child(0))?;
11116 let owners = receiver_resolver(receiver, source);
11117 let mut candidates = Vec::new();
11118 for owner in owners {
11119 let declaring_owner =
11120 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
11121 EnclosingMemberOwnerResolution::Owner(owner) => owner,
11122 EnclosingMemberOwnerResolution::Missing => continue,
11123 EnclosingMemberOwnerResolution::Ambiguous => return None,
11124 };
11125 candidates.extend(callable_members_accepting_arity(
11126 analyzer,
11127 visibility,
11128 file,
11129 &declaring_owner,
11130 member_name,
11131 arity,
11132 ));
11133 }
11134 unanimous_return_binding(analyzer, visibility, file, &candidates)
11135}
11136
11137fn callable_members_accepting_arity(
11138 analyzer: &CppGraphSource<'_>,
11139 visibility: &VisibilityIndex<'_>,
11140 file: &ProjectFile,
11141 owner: &CodeUnit,
11142 member_name: &str,
11143 arity: usize,
11144) -> Vec<CodeUnit> {
11145 visibility
11146 .visible_members_for_owner_name(file, owner, member_name)
11147 .into_iter()
11148 .filter(|unit| {
11149 unit.is_function()
11150 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
11151 })
11152 .cloned()
11153 .collect()
11154}
11155
11156fn unanimous_return_binding(
11157 analyzer: &CppGraphSource<'_>,
11158 visibility: &VisibilityIndex<'_>,
11159 file: &ProjectFile,
11160 candidates: &[CodeUnit],
11161) -> Option<CppScanBinding> {
11162 let mut resolved_return: Option<CppScanBinding> = None;
11163 for function in candidates {
11164 let metadata = analyzer.signature_metadata(function);
11165 let return_types = if metadata.is_empty() {
11166 vec![cpp_function_return_type_text(analyzer, function)?]
11167 } else {
11168 metadata
11169 .iter()
11170 .map(|metadata| metadata.return_type_text().map(str::to_string))
11171 .collect::<Option<Vec<_>>>()?
11172 };
11173 for return_text in return_types {
11174 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
11175 let name = normalize_cpp_type_name(&return_text);
11176 let binding = CppScanBinding::from_type_name(
11177 name.clone(),
11178 visibility
11179 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
11180 indirection,
11181 );
11182 if let Some(existing) = resolved_return.as_ref()
11183 && (existing.indirection != binding.indirection
11184 || match (&existing.unit, &binding.unit) {
11185 (Some(left), Some(right)) => !same_visible_symbol(left, right),
11186 (None, None) => existing.type_name != binding.type_name,
11187 (Some(_), None) | (None, Some(_)) => true,
11188 })
11189 {
11190 return None;
11191 }
11192 resolved_return = Some(binding);
11193 }
11194 }
11195 resolved_return
11196}
11197
11198fn aliases_from_canonical_source(
11199 cpp: &dyn CppSource,
11200 token: QueryToken<'_>,
11201 file: &ProjectFile,
11202) -> Option<Vec<CppAlias>> {
11203 let source = cpp.declaration_source_facts(token, file)?;
11204 Some(
11205 source
11206 .facts
11207 .declarations
11208 .iter()
11209 .filter_map(|fact| {
11210 let alias = fact.file_scope_alias.as_ref()?;
11211 Some(CppAlias {
11212 name: alias.name.clone(),
11213 target: alias.target.clone(),
11214 namespace: alias.namespace.clone(),
11215 })
11216 })
11217 .collect(),
11218 )
11219}
11220
11221pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
11222 let mut current = node.parent();
11223 while let Some(parent) = current {
11224 match parent.kind() {
11225 "translation_unit"
11226 | "namespace_definition"
11227 | "declaration_list"
11228 | "linkage_specification" => current = parent.parent(),
11229 "template_declaration" => current = parent.parent(),
11230 _ => return false,
11231 }
11232 }
11233 true
11234}
11235
11236pub fn collect_include_closure(
11237 analyzer: &CppGraphSource<'_>,
11238 include_targets: &IncludeTargetIndex,
11239 file: &ProjectFile,
11240 out: &mut HashSet<ProjectFile>,
11241 cancellation: Option<&CancellationToken>,
11242) {
11243 let mut stack = vec![file.clone()];
11244 while let Some(file) = stack.pop() {
11245 if cancellation.is_some_and(CancellationToken::is_cancelled) {
11246 break;
11247 }
11248 if !out.insert(file.clone()) {
11249 continue;
11250 }
11251 for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
11252 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
11253 stack.push(target);
11254 }
11255 }
11256 }
11257}
11258
11259fn parse_macro_parameter_list<T>(
11260 replacement: &str,
11261 read: impl FnOnce(Node<'_>, &str) -> Option<T>,
11262) -> Option<T> {
11263 let parsed = parse_sentinel(
11264 "void __bifrost_macro_parameters(",
11265 SentinelInput::replacement(replacement),
11266 ");",
11267 );
11268 let (source, tree) = (parsed.source, parsed.tree);
11269 let root = tree.root_node();
11270 if root.has_error() {
11271 return None;
11272 }
11273 let declaration = single_named_child_matching(root, |child| !child.is_extra())?;
11274 let declarator = declaration.child_by_field_name("declarator")?;
11275 let parameters = declarator.child_by_field_name("parameters")?;
11276 read(parameters, &source)
11277}
11278
11279fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
11280 parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
11281}
11282
11283pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
11286 parse_macro_parameter_list(
11287 replacement,
11288 crate::declarations::cpp_callable_parameter_types,
11289 )
11290}
11291
11292pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
11293 brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
11294}
11295
11296pub fn cpp_callable_parameter_types(
11297 analyzer: &CppGraphSource<'_>,
11298 unit: &CodeUnit,
11299) -> Option<Vec<String>> {
11300 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
11301 analyzer.signature_metadata(unit),
11302 )
11303}
11304
11305fn merge_compatible_callable_arities(
11306 left: CallableArity,
11307 right: CallableArity,
11308) -> Option<CallableArity> {
11309 let total = left.total();
11310 let left_repeated = left.accepts(total.saturating_add(1));
11311 let right_repeated = right.accepts(right.total().saturating_add(1));
11312 if total != right.total() || left_repeated != right_repeated {
11313 return None;
11314 }
11315 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
11316 Some(CallableArity::new(required, total, left_repeated))
11317}
11318
11319fn collect_macro_events(
11320 cpp: &dyn CppSource,
11321 token: QueryToken<'_>,
11322 file: &ProjectFile,
11323) -> Vec<MacroEvent> {
11324 let Some(prepared) = cpp.prepared_syntax(token, file) else {
11325 return Vec::new();
11326 };
11327 let source = prepared.source();
11328 let mut events = Vec::new();
11329 let root = prepared.tree().root_node();
11330 let mut stack = vec![root];
11331 while let Some(node) = stack.pop() {
11332 match node.kind() {
11333 "preproc_def" | "preproc_function_def" => {
11334 let Some(name) = node.child_by_field_name("name") else {
11335 continue;
11336 };
11337 let name = node_text(name, source).to_string();
11338 events.push(MacroEvent::Define {
11339 name,
11340 binding: MacroBinding {
11341 source: file.clone(),
11342 declaration_byte: node.start_byte(),
11343 definition: VisibilityIndex::decode_macro_definition(node, source),
11344 exact: true,
11345 },
11346 byte: node.start_byte(),
11347 conditionals: owning_preprocessor_conditionals(root, node, source),
11348 });
11349 continue;
11350 }
11351 "preproc_include" => {
11352 let Some(path) = node.child_by_field_name("path") else {
11353 events.push(MacroEvent::Include {
11354 targets: Vec::new(),
11355 byte: node.start_byte(),
11356 conditionals: owning_preprocessor_conditionals(root, node, source),
11357 });
11358 continue;
11359 };
11360 let include = crate::imports::include_path_literal_value(path, source);
11361 let targets = include.map_or_else(Vec::new, |include| {
11362 resolve_include_targets_with_index(file, include, &cpp.include_target_index())
11363 });
11364 if targets.is_empty()
11375 && include.is_some_and(|include| {
11376 !cpp.include_target_index().names_indexed_file(include)
11377 })
11378 {
11379 continue;
11380 }
11381 events.push(MacroEvent::Include {
11382 targets,
11383 byte: node.start_byte(),
11384 conditionals: owning_preprocessor_conditionals(root, node, source),
11385 });
11386 continue;
11387 }
11388 "preproc_call" => {
11389 let Some(directive) = node.child_by_field_name("directive") else {
11390 continue;
11391 };
11392 if node_text(directive, source) != "#undef" {
11393 continue;
11394 }
11395 let name = node
11396 .child_by_field_name("argument")
11397 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
11398 if let Some(name) = name {
11399 events.push(MacroEvent::Undef {
11400 name,
11401 byte: node.start_byte(),
11402 conditionals: owning_preprocessor_conditionals(root, node, source),
11403 });
11404 } else {
11405 events.push(MacroEvent::Invalidate {
11406 byte: node.start_byte(),
11407 });
11408 }
11409 continue;
11410 }
11411 _ => {}
11412 }
11413 push_named_children_reversed(node, &mut stack);
11414 }
11415 events.sort_by_key(MacroEvent::byte);
11416 events
11417}
11418
11419fn find_include_activation<'guards>(
11420 cpp: &dyn CppSource,
11421 token: QueryToken<'_>,
11422 file: &ProjectFile,
11423 prepared: &PreparedSyntaxTree,
11424 donor_source: &ProjectFile,
11425 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11426) -> Option<usize> {
11427 let include_targets = cpp.include_target_index();
11428 let mut direct_includes = Vec::new();
11429 let mut nodes = vec![prepared.tree().root_node()];
11430 while let Some(node) = nodes.pop() {
11433 if node.kind() == "preproc_include" {
11434 let Some(required_guards) =
11435 include_directive_guard_requirements(node, prepared.source(), effective_guards)
11436 else {
11437 continue;
11438 };
11439 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11440 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11441 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
11442 file,
11443 include,
11444 &include_targets,
11445 )) {
11446 direct_includes.push((node.end_byte(), target));
11447 }
11448 }
11449 }
11450 continue;
11451 }
11452 push_named_children_reversed(node, &mut nodes);
11453 }
11454 direct_includes.sort_by_key(|(activation, _)| *activation);
11455 let mut known_missing = HashSet::default();
11456 direct_includes
11457 .into_iter()
11458 .find(|(_, direct)| {
11459 unconditional_include_reaches(
11460 cpp,
11461 token,
11462 direct,
11463 donor_source,
11464 file,
11465 &mut known_missing,
11466 effective_guards,
11467 )
11468 })
11469 .map(|(activation, _)| activation)
11470}
11471
11472fn find_conditional_include_projection_index<'guards>(
11473 cpp: &dyn CppSource,
11474 token: QueryToken<'_>,
11475 file: &ProjectFile,
11476 prepared: &PreparedSyntaxTree,
11477 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11478 on_state: &dyn Fn(),
11479) -> ConditionalIncludeProjectionIndex {
11480 let include_targets = cpp.include_target_index();
11481 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
11482 HashMap::default();
11483 let mut pending = Vec::new();
11484 let mut nodes = vec![prepared.tree().root_node()];
11485 while let Some(node) = nodes.pop() {
11486 if node.kind() == "preproc_include" {
11487 let Some(required_guards) =
11488 include_directive_guard_requirements(node, prepared.source(), effective_guards)
11489 else {
11490 continue;
11491 };
11492 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11493 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11494 file,
11495 include,
11496 &include_targets,
11497 )) else {
11498 continue;
11499 };
11500 pending.push((target, node.end_byte(), required_guards.clone()));
11501 }
11502 continue;
11503 }
11504 push_named_children_reversed(node, &mut nodes);
11505 }
11506
11507 let mut expanded: HashMap<(ProjectFile, usize), Vec<PreprocessorGuardEnvironment>> =
11513 HashMap::default();
11514 while let Some((current_file, activation_byte, path)) = pending.pop() {
11515 let guard_sets = expanded
11516 .entry((current_file.clone(), activation_byte))
11517 .or_default();
11518 if guard_sets.iter().any(|existing| existing.is_subset(&path)) {
11519 continue;
11520 }
11521 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
11522 .drain(..)
11523 .partition(|existing| path.is_subset(existing));
11524 *guard_sets = kept;
11525 guard_sets.push(path.clone());
11526 if !evicted.is_empty()
11527 && let Some(projections) = projections_by_source.get_mut(¤t_file)
11528 {
11529 projections.retain(|projection| {
11530 projection.activation_byte != activation_byte
11531 || !evicted.iter().any(|existing| {
11532 existing.all == projection.required_guards
11533 && existing.partial == projection.partial_guards
11534 })
11535 });
11536 }
11537 on_state();
11538
11539 projections_by_source
11542 .entry(current_file.clone())
11543 .or_default()
11544 .push(ConditionalIncludeProjection {
11545 activation_byte,
11546 required_guards: path.all.clone(),
11547 partial_guards: path.partial.clone(),
11548 });
11549
11550 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11551 continue;
11552 };
11553 let mut nodes = vec![current_prepared.tree().root_node()];
11554 while let Some(node) = nodes.pop() {
11555 if node.kind() == "preproc_include" {
11556 let Some(include_guards) = include_directive_guard_requirements(
11557 node,
11558 current_prepared.source(),
11559 effective_guards,
11560 ) else {
11561 continue;
11562 };
11563 let Some(reached) = path.merged(&include_guards) else {
11564 continue;
11565 };
11566 for (include, _) in
11567 include_path_from_node(node, current_prepared.source()).into_iter()
11568 {
11569 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11570 ¤t_file,
11571 include,
11572 &include_targets,
11573 )) else {
11574 continue;
11575 };
11576 pending.push((target, activation_byte, reached.clone()));
11577 }
11578 continue;
11579 }
11580 push_named_children_reversed(node, &mut nodes);
11581 }
11582 }
11583
11584 projections_by_source
11585 .into_iter()
11586 .map(|(source, mut projections)| {
11587 projections.sort_by_key(|projection| projection.activation_byte);
11588 (source, Arc::from(projections))
11589 })
11590 .collect()
11591}
11592
11593#[allow(clippy::too_many_arguments)]
11599fn find_conditional_include_projection_for_source<'guards>(
11600 cpp: &dyn CppSource,
11601 token: QueryToken<'_>,
11602 file: &ProjectFile,
11603 prepared: &PreparedSyntaxTree,
11604 donor_source: &ProjectFile,
11605 admission: IncludePathAdmission,
11606 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11607 reference_byte: usize,
11608 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11609 on_state: &dyn Fn(),
11610) -> bool {
11611 let Some(reference_guards) = reference_guards else {
11612 return false;
11613 };
11614 let include_targets = cpp.include_target_index();
11615 let mut pending = Vec::new();
11616 let mut nodes = vec![prepared.tree().root_node()];
11617 while let Some(node) = nodes.pop() {
11618 if node.kind() == "preproc_include" {
11619 let Some(required_guards) =
11620 include_directive_guard_requirements(node, prepared.source(), effective_guards)
11621 else {
11622 continue;
11623 };
11624 if node.end_byte() > reference_byte
11625 || !admission.admits(
11626 &required_guards.all,
11627 &required_guards.partial,
11628 Some(reference_guards),
11629 )
11630 {
11631 continue;
11632 }
11633 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11634 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11635 file,
11636 include,
11637 &include_targets,
11638 )) else {
11639 continue;
11640 };
11641 if &target == donor_source {
11642 return true;
11643 }
11644 pending.push((target, required_guards.clone()));
11645 }
11646 continue;
11647 }
11648 push_named_children_reversed(node, &mut nodes);
11649 }
11650
11651 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11652 while let Some((current_file, path)) = pending.pop() {
11653 let guard_sets = expanded.entry(current_file.clone()).or_default();
11654 if guard_sets.contains(&path.all) {
11655 continue;
11656 }
11657 guard_sets.push(path.all.clone());
11658 on_state();
11659
11660 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11661 continue;
11662 };
11663 let mut nodes = vec![current_prepared.tree().root_node()];
11664 while let Some(node) = nodes.pop() {
11665 if node.kind() == "preproc_include" {
11666 let Some(include_guards) = include_directive_guard_requirements(
11667 node,
11668 current_prepared.source(),
11669 effective_guards,
11670 ) else {
11671 continue;
11672 };
11673 let Some(reached) = path.merged(&include_guards) else {
11674 continue;
11675 };
11676 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11677 continue;
11678 }
11679 for (include, _) in
11680 include_path_from_node(node, current_prepared.source()).into_iter()
11681 {
11682 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11683 ¤t_file,
11684 include,
11685 &include_targets,
11686 )) else {
11687 continue;
11688 };
11689 if &target == donor_source {
11690 return true;
11691 }
11692 pending.push((target, reached.clone()));
11693 }
11694 continue;
11695 }
11696 push_named_children_reversed(node, &mut nodes);
11697 }
11698 }
11699 false
11700}
11701
11702pub fn cpp_include_closure_reaches(
11715 cpp: &dyn CppSource,
11716 token: QueryToken<'_>,
11717 translation_unit: &ProjectFile,
11718 header: &ProjectFile,
11719) -> bool {
11720 let language_guard_stable = OnceCell::new();
11721 let effective = OnceCell::new();
11722 let proven = compile_proven_guard_facts(cpp, translation_unit);
11723 unconditional_include_reaches(
11724 cpp,
11725 token,
11726 translation_unit,
11727 header,
11728 translation_unit,
11729 &mut HashSet::default(),
11730 &|| {
11731 effective
11732 .get_or_init(|| {
11733 effective_reference_guards(
11734 cpp,
11735 translation_unit,
11736 Some(&HashSet::default()),
11737 &proven,
11738 &|| {
11739 *language_guard_stable.get_or_init(|| {
11740 include_language_guard_is_stable_with(
11741 cpp,
11742 token,
11743 translation_unit,
11744 &mut |file: &ProjectFile| {
11745 collect_macro_events(cpp, token, file).into_boxed_slice()
11746 },
11747 &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11748 collect_structured_include_facts(prepared)
11749 },
11750 )
11751 })
11752 },
11753 )
11754 })
11755 .as_ref()
11756 },
11757 )
11758}
11759
11760fn unconditional_include_reaches<'guards>(
11761 cpp: &dyn CppSource,
11762 token: QueryToken<'_>,
11763 first: &ProjectFile,
11764 donor_source: &ProjectFile,
11765 reference_file: &ProjectFile,
11766 known_missing: &mut HashSet<ProjectFile>,
11767 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11768) -> bool {
11769 if first == donor_source {
11770 return true;
11771 }
11772 if known_missing.contains(first) {
11773 return false;
11774 }
11775 if let Some(reaches) =
11776 cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11777 {
11778 return reaches;
11779 }
11780 let mut visited = HashSet::default();
11781 let mut files = vec![first.clone()];
11782 let include_targets = cpp.include_target_index();
11783 while let Some(file) = files.pop() {
11784 if file == *donor_source {
11785 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11786 return true;
11787 }
11788 if known_missing.contains(&file) || !visited.insert(file.clone()) {
11789 continue;
11790 }
11791 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11792 continue;
11793 };
11794 let mut cursor = prepared.tree().walk();
11796 let mut nodes = vec![prepared.tree().root_node()];
11797 while let Some(node) = nodes.pop() {
11798 if node.kind() == "preproc_include" {
11799 let Some(required_guards) =
11800 include_directive_guard_requirements(node, prepared.source(), effective_guards)
11801 else {
11802 continue;
11803 };
11804 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11805 for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11806 {
11807 if let Some(target) = unique_include_target(
11808 resolve_include_targets_with_index(&file, include, &include_targets),
11809 ) {
11810 files.push(target);
11811 }
11812 }
11813 }
11814 continue;
11815 }
11816 let first_pushed = nodes.len();
11817 nodes.extend(node.named_children(&mut cursor));
11818 nodes[first_pushed..].reverse();
11819 }
11820 }
11821 known_missing.extend(visited);
11822 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11823 false
11824}
11825
11826fn effective_reference_guards(
11833 cpp: &dyn CppSource,
11834 file: &ProjectFile,
11835 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11836 proven: &HashSet<PreprocessorGuard>,
11837 language_guard_is_stable: &dyn Fn() -> bool,
11838) -> Option<HashSet<PreprocessorGuard>> {
11839 let mut effective = reference_guards.cloned()?;
11840 effective.extend(proven.iter().cloned());
11841 if language_guard_is_stable() {
11842 effective.insert(if reference_uses_c_semantics(cpp, file) {
11843 PreprocessorGuard::Undefined("__cplusplus".to_string())
11844 } else {
11845 PreprocessorGuard::Defined("__cplusplus".to_string())
11846 });
11847 }
11848 Some(effective)
11849}
11850
11851fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11852 cpp: &dyn CppSource,
11853 token: QueryToken<'_>,
11854 file: &ProjectFile,
11855 mut macro_events: MacroEvents,
11856 mut include_facts: IncludeFacts,
11857) -> bool
11858where
11859 MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11860 IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11861{
11862 if !crate::imports::is_cpp_translation_unit(file) {
11866 return false;
11867 }
11868 if !cpp.compile_contexts_for(file).is_empty()
11871 || cpp
11872 .reaching_translation_units(file)
11873 .iter()
11874 .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11875 {
11876 return false;
11877 }
11878 let mut pending = vec![file.clone()];
11879 let mut visited = HashSet::default();
11880 while let Some(file) = pending.pop() {
11881 if !visited.insert(file.clone()) {
11882 continue;
11883 }
11884 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11885 return false;
11886 };
11887 if macro_events(&file).iter().any(|event| match event {
11888 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11889 name == "__cplusplus"
11890 }
11891 MacroEvent::Invalidate { .. } => true,
11892 MacroEvent::Include { targets, .. } => targets.is_empty(),
11893 }) {
11894 return false;
11895 }
11896 for fact in include_facts(&file, prepared.as_ref()).iter() {
11899 let targets =
11900 resolve_include_targets_with_index(&file, &fact.path, &cpp.include_target_index());
11901 if targets.is_empty() {
11902 return false;
11903 }
11904 pending.extend(targets);
11905 }
11906 }
11907 true
11908}
11909
11910fn declaration_guard_requirements(
11911 analyzer: &CppGraphSource<'_>,
11912 cpp: &dyn CppSource,
11913 candidate: &CodeUnit,
11914) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11915 if cpp
11916 .prepared_syntax(analyzer.token, candidate.source())
11917 .is_none()
11918 {
11919 return Vec::new();
11920 }
11921 analyzer
11922 .declaration_source_occurrences(candidate)
11923 .into_iter()
11924 .flatten()
11925 .filter_map(|occurrence| {
11926 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11927 Some((occurrence.range.start_byte, guards))
11928 })
11929 .collect()
11930}
11931
11932struct DeclarationGuardSite {
11935 byte: usize,
11937 guards: HashSet<PreprocessorGuard>,
11939 completed_family_before_reference: bool,
11942}
11943
11944fn declaration_guard_sites(
11947 analyzer: &CppGraphSource<'_>,
11948 cpp: &dyn CppSource,
11949 candidate: &CodeUnit,
11950 reference_byte: usize,
11951) -> Vec<DeclarationGuardSite> {
11952 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11953 return Vec::new();
11954 };
11955 let root = prepared.tree().root_node();
11956 analyzer
11957 .declaration_source_occurrences(candidate)
11958 .into_iter()
11959 .flatten()
11960 .filter_map(|occurrence| {
11961 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11962 Some(DeclarationGuardSite {
11963 byte: occurrence.range.start_byte,
11964 guards,
11965 completed_family_before_reference: {
11966 root.descendant_for_byte_range(
11967 occurrence.range.start_byte,
11968 occurrence.range.end_byte,
11969 )
11970 .is_some_and(|node| {
11971 declaration_follows_completed_family_before_reference(
11972 node,
11973 prepared.source(),
11974 reference_byte,
11975 )
11976 })
11977 },
11978 })
11979 })
11980 .collect()
11981}
11982
11983fn declaration_follows_completed_family_before_reference(
12002 node: Node<'_>,
12003 source: &str,
12004 reference_byte: usize,
12005) -> bool {
12006 callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
12007}
12008
12009pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
12012 let mut cache = PreprocessorContextCache::default();
12013 callable_guard_completion_byte_with_cache(node, source, &mut cache)
12014}
12015
12016pub(crate) fn callable_guard_completion_byte_with_cache(
12017 node: Node<'_>,
12018 source: &str,
12019 cache: &mut PreprocessorContextCache,
12020) -> Option<usize> {
12021 let mut completion_byte = 0;
12022 let mut ancestor = node.parent();
12023 while let Some(conditional) = ancestor {
12024 if matches!(
12025 conditional.kind(),
12026 "preproc_if" | "preproc_ifdef" | "preproc_elif"
12027 ) && !is_file_covering_include_guard(conditional, source)
12028 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12029 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12030 {
12031 let family = preprocessor_conditional_family_root(conditional);
12032 if !preprocessor_conditional_family_has_terminal_else(family, cache) {
12033 return None;
12034 }
12035 completion_byte = completion_byte.max(family.end_byte());
12036 }
12037 ancestor = conditional.parent();
12038 }
12039 Some(completion_byte)
12040}
12041
12042fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
12043 analyzer
12044 .ranges(candidate)
12045 .into_iter()
12046 .map(|range| range.start_byte)
12047 .min()
12048}
12049
12050fn compile_proven_guard_facts(
12052 cpp: &dyn CppSource,
12053 file: &ProjectFile,
12054) -> HashSet<PreprocessorGuard> {
12055 if let Some(facts) = context_guard_facts(&cpp.compile_contexts_for(file), file) {
12056 return facts;
12057 }
12058 let mut translation_units = cpp.reaching_translation_units(file).into_iter();
12059 let Some(mut facts) = translation_units.next().and_then(|translation_unit| {
12060 context_guard_facts(
12061 &cpp.compile_contexts_for(&translation_unit),
12062 &translation_unit,
12063 )
12064 }) else {
12065 return HashSet::default();
12066 };
12067 for translation_unit in translation_units {
12068 let Some(reached) = context_guard_facts(
12069 &cpp.compile_contexts_for(&translation_unit),
12070 &translation_unit,
12071 ) else {
12072 facts.clear();
12073 break;
12074 };
12075 facts.retain(|fact| reached.contains(fact));
12076 if facts.is_empty() {
12077 break;
12078 }
12079 }
12080 facts
12081}
12082
12083fn context_guard_facts(
12087 contexts: &[CppCompileContext],
12088 file: &ProjectFile,
12089) -> Option<HashSet<PreprocessorGuard>> {
12090 let (first, rest) = contexts.split_first()?;
12091 let mut facts = first
12092 .defined_macros
12093 .iter()
12094 .filter(|name| {
12095 rest.iter()
12096 .all(|context| context.defined_macros.contains(*name))
12097 })
12098 .cloned()
12099 .map(PreprocessorGuard::Defined)
12100 .collect::<HashSet<_>>();
12101 let language = first.tu_language(file.rel_path());
12102 if contexts
12106 .iter()
12107 .all(|context| !context.cplusplus_macro_configured)
12108 && rest
12109 .iter()
12110 .all(|context| context.tu_language(file.rel_path()) == language)
12111 {
12112 facts.insert(match language {
12113 CompiledLanguage::C => PreprocessorGuard::Undefined("__cplusplus".to_string()),
12114 CompiledLanguage::Cpp => PreprocessorGuard::Defined("__cplusplus".to_string()),
12115 });
12116 }
12117 Some(facts)
12118}
12119
12120pub fn guard_requirements_hold_at_reference(
12121 required: &HashSet<PreprocessorGuard>,
12122 reference: Option<&HashSet<PreprocessorGuard>>,
12123) -> bool {
12124 reference.is_some_and(|active| {
12125 required
12126 .iter()
12127 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
12128 })
12129}
12130
12131fn preprocessor_guard_holds_at_reference(
12132 required: &PreprocessorGuard,
12133 active: &HashSet<PreprocessorGuard>,
12134) -> bool {
12135 if active.contains(required) {
12136 return true;
12137 }
12138 let active_expression =
12139 BooleanGuardExpression::all(active.iter().map(PreprocessorGuard::as_boolean_expression));
12140 active_expression.implies(&required.as_boolean_expression())
12141}
12142
12143fn guards_compatible_at_reference(
12147 declaration: &HashSet<PreprocessorGuard>,
12148 reference: Option<&HashSet<PreprocessorGuard>>,
12149) -> bool {
12150 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
12151}
12152
12153pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
12154 let mut ancestor = Some(node);
12155 while let Some(current) = ancestor {
12156 if is_cpp_preprocessor_conditional(current)
12157 && preprocessor_conditional_contains_descendant(current, node)
12158 {
12159 return Some(preprocessor_conditional_family_root(current));
12160 }
12161 ancestor = current.parent();
12162 }
12163 None
12164}
12165
12166pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
12167 node: Node<'tree>,
12168 cache: &mut PreprocessorContextCache,
12169) -> Option<Node<'tree>> {
12170 let mut ancestor = node.parent();
12171 while let Some(current) = ancestor {
12172 if is_cpp_preprocessor_conditional(current)
12173 && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
12174 {
12175 let family = preprocessor_conditional_family_root(current);
12176 if preprocessor_conditional_family_has_terminal_else(family, cache) {
12177 return Some(family);
12178 }
12179 }
12180 ancestor = current.parent();
12181 }
12182 None
12183}
12184
12185fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
12186 while let Some(parent) = conditional.parent() {
12187 let is_alternative = parent
12188 .child_by_field_name("alternative")
12189 .is_some_and(|alternative| {
12190 alternative.start_byte() == conditional.start_byte()
12191 && alternative.end_byte() == conditional.end_byte()
12192 });
12193 if !is_alternative {
12194 break;
12195 }
12196 conditional = parent;
12197 }
12198 conditional
12199}
12200
12201fn preprocessor_conditional_family_has_terminal_else(
12202 mut conditional: Node<'_>,
12203 cache: &mut PreprocessorContextCache,
12204) -> bool {
12205 loop {
12206 let Some(alternative) = conditional.child_by_field_name("alternative") else {
12207 return cache
12208 .displaced_branch(conditional)
12209 .is_some_and(|directive| directive.is_else);
12210 };
12211 match alternative.kind() {
12212 "preproc_else" => return true,
12213 "preproc_elif" => conditional = alternative,
12214 _ => return false,
12215 }
12216 }
12217}
12218
12219fn include_directive_guard_requirements<'guards>(
12232 node: Node<'_>,
12233 source: &str,
12234 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
12235) -> Option<PreprocessorGuardEnvironment> {
12236 let mut required = preprocessor_guard_environment_by_family(node, source)?;
12237 if !required.all.is_empty() {
12238 let effective = effective_guards();
12239 if !guards_compatible_at_reference(&required.all, effective) {
12240 return None;
12241 }
12242 for guards in [&mut required.all, &mut required.partial] {
12243 guards.retain(|guard| {
12244 !effective
12245 .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active))
12246 });
12247 }
12248 }
12249 Some(required)
12250}
12251
12252pub fn preprocessor_guard_environment(
12253 node: Node<'_>,
12254 source: &str,
12255) -> Option<HashSet<PreprocessorGuard>> {
12256 let mut cache = PreprocessorContextCache::default();
12257 preprocessor_guard_environment_with_cache(node, source, &mut cache)
12258}
12259
12260pub(crate) fn preprocessor_guard_environment_with_cache(
12261 node: Node<'_>,
12262 source: &str,
12263 cache: &mut PreprocessorContextCache,
12264) -> Option<HashSet<PreprocessorGuard>> {
12265 preprocessor_guard_environment_by_family_with_cache(node, source, cache)
12266 .map(|environment| environment.all)
12267}
12268
12269#[derive(Clone)]
12280struct PreprocessorGuardEnvironment {
12281 all: HashSet<PreprocessorGuard>,
12282 partial: HashSet<PreprocessorGuard>,
12283}
12284
12285impl PreprocessorGuardEnvironment {
12286 fn is_subset(&self, other: &Self) -> bool {
12287 self.all.is_subset(&other.all) && self.partial.is_subset(&other.partial)
12288 }
12289
12290 fn merged(&self, other: &Self) -> Option<Self> {
12294 Some(Self {
12295 all: merge_preprocessor_guards(&self.all, &other.all)?,
12296 partial: self.partial.union(&other.partial).cloned().collect(),
12297 })
12298 }
12299}
12300
12301fn preprocessor_guard_environment_by_family(
12302 node: Node<'_>,
12303 source: &str,
12304) -> Option<PreprocessorGuardEnvironment> {
12305 let mut cache = PreprocessorContextCache::default();
12306 preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
12307}
12308
12309fn preprocessor_guard_environment_by_family_with_cache(
12310 node: Node<'_>,
12311 source: &str,
12312 cache: &mut PreprocessorContextCache,
12313) -> Option<PreprocessorGuardEnvironment> {
12314 let mut all = HashSet::default();
12315 let mut partial = HashSet::default();
12316 let mut ancestor = node.parent();
12317 while let Some(conditional) = ancestor {
12318 if matches!(
12319 conditional.kind(),
12320 "preproc_if" | "preproc_ifdef" | "preproc_elif"
12321 ) && !is_file_covering_include_guard(conditional, source)
12322 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12323 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12324 {
12325 let guard = preprocessor_guard_for_descendant(conditional, node, source, cache)?;
12326 match guard {
12327 PreprocessorGuard::Constant(true) => {
12328 ancestor = conditional.parent();
12329 continue;
12330 }
12331 PreprocessorGuard::Constant(false) => return None,
12332 _ => {}
12333 }
12334 if all.contains(&guard.negated()) {
12335 return None;
12336 }
12337 if !preprocessor_conditional_family_has_terminal_else(
12338 preprocessor_conditional_family_root(conditional),
12339 cache,
12340 ) {
12341 partial.insert(guard.clone());
12342 }
12343 all.insert(guard);
12344 }
12345 ancestor = conditional.parent();
12346 }
12347 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
12348 match guard {
12349 PreprocessorGuard::Constant(true) => {}
12350 PreprocessorGuard::Constant(false) => return None,
12351 _ => {
12352 if all.contains(&guard.negated()) {
12353 return None;
12354 }
12355 partial.insert(guard.clone());
12359 all.insert(guard);
12360 }
12361 }
12362 }
12363 Some(PreprocessorGuardEnvironment { all, partial })
12364}
12365
12366pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
12367 node: Node<'_>,
12368 source: &str,
12369 cache: &mut PreprocessorContextCache,
12370) -> Option<HashSet<PreprocessorGuard>> {
12371 let mut guards = HashSet::default();
12372 for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
12373 match guard {
12374 PreprocessorGuard::Constant(true) => {}
12375 PreprocessorGuard::Constant(false) => return None,
12376 _ => {
12377 if guards.contains(&guard.negated()) {
12378 return None;
12379 }
12380 guards.insert(guard);
12381 }
12382 }
12383 }
12384 Some(guards)
12385}
12386
12387fn callable_preprocessor_ancestor_guards(
12388 node: Node<'_>,
12389 source: &str,
12390) -> Option<Vec<PreprocessorGuard>> {
12391 let mut cache = PreprocessorContextCache::default();
12392 callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
12393}
12394
12395fn callable_preprocessor_ancestor_guards_with_cache(
12396 node: Node<'_>,
12397 source: &str,
12398 cache: &mut PreprocessorContextCache,
12399) -> Option<Vec<PreprocessorGuard>> {
12400 let mut guards = Vec::new();
12401 let mut ancestor = node.parent();
12402 while let Some(conditional) = ancestor {
12403 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
12404 && !is_file_covering_include_guard(conditional, source)
12405 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12406 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12407 {
12408 guards.push(preprocessor_guard_for_descendant(
12409 conditional,
12410 node,
12411 source,
12412 cache,
12413 )?);
12414 }
12415 ancestor = conditional.parent();
12416 }
12417 Some(guards)
12418}
12419
12420fn fragmented_statement_preprocessor_guard(
12421 descendant: Node<'_>,
12422 source: &str,
12423) -> Option<PreprocessorGuard> {
12424 let mut ancestor = descendant.parent();
12430 while let Some(statement) = ancestor {
12431 if statement.kind() == "if_statement"
12432 && let (Some(consequence), Some(alternative)) = (
12433 statement.child_by_field_name("consequence"),
12434 statement.child_by_field_name("alternative"),
12435 )
12436 && alternative.start_byte() <= descendant.start_byte()
12437 && descendant.end_byte() <= alternative.end_byte()
12438 {
12439 let mut cursor = consequence.walk();
12440 let openers = consequence
12441 .named_children(&mut cursor)
12442 .filter(|child| {
12443 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
12444 && child
12445 .child(child.child_count().saturating_sub(1))
12446 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
12447 })
12448 .collect::<Vec<_>>();
12449 if openers.len() != 1 {
12450 ancestor = statement.parent();
12451 continue;
12452 }
12453
12454 let mut terminators = Vec::new();
12455 let mut stack = vec![alternative];
12456 while let Some(node) = stack.pop() {
12457 if node.kind() == "preproc_call"
12458 && node.start_byte() >= descendant.end_byte()
12459 && node
12460 .child_by_field_name("directive")
12461 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
12462 {
12463 terminators.push(node);
12464 continue;
12465 }
12466 push_named_children_reversed(node, &mut stack);
12467 }
12468 if terminators.len() == 1 {
12469 return simple_preprocessor_guard(openers[0], source);
12470 }
12471 }
12472 ancestor = statement.parent();
12473 }
12474 None
12475}
12476
12477pub(crate) fn preprocessor_guard_for_descendant(
12478 conditional: Node<'_>,
12479 descendant: Node<'_>,
12480 source: &str,
12481 cache: &mut PreprocessorContextCache,
12482) -> Option<PreprocessorGuard> {
12483 let guard = simple_preprocessor_guard(conditional, source)?;
12484 match preprocessor_branch_of_descendant(conditional, descendant, cache) {
12485 PreprocessorBranch::Consequence => Some(guard),
12486 PreprocessorBranch::Structured(alternative)
12490 if matches!(alternative.kind(), "preproc_else" | "preproc_elif") =>
12491 {
12492 Some(guard.negated())
12493 }
12494 PreprocessorBranch::DisplacedElse => Some(guard.negated()),
12495 PreprocessorBranch::Structured(_) | PreprocessorBranch::Unreadable => None,
12496 }
12497}
12498
12499enum PreprocessorBranch<'tree> {
12501 Consequence,
12502 Structured(Node<'tree>),
12504 DisplacedElse,
12507 Unreadable,
12509}
12510
12511fn preprocessor_branch_of_descendant<'tree>(
12512 conditional: Node<'tree>,
12513 descendant: Node<'_>,
12514 cache: &mut PreprocessorContextCache,
12515) -> PreprocessorBranch<'tree> {
12516 if let Some(alternative) =
12517 conditional
12518 .child_by_field_name("alternative")
12519 .filter(|alternative| {
12520 alternative.start_byte() <= descendant.start_byte()
12521 && descendant.end_byte() <= alternative.end_byte()
12522 })
12523 {
12524 return PreprocessorBranch::Structured(alternative);
12525 }
12526 match cache.displaced_branch(conditional) {
12527 Some(directive) if directive.end_byte <= descendant.start_byte() => {
12528 if directive.is_else {
12529 PreprocessorBranch::DisplacedElse
12530 } else {
12531 PreprocessorBranch::Unreadable
12532 }
12533 }
12534 _ => PreprocessorBranch::Consequence,
12535 }
12536}
12537
12538pub(crate) fn preprocessor_conditional_contains_descendant(
12539 conditional: Node<'_>,
12540 descendant: Node<'_>,
12541) -> bool {
12542 cpp_displaced_preprocessor_boundary(conditional)
12543 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
12544}
12545
12546fn preprocessor_conditional_contains_descendant_with_cache(
12547 conditional: Node<'_>,
12548 descendant: Node<'_>,
12549 cache: &mut PreprocessorContextCache,
12550) -> bool {
12551 cache
12552 .displaced_boundary_end(conditional)
12553 .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
12554}
12555
12556pub fn merge_preprocessor_guards(
12557 left: &HashSet<PreprocessorGuard>,
12558 right: &HashSet<PreprocessorGuard>,
12559) -> Option<HashSet<PreprocessorGuard>> {
12560 let mut merged = left.clone();
12561 for guard in right {
12562 let boolean_negation = guard.as_boolean_expression().negated();
12563 if merged.contains(&guard.negated())
12564 || merged
12565 .iter()
12566 .map(PreprocessorGuard::as_boolean_expression)
12567 .any(|existing| existing == boolean_negation)
12568 {
12569 return None;
12570 }
12571 merged.insert(guard.clone());
12572 }
12573 Some(merged)
12574}
12575
12576fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
12577 if conditional.kind() == "preproc_ifdef" {
12578 let name = conditional.child_by_field_name("name")?;
12579 let name = node_text(name, source).to_string();
12580 return match conditional.child(0)?.kind() {
12581 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
12582 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
12583 _ => None,
12584 };
12585 }
12586 let condition = conditional.child_by_field_name("condition")?;
12587 simple_preprocessor_expression_guard(condition, source).or_else(|| {
12588 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
12589 node_text(condition, source),
12590 )))
12591 })
12592}
12593
12594fn simple_preprocessor_expression_guard(
12595 expression: Node<'_>,
12596 source: &str,
12597) -> Option<PreprocessorGuard> {
12598 match expression.kind() {
12599 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
12600 node_text(expression, source).to_string(),
12601 ))),
12602 "number_literal" => match node_text(expression, source).trim() {
12603 "0" => Some(PreprocessorGuard::Constant(false)),
12604 "1" => Some(PreprocessorGuard::Constant(true)),
12605 _ => None,
12606 },
12607 "preproc_defined" => {
12608 let identifier = (0..expression.named_child_count())
12609 .filter_map(|index| expression.named_child(index))
12610 .find(|child| child.kind() == "identifier")?;
12611 Some(PreprocessorGuard::Defined(
12612 node_text(identifier, source).to_string(),
12613 ))
12614 }
12615 "unary_expression"
12616 if expression
12617 .child_by_field_name("operator")
12618 .is_some_and(|operator| operator.kind() == "!") =>
12619 {
12620 simple_preprocessor_expression_guard(
12621 expression.child_by_field_name("argument")?,
12622 source,
12623 )
12624 .map(|guard| guard.negated())
12625 }
12626 "parenthesized_expression" => (0..expression.named_child_count())
12627 .filter_map(|index| expression.named_child(index))
12628 .next()
12629 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
12630 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
12631 expression, source,
12632 ))),
12633 _ => None,
12634 }
12635}
12636
12637fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
12638 match expression.kind() {
12639 "number_literal" => match node_text(expression, source).trim() {
12640 "0" => BooleanGuardExpression::Constant(false),
12641 "1" => BooleanGuardExpression::Constant(true),
12642 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12643 expression, source,
12644 ))),
12645 },
12646 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
12647 "preproc_defined" => {
12648 let identifier = (0..expression.named_child_count())
12649 .filter_map(|index| expression.named_child(index))
12650 .find(|child| child.kind() == "identifier");
12651 identifier.map_or_else(
12652 || {
12653 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12654 expression, source,
12655 )))
12656 },
12657 |identifier| {
12658 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
12659 },
12660 )
12661 }
12662 "unary_expression"
12663 if expression
12664 .child_by_field_name("operator")
12665 .is_some_and(|operator| operator.kind() == "!") =>
12666 {
12667 expression.child_by_field_name("argument").map_or_else(
12668 || {
12669 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12670 expression, source,
12671 )))
12672 },
12673 |argument| boolean_preprocessor_expression(argument, source).negated(),
12674 )
12675 }
12676 "parenthesized_expression" => (0..expression.named_child_count())
12677 .filter_map(|index| expression.named_child(index))
12678 .next()
12679 .map_or_else(
12680 || {
12681 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12682 expression, source,
12683 )))
12684 },
12685 |child| boolean_preprocessor_expression(child, source),
12686 ),
12687 "binary_expression" => {
12688 let operands = || {
12689 Some((
12690 boolean_preprocessor_expression(
12691 expression.child_by_field_name("left")?,
12692 source,
12693 ),
12694 boolean_preprocessor_expression(
12695 expression.child_by_field_name("right")?,
12696 source,
12697 ),
12698 ))
12699 };
12700 match expression
12701 .child_by_field_name("operator")
12702 .map(|operator| operator.kind())
12703 {
12704 Some("&&") => operands().map_or_else(
12705 || {
12706 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12707 expression, source,
12708 )))
12709 },
12710 |(left, right)| BooleanGuardExpression::all([left, right]),
12711 ),
12712 Some("||") => operands().map_or_else(
12713 || {
12714 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12715 expression, source,
12716 )))
12717 },
12718 |(left, right)| BooleanGuardExpression::any([left, right]),
12719 ),
12720 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12721 expression, source,
12722 ))),
12723 }
12724 }
12725 _ => {
12726 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12727 }
12728 }
12729}
12730
12731fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12732 if targets.len() == 1 {
12733 targets.pop()
12734 } else {
12735 None
12736 }
12737}
12738
12739fn callable_declaration_nodes<'tree>(
12740 analyzer: &CppGraphSource<'_>,
12741 prepared: &'tree PreparedSyntaxTree,
12742 candidate: &CodeUnit,
12743) -> Vec<Node<'tree>> {
12744 let root = prepared.tree().root_node();
12745 analyzer
12746 .ranges(candidate)
12747 .into_iter()
12748 .filter_map(|range| {
12749 let mut declaration =
12750 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12751 while !matches!(
12764 declaration.kind(),
12765 "declaration" | "field_declaration" | "function_definition"
12766 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12767 declaration,
12768 prepared.source(),
12769 ) {
12770 let Some(parent) = declaration.parent() else {
12771 break;
12772 };
12773 declaration = parent;
12774 }
12775 Some(declaration)
12776 })
12777 .collect()
12778}
12779
12780fn real_function_definition_ancestor<'tree>(
12781 node: Node<'tree>,
12782 source: &str,
12783) -> Option<Node<'tree>> {
12784 let mut ancestor = node.parent();
12785 while let Some(node) = ancestor {
12786 if node.kind() == "function_definition"
12787 && !is_recovered_declaration_scope_container(node, source)
12788 {
12789 return Some(node);
12790 }
12791 ancestor = node.parent();
12792 }
12793 None
12794}
12795
12796fn callable_declaration_activation_in_file(
12797 analyzer: &CppGraphSource<'_>,
12798 candidate: &CodeUnit,
12799 reference: &CallableReferenceContext<'_>,
12800) -> Option<usize> {
12801 analyzer
12802 .declaration_source_occurrences(candidate)?
12803 .into_iter()
12804 .filter_map(|occurrence| {
12805 let activation = occurrence.fact.callable_activation?;
12806 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12807 callable_guard_requirements_are_visible(
12808 &guards,
12809 occurrence.fact.callable_guard_completion_byte,
12810 reference,
12811 )
12812 .then_some(activation)
12813 })
12814 .min()
12815}
12816
12817fn callable_guard_requirements_are_visible(
12818 required: &HashSet<PreprocessorGuard>,
12819 completion_byte: Option<usize>,
12820 reference: &CallableReferenceContext<'_>,
12821) -> bool {
12822 if required.is_empty() || guard_requirements_hold_at_reference(required, reference.guards()) {
12823 return true;
12824 }
12825 reference.position.as_ref().is_some_and(|position| {
12826 completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12827 && guards_compatible_at_reference(required, reference.guards())
12828 })
12829}
12830
12831type ReferenceGuardAugmenter<'a> =
12832 dyn Fn(Option<&HashSet<PreprocessorGuard>>) -> Option<HashSet<PreprocessorGuard>> + 'a;
12833
12834struct CallableReferenceContext<'a> {
12844 effective_guards: &'a ReferenceGuardAugmenter<'a>,
12845 file: &'a ProjectFile,
12846 position: Option<CallableReferencePosition<'a>>,
12847 file_guards: OnceCell<Option<HashSet<PreprocessorGuard>>>,
12848}
12849
12850struct CallableReferencePosition<'a> {
12854 prepared: &'a PreparedSyntaxTree,
12855 byte: usize,
12856 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12857}
12858
12859impl CallableReferenceContext<'_> {
12860 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12861 let Some(position) = self.position.as_ref() else {
12862 return self
12863 .file_guards
12864 .get_or_init(|| (self.effective_guards)(Some(&HashSet::default())))
12865 .as_ref();
12866 };
12867 position
12868 .guards
12869 .get_or_init(|| {
12870 let raw = position
12871 .prepared
12872 .tree()
12873 .root_node()
12874 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12875 .and_then(|node| {
12876 preprocessor_guard_environment(node, position.prepared.source())
12877 });
12878 (self.effective_guards)(raw.as_ref())
12879 })
12880 .as_ref()
12881 }
12882}
12883
12884fn callable_declaration_guard_requirements(
12896 node: Node<'_>,
12897 source: &str,
12898 reference: &CallableReferenceContext<'_>,
12899) -> Option<HashSet<PreprocessorGuard>> {
12900 let mut required = HashSet::default();
12901 for guard in callable_preprocessor_ancestor_guards(node, source)? {
12902 match guard {
12903 PreprocessorGuard::Constant(true) => {}
12904 PreprocessorGuard::Constant(false) => return None,
12905 guard => {
12906 required.insert(guard);
12907 }
12908 }
12909 }
12910 guards_compatible_at_reference(&required, reference.guards()).then_some(required)
12911}
12912
12913fn callable_preprocessor_context_is_visible_for_reference(
12924 node: Node<'_>,
12925 source: &str,
12926 reference: &CallableReferenceContext<'_>,
12927) -> bool {
12928 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12929 return false;
12930 };
12931 if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12932 return true;
12933 }
12934 reference.position.as_ref().is_some_and(|position| {
12935 guards_compatible_at_reference(&required, reference.guards())
12936 && declaration_follows_completed_family_before_reference(node, source, position.byte)
12937 })
12938}
12939
12940fn flattened_macro_namespace_declaration_matches(
12941 analyzer: &CppGraphSource<'_>,
12942 _cpp: &dyn CppSource,
12943 reference_file: &ProjectFile,
12944 visible_declaration: &CodeUnit,
12945 qualified_candidate: &CodeUnit,
12946 reference_byte: usize,
12947) -> bool {
12948 if visible_declaration.kind() != qualified_candidate.kind()
12954 || visible_declaration.identifier() != qualified_candidate.identifier()
12955 || visible_declaration.signature() != qualified_candidate.signature()
12956 || !visible_declaration.package_name().is_empty()
12957 || qualified_candidate.package_name().is_empty()
12958 {
12959 return false;
12960 }
12961
12962 let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12963 return false;
12964 };
12965 occurrences.into_iter().any(|occurrence| {
12966 occurrence
12967 .displaced_namespace_closing_brace
12968 .is_some_and(|closing_brace| {
12969 visible_declaration.source() != reference_file || closing_brace < reference_byte
12970 })
12971 })
12972}
12973
12974pub(crate) fn flattened_macro_namespace_components(
12975 declaration: Node<'_>,
12976 source: &str,
12977) -> Option<Vec<String>> {
12978 flattened_macro_function_namespace_components(declaration, source)
12979 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12980}
12981
12982fn flattened_macro_function_namespace_components(
12983 declaration: Node<'_>,
12984 source: &str,
12985) -> Option<Vec<String>> {
12986 let body = declaration
12987 .parent()
12988 .filter(|parent| parent.kind() == "compound_statement")?;
12989 let function = body.parent()?;
12990 if function.child_by_field_name("body") != Some(body) {
12991 return None;
12992 }
12993 let namespace_name = recovered_macro_namespace_name(function, source)?;
12994 let mut components = enclosing_namespace_components(declaration, source)?;
12995 components.push(namespace_name);
12996 Some(components)
12997}
12998
12999fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
13013 if function.kind() != "function_definition" {
13014 return None;
13015 }
13016 let body = function
13017 .child_by_field_name("body")
13018 .filter(|body| body.kind() == "compound_statement")?;
13019 let mut cursor = function.walk();
13020 let prefix = function
13021 .named_children(&mut cursor)
13022 .take_while(|child| child.start_byte() < body.start_byte())
13023 .filter(|child| child.kind() != "comment")
13024 .collect::<Vec<_>>();
13025 let begin_index = prefix.iter().rposition(|child| {
13026 flattened_macro_sentinel_name(*child, source)
13027 .is_some_and(|name| is_namespace_begin_sentinel(&name))
13028 })?;
13029 let mut identifiers = Vec::new();
13030 let mut stack = prefix[begin_index + 1..]
13031 .iter()
13032 .rev()
13033 .copied()
13034 .collect::<Vec<_>>();
13035 while let Some(current) = stack.pop() {
13036 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
13037 identifiers.push(identifier);
13038 continue;
13039 }
13040 let mut cursor = current.walk();
13041 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
13042 stack.extend(children.into_iter().rev());
13043 }
13044 let [keyword, namespace_name] = identifiers.as_slice() else {
13045 return None;
13046 };
13047 if keyword != "namespace" || namespace_name.is_empty() {
13050 return None;
13051 }
13052 let mut next = function.next_named_sibling();
13053 let next = loop {
13054 let candidate = next?;
13055 next = candidate.next_named_sibling();
13056 if candidate.kind() != "comment" {
13057 break candidate;
13058 }
13059 };
13060 flattened_macro_sentinel_name(next, source)
13061 .is_some_and(|name| is_namespace_end_sentinel(&name))
13062 .then(|| namespace_name.clone())
13063}
13064
13065pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
13070 crate::declarations::is_recovered_exported_class_container(node, source)
13071 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
13072 node, source,
13073 )
13074 || recovered_macro_namespace_name(node, source).is_some()
13075}
13076
13077fn flattened_macro_error_namespace_components(
13078 declaration: Node<'_>,
13079 source: &str,
13080) -> Option<Vec<String>> {
13081 let parent = declaration
13082 .parent()
13083 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
13084 let mut cursor = parent.walk();
13085 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
13086 let declaration_index = siblings
13087 .iter()
13088 .position(|candidate| same_node(*candidate, declaration))?;
13089 let begin_index = (0..declaration_index).rev().find(|index| {
13090 flattened_macro_sentinel_name(siblings[*index], source)
13091 .is_some_and(|name| is_namespace_begin_sentinel(&name))
13092 })?;
13093
13094 let significant = siblings[begin_index + 1..declaration_index]
13095 .iter()
13096 .copied()
13097 .filter(|node| node.kind() != "comment")
13098 .collect::<Vec<_>>();
13099 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
13100 return None;
13101 };
13102 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
13103 return None;
13104 }
13105 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
13106 if significant[2..].iter().any(|node| {
13107 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
13108 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
13109 })
13110 }) {
13111 return None;
13112 }
13113
13114 let mut saw_namespace_close = false;
13115 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
13116 if sibling.kind() == "comment" {
13117 continue;
13118 }
13119 if !saw_namespace_close {
13120 if direct_unmatched_closing_brace(sibling) {
13121 saw_namespace_close = true;
13122 continue;
13123 }
13124 if flattened_macro_sentinel_name(sibling, source).is_some() {
13125 return None;
13126 }
13127 continue;
13128 }
13129 if !flattened_macro_sentinel_name(sibling, source)
13130 .is_some_and(|name| is_namespace_end_sentinel(&name))
13131 {
13132 return None;
13133 }
13134 let mut components = enclosing_namespace_components(declaration, source)?;
13135 components.push(namespace_name);
13136 return Some(components);
13137 }
13138 None
13139}
13140
13141fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
13142 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
13150 node.named_child(0)?
13151 } else {
13152 node
13153 };
13154 let candidates = [
13155 direct_cpp_identifier_name(node, source),
13156 node.child_by_field_name("type")
13157 .and_then(|type_node| direct_cpp_identifier_name(type_node, source)),
13158 macro_modifier_identifier(node)
13159 .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source))),
13160 {
13161 let mut cursor = node.walk();
13162 node.named_children(&mut cursor)
13163 .find(|child| child.kind() != "comment")
13164 .and_then(macro_modifier_identifier)
13165 .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source)))
13166 },
13167 ];
13168 candidates.into_iter().flatten().find(|candidate| {
13169 cpp_export_macro_token(candidate)
13170 && (is_namespace_begin_sentinel(candidate) || is_namespace_end_sentinel(candidate))
13171 })
13172}
13173
13174#[derive(Clone, Copy, PartialEq, Eq)]
13177enum NamespaceSentinel {
13178 Begin,
13179 End,
13180}
13181
13182fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
13183 if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
13184 Some(NamespaceSentinel::Begin)
13185 } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
13186 Some(NamespaceSentinel::End)
13187 } else {
13188 None
13189 }
13190}
13191
13192fn is_namespace_begin_sentinel(name: &str) -> bool {
13193 namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
13194}
13195
13196fn is_namespace_end_sentinel(name: &str) -> bool {
13197 namespace_sentinel(name) == Some(NamespaceSentinel::End)
13198}
13199
13200fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
13201 if node.kind() != "ERROR" || node.named_child_count() != 1 {
13202 return None;
13203 }
13204 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
13205 (!cpp_export_macro_token(&name)).then_some(name)
13206}
13207
13208fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
13209 if !matches!(
13210 node.kind(),
13211 "identifier" | "namespace_identifier" | "type_identifier"
13212 ) {
13213 return None;
13214 }
13215 let name = normalize_cpp_whitespace(node_text(node, source));
13216 (!name.is_empty()).then_some(name)
13217}
13218
13219fn guard_requirement_sets_match(
13220 left: &[(usize, HashSet<PreprocessorGuard>)],
13221 right: &[(usize, HashSet<PreprocessorGuard>)],
13222) -> bool {
13223 left.len() == right.len()
13224 && left.iter().all(|(_, left_guards)| {
13225 right
13226 .iter()
13227 .any(|(_, right_guards)| left_guards == right_guards)
13228 })
13229 && right.iter().all(|(_, right_guards)| {
13230 left.iter()
13231 .any(|(_, left_guards)| right_guards == left_guards)
13232 })
13233}
13234
13235pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
13236 node.kind() == "ERROR"
13237 && (0..node.child_count())
13238 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
13239}
13240
13241pub fn binding_site_guards(node: Node<'_>, source: &str) -> Option<HashSet<PreprocessorGuard>> {
13245 if callable_preprocessor_context_is_visible(node, source) {
13246 Some(HashSet::default())
13247 } else {
13248 preprocessor_guard_environment(node, source)
13249 }
13250}
13251
13252pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
13253 let mut ancestor = node.parent();
13254 while let Some(parent) = ancestor {
13255 if is_cpp_preprocessor_conditional(parent)
13256 && !is_file_covering_include_guard(parent, source)
13257 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
13258 {
13259 return false;
13260 }
13261 ancestor = parent.parent();
13262 }
13263 true
13264}
13265
13266fn is_split_cpp_language_linkage_wrapper(
13267 conditional: Node<'_>,
13268 descendant: Node<'_>,
13269 source: &str,
13270) -> bool {
13271 if conditional.child_by_field_name("alternative").is_some()
13272 || !matches!(
13273 simple_preprocessor_guard(conditional, source),
13274 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13275 )
13276 {
13277 return false;
13278 }
13279 let mut current = descendant.parent();
13280 let linkage = loop {
13281 let Some(node) = current else {
13282 return false;
13283 };
13284 if node == conditional {
13285 return false;
13286 }
13287 if node.kind() == "linkage_specification" {
13288 break node;
13289 }
13290 current = node.parent();
13291 };
13292 if linkage
13293 .child_by_field_name("value")
13294 .is_none_or(|value| node_text(value, source) != "\"C\"")
13295 {
13296 return false;
13297 }
13298 let Some(body) = linkage.child_by_field_name("body") else {
13299 return false;
13300 };
13301 let mut cursor = body.walk();
13306 let closes_opening_branch = body
13307 .named_children(&mut cursor)
13308 .take_while(|child| child.end_byte() <= descendant.start_byte())
13309 .any(|child| {
13310 child.kind() == "preproc_call"
13311 && child
13312 .child_by_field_name("directive")
13313 .is_some_and(|directive| node_text(directive, source) == "#endif")
13314 });
13315 let mut cursor = body.walk();
13316 let reopens_for_closing_brace = body
13317 .named_children(&mut cursor)
13318 .skip_while(|child| child.start_byte() < descendant.end_byte())
13319 .any(|child| {
13320 matches!(
13321 simple_preprocessor_guard(child, source),
13322 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13323 ) && {
13324 let mut tokens = child.walk();
13325 child
13326 .children(&mut tokens)
13327 .any(|token| token.kind() == "#endif" && token.is_missing())
13328 }
13329 });
13330 closes_opening_branch && reopens_for_closing_brace
13331}
13332
13333pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
13337 node.child_by_field_name("arguments")
13338 .or_else(|| node.child_by_field_name("parameters"))
13339 .or_else(|| node.child_by_field_name("value"))
13340 .or_else(|| first_named_child_of_kind(node, "argument_list"))
13341 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
13342}
13343
13344pub fn call_arity(node: Node<'_>) -> usize {
13345 call_arguments_node(node)
13346 .map(|args| argument_children(args).count())
13347 .unwrap_or(0)
13348}
13349
13350pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
13351 let recovered_block_arguments = recovered_block_literal_arguments(node);
13352 (0..node.child_count())
13353 .filter_map(move |index| node.child(index))
13354 .filter(|child| child.is_named() && !child.is_extra())
13355 .filter(move |child| node.kind() != "parameter_list" || !block_literal_open_error(*child))
13360 .flat_map(move |child| {
13361 if let Some((raw, left, right)) = recovered_block_arguments
13362 && child == raw
13363 {
13364 [Some(left), Some(right)]
13365 } else {
13366 [Some(child), None]
13367 }
13368 })
13369 .flatten()
13370}
13371
13372fn block_literal_open_error(node: Node<'_>) -> bool {
13376 if node.kind() != "ERROR" || node.is_missing() {
13377 return false;
13378 }
13379 let mut cursor = node.walk();
13380 let tokens = node.children(&mut cursor).collect::<Vec<_>>();
13381 matches!(tokens.as_slice(), [caret, open] if caret.kind() == "^" && open.kind() == "(")
13382}
13383
13384pub fn recovered_c_new_expression_arguments(
13393 node: Node<'_>,
13394 uses_c_semantics: bool,
13395) -> Option<[Node<'_>; 2]> {
13396 if !uses_c_semantics || node.kind() != "new_expression" {
13397 return None;
13398 }
13399 let parent = node.parent()?;
13400 if parent.kind() != "argument_list" {
13401 return None;
13402 }
13403 let keyword = node.child(0)?;
13404 let error = node.child(1)?;
13405 let trailing = node.child(2)?;
13406 if node.child(3).is_some()
13407 || keyword.kind() != "new"
13408 || keyword.is_named()
13409 || keyword.child_count() != 0
13410 || error.kind() != "ERROR"
13411 || !error.is_extra()
13412 || error.child_count() != 1
13413 || error.child(0).is_none_or(|comma| comma.kind() != ",")
13414 || node.child_by_field_name("type") != Some(trailing)
13415 || trailing.kind() != "type_identifier"
13416 {
13417 return None;
13418 }
13419 Some([keyword, trailing])
13420}
13421
13422pub fn recovered_c_new_expression_argument_at(
13425 mut node: Node<'_>,
13426 start_byte: usize,
13427 end_byte: usize,
13428 uses_c_semantics: bool,
13429) -> Option<Node<'_>> {
13430 if !uses_c_semantics {
13436 return None;
13437 }
13438 loop {
13439 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
13440 return arguments.into_iter().find(|argument| {
13441 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
13442 });
13443 }
13444 node = node.parent()?;
13445 }
13446}
13447
13448fn recovered_c_keyword_argument_count(
13449 file: &ProjectFile,
13450 call: Node<'_>,
13451 arguments: Node<'_>,
13452 source: &str,
13453) -> usize {
13454 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
13460 return 0;
13461 }
13462 let mut ancestor = Some(call);
13463 let function = loop {
13464 let Some(current) = ancestor else {
13465 return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
13466 .unwrap_or(0);
13467 };
13468 if current.kind() == "function_definition" {
13469 break current;
13470 }
13471 ancestor = current.parent();
13472 };
13473 let Some(parameters) = function
13474 .child_by_field_name("declarator")
13475 .and_then(|declarator| declarator.child_by_field_name("parameters"))
13476 else {
13477 return 0;
13478 };
13479 let displaced_parameter_keywords = (0..parameters.child_count())
13480 .filter_map(|index| parameters.child(index))
13481 .filter(|error| error.kind() == "ERROR")
13482 .filter_map(|error| {
13483 let parameter = error.prev_named_sibling()?;
13484 if parameter.kind() != "parameter_declaration"
13485 || parameter.end_byte() != error.start_byte()
13486 || extract_variable_name(parameter, source).is_some()
13487 {
13488 return None;
13489 }
13490 let mut children = (0..error.child_count())
13491 .filter_map(|index| error.child(index))
13492 .filter(|child| !child.is_extra() && !child.is_missing());
13493 let keyword = children.next()?;
13494 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
13495 .then_some(keyword)
13496 })
13497 .collect::<Vec<_>>();
13498 if displaced_parameter_keywords.is_empty() {
13499 return 0;
13500 }
13501
13502 (0..arguments.child_count())
13503 .filter_map(|index| arguments.child(index))
13504 .filter(|error| error.kind() == "ERROR" && error.is_extra())
13505 .filter(|error| {
13506 let mut children = (0..error.child_count())
13507 .filter_map(|index| error.child(index))
13508 .filter(|child| !child.is_extra() && !child.is_missing());
13509 let Some(comma) = children.next() else {
13510 return false;
13511 };
13512 let Some(keyword) = children.next() else {
13513 return false;
13514 };
13515 children.next().is_none()
13516 && comma.kind() == ","
13517 && !keyword.is_named()
13518 && keyword.child_count() == 0
13519 && displaced_parameter_keywords
13520 .iter()
13521 .any(|parameter| parameter.kind_id() == keyword.kind_id())
13522 })
13523 .count()
13524}
13525
13526fn recovered_c_keyword_argument_count_from_collapsed_function(
13532 call: Node<'_>,
13533 arguments: Node<'_>,
13534) -> Option<usize> {
13535 let argument_keywords = children_iter(arguments)
13536 .filter(|error| error.kind() == "ERROR" && error.is_extra())
13537 .filter_map(|error| {
13538 let mut children =
13539 children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
13540 let comma = children.next()?;
13541 let keyword = children.next()?;
13542 (children.next().is_none()
13543 && comma.kind() == ","
13544 && !keyword.is_named()
13545 && keyword.child_count() == 0)
13546 .then_some(keyword)
13547 })
13548 .collect::<Vec<_>>();
13549 if argument_keywords.is_empty() {
13550 return Some(0);
13551 }
13552
13553 let mut ancestor = call.parent();
13558 let body = loop {
13559 let current = ancestor?;
13560 if matches!(current.kind(), "function_definition" | "lambda_expression") {
13561 return None;
13562 }
13563 if current.kind() == "compound_statement"
13564 && current
13565 .parent()
13566 .is_some_and(|parent| parent.kind() == "ERROR")
13567 {
13568 break current;
13569 }
13570 ancestor = current.parent();
13571 };
13572 let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
13573 let function_error = parameter_error
13574 .parent()
13575 .filter(|parent| parent.kind() == "ERROR")?;
13576 let function_children = children_iter(function_error).collect::<Vec<_>>();
13577 let parameter_error_index = function_children
13578 .iter()
13579 .position(|child| same_node(*child, parameter_error))?;
13580 let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
13583 .iter()
13584 .enumerate()
13585 .any(|(index, child)| {
13586 function_error.field_name_for_child(index as u32) == Some("type")
13587 && !child.is_extra()
13588 && !child.is_missing()
13589 && matches!(
13590 child.kind(),
13591 "primitive_type" | "sized_type_specifier" | "type_identifier"
13592 )
13593 });
13594 if parameter_error_index < 2
13595 || !has_return_type
13596 || function_children[parameter_error_index - 1].kind() != "("
13597 || function_children[parameter_error_index - 2].kind() != "identifier"
13598 {
13599 return Some(0);
13600 }
13601
13602 let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
13603 let body_index = parameter_children
13604 .iter()
13605 .position(|child| same_node(*child, body))?;
13606 let close_parenthesis_index = parameter_children[..body_index]
13607 .iter()
13608 .position(|child| child.kind() == ")")?;
13609 let parameter_keywords = parameter_children[..close_parenthesis_index]
13610 .iter()
13611 .enumerate()
13612 .filter(|(index, child)| {
13613 !child.is_named()
13614 && !child.is_extra()
13615 && !child.is_missing()
13616 && child.child_count() == 0
13617 && argument_keywords
13618 .iter()
13619 .any(|keyword| keyword.kind_id() == child.kind_id())
13620 && collapsed_c_parameter_name_position(¶meter_children, *index)
13621 })
13622 .map(|(_, child)| *child)
13623 .collect::<Vec<_>>();
13624 Some(
13625 argument_keywords
13626 .iter()
13627 .filter(|keyword| {
13628 parameter_keywords
13629 .iter()
13630 .any(|parameter| parameter.kind_id() == keyword.kind_id())
13631 })
13632 .count(),
13633 )
13634}
13635
13636fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
13641 let segment_start = children[..keyword_index]
13642 .iter()
13643 .rposition(|child| matches!(child.kind(), "(" | ","))
13644 .map_or(0, |index| index + 1);
13645 let mut saw_type = false;
13646 let mut saw_pointer = false;
13647 for child in &children[segment_start..keyword_index] {
13648 if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
13649 saw_pointer = true;
13650 continue;
13651 }
13652 if saw_pointer {
13653 return false;
13654 }
13655 if !child.is_named() || child.is_extra() || child.is_missing() {
13656 return false;
13657 }
13658 if child.kind() == "type_qualifier" {
13659 continue;
13660 }
13661 if matches!(
13662 child.kind(),
13663 "class_specifier"
13664 | "dependent_type"
13665 | "enum_specifier"
13666 | "identifier"
13667 | "primitive_type"
13668 | "qualified_identifier"
13669 | "scoped_identifier"
13670 | "struct_specifier"
13671 | "template_type"
13672 | "type_identifier"
13673 | "union_specifier"
13674 ) {
13675 saw_type = true;
13676 continue;
13677 }
13678 return false;
13679 }
13680 saw_type && saw_pointer
13681}
13682
13683fn recovered_block_literal_arguments<'tree>(
13684 arguments: Node<'tree>,
13685) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
13686 if arguments.kind() != "argument_list" {
13687 return None;
13688 }
13689 let mut raw_arguments = (0..arguments.child_count())
13690 .filter_map(|index| arguments.child(index))
13691 .filter(|child| child.is_named() && !child.is_extra());
13692 let raw = raw_arguments.next()?;
13693 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
13694 return None;
13695 }
13696
13697 let left = raw.child_by_field_name("left")?;
13698 if left.is_missing() || left.start_byte() == left.end_byte() {
13699 return None;
13700 }
13701 let right = raw.child_by_field_name("right")?;
13702 if right.kind() != "compound_literal_expression"
13703 || right.is_missing()
13704 || right
13705 .child_by_field_name("type")
13706 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
13707 || right
13708 .child_by_field_name("value")
13709 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
13710 {
13711 return None;
13712 }
13713 let has_intervening_error = (0..raw.child_count())
13714 .filter_map(|index| raw.child(index))
13715 .any(|child| {
13716 child.kind() == "ERROR"
13717 && !child.is_missing()
13718 && child.start_byte() >= left.end_byte()
13719 && child.end_byte() <= right.start_byte()
13720 });
13721 has_intervening_error.then_some((raw, left, right))
13722}
13723
13724pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
13725 match node.kind() {
13726 "new_expression" => node
13727 .child_by_field_name("type")
13728 .or_else(|| node.named_child(0)),
13729 "compound_literal_expression" => node.child_by_field_name("type"),
13730 "call_expression" => node.child_by_field_name("function"),
13731 _ => None,
13732 }
13733}
13734
13735pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13741 if node.kind() != "cast_expression" {
13742 return None;
13743 }
13744 let descriptor = node.child_by_field_name("type")?;
13745 if descriptor.kind() == "type_descriptor" {
13746 descriptor.child_by_field_name("type")
13747 } else {
13748 Some(descriptor)
13749 }
13750}
13751
13752pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13767 debug_assert_eq!(field.kind(), "field_expression");
13768 let operator = field.child_by_field_name("operator")?;
13769 let mut cursor = field.walk();
13770 let receiver = field
13771 .named_children(&mut cursor)
13772 .filter(|child| child.end_byte() <= operator.start_byte())
13773 .last()?;
13774 if receiver.kind() != "ERROR" {
13775 return Some(receiver);
13776 }
13777 (receiver.named_child_count() == 1)
13778 .then(|| receiver.named_child(0))
13779 .flatten()
13780}
13781
13782pub fn field_initializer_constructs_target(
13783 node: Node<'_>,
13784 ctx: &ScanCtx<'_>,
13785 owner: &CodeUnit,
13786) -> bool {
13787 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13796 return qualified_base_initializer_constructs_target(node, ctx, owner);
13797 }
13798 let Some(name) = node
13799 .child_by_field_name("name")
13800 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13801 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13802 else {
13803 return false;
13804 };
13805 let field_name = node_text(name, ctx.source);
13806 ctx.visibility
13807 .visible_identifier_candidates(ctx.file, field_name)
13808 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13809 .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13810}
13811
13812fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13822 field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13823}
13824
13825fn qualified_base_initializer_constructs_target(
13826 node: Node<'_>,
13827 ctx: &ScanCtx<'_>,
13828 owner: &CodeUnit,
13829) -> bool {
13830 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13831 return false;
13832 };
13833 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13834 return false;
13835 };
13836 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13837 return false;
13838 };
13839 let resolves_target = |components: &[String]| {
13840 matches!(
13841 ctx.visibility.resolve_type_components_lexically_for_target(
13842 &ctx.analyzer,
13843 ctx.file,
13844 components,
13845 is_globally_qualified_cpp_name(qualified),
13846 &lexical_scope,
13847 owner,
13848 ),
13849 LexicalTypeResolution::Resolved { unit, .. }
13850 if same_visible_symbol(&unit, owner)
13851 )
13852 };
13853 if resolves_target(&components) {
13854 return true;
13855 }
13856
13857 components
13863 .last()
13864 .is_some_and(|terminal| terminal == owner.identifier())
13865 && resolves_target(&components[..components.len() - 1])
13866}
13867
13868fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13869 ctx.visibility
13870 .field_declared_type_fact(&ctx.analyzer, unit)
13871 .is_some_and(|fact| {
13872 ctx.visibility
13873 .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13874 || ctx.visibility.resolves_to_type(
13875 &ctx.analyzer,
13876 ctx.file,
13877 &normalize_field_type_text(&fact.type_text),
13878 owner,
13879 )
13880 })
13881}
13882
13883pub fn field_declared_binding(
13884 analyzer: &CppGraphSource<'_>,
13885 visibility: &VisibilityIndex<'_>,
13886 visible_from: &ProjectFile,
13887 field: &CodeUnit,
13888) -> Option<CppScanBinding> {
13889 let (normalized, resolved, indirection) =
13890 field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13891 let resolved = resolved
13892 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13893 Some(CppScanBinding::from_type_name(
13894 normalized,
13895 resolved,
13896 indirection,
13897 ))
13898}
13899
13900pub fn field_initializer_holds_subobject(
13907 analyzer: &CppGraphSource<'_>,
13908 visibility: &VisibilityIndex<'_>,
13909 field: &CodeUnit,
13910) -> bool {
13911 visibility
13912 .field_declared_type_fact(analyzer, field)
13913 .is_none_or(|fact| !fact.binds_indirectly)
13914}
13915
13916fn anonymous_aggregate_field_owner(
13923 analyzer: &CppGraphSource<'_>,
13924 visibility: &VisibilityIndex<'_>,
13925 visible_from: &ProjectFile,
13926 field: &CodeUnit,
13927) -> Option<CodeUnit> {
13928 let owner = type_owner_of(analyzer, field)?;
13929 if !owner.is_class() {
13930 return None;
13931 }
13932 let declaration = analyzer.get_source(field, false)?;
13933 let mut parser = Parser::new();
13934 parser
13935 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13936 .ok()?;
13937 let tree = parser.parse(&declaration, None)?;
13938 let mut stack = vec![tree.root_node()];
13939 while let Some(node) = stack.pop() {
13940 if matches!(node.kind(), "declaration" | "field_declaration")
13941 && let Some(type_node) = node
13942 .child_by_field_name("type")
13943 .or_else(|| first_type_child(node))
13944 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13945 && type_node.child_by_field_name("name").is_none()
13946 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13947 {
13948 let matches = visibility
13949 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13950 .into_iter()
13951 .filter(|child| child.is_class() && child.identifier() == field.identifier())
13952 .collect::<Vec<_>>();
13953 return match matches.as_slice() {
13954 [child] => Some((*child).clone()),
13955 _ => None,
13956 };
13957 }
13958 let mut cursor = node.walk();
13959 stack.extend(node.named_children(&mut cursor));
13960 }
13961 None
13962}
13963
13964pub fn anonymous_aggregate_owner(
13971 analyzer: &CppGraphSource<'_>,
13972 file: &ProjectFile,
13973 node: Node<'_>,
13974) -> Option<CodeUnit> {
13975 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13976 || node.child_by_field_name("name").is_some()
13977 {
13978 return None;
13979 }
13980 let mut candidates = analyzer
13981 .declarations(file)
13982 .into_iter()
13983 .filter(|candidate| {
13984 candidate.is_class()
13985 && analyzer.ranges(candidate).into_iter().any(|range| {
13986 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13987 })
13988 })
13989 .collect::<Vec<_>>();
13990 candidates.sort_by_key(|candidate| candidate.fq_name());
13991 candidates.dedup();
13992 match candidates.as_slice() {
13993 [candidate] => Some(candidate.clone()),
13994 _ => None,
13995 }
13996}
13997
13998pub fn function_local_type_resolution(
14011 analyzer: &CppGraphSource<'_>,
14012 file: &ProjectFile,
14013 node: Node<'_>,
14014 components: &[String],
14015 source: &str,
14016) -> Option<LexicalTypeResolution> {
14017 let (head, rest) = components.split_first()?;
14018 if is_c_source_file(file) {
14019 return None;
14020 }
14021 let declaration = function_local_class_declaration(node, head, source)?;
14022 let Some(mut unit) = function_local_class_unit(analyzer, file, declaration, head, source)
14023 else {
14024 return Some(LexicalTypeResolution::Missing);
14025 };
14026 for component in rest {
14027 let mut nested = analyzer
14028 .declarations(file)
14029 .into_iter()
14030 .filter(|candidate| {
14031 candidate.is_class()
14032 && candidate.identifier() == component
14033 && brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
14034 .is_some_and(|parent| parent == unit.fq_name())
14035 })
14036 .collect::<Vec<_>>();
14037 nested.dedup();
14038 match nested.as_slice() {
14039 [child] => unit = child.clone(),
14040 [] => return Some(LexicalTypeResolution::Missing),
14041 _ => return Some(LexicalTypeResolution::Ambiguous),
14042 }
14043 }
14044 Some(LexicalTypeResolution::Resolved {
14045 components: canonical_cpp_scope_components(&unit),
14046 candidates: vec![unit.clone()],
14047 unit,
14048 })
14049}
14050
14051pub fn is_function_local_unit(unit: &CodeUnit) -> bool {
14055 let interner = segment_interner();
14056 unit.fq().segments().split_last().is_some_and(|(_, scope)| {
14057 scope
14058 .iter()
14059 .any(|&segment| interner.resolve(segment).1 == SegmentKind::Member)
14060 })
14061}
14062
14063fn function_local_class_declaration<'tree>(
14071 node: Node<'tree>,
14072 name: &str,
14073 source: &str,
14074) -> Option<Node<'tree>> {
14075 let mut path = Vec::new();
14079 let mut child = node;
14080 while let Some(ancestor) = child.parent() {
14081 if ancestor.kind() == "compound_statement"
14082 && ancestor.parent().is_some_and(|function| {
14083 function.kind() == "function_definition"
14084 && (is_recovered_declaration_scope_container(function, source)
14085 || crate::graph::extractor::is_malformed_wrapper_function_definition(
14086 function,
14087 ))
14088 })
14089 {
14090 break;
14091 }
14092 path.push((ancestor, child));
14093 child = ancestor;
14094 }
14095 let outermost_block = path
14096 .iter()
14097 .rposition(|(ancestor, _)| ancestor.kind() == "compound_statement")?;
14098 let mut in_member_body = false;
14099 for &(ancestor, _) in &path[..=outermost_block] {
14100 match ancestor.kind() {
14101 "compound_statement" => {
14102 if let Some(found) =
14103 scope_local_class_declaration(ancestor, name, source, Some(node.start_byte()))
14104 {
14105 return Some(found);
14106 }
14107 }
14108 "function_definition" | "lambda_expression" => in_member_body = true,
14109 "field_declaration_list" => {
14110 let class = ancestor.parent()?;
14111 if local_class_name_is(class, name, source) {
14112 return Some(class);
14113 }
14114 let before = (!in_member_body).then_some(node.start_byte());
14115 if let Some(found) = scope_local_class_declaration(ancestor, name, source, before) {
14116 return Some(found);
14117 }
14118 in_member_body = false;
14119 }
14120 _ => {}
14121 }
14122 }
14123 None
14124}
14125
14126fn scope_local_class_declaration<'tree>(
14131 scope: Node<'tree>,
14132 name: &str,
14133 source: &str,
14134 before: Option<usize>,
14135) -> Option<Node<'tree>> {
14136 named_children_iter(scope)
14137 .filter_map(scope_level_class_specifier)
14138 .filter(|specifier| before.is_none_or(|before| specifier.end_byte() <= before))
14139 .filter(|specifier| local_class_name_is(*specifier, name, source))
14140 .last()
14141}
14142
14143fn scope_level_class_specifier(statement: Node<'_>) -> Option<Node<'_>> {
14144 let is_class_like = |node: &Node<'_>| {
14145 matches!(
14146 node.kind(),
14147 "class_specifier" | "struct_specifier" | "union_specifier"
14148 )
14149 };
14150 if is_class_like(&statement) {
14151 return Some(statement);
14152 }
14153 if !matches!(
14154 statement.kind(),
14155 "declaration" | "field_declaration" | "type_definition"
14156 ) {
14157 return None;
14158 }
14159 let specifier = statement
14160 .child_by_field_name("type")
14161 .filter(is_class_like)?;
14162 (specifier.child_by_field_name("body").is_some()
14163 || statement.child_by_field_name("declarator").is_none())
14164 .then_some(specifier)
14165}
14166
14167fn local_class_name_is(specifier: Node<'_>, name: &str, source: &str) -> bool {
14168 specifier
14169 .child_by_field_name("name")
14170 .is_some_and(|name_node| {
14171 name_node.kind() == "type_identifier" && node_text(name_node, source) == name
14172 })
14173}
14174
14175fn function_local_class_unit(
14178 analyzer: &CppGraphSource<'_>,
14179 file: &ProjectFile,
14180 declaration: Node<'_>,
14181 name: &str,
14182 source: &str,
14183) -> Option<CodeUnit> {
14184 let definition = if declaration.child_by_field_name("body").is_some() {
14185 declaration
14186 } else {
14187 let mut scope = declaration.parent()?;
14188 if scope.kind() != "compound_statement" && scope.kind() != "field_declaration_list" {
14189 scope = scope.parent()?;
14190 }
14191 named_children_iter(scope)
14192 .filter_map(scope_level_class_specifier)
14193 .filter(|specifier| specifier.child_by_field_name("body").is_some())
14194 .find(|specifier| local_class_name_is(*specifier, name, source))?
14195 };
14196 let mut candidates = analyzer
14197 .declarations(file)
14198 .into_iter()
14199 .filter(|candidate| {
14200 candidate.is_class()
14201 && candidate.identifier() == name
14202 && is_function_local_unit(candidate)
14203 && analyzer.ranges(candidate).into_iter().any(|range| {
14204 range.start_byte == definition.start_byte()
14205 && range.end_byte == definition.end_byte()
14206 })
14207 })
14208 .collect::<Vec<_>>();
14209 candidates.dedup();
14210 match candidates.as_slice() {
14211 [candidate] => Some(candidate.clone()),
14212 _ => None,
14213 }
14214}
14215
14216fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
14218 let Some(first) = candidates.first() else {
14219 return Err(TypeCandidateFailure::Unresolvable);
14220 };
14221 if candidates
14222 .iter()
14223 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
14224 {
14225 Ok((*first).clone())
14226 } else {
14227 Err(TypeCandidateFailure::Ambiguous)
14228 }
14229}
14230
14231fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
14232 logical_type_candidate(candidates).ok()
14233}
14234
14235fn unique_type_candidate_preserving_alias(
14236 analyzer: &CppGraphSource<'_>,
14237 file: &ProjectFile,
14238 candidates: &[&CodeUnit],
14239) -> Option<CodeUnit> {
14240 let first = *candidates.first()?;
14241 if declared_type_alias(analyzer, first) {
14242 return candidates
14243 .iter()
14244 .all(|candidate| {
14245 declared_type_alias(analyzer, candidate)
14246 && candidate.kind() == first.kind()
14247 && candidate.fq_name() == first.fq_name()
14248 && candidate.source() == first.source()
14249 })
14250 .then(|| first.clone());
14251 }
14252 if analyzer.reference_uses_c_semantics(file)
14253 && first.is_class()
14254 && indexed_c_tag_kind(analyzer, first).is_some()
14255 {
14256 let mut full_source = None;
14257 let mut tag_kind = None;
14258 for candidate in candidates.iter().copied() {
14259 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
14260 if tag_kind
14261 .replace(candidate_tag_kind)
14262 .is_some_and(|existing| existing != candidate_tag_kind)
14263 {
14264 return None;
14265 }
14266 if cpp_class_declaration_strength(analyzer, candidate)
14267 == CppClassDeclarationStrength::Full
14268 && full_source
14269 .replace(candidate.source())
14270 .is_some_and(|existing| existing != candidate.source())
14271 {
14272 return None;
14273 }
14274 }
14275 }
14276 candidates
14277 .iter()
14278 .all(|candidate| {
14279 !declared_type_alias(analyzer, candidate)
14280 && candidate.kind() == first.kind()
14281 && candidate.fq_name() == first.fq_name()
14282 })
14283 .then(|| first.clone())
14284}
14285
14286pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
14287 brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
14288 analyzer.type_alias_provider(),
14289 unit,
14290 )
14291}
14292
14293pub fn field_declared_type_binding(
14294 analyzer: &CppGraphSource<'_>,
14295 visibility: &VisibilityIndex<'_>,
14296 visible_from: &ProjectFile,
14297 field: &CodeUnit,
14298) -> Option<(String, Option<CodeUnit>, i32)> {
14299 let fact = visibility.field_declared_type_fact(analyzer, field)?;
14300 let normalized = normalize_field_type_text(&fact.type_text);
14301 let primary = visibility.resolve_unique_canonical_type_for_declaration(
14302 analyzer,
14303 visible_from,
14304 field,
14305 &normalized,
14306 );
14307 let resolved = match (primary, fact.template_arguments.as_deref()) {
14308 (Some(primary), Some(arguments)) => visibility
14309 .resolve_template_arguments(visible_from, primary, arguments)
14310 .ok(),
14311 (resolved, None) => resolved,
14312 (None, Some(_)) => None,
14313 };
14314 Some((normalized, resolved, fact.indirection))
14315}
14316
14317fn decode_field_declared_type_fact(
14318 analyzer: &CppGraphSource<'_>,
14319 field: &CodeUnit,
14320) -> Option<DeclaredFieldTypeFact> {
14321 if let Some(facts) = analyzer.declaration_source_properties(field)
14322 && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
14323 && facts
14324 .iter()
14325 .all(|fact| fact.field_type.as_ref() == Some(first))
14326 {
14327 return Some(first.clone());
14328 }
14329 let Some(declaration) = analyzer.get_source(field, false) else {
14330 return decode_indexed_field_declared_type_fact(analyzer, field);
14331 };
14332 let mut parser = Parser::new();
14333 parser
14334 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14335 .ok()?;
14336 let contextual = parse_sentinel(
14340 "struct __bifrost_field_context { ",
14341 SentinelInput::region(&declaration),
14342 " };",
14343 );
14344 let (contextual_declaration, contextual_tree) = (contextual.source, contextual.tree);
14345 let mut stack = vec![contextual_tree.root_node()];
14346 while let Some(node) = stack.pop() {
14347 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
14348 && node_text(recovered.name, &contextual_declaration) == field.identifier()
14349 {
14350 return Some(DeclaredFieldTypeFact {
14351 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
14352 indirection: recovered.pointer_depth(),
14353 binds_indirectly: recovered.pointer_depth() > 0,
14354 template_arguments: None,
14355 });
14356 }
14357 if let Some(recovered) =
14358 recovered_function_like_field_declarator(node, &contextual_declaration)
14359 && node_text(recovered.name, &contextual_declaration) == field.identifier()
14360 {
14361 let type_node = node
14362 .child_by_field_name("type")
14363 .or_else(|| first_type_child(node))?;
14364 return Some(DeclaredFieldTypeFact {
14365 type_text: node_text(type_node, &contextual_declaration).to_string(),
14366 indirection: recovered.pointer_depth(),
14367 binds_indirectly: true,
14370 template_arguments: cpp_template_reference_arguments(
14371 type_node,
14372 &contextual_declaration,
14373 ),
14374 });
14375 }
14376 if let Some(fact) =
14377 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
14378 {
14379 return Some(fact);
14380 }
14381 let mut cursor = node.walk();
14382 stack.extend(node.named_children(&mut cursor));
14383 }
14384 let tree = parser.parse(&declaration, None)?;
14385 let mut stack = vec![tree.root_node()];
14386 while let Some(node) = stack.pop() {
14387 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
14388 {
14389 return Some(fact);
14390 }
14391 let mut cursor = node.walk();
14392 stack.extend(node.named_children(&mut cursor));
14393 }
14394 None
14395}
14396
14397fn decode_indexed_field_declared_type_fact(
14402 analyzer: &CppGraphSource<'_>,
14403 field: &CodeUnit,
14404) -> Option<DeclaredFieldTypeFact> {
14405 let cpp = analyzer.cpp?;
14406 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
14407 let source = prepared.source();
14408 let root = prepared.tree().root_node();
14409 for range in analyzer.ranges(field) {
14410 let end = range.start_byte.saturating_add(1).min(source.len());
14411 let mut current = root.descendant_for_byte_range(range.start_byte, end);
14412 while let Some(node) = current {
14413 if matches!(node.kind(), "declaration" | "field_declaration")
14414 && let Some(fact) =
14415 decode_declared_field_type_node(node, field.identifier(), source)
14416 {
14417 return Some(fact);
14418 }
14419 current = node.parent();
14420 }
14421 }
14422 None
14423}
14424
14425fn decode_declared_field_type_node(
14426 node: Node<'_>,
14427 field_name: &str,
14428 source: &str,
14429) -> Option<DeclaredFieldTypeFact> {
14430 if !matches!(node.kind(), "declaration" | "field_declaration") {
14431 return None;
14432 }
14433 let type_node = node
14434 .child_by_field_name("type")
14435 .or_else(|| first_type_child(node))?;
14436 cpp_declared_field_type_fact(node, type_node, field_name, source)
14437}
14438
14439pub(crate) fn cpp_declared_field_type_fact(
14440 node: Node<'_>,
14441 type_node: Node<'_>,
14442 field_name: &str,
14443 source: &str,
14444) -> Option<DeclaredFieldTypeFact> {
14445 let binding = declared_name_binding(node, type_node, field_name, source)?;
14446 let declared_type = if matches!(
14447 type_node.kind(),
14448 "class_specifier" | "struct_specifier" | "union_specifier"
14449 ) {
14450 type_node.child_by_field_name("name")
14451 } else {
14452 Some(type_node)
14453 };
14454 Some(DeclaredFieldTypeFact {
14455 type_text: declared_type.map_or_else(
14456 || field_name.to_string(),
14457 |declared_type| node_text(declared_type, source).to_string(),
14458 ),
14459 indirection: binding.pointer_depth,
14460 binds_indirectly: binding.indirect,
14461 template_arguments: declared_type
14462 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
14463 })
14464}
14465
14466pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
14472 let mut current = Some(declarator);
14473 while let Some(node) = current {
14474 match node.kind() {
14475 "function_declarator" | "abstract_function_declarator" => return true,
14476 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
14477 current = node.named_child(0);
14478 }
14479 _ => current = node.child_by_field_name("declarator"),
14480 }
14481 }
14482 false
14483}
14484
14485fn decode_structured_alias_target(
14486 analyzer: &CppGraphSource<'_>,
14487 unit: &CodeUnit,
14488) -> Option<StructuredAliasTarget> {
14489 let facts = analyzer.declaration_source_properties(unit)?;
14490 let first = facts.first()?.alias_target.as_ref()?;
14491 facts
14492 .iter()
14493 .all(|fact| fact.alias_target.as_ref() == Some(first))
14494 .then(|| first.clone())
14495}
14496
14497pub(crate) fn structured_alias_type_target(
14498 mut type_node: Node<'_>,
14499 source: &str,
14500) -> Option<StructuredAliasTarget> {
14501 while type_node.kind() == "type_descriptor" {
14502 type_node = type_node.child_by_field_name("type")?;
14503 }
14504 if type_node.kind() == "primitive_type" {
14505 return Some(StructuredAliasTarget::Builtin);
14506 }
14507 if matches!(
14508 type_node.kind(),
14509 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14510 ) {
14511 type_node = type_node.child_by_field_name("name")?;
14512 }
14513 let global = type_node.child_by_field_name("scope").is_none()
14514 && type_node.child(0).is_some_and(|child| child.kind() == "::");
14515 let mut components = Vec::new();
14516 append_structured_type_components(type_node, source, &mut components)?;
14517 let arguments = cpp_template_reference_arguments(type_node, source);
14518 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
14519 components,
14520 global,
14521 arguments,
14522 })
14523}
14524
14525fn append_structured_type_components(
14526 node: Node<'_>,
14527 source: &str,
14528 out: &mut Vec<String>,
14529) -> Option<()> {
14530 match node.kind() {
14531 "identifier" | "namespace_identifier" | "type_identifier" => {
14532 out.push(node_text(node, source).to_string());
14533 Some(())
14534 }
14535 "template_type" | "template_function" => {
14536 append_structured_type_components(node.child_by_field_name("name")?, source, out)
14537 }
14538 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
14539 let (scope, name) = qualified_name_parts(node)?;
14540 if let Some(scope) = scope {
14541 append_structured_type_components(scope, source, out)?;
14542 }
14543 append_structured_type_components(name, source, out)
14544 }
14545 _ => None,
14546 }
14547}
14548
14549pub(crate) struct DeclaredNameBinding {
14552 pub pointer_depth: i32,
14554 pub indirect: bool,
14558}
14559
14560pub(crate) fn declared_name_binding(
14561 declaration: Node<'_>,
14562 type_node: Node<'_>,
14563 field_name: &str,
14564 source: &str,
14565) -> Option<DeclaredNameBinding> {
14566 let mut stack = Vec::new();
14567 let mut cursor = declaration.walk();
14568 stack.extend(
14569 declaration
14570 .named_children(&mut cursor)
14571 .filter(|child| !same_node(*child, type_node)),
14572 );
14573 while let Some(node) = stack.pop() {
14574 if matches!(node.kind(), "identifier" | "field_identifier")
14575 && node_text(node, source) == field_name
14576 {
14577 let mut binding = DeclaredNameBinding {
14578 pointer_depth: 0,
14579 indirect: false,
14580 };
14581 let mut current = node.parent();
14582 while let Some(parent) = current {
14583 if same_node(parent, declaration) {
14584 return Some(binding);
14585 }
14586 match parent.kind() {
14587 "pointer_declarator" => {
14588 binding.pointer_depth += 1;
14589 binding.indirect = true;
14590 }
14591 "reference_declarator" => binding.indirect = true,
14592 _ => {}
14593 }
14594 current = parent.parent();
14595 }
14596 return None;
14597 }
14598 let mut cursor = node.walk();
14599 stack.extend(node.named_children(&mut cursor));
14600 }
14601 None
14602}
14603
14604fn normalize_field_type_text(type_text: &str) -> String {
14605 const FIELD_SPECIFIERS: [&str; 8] = [
14606 "extern ",
14607 "static ",
14608 "mutable ",
14609 "constexpr ",
14610 "constinit ",
14611 "inline ",
14612 "volatile ",
14613 "const ",
14614 ];
14615
14616 let mut normalized = normalize_type_text(type_text);
14617 loop {
14618 let Some(stripped) = FIELD_SPECIFIERS
14619 .iter()
14620 .find_map(|specifier| normalized.strip_prefix(specifier))
14621 else {
14622 return normalized;
14623 };
14624 normalized = normalize_type_text(stripped);
14625 }
14626}
14627
14628pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
14629 let Some(type_node) = node.child_by_field_name("type") else {
14630 return false;
14631 };
14632 ctx.visibility.resolves_to_type(
14633 &ctx.analyzer,
14634 ctx.file,
14635 node_text(type_node, ctx.source),
14636 owner,
14637 )
14638}
14639
14640pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
14641 !ctx.analyzer
14642 .declarations(ctx.file)
14643 .into_iter()
14644 .filter(|unit| unit.is_function())
14645 .any(|unit| {
14646 ctx.analyzer.ranges(&unit).iter().any(|range| {
14647 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
14648 })
14649 })
14650}
14651
14652pub enum DeclarationConstructorInitializer<'tree> {
14654 Arguments(Node<'tree>),
14657 Expression(Node<'tree>),
14660 Empty,
14662}
14663
14664pub fn declaration_constructor_initializer(
14665 node: Node<'_>,
14666) -> DeclarationConstructorInitializer<'_> {
14667 let mut cursor = node.walk();
14668 for child in node.named_children(&mut cursor) {
14669 if child.kind() == "init_declarator" {
14670 let Some(value) = child
14671 .child_by_field_name("value")
14672 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
14673 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
14674 else {
14675 return DeclarationConstructorInitializer::Empty;
14676 };
14677 return match value.kind() {
14678 "argument_list" | "initializer_list" => {
14679 DeclarationConstructorInitializer::Arguments(value)
14680 }
14681 "compound_literal_expression" => call_arguments_node(value)
14682 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
14683 DeclarationConstructorInitializer::Arguments(arguments)
14684 }),
14685 _ => DeclarationConstructorInitializer::Expression(value),
14686 };
14687 }
14688 if let Some(declarator) = declaration_declarator(node, child) {
14689 return declarator_parameters(declarator)
14690 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
14691 DeclarationConstructorInitializer::Arguments(parameters)
14692 });
14693 }
14694 }
14695 DeclarationConstructorInitializer::Empty
14696}
14697
14698pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
14699 match declaration_constructor_initializer(node) {
14700 DeclarationConstructorInitializer::Arguments(arguments) => {
14701 argument_children(arguments).count()
14702 }
14703 DeclarationConstructorInitializer::Expression(_) => 1,
14704 DeclarationConstructorInitializer::Empty => 0,
14705 }
14706}
14707
14708fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
14712 let mut current = node;
14713 loop {
14714 if let Some(parameters) = current.child_by_field_name("parameters") {
14715 return Some(parameters);
14716 }
14717 current = current.child_by_field_name("declarator")?;
14718 }
14719}
14720
14721fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
14722 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
14723}
14724
14725fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
14726 if node.kind() == "identifier" {
14727 return true;
14728 }
14729 if node.kind() == "parenthesized_expression" {
14730 return false;
14731 }
14732 if node.kind() == "call_expression" {
14733 return node
14734 .child_by_field_name("function")
14735 .is_some_and(|function| function.kind() == "identifier");
14736 }
14737 let mut stack = vec![node];
14738 while let Some(descendant) = stack.pop() {
14739 if descendant != node && descendant.kind() == "parenthesized_expression" {
14740 continue;
14741 }
14742 if descendant.kind() == "identifier" {
14743 return true;
14744 }
14745 if descendant.kind() == "call_expression" {
14746 if descendant
14747 .child_by_field_name("function")
14748 .is_some_and(|function| function.kind() == "identifier")
14749 {
14750 return true;
14751 }
14752 continue;
14753 }
14754 push_named_children_reversed(descendant, &mut stack);
14755 }
14756 false
14757}
14758
14759fn macro_expansion_shape_is_safe(
14760 node: Node<'_>,
14761 source: &str,
14762 parameters: &[String],
14763 environment: &MacroEnvironment,
14764) -> bool {
14765 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
14766 return true;
14767 }
14768 if node.kind() == "call_expression" {
14769 let Some(function) = node.child_by_field_name("function") else {
14770 return true;
14771 };
14772 if function.kind() != "identifier" {
14773 return true;
14774 }
14775 let function_name = node_text(function, source);
14776 if parameters
14777 .iter()
14778 .any(|parameter| parameter == function_name)
14779 {
14780 return false;
14781 }
14782 if !environment.may_bind(function_name) {
14783 return true;
14784 }
14785 let Some(arguments) = node.child_by_field_name("arguments") else {
14786 return false;
14787 };
14788 return argument_children(arguments).all(|argument| {
14789 if argument.kind() == "identifier"
14790 && parameters
14791 .iter()
14792 .any(|parameter| parameter == node_text(argument, source))
14793 {
14794 return false;
14795 }
14796 macro_expansion_shape_is_safe(argument, source, parameters, environment)
14797 });
14798 }
14799 let mut stack = vec![node];
14800 while let Some(descendant) = stack.pop() {
14801 if descendant != node {
14802 if descendant.kind() == "parenthesized_expression" {
14803 continue;
14804 }
14805 if descendant.kind() == "call_expression" {
14806 let expands = descendant
14807 .child_by_field_name("function")
14808 .filter(|function| function.kind() == "identifier")
14809 .is_some_and(|function| environment.may_bind(node_text(function, source)));
14810 if expands {
14811 return false;
14812 }
14813 continue;
14814 }
14815 }
14816 if descendant.kind() == "identifier" {
14817 let identifier = node_text(descendant, source);
14818 if parameters.iter().any(|parameter| parameter == identifier)
14819 || environment.may_bind(identifier)
14820 {
14821 return false;
14822 }
14823 }
14824 push_named_children_reversed(descendant, &mut stack);
14825 }
14826 true
14827}
14828
14829fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
14830 let source = prepared.source();
14831 let mut facts = Vec::new();
14832 let mut nodes = vec![prepared.tree().root_node()];
14833 while let Some(node) = nodes.pop() {
14834 if node.kind() == "preproc_include" {
14835 let Some(path) = node
14836 .child_by_field_name("path")
14837 .and_then(|path| crate::imports::include_path_literal_value(path, source))
14838 .map(str::to_owned)
14839 else {
14840 continue;
14841 };
14842 facts.push(StructuredIncludeFact {
14843 start_byte: node.start_byte(),
14844 end_byte: node.end_byte(),
14845 path,
14846 });
14847 continue;
14848 }
14849 push_named_children_reversed(node, &mut nodes);
14850 }
14851 Arc::from(facts.into_boxed_slice())
14852}
14853
14854fn has_unresolved_include_visible_before_in_prepared(
14855 file: &ProjectFile,
14856 prepared: &PreparedSyntaxTree,
14857 include_targets: &IncludeTargetIndex,
14858 facts: &[StructuredIncludeFact],
14859 before_byte: usize,
14860 effective_guards: &ReferenceGuardAugmenter<'_>,
14861) -> bool {
14862 let guards = OnceCell::new();
14863 let reference = CallableReferenceContext {
14864 effective_guards,
14865 file_guards: OnceCell::new(),
14866 file,
14867 position: Some(CallableReferencePosition {
14868 prepared,
14869 byte: before_byte,
14870 guards: &guards,
14871 }),
14872 };
14873 let root = prepared.tree().root_node();
14874 facts
14875 .iter()
14876 .filter(|fact| fact.end_byte <= before_byte)
14877 .any(|fact| {
14878 let node = root
14879 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
14880 .expect("structured include fact range must be in prepared tree");
14881 assert_eq!(
14882 node.kind(),
14883 "preproc_include",
14884 "structured include fact range must identify its include node"
14885 );
14886 callable_preprocessor_context_is_visible_for_reference(
14887 node,
14888 prepared.source(),
14889 &reference,
14890 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
14891 })
14892}
14893
14894fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
14895 let descendant = node;
14896 while let Some(parent) = node.parent() {
14897 if is_cpp_preprocessor_conditional(parent)
14898 && !is_file_covering_include_guard(parent, source)
14899 && preprocessor_conditional_contains_descendant(parent, descendant)
14900 {
14901 return true;
14902 }
14903 node = parent;
14904 }
14905 false
14906}
14907
14908fn owning_preprocessor_conditionals(
14920 root: Node<'_>,
14921 event: Node<'_>,
14922 source: &str,
14923) -> OwningPreprocessorConditionals {
14924 if !has_preprocessor_conditional_ancestor(event, source) {
14925 return OwningPreprocessorConditionals::default();
14926 }
14927 let start = event.start_byte();
14928 let descendant = root
14929 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14930 .expect("a byte inside the parsed tree names a descendant");
14931 let mut owners = Vec::new();
14932 let mut current = descendant.parent();
14933 while let Some(conditional) = current {
14934 if is_cpp_preprocessor_conditional(conditional)
14935 && !is_file_covering_include_guard(conditional, source)
14936 && preprocessor_conditional_contains_descendant(conditional, descendant)
14937 {
14938 owners.push(conditional.start_byte());
14939 }
14940 current = conditional.parent();
14941 }
14942 owners.into_boxed_slice()
14943}
14944
14945fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14946 node.parent()
14947 .filter(|parent| parent.kind() == "translation_unit")
14948 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14949 && is_canonical_include_guard(node, source)
14950}
14951
14952fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14953 if node.kind() != "preproc_ifdef"
14954 || node
14955 .child(0)
14956 .is_none_or(|directive| directive.kind() != "#ifndef")
14957 || node.child_by_field_name("alternative").is_some()
14958 {
14959 return false;
14960 }
14961 let Some(guard_name) = node.child_by_field_name("name") else {
14962 return false;
14963 };
14964 let mut cursor = node.walk();
14965 node.named_children(&mut cursor)
14966 .find(|child| *child != guard_name && child.kind() != "comment")
14967 .filter(|child| child.kind() == "preproc_def")
14968 .and_then(|definition| definition.child_by_field_name("name"))
14969 .is_some_and(|defined_name| {
14970 node_text(defined_name, source) == node_text(guard_name, source)
14971 })
14972}
14973
14974fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14975 let mut guard = None;
14976 for child in named_children_iter(root) {
14977 if child.kind() == "comment" || is_pragma_once(child, source) {
14978 continue;
14979 }
14980 if guard.is_none() && is_canonical_include_guard(child, source) {
14981 guard = Some(child);
14982 } else {
14983 return None;
14984 }
14985 }
14986 guard
14987 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14988 .map(|name| node_text(name, source).to_string())
14989}
14990
14991fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14992 if (0..root.named_child_count())
14993 .filter_map(|index| root.named_child(index))
14994 .any(|child| is_pragma_once(child, source))
14995 {
14996 return MacroIncludeProtection::PragmaOnce;
14997 }
14998 top_level_canonical_include_guard_name(root, source)
14999 .map(MacroIncludeProtection::MacroGuard)
15000 .unwrap_or(MacroIncludeProtection::None)
15001}
15002
15003fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
15004 node.kind() == "preproc_call"
15005 && node
15006 .child_by_field_name("directive")
15007 .is_some_and(|directive| node_text(directive, source) == "#pragma")
15008 && node
15009 .child_by_field_name("argument")
15010 .is_some_and(|argument| node_text(argument, source).trim() == "once")
15011}
15012
15013fn parse_preproc_identifier(argument: &str) -> Option<String> {
15014 let parsed = parse_sentinel(
15015 "void __bifrost_undef() { ",
15016 SentinelInput::replacement(argument),
15017 "; }",
15018 );
15019 let (sentinel, tree) = (parsed.source, parsed.tree);
15020 if tree.root_node().has_error() {
15021 return None;
15022 }
15023 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
15024 let identifier = single_named_child_matching(statement, |child| !child.is_extra())?;
15027 (identifier.kind() == "identifier").then(|| node_text(identifier, &sentinel).to_string())
15028}
15029
15030pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
15031 match node.kind() {
15032 "identifier" | "field_identifier" => {
15033 let name = node_text(node, source).trim();
15034 (!name.is_empty()).then(|| name.to_string())
15035 }
15036 "abstract_array_declarator"
15037 | "abstract_function_declarator"
15038 | "abstract_parenthesized_declarator"
15039 | "abstract_pointer_declarator"
15040 | "abstract_reference_declarator" => None,
15041 "function_declarator" => node
15042 .child_by_field_name("declarator")
15043 .or_else(|| node.child_by_field_name("name"))
15044 .and_then(|child| extract_variable_name(child, source)),
15045 _ => node
15046 .child_by_field_name("declarator")
15047 .or_else(|| node.child_by_field_name("name"))
15048 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
15049 .and_then(|child| extract_variable_name(child, source)),
15050 }
15051}
15052
15053pub fn is_c_source_file(file: &ProjectFile) -> bool {
15064 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
15065}
15066
15067pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
15074 if !is_c_source_file(file) || node.kind() != "identifier" {
15075 return false;
15076 }
15077 let mut operand = node;
15078 while let Some(parent) = operand.parent().filter(|parent| {
15079 parent.kind() == "parenthesized_expression"
15080 && parent.named_child_count() == 1
15081 && parent.named_child(0) == Some(operand)
15082 }) {
15083 operand = parent;
15084 }
15085 operand.parent().is_some_and(|parent| {
15086 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
15087 })
15088}
15089
15090pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
15099 if !is_c_offsetof_member_node(node) {
15100 return None;
15101 }
15102 let expression = node.parent()?;
15103 if expression.has_error() {
15104 return None;
15105 }
15106 let closing = expression.child(expression.child_count().saturating_sub(1))?;
15107 if closing.kind() != ")" || closing.is_missing() {
15108 return None;
15109 }
15110 let type_descriptor = expression.child_by_field_name("type")?;
15111 if type_descriptor.kind() != "type_descriptor"
15112 || type_descriptor.is_missing()
15113 || type_descriptor.has_error()
15114 {
15115 return None;
15116 }
15117 let type_specifier = type_descriptor.child_by_field_name("type")?;
15118 if type_specifier.is_missing() || type_specifier.has_error() {
15119 return None;
15120 }
15121 let type_reference = match type_specifier.kind() {
15122 "class_specifier" | "struct_specifier" | "union_specifier" => {
15123 type_specifier.child_by_field_name("name")?
15124 }
15125 _ => type_specifier,
15126 };
15127 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
15128}
15129
15130pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
15135 node.kind() == "field_identifier"
15136 && node.parent().is_some_and(|parent| {
15137 parent.kind() == "offsetof_expression"
15138 && parent.child_by_field_name("member") == Some(node)
15139 })
15140}
15141
15142pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
15146 if node.kind() != "template_function" {
15147 return false;
15148 }
15149 let Some(arguments) = node
15150 .parent()
15151 .filter(|parent| parent.kind() == "template_argument_list")
15152 else {
15153 return false;
15154 };
15155 let Some(owner) = arguments.parent().filter(|parent| {
15156 parent.kind() == "template_type"
15157 && parent.child_by_field_name("arguments") == Some(arguments)
15158 }) else {
15159 return false;
15160 };
15161
15162 let mut current = owner;
15163 while let Some(parent) = current.parent() {
15164 if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
15165 {
15166 return true;
15167 }
15168 if parent.kind() == "qualified_identifier"
15169 && parent.child_by_field_name("name") == Some(current)
15170 {
15171 current = parent;
15172 continue;
15173 }
15174 return false;
15175 }
15176 false
15177}
15178
15179pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
15195 if node.kind() != "type_identifier" {
15196 return false;
15197 }
15198 let Some(descriptor) = node
15199 .parent()
15200 .filter(|parent| parent.kind() == "type_descriptor")
15201 else {
15202 return false;
15203 };
15204 if descriptor.child_by_field_name("type") != Some(node) {
15205 return false;
15206 }
15207 let Some(arguments) = descriptor
15208 .parent()
15209 .filter(|parent| parent.kind() == "template_argument_list")
15210 else {
15211 return false;
15212 };
15213 arguments.parent().is_some_and(|owner| {
15214 matches!(
15215 owner.kind(),
15216 "template_type" | "template_function" | "template_method"
15217 ) && owner.child_by_field_name("arguments") == Some(arguments)
15218 })
15219}
15220
15221pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
15234 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
15235}
15236
15237pub fn is_declarator_node(node: Node<'_>) -> bool {
15238 matches!(
15239 node.kind(),
15240 "identifier"
15241 | "field_identifier"
15242 | "qualified_identifier"
15243 | "scoped_identifier"
15244 | "pointer_declarator"
15245 | "reference_declarator"
15246 | "array_declarator"
15247 | "parenthesized_declarator"
15248 | "function_declarator"
15249 )
15250}
15251
15252pub fn declaration_declarator<'tree>(
15261 declaration: Node<'tree>,
15262 child: Node<'tree>,
15263) -> Option<Node<'tree>> {
15264 if !matches!(
15265 declaration.kind(),
15266 "declaration"
15267 | "field_declaration"
15268 | "parameter_declaration"
15269 | "optional_parameter_declaration"
15270 | "function_definition"
15271 | "type_definition"
15272 | "alias_declaration"
15273 | "template_instantiation"
15274 ) {
15275 return None;
15276 }
15277 if declaration
15278 .child_by_field_name("type")
15279 .is_some_and(|type_node| same_node(type_node, child))
15280 {
15281 return None;
15282 }
15283 if child.kind() == "init_declarator" {
15284 return child.child_by_field_name("declarator");
15285 }
15286 let field = field_name_in_parent(declaration, child);
15287 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
15288 || (declaration.kind() == "type_definition"
15289 && child.kind() == "type_identifier"
15290 && matches!(field, Some("declarator") | None))
15291 {
15292 Some(child)
15293 } else {
15294 None
15295 }
15296}
15297
15298pub fn cpp_binding_parts<'tree>(
15300 node: Node<'tree>,
15301 source: &str,
15302) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
15303 let declarator = node.child_by_field_name("declarator")?;
15304 let name = extract_variable_name(declarator, source)?;
15305 let type_node = node
15306 .child_by_field_name("type")
15307 .or_else(|| first_type_child(node));
15308 Some((name, declarator, type_node))
15309}
15310
15311#[derive(Clone, Debug, PartialEq, Eq)]
15314pub struct RecoveredNamespaceRegion {
15315 pub start: usize,
15317 pub end: usize,
15319 pub components: Vec<String>,
15322 pub component_ranges: Vec<Range>,
15333 pub component_name_ranges: Vec<Range>,
15339 pub component_closes: Vec<Option<Range>>,
15345}
15346
15347#[derive(Clone, Debug, Default)]
15412pub struct OrphanedNamespaceScopeIndex {
15413 regions: Vec<RecoveredNamespaceRegion>,
15414 brace_closes: HashMap<usize, Range>,
15417}
15418
15419impl OrphanedNamespaceScopeIndex {
15420 pub fn build(root: Node<'_>, source: &str) -> Self {
15421 if !root.has_error() {
15422 return Self::default();
15423 }
15424 #[derive(Clone, Copy)]
15430 struct OpenHead {
15431 head: Range,
15432 open: usize,
15433 }
15434 struct PendingRegion {
15437 start: usize,
15438 end: usize,
15439 components: Vec<String>,
15440 heads: Vec<OpenHead>,
15441 name_ranges: Vec<Range>,
15442 }
15443 struct Frame<'tree> {
15444 node: Node<'tree>,
15445 children: Vec<Node<'tree>>,
15446 next: usize,
15447 parsed_scope: Vec<String>,
15448 run: Option<PendingRegion>,
15449 }
15450 fn frame<'tree>(
15451 node: Node<'tree>,
15452 mut parsed_scope: Vec<String>,
15453 source: &str,
15454 ) -> Frame<'tree> {
15455 if node.kind() == "namespace_definition"
15456 && let Some(name) = node.child_by_field_name("name")
15457 {
15458 let mut components = Vec::new();
15459 if append_cpp_name_components(name, source, &mut components).is_some() {
15460 parsed_scope.extend(components);
15461 }
15462 }
15463 let mut cursor = node.walk();
15464 Frame {
15465 node,
15466 children: node.children(&mut cursor).collect(),
15467 next: 0,
15468 parsed_scope,
15469 run: None,
15470 }
15471 }
15472 let mut regions = Vec::new();
15473 let mut brace_closes = HashMap::default();
15474 let mut namespace_brace_proofs = Vec::new();
15481 let mut unpaired_closes: Vec<Range> = Vec::new();
15487 let mut open = Vec::new();
15491 let mut lexical_scope = Vec::new();
15492 let mut lexical_heads: Vec<OpenHead> = Vec::new();
15497 let mut lexical_name_ranges: Vec<Range> = Vec::new();
15500 let mut frames = vec![frame(root, Vec::new(), source)];
15501 while !frames.is_empty() {
15506 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
15507 let current = frames.last_mut().expect("frames is non-empty");
15508 if current.next == current.children.len() {
15509 regions.extend(frames.pop().expect("the frame just borrowed").run);
15510 continue;
15511 }
15512 let child = current.children[current.next];
15513 current.next += 1;
15514 match child.kind() {
15515 "{" if !child.is_missing() => {
15516 regions.extend(current.run.take());
15517 if let Some(namespace) = parent_node.filter(|parent| {
15518 parent.kind() == "namespace_definition"
15519 && parent.child_by_field_name("body") == Some(current.node)
15520 && child.start_byte() == current.node.start_byte()
15521 }) && let Some(proof) = namespace_body_brace_proof(current.node)
15522 {
15523 namespace_brace_proofs.push((child.start_byte(), namespace, proof));
15524 }
15525 let mut named = parent_node.and_then(|parent| {
15530 namespace_body_name_components(parent, current.node, source)
15531 });
15532 if named.is_none() {
15533 named = recovered_namespace_open_components(
15534 ¤t.children[..current.next - 1],
15535 source,
15536 );
15537 }
15538 open.push((child.start_byte(), lexical_scope.len()));
15539 if let Some((written_at, components)) = named {
15540 let head = OpenHead {
15541 head: Range {
15542 start_byte: written_at.start_byte(),
15543 end_byte: child.end_byte(),
15544 start_line: written_at.start_position().row + 1,
15545 end_line: child.end_position().row + 1,
15546 },
15547 open: child.start_byte(),
15548 };
15549 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
15550 lexical_name_ranges
15551 .extend(components.iter().map(|(_, name_range)| *name_range));
15552 lexical_scope
15553 .extend(components.into_iter().map(|(component, _)| component));
15554 }
15555 continue;
15556 }
15557 "}" if !child.is_missing() => {
15558 regions.extend(current.run.take());
15559 let close = Range {
15560 start_byte: child.start_byte(),
15561 end_byte: child.end_byte(),
15562 start_line: child.start_position().row + 1,
15563 end_line: child.end_position().row + 1,
15564 };
15565 match open.pop() {
15566 Some((start, namespace_len)) => {
15567 debug_assert!(
15568 start < close.start_byte,
15569 "a close pairs with an open before it: {start} then {close:?}"
15570 );
15571 lexical_scope.truncate(namespace_len);
15572 lexical_heads.truncate(namespace_len);
15573 lexical_name_ranges.truncate(namespace_len);
15574 brace_closes.insert(start, close);
15575 }
15576 None => unpaired_closes.push(close),
15577 }
15578 continue;
15579 }
15580 _ => {}
15581 }
15582 if current.node.kind() != "namespace_definition"
15586 && lexical_scope != current.parsed_scope
15587 {
15588 match &mut current.run {
15589 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
15590 run => {
15591 regions.extend(run.take());
15592 *run = Some(PendingRegion {
15593 start: child.start_byte(),
15594 end: child.end_byte(),
15595 components: lexical_scope.clone(),
15596 heads: lexical_heads.clone(),
15597 name_ranges: lexical_name_ranges.clone(),
15598 });
15599 }
15600 }
15601 } else {
15602 regions.extend(current.run.take());
15603 }
15604 let descend = child.has_error()
15612 || (child.kind() == "function_definition"
15613 && child.child_by_field_name("body").is_some_and(|body| {
15614 namespace_body_name_components(child, body, source).is_some()
15615 }));
15616 if descend {
15617 let parsed_scope = current.parsed_scope.clone();
15618 frames.push(frame(child, parsed_scope, source));
15619 }
15620 }
15621 if !unpaired_closes.is_empty() {
15630 brace_closes.clear();
15631 } else {
15632 let missing_namespaces = namespace_brace_proofs
15638 .iter()
15639 .filter_map(|(_, namespace, proof)| {
15640 matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
15641 })
15642 .collect::<HashSet<_>>();
15643 for (open, namespace, proof) in namespace_brace_proofs {
15644 if !std::iter::successors(Some(namespace), |node| node.parent())
15645 .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
15646 {
15647 continue;
15648 }
15649 match proof {
15650 NamespaceBodyBraceProof::Real(close) => {
15651 brace_closes.insert(open, close);
15652 }
15653 NamespaceBodyBraceProof::Missing => {
15654 brace_closes.remove(&open);
15655 }
15656 }
15657 }
15658 }
15659 let regions = regions
15664 .into_iter()
15665 .map(|region| {
15666 debug_assert_eq!(
15667 region.components.len(),
15668 region.heads.len(),
15669 "every recovered namespace level carries the head that writes it: {:?}",
15670 region.components
15671 );
15672 debug_assert_eq!(
15673 region.components.len(),
15674 region.name_ranges.len(),
15675 "every recovered namespace level carries the name token that writes it: {:?}",
15676 region.components
15677 );
15678 let component_closes: Vec<_> = region
15679 .heads
15680 .iter()
15681 .map(|level| brace_closes.get(&level.open).copied())
15682 .collect();
15683 let component_ranges = region
15684 .heads
15685 .iter()
15686 .zip(&component_closes)
15687 .map(|(level, close)| match close {
15688 Some(close) => Range {
15689 start_byte: level.head.start_byte,
15690 end_byte: close.end_byte,
15691 start_line: level.head.start_line,
15692 end_line: close.end_line,
15693 },
15694 None => level.head,
15695 })
15696 .collect();
15697 RecoveredNamespaceRegion {
15698 start: region.start,
15699 end: region.end,
15700 components: region.components,
15701 component_ranges,
15702 component_name_ranges: region.name_ranges,
15703 component_closes,
15704 }
15705 })
15706 .collect();
15707 Self {
15708 regions,
15709 brace_closes,
15710 }
15711 }
15712
15713 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
15720 self.brace_closes.get(&open).copied()
15721 }
15722
15723 pub fn is_empty(&self) -> bool {
15724 self.regions.is_empty()
15725 }
15726
15727 pub fn approximate_size(&self) -> usize {
15729 self.regions.iter().fold(
15730 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
15731 |total, region| {
15732 total
15733 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
15734 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
15735 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
15736 .saturating_add(
15737 region.component_name_ranges.len() * std::mem::size_of::<Range>(),
15738 )
15739 .saturating_add(
15740 region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
15741 )
15742 },
15743 )
15744 }
15745
15746 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
15748 self.regions
15749 .iter()
15750 .filter(|region| region.start <= byte && byte < region.end)
15751 .min_by_key(|region| region.end - region.start)
15752 }
15753
15754 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
15758 self.enclosing_namespace_levels(node, source)
15759 .into_iter()
15760 .map(|(component, ..)| component)
15761 .collect()
15762 }
15763
15764 pub fn enclosing_namespace_levels(
15773 &self,
15774 node: Node<'_>,
15775 source: &str,
15776 ) -> Vec<(String, Range, Range)> {
15777 let mut parsed = Vec::new();
15778 let mut current = node.parent();
15779 while let Some(parent) = current {
15780 if parent.kind() == "namespace_definition"
15781 && let Some(name) = parent.child_by_field_name("name")
15782 && let Some(components) = cpp_name_component_ranges(name, source)
15783 {
15784 let range = node_range(parent);
15785 parsed.push((
15786 parent.start_byte(),
15787 components
15788 .into_iter()
15789 .map(|(component, name_range)| (component, range, name_range))
15790 .collect(),
15791 namespace_definition_has_real_braces(parent),
15792 ));
15793 }
15794 current = parent.parent();
15795 }
15796 parsed.reverse();
15797 self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
15798 region
15799 .components
15800 .iter()
15801 .take(active_components)
15802 .cloned()
15803 .zip(
15804 region
15805 .component_ranges
15806 .iter()
15807 .take(active_components)
15808 .copied(),
15809 )
15810 .zip(
15811 region
15812 .component_name_ranges
15813 .iter()
15814 .take(active_components)
15815 .copied(),
15816 )
15817 .map(|((component, range), name_range)| (component, range, name_range))
15818 .collect()
15819 })
15820 }
15821
15822 pub fn restore_enclosing_namespaces(
15828 &self,
15829 parsed: Vec<(usize, Vec<String>, bool)>,
15830 node_start: usize,
15831 ) -> Vec<String> {
15832 self.splice_recovered_path(parsed, node_start, |region, active_components| {
15833 region
15834 .components
15835 .iter()
15836 .take(active_components)
15837 .cloned()
15838 .collect()
15839 })
15840 }
15841
15842 fn splice_recovered_path<T>(
15848 &self,
15849 parsed: Vec<(usize, Vec<T>, bool)>,
15850 node_start: usize,
15851 recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
15852 ) -> Vec<T> {
15853 let Some(region) = self.region_at(node_start) else {
15854 return parsed
15855 .into_iter()
15856 .flat_map(|(_, levels, _)| levels)
15857 .collect();
15858 };
15859 debug_assert_eq!(
15860 region.components.len(),
15861 region.component_closes.len(),
15862 "every recovered namespace component carries its close proof: {region:?}"
15863 );
15864 debug_assert_eq!(
15865 region.components.len(),
15866 region.component_name_ranges.len(),
15867 "every recovered namespace component carries its name token: {region:?}"
15868 );
15869 if region.components.is_empty() {
15870 return parsed
15871 .into_iter()
15872 .filter(|(_, _, complete)| *complete)
15873 .flat_map(|(_, levels, _)| levels)
15874 .collect();
15875 }
15876 let active_components = region
15877 .component_closes
15878 .iter()
15879 .take_while(|close| {
15880 close
15881 .as_ref()
15882 .is_none_or(|close| node_start < close.end_byte)
15883 })
15884 .count();
15885 recovered(region, active_components)
15886 .into_iter()
15887 .chain(
15888 parsed
15889 .into_iter()
15890 .filter(|(start, _, _)| *start >= region.start)
15891 .flat_map(|(_, levels, _)| levels),
15892 )
15893 .collect()
15894 }
15895}
15896
15897pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
15901 debug_assert_eq!(namespace.kind(), "namespace_definition");
15902 let Some(body) = namespace.child_by_field_name("body") else {
15903 return false;
15904 };
15905 let mut opening = false;
15906 let mut closing = false;
15907 let mut cursor = body.walk();
15908 for child in body.children(&mut cursor) {
15909 match child.kind() {
15910 "{" if !child.is_missing() => opening = true,
15911 "}" if !child.is_missing() => closing = true,
15912 _ => {}
15913 }
15914 }
15915 opening && closing
15916}
15917
15918fn recovered_namespace_open_components<'tree>(
15934 preceding: &[Node<'tree>],
15935 source: &str,
15936) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15937 let mut head = Vec::new();
15938 for &sibling in preceding.iter().rev() {
15939 if sibling.kind() != "comment" {
15940 head.push(sibling);
15941 if head.len() == 2 {
15942 break;
15943 }
15944 }
15945 }
15946 let [name, keyword] = head[..] else {
15947 return None;
15948 };
15949 if keyword.kind() != "namespace" {
15950 return None;
15951 }
15952 let components = cpp_name_component_ranges(name, source)?;
15953 (!components.is_empty()).then_some((keyword, components))
15954}
15955
15956fn namespace_body_name_components<'tree>(
15961 parent: Node<'tree>,
15962 body: Node<'tree>,
15963 source: &str,
15964) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15965 if parent.child_by_field_name("body") != Some(body) {
15966 return None;
15967 }
15968 let (written_at, name) = match (parent.kind(), body.kind()) {
15969 ("namespace_definition", "declaration_list") => {
15970 (parent, parent.child_by_field_name("name")?)
15971 }
15972 ("function_definition", "compound_statement") => {
15973 recovered_literal_namespace_head(parent, body, source)
15979 .or_else(|| macro_modifier_namespace_head(parent, body, source))?
15980 }
15981 _ => return None,
15982 };
15983 let components = cpp_name_component_ranges(name, source)?;
15984 (!components.is_empty()).then_some((written_at, components))
15985}
15986
15987fn recovered_literal_namespace_head<'tree>(
15990 parent: Node<'tree>,
15991 body: Node<'tree>,
15992 source: &str,
15993) -> Option<(Node<'tree>, Node<'tree>)> {
15994 let mut cursor = parent.walk();
15998 let children = parent
15999 .children(&mut cursor)
16000 .filter(|child| child.kind() != "comment")
16001 .collect::<Vec<_>>();
16002 let [prefix, keyword, error, actual_body] = children.as_slice() else {
16003 return None;
16004 };
16005 if *actual_body != body
16006 || parent.child_by_field_name("type") != Some(*prefix)
16007 || parent.child_by_field_name("declarator") != Some(*keyword)
16008 || prefix.kind() != "type_identifier"
16009 || prefix.is_missing()
16010 || prefix.has_error()
16011 || prefix.child_count() != 0
16012 || keyword.kind() != "identifier"
16013 || keyword.is_missing()
16014 || keyword.has_error()
16015 || keyword.child_count() != 0
16016 || node_text(*keyword, source) != "namespace"
16017 || error.kind() != "ERROR"
16018 || error.is_missing()
16019 || body.is_missing()
16020 {
16021 return None;
16022 }
16023 let mut cursor = error.walk();
16024 let names = error
16025 .children(&mut cursor)
16026 .filter(|child| child.kind() != "comment")
16027 .collect::<Vec<_>>();
16028 let [name] = names.as_slice() else {
16029 return None;
16030 };
16031 if !matches!(name.kind(), "identifier" | "namespace_identifier")
16032 || name.is_missing()
16033 || name.has_error()
16034 || name.child_count() != 0
16035 {
16036 return None;
16037 }
16038 let open = body.child(0)?;
16039 if open.kind() != "{"
16040 || open.is_missing()
16041 || open.has_error()
16042 || open.start_byte() != body.start_byte()
16043 {
16044 return None;
16045 }
16046 Some((*keyword, *name))
16049}
16050
16051fn macro_modifier_namespace_head<'tree>(
16055 parent: Node<'tree>,
16056 body: Node<'tree>,
16057 source: &str,
16058) -> Option<(Node<'tree>, Node<'tree>)> {
16059 let mut cursor = parent.walk();
16067 let children = parent
16068 .children(&mut cursor)
16069 .filter(|child| child.kind() != "comment")
16070 .collect::<Vec<_>>();
16071 let [prefix, keyword, name, actual_body] = children.as_slice() else {
16072 return None;
16073 };
16074 if *actual_body != body
16075 || parent.child_by_field_name("type") != Some(*keyword)
16076 || parent.child_by_field_name("declarator") != Some(*name)
16077 || prefix.kind() != "macro_modifier"
16078 || prefix.is_missing()
16079 || prefix.has_error()
16080 || macro_modifier_identifier(*prefix).is_none()
16081 || keyword.kind() != "type_identifier"
16082 || keyword.is_missing()
16083 || keyword.has_error()
16084 || keyword.child_count() != 0
16085 || node_text(*keyword, source) != "namespace"
16086 || !matches!(name.kind(), "identifier" | "namespace_identifier")
16087 || name.is_missing()
16088 || name.has_error()
16089 || name.child_count() != 0
16090 || body.is_missing()
16091 {
16092 return None;
16093 }
16094 let open = body.child(0)?;
16095 if open.kind() != "{"
16096 || open.is_missing()
16097 || open.has_error()
16098 || open.start_byte() != body.start_byte()
16099 {
16100 return None;
16101 }
16102 Some((*keyword, *name))
16105}
16106
16107#[derive(Clone, Copy)]
16108enum NamespaceBodyBraceProof {
16109 Real(Range),
16110 Missing,
16111}
16112
16113fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
16119 if body.kind() != "declaration_list" {
16120 return None;
16121 }
16122 let mut cursor = body.walk();
16123 let children = body.children(&mut cursor).collect::<Vec<_>>();
16124 let close_index = children
16125 .iter()
16126 .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
16127 let close = children[close_index];
16128 if close.is_missing() {
16129 return Some(NamespaceBodyBraceProof::Missing);
16130 }
16131 let namespace_terminator_sibling = body
16132 .parent()
16133 .filter(|namespace| namespace.kind() == "namespace_definition")
16134 .and_then(|namespace| namespace.next_sibling())
16135 .is_some_and(|sibling| {
16136 sibling.kind() == ";"
16137 || (sibling.kind() == "expression_statement"
16138 && sibling.child_count() == 1
16139 && sibling.child(0).is_some_and(|child| child.kind() == ";"))
16140 });
16141 if namespace_terminator_sibling
16146 || children
16147 .get(close_index + 1)
16148 .is_some_and(|sibling| sibling.kind() == ";")
16149 || children[..close_index]
16150 .iter()
16151 .rev()
16152 .find(|child| !child.is_extra() && child.kind() != "{")
16153 .is_some_and(|previous| {
16154 matches!(
16155 previous.kind(),
16156 "expression_statement" | "labeled_statement"
16157 )
16158 })
16159 {
16160 return None;
16161 }
16162 Some(NamespaceBodyBraceProof::Real(node_range(close)))
16163}
16164
16165#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16166pub enum RecoveredDeclaratorTypeContext {
16167 Declaration,
16168 FunctionDefinition,
16169 Parameter,
16170}
16171
16172pub fn recovered_macro_decorated_declarator_type(
16187 node: Node<'_>,
16188) -> Option<RecoveredDeclaratorTypeContext> {
16189 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
16190}
16191
16192pub fn recovered_macro_decorated_type_node(
16197 node: Node<'_>,
16198) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
16199 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
16200 return None;
16201 }
16202 let qualified = node.parent()?;
16203 if qualified.kind() != "qualified_identifier"
16204 || qualified.child_by_field_name("scope") != Some(node)
16205 || !(0..qualified.child_count())
16206 .filter_map(|index| qualified.child(index))
16207 .any(|child| child.kind() == "::" && child.is_missing())
16208 {
16209 return None;
16210 }
16211 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
16212 return None;
16213 }
16214
16215 let (declaration, context) = recovered_declarator_container(qualified)?;
16216 let type_node = declaration
16217 .child_by_field_name("type")
16218 .filter(|type_node| {
16219 *type_node != qualified
16220 && !type_node.is_missing()
16221 && type_node.start_byte() != type_node.end_byte()
16222 })?;
16223 Some((type_node, context))
16224}
16225
16226pub fn macro_modifier_decorated_type_node(node: Node<'_>) -> bool {
16233 matches!(node.kind(), "type_identifier" | "template_type")
16234 && node.parent().is_some_and(|declaration| {
16235 matches!(
16236 declaration.kind(),
16237 "declaration"
16238 | "function_definition"
16239 | "parameter_declaration"
16240 | "optional_parameter_declaration"
16241 ) && declaration.child_by_field_name("type") == Some(node)
16242 && declaration.child_by_field_name("declarator").is_some()
16243 && crate::declarations::cpp_declaration_type_follows_macro_modifier(
16244 declaration,
16245 node,
16246 )
16247 })
16248}
16249
16250fn recovered_declarator_container(
16251 mut declarator: Node<'_>,
16252) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
16253 loop {
16254 let parent = declarator.parent()?;
16255 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
16256 return Some((
16257 parent
16258 .parent()
16259 .filter(|declaration| declaration.kind() == "declaration")?,
16260 RecoveredDeclaratorTypeContext::Declaration,
16261 ));
16262 }
16263 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
16264 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
16265 }
16266 if parent.kind() == "function_definition"
16267 && has_field_child(parent, "declarator", declarator)
16268 {
16269 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
16270 }
16271 if matches!(
16277 parent.kind(),
16278 "parameter_declaration" | "optional_parameter_declaration"
16279 ) && has_field_child(parent, "declarator", declarator)
16280 {
16281 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
16282 }
16283 if !matches!(
16284 parent.kind(),
16285 "array_declarator"
16286 | "function_declarator"
16287 | "parenthesized_declarator"
16288 | "pointer_declarator"
16289 | "pointer_type_declarator"
16290 | "reference_declarator"
16291 ) || !has_field_child(parent, "declarator", declarator)
16292 {
16293 return None;
16294 }
16295 declarator = parent;
16296 }
16297}
16298
16299fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
16300 brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
16301}
16302
16303fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
16304 loop {
16305 if node.is_missing() || node.start_byte() == node.end_byte() {
16306 return false;
16307 }
16308 match node.kind() {
16309 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
16310 return true;
16311 }
16312 "array_declarator"
16313 | "function_declarator"
16314 | "parenthesized_declarator"
16315 | "pointer_declarator"
16316 | "pointer_type_declarator"
16317 | "reference_declarator" => {
16318 let Some(declarator) = node.child_by_field_name("declarator") else {
16319 return false;
16320 };
16321 node = declarator;
16322 }
16323 _ => return false,
16324 }
16325 }
16326}
16327
16328pub enum DesignatedInitializerOwner {
16330 Resolved(CodeUnit),
16331 Unresolved,
16332}
16333
16334enum InitializerOwnerStep {
16335 Field(String),
16336 AggregateWrapper,
16337}
16338
16339pub fn designated_initializer_owner(
16349 analyzer: &CppGraphSource<'_>,
16350 visibility: &VisibilityIndex<'_>,
16351 file: &ProjectFile,
16352 source: &str,
16353 node: Node<'_>,
16354) -> Option<DesignatedInitializerOwner> {
16355 if let Some(designator) = node
16356 .parent()
16357 .filter(|parent| parent.kind() == "field_designator")
16358 {
16359 let pair = designator.parent()?;
16360 if pair.kind() != "initializer_pair" {
16361 return None;
16362 }
16363 let mut cursor = pair.walk();
16364 let designators = pair
16365 .children_by_field_name("designator", &mut cursor)
16366 .collect::<Vec<_>>();
16367 let position = designators
16368 .iter()
16369 .position(|candidate| same_node(*candidate, designator))?;
16370 let initializer = pair.parent()?;
16371 if initializer.kind() != "initializer_list" {
16372 return None;
16373 }
16374 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
16375 for prior in &designators[..position] {
16376 let field = prior
16377 .child_by_field_name("field")
16378 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
16379 owner = owner.and_then(|owner| {
16380 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
16381 });
16382 }
16383 return Some(classified_designated_owner(owner));
16384 }
16385
16386 let init_declarator = node.parent()?;
16387 if init_declarator.child_by_field_name("declarator") != Some(node)
16388 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
16389 {
16390 return None;
16391 }
16392 Some(classified_designated_owner(declaration_owner(
16393 analyzer,
16394 visibility,
16395 file,
16396 source,
16397 init_declarator.parent()?,
16398 )))
16399}
16400
16401fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
16402 owner.map_or(
16403 DesignatedInitializerOwner::Unresolved,
16404 DesignatedInitializerOwner::Resolved,
16405 )
16406}
16407
16408fn initializer_list_owner(
16409 analyzer: &CppGraphSource<'_>,
16410 visibility: &VisibilityIndex<'_>,
16411 file: &ProjectFile,
16412 source: &str,
16413 initializer: Node<'_>,
16414) -> Option<CodeUnit> {
16415 let mut current = initializer;
16416 let mut steps = Vec::new();
16417 loop {
16418 let parent = current.parent()?;
16419 match parent.kind() {
16420 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
16421 let designator = parent.child_by_field_name("designator")?;
16422 let step = designator
16423 .child_by_field_name("field")
16424 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
16425 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
16426 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
16427 steps.push(step);
16428 current = parent.parent()?;
16429 }
16430 "initializer_list" => {
16431 current = parent;
16432 }
16433 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
16434 let declaration = parent.parent()?;
16435 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
16436 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
16437 }
16438 "compound_literal_expression"
16439 if parent.child_by_field_name("value") == Some(current) =>
16440 {
16441 let type_node = parent.child_by_field_name("type")?;
16442 let owner =
16443 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
16444 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
16445 }
16446 "ERROR" => current = parent,
16447 _ => return None,
16448 }
16449 }
16450}
16451
16452fn apply_initializer_owner_steps(
16453 analyzer: &CppGraphSource<'_>,
16454 visibility: &VisibilityIndex<'_>,
16455 file: &ProjectFile,
16456 mut owner: CodeUnit,
16457 steps: Vec<InitializerOwnerStep>,
16458) -> Option<CodeUnit> {
16459 for step in steps.into_iter().rev() {
16460 if let InitializerOwnerStep::Field(field_name) = step {
16461 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
16462 }
16463 }
16464 Some(owner)
16465}
16466
16467fn initializer_field_owner(
16468 analyzer: &CppGraphSource<'_>,
16469 visibility: &VisibilityIndex<'_>,
16470 file: &ProjectFile,
16471 owner: CodeUnit,
16472 field_name: &str,
16473) -> Option<CodeUnit> {
16474 let fields = visibility
16475 .visible_members_for_owner_name(file, &owner, field_name)
16476 .into_iter()
16477 .filter(|field| field.is_field())
16478 .collect::<Vec<_>>();
16479 let field = match fields.as_slice() {
16480 [field] => *field,
16481 _ => return None,
16482 };
16483 field_declared_binding(analyzer, visibility, file, field)?.unit
16484}
16485
16486fn declaration_owner(
16487 analyzer: &CppGraphSource<'_>,
16488 visibility: &VisibilityIndex<'_>,
16489 file: &ProjectFile,
16490 source: &str,
16491 declaration: Node<'_>,
16492) -> Option<CodeUnit> {
16493 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
16494 return None;
16495 }
16496 let type_node = declaration
16497 .child_by_field_name("type")
16498 .or_else(|| first_type_child(declaration))?;
16499 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
16500}
16501
16502fn resolve_designated_owner_type(
16503 analyzer: &CppGraphSource<'_>,
16504 visibility: &VisibilityIndex<'_>,
16505 file: &ProjectFile,
16506 source: &str,
16507 type_node: Node<'_>,
16508) -> Option<CodeUnit> {
16509 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
16510 return Some(owner);
16511 }
16512 if !is_globally_qualified_cpp_name(type_node)
16513 && let Some(components) = cpp_type_name_components(type_node, source)
16514 && let Some(resolution) =
16515 function_local_type_resolution(analyzer, file, type_node, &components, source)
16516 {
16517 return match resolution {
16518 LexicalTypeResolution::Resolved { unit, .. } => Some(unit).filter(CodeUnit::is_class),
16519 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
16520 };
16521 }
16522 let type_name = normalize_type_text(node_text(type_node, source));
16523 visibility
16524 .resolve_type(file, &type_name)
16525 .filter(CodeUnit::is_class)
16526}
16527
16528pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
16529 let mut cursor = node.walk();
16530 node.named_children(&mut cursor).find(|child| {
16531 matches!(
16532 child.kind(),
16533 "type_identifier"
16534 | "primitive_type"
16535 | "qualified_identifier"
16536 | "scoped_type_identifier"
16537 | "struct_specifier"
16538 | "union_specifier"
16539 | "enum_specifier"
16540 )
16541 })
16542}
16543
16544pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
16545 visibility: &VisibilityIndex<'_>,
16546 file: &ProjectFile,
16547 source: &str,
16548 declarator: Node<'_>,
16549 type_text: Option<&str>,
16550 bindings: &LocalInferenceEngine<T>,
16551) -> bool {
16552 if !has_ancestor_kind(declarator, "compound_statement") {
16553 return false;
16554 }
16555 if declarator
16556 .child_by_field_name("declarator")
16557 .is_none_or(|declarator| declarator.kind() != "identifier")
16558 {
16559 return false;
16560 }
16561 if !type_text
16562 .and_then(|text| visibility.resolve_type(file, text))
16563 .is_some_and(|unit| unit.is_class())
16564 {
16565 return false;
16566 }
16567 declarator
16568 .child_by_field_name("parameters")
16569 .is_some_and(|parameters| {
16570 constructor_parameters_look_like_expressions(parameters, source, bindings)
16571 })
16572}
16573
16574fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
16575 parameters: Node<'_>,
16576 source: &str,
16577 bindings: &LocalInferenceEngine<T>,
16578) -> bool {
16579 let mut cursor = parameters.walk();
16580 parameters.named_children(&mut cursor).any(|parameter| {
16581 !matches!(
16582 parameter.kind(),
16583 "parameter_declaration" | "optional_parameter_declaration"
16584 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
16585 })
16586}
16587
16588fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
16589 parameter: Node<'_>,
16590 source: &str,
16591 bindings: &LocalInferenceEngine<T>,
16592) -> bool {
16593 let text = node_text(parameter, source).trim();
16594 if text
16595 .chars()
16596 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
16597 && bindings.is_shadowed(text)
16598 {
16599 return true;
16600 }
16601
16602 let Some(base) = parameter
16603 .child_by_field_name("type")
16604 .filter(|base| base.kind() == "type_identifier")
16605 else {
16606 return false;
16607 };
16608 let Some(subscript) = parameter
16609 .child_by_field_name("declarator")
16610 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
16611 else {
16612 return false;
16613 };
16614 subscript.child_by_field_name("size").is_some()
16615 && bindings.is_shadowed(node_text(base, source).trim())
16616}
16617
16618pub fn is_declaration_name(node: Node<'_>) -> bool {
16619 let Some(parent) = node.parent() else {
16620 return false;
16621 };
16622 if parent
16623 .child_by_field_name("name")
16624 .is_some_and(|name| same_node(name, node))
16625 {
16626 if matches!(
16627 parent.kind(),
16628 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
16629 ) {
16630 return cpp_tag_specifier_declares_name(parent);
16631 }
16632 if matches!(
16633 parent.kind(),
16634 "namespace_definition"
16635 | "namespace_alias_definition"
16636 | "alias_declaration"
16637 | "enumerator"
16638 ) {
16639 return true;
16640 }
16641 }
16642
16643 let mut current = Some(parent);
16644 while let Some(ancestor) = current {
16645 let type_definition = ancestor.kind() == "type_definition";
16646 let mut child_cursor = ancestor.walk();
16647 if ancestor.named_children(&mut child_cursor).any(|child| {
16648 declaration_declarator(ancestor, child).is_some_and(|declarator| {
16649 declarator_name_path_contains(declarator, node, type_definition)
16650 })
16651 }) {
16652 return true;
16653 }
16654 if matches!(
16655 ancestor.kind(),
16656 "declaration"
16657 | "field_declaration"
16658 | "parameter_declaration"
16659 | "optional_parameter_declaration"
16660 | "function_definition"
16661 | "type_definition"
16662 | "alias_declaration"
16663 | "template_instantiation"
16664 | "class_specifier"
16665 | "struct_specifier"
16666 | "union_specifier"
16667 | "enum_specifier"
16668 ) {
16669 return false;
16670 }
16671 current = ancestor.parent();
16672 }
16673 false
16674}
16675
16676pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
16685 if !matches!(
16686 node.kind(),
16687 "qualified_identifier" | "scoped_type_identifier"
16688 ) {
16689 return false;
16690 }
16691 let Some(declaration) = node
16692 .parent()
16693 .filter(|parent| parent.kind() == "declaration")
16694 else {
16695 return false;
16696 };
16697 if declaration.child_by_field_name("declarator") != Some(node)
16698 || !declaration
16699 .child_by_field_name("type")
16700 .is_some_and(|friend| {
16701 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
16702 })
16703 {
16704 return false;
16705 }
16706 let mut cursor = declaration.walk();
16707 let mut errors = declaration
16708 .named_children(&mut cursor)
16709 .filter(|child| child.kind() == "ERROR");
16710 let Some(error) = errors.next() else {
16711 return false;
16712 };
16713 errors.next().is_none()
16714 && error.named_child_count() == 1
16715 && error.named_child(0).is_some_and(|class| {
16716 class.kind() == "identifier" && node_text(class, source) == "class"
16717 })
16718}
16719
16720pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
16721 if !matches!(node.kind(), "identifier" | "field_identifier") {
16722 return false;
16723 }
16724 if let Some(parent) = node.parent() {
16725 if parent.kind() == "call_expression"
16726 && parent.child_by_field_name("function") == Some(node)
16727 {
16728 return false;
16729 }
16730 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
16731 && parent.child_by_field_name("label") == Some(node)
16732 {
16733 return false;
16734 }
16735 }
16736 if is_declaration_name(node) {
16737 return false;
16738 }
16739 let mut current = node.parent();
16740 while let Some(ancestor) = current {
16741 match ancestor.kind() {
16742 "preproc_ifdef" | "preproc_ifndef" => {
16743 if ancestor
16744 .child_by_field_name("name")
16745 .is_some_and(|name| node_range_contains(name, node))
16746 {
16747 return false;
16748 }
16749 }
16750 "preproc_if" | "preproc_elif" => {
16751 if ancestor
16752 .child_by_field_name("condition")
16753 .is_some_and(|condition| node_range_contains(condition, node))
16754 {
16755 return false;
16756 }
16757 }
16758 "preproc_else" => {}
16759 kind if kind.starts_with("preproc_") => return false,
16760 _ => {}
16761 }
16762 if matches!(
16763 ancestor.kind(),
16764 "translation_unit" | "function_definition" | "compound_statement"
16765 ) {
16766 break;
16767 }
16768 current = ancestor.parent();
16769 }
16770 true
16771}
16772
16773fn recovered_c_reference_node(
16774 visibility: &VisibilityIndex<'_>,
16775 file: &ProjectFile,
16776 node: Node<'_>,
16777 source: &str,
16778) -> bool {
16779 if node.start_byte() >= node.end_byte()
16780 || node.is_error()
16781 || node.is_missing()
16782 || !matches!(
16783 node.kind(),
16784 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
16785 )
16786 || recovered_c_macro_binding_role(node)
16787 || recovered_c_label_role(node)
16788 {
16789 return false;
16790 }
16791 let name = node_text(node, source);
16799 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
16800 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
16801 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
16802 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
16803 if is_declaration_name(node)
16804 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
16805 && !recovered_function_call
16806 && !recovered_macro_call
16807 && !recovered_parenthesized_reference
16808 {
16809 return false;
16810 }
16811
16812 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
16813 return true;
16814 }
16815 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
16816 return true;
16817 }
16818 if recovered_parenthesized_reference {
16819 return true;
16820 }
16821 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
16822 if recovered_function_call {
16823 return true;
16824 }
16825 return visibility
16826 .visible_identifier_candidates(file, name)
16827 .any(|candidate| candidate.is_class() || candidate.is_module());
16828 }
16829 let visible = visibility
16830 .visible_identifier_candidates(file, name)
16831 .next()
16832 .is_some();
16833 visible
16834 && (recovered_c_reference_anchor(node)
16835 || recovered_c_error_expression_leaf(node)
16836 || recovered_function_call)
16837}
16838
16839fn push_recovered_c_range(
16840 ranges: &mut Vec<Range>,
16841 seen: &mut HashSet<(usize, usize)>,
16842 start_byte: usize,
16843 end_byte: usize,
16844 node: Node<'_>,
16845 limit: usize,
16846) -> bool {
16847 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
16848 return true;
16849 }
16850 if ranges.len() >= limit {
16851 return false;
16852 }
16853 ranges.push(Range {
16854 start_byte,
16855 end_byte,
16856 start_line: node.start_position().row,
16857 end_line: node.end_position().row,
16858 });
16859 true
16860}
16861
16862fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
16867 let mut current = node.parent();
16868 while let Some(parent) = current {
16869 if parent.is_error() {
16870 let Some(anchor) = parent.parent() else {
16871 return false;
16872 };
16873 return anchor.kind().ends_with("_expression")
16874 || matches!(
16875 anchor.kind(),
16876 "argument_list"
16877 | "return_statement"
16878 | "expression_statement"
16879 | "case_statement"
16880 | "initializer_list"
16881 | "field_designator"
16882 | "enumerator"
16883 );
16884 }
16885 if matches!(
16886 parent.kind(),
16887 "translation_unit" | "function_definition" | "compound_statement"
16888 ) {
16889 return false;
16890 }
16891 current = parent.parent();
16892 }
16893 false
16894}
16895
16896fn recovered_c_function_call(
16903 visibility: &VisibilityIndex<'_>,
16904 file: &ProjectFile,
16905 node: Node<'_>,
16906 name: &str,
16907) -> bool {
16908 if !matches!(
16909 node.kind(),
16910 "identifier" | "field_identifier" | "type_identifier"
16911 ) {
16912 return false;
16913 }
16914 let error_call_prefix = node.parent().is_some_and(|error| {
16919 error.is_error()
16920 && error
16921 .parent()
16922 .is_some_and(|parent| parent.kind() == "compound_statement")
16923 }) && node
16924 .prev_sibling()
16925 .is_none_or(|previous| previous.kind() == ";")
16926 && node.next_sibling().is_some_and(|open| {
16927 open.kind() == "("
16928 && open.next_named_sibling().is_some_and(|argument| {
16929 argument.kind() == "parameter_declaration" && !argument.has_error()
16930 })
16931 });
16932 (error_call_prefix || recovered_c_function_declarator_invocation(node))
16933 && visibility
16934 .visible_identifier_candidates(file, name)
16935 .any(CodeUnit::is_function)
16936}
16937
16938pub fn recovered_c_block_call_envelope(declarator: Node<'_>) -> Option<Node<'_>> {
16946 if declarator.kind() != "function_declarator" || !declarator.has_error() {
16947 return None;
16948 }
16949 let definition = declarator.parent().filter(|parent| {
16950 parent.kind() == "function_definition"
16951 && parent.child_by_field_name("declarator") == Some(declarator)
16952 })?;
16953 if definition.child_by_field_name("type").is_some()
16954 || definition.named_child(0) != Some(declarator)
16955 || definition.child_by_field_name("body").is_none()
16956 || definition.parent()?.kind() != "compound_statement"
16957 {
16958 return None;
16959 }
16960 Some(definition)
16961}
16962
16963fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
16973 let mut current = if node.parent().is_some_and(|parent| {
16978 parent.kind() == "macro_type_specifier" && parent.child_by_field_name("name") == Some(node)
16979 }) {
16980 node.parent().expect("checked macro_type_specifier parent")
16981 } else {
16982 let mut function_declarator = if node.parent().is_some_and(|parent| {
16983 parent.kind() == "function_declarator"
16984 && parent.child_by_field_name("declarator") == Some(node)
16985 }) {
16986 node.parent().expect("checked function declarator parent")
16987 } else {
16988 let Some(parameter) = node.parent().filter(|parent| {
16989 parent.kind() == "parameter_declaration"
16990 && parent.child_by_field_name("type") == Some(node)
16991 }) else {
16992 return false;
16993 };
16994 if !parameter
16995 .child_by_field_name("declarator")
16996 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
16997 {
16998 return false;
16999 }
17000 let Some(parameters) = parameter
17001 .parent()
17002 .filter(|parent| parent.kind() == "parameter_list")
17003 else {
17004 return false;
17005 };
17006 let Some(function_declarator) = parameters
17007 .parent()
17008 .filter(|parent| parent.kind() == "function_declarator")
17009 else {
17010 return false;
17011 };
17012 function_declarator
17013 };
17014
17015 while let Some(parent) = function_declarator.parent().filter(|parent| {
17018 parent.kind() == "function_declarator"
17019 && parent.child_by_field_name("declarator") == Some(function_declarator)
17020 }) {
17021 function_declarator = parent;
17022 }
17023 let Some(current) = function_declarator.parent().filter(|parent| {
17028 parent.is_error() || recovered_c_block_call_envelope(function_declarator).is_some()
17029 }) else {
17030 return false;
17031 };
17032 current
17033 };
17034 loop {
17035 let Some(parent) = current.parent() else {
17036 return false;
17037 };
17038 if matches!(
17039 parent.kind(),
17040 "translation_unit"
17041 | "compound_statement"
17042 | "preproc_if"
17043 | "preproc_ifdef"
17044 | "preproc_ifndef"
17045 | "preproc_else"
17046 | "preproc_elif"
17047 ) {
17048 return true;
17049 }
17050 if parent.kind() == "function_definition"
17051 && parent.child_by_field_name("declarator") == Some(current)
17052 && parent.named_child(0) == Some(current)
17053 && parent.child_by_field_name("body").is_some()
17054 {
17055 return true;
17056 }
17057 if parent.is_error()
17058 || matches!(
17059 parent.kind(),
17060 "parameter_declaration"
17061 | "parameter_list"
17062 | "function_declarator"
17063 | "abstract_function_declarator"
17064 | "parenthesized_declarator"
17065 )
17066 {
17067 current = parent;
17068 continue;
17069 }
17070 return false;
17071 }
17072}
17073
17074fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
17080 if node
17086 .parent()
17087 .is_some_and(|parent| parent.kind() == "type_descriptor")
17088 {
17089 let Some(type_descriptor) = node.parent() else {
17090 return false;
17091 };
17092 let Some(error) = type_descriptor.parent().filter(|parent| parent.is_error()) else {
17093 return false;
17094 };
17095 if error.named_child_count() != 1 || error.named_child(0) != Some(type_descriptor) {
17096 return false;
17097 }
17098 let Some(dependent) = error
17099 .parent()
17100 .and_then(|parent| parent.parent())
17101 .filter(|parent| parent.kind() == "dependent_type")
17102 else {
17103 return false;
17104 };
17105 return dependent
17106 .child(0)
17107 .is_some_and(|keyword| keyword.kind() == "typename");
17108 }
17109 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
17110 return false;
17111 };
17112 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
17113 return false;
17114 }
17115 let Some(declarator) = error
17116 .parent()
17117 .filter(|parent| parent.kind() == "parenthesized_declarator")
17118 else {
17119 return false;
17120 };
17121 let Some(declaration) = declarator
17122 .parent()
17123 .filter(|parent| parent.kind() == "declaration")
17124 else {
17125 return false;
17126 };
17127 if declaration.child_by_field_name("declarator") != Some(declarator) {
17128 return false;
17129 }
17130 let Some(type_node) = declaration.child_by_field_name("type") else {
17131 return false;
17132 };
17133 type_node.kind() == "dependent_type"
17134 && type_node
17135 .child(0)
17136 .is_some_and(|keyword| keyword.kind() == "typename")
17137}
17138
17139fn recovered_c_explicit_assignment_callee(
17140 visibility: &VisibilityIndex<'_>,
17141 file: &ProjectFile,
17142 node: Node<'_>,
17143 name: &str,
17144) -> bool {
17145 let mut current = node;
17146 let error = loop {
17147 let Some(parent) = current.parent() else {
17148 return false;
17149 };
17150 if parent.is_error() {
17151 break parent;
17152 }
17153 current = parent;
17154 };
17155 let mut cursor = error.walk();
17156 let explicit_recovery_precedes_callee = error
17157 .named_children(&mut cursor)
17158 .take_while(|child| child.start_byte() < node.start_byte())
17159 .any(|child| child.kind() == "explicit_function_specifier");
17160 if !explicit_recovery_precedes_callee {
17161 return false;
17162 }
17163 visibility
17164 .visible_identifier_candidates(file, name)
17165 .any(CodeUnit::is_function)
17166}
17167
17168fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
17169 while let Some(parent) = node.parent() {
17170 if matches!(
17171 parent.kind(),
17172 "preproc_def" | "preproc_function_def" | "preproc_params"
17173 ) {
17174 return true;
17175 }
17176 if parent.is_error()
17177 || matches!(
17178 parent.kind(),
17179 "translation_unit" | "function_definition" | "compound_statement"
17180 )
17181 {
17182 return false;
17183 }
17184 node = parent;
17185 }
17186 false
17187}
17188
17189fn recovered_c_label_role(node: Node<'_>) -> bool {
17190 node.parent().is_some_and(|parent| {
17191 matches!(parent.kind(), "labeled_statement" | "goto_statement")
17192 && parent.child_by_field_name("label") == Some(node)
17193 })
17194}
17195
17196fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
17197 while let Some(parent) = node.parent() {
17198 if parent.is_error() {
17199 return false;
17200 }
17201 if parent.kind() == "optional_parameter_declaration"
17206 && parent
17207 .child_by_field_name("default_value")
17208 .is_some_and(|value| node_range_contains(value, node))
17209 {
17210 return true;
17211 }
17212 if parent.kind().ends_with("_expression")
17213 || matches!(
17214 parent.kind(),
17215 "argument_list"
17216 | "return_statement"
17217 | "expression_statement"
17218 | "case_statement"
17219 | "initializer_list"
17220 | "init_declarator"
17221 | "array_declarator"
17222 | "field_designator"
17223 | "enumerator"
17224 )
17225 {
17226 return true;
17227 }
17228 if matches!(
17229 parent.kind(),
17230 "translation_unit"
17231 | "function_definition"
17232 | "compound_statement"
17233 | "declaration"
17234 | "field_declaration"
17235 | "parameter_declaration"
17236 ) {
17237 return false;
17238 }
17239 node = parent;
17240 }
17241 false
17242}
17243
17244pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
17252 let mut current = parameter.parent();
17253 while let Some(ancestor) = current {
17254 if ancestor.kind() == "lambda_expression" {
17255 return ancestor
17256 .child_by_field_name("declarator")
17257 .is_some_and(|declarator| {
17258 declarator.start_byte() <= parameter.start_byte()
17259 && parameter.end_byte() <= declarator.end_byte()
17260 });
17261 }
17262 if ancestor.kind() == "function_definition" {
17263 return ancestor
17264 .child_by_field_name("declarator")
17265 .is_some_and(|declarator| {
17266 declarator.start_byte() <= parameter.start_byte()
17267 && parameter.end_byte() <= declarator.end_byte()
17268 });
17269 }
17270 current = ancestor.parent();
17271 }
17272 false
17273}
17274
17275pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
17276 let mut current = node.parent();
17277 while let Some(ancestor) = current {
17278 if matches!(
17279 ancestor.kind(),
17280 "parameter_declaration" | "optional_parameter_declaration"
17281 ) {
17282 return crate::declarations::cpp_type_field_contains_node(ancestor, node);
17283 }
17284 if matches!(
17285 ancestor.kind(),
17286 "function_definition" | "lambda_expression" | "compound_statement"
17287 ) {
17288 return false;
17289 }
17290 current = ancestor.parent();
17291 }
17292 false
17293}
17294
17295pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
17299 specifier: Node<'_>,
17300) -> impl Iterator<Item = Node<'_>> {
17301 std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
17302 !matches!(
17303 ancestor.kind(),
17304 "type_descriptor"
17305 | "parameter_declaration"
17306 | "optional_parameter_declaration"
17307 | "template_argument_list"
17308 | "cast_expression"
17309 )
17310 })
17311}
17312
17313pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
17314 if specifier.child_by_field_name("body").is_some() {
17315 return true;
17316 }
17317 for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
17318 match ancestor.kind() {
17319 "declaration" | "field_declaration" => {
17320 let mut cursor = ancestor.walk();
17321 return ancestor
17322 .children_by_field_name("declarator", &mut cursor)
17323 .next()
17324 .is_none();
17325 }
17326 "translation_unit" => return true,
17327 _ => {}
17328 }
17329 }
17330 false
17331}
17332
17333pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
17334 match node.kind() {
17335 "identifier"
17336 | "field_identifier"
17337 | "qualified_identifier"
17338 | "scoped_identifier"
17339 | "operator_name"
17340 | "destructor_name"
17341 | "literal_operator_name" => Some(node),
17342 "reference_declarator" | "parenthesized_declarator" => {
17343 node.named_child(0).and_then(declarator_name_node)
17344 }
17345 _ => node
17346 .child_by_field_name("declarator")
17347 .or_else(|| node.child_by_field_name("name"))
17348 .or_else(|| node.child_by_field_name("field"))
17349 .and_then(declarator_name_node),
17350 }
17351}
17352
17353pub fn declarator_path_contains_before_name(
17355 declarator: Node<'_>,
17356 name: Node<'_>,
17357 candidate: Node<'_>,
17358) -> bool {
17359 let mut current = Some(declarator);
17360 while let Some(node) = current {
17361 if same_node(node, candidate) {
17362 return true;
17363 }
17364 if same_node(node, name) {
17365 return false;
17366 }
17367 current = node
17368 .child_by_field_name("declarator")
17369 .or_else(|| node.child_by_field_name("name"))
17370 .or_else(|| node.child_by_field_name("field"));
17371 }
17372 false
17373}
17374
17375fn declarator_name_path_contains(
17376 declarator: Node<'_>,
17377 candidate: Node<'_>,
17378 allow_type_identifier: bool,
17379) -> bool {
17380 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
17381 return false;
17382 };
17383 declarator_path_contains_before_name(declarator, name, candidate)
17384}
17385
17386fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
17387 match node.kind() {
17388 "identifier"
17389 | "field_identifier"
17390 | "operator_name"
17391 | "destructor_name"
17392 | "literal_operator_name" => Some(node),
17393 "type_identifier" if allow_type_identifier => Some(node),
17394 _ => node
17395 .child_by_field_name("declarator")
17396 .or_else(|| node.child_by_field_name("name"))
17397 .or_else(|| node.child_by_field_name("field"))
17398 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
17399 }
17400}
17401
17402pub fn is_nested_type_node(node: Node<'_>) -> bool {
17405 node.parent().is_some_and(|parent| {
17406 matches!(
17407 parent.kind(),
17408 "qualified_identifier" | "scoped_type_identifier" | "template_type"
17409 )
17410 })
17411}
17412
17413pub struct OutOfLineMemberDefinitionOwners<'tree> {
17414 pub owners: Vec<(Node<'tree>, CodeUnit)>,
17415 innermost: Option<(Node<'tree>, CodeUnit)>,
17416}
17417
17418impl OutOfLineMemberDefinitionOwners<'_> {
17419 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
17420 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
17421 }
17422}
17423
17424pub struct QualifiedOwnerComponents<'tree> {
17425 pub nodes: Vec<Node<'tree>>,
17426 pub names: Vec<String>,
17427 pub global: bool,
17428}
17429
17430pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
17435 let mut stack = vec![node];
17436 let mut found_separator = false;
17437 while let Some(current) = stack.pop() {
17438 if !matches!(
17439 current.kind(),
17440 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17441 ) {
17442 continue;
17443 }
17444 let mut current_has_separator = false;
17445 for child in children_iter(current) {
17446 if child.kind() == "::" {
17447 if child.is_missing() {
17448 return false;
17449 }
17450 current_has_separator = true;
17451 found_separator = true;
17452 }
17453 }
17454 if !current_has_separator {
17455 return false;
17456 }
17457 for field in ["scope", "name"] {
17458 if let Some(child) = current.child_by_field_name(field)
17459 && matches!(
17460 child.kind(),
17461 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17462 )
17463 {
17464 stack.push(child);
17465 }
17466 }
17467 }
17468 found_separator
17469}
17470
17471pub fn qualified_owner_components<'tree>(
17472 node: Node<'tree>,
17473 source: &str,
17474) -> Option<QualifiedOwnerComponents<'tree>> {
17475 if !qualified_name_has_concrete_scope_separators(node) {
17476 return None;
17477 }
17478 let mut nodes = cpp_name_component_nodes(node)?;
17479 nodes.pop()?;
17480 if nodes.is_empty() {
17481 return None;
17482 }
17483 let names = nodes
17484 .iter()
17485 .map(|component| node_text(*component, source).to_string())
17486 .collect();
17487 Some(QualifiedOwnerComponents {
17488 nodes,
17489 names,
17490 global: is_globally_qualified_cpp_name(node),
17491 })
17492}
17493
17494pub fn out_of_line_member_definition_owner<'tree>(
17495 analyzer: &CppGraphSource<'_>,
17496 visibility: &VisibilityIndex<'_>,
17497 file: &ProjectFile,
17498 source: &str,
17499 node: Node<'tree>,
17500) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
17501 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
17502 || !has_ancestor_kind(node, "function_definition")
17503 || !is_function_declarator_name_root(node)
17504 {
17505 return None;
17506 }
17507 let qualified = qualified_owner_components(node, source)?;
17508 let lexical_scope = enclosing_namespace_components(node, source)?;
17509 let mut owners = Vec::new();
17510 let mut innermost = None;
17511
17512 for component_count in 1..=qualified.names.len() {
17513 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
17514 .resolve_type_components_lexically(
17515 analyzer,
17516 file,
17517 &qualified.names[..component_count],
17518 qualified.global,
17519 &lexical_scope,
17520 )
17521 && !owners
17522 .iter()
17523 .any(|(_, existing)| same_visible_symbol(existing, &unit))
17524 {
17525 if component_count == qualified.names.len() {
17526 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
17527 }
17528 owners.push((qualified.nodes[component_count - 1], unit));
17529 }
17530 }
17531
17532 if innermost.is_none() {
17542 let indexed_owner_components = visibility
17543 .indexed_enclosing_owner_scope(analyzer, file, node)
17544 .or_else(|| {
17545 if qualified.names.len() <= 1 {
17550 return None;
17551 }
17552 let range = Range {
17553 start_byte: node.start_byte(),
17554 end_byte: node.end_byte(),
17555 start_line: node.start_position().row,
17556 end_line: node.end_position().row,
17557 };
17558 let start = analyzer.enclosing_code_unit(file, &range)?;
17559 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17560 brokk_bifrost_core::analyzer::Language::Cpp,
17561 &cpp_name_for(&start),
17562 );
17563 components.pop();
17564 Some(components)
17565 });
17566 if let Some(indexed_owner_components) = indexed_owner_components
17567 && indexed_owner_components.len() > qualified.names.len()
17568 && indexed_owner_components.ends_with(&qualified.names)
17569 && indexed_namespace_path_is_recoverable(
17570 &lexical_scope,
17571 &indexed_owner_components,
17572 qualified.names.len(),
17573 )
17574 && (qualified.names.len() > 1 || !qualified.global)
17579 {
17580 let namespace_count = indexed_owner_components.len() - qualified.names.len();
17581 for component_count in 1..=qualified.names.len() {
17582 let expected = &indexed_owner_components[..namespace_count + component_count];
17583 let owner_node = qualified.nodes[component_count - 1];
17584 for owner in visibility
17585 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
17586 .filter(|candidate| candidate.is_class())
17587 .filter(|candidate| {
17588 canonical_cpp_scope_components(candidate) == expected
17589 && visibility.external_type_candidate_visible_in_context(
17590 analyzer, file, candidate, node,
17591 )
17592 })
17593 {
17594 if component_count == qualified.names.len() && innermost.is_none() {
17595 innermost = Some((owner_node, owner.clone()));
17596 }
17597 if !owners
17598 .iter()
17599 .any(|(_, existing)| same_symbol(existing, owner))
17600 {
17601 owners.push((owner_node, owner.clone()));
17602 }
17603 }
17604 }
17605 }
17606 }
17607 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
17608}
17609
17610fn is_function_declarator_name_root(node: Node<'_>) -> bool {
17611 let mut current = node;
17612 while let Some(parent) = current.parent() {
17613 if parent.kind() == "function_declarator" {
17614 return parent.child_by_field_name("declarator") == Some(current);
17615 }
17616 if matches!(
17617 parent.kind(),
17618 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
17619 ) && parent.child_by_field_name("declarator") == Some(current)
17620 {
17621 current = parent;
17622 continue;
17623 }
17624 return false;
17625 }
17626 false
17627}
17628
17629pub fn append_cpp_name_components(
17630 node: Node<'_>,
17631 source: &str,
17632 out: &mut Vec<String>,
17633) -> Option<()> {
17634 out.extend(
17635 cpp_name_component_nodes(node)?
17636 .into_iter()
17637 .map(|component| node_text(component, source).to_string()),
17638 );
17639 Some(())
17640}
17641
17642fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
17645 cpp_name_component_nodes(node).map(|components| {
17646 components
17647 .into_iter()
17648 .map(|component| {
17649 (
17650 node_text(component, source).to_string(),
17651 node_range(component),
17652 )
17653 })
17654 .collect()
17655 })
17656}
17657
17658pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17659 let mut components = Vec::new();
17660 append_cpp_name_components(node, source, &mut components)?;
17661 Some(components)
17662}
17663
17664pub fn unique_macro_replacement_type_candidate(
17673 analyzer: &CppGraphSource<'_>,
17674 visibility: &VisibilityIndex<'_>,
17675 file: &ProjectFile,
17676 components: &[String],
17677) -> Option<CodeUnit> {
17678 let terminal = components.last()?;
17679 let mut candidates = Vec::new();
17680 for candidate in visibility
17681 .visible_identifier_candidates(file, terminal)
17682 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
17683 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
17684 {
17685 if !candidates
17686 .iter()
17687 .any(|existing| same_logical_symbol(existing, candidate))
17688 {
17689 candidates.push(candidate.clone());
17690 }
17691 }
17692 (candidates.len() == 1).then(|| candidates.remove(0))
17693}
17694
17695pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
17699 qualified == scope
17700 || qualified
17701 .strip_suffix(scope)
17702 .is_some_and(|prefix| prefix.ends_with("::"))
17703}
17704
17705pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
17710 let Some(type_descriptor) = node.parent() else {
17711 return false;
17712 };
17713 if type_descriptor.kind() != "type_descriptor"
17714 || type_descriptor.child_by_field_name("type") != Some(node)
17715 {
17716 return false;
17717 }
17718 let Some(arguments) = type_descriptor.parent() else {
17719 return false;
17720 };
17721 if arguments.kind() != "template_argument_list" {
17722 return false;
17723 }
17724 arguments.parent().is_some_and(|parent| {
17725 matches!(parent.kind(), "template_type" | "template_function")
17726 && parent.child_by_field_name("arguments") == Some(arguments)
17727 })
17728}
17729
17730pub fn cpp_template_reference_arguments(
17731 mut node: Node<'_>,
17732 source: &str,
17733) -> Option<Vec<CppTemplateExpression>> {
17734 loop {
17735 match node.kind() {
17736 "template_type" | "template_function" => {
17737 let arguments = node.child_by_field_name("arguments")?;
17738 let mut cursor = arguments.walk();
17739 return Some(
17740 arguments
17741 .named_children(&mut cursor)
17742 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
17743 .map(|argument| CppTemplateExpression {
17744 text: normalize_cpp_whitespace(node_text(argument, source)),
17745 term: cpp_template_term(
17747 argument,
17748 source,
17749 &[],
17750 &ParentIndex::unindexed(),
17751 ),
17752 })
17753 .collect(),
17754 );
17755 }
17756 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
17757 node = node
17758 .child_by_field_name("name")
17759 .or_else(|| node.child_by_field_name("type"))?;
17760 }
17761 _ => return None,
17762 }
17763 }
17764}
17765
17766fn cpp_reconcile_primary_template_parameters(
17767 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
17768 preferred: &CodeUnit,
17769) -> Option<Vec<CppTemplateParameterMetadata>> {
17770 let canonical = candidates
17771 .iter()
17772 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
17773 let mut merged = canonical
17774 .parameters
17775 .iter()
17776 .map(|parameter| CppTemplateParameterMetadata {
17777 name: parameter.name.clone(),
17778 kind: parameter.kind,
17779 variadic: parameter.variadic,
17780 default: None,
17781 })
17782 .collect::<Vec<_>>();
17783
17784 for (_, metadata) in candidates {
17785 if metadata.parameters.len() != merged.len() {
17786 return None;
17787 }
17788 let rename_bindings = metadata
17789 .parameters
17790 .iter()
17791 .zip(&merged)
17792 .map(|(parameter, canonical)| {
17793 (
17794 parameter.name.clone(),
17795 CppTemplateTerm::Parameter(canonical.name.clone()),
17796 )
17797 })
17798 .collect::<HashMap<_, _>>();
17799 for ((parameter, canonical), merged_parameter) in metadata
17800 .parameters
17801 .iter()
17802 .zip(&canonical.parameters)
17803 .zip(&mut merged)
17804 {
17805 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
17806 return None;
17807 }
17808 let Some(default) = ¶meter.default else {
17809 continue;
17810 };
17811 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
17812 if let Some(existing) = &merged_parameter.default {
17813 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
17814 return None;
17815 }
17816 } else {
17817 merged_parameter.default = Some(CppTemplateExpression {
17818 text: default.text.clone(),
17819 term: normalized_term,
17820 });
17821 }
17822 }
17823 }
17824 Some(merged)
17825}
17826
17827pub fn cpp_bind_template_arguments(
17828 parameters: &[CppTemplateParameterMetadata],
17829 explicit_arguments: &[CppTemplateExpression],
17830) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
17831 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
17832 if variadic_index.is_some_and(|index| {
17833 index + 1 != parameters.len()
17834 || parameters[index + 1..]
17835 .iter()
17836 .any(|parameter| parameter.variadic)
17837 }) {
17838 return None;
17839 }
17840 let fixed_count = variadic_index.unwrap_or(parameters.len());
17841 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
17842 return None;
17843 }
17844 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
17845 let mut expanded = explicit_arguments[..explicit_fixed_count]
17846 .iter()
17847 .map(cpp_clone_template_expression_iterative)
17848 .collect::<Vec<_>>();
17849 let mut bindings = HashMap::default();
17850 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
17851 bindings.insert(
17852 parameter.name.clone(),
17853 cpp_clone_template_term_iterative(&argument.term),
17854 );
17855 }
17856 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
17857 let default = parameter.default.as_ref()?;
17858 let term = cpp_substitute_template_term(&default.term, &bindings)?;
17859 bindings.insert(parameter.name.clone(), term.clone());
17860 expanded.push(CppTemplateExpression {
17861 text: default.text.clone(),
17862 term,
17863 });
17864 }
17865 if let Some(index) = variadic_index {
17866 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
17867 expanded.extend(
17868 packed_arguments
17869 .iter()
17870 .map(cpp_clone_template_expression_iterative),
17871 );
17872 bindings.insert(
17873 parameters[index].name.clone(),
17874 CppTemplateTerm::Node {
17875 kind: "parameter_pack".to_string(),
17876 children: packed_arguments
17877 .iter()
17878 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
17879 .collect(),
17880 },
17881 );
17882 }
17883 Some((expanded, bindings))
17884}
17885
17886fn cpp_specialization_matches(
17887 metadata: &CppTemplateMetadata,
17888 arguments: &[CppTemplateExpression],
17889) -> bool {
17890 cpp_specialization_terms_match(
17891 &metadata.parameters,
17892 &metadata.specialization_arguments,
17893 arguments,
17894 )
17895}
17896
17897fn cpp_specialization_terms_match(
17898 parameters: &[CppTemplateParameterMetadata],
17899 patterns: &[CppTemplateExpression],
17900 arguments: &[CppTemplateExpression],
17901) -> bool {
17902 if patterns.len() != arguments.len() {
17903 return false;
17904 }
17905 let parameter_names = parameters
17906 .iter()
17907 .map(|parameter| parameter.name.as_str())
17908 .collect::<HashSet<_>>();
17909 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
17910 patterns.iter().zip(arguments).all(|(pattern, argument)| {
17911 cpp_unify_template_term(
17912 &pattern.term,
17913 &argument.term,
17914 ¶meter_names,
17915 &mut bindings,
17916 )
17917 })
17918}
17919
17920fn cpp_specialization_more_specialized(
17921 candidate: &CppTemplateMetadata,
17922 other: &CppTemplateMetadata,
17923) -> bool {
17924 cpp_specialization_pattern_accepts(other, candidate)
17925 && !cpp_specialization_pattern_accepts(candidate, other)
17926}
17927
17928fn cpp_specialization_pattern_accepts(
17929 broader: &CppTemplateMetadata,
17930 narrower: &CppTemplateMetadata,
17931) -> bool {
17932 cpp_specialization_terms_match(
17933 &broader.parameters,
17934 &broader.specialization_arguments,
17935 &narrower.specialization_arguments,
17936 )
17937}
17938
17939pub fn cpp_substitute_template_term(
17940 term: &CppTemplateTerm,
17941 bindings: &HashMap<String, CppTemplateTerm>,
17942) -> Option<CppTemplateTerm> {
17943 enum Work<'a> {
17944 Visit(&'a CppTemplateTerm),
17945 Build { kind: String, child_count: usize },
17946 }
17947
17948 let mut work = vec![Work::Visit(term)];
17949 let mut substituted = Vec::new();
17950 while let Some(next) = work.pop() {
17951 match next {
17952 Work::Visit(CppTemplateTerm::Parameter(name)) => {
17953 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
17954 }
17955 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17956 substituted.push(CppTemplateTerm::Atom {
17957 kind: kind.clone(),
17958 text: text.clone(),
17959 });
17960 }
17961 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17962 work.push(Work::Build {
17963 kind: kind.clone(),
17964 child_count: children.len(),
17965 });
17966 work.extend(children.iter().rev().map(Work::Visit));
17967 }
17968 Work::Build { kind, child_count } => {
17969 let children = substituted.split_off(substituted.len() - child_count);
17970 substituted.push(CppTemplateTerm::Node { kind, children });
17971 }
17972 }
17973 }
17974 substituted.pop()
17975}
17976
17977pub fn cpp_substitute_template_arguments(
17978 arguments: &[CppTemplateExpression],
17979 bindings: &HashMap<String, CppTemplateTerm>,
17980) -> Option<Vec<CppTemplateExpression>> {
17981 let mut substituted = Vec::new();
17982 for argument in arguments {
17983 let CppTemplateTerm::Node { kind, children } = &argument.term else {
17984 substituted.push(CppTemplateExpression {
17985 text: argument.text.clone(),
17986 term: cpp_substitute_template_term(&argument.term, bindings)?,
17987 });
17988 continue;
17989 };
17990 if kind != "parameter_pack_expansion" {
17991 substituted.push(CppTemplateExpression {
17992 text: argument.text.clone(),
17993 term: cpp_substitute_template_term(&argument.term, bindings)?,
17994 });
17995 continue;
17996 }
17997 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
17998 return None;
17999 };
18000 if ellipsis != "..." {
18001 return None;
18002 }
18003
18004 let mut pack_names = Vec::new();
18005 let mut work = vec![pattern];
18006 while let Some(term) = work.pop() {
18007 match term {
18008 CppTemplateTerm::Parameter(name)
18009 if matches!(
18010 bindings.get(name),
18011 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
18012 ) =>
18013 {
18014 if !pack_names.contains(name) {
18015 pack_names.push(name.clone());
18016 }
18017 }
18018 CppTemplateTerm::Node { children, .. } => work.extend(children),
18019 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
18020 }
18021 }
18022 let first_pack = pack_names.first()?;
18023 let CppTemplateTerm::Node {
18024 children: first_elements,
18025 ..
18026 } = bindings.get(first_pack)?
18027 else {
18028 return None;
18029 };
18030 let pack_len = first_elements.len();
18031 for pack_name in &pack_names {
18032 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
18033 return None;
18034 };
18035 if children.len() != pack_len {
18036 return None;
18037 }
18038 }
18039 for index in 0..pack_len {
18040 let mut element_bindings = bindings.clone();
18041 for pack_name in &pack_names {
18042 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
18043 return None;
18044 };
18045 element_bindings.insert(
18046 pack_name.clone(),
18047 cpp_clone_template_term_iterative(&children[index]),
18048 );
18049 }
18050 substituted.push(CppTemplateExpression {
18051 text: argument.text.clone(),
18052 term: cpp_substitute_template_term(pattern, &element_bindings)?,
18053 });
18054 }
18055 }
18056 Some(substituted)
18057}
18058
18059fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
18060 enum Work<'a> {
18061 Visit(&'a CppTemplateTerm),
18062 Build { kind: String, child_count: usize },
18063 }
18064
18065 let mut work = vec![Work::Visit(term)];
18066 let mut cloned = Vec::new();
18067 while let Some(next) = work.pop() {
18068 match next {
18069 Work::Visit(CppTemplateTerm::Parameter(name)) => {
18070 cloned.push(CppTemplateTerm::Parameter(name.clone()));
18071 }
18072 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
18073 cloned.push(CppTemplateTerm::Atom {
18074 kind: kind.clone(),
18075 text: text.clone(),
18076 });
18077 }
18078 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
18079 work.push(Work::Build {
18080 kind: kind.clone(),
18081 child_count: children.len(),
18082 });
18083 work.extend(children.iter().rev().map(Work::Visit));
18084 }
18085 Work::Build { kind, child_count } => {
18086 let children = cloned.split_off(cloned.len() - child_count);
18087 cloned.push(CppTemplateTerm::Node { kind, children });
18088 }
18089 }
18090 }
18091 cloned
18092 .pop()
18093 .expect("template term traversal emits one root")
18094}
18095
18096fn cpp_clone_template_expression_iterative(
18097 expression: &CppTemplateExpression,
18098) -> CppTemplateExpression {
18099 CppTemplateExpression {
18100 text: expression.text.clone(),
18101 term: cpp_clone_template_term_iterative(&expression.term),
18102 }
18103}
18104
18105pub fn cpp_unify_template_term(
18106 pattern: &CppTemplateTerm,
18107 argument: &CppTemplateTerm,
18108 parameters: &HashSet<&str>,
18109 bindings: &mut HashMap<String, CppTemplateTerm>,
18110) -> bool {
18111 let mut work = vec![(pattern, argument)];
18112 while let Some((pattern, argument)) = work.pop() {
18113 match pattern {
18114 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
18115 if let Some(bound) = bindings.get(name) {
18116 if !cpp_template_terms_equal(bound, argument) {
18117 return false;
18118 }
18119 } else {
18120 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
18121 }
18122 }
18123 CppTemplateTerm::Atom {
18124 kind: pattern_kind,
18125 text: pattern_text,
18126 } => {
18127 if !matches!(
18128 argument,
18129 CppTemplateTerm::Atom { kind, text }
18130 if kind == pattern_kind && text == pattern_text
18131 ) {
18132 return false;
18133 }
18134 }
18135 CppTemplateTerm::Node {
18136 kind: pattern_kind,
18137 children: pattern_children,
18138 } => {
18139 let CppTemplateTerm::Node { kind, children } = argument else {
18140 return false;
18141 };
18142 if kind != pattern_kind || children.len() != pattern_children.len() {
18143 return false;
18144 }
18145 work.extend(pattern_children.iter().zip(children).rev());
18146 }
18147 CppTemplateTerm::Parameter(_) => return false,
18148 }
18149 }
18150 true
18151}
18152
18153pub(crate) fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
18154 let mut work = vec![(left, right)];
18155 while let Some((left, right)) = work.pop() {
18156 match (left, right) {
18157 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
18158 if left != right {
18159 return false;
18160 }
18161 }
18162 (
18163 CppTemplateTerm::Atom {
18164 kind: left_kind,
18165 text: left_text,
18166 },
18167 CppTemplateTerm::Atom {
18168 kind: right_kind,
18169 text: right_text,
18170 },
18171 ) => {
18172 if left_kind != right_kind || left_text != right_text {
18173 return false;
18174 }
18175 }
18176 (
18177 CppTemplateTerm::Node {
18178 kind: left_kind,
18179 children: left_children,
18180 },
18181 CppTemplateTerm::Node {
18182 kind: right_kind,
18183 children: right_children,
18184 },
18185 ) => {
18186 if left_kind != right_kind || left_children.len() != right_children.len() {
18187 return false;
18188 }
18189 work.extend(left_children.iter().zip(right_children).rev());
18190 }
18191 _ => return false,
18192 }
18193 }
18194 true
18195}
18196
18197pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
18198 let mut components = Vec::new();
18199 let mut stack = vec![node];
18200 while let Some(current) = stack.pop() {
18201 match current.kind() {
18202 "identifier"
18203 | "field_identifier"
18204 | "namespace_identifier"
18205 | "type_identifier"
18206 | "operator_name"
18207 | "destructor_name" => components.push(current),
18208 "template_type" | "template_function" => {
18209 stack.push(current.child_by_field_name("name")?);
18210 }
18211 "dependent_name" => stack.push(current.named_child(0)?),
18212 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
18213 stack.push(current.child_by_field_name("name")?);
18214 if let Some(scope) = current.child_by_field_name("scope") {
18215 stack.push(scope);
18216 }
18217 }
18218 "nested_namespace_specifier" => {
18219 for index in (0..current.named_child_count()).rev() {
18220 stack.push(current.named_child(index)?);
18221 }
18222 }
18223 _ => return None,
18224 }
18225 }
18226 Some(components)
18227}
18228
18229pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
18230 node.child_by_field_name("scope").is_none()
18231 && node.child(0).is_some_and(|child| child.kind() == "::")
18232}
18233
18234fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
18235 let mut namespaces = Vec::new();
18236 for parent in enclosing_cpp_namespace_nodes(node) {
18237 if let Some(name) = parent.child_by_field_name("name") {
18238 let mut components = Vec::new();
18239 append_cpp_name_components(name, source, &mut components)?;
18240 namespaces.push(components);
18241 }
18242 }
18243 Some(namespaces.into_iter().flatten().collect())
18244}
18245
18246fn indexed_namespace_path_is_recoverable(
18257 lexical_scope: &[String],
18258 indexed_owner_scope: &[String],
18259 explicit_owner_component_count: usize,
18260) -> bool {
18261 if lexical_scope.is_empty() {
18262 return explicit_owner_component_count > 1;
18263 }
18264 if lexical_scope.len() >= indexed_owner_scope.len() {
18265 return false;
18266 }
18267 super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
18268}
18269
18270pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
18271
18272pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
18278 let mut current = Some(node);
18279 while let Some(candidate) = current {
18280 if candidate.kind() == "declaration" {
18281 if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
18282 return false;
18283 }
18284 let mut cursor = candidate.walk();
18285 return candidate.named_children(&mut cursor).any(|child| {
18286 child.kind() == "init_declarator"
18287 && child
18288 .child_by_field_name("value")
18289 .is_some_and(|value| value.kind() == "cast_expression")
18290 });
18291 }
18292 current = candidate.parent();
18293 }
18294 false
18295}
18296
18297#[derive(Clone, Copy, PartialEq, Eq)]
18298pub(crate) enum QualifiedAliasReferenceKind {
18299 Ordinary,
18300 ConstructorWithExpressionArgument,
18301 ExhaustiveTemplate,
18302}
18303
18304pub(crate) fn qualified_alias_reference_preserves_target(
18311 node: Node<'_>,
18312 target: &CodeUnit,
18313 analyzer: &CppGraphSource<'_>,
18314 visibility: &VisibilityIndex<'_>,
18315 file: &ProjectFile,
18316 source: &str,
18317) -> Option<QualifiedAliasReferenceKind> {
18318 if !matches!(
18319 node.kind(),
18320 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18321 ) {
18322 return None;
18323 }
18324 let components = cpp_type_name_components(node, source)?;
18325 let name = components.last()?;
18326 analyzer.type_alias_provider().and_then(|provider| {
18327 visibility
18328 .visible_identifier_candidates(file, name)
18329 .find_map(|candidate| {
18330 let proof = provider.is_type_alias(candidate)
18331 && canonical_cpp_scope_components(candidate) == components
18332 && visibility.external_type_candidate_visible_in_context(
18333 analyzer, file, candidate, node,
18334 )
18335 && match cpp_template_reference_arguments(node, source) {
18336 Some(arguments) => visibility.template_alias_arguments_preserve_target(
18337 analyzer, file, candidate, &arguments, target,
18338 ),
18339 None => visibility.structured_alias_primary_preserves_target(
18340 analyzer, file, candidate, target,
18341 ),
18342 };
18343 proof.then(|| {
18344 if cpp_template_reference_arguments(node, source).is_some()
18345 && visibility.is_exhaustive_same_fqn_type_declaration_family(
18346 analyzer, file, candidate,
18347 )
18348 {
18349 QualifiedAliasReferenceKind::ExhaustiveTemplate
18350 } else if qualified_alias_constructor_has_expression_argument(node)
18351 || qualified_alias_local_constructor_declaration(node)
18352 {
18353 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
18354 } else {
18355 QualifiedAliasReferenceKind::Ordinary
18356 }
18357 })
18358 })
18359 })
18360}
18361
18362pub(crate) fn qualified_alias_reference_requires_terminal(
18363 reference: Option<QualifiedAliasReferenceKind>,
18364) -> bool {
18365 matches!(
18366 reference,
18367 Some(
18368 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
18369 | QualifiedAliasReferenceKind::ExhaustiveTemplate
18370 )
18371 )
18372}
18373
18374fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
18375 let Some(declaration) = node.parent().filter(|parent| {
18376 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
18377 }) else {
18378 return false;
18379 };
18380 let mut cursor = declaration.walk();
18381 declaration.named_children(&mut cursor).any(|child| {
18382 child.kind() == "init_declarator"
18383 && child
18384 .child_by_field_name("value")
18385 .filter(|value| value.kind() == "argument_list")
18386 .is_some_and(|arguments| {
18387 let mut cursor = arguments.walk();
18388 arguments.named_children(&mut cursor).any(|argument| {
18389 let is_parameter = matches!(
18390 argument.kind(),
18391 "parameter_declaration" | "optional_parameter_declaration"
18392 );
18393 if is_parameter {
18394 argument
18395 .child_by_field_name("type")
18396 .is_some_and(|type_node| {
18397 type_node.kind() == "type_identifier"
18398 && argument.child_by_field_name("declarator").is_none()
18399 })
18400 } else {
18401 !argument.kind().ends_with("_literal")
18402 && !matches!(argument.kind(), "true" | "false" | "nullptr")
18403 }
18404 })
18405 })
18406 })
18407}
18408
18409fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
18414 let Some(declaration) = node.parent().filter(|parent| {
18415 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
18416 }) else {
18417 return false;
18418 };
18419 if declaration
18420 .parent()
18421 .is_none_or(|parent| parent.kind() != "compound_statement")
18422 {
18423 return false;
18424 }
18425 let mut cursor = declaration.walk();
18426 declaration
18427 .named_children(&mut cursor)
18428 .any(|child| child.kind() == "function_declarator")
18429}
18430
18431pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
18437 loop {
18438 let next = match node.kind() {
18439 "qualified_identifier"
18440 | "scoped_identifier"
18441 | "template_method"
18442 | "template_function"
18443 | "template_type" => node.child_by_field_name("name"),
18444 "field_expression" => node.child_by_field_name("field"),
18445 _ => None,
18446 };
18447 let Some(next) = next else {
18448 return node;
18449 };
18450 node = next;
18451 }
18452}
18453
18454#[derive(Clone, Copy)]
18455pub struct RecoveredRelationalTemplateMemberCall<'tree> {
18456 pub receiver: Node<'tree>,
18457 pub member: Node<'tree>,
18458 pub arity: usize,
18459}
18460
18461pub fn recovered_relational_template_member_call(
18469 field: Node<'_>,
18470) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
18471 if field.kind() != "field_expression" {
18472 return None;
18473 }
18474 let receiver = field
18475 .child_by_field_name("argument")
18476 .or_else(|| field.child_by_field_name("object"))?;
18477 let member = field.child_by_field_name("field")?;
18478 let less = field.parent()?;
18479 if less.kind() != "binary_expression"
18480 || less.child_by_field_name("left") != Some(field)
18481 || less
18482 .child_by_field_name("operator")
18483 .is_none_or(|operator| operator.kind() != "<")
18484 || less.child_by_field_name("right").is_none()
18485 {
18486 return None;
18487 }
18488 let greater = less.parent()?;
18489 if greater.kind() != "binary_expression"
18490 || greater.child_by_field_name("left") != Some(less)
18491 || greater
18492 .child_by_field_name("operator")
18493 .is_none_or(|operator| operator.kind() != ">")
18494 {
18495 return None;
18496 }
18497 let arguments = greater.child_by_field_name("right")?;
18498 if arguments.kind() != "parenthesized_expression" {
18499 return None;
18500 }
18501 let arity = parenthesized_call_argument_arity(arguments)?;
18502 Some(RecoveredRelationalTemplateMemberCall {
18503 receiver,
18504 member,
18505 arity,
18506 })
18507}
18508
18509fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
18510 let expression = arguments.named_child(0)?;
18511 if expression.kind() != "comma_expression" {
18512 return Some(1);
18513 }
18514 let mut arity = 0usize;
18515 let mut stack = vec![expression];
18516 while let Some(node) = stack.pop() {
18517 if node.kind() == "comma_expression" {
18518 stack.push(node.child_by_field_name("right")?);
18519 stack.push(node.child_by_field_name("left")?);
18520 } else {
18521 arity += 1;
18522 }
18523 }
18524 Some(arity)
18525}
18526
18527pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
18530 while let Some(parent) = node.parent() {
18531 match parent.kind() {
18532 "call_expression" => {
18533 return parent
18534 .child_by_field_name("function")
18535 .or_else(|| parent.named_child(0))
18536 == Some(node);
18537 }
18538 "qualified_identifier"
18539 | "scoped_identifier"
18540 | "template_function"
18541 | "template_type"
18542 | "field_expression" => node = parent,
18543 _ => return false,
18544 }
18545 }
18546 false
18547}
18548
18549pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
18553 loop {
18554 let Some(parent) = node.parent() else {
18555 return node;
18556 };
18557 if parent.kind() == "template_type"
18558 && parent
18559 .child_by_field_name("name")
18560 .is_some_and(|name| same_node(name, node))
18561 {
18562 node = parent;
18563 continue;
18564 }
18565 if matches!(
18566 parent.kind(),
18567 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18568 ) && (parent
18569 .child_by_field_name("scope")
18570 .is_some_and(|scope| same_node(scope, node))
18571 || parent
18572 .child_by_field_name("name")
18573 .is_some_and(|name| same_node(name, node)))
18574 && parent.end_byte() == node.end_byte()
18575 {
18576 node = parent;
18577 continue;
18578 }
18579 return node;
18580 }
18581}
18582
18583pub fn normalize_type_text(value: &str) -> String {
18584 strip_tag_type_prefix(
18585 normalize_cpp_whitespace(value)
18586 .trim_start_matches("const ")
18587 .trim_end_matches('*')
18588 .trim_end_matches('&')
18589 .trim(),
18590 )
18591 .to_string()
18592}
18593
18594use crate::declarations::strip_tag_type_prefix;
18595
18596pub fn normalize_reference_name(value: &str) -> Option<String> {
18597 let normalized = normalize_cpp_reference_text(value);
18598 (!normalized.is_empty()).then_some(normalized)
18599}
18600
18601pub fn normalize_cpp_reference_text(value: &str) -> String {
18602 let mut text = normalize_cpp_whitespace(value)
18603 .trim_start_matches("new ")
18604 .trim()
18605 .to_string();
18606 if let Some(index) = text.find(['(', '{']) {
18607 text.truncate(index);
18608 }
18609 if let Some(index) = text.find('<') {
18610 text.truncate(index);
18611 }
18612 let normalized = text
18613 .trim()
18614 .trim_start_matches("const ")
18615 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
18616 .trim_matches(':')
18617 .trim();
18618 strip_tag_type_prefix(normalized).to_string()
18619}
18620
18621pub fn cpp_name_for(unit: &CodeUnit) -> String {
18622 let short = unit.short_name().replace(['.', '$'], "::");
18623 if unit.package_name().is_empty() {
18624 short
18625 } else {
18626 format!("{}::{}", unit.package_name(), short)
18627 }
18628}
18629
18630fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
18634 let fq = unit.fq();
18635 if fq.is_empty() {
18636 return None;
18637 }
18638 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18639 Some(
18640 fq.segments()
18641 .iter()
18642 .map(|&segment| interner.resolve(segment).0)
18643 .collect::<Vec<_>>()
18644 .join("::"),
18645 )
18646}
18647
18648fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
18649 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
18650 || unit.fq().is_empty() && cpp_name_for(unit) == expected
18651}
18652
18653pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
18666 let fq = unit.fq();
18667 if !fq.is_empty() {
18668 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18669 let segments = fq.segments();
18670 let scope = segments
18671 .iter()
18672 .enumerate()
18673 .filter_map(|(index, &segment)| {
18674 let (text, kind) = interner.resolve(segment);
18675 match kind {
18676 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => true,
18677 SegmentKind::Member => index + 1 < segments.len(),
18678 SegmentKind::Path | SegmentKind::Companion | SegmentKind::Unknown => false,
18679 }
18680 .then(|| text.to_string())
18681 })
18682 .collect();
18683 return scope;
18684 }
18685 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18686 brokk_bifrost_core::analyzer::Language::Cpp,
18687 &cpp_name_for(unit),
18688 )
18689}
18690
18691pub fn terminal_name(value: &str) -> &str {
18702 value
18703 .rsplit("::")
18704 .next()
18705 .unwrap_or(value)
18706 .rsplit(['.', '-', '>'])
18707 .next()
18708 .unwrap_or(value)
18709 .trim()
18710}
18711
18712pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
18713 terminal_name(&normalize_cpp_reference_text(value)) == expected
18714}
18715
18716pub fn name_matches_callable(value: &str, expected: &str) -> bool {
18717 name_matches_terminal(value, expected)
18718 || expected.starts_with("operator")
18719 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
18720}
18721
18722pub fn name_mentions(value: &str, expected: &str) -> bool {
18723 normalize_cpp_reference_text(value)
18724 .split("::")
18725 .any(|part| part == expected)
18726}
18727
18728pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
18729 let cpp_name = cpp_name_for(unit);
18730 if reference.contains("::") {
18731 return reference == cpp_name;
18732 }
18733 reference == cpp_name
18734 || terminal_name(reference) == unit.identifier()
18735 && (unit.package_name().is_empty() || reference == unit.identifier())
18736}
18737
18738pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
18739 match kind {
18740 TargetKind::Type
18741 | TargetKind::Constructor
18742 | TargetKind::Method
18743 | TargetKind::MemberField => true,
18744 TargetKind::FreeFunction => unit.is_function(),
18745 TargetKind::GlobalField => unit.is_field(),
18746 TargetKind::Macro => unit.is_macro(),
18747 }
18748}
18749
18750fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
18751 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
18752 let target_name = cpp_name_for(target);
18753 if normalized.contains("::") {
18754 return normalized == target_name;
18755 }
18756 if let Some(namespace) = alias.namespace.as_deref() {
18757 return namespace_prefixes(namespace)
18758 .into_iter()
18759 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
18760 }
18761 target.package_name().is_empty() && normalized == target.identifier()
18762}
18763
18764pub fn cpp_function_return_type_text(
18767 analyzer: &CppGraphSource<'_>,
18768 function: &CodeUnit,
18769) -> Option<String> {
18770 let metadata = analyzer.signature_metadata(function);
18771 let first = metadata.first()?.return_type_text()?;
18772 metadata
18773 .iter()
18774 .all(|metadata| metadata.return_type_text() == Some(first))
18775 .then(|| first.to_owned())
18776}
18777
18778pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
18779 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
18789 namespace
18790 .strip_prefix("anonymous_namespace::")
18791 .unwrap_or(namespace)
18792 .to_string()
18793 })
18794}
18795
18796pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
18798 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18804 brokk_bifrost_core::analyzer::Language::Cpp,
18805 namespace,
18806 );
18807 let mut prefixes = Vec::new();
18808 while !parts.is_empty() {
18809 prefixes.push(parts.join("::"));
18810 parts.pop();
18811 }
18812 prefixes
18813}
18814
18815fn nearest_namespace_candidates(
18816 candidates: Vec<CodeUnit>,
18817 normalized: &str,
18818 lexical_namespace: Option<&str>,
18819) -> Vec<CodeUnit> {
18820 if normalized.contains("::") {
18821 return candidates;
18822 }
18823 if let Some(namespace) = lexical_namespace {
18824 for prefix in namespace_prefixes(namespace) {
18825 let scoped = candidates
18826 .iter()
18827 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
18828 .cloned()
18829 .collect::<Vec<_>>();
18830 if !scoped.is_empty() {
18831 return scoped;
18832 }
18833 }
18834 }
18835 candidates
18836 .into_iter()
18837 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
18838 .collect()
18839}
18840
18841pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
18842 let mut namespaces = Vec::new();
18843 for parent in enclosing_cpp_namespace_nodes(node) {
18844 if let Some(name) = parent.child_by_field_name("name") {
18845 let namespace = normalize_cpp_reference_text(node_text(name, source));
18846 if !namespace.is_empty() {
18847 namespaces.push(namespace);
18848 }
18849 }
18850 }
18851 if namespaces.is_empty() {
18852 None
18853 } else {
18854 Some(namespaces.join("::"))
18855 }
18856}
18857
18858pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18862 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
18863}
18864
18865fn type_owner_resolution(
18866 analyzer: &CppGraphSource<'_>,
18867 code_unit: &CodeUnit,
18868) -> Option<ResolvedTypeOwner> {
18869 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
18870}
18871
18872fn target_type_owner_resolution(
18873 analyzer: &CppGraphSource<'_>,
18874 code_unit: &CodeUnit,
18875) -> Option<ResolvedTypeOwner> {
18876 match type_owner_resolution(analyzer, code_unit) {
18877 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
18878 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
18879 }
18880}
18881
18882fn target_forward_owner_resolution(
18894 analyzer: &CppGraphSource<'_>,
18895 code_unit: &CodeUnit,
18896) -> Option<ResolvedTypeOwner> {
18897 if !code_unit.is_function() {
18898 return None;
18899 }
18900 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
18906 let cpp = analyzer.cpp?;
18907 let mut visible_files = HashSet::default();
18908 collect_include_closure(
18909 analyzer,
18910 &cpp.include_target_index(),
18911 code_unit.source(),
18912 &mut visible_files,
18913 None,
18914 );
18915 let candidates = analyzer.workspace_definitions().exact(&owner_name);
18916 let visible_candidates = candidates
18917 .iter()
18918 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18919 .cloned()
18920 .collect::<Vec<_>>();
18921 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
18922 DirectOwnerResolution::UniqueFull(unit) => {
18923 return Some(ResolvedTypeOwner {
18924 unit,
18925 is_forward_declaration: false,
18926 });
18927 }
18928 DirectOwnerResolution::ForwardsOnly(forwards) => {
18929 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
18930 unit: forwards.into_iter().next().unwrap(),
18931 is_forward_declaration: true,
18932 });
18933 }
18934 DirectOwnerResolution::Ambiguous => return None,
18935 DirectOwnerResolution::None => {}
18936 }
18937
18938 let candidates = candidates
18939 .into_iter()
18940 .filter(|candidate| candidate.is_class())
18941 .collect::<Vec<_>>();
18942 let (unit, is_forward_declaration) =
18943 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
18944 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
18945 DirectOwnerResolution::ForwardsOnly(forwards) => {
18946 (unique_logical_forward_owner(forwards)?, true)
18947 }
18948 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
18949 };
18950 Some(ResolvedTypeOwner {
18951 unit,
18952 is_forward_declaration,
18953 })
18954}
18955
18956pub fn precise_parent_of(
18957 analyzer: &CppGraphSource<'_>,
18958 visibility: &VisibilityIndex<'_>,
18959 code_unit: &CodeUnit,
18960) -> Option<CodeUnit> {
18961 visibility.cached_precise_parent_of(analyzer, code_unit)
18962}
18963
18964fn precise_parent_resolution(
18965 analyzer: &CppGraphSource<'_>,
18966 code_unit: &CodeUnit,
18967) -> Option<ResolvedTypeOwner> {
18968 #[cfg(any(test, feature = "test-support"))]
18969 if let Some(cpp) = analyzer.cpp {
18970 cpp.record_cpp_parent_resolution_for_test();
18971 }
18972 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
18973 return Some(ResolvedTypeOwner {
18974 unit,
18975 is_forward_declaration: false,
18976 });
18977 }
18978 let fallback = analyzer.parent_of(code_unit);
18979 if !code_unit.owner_is_type_scope() {
18980 return fallback.map(|unit| ResolvedTypeOwner {
18981 unit,
18982 is_forward_declaration: false,
18983 });
18984 }
18985 let owner_fq = code_unit
18986 .fq()
18987 .parent()
18988 .expect("a unit with an owner identifier has a structured parent");
18989 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
18990 match same_source_owner(analyzer, code_unit, &owner_candidates) {
18991 DirectOwnerResolution::UniqueFull(owner) => {
18992 return Some(ResolvedTypeOwner {
18993 unit: owner,
18994 is_forward_declaration: false,
18995 });
18996 }
18997 DirectOwnerResolution::Ambiguous => return None,
18998 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
18999 }
19000 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
19001 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
19002 unit: owner,
19003 is_forward_declaration: false,
19004 }),
19005 DirectOwnerResolution::Ambiguous => None,
19006 DirectOwnerResolution::ForwardsOnly(forwards) => {
19007 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
19008 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
19009 unit: owner,
19010 is_forward_declaration: false,
19011 }),
19012 FullOwnerResolution::None => {
19013 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
19014 unit,
19015 is_forward_declaration: true,
19016 })
19017 }
19018 FullOwnerResolution::Ambiguous => None,
19019 }
19020 }
19021 DirectOwnerResolution::None => {
19022 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
19023 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
19024 unit: owner,
19025 is_forward_declaration: false,
19026 }),
19027 FullOwnerResolution::Ambiguous => None,
19028 FullOwnerResolution::None => fallback
19029 .filter(|parent| {
19030 parent.source() == code_unit.source()
19031 && parent.fq() == &owner_fq
19032 && (!parent.is_class()
19033 || cpp_class_declaration_strength(analyzer, parent)
19034 == CppClassDeclarationStrength::Full)
19035 })
19036 .map(|unit| ResolvedTypeOwner {
19037 unit,
19038 is_forward_declaration: false,
19039 }),
19040 }
19041 }
19042 }
19043}
19044
19045fn exact_structural_type_parent(
19046 analyzer: &CppGraphSource<'_>,
19047 code_unit: &CodeUnit,
19048) -> Option<CodeUnit> {
19049 if !code_unit.is_function() && !code_unit.is_field() {
19050 return None;
19051 }
19052 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
19054 let parent = cpp.structural_parent_of(code_unit)?;
19055 (!parent.is_module()
19056 && parent.source() == code_unit.source()
19057 && parent.package_name() == code_unit.package_name()
19058 && parent.short_name() == encoded_owner)
19059 .then_some(parent)
19060}
19061
19062fn same_source_owner(
19063 analyzer: &CppGraphSource<'_>,
19064 code_unit: &CodeUnit,
19065 owner_candidates: &[CodeUnit],
19066) -> DirectOwnerResolution {
19067 let candidates = owner_candidates
19068 .iter()
19069 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
19070 .cloned()
19071 .collect::<Vec<_>>();
19072 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19073 classify_direct_owner_candidates(analyzer, candidates.into_iter())
19074}
19075
19076fn visible_full_cpp_owner(
19077 analyzer: &CppGraphSource<'_>,
19078 code_unit: &CodeUnit,
19079 owner_candidates: &[CodeUnit],
19080) -> FullOwnerResolution {
19081 let Some(cpp) = analyzer.cpp else {
19082 return FullOwnerResolution::None;
19083 };
19084 let mut visible_files = HashSet::default();
19085 collect_include_closure(
19086 analyzer,
19087 &cpp.include_target_index(),
19088 code_unit.source(),
19089 &mut visible_files,
19090 None,
19091 );
19092 let candidates = owner_candidates
19093 .iter()
19094 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
19095 .cloned()
19096 .collect::<Vec<_>>();
19097 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19098 match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
19099 let strength = cpp_class_declaration_strength(analyzer, &candidate);
19100 (candidate, strength)
19101 })) {
19102 DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
19103 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
19104 FullOwnerResolution::None
19105 }
19106 DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
19107 }
19108}
19109
19110pub enum DirectOwnerResolution {
19111 None,
19112 ForwardsOnly(Vec<CodeUnit>),
19113 UniqueFull(CodeUnit),
19114 Ambiguous,
19115}
19116
19117enum FullOwnerResolution {
19118 None,
19119 Unique(CodeUnit),
19120 Ambiguous,
19121}
19122
19123fn directly_included_owner(
19124 analyzer: &CppGraphSource<'_>,
19125 code_unit: &CodeUnit,
19126 owner_candidates: &[CodeUnit],
19127) -> DirectOwnerResolution {
19128 let Some(cpp) = analyzer.cpp else {
19129 return DirectOwnerResolution::None;
19130 };
19131 let direct_includes: HashSet<ProjectFile> = analyzer
19132 .canonical_include_paths(code_unit.source())
19133 .unwrap_or_default()
19134 .into_iter()
19135 .flat_map(|include| {
19136 resolve_include_targets_with_index(
19137 code_unit.source(),
19138 &include,
19139 &cpp.include_target_index(),
19140 )
19141 })
19142 .collect();
19143 let candidates = owner_candidates
19144 .iter()
19145 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
19146 .cloned()
19147 .collect::<Vec<_>>();
19148 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19149 classify_direct_owner_candidates(analyzer, candidates.into_iter())
19150}
19151
19152fn prefer_member_declaring_owners(
19153 analyzer: &CppGraphSource<'_>,
19154 member: &CodeUnit,
19155 candidates: Vec<CodeUnit>,
19156) -> Vec<CodeUnit> {
19157 let matching = candidates
19158 .iter()
19159 .filter(|owner| owner_declares_member(analyzer, owner, member))
19160 .cloned()
19161 .collect::<Vec<_>>();
19162 if matching.is_empty() {
19163 candidates
19164 } else {
19165 matching
19166 }
19167}
19168
19169fn owner_declares_member(
19170 analyzer: &CppGraphSource<'_>,
19171 owner: &CodeUnit,
19172 member: &CodeUnit,
19173) -> bool {
19174 analyzer.direct_children(owner).into_iter().any(|child| {
19175 child.kind() == member.kind()
19176 && child.identifier() == member.identifier()
19177 && child.signature() == member.signature()
19178 })
19179}
19180
19181fn classify_direct_owner_candidates(
19182 analyzer: &CppGraphSource<'_>,
19183 candidates: impl Iterator<Item = CodeUnit>,
19184) -> DirectOwnerResolution {
19185 collapse_owner_candidates(candidates.map(|candidate| {
19186 let strength = cpp_class_declaration_strength(analyzer, &candidate);
19187 (candidate, strength)
19188 }))
19189}
19190
19191pub fn collapse_owner_candidates(
19192 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
19193) -> DirectOwnerResolution {
19194 let mut full_definition = None;
19195 let mut forwards = Vec::new();
19196 for (candidate, strength) in candidates {
19197 match strength {
19198 CppClassDeclarationStrength::Full if full_definition.is_some() => {
19199 return DirectOwnerResolution::Ambiguous;
19200 }
19201 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
19202 CppClassDeclarationStrength::Forward => forwards.push(candidate),
19203 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
19204 }
19205 }
19206 if let Some(owner) = full_definition {
19207 DirectOwnerResolution::UniqueFull(owner)
19208 } else if !forwards.is_empty() {
19209 DirectOwnerResolution::ForwardsOnly(forwards)
19210 } else {
19211 DirectOwnerResolution::None
19212 }
19213}
19214
19215#[cfg(any(test, feature = "test-support"))]
19216pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
19217 unique_logical_forward_owner(forwards)
19218}
19219
19220fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
19221 let first = forwards.pop()?;
19222 forwards
19223 .iter()
19224 .all(|forward| same_logical_symbol(forward, &first))
19225 .then_some(first)
19226}
19227
19228pub fn cpp_class_declaration_strength(
19229 analyzer: &CppGraphSource<'_>,
19230 candidate: &CodeUnit,
19231) -> CppClassDeclarationStrength {
19232 let Some(cpp) = analyzer.cpp else {
19240 return uncached_cpp_class_declaration_strength(analyzer, candidate);
19241 };
19242 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
19243 return strength;
19244 }
19245 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
19246 cpp.cache_class_declaration_strength(candidate, strength);
19247 strength
19248}
19249
19250fn uncached_cpp_class_declaration_strength(
19251 analyzer: &CppGraphSource<'_>,
19252 candidate: &CodeUnit,
19253) -> CppClassDeclarationStrength {
19254 if let Some(facts) = analyzer.declaration_source_properties(candidate) {
19255 if facts
19256 .iter()
19257 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
19258 {
19259 return CppClassDeclarationStrength::Full;
19260 }
19261 if facts
19262 .iter()
19263 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
19264 {
19265 return CppClassDeclarationStrength::Forward;
19266 }
19267 }
19268 if let Some(cpp) = analyzer.cpp
19269 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
19270 {
19271 return cpp_class_declaration_strength_in_tree(
19272 analyzer,
19273 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
19274 candidate,
19275 prepared.source(),
19276 prepared.tree().root_node(),
19277 );
19278 }
19279 let Some(source) = analyzer.indexed_source(candidate.source()) else {
19280 return CppClassDeclarationStrength::Unknown;
19281 };
19282 let mut parser = Parser::new();
19283 if parser
19284 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19285 .is_err()
19286 {
19287 return CppClassDeclarationStrength::Unknown;
19288 }
19289 let Some(tree) = parser.parse(&source, None) else {
19290 return CppClassDeclarationStrength::Unknown;
19291 };
19292 let recovered_export_classes =
19295 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
19296 cpp_class_declaration_strength_in_tree(
19297 analyzer,
19298 &recovered_export_classes,
19299 candidate,
19300 &source,
19301 tree.root_node(),
19302 )
19303}
19304
19305fn cpp_class_declaration_strength_in_tree(
19306 analyzer: &CppGraphSource<'_>,
19307 recovered_export_classes: &CppRecoveredExportClassIndex,
19308 candidate: &CodeUnit,
19309 source: &str,
19310 root: Node<'_>,
19311) -> CppClassDeclarationStrength {
19312 let ranges = analyzer.ranges(candidate);
19313 let mut saw_forward = false;
19314 for range in ranges {
19315 match recovered_class_body_at(
19318 recovered_export_classes,
19319 root,
19320 source,
19321 candidate.identifier(),
19322 &range,
19323 ) {
19324 Some(true) => return CppClassDeclarationStrength::Full,
19325 Some(false) => {
19326 saw_forward = true;
19327 continue;
19328 }
19329 None => {}
19330 }
19331 let covers_range_start = |node: &Node<'_>| {
19338 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
19339 };
19340 let mut stack = Vec::new();
19341 if covers_range_start(&root) {
19342 stack.push(root);
19343 }
19344 while let Some(node) = stack.pop() {
19345 if node.start_byte() == range.start_byte
19346 && node.end_byte() == range.end_byte
19347 && matches!(
19348 node.kind(),
19349 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
19350 )
19351 {
19352 if cpp_class_node_has_body(node) {
19353 return CppClassDeclarationStrength::Full;
19354 }
19355 saw_forward = true;
19356 }
19357 let mut cursor = node.walk();
19358 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
19359 }
19360 }
19361 if saw_forward {
19362 CppClassDeclarationStrength::Forward
19363 } else {
19364 CppClassDeclarationStrength::Unknown
19365 }
19366}
19367
19368fn cpp_class_node_has_body(node: Node<'_>) -> bool {
19369 child_by_field_or_named_child_of_kinds(
19370 node,
19371 "body",
19372 &[
19373 "declaration_list",
19374 "field_declaration_list",
19375 "enumerator_list",
19376 ],
19377 )
19378 .is_some()
19379}
19380
19381#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19382enum CppCTagKind {
19383 Struct,
19384 Union,
19385}
19386
19387fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
19388 let declaration = analyzer.get_source(code_unit, false)?;
19389 let mut parser = Parser::new();
19390 parser
19391 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19392 .ok()?;
19393 let tree = parser.parse(&declaration, None)?;
19394 let mut stack = vec![tree.root_node()];
19395 while let Some(node) = stack.pop() {
19396 let kind = match node.kind() {
19397 "struct_specifier" => CppCTagKind::Struct,
19398 "union_specifier" => CppCTagKind::Union,
19399 _ => {
19400 let mut cursor = node.walk();
19401 stack.extend(node.named_children(&mut cursor));
19402 continue;
19403 }
19404 };
19405 if node
19406 .child_by_field_name("name")
19407 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
19408 {
19409 return Some(kind);
19410 }
19411 let mut cursor = node.walk();
19412 stack.extend(node.named_children(&mut cursor));
19413 }
19414 None
19415}
19416
19417pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
19418 if !code_unit.owner_is_type_scope() {
19419 return None;
19420 }
19421 let owner_fq = code_unit.fq().parent()?;
19422 ctx.analyzer
19423 .workspace_definitions()
19424 .exact(&owner_fq)
19425 .into_iter()
19426 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
19427}
19428
19429pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19430 same_logical_symbol(left, right) && left.source() == right.source()
19431}
19432
19433pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19434 same_symbol(left, right) || same_logical_symbol(left, right)
19435}
19436
19437pub fn same_visible_global_field_symbol(
19438 analyzer: &CppGraphSource<'_>,
19439 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
19440 left: &CodeUnit,
19441 right: &CodeUnit,
19442) -> bool {
19443 if same_symbol(left, right) {
19444 return true;
19445 }
19446 if !same_logical_symbol(left, right) {
19447 return false;
19448 }
19449 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
19450 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
19451 {
19452 left.source() == right.source()
19453 } else {
19454 true
19455 }
19456}
19457
19458fn cpp_global_field_has_internal_linkage_cached(
19459 analyzer: &CppGraphSource<'_>,
19460 cache: &mut HashMap<CodeUnit, bool>,
19461 candidate: &CodeUnit,
19462) -> bool {
19463 if let Some(internal) = cache.get(candidate) {
19464 return *internal;
19465 }
19466 #[cfg(any(test, feature = "test-support"))]
19467 note_cpp_global_field_internal_linkage_classification_for_test();
19468 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
19469 cache.insert(candidate.clone(), internal);
19470 internal
19471}
19472
19473#[cfg(any(test, feature = "test-support"))]
19474thread_local! {
19475 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19476}
19477
19478#[cfg(any(test, feature = "test-support"))]
19479fn note_cpp_global_field_internal_linkage_classification_for_test() {
19480 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
19481 count.set(count.get() + 1);
19482 });
19483}
19484
19485#[cfg(any(test, feature = "test-support"))]
19486pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
19487 body: impl FnOnce() -> T,
19488) -> (T, usize) {
19489 with_thread_local_counter_for_test(
19490 &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
19491 body,
19492 )
19493}
19494
19495#[cfg(any(test, feature = "test-support"))]
19496fn with_thread_local_counter_for_test<T>(
19497 counter: &'static std::thread::LocalKey<Cell<usize>>,
19498 body: impl FnOnce() -> T,
19499) -> (T, usize) {
19500 counter.with(|count| {
19501 count.set(0);
19502 let result = body();
19503 let observed = count.get();
19504 count.set(0);
19505 (result, observed)
19506 })
19507}
19508
19509pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19510 left.kind() == right.kind()
19511 && left.fq_name() == right.fq_name()
19512 && left.signature() == right.signature()
19513}
19514
19515pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
19518 candidates: &[&'a CodeUnit],
19519 mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
19520) -> Option<&'a CodeUnit> {
19521 let first = *candidates.first()?;
19522 if candidates.len() == 1 {
19523 return Some(first);
19524 }
19525 let first_target = structured_target(first)?;
19526 candidates
19527 .iter()
19528 .skip(1)
19529 .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
19530 .then_some(first)
19531}
19532
19533pub fn cpp_global_field_has_internal_linkage(
19534 analyzer: &CppGraphSource<'_>,
19535 candidate: &CodeUnit,
19536) -> bool {
19537 if !candidate.is_field() || candidate.short_name().contains('.') {
19538 return false;
19539 }
19540 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
19541 return false;
19542 };
19543 match local_linkage {
19544 CppFieldLinkage::Internal => true,
19545 CppFieldLinkage::External => false,
19546 CppFieldLinkage::InternalUnlessExternalPeer => {
19547 !cpp_global_field_linkage_peers(analyzer, candidate)
19548 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
19549 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
19550 }
19551 }
19552}
19553
19554fn cpp_global_field_linkage_peers<'a>(
19555 analyzer: &CppGraphSource<'a>,
19556 candidate: &'a CodeUnit,
19557) -> impl Iterator<Item = CodeUnit> + 'a {
19558 let name = candidate.fq().clone();
19559 analyzer
19560 .workspace_definitions()
19561 .exact(&name)
19562 .into_iter()
19563 .filter(move |peer| {
19564 if peer == candidate {
19565 return false;
19566 }
19567 #[cfg(any(test, feature = "test-support"))]
19568 note_cpp_global_field_linkage_peer_inspection_for_test();
19569 same_logical_symbol(peer, candidate)
19570 })
19571}
19572
19573#[cfg(any(test, feature = "test-support"))]
19574thread_local! {
19575 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19576}
19577
19578#[cfg(any(test, feature = "test-support"))]
19579fn note_cpp_global_field_linkage_peer_inspection_for_test() {
19580 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
19581 count.set(count.get() + 1);
19582 });
19583}
19584
19585#[cfg(any(test, feature = "test-support"))]
19586pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
19587 body: impl FnOnce() -> T,
19588) -> (T, usize) {
19589 with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
19590}
19591
19592fn cpp_global_field_declaration_linkage(
19593 analyzer: &CppGraphSource<'_>,
19594 candidate: &CodeUnit,
19595) -> Option<CppFieldLinkage> {
19596 analyzer.cpp_field_linkage(candidate)
19597}
19598
19599pub fn cpp_canonical_alias_target_text(
19601 analyzer: &CppGraphSource<'_>,
19602 unit: &CodeUnit,
19603) -> Option<String> {
19604 let facts = analyzer.declaration_source_properties(unit)?;
19605 let first_fact = facts.first()?;
19606 let first = first_fact.alias_target_text.as_ref()?;
19607 let normalized = normalize_cpp_whitespace(first);
19608 facts
19612 .iter()
19613 .all(|fact| {
19614 fact.alias_target_text.as_ref().is_some_and(|target| {
19615 target == first
19616 || (first_fact.alias_target.is_some()
19617 && fact.alias_target == first_fact.alias_target
19618 && normalize_cpp_whitespace(target) == normalized)
19619 })
19620 })
19621 .then(|| first.clone())
19622}
19623
19624#[cfg(test)]
19625mod tests {
19626
19627 #[test]
19628 fn resolver_displaced_conditional_grammar_boundary() {
19629 let source = "#ifdef FLAG\nint values[\n#endif\n1];\nint outside;\n";
19633 let tree = parse_cpp(source);
19634 let conditional = tree.root_node().named_child(0).unwrap();
19635 assert_eq!(conditional.kind(), "preproc_ifdef");
19636 assert!(conditional.has_error());
19637 let boundary = cpp_displaced_preprocessor_boundary(conditional).unwrap();
19638 let directive = crate::grammar_test_support::nodes(conditional)
19639 .into_iter()
19640 .find(|node| node.kind() == "#endif" && !node.is_missing())
19641 .unwrap();
19642 assert_eq!(directive.parent().unwrap().kind(), "ERROR");
19643 assert_eq!(directive.end_byte(), boundary.end_byte);
19644 let inside = named_node_at(&tree, source, "values");
19645 let outside = named_node_at(&tree, source, "outside");
19646 assert!(has_ancestor_kind(inside, "preproc_ifdef"));
19647 assert!(has_ancestor_kind(outside, "preproc_ifdef"));
19648 assert!(inside.end_byte() < directive.start_byte());
19649 assert!(outside.start_byte() > directive.end_byte());
19650 assert!(preprocessor_conditional_contains_descendant(
19651 conditional,
19652 inside
19653 ));
19654 assert!(!preprocessor_conditional_contains_descendant(
19655 conditional,
19656 outside
19657 ));
19658 }
19659
19660 #[test]
19661 fn resolver_recovered_namespace_boundaries() {
19662 let source = STOLEN_BRACE_CASCADE;
19663 let tree = parse_cpp(source);
19664 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19665 assert!(!index.regions.is_empty(), "fixture must enter recovery");
19666 for (needle, expected) in [
19667 ("First", vec!["app", "matchers", "detail"]),
19668 ("Second", vec!["app", "matchers", "detail"]),
19669 ("tail_function", vec!["app", "matchers"]),
19670 ("TailClass", vec!["app", "matchers"]),
19671 ("AfterAll", vec![]),
19672 ] {
19673 let node = named_node_at(&tree, source, needle);
19674 let mut parsed: Vec<_> = std::iter::successors(node.parent(), |node| node.parent())
19675 .filter(|node| node.kind() == "namespace_definition")
19676 .filter_map(|namespace| {
19677 let name = namespace.child_by_field_name("name")?;
19678 let mut components = Vec::new();
19679 append_cpp_name_components(name, source, &mut components)?;
19680 Some((
19681 namespace.start_byte(),
19682 components,
19683 namespace_definition_has_real_braces(namespace),
19684 ))
19685 })
19686 .collect();
19687 parsed.reverse();
19688 assert_eq!(
19689 index.restore_enclosing_namespaces(parsed, node.start_byte()),
19690 expected,
19691 "{needle}"
19692 );
19693 }
19694 for region in &index.regions {
19696 assert!(region.start < region.end);
19697 assert!(index.region_at(region.start).is_some());
19698 assert!(
19699 index
19700 .region_at(region.end)
19701 .is_none_or(|found| !std::ptr::eq(found, region)),
19702 "region leaked beyond its half-open boundary: {region:?}"
19703 );
19704 }
19705 let errors: Vec<_> = crate::grammar_test_support::nodes(tree.root_node())
19708 .into_iter()
19709 .filter(|node| node.kind() == "ERROR")
19710 .filter(|node| {
19711 let mut cursor = node.walk();
19712 node.children(&mut cursor).any(|child| child.kind() == "}")
19713 })
19714 .collect();
19715 assert!(
19716 !errors.is_empty(),
19717 "fixture must retain unmatched close tokens"
19718 );
19719 for error in errors {
19720 assert!(direct_unmatched_closing_brace(error), "{error:?}");
19721 }
19722 }
19723
19724 #[test]
19727 fn resolver_declarator_grammar_boundary_table() {
19728 for (source, name, kind, binder) in [
19729 ("int first, values[3];", "values", "identifier", true),
19730 (
19731 "void f() { int first, values[3]; }",
19732 "values",
19733 "identifier",
19734 true,
19735 ),
19736 (
19737 "typedef unsigned Underlying; enum Named : Underlying { Value };",
19738 "Underlying {",
19739 "type_identifier",
19740 false,
19741 ),
19742 ] {
19743 let tree = parse_cpp(source);
19744 assert!(!tree.root_node().has_error(), "{source}");
19745 let spelling = name.split_whitespace().next().unwrap();
19746 let start = source.find(name).unwrap();
19747 let node = tree
19748 .root_node()
19749 .named_descendant_for_byte_range(start, start + spelling.len())
19750 .unwrap();
19751 assert_eq!(node.kind(), kind, "{source}");
19752 if spelling == "values" {
19753 let array = node.parent().unwrap();
19754 assert_eq!(array.kind(), "array_declarator");
19755 assert_eq!(array.child_by_field_name("declarator"), Some(node));
19756 assert!(is_declarator_node(array), "{source}");
19757 } else {
19758 let enumeration = node.parent().unwrap();
19759 assert_eq!(enumeration.kind(), "enum_specifier");
19760 assert_eq!(enumeration.child_by_field_name("base"), Some(node));
19761 assert_eq!(
19762 enumeration.child_by_field_name("name").is_some(),
19763 source.contains("enum Named")
19764 );
19765 }
19766 assert_eq!(is_declaration_name(node), binder, "{source}");
19767 }
19768 }
19769
19770 #[test]
19771 fn resolver_alias_scope_and_argument_grammar_table() {
19772 for (source, scope) in [
19773 ("template<class T> using Alias = T;", true),
19774 ("namespace n { template<class T> using Alias = T; }", true),
19775 (
19776 "struct Owner { template<class T> using Alias = T; };",
19777 false,
19778 ),
19779 ("void f() { using Alias = int; }", false),
19780 ] {
19781 let tree = parse_cpp(source);
19782 assert!(!tree.root_node().has_error(), "{source}");
19783 let alias = crate::grammar_test_support::nodes(tree.root_node())
19784 .into_iter()
19785 .find(|node| node.kind() == "alias_declaration")
19786 .unwrap();
19787 assert_eq!(
19788 node_text(alias.child_by_field_name("name").unwrap(), source),
19789 "Alias"
19790 );
19791 assert!(alias.child_by_field_name("type").is_some());
19792 assert_eq!(alias_has_visible_file_scope(alias), scope, "{source}");
19793 }
19794 for (source, argument_kinds, expression) in [
19795 (
19796 "ns::Alias value(42, arg);",
19797 vec!["number_literal", "identifier"],
19798 true,
19799 ),
19800 (
19801 "ns::Alias value(42, 7);",
19802 vec!["number_literal", "number_literal"],
19803 false,
19804 ),
19805 ("ns::Alias value();", vec![], false),
19806 ] {
19807 let tree = parse_cpp(source);
19808 assert!(!tree.root_node().has_error(), "{source}");
19809 let declaration = tree.root_node().named_child(0).unwrap();
19810 let ty = declaration.child_by_field_name("type").unwrap();
19811 assert_eq!(node_text(ty, source), "ns::Alias");
19812 let argument_lists: Vec<_> = crate::grammar_test_support::nodes(declaration)
19813 .into_iter()
19814 .filter(|node| node.kind() == "argument_list")
19815 .collect();
19816 let kinds: Vec<_> = argument_lists
19817 .iter()
19818 .flat_map(|node| {
19819 let mut cursor = node.walk();
19820 node.named_children(&mut cursor)
19821 .map(|child| child.kind())
19822 .collect::<Vec<_>>()
19823 })
19824 .collect();
19825 assert_eq!(kinds, argument_kinds, "{source}");
19826 assert_eq!(
19827 qualified_alias_constructor_has_expression_argument(ty),
19828 expression,
19829 "{source}"
19830 );
19831 }
19832 }
19833
19834 #[test]
19835 fn resolver_namespace_brace_grammar_table() {
19836 for (source, complete, closing_missing) in [
19837 ("namespace n { int item; }", true, false),
19838 ("namespace n { int item;", false, true),
19839 ] {
19840 let tree = parse_cpp(source);
19841 let namespace = tree.root_node().named_child(0).unwrap();
19842 assert_eq!(namespace.kind(), "namespace_definition");
19843 let body = namespace.child_by_field_name("body").unwrap();
19844 assert_eq!(body.kind(), "declaration_list");
19845 let open = body.child(0).unwrap();
19846 let close = body.child(body.child_count() - 1).unwrap();
19847 assert_eq!(open.kind(), "{");
19848 assert!(!open.is_missing());
19849 assert_eq!(close.kind(), "}");
19850 assert_eq!(close.is_missing(), closing_missing);
19851 assert_eq!(
19852 namespace_definition_has_real_braces(namespace),
19853 complete,
19854 "{source}"
19855 );
19856 }
19857 let source = "namespace { namespace detail { struct Derived {}; void run() {} } }";
19858 let tree = parse_cpp(source);
19859 assert!(!tree.root_node().has_error());
19860 let anonymous = tree.root_node().named_child(0).unwrap();
19861 assert_eq!(anonymous.kind(), "namespace_definition");
19862 assert!(anonymous.child_by_field_name("name").is_none());
19863 let run = named_node_at(&tree, source, "run");
19864 assert_eq!(
19865 enclosing_namespace_components(run, source),
19866 Some(vec!["detail".to_owned()])
19867 );
19868 }
19869
19870 #[test]
19871 fn resolver_c_block_envelope_grammar_table() {
19872 for (source, name, damaged, recovered) in [
19873 ("void outer(void) { nested() {} }", "nested", false, false),
19874 (
19875 "void outer(void) { Apply(first, ^(Bucket b) { return b.value; }); }",
19876 "Apply",
19877 true,
19878 true,
19879 ),
19880 ] {
19881 let tree = parse_cpp(source);
19882 let node = named_node_at(&tree, source, name);
19883 let declarator = node.parent().unwrap();
19884 assert_eq!(
19885 declarator.kind(),
19886 "function_declarator",
19887 "{source}: {:?}",
19888 tree.root_node()
19889 );
19890 assert_eq!(declarator.child_by_field_name("declarator"), Some(node));
19891 assert_eq!(declarator.has_error(), damaged, "{source}");
19892 let definition = declarator.parent().unwrap();
19893 assert_eq!(definition.kind(), "function_definition");
19894 assert!(definition.child_by_field_name("type").is_none());
19895 assert!(definition.child_by_field_name("body").is_some());
19896 assert_eq!(
19897 recovered_c_block_call_envelope(declarator).is_some(),
19898 recovered,
19899 "{source}"
19900 );
19901 }
19902 }
19903
19904 #[test]
19905 fn resolver_linkage_guard_grammar_table() {
19906 for (source, recovered, visible) in [
19907 (
19908 "#ifdef __cplusplus\nextern \"C\" {\n#endif\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19909 false,
19910 true,
19911 ),
19912 (
19913 "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19914 true,
19915 false,
19916 ),
19917 (
19918 "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n}\n#endif\n",
19919 true,
19920 false,
19921 ),
19922 ] {
19923 let tree = parse_cpp(source);
19924 let name = named_node_at(&tree, source, "target");
19925 let mut declaration = name;
19926 while declaration.kind() != "declaration" {
19927 declaration = declaration.parent().unwrap();
19928 }
19929 assert_eq!(
19930 has_ancestor_kind(declaration, "linkage_specification"),
19931 recovered,
19932 "{source}"
19933 );
19934 assert_eq!(
19935 has_ancestor_kind(declaration, "preproc_ifdef"),
19936 recovered,
19937 "{source}"
19938 );
19939 if !recovered {
19940 assert!(!tree.root_node().has_error());
19941 let open = tree.root_node().named_child(0).unwrap();
19942 assert_eq!(open.kind(), "preproc_linkage_open");
19943 assert!(open.child_by_field_name("value").is_some());
19944 }
19945 assert_eq!(
19946 callable_preprocessor_context_is_visible(declaration, source),
19947 visible,
19948 "{source}"
19949 );
19950 }
19951 }
19952
19953 #[test]
19954 #[hardwon("#6902: grammar 0.24.4 parsed an unnamed enum's base as its name")]
19955 fn resolver_anonymous_enum_base_grammar_boundary() {
19956 let source = "typedef unsigned Underlying; enum : Underlying { Value };";
19957 let tree = parse_cpp(source);
19958 assert!(
19959 !tree.root_node().has_error(),
19960 "{}",
19961 tree.root_node().to_sexp()
19962 );
19963 let start = source.find("Underlying {").unwrap();
19964 let base = tree
19965 .root_node()
19966 .named_descendant_for_byte_range(start, start + "Underlying".len())
19967 .unwrap();
19968 let enumeration = base.parent().unwrap();
19969 assert_eq!(enumeration.kind(), "enum_specifier");
19970 assert!(enumeration.child_by_field_name("name").is_none());
19971 assert_eq!(enumeration.child_by_field_name("base"), Some(base));
19972 assert!(!is_declaration_name(base));
19973 }
19974
19975 #[test]
19976 #[hardwon(
19977 "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: spliced macro tokens need physical source coordinates"
19978 )]
19979 fn macro_splices_preserve_statement_origin_ranges() {
19980 for newline in ["\n", "\r\n"] {
19981 for (replacement, name, written) in [
19982 (
19983 format!("jo\\{newline}ined\\{newline}();\\{newline}after();"),
19984 "joined",
19985 format!("jo\\{newline}ined"),
19986 ),
19987 (
19988 format!("do {{ if (ready) bl\\{newline}ock }} while (0)"),
19989 "block",
19990 format!("bl\\{newline}ock"),
19991 ),
19992 ] {
19993 let parameters = vec!["block".to_owned()];
19994 let (source, tree, original_offsets) =
19995 VisibilityIndex::parse_macro_replacement_body(&replacement, ¶meters)
19996 .expect("joined replacement parses");
19997 let body = ParsedReplacementBody {
19998 source,
19999 tree,
20000 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
20001 parameters,
20002 original_offsets,
20003 };
20004 let nodes = crate::grammar_test_support::nodes(body.tree.root_node());
20005 let matching: Vec<_> = nodes
20006 .iter()
20007 .filter(|node| {
20008 node.kind() == "identifier" && node_text(**node, &body.source) == name
20009 })
20010 .collect();
20011 assert_eq!(matching.len(), 1, "{replacement}");
20012 let range = body.file_range(*matching[0], 0);
20013 assert_eq!(&replacement[range], written);
20014 for node in nodes.iter().filter(|node| {
20015 node.kind() == "identifier" && node_text(**node, &body.source) == "after"
20016 }) {
20017 assert_eq!(&replacement[body.file_range(*node, 0)], "after");
20018 }
20019 }
20020 }
20021 }
20022
20023 #[test]
20024 #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
20025 fn issue_3089_statement_formal_at_end_of_replacement() {
20026 let parameters = vec!["handle".to_owned(), "block".to_owned()];
20027 for replacement in [
20028 "do { header_event_t* event; if ((handle)->active) block } while (0)",
20029 "do { header_event_t* event; block } while (0)",
20030 ] {
20031 assert!(
20032 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
20033 .is_some(),
20034 "{replacement}"
20035 );
20036 }
20037 }
20038 use super::*;
20039 use brokk_bifrost_test_macros::hardwon;
20040
20041 #[test]
20042 #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
20043 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
20044 let source = "int size(void) { return sizeof(((Payload))); }\n";
20045 let mut parser = Parser::new();
20046 parser
20047 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20048 .expect("C++ grammar");
20049 let tree = parser.parse(source, None).expect("fixture tree");
20050 let start = source.find("Payload").expect("sizeof operand");
20051 let node = tree
20052 .root_node()
20053 .named_descendant_for_byte_range(start, start + "Payload".len())
20054 .expect("focused operand");
20055 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
20056 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
20057
20058 assert_eq!(node.kind(), "identifier");
20059 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
20060 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
20061 }
20062
20063 #[test]
20064 fn recovered_macro_actuals_keep_their_cst_boundaries() {
20065 for (source, recovered) in [
20066 ("int f() { return M(Type, value); }", false),
20067 ("int f() { return M(Type, ^(int n){ return n; }); }", true),
20068 ] {
20069 let tree = parse_cpp(source);
20070 let first = named_node_at(&tree, source, "Type");
20071 let (call, index, actual) = c_function_macro_argument(first)
20072 .unwrap_or_else(|| panic!("first actual {source}: {}", tree.root_node().to_sexp()));
20073 assert_eq!(index, 0);
20074 assert_eq!(node_text(actual, source), "Type");
20075 let arguments = call.child_by_field_name("arguments").unwrap();
20076 let raw = arguments.named_child(0).unwrap();
20077 if recovered {
20078 assert_eq!(raw.kind(), "binary_expression");
20079 assert_eq!(raw.child_by_field_name("left"), Some(actual));
20080 let inside = named_node_at(&tree, source, "return n;");
20081 let (_, index, block) = c_function_macro_argument(inside).expect("block actual");
20082 assert_eq!(index, 1);
20083 assert_eq!(block.kind(), "compound_literal_expression");
20084 assert_eq!(node_text(block, source), "(int n){ return n; }");
20085 assert_eq!(raw.child_by_field_name("right"), Some(block));
20086 } else {
20087 assert_eq!(raw, actual);
20088 let second = named_node_at(&tree, source, "value");
20089 let (_, index, actual) = c_function_macro_argument(second).expect("second actual");
20090 assert_eq!(index, 1);
20091 assert_eq!(node_text(actual, source), "value");
20092 }
20093 }
20094 }
20095
20096 fn parse_cpp(source: &str) -> Tree {
20097 crate::diagnostics::parse_cpp_tree(source)
20098 }
20099
20100 #[test]
20101 #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
20102 fn cpp_new_expression_type_comes_from_the_structured_type_field() {
20103 let source = "auto value = new ns::Box<int> [ 3 ];";
20104 let tree = parse_cpp(source);
20105 let mut pending = vec![tree.root_node()];
20106 let allocation = loop {
20107 let node = pending.pop().expect("new expression in fixture");
20108 if node.kind() == "new_expression" {
20109 break node;
20110 }
20111 let mut cursor = node.walk();
20112 pending.extend(
20113 node.named_children(&mut cursor)
20114 .collect::<Vec<_>>()
20115 .into_iter()
20116 .rev(),
20117 );
20118 };
20119
20120 let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
20121 assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
20122 assert_eq!(node_text(type_node, source), "ns::Box<int>");
20123 assert_eq!(
20124 allocation
20125 .child_by_field_name("declarator")
20126 .expect("array declarator")
20127 .kind(),
20128 "new_declarator"
20129 );
20130
20131 let old_split = normalize_cpp_whitespace(node_text(allocation, source));
20132 let old_split = old_split
20133 .strip_prefix("new ")
20134 .unwrap_or(old_split.as_str())
20135 .split(['(', '{'])
20136 .next()
20137 .expect("old split result");
20138 assert_ne!(old_split.trim(), node_text(type_node, source));
20139 }
20140
20141 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
20142 let start = source.find(needle).expect("fixture needle");
20143 tree.root_node()
20144 .named_descendant_for_byte_range(start, start + needle.len())
20145 .expect("node at needle")
20146 }
20147
20148 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
20149 let tree = crate::diagnostics::parse_cpp_tree(source);
20150 PreparedSyntaxTree::new(
20151 PreparedSyntaxSource::Exact(Arc::from(source)),
20152 tree,
20153 compute_line_starts(source),
20154 LanguageDialect::Standard(Language::Cpp),
20155 PreparedSourceOrigin::Disk,
20156 None,
20157 )
20158 }
20159
20160 fn unresolved_include_before(source: &str, reference: &str) -> bool {
20161 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
20162 let prepared = prepared_cpp(source);
20163 let facts = collect_structured_include_facts(&prepared);
20164 let include_targets = IncludeTargetIndex::build([&file]);
20165 has_unresolved_include_visible_before_in_prepared(
20166 &file,
20167 &prepared,
20168 &include_targets,
20169 &facts,
20170 source.find(reference).expect("reference fixture"),
20171 &|guards| guards.cloned(),
20172 )
20173 }
20174
20175 #[test]
20176 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20177 fn unresolved_include_before_reference_is_visible() {
20178 let source = "#include \"missing.h\"\nint use = Missing;\n";
20179 assert!(unresolved_include_before(source, "Missing"));
20180 }
20181
20182 #[test]
20183 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20184 fn unresolved_include_after_reference_is_not_visible() {
20185 let source = "int use = Missing;\n#include \"missing.h\"\n";
20186 assert!(!unresolved_include_before(source, "Missing"));
20187 }
20188
20189 #[test]
20190 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20191 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
20192 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
20193 assert!(!unresolved_include_before(source, "Missing"));
20194 }
20195
20196 #[test]
20197 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20198 fn unresolved_include_in_current_branch_is_visible() {
20199 let source =
20200 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
20201 assert!(unresolved_include_before(source, "Missing"));
20202 }
20203
20204 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
20209namespace matchers {
20210 namespace detail {
20211 class API [[nodiscard]] First {
20212 public:
20213 int value() const { return count_ + 1; }
20214 private:
20215 int count_;
20216 };
20217 class API [[nodiscard]] Second {
20218 public:
20219 int value() const { return count_ + 2; }
20220 private:
20221 int count_;
20222 };
20223 } // namespace detail
20224
20225 template <typename T>
20226 void tail_function(MatcherBase<T> const& value);
20227
20228 class TailClass {};
20229} // namespace matchers
20230} // namespace app
20231
20232struct AfterAll {};
20233"#;
20234
20235 #[test]
20236 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
20237 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
20238 let source = STOLEN_BRACE_CASCADE;
20239 let tree = parse_cpp(source);
20240 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
20241
20242 let tail_class = named_node_at(&tree, source, "TailClass");
20243 assert!(
20244 !has_ancestor_kind(tail_class, "namespace_definition"),
20245 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
20246 );
20247 let displaced = named_node_at(&tree, source, "Second");
20248 assert_eq!(
20249 enclosing_namespace_components(displaced, source),
20250 Some(vec!["app".to_string(), "matchers".to_string()]),
20251 "the fixture must displace the second class out of detail"
20252 );
20253
20254 let components = |needle: &str| {
20255 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
20256 };
20257 assert_eq!(components("First"), ["app", "matchers", "detail"]);
20258 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
20259 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
20260 assert_eq!(components("tail_function"), ["app", "matchers"]);
20261 assert_eq!(components("TailClass"), ["app", "matchers"]);
20262 assert!(components("AfterAll").is_empty());
20263 }
20264
20265 #[test]
20266 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
20267 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
20268 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
20269 let tree = parse_cpp(clean);
20270 assert!(!tree.root_node().has_error());
20271 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
20272
20273 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
20276 let tree = parse_cpp(damaged);
20277 assert!(tree.root_node().has_error());
20278 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
20279 assert_eq!(
20280 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
20281 ["a"]
20282 );
20283 }
20284
20285 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
20292
20293 class Target {
20294 int value_;
20295 };
20296
20297 template <typename T>
20298 class Holder {
20299 public:
20300 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
20301
20302#define HOLDER_DEFINE_OP( id, op ) \
20303 template <typename U> \
20304 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
20305 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
20306 return Target{}; \
20307 }
20308
20309 HOLDER_DEFINE_OP( equal, == )
20310#undef HOLDER_DEFINE_OP
20311 T m_lhs;
20312 };
20313
20314 class Tail {};
20315}
20316"#;
20317
20318 #[test]
20319 #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
20320 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
20321 let source = COLLAPSED_NAMESPACE_HEAD;
20322 let tree = parse_cpp(source);
20323 let target = named_node_at(&tree, source, "class Target");
20324
20325 assert!(
20331 tree.root_node().has_error(),
20332 "the fixture must retain the macro invocation's parse damage"
20333 );
20334 assert!(
20335 has_ancestor_kind(target, "namespace_definition"),
20336 "the namespace head survives: the class keeps its parsed namespace ancestor"
20337 );
20338
20339 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
20340 assert_eq!(
20341 index.enclosing_namespace_components(target, source),
20342 ["app"]
20343 );
20344 assert_eq!(
20345 index.enclosing_namespace_levels(target, source),
20346 vec![(
20347 "app".to_string(),
20348 Range {
20349 start_byte: 0,
20350 end_byte: source.len() - 1,
20351 start_line: 1,
20352 end_line: 25,
20353 },
20354 Range {
20355 start_byte: 10,
20356 end_byte: 13,
20357 start_line: 1,
20358 end_line: 1,
20359 },
20360 )],
20361 "the recovered namespace owns its full construct, not its first member or just its header"
20362 );
20363 }
20364
20365 #[test]
20366 #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
20367 fn namespace_body_name_components_recovers_literal_function_head() {
20368 let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
20374 let tree = parse_cpp(source);
20375 let keyword_start = source.find("namespace recovered").unwrap();
20376 let keyword = tree
20377 .root_node()
20378 .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
20379 .expect("literal keyword node");
20380 assert_eq!(keyword.kind(), "type_identifier");
20385 assert_eq!(keyword.child_count(), 0);
20386 assert!(!keyword.is_missing() && !keyword.has_error());
20387 assert_eq!(node_text(keyword, source), "namespace");
20388 let wrapper = keyword.parent().expect("actual wrapper");
20389 assert_eq!(wrapper.kind(), "function_definition");
20390 let mut cursor = wrapper.walk();
20391 let children = wrapper
20392 .children(&mut cursor)
20393 .filter(|child| child.kind() != "comment")
20394 .collect::<Vec<_>>();
20395 assert_eq!(
20396 children.len(),
20397 4,
20398 "all named and unnamed children accounted: {children:?}"
20399 );
20400 let prefix = children[0];
20401 assert_eq!(prefix.kind(), "macro_modifier");
20402 assert!(!prefix.is_missing() && !prefix.has_error());
20403 assert_eq!(node_text(prefix, source), "OPAQUE_PREFIX");
20404 assert_eq!(children[1], keyword);
20405 assert_eq!(wrapper.child_by_field_name("type"), Some(keyword));
20406 let name = children[2];
20407 assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
20408 assert_eq!(name.child_count(), 0);
20409 assert!(!name.is_missing() && !name.has_error());
20410 assert_eq!(node_text(name, source), "recovered");
20411 assert_eq!(wrapper.child_by_field_name("declarator"), Some(name));
20412 let body = wrapper.child_by_field_name("body").expect("actual body");
20413 assert_eq!(children[3], body);
20414 assert_eq!(body.kind(), "compound_statement");
20415 assert!(!body.is_missing());
20416 let open = body.child(0).expect("actual first child");
20417 assert_eq!(open.kind(), "{");
20418 assert!(!open.is_missing() && !open.has_error());
20419 assert_eq!(open.start_byte(), body.start_byte());
20420 let (head, names) = namespace_body_name_components(wrapper, body, source)
20421 .expect("strict synthetic literal head recognized");
20422 assert_eq!(head, keyword);
20423 assert_eq!(
20424 names
20425 .iter()
20426 .map(|(name, _)| name.as_str())
20427 .collect::<Vec<_>>(),
20428 ["recovered"]
20429 );
20430 assert_eq!(
20431 (names[0].1.start_byte, names[0].1.end_byte),
20432 (name.start_byte(), name.end_byte())
20433 );
20434 let inside = named_node_at(&tree, source, "int member;");
20435 assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
20436 }
20437
20438 #[test]
20439 #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
20440 fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
20441 for refusal in [
20442 "int ordinary(int value) { return value; }",
20443 "OPAQUE_PREFIX namespace_ recovered { int member; }",
20444 "OPAQUE_PREFIX namespace recovered other { int member; }",
20445 "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
20446 ] {
20447 let tree = parse_cpp(refusal);
20448 let mut stack = vec![tree.root_node()];
20449 let mut helper_calls = 0;
20450 while let Some(node) = stack.pop() {
20451 if node.kind() == "function_definition"
20452 && let Some(body) = node.child_by_field_name("body")
20453 {
20454 helper_calls += 1;
20455 assert_eq!(
20456 (node.start_byte(), node.end_byte()),
20457 (0, refusal.len()),
20458 "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
20459 );
20460 assert!(
20461 namespace_body_name_components(node, body, refusal).is_none(),
20462 "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
20463 );
20464 }
20465 let mut cursor = node.walk();
20466 stack.extend(node.named_children(&mut cursor));
20467 }
20468 assert_eq!(
20469 helper_calls,
20470 1,
20471 "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
20472 tree.root_node()
20473 );
20474 }
20475
20476 for absent in [
20482 "OPAQUE_PREFIX namespace recovered = other { int member; }",
20483 "OPAQUE_PREFIX namespace recovered int member;",
20484 ] {
20485 let tree = parse_cpp(absent);
20486 let mut stack = vec![tree.root_node()];
20487 let mut helper_candidates = 0;
20488 while let Some(node) = stack.pop() {
20489 if node.kind() == "function_definition"
20490 && node.child_by_field_name("body").is_some()
20491 {
20492 helper_candidates += 1;
20493 }
20494 let mut cursor = node.walk();
20495 stack.extend(node.named_children(&mut cursor));
20496 }
20497 assert_eq!(
20498 helper_candidates,
20499 0,
20500 "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
20501 tree.root_node()
20502 );
20503 }
20504 }
20505
20506 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
20515
20516namespace app {
20517namespace detail {
20518
20519bool check(bool active) {
20520 APP_IF_ACTIVE
20521 return true;
20522 }
20523 return false;
20524}
20525
20526class Tail {};
20527
20528} // namespace detail
20529} // namespace app
20530"#;
20531
20532 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
20539namespace compile_time {
20540namespace number_parsing {
20541consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
20542 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
20543 auto digit = [](uint8_t c) -> uint32_t {
20544 }
20545 }
20546 if (a) {
20547 while (a) {
20548 }
20549 }
20550 switch (c) {
20551 case '{': {
20552 }
20553 }
20554 }
20555 while (a) {
20556 cursor += field.second;
20557 if (a) {
20558 case '{': {
20559 if (a) {
20560 }
20561 }
20562 case 'n': {
20563 if (a) {
20564 }
20565 }
20566 }
20567 }
20568 if (a) {
20569 simdjson_consteval_error("Expected '}'");
20570 }
20571}
20572}
20573}
20574"#;
20575
20576 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
20578 let mut stack = vec![root];
20579 let mut counts = (0, 0);
20580 while let Some(node) = stack.pop() {
20581 match (node.kind(), node.is_missing()) {
20582 ("{", false) => counts.0 += 1,
20583 ("}", false) => counts.1 += 1,
20584 _ => {}
20585 }
20586 let mut cursor = node.walk();
20587 stack.extend(node.children(&mut cursor));
20588 }
20589 counts
20590 }
20591
20592 fn namespace_open_byte(source: &str, head: &str) -> usize {
20594 let at = source.find(head).expect("fixture namespace head");
20595 at + head.len() - 1
20596 }
20597
20598 fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
20599 let index = OrphanedNamespaceScopeIndex::build(root, source);
20600 for head in heads {
20601 assert_eq!(
20602 index.matching_close_brace(namespace_open_byte(source, head)),
20603 None,
20604 "{head} must not claim a close belonging to an inner scope"
20605 );
20606 }
20607 }
20608
20609 #[test]
20610 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20611 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
20612 let source = MACRO_OPENED_SCOPE;
20613 let tree = parse_cpp(source);
20614 let root = tree.root_node();
20615 assert_eq!(
20616 real_brace_token_counts(root),
20617 (4, 5),
20618 "the fixture must reproduce the loss: one scope the source opens \
20619 has no `{{` token, so the stream holds an extra real close"
20620 );
20621
20622 assert_namespace_heads_have_no_matching_close(
20623 root,
20624 source,
20625 &["namespace app {", "namespace detail {"],
20626 );
20627 }
20628
20629 #[test]
20630 #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
20631 fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
20632 let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
20636 let tree = parse_cpp(&source);
20637 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
20638 let after_all = named_node_at(&tree, &source, "struct AfterAll");
20639 assert!(
20640 index
20641 .enclosing_namespace_components(after_all, &source)
20642 .is_empty()
20643 );
20644 }
20645
20646 #[test]
20647 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20648 fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
20649 let source = REFLECTION_CASE_LABEL_RECOVERY;
20650 let tree = parse_cpp(source);
20651 let root = tree.root_node();
20652 let mut stack = vec![root];
20653 let mut mis_lexed_opens = Vec::new();
20654 while let Some(node) = stack.pop() {
20655 if node.kind() == "character"
20656 && &source[node.start_byte()..node.end_byte()] == "{"
20657 && node
20658 .parent()
20659 .is_some_and(|parent| parent.kind() != "char_literal")
20660 {
20661 mis_lexed_opens.push(node.start_position().row + 1);
20662 }
20663 let mut cursor = node.walk();
20664 stack.extend(node.children(&mut cursor));
20665 }
20666 assert!(
20667 mis_lexed_opens.is_empty(),
20668 "ABI15 should keep each case-label `{{` inside its `char_literal`; \
20669 the old ABI14 mis-lex is no longer a valid recovery witness"
20670 );
20671 assert_eq!(
20672 real_brace_token_counts(root),
20673 (17, 17),
20674 "the improved parse retains balanced real brace tokens"
20675 );
20676 assert_eq!(root.kind(), "translation_unit");
20677 assert!(root.has_error(), "the malformed body remains incomplete");
20678 let index = OrphanedNamespaceScopeIndex::build(root, source);
20679 assert_eq!(
20680 index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
20681 None,
20682 "a missing outer close must not borrow an inner namespace's real close"
20683 );
20684 for (head, close) in [
20685 ("namespace compile_time {", source.len() - 2),
20686 ("namespace number_parsing {", source.len() - 4),
20687 ] {
20688 assert_eq!(
20689 index
20690 .matching_close_brace(namespace_open_byte(source, head))
20691 .map(|range| range.start_byte),
20692 Some(close),
20693 "{head} retains its own real close"
20694 );
20695 }
20696 }
20697
20698 #[test]
20699 #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
20700 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
20701 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
20702 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
20703 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
20704 assert!(indexed_namespace_path_is_recoverable(
20705 &["cache".to_string()],
20706 &indexed,
20707 1,
20708 ));
20709 }
20710
20711 #[test]
20712 #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
20713 fn sort_lookup_units_totally_orders_every_identity_field() {
20714 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
20715 let base = CodeUnit::with_signature(
20716 file.clone(),
20717 CodeUnitType::Function,
20718 "scope",
20719 "value",
20720 Some("()".to_string()),
20721 false,
20722 );
20723 let different_kind = CodeUnit::with_signature(
20724 file.clone(),
20725 CodeUnitType::Field,
20726 "scope",
20727 "value",
20728 Some("()".to_string()),
20729 false,
20730 );
20731 let synthetic = base.with_synthetic(true);
20732
20733 let interner = segment_interner();
20734 let mut member_fq = FqName::new();
20735 member_fq.push(interner.intern("scope", SegmentKind::Package));
20736 member_fq.push(interner.intern("value", SegmentKind::Member));
20737 let different_package_boundary = CodeUnit::from_fq(
20738 file.clone(),
20739 CodeUnitType::Function,
20740 member_fq,
20741 0,
20742 Some("()".to_string()),
20743 false,
20744 );
20745
20746 let mut unknown_fq = FqName::new();
20747 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
20748 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
20749 let different_segment_kind = CodeUnit::from_fq(
20750 file,
20751 CodeUnitType::Function,
20752 unknown_fq,
20753 1,
20754 Some("()".to_string()),
20755 false,
20756 );
20757
20758 let input = vec![
20759 base,
20760 different_kind,
20761 synthetic,
20762 different_package_boundary,
20763 different_segment_kind,
20764 ];
20765 let mut expected = input.clone();
20766 sort_lookup_units(&mut expected);
20767 assert!(expected.windows(2).all(|pair| {
20768 let mut ordered = pair.to_vec();
20769 sort_lookup_units(&mut ordered);
20770 ordered == pair && pair[0] != pair[1]
20771 }));
20772
20773 let mut reversed = input.clone();
20774 reversed.reverse();
20775 sort_lookup_units(&mut reversed);
20776 assert_eq!(reversed, expected);
20777
20778 let mut rotated = input;
20779 rotated.rotate_left(2);
20780 sort_lookup_units(&mut rotated);
20781 assert_eq!(rotated, expected);
20782 }
20783
20784 #[test]
20785 #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
20786 fn structured_conditional_array_bound_preserves_the_real_guard() {
20787 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";
20788 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
20789 let parse = |source: &str| {
20790 let mut parser = Parser::new();
20791 parser
20792 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20793 .expect("C++ grammar");
20794 parser.parse(source, None).expect("fixture tree")
20795 };
20796
20797 let tree = parse(damaged);
20798 let root = tree.root_node();
20799 assert!(
20800 !root.has_error(),
20801 "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
20802 root.to_sexp()
20803 );
20804 let target = damaged.find("target").expect("target byte");
20805 let declaration = root
20806 .descendant_for_byte_range(target, target + "target".len())
20807 .and_then(|mut node| {
20808 loop {
20809 if node.kind() == "declaration" {
20810 break Some(node);
20811 }
20812 node = node.parent()?;
20813 }
20814 })
20815 .expect("declaration after the displaced terminator");
20816 let outer = declaration
20817 .parent()
20818 .filter(|node| node.kind() == "preproc_ifdef")
20819 .expect("ordinary outer include guard");
20820 let bound = outer
20821 .named_children(&mut outer.walk())
20822 .filter(|node| node.kind() == "declaration")
20823 .filter_map(|node| node.child_by_field_name("declarator"))
20824 .find(|node| node.kind() == "array_declarator")
20825 .and_then(|node| node.child_by_field_name("size"))
20826 .expect("structured conditional array bound");
20827 assert_eq!(bound.kind(), "preproc_array_size");
20828 let conditional = bound
20829 .named_children(&mut bound.walk())
20830 .find(|node| node.kind() == "preproc_ifdef")
20831 .expect("array-size conditional");
20832 assert!(conditional.end_byte() <= declaration.start_byte());
20833 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20834 assert!(
20835 !std::iter::successors(Some(declaration), |node| node.parent())
20836 .any(|ancestor| same_node(ancestor, conditional)),
20837 "the declaration must not inherit the array-size feature guard"
20838 );
20839 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
20840 assert!(preprocessor_conditional_contains_descendant(
20841 outer,
20842 declaration
20843 ));
20844
20845 let tree = parse(guarded);
20846 let conditional = tree
20847 .root_node()
20848 .named_child(0)
20849 .filter(|node| node.kind() == "preproc_ifdef")
20850 .expect("ordinary conditional");
20851 let declaration = conditional
20852 .named_children(&mut conditional.walk())
20853 .find(|node| node.kind() == "declaration")
20854 .expect("guarded declaration");
20855 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20856 assert!(preprocessor_conditional_contains_descendant(
20857 conditional,
20858 declaration
20859 ));
20860
20861 let damaged_alternative = format!(
20862 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
20863 "UNUSED(value)\n".repeat(64)
20864 );
20865 let tree = parse(&damaged_alternative);
20866 let conditional = tree
20867 .root_node()
20868 .named_child(0)
20869 .filter(|node| node.kind() == "preproc_ifdef")
20870 .expect("outer conditional with an alternative");
20871 assert!(conditional.has_error());
20872 assert!(conditional.child_by_field_name("alternative").is_some());
20873 assert!(
20874 conditional
20875 .child(conditional.child_count() - 1)
20876 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
20877 );
20878 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20879
20880 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";
20881 let tree = parse(split_declaration);
20882 let root = tree.root_node();
20883 let split = root
20890 .named_children(&mut root.walk())
20891 .find(|node| node.kind() == "type_definition")
20892 .expect("the split declaration folds into one type definition");
20893 assert_eq!(
20894 split.end_position().row + 1,
20895 9,
20896 "the split declaration ends at its own declarator line"
20897 );
20898 let target = split_declaration
20899 .find("static int target")
20900 .expect("target byte");
20901 assert!(split.end_byte() <= target);
20902 let target_node = root
20903 .descendant_for_byte_range(target, target + "static".len())
20904 .expect("target node");
20905 assert!(
20906 !std::iter::successors(Some(target_node), |node| node.parent())
20907 .any(|ancestor| ancestor.kind() == "preproc_ifdef" && ancestor.has_error()),
20908 "the declaration after the shredded split declaration must not \
20909 inherit a damaged conditional ancestor"
20910 );
20911 }
20912
20913 #[test]
20914 #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
20915 fn fragmented_reference_guard_is_recovered() {
20916 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";
20917 let mut parser = Parser::new();
20918 parser
20919 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20920 .expect("C++ grammar");
20921 let tree = parser.parse(source, None).expect("fixture tree");
20922 let start = source.rfind("helper").expect("reference byte");
20923 let node = tree
20924 .root_node()
20925 .descendant_for_byte_range(start, start + "helper".len())
20926 .expect("reference node");
20927 let mut expected = HashSet::default();
20928 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
20929 vec![
20930 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
20931 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
20932 ],
20933 )));
20934 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
20935 }
20936
20937 #[test]
20938 #[hardwon(
20939 "#6325: grammar 0.24.4 displaced #else/#elif into a declaration ERROR and the branch boundary vanished"
20940 )]
20941 fn displaced_branch_directives_decide_guards_without_proving_unreadable_branches() {
20942 let parse = |source: &str| {
20943 let mut parser = Parser::new();
20944 parser
20945 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20946 .expect("C++ grammar");
20947 parser.parse(source, None).expect("fixture tree")
20948 };
20949 fn at<'tree>(root: Node<'tree>, source: &str, needle: &str, token: &str) -> Node<'tree> {
20950 let start = source.find(needle).expect("needle") + needle.find(token).expect("token");
20951 root.descendant_for_byte_range(start, start + token.len())
20952 .expect("token node")
20953 }
20954 let guards = |node: Node<'_>, source: &str| preprocessor_guard_environment(node, source);
20955 let set = |items: &[PreprocessorGuard]| items.iter().cloned().collect::<HashSet<_>>();
20956
20957 let source = "namespace n {\n#ifdef A\nBEGIN_NS\nint x;\nEND_NS\n#else\nBEGIN_NS\nclass T {};\nEND_NS\n#define MODE 2\n#endif\n}\n";
20961 let tree = parse(source);
20962 let root = tree.root_node();
20963 let x = at(root, source, "int x", "x");
20964 let conditional = std::iter::successors(Some(x), |node| node.parent())
20965 .find(|node| node.kind() == "preproc_ifdef")
20966 .expect("ifdef");
20967 assert!(conditional.child_by_field_name("alternative").is_none());
20968 let directive = cpp_displaced_preprocessor_branch(conditional).expect("displaced #else");
20969 assert_eq!(directive.kind(), "#else");
20970 assert_eq!(directive.parent().map(|node| node.kind()), Some("ERROR"));
20971 let defined = PreprocessorGuard::Defined("A".to_string());
20972 assert_eq!(guards(x, source), Some(set(std::slice::from_ref(&defined))));
20973 let class_name = at(root, source, "class T", "T");
20974 assert_eq!(guards(class_name, source), Some(set(&[defined.negated()])));
20975 let define = at(root, source, "#define MODE", "MODE")
20976 .parent()
20977 .filter(|node| node.kind() == "preproc_def")
20978 .expect("macro event");
20979 let mut cache = PreprocessorContextCache::default();
20980 assert!(matches!(
20981 preprocessor_branch_of_descendant(conditional, define, &mut cache),
20982 PreprocessorBranch::DisplacedElse
20983 ));
20984 assert_eq!(
20985 preprocessor_conditional_family_for_declaration_with_cache(class_name, &mut cache),
20986 Some(conditional),
20987 "a displaced #else completes the family"
20988 );
20989 assert_eq!(
20990 callable_guard_completion_byte(class_name, source),
20991 Some(conditional.end_byte())
20992 );
20993
20994 let source = "namespace n {\n#if A\nBEGIN_NS\nint x;\nEND_NS\n#elif B\nBEGIN_NS\nclass T {};\nEND_NS\n#else\nBEGIN_NS\nstruct T {};\nEND_NS\n#endif\n}\n";
20998 let tree = parse(source);
20999 let root = tree.root_node();
21000 let x = at(root, source, "int x", "x");
21001 let conditional = std::iter::successors(Some(x), |node| node.parent())
21002 .find(|node| node.kind() == "preproc_if")
21003 .expect("if");
21004 assert!(conditional.child_by_field_name("alternative").is_none());
21005 assert_eq!(
21006 cpp_displaced_preprocessor_branch(conditional).map(|node| node.kind()),
21007 Some("#elif")
21008 );
21009 let truthy = PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy("A".to_string()));
21010 assert_eq!(guards(x, source), Some(set(&[truthy])));
21011 let mut cache = PreprocessorContextCache::default();
21012 for needle in ["class T", "struct T"] {
21013 let name = at(root, source, needle, "T");
21014 assert_eq!(
21015 guards(name, source),
21016 None,
21017 "{needle} after a displaced #elif"
21018 );
21019 assert!(matches!(
21020 preprocessor_branch_of_descendant(conditional, name, &mut cache),
21021 PreprocessorBranch::Unreadable
21022 ));
21023 assert_eq!(
21024 preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache),
21025 None,
21026 "{needle}: an unreadable #elif branch does not complete the family"
21027 );
21028 assert_eq!(callable_guard_completion_byte(name, source), None);
21029 }
21030
21031 let source = "namespace n {\n#if defined(A)\nBEGIN_NS\nint x;\nEND_NS\n#elif defined(B)\nBEGIN_NS\nclass T {};\nEND_NS\n#else\nBEGIN_NS\nclass T {};\nEND_NS\n#endif\n}\n";
21034 let tree = parse(source);
21035 let root = tree.root_node();
21036 let start = source.rfind("class T").expect("else class") + "class ".len();
21037 let name = root
21038 .descendant_for_byte_range(start, start + 1)
21039 .expect("else class name");
21040 let elif = std::iter::successors(Some(name), |node| node.parent())
21041 .find(|node| node.kind() == "preproc_elif")
21042 .expect("structured elif");
21043 assert_eq!(
21044 cpp_displaced_preprocessor_branch(elif).map(|node| node.kind()),
21045 Some("#else")
21046 );
21047 assert_eq!(
21048 guards(name, source),
21049 Some(set(&[
21050 PreprocessorGuard::Defined("A".to_string()).negated(),
21051 PreprocessorGuard::Defined("B".to_string()).negated(),
21052 ]))
21053 );
21054 let mut cache = PreprocessorContextCache::default();
21055 assert!(
21056 preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache).is_some()
21057 );
21058 }
21059
21060 #[test]
21061 #[hardwon(
21062 "#2184: grammar 0.24.4 reads a macro-decorated return type cleanly, so the recovered-only same-file alias gate stopped matching"
21063 )]
21064 fn macro_modifier_decorated_type_matches_the_recovered_gate_scope() {
21065 let source = "API static inline T make(void);\nAPI T value;\nvoid take(API T p);\nstatic inline T plain(void);\nAPI ns::T qualified(void);\nAPI static inline T body(void) { return 0; }\n";
21066 let mut parser = Parser::new();
21067 parser
21068 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21069 .expect("C++ grammar");
21070 let tree = parser.parse(source, None).expect("fixture tree");
21071 let root = tree.root_node();
21072 let type_before = |declared: &str| {
21073 let end = source.find(declared).expect("declared name");
21074 let start = source[..end].rfind('T').expect("type token");
21075 root.descendant_for_byte_range(start, start + 1)
21076 .expect("type node")
21077 };
21078 for declared in ["make(", "value;", "p)", "body("] {
21079 let node = type_before(declared);
21080 assert_eq!(node.kind(), "type_identifier", "{declared}");
21081 assert!(
21082 macro_modifier_decorated_type_node(node),
21083 "{declared}: {}",
21084 node.parent().expect("declaration").to_sexp()
21085 );
21086 }
21087 for declared in ["plain(", "qualified("] {
21088 let node = type_before(declared);
21089 assert!(
21090 !macro_modifier_decorated_type_node(node),
21091 "{declared} is outside the macro-decorated head scope: {}",
21092 node.parent().expect("parent").to_sexp()
21093 );
21094 }
21095 }
21096
21097 #[test]
21098 fn expression_defined_and_ifndef_guards_are_incompatible() {
21099 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
21100 let mut parser = Parser::new();
21101 parser
21102 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21103 .expect("C++ grammar");
21104 let tree = parser.parse(source, None).expect("fixture tree");
21105 let root = tree.root_node();
21106 let selected_start = source.find("selected").expect("selected declaration");
21107 let rejected_start = source.find("rejected").expect("rejected declaration");
21108 let selected = root
21109 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
21110 .expect("selected node");
21111 let rejected = root
21112 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
21113 .expect("rejected node");
21114 let selected_guards =
21115 preprocessor_guard_environment(selected, source).expect("selected guards");
21116 let rejected_guards =
21117 preprocessor_guard_environment(rejected, source).expect("rejected guards");
21118
21119 assert!(
21120 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
21121 "opposite spellings of one macro guard must contradict"
21122 );
21123 }
21124
21125 #[test]
21126 #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
21127 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
21128 let source = r#"#ifdef _WIN32
21129#if defined(__cplusplus)
21130extern "C"
21131#endif
21132int platform_api(void);
21133#endif
21134
21135#ifdef _WIN32
21136static int entropy_target(void) { return 0; }
21137#else
21138#ifdef HAVE_COMMON_RANDOM
21139static int other_target(void) { return 0; }
21140#elif defined(HAVE_GETENTROPY)
21141static int entropy_target(void) { return 1; }
21142static int use_entropy(void) { return entropy_target(); }
21143#endif
21144#endif
21145"#;
21146 let mut parser = Parser::new();
21147 parser
21148 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21149 .expect("C++ grammar");
21150 let tree = parser.parse(source, None).expect("fixture tree");
21151 let start = source.rfind("entropy_target()").expect("reference");
21152 let node = tree
21153 .root_node()
21154 .descendant_for_byte_range(start, start + "entropy_target".len())
21155 .expect("reference node");
21156 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
21157 assert!(
21158 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
21159 "{guards:#?}"
21160 );
21161 assert!(
21162 guards.contains(&PreprocessorGuard::Undefined(
21163 "HAVE_COMMON_RANDOM".to_string()
21164 )),
21165 "{guards:#?}"
21166 );
21167 assert!(
21168 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
21169 "{guards:#?}"
21170 );
21171 assert!(
21172 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
21173 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
21174 );
21175 }
21176
21177 #[test]
21178 #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
21179 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
21180 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";
21181 let mut parser = Parser::new();
21182 parser
21183 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21184 .expect("C++ grammar");
21185 let tree = parser.parse(source, None).expect("fixture tree");
21186 let root = tree.root_node();
21187 let node_at = |text: &str, start: usize| {
21188 root.descendant_for_byte_range(start, start + text.len())
21189 .expect("token node")
21190 };
21191
21192 let key_start = source.find("case KEY").expect("case label") + "case ".len();
21193 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
21194 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
21195 assert!(!is_ordinary_macro_reference_node(node_at(
21196 "ENABLE_KEYS",
21197 guard_start,
21198 )));
21199 }
21200
21201 #[test]
21202 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
21203 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
21204 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";
21205 let mut parser = Parser::new();
21206 parser
21207 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21208 .expect("C++ grammar");
21209 let tree = parser.parse(source, None).expect("fixture tree");
21210 let root = tree.root_node();
21211 let definition_start = source.find("target(void)").expect("definition");
21212 let reference_start = source.rfind("target()").expect("reference");
21213 let definition = root
21214 .descendant_for_byte_range(definition_start, definition_start + "target".len())
21215 .expect("definition node");
21216 let reference = root
21217 .descendant_for_byte_range(reference_start, reference_start + "target".len())
21218 .expect("reference node");
21219 let required =
21220 preprocessor_guard_environment(definition, source).expect("definition guard");
21221 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
21222 assert!(guard_requirements_hold_at_reference(
21223 &required,
21224 Some(&active)
21225 ));
21226 }
21227
21228 #[test]
21229 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
21230 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
21231 let source = "g_autoptr(FuChunkArray) self = make_array();";
21232 let mut parser = Parser::new();
21233 parser
21234 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21235 .expect("C++ grammar");
21236 let tree = parser.parse(source, None).expect("fixture tree");
21237 let statement = tree.root_node().named_child(0).expect("statement");
21238 let binding =
21239 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
21240 assert_eq!(binding.name, "self");
21241 assert_eq!(binding.type_name, "FuChunkArray");
21242 assert_eq!(binding.pointer_depth, 1);
21243
21244 let near_miss = "holder(FuChunkArray) self = make_array();";
21245 let tree = parser.parse(near_miss, None).expect("near-miss tree");
21246 let statement = tree.root_node().named_child(0).expect("statement");
21247 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
21248 }
21249
21250 #[test]
21251 #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
21252 fn boolean_guard_normalization_proves_equivalence_and_implication() {
21253 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
21254 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
21255 let negated_windows_branch =
21256 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
21257 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
21258 assert_eq!(negated_windows_branch, portable);
21259
21260 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
21261 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
21262 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
21263 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
21264 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
21265 assert!(fallback_branch.implies(&fallback_declaration));
21266 assert!(
21267 BooleanGuardExpression::Truthy("FEATURE".to_string())
21268 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
21269 );
21270 assert!(
21271 BooleanGuardExpression::Undefined("FEATURE".to_string())
21272 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
21273 );
21274 assert!(
21275 !BooleanGuardExpression::Defined("FEATURE".to_string())
21276 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
21277 );
21278 assert!(!fallback_declaration.implies(&fallback_branch));
21279 }
21280
21281 #[test]
21282 #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
21283 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
21284 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";
21285 let mut parser = Parser::new();
21286 parser
21287 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21288 .expect("C++ grammar");
21289 let tree = parser.parse(source, None).expect("fixture tree");
21290 let root = tree.root_node();
21291 let call = |marker: &str| {
21292 let start = source.find(marker).expect("call marker");
21293 let mut node = root
21294 .descendant_for_byte_range(start, start + "helper".len())
21295 .expect("call name node");
21296 loop {
21297 if node.kind() == "call_expression" {
21298 break node;
21299 }
21300 node = node.parent().expect("call expression ancestor");
21301 }
21302 };
21303 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
21304 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
21305 let keyword_call = call("helper(NULL, template); /* bound */");
21306 let keyword_arguments = keyword_call
21307 .child_by_field_name("arguments")
21308 .expect("keyword argument list");
21309 assert_eq!(
21310 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
21311 1
21312 );
21313
21314 let nested_call = call("helper(NULL, template); /* nested bound */");
21315 let nested_arguments = nested_call
21316 .child_by_field_name("arguments")
21317 .expect("nested keyword argument list");
21318 assert_eq!(
21319 recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
21320 1
21321 );
21322
21323 let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
21324 let lambda_arguments = lambda_call
21325 .child_by_field_name("arguments")
21326 .expect("lambda keyword argument list");
21327 assert_eq!(
21328 recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
21329 0,
21330 "a lambda body must not borrow the enclosing collapsed C parameter"
21331 );
21332
21333 assert_eq!(
21334 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
21335 0
21336 );
21337
21338 let unbound_call = call("helper(NULL, template); /* unbound */");
21339 let unbound_arguments = unbound_call
21340 .child_by_field_name("arguments")
21341 .expect("unbound argument list");
21342 assert_eq!(
21343 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
21344 0
21345 );
21346 }
21347
21348 #[test]
21349 #[brokk_bifrost_test_macros::hardwon(
21350 "#4407: collapsed C keyword parameters need structural position proof"
21351 )]
21352 fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
21353 let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
21354int type_keyword(template *value) {
21355 return helper(NULL, template); /* type */
21356}
21357int qualifier_keyword(const *template) {
21358 return helper(NULL, template); /* qualifier */
21359}
21360int annotation_keyword(wchar_t *value __attribute__((template))) {
21361 return helper(NULL, template); /* annotation */
21362}
21363int default_keyword(wchar_t *value = template) {
21364 return helper(NULL, template); /* default */
21365}
21366int nested_expression(wchar_t *value, (template)) {
21367 return helper(NULL, template); /* nested */
21368}
21369int malformed_head = (wchar_t *template) {
21370 return helper(NULL, template); /* malformed */
21371};
21372"#;
21373 let tree = parse_cpp(source);
21374 let root = tree.root_node();
21375 let call = |marker: &str| {
21376 let start = source.find(marker).expect("call marker");
21377 let mut node = root
21378 .descendant_for_byte_range(start, start + "helper".len())
21379 .expect("call name node");
21380 loop {
21381 if node.kind() == "call_expression" {
21382 break Some(node);
21383 }
21384 let Some(parent) = node.parent() else {
21385 break None;
21386 };
21387 node = parent;
21388 }
21389 };
21390 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
21391 let markers = [
21392 ("helper(NULL, template); /* type */", true),
21393 ("helper(NULL, template); /* qualifier */", false),
21394 ("helper(NULL, template); /* annotation */", false),
21395 ("helper(NULL, template); /* default */", false),
21396 ("helper(NULL, template); /* nested */", false),
21397 ("helper(NULL, template); /* malformed */", false),
21398 ];
21399 let mut exercised = 0;
21400 for (marker, expect_call) in markers {
21401 let Some(keyword_call) = call(marker) else {
21408 assert!(
21409 !expect_call,
21410 "{marker} lost its call_expression; the near-miss matrix changed"
21411 );
21412 continue;
21413 };
21414 assert!(
21415 expect_call,
21416 "{marker} unexpectedly retains a call_expression; the near-miss matrix changed"
21417 );
21418 exercised += 1;
21419 let keyword_arguments = keyword_call
21420 .child_by_field_name("arguments")
21421 .expect("keyword argument list");
21422 assert_eq!(
21423 recovered_c_keyword_argument_count(
21424 &c_file,
21425 keyword_call,
21426 keyword_arguments,
21427 source,
21428 ),
21429 0,
21430 "keyword in {marker} must not prove a parameter binding"
21431 );
21432 }
21433 assert!(
21434 exercised > 0,
21435 "at least one near-miss marker must exercise the count itself"
21436 );
21437 }
21438
21439 #[test]
21440 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21441 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
21442 let source = r#"#define MAKE(type) type *value
21443MAKE(int *);
21444typedef struct Item Item;
21445struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
21446void gen_op_movl(void *s, int first, int second) { }
21447const char *strZ(const char *value) { return value; }
21448int body(void *s) {
21449 MAKE(int *);
21450 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
21451 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
21452 execvp(strZ(value), UNCONSTIFY(char **, args));
21453}
21454 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
21455 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
21456int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
21457 if (d->units) {
21458 switch (target->nodetype) {
21459 case 1:
21460 case 2:
21461 break;
21462 default:
21463 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
21464 }
21465 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
21466 lysdict_remove(ctx, ((struct Item *)target)->units);
21467 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
21468 }
21469 return 0;
21470 }
21471STATIC EFI_STATUS Encode () { return 0; }
21472"#;
21473 let tree = parse_cpp(source);
21474 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
21475 let top_macro = tree
21476 .root_node()
21477 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
21478 .expect("top macro node");
21479 let body_macro_start = source
21480 .match_indices("MAKE(int *);")
21481 .nth(1)
21482 .expect("body macro")
21483 .0;
21484 let body_macro = tree
21485 .root_node()
21486 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
21487 .expect("body macro node");
21488 let function_call_start = source
21489 .find("gen_op_movl(s, offsetof(CPUX86State")
21490 .expect("function call");
21491 let function_call = tree
21492 .root_node()
21493 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
21494 .expect("function call node");
21495 let strz_start = source.find("strZ(value)").expect("nested function call");
21496 let strz = tree
21497 .root_node()
21498 .named_descendant_for_byte_range(strz_start, strz_start + 4)
21499 .expect("nested function call node");
21500 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
21501 let recovered_call = tree
21502 .root_node()
21503 .named_descendant_for_byte_range(
21504 recovered_call_start,
21505 recovered_call_start + "lysdict_remove".len(),
21506 )
21507 .expect("recovered call node");
21508 let binder_start = source.find("Encode").expect("binder");
21509 let binder = tree
21510 .root_node()
21511 .named_descendant_for_byte_range(binder_start, binder_start + 6)
21512 .expect("binder node");
21513
21514 assert!(recovered_c_function_declarator_invocation(top_macro));
21515 assert!(recovered_c_function_declarator_invocation(body_macro));
21516 assert!(recovered_c_function_declarator_invocation(function_call));
21517 assert!(recovered_c_function_declarator_invocation(strz));
21518 assert!(recovered_c_function_declarator_invocation(recovered_call));
21519 assert!(!recovered_c_function_declarator_invocation(binder));
21520 }
21521
21522 #[test]
21523 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21524 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
21525 let source = r#"typedef int krb5_context;
21526int helper(int first, int second) { return first + second; }
21527static krb5_context ctx;
21528int main(int argc, char **argv) {
21529 int ccinitial;
21530 const char *collection_name, *typename;
21531 typename = helper(ctx, ccinitial);
21532 return 0;
21533}
21534"#;
21535 let tree = parse_cpp(source);
21536 let ctx = tree
21537 .root_node()
21538 .descendant_for_byte_range(
21539 source.find("ctx, ccinitial").expect("ctx argument"),
21540 source.find("ctx, ccinitial").expect("ctx argument") + 3,
21541 )
21542 .expect("ctx node");
21543 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
21544 let ccinitial = tree
21545 .root_node()
21546 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
21547 .expect("sibling node");
21548 let typename = named_node_at(&tree, source, "typename = helper");
21549 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
21550
21551 assert_eq!(ctx.kind(), "type_identifier");
21555 assert!(recovered_c_parenthesized_declarator_reference(ctx));
21556 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
21557 assert!(!recovered_c_parenthesized_declarator_reference(typename));
21558 assert!(!recovered_c_parenthesized_declarator_reference(helper));
21559 }
21560
21561 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
21562 let mut parser = Parser::new();
21563 parser
21564 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21565 .expect("C++ grammar");
21566 let tree = parser.parse(source, None).expect("C++ fixture tree");
21567 let mut stack = vec![tree.root_node()];
21568 while let Some(node) = stack.pop() {
21569 if node.kind() == "enum_specifier" {
21570 return flattened_macro_namespace_components(node, source);
21571 }
21572 let mut cursor = node.walk();
21573 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
21574 stack.extend(children.into_iter().rev());
21575 }
21576 None
21577 }
21578
21579 #[test]
21580 #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
21581 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
21582 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21583namespace detail
21584{
21585enum class value_t { null };
21586}
21587NLOHMANN_JSON_NAMESPACE_END
21588NLOHMANN_JSON_NAMESPACE_BEGIN
21589namespace next
21590{
21591struct next_type {};
21592}
21593NLOHMANN_JSON_NAMESPACE_END
21594"#;
21595 assert_eq!(
21596 first_enum_flattened_namespace(complete),
21597 Some(vec!["detail".to_string()])
21598 );
21599
21600 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
21601 assert_eq!(
21602 first_enum_flattened_namespace(&stale_end),
21603 Some(vec!["detail".to_string()]),
21604 "a stale end marker before the begin marker must not replace the intended namespace"
21605 );
21606
21607 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21608namespace detail
21609{
21610enum class value_t { null };
21611}
21612struct next_type {};
21613"#;
21614 assert_eq!(first_enum_flattened_namespace(incomplete), None);
21615 }
21616}
21617
21618#[cfg(test)]
21634mod lookup_order_properties {
21635 use super::*;
21636 use proptest::prelude::*;
21637
21638 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
21642 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
21643 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
21647 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
21648 const KINDS: [CodeUnitType; 6] = [
21649 CodeUnitType::Class,
21650 CodeUnitType::Function,
21651 CodeUnitType::Field,
21652 CodeUnitType::Module,
21653 CodeUnitType::Macro,
21654 CodeUnitType::FileScope,
21655 ];
21656
21657 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21660 enum ProbedOrder {
21661 Before,
21662 Tied,
21663 After,
21664 Contradictory,
21667 }
21668
21669 impl ProbedOrder {
21670 fn mirror(self) -> Self {
21671 match self {
21672 ProbedOrder::Before => ProbedOrder::After,
21673 ProbedOrder::After => ProbedOrder::Before,
21674 other => other,
21675 }
21676 }
21677
21678 fn signum(self) -> i8 {
21680 match self {
21681 ProbedOrder::Before => -1,
21682 ProbedOrder::Tied => 0,
21683 ProbedOrder::After => 1,
21684 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
21685 }
21686 }
21687 }
21688
21689 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
21697 if left == right {
21698 return ProbedOrder::Tied;
21701 }
21702 let mut forward = vec![left.clone(), right.clone()];
21703 sort_lookup_units(&mut forward);
21704 let mut backward = vec![right.clone(), left.clone()];
21705 sort_lookup_units(&mut backward);
21706 let left_first = backward[0] == *left;
21707 let right_first = forward[0] == *right;
21708 match (left_first, right_first) {
21709 (true, true) => ProbedOrder::Contradictory,
21710 (true, false) => ProbedOrder::Before,
21711 (false, true) => ProbedOrder::After,
21712 (false, false) => ProbedOrder::Tied,
21713 }
21714 }
21715
21716 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
21717 (
21718 0..ROOT_NAMES.len(),
21719 0..REL_PATHS.len(),
21720 0..KINDS.len(),
21721 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
21722 0..3usize,
21723 0..SIGNATURES.len(),
21724 any::<bool>(),
21725 )
21726 .prop_map(
21727 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
21728 let source = ProjectFile::new(
21729 std::env::temp_dir().join(ROOT_NAMES[root]),
21730 REL_PATHS[rel_path],
21731 );
21732 let interner = segment_interner();
21733 let mut fq = FqName::new();
21734 for (atom, segment_kind) in &segments {
21735 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
21736 }
21737 let package_segment_count = package_prefix % fq.len();
21739 CodeUnit::from_fq(
21740 source,
21741 KINDS[kind],
21742 fq,
21743 package_segment_count,
21744 SIGNATURES[signature].map(str::to_string),
21745 synthetic,
21746 )
21747 },
21748 )
21749 }
21750
21751 proptest! {
21752 #![proptest_config(ProptestConfig::with_cases(256))]
21753
21754 #[test]
21757 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21758 fn lookup_order_is_reflexive_and_dual(
21759 left in code_unit_strategy(),
21760 right in code_unit_strategy(),
21761 ) {
21762 prop_assert_eq!(
21763 probe_order(&left, &left),
21764 ProbedOrder::Tied,
21765 "a unit must tie with itself: {:?}",
21766 left
21767 );
21768 let forward = probe_order(&left, &right);
21769 prop_assert_ne!(
21770 forward,
21771 ProbedOrder::Contradictory,
21772 "comparator put each of these strictly first: left={:?} right={:?}",
21773 left,
21774 right
21775 );
21776 prop_assert_eq!(
21777 probe_order(&right, &left),
21778 forward.mirror(),
21779 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
21780 left,
21781 right
21782 );
21783 }
21784
21785 #[test]
21787 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21788 fn lookup_order_is_transitive(
21789 a in code_unit_strategy(),
21790 b in code_unit_strategy(),
21791 c in code_unit_strategy(),
21792 ) {
21793 let ab = probe_order(&a, &b);
21794 let bc = probe_order(&b, &c);
21795 let ac = probe_order(&a, &c);
21796 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
21797 prop_assert_ne!(
21798 probed,
21799 ProbedOrder::Contradictory,
21800 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
21801 pair,
21802 a,
21803 b,
21804 c
21805 );
21806 }
21807 if ab.signum() <= 0 && bc.signum() <= 0 {
21808 prop_assert!(
21809 ac.signum() <= 0,
21810 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
21811 a={:?} b={:?} c={:?}",
21812 ab,
21813 bc,
21814 ac,
21815 a,
21816 b,
21817 c
21818 );
21819 }
21820 }
21821
21822 #[test]
21825 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21826 fn lookup_order_separates_distinct_identities(
21827 left in code_unit_strategy(),
21828 right in code_unit_strategy(),
21829 ) {
21830 if probe_order(&left, &right) == ProbedOrder::Tied {
21831 prop_assert_eq!(
21832 &left,
21833 &right,
21834 "distinct identities tied, so their order is whatever order they \
21835 arrived in: left_segments={:?} right_segments={:?}",
21836 left.fq_segments_debug(),
21837 right.fq_segments_debug()
21838 );
21839 }
21840 }
21841
21842 #[test]
21845 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21846 fn lookup_sort_is_permutation_invariant(
21847 units in prop::collection::vec(code_unit_strategy(), 1..=8),
21848 ) {
21849 let mut sorted = units.clone();
21850 sort_lookup_units(&mut sorted);
21851 for rotation in 0..units.len() {
21852 for reversed in [false, true] {
21853 let mut permuted = units.clone();
21854 permuted.rotate_left(rotation);
21855 if reversed {
21856 permuted.reverse();
21857 }
21858 sort_lookup_units(&mut permuted);
21859 prop_assert_eq!(
21860 &permuted,
21861 &sorted,
21862 "sorting a permutation gave a different list \
21863 (rotation={}, reversed={}): input={:?}",
21864 rotation,
21865 reversed,
21866 units
21867 );
21868 }
21869 }
21870 }
21871 }
21872}
21873
21874#[cfg(test)]
21875mod resolver_domain_properties {
21876 use super::*;
21877 use proptest::prelude::*;
21878
21879 proptest! {
21880 #[test]
21883 fn template_substitution_respects_free_variables(
21884 first in 0u16..1000,
21885 second in 1000u16..2000,
21886 unrelated in prop::collection::vec(0u16..1000, 1..8),
21887 rename in "[A-Z][a-z]{0,6}",
21888 ) {
21889 let atom = |value: u16| CppTemplateTerm::Atom {
21890 kind: "number_literal".to_owned(), text: value.to_string(),
21891 };
21892 let term = CppTemplateTerm::Node {
21893 kind: "template_argument_list".to_owned(),
21894 children: vec![CppTemplateTerm::Parameter("T".to_owned()), CppTemplateTerm::Parameter(format!("U{rename}"))],
21895 };
21896 let mut bindings = HashMap::default();
21897 bindings.insert("T".to_owned(), atom(first));
21898 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21899 for (index, value) in unrelated.into_iter().enumerate() {
21900 bindings.insert(format!("unrelated{index}"), atom(value));
21901 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21902 }
21903 bindings.insert(format!("U{rename}"), atom(second));
21904 let expected = CppTemplateTerm::Node {
21905 kind: "template_argument_list".to_owned(), children: vec![atom(first), atom(second)],
21906 };
21907 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), Some(expected));
21908 }
21909
21910 #[test]
21913 fn callable_arity_merge_is_a_symmetric_domain_union(
21914 total in 0usize..20,
21915 left_required in 0usize..20,
21916 right_required in 0usize..20,
21917 left_variadic in any::<bool>(),
21918 right_variadic in any::<bool>(),
21919 ) {
21920 let left = CallableArity::new(left_required.min(total), total, left_variadic);
21921 let right = CallableArity::new(right_required.min(total), total, right_variadic);
21922 let merged = merge_compatible_callable_arities(left, right);
21923 prop_assert_eq!(merged, merge_compatible_callable_arities(right, left));
21924 if let Some(merged) = merged {
21925 for count in 0..=total + 2 {
21926 prop_assert_eq!(merged.accepts(count), left.accepts(count) || right.accepts(count));
21927 }
21928 } else {
21929 prop_assert_ne!(left_variadic, right_variadic);
21930 }
21931 }
21932
21933 #[test]
21936 fn guard_environment_conjunction_is_idempotent(names in prop::collection::vec("[A-Z]{1,5}", 1..8)) {
21937 let guards: HashSet<_> = names.into_iter().map(PreprocessorGuard::Defined).collect();
21938 let environment = PreprocessorGuardEnvironment { all: guards.clone(), partial: guards.clone() };
21939 let twice = environment.merged(&environment).unwrap();
21940 prop_assert_eq!(&twice.all, &guards);
21941 prop_assert_eq!(&twice.partial, &guards);
21942 }
21943 }
21944}
21945
21946#[cfg(test)]
21947mod resolver_lookup_key_properties {
21948 use super::*;
21949 use proptest::prelude::*;
21950
21951 proptest! {
21952 #![proptest_config(ProptestConfig::with_cases(32))]
21953
21954 #[test]
21958 fn parsed_nested_types_round_trip_through_bounded_lookup_keys(
21959 namespaces in 0usize..3,
21960 owners in 1usize..5,
21961 suffix in "[a-z]{1,6}",
21962 ) {
21963 let mut source = String::new();
21964 let mut components = Vec::new();
21965 for index in 0..namespaces {
21966 let name = format!("ns{index}_{suffix}");
21967 source.push_str(&format!("namespace {name} {{ "));
21968 components.push(name);
21969 }
21970 for index in 0..owners {
21971 let name = format!("Owner{index}");
21972 source.push_str(&format!("struct {name} {{ "));
21973 components.push(name);
21974 }
21975 source.push_str("struct Leaf {}; ");
21976 for _ in 0..owners { source.push_str("}; "); }
21977 for _ in 0..namespaces { source.push_str("} "); }
21978 components.push("Leaf".to_owned());
21979 source.push_str(&format!("{}* value;", components.join("::")));
21980
21981 let tree = crate::diagnostics::parse_cpp_tree(&source);
21982 prop_assert!(!tree.root_node().has_error());
21983 let file = ProjectFile::new(std::env::temp_dir(), "lookup-keys.cpp");
21984 let parsed = crate::adapter::parse_cpp_file(&file, &source, &tree);
21985 let leaf = parsed.declarations().iter()
21986 .find(|unit| unit.is_class() && unit.identifier() == "Leaf")
21987 .expect("declaration producer indexed the nested type");
21988 let declaration = tree.root_node().named_child(tree.root_node().named_child_count() - 1).unwrap();
21989 prop_assert_eq!(declaration.kind(), "declaration");
21990 let type_node = declaration.child_by_field_name("type").unwrap();
21991 let parts = cpp_type_name_components(type_node, &source).unwrap();
21992 prop_assert_eq!(&parts, &components);
21993 let candidates = cpp_reference_fqn_candidates_from_parts(&parts, TargetKind::Type);
21994 let declaration_key = leaf.fq_name();
21995 prop_assert!(candidates.contains(&declaration_key), "declaration key {:?} absent from bounded query keys {:?}", declaration_key, candidates);
21996 }
21997 }
21998}