1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::CppCompileContext;
3#[cfg(test)]
4use crate::declarations::cpp_displaced_preprocessor_terminator;
5use crate::declarations::{
6 CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
7 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term, node_text,
8 normalize_cpp_whitespace, recovered_class_body_at, recovered_function_like_field_declarator,
9 recovered_pyobject_head_field,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::extractor::ScanCtx;
13use crate::graph::syntax::{
14 function_macro_replacement_span, normalize_macro_continuations,
15 object_macro_replacement_type_references,
16};
17use crate::graph_support::CppSource;
18use crate::imports::{
19 IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
20};
21use crate::source_context::cpp_guard_set_to_runtime;
22pub use brokk_bifrost_core::analyzer::cpp_facts::{
23 CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
24};
25use brokk_bifrost_core::analyzer::cpp_facts::{
26 CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
27 CppStructuredAliasTarget as StructuredAliasTarget,
28};
29use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
30use brokk_bifrost_core::analyzer::model::{
31 CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
32 CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
33};
34use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
35use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
36#[cfg(test)]
37use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
38use brokk_bifrost_core::analyzer::query_token::QueryToken;
39use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
40use brokk_bifrost_core::analyzer::tree_walk::{
41 ParentIndex, children_iter, named_children_iter, node_for_exact_range,
42 push_named_children_reversed,
43};
44use brokk_bifrost_core::analyzer::usages::common::same_node;
45use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
46use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
47use brokk_bifrost_core::cancellation::CancellationToken;
48use brokk_bifrost_core::hash::{HashMap, HashSet};
49#[cfg(test)]
50use brokk_bifrost_core::text_utils::compute_line_starts;
51use std::borrow::Cow;
52#[cfg(any(test, feature = "test-support"))]
53use std::cell::Cell;
54use std::cell::OnceCell;
55use std::cmp::Ordering as CmpOrdering;
56use std::collections::BTreeSet;
57use std::hash::Hash;
58use std::sync::atomic::{AtomicUsize, Ordering};
59use std::sync::{Arc, Mutex, OnceLock, RwLock};
60use std::time::{Duration, Instant};
61use tree_sitter::{Node, Parser, Tree};
62
63#[cfg(any(test, feature = "test-support"))]
64thread_local! {
65 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
66 static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
67}
68
69#[derive(Clone, Copy, PartialEq, Eq)]
70pub enum TargetKind {
71 Type,
72 Constructor,
73 FreeFunction,
74 Method,
75 GlobalField,
76 MemberField,
77 Macro,
78}
79
80pub enum LexicalTypeResolution {
81 Resolved {
82 unit: CodeUnit,
83 components: Vec<String>,
84 candidates: Vec<CodeUnit>,
85 },
86 Ambiguous,
87 Missing,
88}
89
90#[derive(Clone, Copy)]
91enum TypeCandidateResolution<'a> {
92 Canonical,
93 PreserveAlias,
94 PreserveTarget(&'a CodeUnit),
95}
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106enum TypeCandidateFailure {
107 Ambiguous,
108 Unresolvable,
109}
110
111impl TypeCandidateFailure {
112 fn lexical_resolution(self) -> LexicalTypeResolution {
113 match self {
114 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
115 Self::Unresolvable => LexicalTypeResolution::Missing,
116 }
117 }
118}
119
120pub enum LexicalCallableValueResolution {
121 Type(CodeUnit),
122 FreeFunction(CodeUnit),
123 Ambiguous,
124 Missing,
125}
126
127pub enum UsingEnumMemberResolution {
128 Resolved { owner: CodeUnit, member: CodeUnit },
129 Ambiguous,
130 Missing,
131}
132
133pub enum NamespaceValueResolution {
134 Resolved,
135 Ambiguous,
136 Missing,
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
140pub enum OrdinaryMacroReferenceResolution {
141 Resolved(CodeUnit),
142 Ambiguous,
143 Missing,
144}
145
146#[derive(Clone, Debug, PartialEq, Eq)]
147pub enum RecoveredCReferenceRanges {
148 Complete(Vec<Range>),
149 LimitExceeded,
150}
151
152pub fn resolve_namespace_value(
153 analyzer: &CppGraphSource<'_>,
154 visibility: &VisibilityIndex<'_>,
155 file: &ProjectFile,
156 namespace: &str,
157 name: &str,
158 before_byte: usize,
159) -> NamespaceValueResolution {
160 let mut matches = Vec::new();
161 for candidate in visibility.visible_identifier_candidates(file, name) {
162 if type_owner_of(analyzer, candidate).is_some()
163 || candidate.package_name() != namespace
164 || (candidate.source() == file
165 && !analyzer
166 .ranges(candidate)
167 .iter()
168 .any(|range| range.start_byte < before_byte))
169 || matches
170 .iter()
171 .any(|existing| same_visible_symbol(existing, candidate))
172 {
173 continue;
174 }
175 matches.push(candidate.clone());
176 if matches.len() > 1 {
177 return NamespaceValueResolution::Ambiguous;
178 }
179 }
180 matches
181 .pop()
182 .map(|_| NamespaceValueResolution::Resolved)
183 .unwrap_or(NamespaceValueResolution::Missing)
184}
185
186pub(crate) struct ScopedUsingEnumOwners {
187 scopes: Vec<Vec<CodeUnit>>,
188}
189
190pub(crate) struct SemanticUsingEnumOwners {
195 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
196 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
197}
198
199pub(crate) enum SemanticUsingEnumMemberResolution {
200 Class(UsingEnumMemberResolution),
201 Namespace(UsingEnumMemberResolution),
202 Missing,
203}
204
205impl SemanticUsingEnumOwners {
206 pub(crate) fn new() -> Self {
207 Self {
208 class_imports: HashMap::default(),
209 namespace_imports: HashMap::default(),
210 }
211 }
212
213 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
214 let imports = self.class_imports.entry(class).or_default();
215 if !imports
216 .iter()
217 .any(|existing| same_visible_symbol(existing, &enum_owner))
218 {
219 imports.push(enum_owner);
220 }
221 }
222
223 pub fn import_namespace(
224 &mut self,
225 namespace: Vec<String>,
226 declaration_byte: usize,
227 enum_owner: CodeUnit,
228 ) {
229 let imports = self.namespace_imports.entry(namespace).or_default();
230 if !imports
231 .iter()
232 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
233 {
234 imports.push((declaration_byte, enum_owner));
235 }
236 }
237
238 pub fn resolve_member(
239 &self,
240 visibility: &VisibilityIndex<'_>,
241 file: &ProjectFile,
242 class: Option<&CodeUnit>,
243 namespace: &[String],
244 before_byte: usize,
245 name: &str,
246 ) -> SemanticUsingEnumMemberResolution {
247 if let Some(class) = class
248 && let Some((_, imports)) = self
249 .class_imports
250 .iter()
251 .find(|(owner, _)| same_visible_symbol(owner, class))
252 {
253 let resolution =
254 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
255 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
256 return SemanticUsingEnumMemberResolution::Class(resolution);
257 }
258 }
259 for prefix_len in (0..=namespace.len()).rev() {
260 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
261 continue;
262 };
263 let owners = imports
264 .iter()
265 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
266 .map(|(_, owner)| owner);
267 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
268 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
269 return SemanticUsingEnumMemberResolution::Namespace(resolution);
270 }
271 }
272 SemanticUsingEnumMemberResolution::Missing
273 }
274}
275
276fn resolve_using_enum_member_for_owners<'a>(
277 visibility: &VisibilityIndex<'_>,
278 file: &ProjectFile,
279 owners: impl IntoIterator<Item = &'a CodeUnit>,
280 name: &str,
281) -> UsingEnumMemberResolution {
282 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
283 for owner in owners {
284 for member in visibility.visible_members_for_owner_name(file, owner, name) {
285 if !member.is_field()
286 || matches.iter().any(|(existing_owner, existing_member)| {
287 same_visible_symbol(existing_owner, owner)
288 && same_visible_symbol(existing_member, member)
289 })
290 {
291 continue;
292 }
293 matches.push((owner.clone(), member.clone()));
294 }
295 }
296 match matches.len() {
297 0 => UsingEnumMemberResolution::Missing,
298 1 => {
299 let (owner, member) = matches.pop().expect("one using-enum match");
300 UsingEnumMemberResolution::Resolved { owner, member }
301 }
302 _ => UsingEnumMemberResolution::Ambiguous,
303 }
304}
305
306impl ScopedUsingEnumOwners {
307 pub(crate) fn new() -> Self {
308 Self {
309 scopes: vec![Vec::new()],
310 }
311 }
312
313 pub fn enter_scope(&mut self) {
314 self.scopes.push(Vec::new());
315 }
316
317 pub fn exit_scope(&mut self) {
318 if self.scopes.len() > 1 {
319 self.scopes.pop();
320 }
321 }
322
323 pub fn import(&mut self, owner: CodeUnit) {
324 let scope = self
325 .scopes
326 .last_mut()
327 .expect("using-enum scope stack is never empty");
328 if !scope
329 .iter()
330 .any(|existing| same_visible_symbol(existing, &owner))
331 {
332 scope.push(owner);
333 }
334 }
335
336 pub fn resolve_member(
337 &self,
338 visibility: &VisibilityIndex<'_>,
339 file: &ProjectFile,
340 name: &str,
341 ) -> UsingEnumMemberResolution {
342 for scope in self.scopes.iter().rev() {
343 let resolution =
344 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
345 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
346 return resolution;
347 }
348 }
349 UsingEnumMemberResolution::Missing
350 }
351}
352
353#[derive(Clone)]
354pub struct TargetSpec {
355 pub target: CodeUnit,
356 pub kind: TargetKind,
357 pub owner: Option<CodeUnit>,
358 pub member_name: String,
359 pub callable_arity: Option<CallableArity>,
360 pub activated_callable_arities: Vec<ActivatedCallableArity>,
361 pub param_types: Option<Vec<String>>,
362 pub enum_owner_kind: EnumOwnerKind,
363 pub owner_is_forward_declaration: bool,
364 pub callable_has_definition_body: bool,
365}
366
367#[derive(Clone, Copy)]
368pub struct ActivatedCallableArity {
369 pub activation_byte: usize,
370 pub arity: CallableArity,
371}
372
373#[derive(Debug, PartialEq, Eq, Hash)]
374pub struct TypeScanKey {
375 target: LogicalSymbolKey,
376 member_name: String,
377}
378
379#[derive(Clone, Debug, PartialEq, Eq, Hash)]
380struct LogicalSymbolKey {
381 kind: CodeUnitType,
382 fq_name: String,
383 signature: Option<String>,
384}
385
386struct ResolvedTypeOwner {
387 unit: CodeUnit,
388 is_forward_declaration: bool,
389}
390
391impl TargetSpec {
392 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
393 (self.kind == TargetKind::Type).then(|| TypeScanKey {
394 target: logical_symbol_key(&self.target),
395 member_name: self.member_name.clone(),
396 })
397 }
398
399 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
400 if target.is_class() {
401 return Some(Self::new(
402 target.clone(),
403 TargetKind::Type,
404 Some(target.clone()),
405 target.identifier().to_string(),
406 None,
407 None,
408 ));
409 }
410
411 if target.is_field() {
412 let owner = type_owner_of(analyzer, target);
418 let kind = if owner.is_some() {
419 TargetKind::MemberField
420 } else {
421 TargetKind::GlobalField
422 };
423 let enum_owner_kind = owner
424 .as_ref()
425 .map(|owner| classify_enum_owner(analyzer, owner))
426 .unwrap_or(EnumOwnerKind::NonEnum);
427 let mut spec = Self::new(
428 target.clone(),
429 kind,
430 owner,
431 target.identifier().to_string(),
432 None,
433 None,
434 );
435 spec.enum_owner_kind = enum_owner_kind;
436 return Some(spec);
437 }
438
439 if target.is_function() {
440 let owner_resolution = target_type_owner_resolution(analyzer, target);
443 let owner_is_forward_declaration = owner_resolution
444 .as_ref()
445 .is_some_and(|owner| owner.is_forward_declaration);
446 let owner = owner_resolution.map(|owner| owner.unit);
447 let kind = if owner.as_ref().is_some_and(|owner| {
448 target.identifier() == owner.identifier()
449 || analyzer
450 .cpp
451 .and_then(|cpp| cpp.template_metadata(owner))
452 .is_some_and(|metadata| metadata.primary_name == target.identifier())
453 }) {
454 TargetKind::Constructor
455 } else if owner.is_some() {
456 TargetKind::Method
457 } else {
458 TargetKind::FreeFunction
459 };
460 let mut spec = Self::new(
461 target.clone(),
462 kind,
463 owner,
464 target.identifier().to_string(),
465 cpp_callable_arity(analyzer, target),
466 cpp_callable_parameter_types(analyzer, target),
467 );
468 spec.owner_is_forward_declaration = owner_is_forward_declaration;
469 spec.callable_has_definition_body =
470 callable_target_has_definition_body(analyzer, target);
471 return Some(spec);
472 }
473
474 if target.is_macro() {
475 return Some(Self::new(
476 target.clone(),
477 TargetKind::Macro,
478 None,
479 target.identifier().to_string(),
480 None,
481 None,
482 ));
483 }
484
485 None
486 }
487
488 pub fn with_visible_callable_arities<'a>(
489 &'a self,
490 analyzer: &CppGraphSource<'_>,
491 cpp: &dyn CppSource,
492 visibility: &VisibilityIndex<'_>,
493 file: &ProjectFile,
494 prepared: &PreparedSyntaxTree,
495 ) -> Cow<'a, Self> {
496 let macro_parameter_arity =
497 visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
498 let activated_callable_arities =
499 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
500 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
501 return Cow::Borrowed(self);
502 }
503 let mut effective = self.clone();
504 if let Some(macro_parameter_arity) = macro_parameter_arity {
505 effective.callable_arity = Some(macro_parameter_arity);
506 }
507 effective.activated_callable_arities = activated_callable_arities;
508 Cow::Owned(effective)
509 }
510
511 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
512 let base = self.callable_arity?;
513 Some(
514 self.activated_callable_arities
515 .iter()
516 .filter(|candidate| candidate.activation_byte <= byte)
517 .fold(base, |arity, candidate| {
518 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
519 }),
520 )
521 }
522
523 pub fn new(
524 target: CodeUnit,
525 kind: TargetKind,
526 owner: Option<CodeUnit>,
527 member_name: String,
528 callable_arity: Option<CallableArity>,
529 param_types: Option<Vec<String>>,
530 ) -> Self {
531 Self {
532 target,
533 kind,
534 owner,
535 member_name,
536 callable_arity,
537 activated_callable_arities: Vec::new(),
538 param_types,
539 enum_owner_kind: EnumOwnerKind::NonEnum,
540 owner_is_forward_declaration: false,
541 callable_has_definition_body: false,
542 }
543 }
544}
545
546fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
547 analyzer
548 .signature_metadata(target)
549 .iter()
550 .any(|metadata| !metadata.is_declaration_only())
551}
552
553fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
554 LogicalSymbolKey {
555 kind: unit.kind(),
556 fq_name: unit.fq_name(),
557 signature: unit.signature().map(str::to_string),
558 }
559}
560
561fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
562 let Some(facts) = analyzer.declaration_source_properties(owner) else {
563 return EnumOwnerKind::NonEnum;
564 };
565 let Some(first) = facts.first() else {
566 return EnumOwnerKind::NonEnum;
567 };
568 if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
569 first.enum_kind
570 } else {
571 EnumOwnerKind::NonEnum
572 }
573}
574
575#[derive(Clone, PartialEq, Eq, Hash)]
576pub struct CppScanBinding {
577 pub unit: Option<CodeUnit>,
578 pub type_name: Option<String>,
579 pub indirection: i32,
580}
581
582impl CppScanBinding {
583 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
584 Self {
585 type_name: Some(cpp_name_for(&unit)),
586 unit: Some(unit),
587 indirection,
588 }
589 }
590
591 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
592 Self {
593 type_name: Some(type_name),
594 unit,
595 indirection,
596 }
597 }
598
599 pub fn as_arg_type(&self) -> Option<CppArgType> {
600 let name = self
601 .type_name
602 .clone()
603 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
604 Some(CppArgType {
605 name,
606 unit: self.unit.clone(),
607 indirection: self.indirection,
608 pointee_const: false,
609 })
610 }
611}
612
613type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
614pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
615pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
616type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
617type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
618type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
619type MacroLexicalTemplateCache =
620 HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
621
622type MacroLocalBindingTemplateCache =
623 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
624type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
625type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
626type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
627
628struct StructuredIncludeFact {
629 start_byte: usize,
630 end_byte: usize,
631 path: String,
632}
633
634#[derive(Clone, Default)]
635pub struct MacroEnvironment {
636 bindings: HashMap<String, MacroBinding>,
637 known_undefined_names: HashSet<String>,
638 build_proven_defines: HashSet<String>,
643 unknown_names: bool,
644 applied_pragma_once_files: HashSet<ProjectFile>,
645 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
646}
647
648pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
657
658struct MacroEnvironmentCheckpoint {
660 frontier: usize,
662 environment: Arc<MacroEnvironment>,
663}
664
665struct MacroEnvironmentCheckpoints {
674 checkpoints: Vec<MacroEnvironmentCheckpoint>,
675}
676
677impl MacroEnvironmentCheckpoints {
678 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
680 let index = self
681 .checkpoints
682 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
683 assert!(
684 index > 0,
685 "a checkpoint vector starts at frontier zero, which precedes every request"
686 );
687 &self.checkpoints[index - 1]
688 }
689}
690
691impl MacroEnvironment {
692 fn binding(&self, name: &str) -> Option<&MacroBinding> {
693 self.bindings.get(name)
694 }
695
696 fn may_bind(&self, name: &str) -> bool {
697 self.bindings.contains_key(name) || self.unknown_names
698 }
699
700 fn insert(&mut self, name: String, binding: MacroBinding) {
701 self.known_undefined_names.remove(&name);
702 self.bindings.insert(name, binding);
703 }
704
705 fn remove(&mut self, name: &str) {
706 self.bindings.remove(name);
707 self.known_undefined_names.insert(name.to_string());
708 }
709
710 fn remove_known_undefined(&mut self, name: &str) {
711 self.known_undefined_names.remove(name);
712 }
713
714 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
715 for binding in self.bindings.values_mut() {
716 *binding = MacroBinding::uncertain_from(binding, source, byte);
717 }
718 self.known_undefined_names.clear();
719 self.build_proven_defines.clear();
724 self.unknown_names = true;
725 }
726
727 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
728 guards.iter().all(|guard| self.guard_may_hold(guard))
729 }
730
731 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
732 let Some(expression) = guard.as_boolean_expression() else {
733 return true;
734 };
735 self.boolean_guard_may_hold(&expression)
736 }
737
738 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
739 match expression {
740 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
741 BooleanGuardExpression::Undefined(name) => {
742 self.bindings
743 .get(name)
744 .is_none_or(|binding| !binding.is_exact())
745 && (!self.build_proven_defines.contains(name)
746 || self.known_undefined_names.contains(name))
747 }
748 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
749 BooleanGuardExpression::Opaque(_)
750 | BooleanGuardExpression::NegatedOpaque(_)
751 | BooleanGuardExpression::Constant(true) => true,
752 BooleanGuardExpression::Constant(false) => false,
753 BooleanGuardExpression::All(expressions) => expressions
754 .iter()
755 .all(|expression| self.boolean_guard_may_hold(expression)),
756 BooleanGuardExpression::Any(expressions) => expressions
757 .iter()
758 .any(|expression| self.boolean_guard_may_hold(expression)),
759 }
760 }
761}
762
763#[derive(Clone)]
764pub enum EffectiveUsingTarget {
765 Ordinary {
766 name: String,
767 target_components: Vec<String>,
768 global: bool,
769 },
770 Namespace {
771 namespace_components: Vec<String>,
772 global: bool,
773 },
774}
775
776#[derive(Clone)]
777pub struct OrdinaryTypeImport {
778 pub target: EffectiveUsingTarget,
779 pub source: ProjectFile,
780 pub declaration_byte: usize,
781 pub scope_start: usize,
782 pub scope_end: usize,
783 pub scope_depth: usize,
784 pub block_scope: bool,
785 pub lexical_depth: usize,
786 pub declaration_namespace: Vec<String>,
787 pub namespace_scope: Option<Vec<String>>,
788 pub resolved_target_components: Option<Vec<String>>,
789 pub required_guards: HashSet<PreprocessorGuard>,
790}
791
792#[derive(Clone)]
793pub struct ConditionalIncludeProjection {
794 pub activation_byte: usize,
795 pub required_guards: HashSet<PreprocessorGuard>,
796 pub partial_guards: HashSet<PreprocessorGuard>,
800}
801
802#[derive(Default)]
803pub struct SourceUsingIndex {
804 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
805 pub directives: Vec<OrdinaryTypeImport>,
806}
807
808#[derive(Default)]
809pub struct ProjectUsingIndex {
810 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
811 pub directives: Vec<OrdinaryTypeImport>,
812}
813
814type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
815
816pub struct EffectiveUsingIndex {
817 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
818}
819
820impl EffectiveUsingIndex {
821 fn new(_root: ProjectFile) -> Self {
822 Self {
823 projected_by_name: Mutex::new(HashMap::default()),
824 }
825 }
826
827 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
828 self.projected_by_name
829 .lock()
830 .expect("C++ effective-using projection cache poisoned")
831 .entry(name.to_string())
832 .or_default()
833 .clone()
834 }
835}
836
837pub enum OrdinaryTypeImportResolution {
838 Resolved {
839 target: CodeUnit,
840 target_components: Vec<String>,
841 lexical_depth: usize,
842 is_direct: bool,
843 },
844 Ambiguous {
845 lexical_depth: usize,
846 },
847 Missing,
848}
849
850type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
851type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
852type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
853type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
854type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
855type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
856type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
857type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
858
859struct ExtractedComparable {
864 shapes: Vec<CppComparableSlot>,
865 suffix: String,
866}
867
868const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
872
873#[derive(Clone, Copy, Debug, PartialEq, Eq)]
895enum IncludePathAdmission {
896 Proven,
897 Compatible,
898}
899
900impl IncludePathAdmission {
901 fn admits(
902 self,
903 required: &HashSet<PreprocessorGuard>,
904 partial: &HashSet<PreprocessorGuard>,
905 reference_guards: Option<&HashSet<PreprocessorGuard>>,
906 ) -> bool {
907 match self {
908 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
909 Self::Compatible => {
910 guard_requirements_hold_at_reference(partial, reference_guards)
911 && guards_compatible_at_reference(required, reference_guards)
912 }
913 }
914 }
915}
916
917pub struct VisibilityIndex<'a> {
928 cpp: &'a dyn CppSource,
929 token: QueryToken<'a>,
934 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
935 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
936 global_field_internal_linkage: HashMap<CodeUnit, bool>,
937 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
938 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
939 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
940 parser_alias_target_matches:
941 RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
942 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
943 project_using_index: OnceLock<ProjectUsingIndex>,
944 callable_reference_specs:
945 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
946 structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
947 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
948 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
949 include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
950 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
951 #[cfg(any(test, feature = "test-support"))]
952 conditional_include_projection_index_build_count: AtomicUsize,
953 #[cfg(any(test, feature = "test-support"))]
954 conditional_include_projection_state_count: AtomicUsize,
955 #[cfg(any(test, feature = "test-support"))]
956 conditional_include_target_state_count: AtomicUsize,
957 #[cfg(any(test, feature = "test-support"))]
958 include_activation_build_count: AtomicUsize,
959 #[cfg(any(test, feature = "test-support"))]
960 using_donor_activation_count: AtomicUsize,
961 #[cfg(any(test, feature = "test-support"))]
962 using_namespace_lookup_count: AtomicUsize,
963 #[cfg(any(test, feature = "test-support"))]
964 using_name_candidate_inspection_count: AtomicUsize,
965 #[cfg(any(test, feature = "test-support"))]
966 callable_reference_spec_build_count: AtomicUsize,
967 #[cfg(any(test, feature = "test-support"))]
968 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
969 #[cfg(any(test, feature = "test-support"))]
970 visible_parser_alias_name_set_build_count: AtomicUsize,
971 parser_alias_fallback_calls: AtomicUsize,
972 parser_alias_fallback_files: AtomicUsize,
973 parser_alias_source_parses: AtomicUsize,
974 parser_alias_fallback_elapsed_micros: AtomicUsize,
975 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
976 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
977 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
978 comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
979 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
980 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
981 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
982 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
983 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
984 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
985 macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
986 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
987 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
994 macro_replacements: Mutex<MacroReplacementCache>,
995 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
996 pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
997 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
998 macro_type_parameters: Mutex<MacroTypeParameterCache>,
999 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1000 #[cfg(any(test, feature = "test-support"))]
1001 pub macro_replacement_parse_count: AtomicUsize,
1002 #[cfg(any(test, feature = "test-support"))]
1003 pub macro_event_application_count: AtomicUsize,
1004 #[cfg(any(test, feature = "test-support"))]
1007 pub macro_environment_checkpoint_build_count: AtomicUsize,
1008 #[cfg(any(test, feature = "test-support"))]
1011 pub macro_environment_copy_count: AtomicUsize,
1012 #[cfg(any(test, feature = "test-support"))]
1013 pub macro_environment_request_count: AtomicUsize,
1014 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1015 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1016 #[cfg(any(test, feature = "test-support"))]
1017 qualified_candidate_inspections: AtomicUsize,
1018 #[cfg(any(test, feature = "test-support"))]
1019 target_preserving_type_resolution_count: AtomicUsize,
1020 #[cfg(any(test, feature = "test-support"))]
1021 visibility_identifier_lookup_count: usize,
1022 #[cfg(any(test, feature = "test-support"))]
1023 visibility_identifier_batch_count: usize,
1024}
1025
1026impl Drop for VisibilityIndex<'_> {
1027 fn drop(&mut self) {
1028 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1029 return;
1030 }
1031 #[cfg(any(test, feature = "test-support"))]
1032 eprintln!(
1033 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1034 self.macro_environment_request_count.load(Ordering::Relaxed),
1035 self.macro_environment_copy_count.load(Ordering::Relaxed),
1036 self.macro_environment_checkpoint_build_count
1037 .load(Ordering::Relaxed),
1038 self.macro_event_application_count.load(Ordering::Relaxed),
1039 );
1040 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1041 if calls == 0 {
1042 return;
1043 }
1044 eprintln!(
1045 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1046 calls,
1047 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1048 self.parser_alias_source_parses.load(Ordering::Relaxed),
1049 self.parser_alias_fallback_elapsed_micros
1050 .load(Ordering::Relaxed)
1051 / 1_000,
1052 );
1053 }
1054}
1055
1056#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1057pub enum PreprocessorGuard {
1058 Defined(String),
1059 Undefined(String),
1060 Boolean(BooleanGuardExpression),
1061 Expression(String),
1062 NegatedExpression(String),
1063 Constant(bool),
1064}
1065
1066#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1067pub enum BooleanGuardExpression {
1068 Defined(String),
1069 Undefined(String),
1070 Truthy(String),
1071 Falsy(String),
1072 Opaque(String),
1073 NegatedOpaque(String),
1074 All(Vec<BooleanGuardExpression>),
1075 Any(Vec<BooleanGuardExpression>),
1076 Constant(bool),
1077}
1078
1079impl BooleanGuardExpression {
1080 fn negated(&self) -> Self {
1081 match self {
1082 Self::Defined(name) => Self::Undefined(name.clone()),
1083 Self::Undefined(name) => Self::Defined(name.clone()),
1084 Self::Truthy(name) => Self::Falsy(name.clone()),
1085 Self::Falsy(name) => Self::Truthy(name.clone()),
1086 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1087 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1088 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1089 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1090 Self::Constant(value) => Self::Constant(!value),
1091 }
1092 }
1093
1094 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1095 Self::normalized(expressions, true)
1096 }
1097
1098 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1099 Self::normalized(expressions, false)
1100 }
1101
1102 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1103 let mut normalized = Vec::new();
1104 for expression in expressions {
1105 match expression {
1106 Self::All(nested) if conjunction => normalized.extend(nested),
1107 Self::Any(nested) if !conjunction => normalized.extend(nested),
1108 Self::Constant(value) if value == conjunction => {}
1109 Self::Constant(value) => return Self::Constant(value),
1110 expression => normalized.push(expression),
1111 }
1112 }
1113 normalized.sort_unstable();
1114 normalized.dedup();
1115 match normalized.len() {
1116 0 => Self::Constant(conjunction),
1117 1 => normalized.pop().expect("one Boolean guard expression"),
1118 _ if conjunction => Self::All(normalized),
1119 _ => Self::Any(normalized),
1120 }
1121 }
1122
1123 fn implies(&self, required: &Self) -> bool {
1124 if self == required
1125 || matches!(self, Self::Constant(false))
1126 || matches!(required, Self::Constant(true))
1127 {
1128 return true;
1129 }
1130 if matches!(
1131 (self, required),
1132 (Self::Truthy(active), Self::Defined(required))
1133 | (Self::Undefined(active), Self::Falsy(required))
1134 if active == required
1135 ) {
1136 return true;
1137 }
1138 match self {
1139 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1140 Self::All(active) => match required {
1141 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1142 _ => active.iter().any(|expression| expression.implies(required)),
1143 },
1144 _ => match required {
1145 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1146 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1147 _ => false,
1148 },
1149 }
1150 }
1151
1152 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1153 match self {
1154 Self::Defined(name)
1155 | Self::Undefined(name)
1156 | Self::Truthy(name)
1157 | Self::Falsy(name) => name == macro_name,
1158 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1161 Self::All(expressions) | Self::Any(expressions) => expressions
1162 .iter()
1163 .any(|expression| expression.may_depend_on_macro(macro_name)),
1164 Self::Constant(_) => false,
1165 }
1166 }
1167
1168 pub fn heap_size(&self) -> usize {
1169 match self {
1170 Self::Defined(value)
1171 | Self::Undefined(value)
1172 | Self::Truthy(value)
1173 | Self::Falsy(value)
1174 | Self::Opaque(value)
1175 | Self::NegatedOpaque(value) => value.len(),
1176 Self::All(expressions) | Self::Any(expressions) => {
1177 expressions
1178 .iter()
1179 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1180 size.saturating_add(std::mem::size_of::<Self>())
1181 .saturating_add(expression.heap_size())
1182 })
1183 }
1184 Self::Constant(_) => 0,
1185 }
1186 }
1187}
1188
1189impl PreprocessorGuard {
1190 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1191 match self {
1192 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1193 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1194 Self::Boolean(expression) => Some(expression.clone()),
1195 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1196 Self::Expression(_) | Self::NegatedExpression(_) => None,
1197 }
1198 }
1199
1200 fn negated(&self) -> Self {
1201 match self {
1202 Self::Defined(name) => Self::Undefined(name.clone()),
1203 Self::Undefined(name) => Self::Defined(name.clone()),
1204 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1205 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1206 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1207 Self::Constant(value) => Self::Constant(!value),
1208 }
1209 }
1210
1211 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1212 match self {
1213 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1214 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1215 Self::Expression(_) | Self::NegatedExpression(_) => true,
1218 Self::Constant(_) => false,
1219 }
1220 }
1221}
1222
1223#[derive(Clone, PartialEq, Eq)]
1224pub enum MacroDefinition {
1225 Object {
1226 replacement: String,
1227 },
1228 Function {
1229 parameters: Vec<String>,
1230 replacement: String,
1231 },
1232 VariadicFunction {
1233 parameters: Vec<String>,
1234 replacement: String,
1235 },
1236 Unsupported,
1237}
1238
1239#[derive(Clone, Debug, PartialEq, Eq)]
1240pub enum MacroIncludeProtection {
1241 MacroGuard(String),
1242 PragmaOnce,
1243 None,
1244}
1245
1246enum ParsedMacroReplacement {
1247 Parsed { source: String, tree: Tree },
1248 Unsupported,
1249}
1250
1251const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1255
1256pub struct ParsedReplacementBody {
1265 pub source: String,
1266 pub tree: Tree,
1267 pub body_offset: usize,
1268 pub parameters: Vec<String>,
1269 original_offsets: Box<[usize]>,
1274}
1275
1276impl ParsedReplacementBody {
1277 pub fn statements(&self) -> Option<Node<'_>> {
1279 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1280 .child_by_field_name("body")
1281 }
1282
1283 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1289 assert!(
1290 node.start_byte() >= self.body_offset,
1291 "synthetic sentinel node cannot be mapped to a macro replacement"
1292 );
1293 assert!(
1294 node.end_byte() >= self.body_offset,
1295 "synthetic sentinel node cannot be mapped to a macro replacement"
1296 );
1297 let start_offset = node.start_byte() - self.body_offset;
1298 let end_offset = node.end_byte() - self.body_offset;
1299 assert!(start_offset <= end_offset);
1300 let start_origin = *self
1301 .original_offsets
1302 .get(start_offset)
1303 .expect("replacement node start must have a source mapping");
1304 let end_origin = *self
1305 .original_offsets
1306 .get(end_offset)
1307 .expect("replacement node end must have a source mapping");
1308 assert!(start_origin <= end_origin);
1309 let start = replacement_start + start_origin;
1310 let end = replacement_start + end_origin;
1311 start..end
1312 }
1313
1314 fn expands_variadic_arguments(&self) -> bool {
1321 let mut stack = vec![self.tree.root_node()];
1322 while let Some(node) = stack.pop() {
1323 if matches!(
1324 node.kind(),
1325 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1326 ) && node_text(node, &self.source) == "__VA_ARGS__"
1327 {
1328 return true;
1329 }
1330 push_named_children_reversed(node, &mut stack);
1331 }
1332 false
1333 }
1334}
1335
1336fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1337 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1338 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1339 if compact.starts_with("0x") || compact.starts_with("0X") {
1340 (16, 2, |ch| ch.is_ascii_hexdigit())
1341 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1342 (2, 2, |ch| matches!(ch, '0' | '1'))
1343 } else if compact.starts_with('0') && compact.len() > 1 {
1344 (8, 0, |ch| matches!(ch, '0'..='7'))
1345 } else {
1346 (10, 0, |ch| ch.is_ascii_digit())
1347 };
1348 let digit_len = compact[digits_start..]
1349 .chars()
1350 .take_while(|ch| digit_matches(*ch))
1351 .map(char::len_utf8)
1352 .sum::<usize>();
1353 if digit_len == 0 {
1354 return None;
1355 }
1356 let digits_end = digits_start + digit_len;
1357 if !compact[digits_end..]
1358 .chars()
1359 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1360 {
1361 return None;
1362 }
1363 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1364}
1365
1366#[derive(Clone)]
1367enum MacroLocalBindingTypeTemplate {
1368 Parameter(usize),
1369 Fixed(String),
1370}
1371
1372#[derive(Clone)]
1373struct MacroLocalBindingTemplate {
1374 name: String,
1375 declared_type: MacroLocalBindingTypeTemplate,
1376 pointer_depth: i32,
1377}
1378
1379pub struct MacroLocalBinding<'tree> {
1387 pub name: String,
1388 pub type_name: String,
1389 pub type_node: Option<Node<'tree>>,
1390 pub pointer_depth: i32,
1391 pub proven_unit: Option<CodeUnit>,
1392}
1393
1394#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1400pub enum MacroLexicalBindingKind {
1401 Parameter,
1402 Local,
1403}
1404
1405#[derive(Clone, Debug, PartialEq, Eq)]
1406pub struct MacroLexicalBinding {
1407 pub definition: ProjectFile,
1408 pub kind: MacroLexicalBindingKind,
1409 pub name: String,
1410 pub name_range: std::ops::Range<usize>,
1411 pub declaration_range: std::ops::Range<usize>,
1412}
1413
1414#[derive(Clone, Debug, Default, PartialEq, Eq)]
1418pub struct MacroLexicalReferences {
1419 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1420 pub truncated: bool,
1421 pub cancelled: bool,
1422}
1423
1424fn macro_replacement_type_parameters(
1425 body: &ParsedReplacementBody,
1426 parameters: &[String],
1427) -> Option<Vec<usize>> {
1428 let mut found = Vec::new();
1429 let mut stack = vec![body.tree.root_node()];
1430 while let Some(node) = stack.pop() {
1431 let type_position = node.kind() == "type_identifier"
1432 || (node.kind() == "identifier"
1433 && node.parent().is_some_and(|parent| {
1434 parent.kind() == "type_descriptor"
1435 && parent.child_by_field_name("type") == Some(node)
1436 }));
1437 let offsetof_type_position = node.kind() == "identifier"
1438 && node
1439 .parent()
1440 .filter(|parent| parent.kind() == "argument_list")
1441 .and_then(|arguments| arguments.parent())
1442 .is_some_and(|call| {
1443 call.kind() == "call_expression"
1444 && call
1445 .child_by_field_name("function")
1446 .is_some_and(|function| {
1447 function.kind() == "identifier"
1448 && node_text(function, &body.source) == "offsetof"
1449 })
1450 && call
1451 .child_by_field_name("arguments")
1452 .is_some_and(|arguments| {
1453 argument_children(arguments).next() == Some(node)
1454 })
1455 });
1456 if (type_position || offsetof_type_position)
1457 && let Some(index) = parameters
1458 .iter()
1459 .position(|parameter| parameter == node_text(node, &body.source))
1460 && !found.contains(&index)
1461 {
1462 found.push(index);
1463 }
1464 push_named_children_reversed(node, &mut stack);
1465 }
1466 (!found.is_empty()).then_some(found)
1467}
1468
1469fn macro_replacement_type_parameter(
1470 body: &ParsedReplacementBody,
1471 parameters: &[String],
1472) -> Option<usize> {
1473 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1474 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1475}
1476
1477pub(crate) fn macro_type_argument_node<'tree>(
1478 node: Node<'tree>,
1479 source: &str,
1480) -> Option<Node<'tree>> {
1481 match node.kind() {
1482 "type_descriptor" => {
1483 let type_child = node
1484 .child_by_field_name("type")
1485 .or_else(|| first_type_child(node))?;
1486 for index in (0..node.named_child_count()).rev() {
1487 let child = node.named_child(index)?;
1488 if child != type_child
1489 && matches!(
1490 child.kind(),
1491 "identifier"
1492 | "type_identifier"
1493 | "qualified_identifier"
1494 | "scoped_type_identifier"
1495 )
1496 {
1497 return Some(child);
1498 }
1499 }
1500 macro_type_argument_node(type_child, source)
1501 }
1502 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1503 node.child_by_field_name("name")
1504 }
1505 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1506 .next_named_sibling()
1507 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1508 .and_then(|error| error.named_child(0))
1509 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1510 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1511 }
1512}
1513
1514fn c_function_macro_argument<'tree>(
1515 node: Node<'tree>,
1516) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1517 let mut current = node;
1518 while let Some(parent) = current.parent() {
1519 if parent.kind() == "argument_list" {
1520 let call = parent.parent().filter(|call| {
1521 call.kind() == "call_expression"
1522 && call.child_by_field_name("arguments") == Some(parent)
1523 && call
1524 .child_by_field_name("function")
1525 .is_some_and(|function| {
1526 function.kind() == "identifier"
1527 && !node_range_contains(function, current)
1528 })
1529 })?;
1530 let mut actuals = argument_children(parent).enumerate();
1531 let (index, argument) = actuals.find(|(_, argument)| {
1532 node_range_contains(*argument, node)
1533 && (argument.start_byte() == current.start_byte()
1534 || argument.end_byte() == current.end_byte())
1535 })?;
1536 return Some((call, index, argument));
1537 }
1538 current = parent;
1539 }
1540 None
1541}
1542
1543fn recognized_c_macro_declarator_binding<'tree>(
1548 statement: Node<'tree>,
1549 source: &str,
1550) -> Option<MacroLocalBinding<'tree>> {
1551 let assignment = match statement.kind() {
1552 "assignment_expression" => statement,
1553 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1554 _ => return None,
1555 };
1556 if assignment.kind() != "assignment_expression" {
1557 return None;
1558 }
1559 let call = assignment.child_by_field_name("left")?;
1560 if call.kind() != "call_expression" {
1561 return None;
1562 }
1563 let function = call.child_by_field_name("function")?;
1564 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1565 return None;
1566 }
1567 let arguments = call.child_by_field_name("arguments")?;
1568 let mut actuals = argument_children(arguments);
1569 let type_node = actuals.next()?;
1570 if actuals.next().is_some()
1571 || !matches!(
1572 type_node.kind(),
1573 "identifier"
1574 | "type_identifier"
1575 | "qualified_identifier"
1576 | "scoped_type_identifier"
1577 | "template_type"
1578 )
1579 {
1580 return None;
1581 }
1582 let name_node = (0..assignment.named_child_count())
1583 .filter_map(|index| assignment.named_child(index))
1584 .filter(|child| child.kind() == "ERROR")
1585 .filter_map(|error| {
1586 (error.named_child_count() == 1)
1587 .then(|| error.named_child(0))
1588 .flatten()
1589 })
1590 .find(|node| node.kind() == "identifier")?;
1591 let name = node_text(name_node, source).trim();
1592 let type_name = node_text(type_node, source).trim();
1593 if name.is_empty() || type_name.is_empty() {
1594 return None;
1595 }
1596 Some(MacroLocalBinding {
1597 name: name.to_string(),
1598 type_name: type_name.to_string(),
1599 type_node: Some(type_node),
1600 pointer_depth: 1,
1601 proven_unit: None,
1602 })
1603}
1604
1605#[derive(Clone, PartialEq, Eq)]
1606pub struct MacroBinding {
1607 source: ProjectFile,
1608 declaration_byte: usize,
1609 definition: MacroDefinition,
1610 exact: bool,
1611}
1612
1613impl MacroBinding {
1614 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1615 Self {
1616 source: source.clone(),
1617 declaration_byte,
1618 definition: MacroDefinition::Unsupported,
1619 exact: false,
1620 }
1621 }
1622
1623 fn is_exact(&self) -> bool {
1624 self.exact
1625 }
1626
1627 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1628 Self {
1629 source: source.clone(),
1630 declaration_byte,
1631 definition: current.definition.clone(),
1632 exact: false,
1633 }
1634 }
1635}
1636
1637type OwningPreprocessorConditionals = Box<[usize]>;
1649
1650#[derive(Clone)]
1651pub enum MacroEvent {
1652 Define {
1653 name: String,
1654 binding: MacroBinding,
1655 byte: usize,
1656 conditionals: OwningPreprocessorConditionals,
1657 },
1658 Undef {
1659 name: String,
1660 byte: usize,
1661 conditionals: OwningPreprocessorConditionals,
1662 },
1663 Include {
1664 targets: Vec<ProjectFile>,
1665 byte: usize,
1666 conditionals: OwningPreprocessorConditionals,
1667 },
1668 Invalidate {
1669 byte: usize,
1670 },
1671}
1672
1673impl MacroEvent {
1674 pub fn byte(&self) -> usize {
1675 match self {
1676 Self::Define { byte, .. }
1677 | Self::Undef { byte, .. }
1678 | Self::Include { byte, .. }
1679 | Self::Invalidate { byte } => *byte,
1680 }
1681 }
1682}
1683
1684#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1685pub enum CallArityEvidence {
1686 Exact(usize),
1687 Unknown,
1688}
1689
1690impl CallArityEvidence {
1691 pub fn exact(self) -> Option<usize> {
1692 match self {
1693 Self::Exact(arity) => Some(arity),
1694 Self::Unknown => None,
1695 }
1696 }
1697
1698 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1699 self.exact().map(|arity| expected.accepts(arity))
1700 }
1701}
1702
1703struct CppAlias {
1704 name: String,
1705 target: String,
1706 namespace: Option<String>,
1707}
1708
1709type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1710
1711#[derive(Debug, Clone, PartialEq, Eq)]
1715pub enum CppTemplateResolutionError {
1716 AliasCycle { alias: CodeUnit },
1718 ArgumentBinding,
1720 Substitution,
1722 PrimarySelection,
1725 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1728}
1729
1730fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1733 let mut distinct: Vec<CodeUnit> = Vec::new();
1734 for unit in units {
1735 if !distinct
1736 .iter()
1737 .any(|existing| same_visible_symbol(existing, unit))
1738 {
1739 distinct.push(unit.clone());
1740 }
1741 }
1742 distinct
1743}
1744
1745pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1748 visibility: Factory,
1749 file: &ProjectFile,
1750 root: Node<'_>,
1751 source: &str,
1752 max_references: usize,
1753 cancelled: impl FnMut() -> bool,
1754) -> MacroLexicalReferences
1755where
1756 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1757{
1758 crate::graph::macro_lexical::all_references(
1759 visibility,
1760 file,
1761 root,
1762 source,
1763 max_references,
1764 cancelled,
1765 )
1766}
1767
1768impl<'a> VisibilityIndex<'a> {
1769 pub fn cpp(&self) -> &'a dyn CppSource {
1770 self.cpp
1771 }
1772
1773 pub fn token(&self) -> QueryToken<'a> {
1775 self.token
1776 }
1777
1778 pub fn has_unresolved_include_visible_before(
1786 &self,
1787 file: &ProjectFile,
1788 before_byte: usize,
1789 ) -> bool {
1790 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1791 return false;
1792 };
1793 let cell = self
1794 .structured_include_fact_cells
1795 .lock()
1796 .expect("C++ structured include-fact cache poisoned")
1797 .entry(file.clone())
1798 .or_default()
1799 .clone();
1800 let facts = cell.get_or_init(|| collect_structured_include_facts(prepared.as_ref()));
1801 has_unresolved_include_visible_before_in_prepared(
1802 file,
1803 prepared.as_ref(),
1804 self.cpp.include_target_index(),
1805 facts,
1806 before_byte,
1807 )
1808 }
1809
1810 #[cfg(any(test, feature = "test-support"))]
1818 pub fn from_visible_files_for_test(
1819 cpp: &'a dyn CppSource,
1820 token: QueryToken<'a>,
1821 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1822 ) -> Self {
1823 let visible_source_files_by_root = visible_by_file
1824 .iter()
1825 .map(|(file, visible)| {
1826 (
1827 file.clone(),
1828 visible
1829 .iter()
1830 .map(|unit| unit.source().clone())
1831 .chain(std::iter::once(file.clone()))
1832 .collect(),
1833 )
1834 })
1835 .collect();
1836 let mut global_field_internal_linkage = HashMap::default();
1837 Self {
1838 cpp,
1839 token,
1840 visible_by_identifier: build_visible_identifier_index(
1841 &CppGraphSource::from_source(cpp, token),
1842 &visible_by_file,
1843 &visible_source_files_by_root,
1844 &mut global_field_internal_linkage,
1845 ),
1846 global_field_internal_linkage,
1847 visible_by_file,
1848 visible_source_files_by_root,
1849 alias_cells: Mutex::new(HashMap::default()),
1850 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1851 parser_alias_target_matches: RwLock::new(HashMap::default()),
1852 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1853 project_using_index: OnceLock::new(),
1854 callable_reference_specs: Mutex::new(HashMap::default()),
1855 structured_include_fact_cells: Mutex::new(HashMap::default()),
1856 include_activation_cells: Mutex::new(HashMap::default()),
1857 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1858 include_path_admission_cells: Mutex::new(HashMap::default()),
1859 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1860 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1861 conditional_include_projection_state_count: AtomicUsize::new(0),
1862 conditional_include_target_state_count: AtomicUsize::new(0),
1863 include_activation_build_count: AtomicUsize::new(0),
1864 using_donor_activation_count: AtomicUsize::new(0),
1865 using_namespace_lookup_count: AtomicUsize::new(0),
1866 using_name_candidate_inspection_count: AtomicUsize::new(0),
1867 callable_reference_spec_build_count: AtomicUsize::new(0),
1868 alias_source_parse_counts: Mutex::new(HashMap::default()),
1869 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1870 parser_alias_fallback_calls: AtomicUsize::new(0),
1871 parser_alias_fallback_files: AtomicUsize::new(0),
1872 parser_alias_source_parses: AtomicUsize::new(0),
1873 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1874 field_type_facts: Mutex::new(HashMap::default()),
1875 structured_alias_targets: Mutex::new(HashMap::default()),
1876 callable_comparables: Mutex::new(HashMap::default()),
1877 comparable_name_declarations: Mutex::new(HashMap::default()),
1878 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1879 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1880 precise_parent_cache: Mutex::new(HashMap::default()),
1881 c_tag_kind_cache: Mutex::new(HashMap::default()),
1882 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1883 macro_event_cells: Mutex::new(HashMap::default()),
1884 macro_event_name_sets: Mutex::new(HashMap::default()),
1885 macro_include_protection_cells: Mutex::new(HashMap::default()),
1886 macro_environment_checkpoints: Mutex::new(HashMap::default()),
1887 macro_replacements: Mutex::new(HashMap::default()),
1888 macro_local_binding_templates: Mutex::new(HashMap::default()),
1889 macro_lexical_templates: Mutex::new(HashMap::default()),
1890 macro_replacement_bodies: Mutex::new(HashMap::default()),
1891 macro_type_parameters: Mutex::new(HashMap::default()),
1892 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1893 macro_replacement_parse_count: AtomicUsize::new(0),
1894 macro_event_application_count: AtomicUsize::new(0),
1895 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1896 macro_environment_copy_count: AtomicUsize::new(0),
1897 macro_environment_request_count: AtomicUsize::new(0),
1898 cpp_template_metadata: HashMap::default(),
1899 cpp_template_families: HashMap::default(),
1900 qualified_candidate_inspections: AtomicUsize::new(0),
1901 target_preserving_type_resolution_count: AtomicUsize::new(0),
1902 visibility_identifier_lookup_count: 0,
1903 visibility_identifier_batch_count: 0,
1904 }
1905 }
1906
1907 fn cpp_source(&self) -> CppGraphSource<'a> {
1914 CppGraphSource::from_source(self.cpp, self.token)
1915 }
1916
1917 pub fn build(
1918 cpp: &'a dyn CppSource,
1919 token: QueryToken<'a>,
1920 analyzer: &CppGraphSource<'_>,
1921 roots: &HashSet<ProjectFile>,
1922 ) -> Self {
1923 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1924 }
1925
1926 pub fn build_with_cancellation(
1927 cpp: &'a dyn CppSource,
1928 token: QueryToken<'a>,
1929 analyzer: &CppGraphSource<'_>,
1930 roots: &HashSet<ProjectFile>,
1931 cancellation: Option<&CancellationToken>,
1932 ) -> Self {
1933 let visibility_started = Instant::now();
1934 let include_targets = cpp.include_target_index();
1935 let includes_started = Instant::now();
1936 let mut include_graph = IncludeGraph::default();
1937 for root in roots {
1938 include_graph.extend_with(root, cancellation, &mut |file| {
1939 cpp.canonical_include_paths(token, file)
1940 .unwrap_or_default()
1941 .into_iter()
1942 .flat_map(|include| {
1943 resolve_include_targets_with_index(file, &include, include_targets)
1944 })
1945 .collect()
1946 });
1947 }
1948 let include_elapsed = includes_started.elapsed();
1949 let include_file_count = include_graph.files().count();
1950 let visible_source_files_by_root = roots
1951 .iter()
1952 .map(|root| {
1953 (
1954 root.clone(),
1955 include_graph.reachable_files(root, cancellation),
1956 )
1957 })
1958 .collect::<HashMap<_, _>>();
1959 let mut visibility_stats = BoundedVisibilityStats::default();
1960 let mut visible_by_file = build_bounded_visible_declarations(
1961 cpp,
1962 token,
1963 analyzer,
1964 roots,
1965 &visible_source_files_by_root,
1966 cancellation,
1967 &mut visibility_stats,
1968 );
1969 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
1970 eprintln!(
1971 "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={}",
1972 visibility_started.elapsed().as_millis(),
1973 include_elapsed.as_millis(),
1974 include_file_count,
1975 visibility_stats.rounds,
1976 visibility_stats.root_names,
1977 visibility_stats.identifier_lookups,
1978 visibility_stats.identifier_batches,
1979 visibility_stats.candidate_units,
1980 visibility_stats.candidate_sources,
1981 visibility_stats.declaration_reads,
1982 visibility_stats.declaration_units,
1983 visibility_stats.selected_units,
1984 visibility_stats.dependency_source_names,
1985 visibility_stats.dependency_names,
1986 visibility_stats.lookup_elapsed.as_millis(),
1987 visibility_stats.declaration_elapsed.as_millis(),
1988 visibility_stats.dependency_source_elapsed.as_millis(),
1989 );
1990 }
1991 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
1992 let finalize_started = Instant::now();
1993 if report_stats {
1994 eprintln!(
1995 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
1996 visible_by_file.len(),
1997 visible_by_file.values().map(HashSet::len).sum::<usize>(),
1998 );
1999 }
2000 let owner_started = Instant::now();
2001 if report_stats {
2002 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2003 }
2004 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2005 if report_stats {
2006 eprintln!(
2007 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2008 owner_stats.unseen_owners,
2009 owner_stats.definition_lookups,
2010 owner_stats.admitted,
2011 owner_started.elapsed().as_millis(),
2012 );
2013 }
2014 let mut global_field_internal_linkage = HashMap::default();
2015 let identifier_started = Instant::now();
2016 if report_stats {
2017 eprintln!(
2018 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2019 );
2020 }
2021 let visible_by_identifier = build_visible_identifier_index(
2022 analyzer,
2023 &visible_by_file,
2024 &visible_source_files_by_root,
2025 &mut global_field_internal_linkage,
2026 );
2027 if report_stats {
2028 eprintln!(
2029 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2030 visible_by_identifier.len(),
2031 visible_by_identifier
2032 .values()
2033 .map(HashMap::len)
2034 .sum::<usize>(),
2035 visible_by_identifier
2036 .values()
2037 .flat_map(HashMap::values)
2038 .map(Vec::len)
2039 .sum::<usize>(),
2040 identifier_started.elapsed().as_millis(),
2041 );
2042 }
2043 let mut cpp_template_metadata = HashMap::default();
2044 let metadata_started = Instant::now();
2045 let mut template_classes = 0usize;
2046 if report_stats {
2047 eprintln!(
2048 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2049 );
2050 }
2051 for unit in visible_by_file
2052 .values()
2053 .flatten()
2054 .filter(|unit| unit.is_class())
2055 {
2056 template_classes += 1;
2057 if cpp_template_metadata.contains_key(unit) {
2058 continue;
2059 }
2060 if let Some(metadata) = cpp.template_metadata(unit) {
2061 cpp_template_metadata.insert(unit.clone(), metadata);
2062 }
2063 }
2064 if report_stats {
2065 eprintln!(
2066 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2067 template_classes,
2068 cpp_template_metadata.len(),
2069 metadata_started.elapsed().as_millis(),
2070 );
2071 }
2072 let families_started = Instant::now();
2073 if report_stats {
2074 eprintln!(
2075 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2076 );
2077 }
2078 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2079 for (unit, metadata) in &cpp_template_metadata {
2080 cpp_template_families
2081 .entry(metadata.primary_fq_name.clone())
2082 .or_default()
2083 .push(unit.clone());
2084 }
2085 for family in cpp_template_families.values_mut() {
2094 sort_lookup_units(family);
2095 }
2096 if report_stats {
2097 eprintln!(
2098 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2099 cpp_template_families.len(),
2100 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2101 families_started.elapsed().as_millis(),
2102 );
2103 eprintln!(
2104 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2105 visible_by_file.len(),
2106 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2107 finalize_started.elapsed().as_millis(),
2108 visibility_started.elapsed().as_millis(),
2109 );
2110 }
2111 Self {
2112 cpp,
2113 token,
2114 visible_by_file,
2115 visible_by_identifier,
2116 global_field_internal_linkage,
2117 visible_source_files_by_root,
2118 alias_cells: Mutex::new(HashMap::default()),
2119 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2120 parser_alias_target_matches: RwLock::new(HashMap::default()),
2121 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2122 project_using_index: OnceLock::new(),
2123 callable_reference_specs: Mutex::new(HashMap::default()),
2124 structured_include_fact_cells: Mutex::new(HashMap::default()),
2125 include_activation_cells: Mutex::new(HashMap::default()),
2126 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2127 include_path_admission_cells: Mutex::new(HashMap::default()),
2128 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2129 #[cfg(any(test, feature = "test-support"))]
2130 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2131 #[cfg(any(test, feature = "test-support"))]
2132 conditional_include_projection_state_count: AtomicUsize::new(0),
2133 #[cfg(any(test, feature = "test-support"))]
2134 conditional_include_target_state_count: AtomicUsize::new(0),
2135 #[cfg(any(test, feature = "test-support"))]
2136 include_activation_build_count: AtomicUsize::new(0),
2137 #[cfg(any(test, feature = "test-support"))]
2138 using_donor_activation_count: AtomicUsize::new(0),
2139 #[cfg(any(test, feature = "test-support"))]
2140 using_namespace_lookup_count: AtomicUsize::new(0),
2141 #[cfg(any(test, feature = "test-support"))]
2142 using_name_candidate_inspection_count: AtomicUsize::new(0),
2143 #[cfg(any(test, feature = "test-support"))]
2144 callable_reference_spec_build_count: AtomicUsize::new(0),
2145 #[cfg(any(test, feature = "test-support"))]
2146 alias_source_parse_counts: Mutex::new(HashMap::default()),
2147 #[cfg(any(test, feature = "test-support"))]
2148 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2149 parser_alias_fallback_calls: AtomicUsize::new(0),
2150 parser_alias_fallback_files: AtomicUsize::new(0),
2151 parser_alias_source_parses: AtomicUsize::new(0),
2152 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2153 field_type_facts: Mutex::new(HashMap::default()),
2154 structured_alias_targets: Mutex::new(HashMap::default()),
2155 callable_comparables: Mutex::new(HashMap::default()),
2156 comparable_name_declarations: Mutex::new(HashMap::default()),
2157 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2158 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2159 precise_parent_cache: Mutex::new(HashMap::default()),
2160 c_tag_kind_cache: Mutex::new(HashMap::default()),
2161 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2162 macro_event_cells: Mutex::new(HashMap::default()),
2163 macro_event_name_sets: Mutex::new(HashMap::default()),
2164 macro_include_protection_cells: Mutex::new(HashMap::default()),
2165 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2166 macro_replacements: Mutex::new(HashMap::default()),
2167 macro_local_binding_templates: Mutex::new(HashMap::default()),
2168 macro_lexical_templates: Mutex::new(HashMap::default()),
2169 macro_replacement_bodies: Mutex::new(HashMap::default()),
2170 macro_type_parameters: Mutex::new(HashMap::default()),
2171 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2172 #[cfg(any(test, feature = "test-support"))]
2173 macro_replacement_parse_count: AtomicUsize::new(0),
2174 #[cfg(any(test, feature = "test-support"))]
2175 macro_event_application_count: AtomicUsize::new(0),
2176 #[cfg(any(test, feature = "test-support"))]
2177 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2178 #[cfg(any(test, feature = "test-support"))]
2179 macro_environment_copy_count: AtomicUsize::new(0),
2180 #[cfg(any(test, feature = "test-support"))]
2181 macro_environment_request_count: AtomicUsize::new(0),
2182 cpp_template_metadata,
2183 cpp_template_families,
2184 #[cfg(any(test, feature = "test-support"))]
2185 qualified_candidate_inspections: AtomicUsize::new(0),
2186 #[cfg(any(test, feature = "test-support"))]
2187 target_preserving_type_resolution_count: AtomicUsize::new(0),
2188 #[cfg(any(test, feature = "test-support"))]
2189 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2190 #[cfg(any(test, feature = "test-support"))]
2191 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2192 }
2193 }
2194
2195 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2196 if file == target.source() {
2197 return true;
2198 }
2199 if self.global_field_has_internal_linkage(target) {
2200 return self
2201 .visible_source_files_by_root
2202 .get(file)
2203 .is_some_and(|sources| sources.contains(target.source()));
2204 }
2205 self.visible_by_file
2206 .get(file)
2207 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2208 }
2209
2210 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2211 self.global_field_internal_linkage
2212 .get(unit)
2213 .copied()
2214 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2215 }
2216
2217 pub fn call_arity_evidence(
2218 &self,
2219 file: &ProjectFile,
2220 call: Node<'_>,
2221 source: &str,
2222 ) -> CallArityEvidence {
2223 self.call_arity_evidence_at(file, call, source, call.start_byte())
2224 }
2225
2226 pub fn call_arity_evidence_at(
2234 &self,
2235 file: &ProjectFile,
2236 call: Node<'_>,
2237 source: &str,
2238 environment_byte: usize,
2239 ) -> CallArityEvidence {
2240 let Some(arguments) = call
2241 .child_by_field_name("arguments")
2242 .or_else(|| call.child_by_field_name("parameters"))
2243 .or_else(|| call.child_by_field_name("value"))
2244 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2245 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2246 else {
2247 return CallArityEvidence::Exact(0);
2248 };
2249 let recovered_c_keyword_arguments =
2250 recovered_c_keyword_argument_count(file, call, arguments, source);
2251 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2252 let arguments = argument_children(arguments)
2253 .flat_map(|argument| {
2254 recovered_c_new_expression_arguments(argument, c_semantics)
2255 .map(Vec::from)
2256 .unwrap_or_else(|| vec![argument])
2257 })
2258 .collect::<Vec<_>>();
2259 if arguments
2260 .iter()
2261 .all(|argument| !argument_shape_may_change_arity(*argument))
2262 {
2263 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2264 }
2265 let environment = self.macro_environment(file, environment_byte);
2266 let mut stack = Vec::new();
2267 let mut total = recovered_c_keyword_arguments;
2268 for argument in arguments {
2269 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2270 return CallArityEvidence::Unknown;
2271 }
2272 let CallArityEvidence::Exact(spread) =
2273 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2274 else {
2275 return CallArityEvidence::Unknown;
2276 };
2277 total += spread;
2278 }
2279 CallArityEvidence::Exact(total)
2280 }
2281
2282 fn argument_arity_evidence(
2283 &self,
2284 argument: Node<'_>,
2285 source: &str,
2286 environment: &MacroEnvironment,
2287 stack: &mut Vec<(ProjectFile, usize)>,
2288 ) -> CallArityEvidence {
2289 let (name, invocation_arguments, function_like) = match argument.kind() {
2290 "identifier" => (node_text(argument, source), None, false),
2291 "call_expression" => {
2292 let Some(function) = argument.child_by_field_name("function") else {
2293 return CallArityEvidence::Exact(1);
2294 };
2295 if function.kind() != "identifier" {
2296 return CallArityEvidence::Exact(1);
2297 }
2298 let Some(arguments) = argument.child_by_field_name("arguments") else {
2299 return CallArityEvidence::Exact(1);
2300 };
2301 (node_text(function, source), Some(arguments), true)
2302 }
2303 _ => return CallArityEvidence::Exact(1),
2304 };
2305 let Some(binding) = environment.binding(name) else {
2306 return if environment.unknown_names {
2307 CallArityEvidence::Unknown
2308 } else {
2309 CallArityEvidence::Exact(1)
2310 };
2311 };
2312 if !binding.is_exact() {
2313 return CallArityEvidence::Unknown;
2314 }
2315 match (&binding.definition, invocation_arguments, function_like) {
2316 (MacroDefinition::Object { replacement }, None, false) => self
2317 .replacement_arity_evidence(
2318 replacement,
2319 &[],
2320 &[],
2321 source,
2322 environment,
2323 stack,
2324 binding,
2325 ),
2326 (
2327 MacroDefinition::Function {
2328 parameters,
2329 replacement,
2330 },
2331 Some(arguments),
2332 true,
2333 ) => {
2334 let actuals = argument_children(arguments).collect::<Vec<_>>();
2335 if actuals.len() != parameters.len() {
2336 CallArityEvidence::Unknown
2337 } else {
2338 self.replacement_arity_evidence(
2339 replacement,
2340 parameters,
2341 &actuals,
2342 source,
2343 environment,
2344 stack,
2345 binding,
2346 )
2347 }
2348 }
2349 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2350 _ => CallArityEvidence::Unknown,
2351 }
2352 }
2353
2354 #[allow(clippy::too_many_arguments)]
2355 fn replacement_arity_evidence(
2356 &self,
2357 replacement: &str,
2358 parameters: &[String],
2359 actuals: &[Node<'_>],
2360 actual_source: &str,
2361 environment: &MacroEnvironment,
2362 stack: &mut Vec<(ProjectFile, usize)>,
2363 binding: &MacroBinding,
2364 ) -> CallArityEvidence {
2365 let identity = (binding.source.clone(), binding.declaration_byte);
2366 if stack.contains(&identity) || replacement.trim().is_empty() {
2367 return CallArityEvidence::Unknown;
2368 }
2369 stack.push(identity);
2370 let parsed = self.parsed_macro_replacement(binding, replacement);
2371 let evidence = (|| {
2372 let ParsedMacroReplacement::Parsed {
2373 source: sentinel,
2374 tree,
2375 } = parsed.as_ref()
2376 else {
2377 return None;
2378 };
2379 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2380 let arguments = call.child_by_field_name("arguments")?;
2381 let mut total = 0usize;
2382 for argument in argument_children(arguments) {
2383 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2384 return None;
2385 }
2386 if argument.kind() == "identifier"
2387 && let Some(parameter_index) = parameters
2388 .iter()
2389 .position(|parameter| parameter == node_text(argument, sentinel))
2390 {
2391 if !macro_expansion_shape_is_safe(
2392 actuals[parameter_index],
2393 actual_source,
2394 &[],
2395 environment,
2396 ) {
2397 return None;
2398 }
2399 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2400 actuals[parameter_index],
2401 actual_source,
2402 environment,
2403 stack,
2404 ) else {
2405 return None;
2406 };
2407 total += spread;
2408 continue;
2409 }
2410 let CallArityEvidence::Exact(spread) =
2411 self.argument_arity_evidence(argument, sentinel, environment, stack)
2412 else {
2413 return None;
2414 };
2415 total += spread;
2416 }
2417 Some(CallArityEvidence::Exact(total))
2418 })()
2419 .unwrap_or(CallArityEvidence::Unknown);
2420 stack.pop();
2421 evidence
2422 }
2423
2424 fn parsed_macro_replacement(
2425 &self,
2426 binding: &MacroBinding,
2427 replacement: &str,
2428 ) -> Arc<ParsedMacroReplacement> {
2429 let key = (binding.source.clone(), binding.declaration_byte);
2430 let mut cache = self
2431 .macro_replacements
2432 .lock()
2433 .expect("C++ macro replacement cache poisoned");
2434 if let Some(parsed) = cache.get(&key) {
2435 return Arc::clone(parsed);
2436 }
2437 #[cfg(any(test, feature = "test-support"))]
2438 self.macro_replacement_parse_count
2439 .fetch_add(1, Ordering::Relaxed);
2440 let source =
2441 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2442 let mut parser = Parser::new();
2443 let parsed = parser
2444 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2445 .ok()
2446 .and_then(|()| parser.parse(&source, None))
2447 .filter(|tree| !tree.root_node().has_error())
2448 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2449 ParsedMacroReplacement::Parsed { source, tree }
2450 });
2451 let parsed = Arc::new(parsed);
2452 cache.insert(key, Arc::clone(&parsed));
2453 parsed
2454 }
2455
2456 pub fn function_macro_local_binding<'tree>(
2466 &self,
2467 file: &ProjectFile,
2468 statement: Node<'tree>,
2469 source: &str,
2470 ) -> Option<MacroLocalBinding<'tree>> {
2471 if !is_c_source_file(file) {
2472 return None;
2473 }
2474 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2475 return Some(binding);
2476 }
2477 let call = match statement.kind() {
2478 "call_expression" => statement,
2479 "expression_statement" if statement.named_child_count() == 1 => {
2480 statement.named_child(0)?
2481 }
2482 _ => return None,
2483 };
2484 if call.kind() != "call_expression" {
2485 return None;
2486 }
2487 let function = call.child_by_field_name("function")?;
2488 if function.kind() != "identifier" {
2489 return None;
2490 }
2491 let arguments = call.child_by_field_name("arguments")?;
2492 let actuals = argument_children(arguments).collect::<Vec<_>>();
2493 let environment = self.macro_environment(file, call.start_byte());
2494 let function_name = node_text(function, source);
2495 let binding = environment.binding(function_name)?;
2496 let MacroDefinition::Function {
2497 parameters,
2498 replacement,
2499 } = &binding.definition
2500 else {
2501 return None;
2502 };
2503 if actuals.len() != parameters.len() {
2504 return None;
2505 }
2506 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2507 let (type_name, type_node) = match &template.declared_type {
2508 MacroLocalBindingTypeTemplate::Parameter(index) => {
2509 let actual = *actuals.get(*index)?;
2510 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2511 return None;
2512 }
2513 (node_text(actual, source).trim().to_string(), Some(actual))
2514 }
2515 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2516 };
2517 if type_name.is_empty() {
2518 return None;
2519 }
2520 Some(MacroLocalBinding {
2521 name: template.name.clone(),
2522 type_name,
2523 type_node,
2524 pointer_depth: template.pointer_depth,
2525 proven_unit: None,
2526 })
2527 }
2528
2529 pub fn function_macro_container_binding<'tree>(
2538 &self,
2539 analyzer: &CppGraphSource<'_>,
2540 file: &ProjectFile,
2541 assignment: Node<'tree>,
2542 source: &str,
2543 ) -> Option<MacroLocalBinding<'tree>> {
2544 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2545 return None;
2546 }
2547 let name_node = assignment.child_by_field_name("left")?;
2548 if name_node.kind() != "identifier" {
2549 return None;
2550 }
2551 let call = assignment.child_by_field_name("right")?;
2552 if call.kind() != "call_expression" {
2553 return None;
2554 }
2555 let function = call.child_by_field_name("function")?;
2556 if function.kind() != "identifier" {
2557 return None;
2558 }
2559 let arguments = call.child_by_field_name("arguments")?;
2560 let actuals = argument_children(arguments).collect::<Vec<_>>();
2561 let function_name = node_text(function, source);
2562 let environment = self.macro_environment(file, call.start_byte());
2563 let binding = environment.binding(function_name)?;
2564 if !binding.is_exact() {
2565 return None;
2566 }
2567 let MacroDefinition::Function {
2568 parameters,
2569 replacement,
2570 } = &binding.definition
2571 else {
2572 return None;
2573 };
2574 if actuals.len() != parameters.len() {
2575 return None;
2576 }
2577 let body = self.parsed_macro_replacement_body(
2578 &(binding.source.clone(), binding.declaration_byte),
2579 parameters,
2580 replacement,
2581 )?;
2582 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2583 let type_argument = *actuals.get(type_parameter)?;
2584 let type_node = macro_type_argument_node(type_argument, source)?;
2585 let type_name = node_text(type_node, source).trim().to_string();
2586 if type_name.is_empty() {
2587 return None;
2588 }
2589 let explicit_tag = match node_text(type_argument, source) {
2590 "struct" => Some(CppCTagKind::Struct),
2591 "union" => Some(CppCTagKind::Union),
2592 _ => None,
2593 };
2594 let resolved_type = if let Some(tag) = explicit_tag {
2595 let candidates = self
2596 .visible_identifier_candidates(file, &type_name)
2597 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2598 .collect::<Vec<_>>();
2599 self.resolve_type_candidates(
2600 analyzer,
2601 file,
2602 &candidates,
2603 TypeCandidateResolution::Canonical,
2604 )
2605 .ok()
2606 } else {
2607 self.resolve_type_node_result(file, type_node, source)
2608 .ok()
2609 .flatten()
2610 };
2611 let proven_unit = resolved_type?;
2612 Some(MacroLocalBinding {
2613 name: node_text(name_node, source).to_string(),
2614 type_name,
2615 type_node: Some(type_node),
2616 pointer_depth: 1,
2617 proven_unit: Some(proven_unit),
2618 })
2619 }
2620
2621 pub fn macro_local_binding_at<'tree>(
2624 &self,
2625 file: &ProjectFile,
2626 root: Node<'tree>,
2627 source: &str,
2628 start_byte: usize,
2629 end_byte: usize,
2630 ) -> Option<MacroLocalBinding<'tree>> {
2631 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2632 }
2633
2634 pub fn macro_lexical_binding(
2638 &self,
2639 file: &ProjectFile,
2640 root: Node<'_>,
2641 source: &str,
2642 start_byte: usize,
2643 end_byte: usize,
2644 ) -> Option<MacroLexicalBinding> {
2645 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2646 }
2647
2648 pub fn macro_lexical_references(
2653 &self,
2654 file: &ProjectFile,
2655 root: Node<'_>,
2656 source: &str,
2657 max_references: usize,
2658 cancelled: impl FnMut() -> bool,
2659 ) -> MacroLexicalReferences {
2660 crate::graph::macro_lexical::all_references(
2661 || self,
2662 file,
2663 root,
2664 source,
2665 max_references,
2666 cancelled,
2667 )
2668 }
2669
2670 pub(crate) fn function_macro_binding_at(
2675 &self,
2676 file: &ProjectFile,
2677 name: &str,
2678 before_byte: usize,
2679 ) -> Option<(ProjectFile, usize)> {
2680 let environment = self.macro_environment(file, before_byte);
2681 let binding = environment.binding(name)?;
2682 if binding.is_exact()
2683 && matches!(
2684 binding.definition,
2685 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2686 )
2687 {
2688 return Some((binding.source.clone(), binding.declaration_byte));
2689 }
2690 if binding.source != *file {
2696 return None;
2697 }
2698 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2699 let root = prepared.tree().root_node();
2700 let source = prepared.source();
2701 let reference = root.descendant_for_byte_range(
2702 before_byte,
2703 before_byte.saturating_add(1).min(source.len()),
2704 )?;
2705 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2706 let cell = self.macro_event_cell(file);
2707 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2708 let event = events
2709 .iter()
2710 .find(|event| event.byte() == binding.declaration_byte)?;
2711 let MacroEvent::Define {
2712 name: defined_name,
2713 binding: definition,
2714 conditionals,
2715 ..
2716 } = event
2717 else {
2718 return None;
2719 };
2720 if defined_name != name
2721 || conditionals.is_empty()
2722 || !conditionals
2723 .iter()
2724 .all(|condition| reference_conditions.contains(condition))
2725 || !matches!(
2726 definition.definition,
2727 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2728 )
2729 {
2730 return None;
2731 }
2732 Some((definition.source.clone(), definition.declaration_byte))
2733 }
2734
2735 pub fn function_macro_type_argument<'tree>(
2742 &self,
2743 file: &ProjectFile,
2744 node: Node<'tree>,
2745 source: &str,
2746 ) -> Option<Node<'tree>> {
2747 if !is_c_source_file(file) {
2748 return None;
2749 }
2750 let (call, argument_index, argument) = c_function_macro_argument(node)?;
2751 let function = call.child_by_field_name("function")?;
2752 let function_name = node_text(function, source);
2753 if function_name.is_empty() {
2754 return None;
2755 }
2756 if !self.file_defines_macro_name(file, function_name)
2757 && !self
2758 .visible_identifier_candidates(file, function_name)
2759 .any(|candidate| candidate.is_macro())
2760 {
2761 return None;
2762 }
2763 let environment = self.macro_environment(file, call.start_byte());
2764 let binding = environment.binding(function_name)?;
2765 if !binding.is_exact() {
2766 return None;
2767 }
2768 let (parameters, replacement, variadic) = match &binding.definition {
2769 MacroDefinition::Function {
2770 parameters,
2771 replacement,
2772 } => (parameters, replacement, false),
2773 MacroDefinition::VariadicFunction {
2774 parameters,
2775 replacement,
2776 } => (parameters, replacement, true),
2777 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2778 };
2779 let actuals = call
2780 .child_by_field_name("arguments")
2781 .map(argument_children)
2782 .into_iter()
2783 .flatten()
2784 .collect::<Vec<_>>();
2785 let arity_matches = if variadic {
2786 actuals.len() >= parameters.len()
2787 } else {
2788 actuals.len() == parameters.len()
2789 };
2790 let type_parameters = self.macro_type_parameter_indices(
2791 &(binding.source.clone(), binding.declaration_byte),
2792 parameters,
2793 replacement,
2794 )?;
2795 if !arity_matches || !type_parameters.contains(&argument_index) {
2796 return None;
2797 }
2798 let type_node = macro_type_argument_node(argument, source)?;
2799 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2800 return None;
2801 }
2802 Some(type_node)
2803 }
2804
2805 fn macro_local_binding_template(
2806 &self,
2807 binding: &MacroBinding,
2808 parameters: &[String],
2809 replacement: &str,
2810 ) -> Option<Arc<MacroLocalBindingTemplate>> {
2811 let key = (binding.source.clone(), binding.declaration_byte);
2812 if let Some(template) = self
2813 .macro_local_binding_templates
2814 .lock()
2815 .expect("C++ macro local-binding cache poisoned")
2816 .get(&key)
2817 {
2818 return template.clone();
2819 }
2820 let template = (|| {
2821 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2822 let sentinel = body.source.as_str();
2823 let statements = body.statements()?;
2824 if statements.named_child_count() != 1 {
2825 return None;
2826 }
2827 let declaration = statements.named_child(0)?;
2828 if declaration.kind() != "declaration" {
2829 return None;
2830 }
2831 let type_node = declaration
2832 .child_by_field_name("type")
2833 .or_else(|| first_type_child(declaration))?;
2834 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2835 let mut cursor = declaration.walk();
2836 declaration.named_children(&mut cursor).find_map(|child| {
2837 if child.kind() == "init_declarator" {
2838 child.child_by_field_name("declarator")
2839 } else {
2840 is_declarator_node(child).then_some(child)
2841 }
2842 })
2843 })?;
2844 let name = extract_variable_name(declarator, sentinel)?;
2845 let pointer_depth =
2846 declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
2847 let type_text = node_text(type_node, sentinel).trim();
2848 let declared_type = parameters
2849 .iter()
2850 .position(|parameter| parameter == type_text)
2851 .map(MacroLocalBindingTypeTemplate::Parameter)
2852 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2853 Some(Arc::new(MacroLocalBindingTemplate {
2854 name,
2855 declared_type,
2856 pointer_depth,
2857 }))
2858 })();
2859 self.macro_local_binding_templates
2860 .lock()
2861 .expect("C++ macro local-binding cache poisoned")
2862 .insert(key, template.clone());
2863 template
2864 }
2865
2866 pub fn function_macro_replacement_body(
2873 &self,
2874 file: &ProjectFile,
2875 definition: Node<'_>,
2876 source: &str,
2877 ) -> Option<Arc<ParsedReplacementBody>> {
2878 debug_assert_eq!(definition.kind(), "preproc_function_def");
2879 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
2880 MacroDefinition::Function {
2881 parameters,
2882 replacement,
2883 }
2884 | MacroDefinition::VariadicFunction {
2885 parameters,
2886 replacement,
2887 } => (parameters, replacement),
2888 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2889 };
2890 self.parsed_macro_replacement_body(
2891 &(file.clone(), definition.start_byte()),
2892 ¶meters,
2893 &replacement,
2894 )
2895 }
2896
2897 fn parsed_macro_replacement_body(
2905 &self,
2906 key: &(ProjectFile, usize),
2907 parameters: &[String],
2908 replacement: &str,
2909 ) -> Option<Arc<ParsedReplacementBody>> {
2910 if let Some(body) = self
2911 .macro_replacement_bodies
2912 .lock()
2913 .expect("C++ macro replacement body cache poisoned")
2914 .get(key)
2915 {
2916 return body.clone();
2917 }
2918 let body = (|| {
2919 if replacement.trim().is_empty() {
2920 return None;
2921 }
2922 let (source, tree, original_offsets) =
2923 Self::parse_macro_replacement_body(replacement, parameters)?;
2924 let body = ParsedReplacementBody {
2925 source,
2926 tree,
2927 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2928 parameters: parameters.to_vec(),
2929 original_offsets,
2930 };
2931 body.statements()?;
2932 if body.expands_variadic_arguments() {
2933 return None;
2934 }
2935 Some(Arc::new(body))
2936 })();
2937 self.macro_replacement_bodies
2938 .lock()
2939 .expect("C++ macro replacement body cache poisoned")
2940 .insert(key.clone(), body.clone());
2941 body
2942 }
2943
2944 fn parse_macro_replacement_body(
2952 replacement: &str,
2953 parameters: &[String],
2954 ) -> Option<(String, Tree, Box<[usize]>)> {
2955 let normalized = normalize_macro_continuations(replacement);
2956 let parse = |replacement: &str| {
2957 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
2958 let mut parser = Parser::new();
2959 parser
2960 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2961 .ok()?;
2962 let tree = parser.parse(&source, None)?;
2963 Some((source, tree))
2964 };
2965 let (source, tree) = parse(&normalized)?;
2966 if !tree.root_node().has_error() {
2967 let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
2968 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
2969 return Some((source, tree, original_offsets.into_boxed_slice()));
2970 }
2971
2972 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
2973 let mut insertion_points = Vec::new();
2974 let mut stack = vec![tree.root_node()];
2975 while let Some(node) = stack.pop() {
2976 if matches!(
2977 node.kind(),
2978 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
2979 ) && parameters
2980 .iter()
2981 .any(|parameter| parameter.as_str() == node_text(node, &source))
2982 && Self::macro_formal_needs_statement_separator(node)
2983 {
2984 let point = node.end_byte().saturating_sub(body_offset);
2985 if point <= normalized.len() && !insertion_points.contains(&point) {
2986 insertion_points.push(point);
2987 }
2988 }
2989 push_named_children_reversed(node, &mut stack);
2990 }
2991 if insertion_points.is_empty() {
2992 return None;
2993 }
2994 insertion_points.sort_unstable();
2995 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
2996 let mut original_offsets =
2997 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
2998 let mut next_insertion = 0;
2999 for (index, byte) in normalized.bytes().enumerate() {
3000 recovered.push(byte);
3001 original_offsets.push(index);
3002 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3003 recovered.push(b';');
3004 original_offsets.push(index + 1);
3005 next_insertion += 1;
3006 }
3007 }
3008 original_offsets.push(normalized.len());
3009 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3010 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3011 let (source, tree) = parse(&recovered)?;
3012 if tree.root_node().has_error() {
3013 return None;
3014 }
3015 Some((source, tree, original_offsets.into_boxed_slice()))
3016 }
3017
3018 fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3023 offsets.extend([replacement_end; 3]);
3024 }
3025
3026 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3031 let mut current = node;
3032 let mut crossed_recovery = false;
3033 while let Some(parent) = current.parent() {
3034 if parent.is_error() {
3035 crossed_recovery = true;
3036 current = parent;
3037 continue;
3038 }
3039 if matches!(
3040 parent.kind(),
3041 "call_expression"
3042 | "argument_list"
3043 | "field_expression"
3044 | "binary_expression"
3045 | "unary_expression"
3046 | "assignment_expression"
3047 | "conditional_expression"
3048 | "parenthesized_expression"
3049 | "subscript_expression"
3050 ) {
3051 return false;
3052 }
3053 if parent.kind() == "expression_statement" {
3054 return parent.named_child_count() == 1;
3055 }
3056 if parent.kind() == "compound_statement" && current == node {
3057 return true;
3058 }
3059 if matches!(
3060 parent.kind(),
3061 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3062 ) {
3063 return crossed_recovery
3064 || parent.child_by_field_name("consequence") == Some(current);
3065 }
3066 if matches!(parent.kind(), "declaration" | "init_declarator") {
3067 return false;
3068 }
3069 current = parent;
3070 }
3071 false
3072 }
3073
3074 fn macro_type_parameter_indices(
3082 &self,
3083 key: &(ProjectFile, usize),
3084 parameters: &[String],
3085 replacement: &str,
3086 ) -> Option<Arc<[usize]>> {
3087 if let Some(indices) = self
3088 .macro_type_parameters
3089 .lock()
3090 .expect("C++ macro type-parameter cache poisoned")
3091 .get(key)
3092 {
3093 return indices.clone();
3094 }
3095 #[cfg(any(test, feature = "test-support"))]
3096 self.macro_replacement_parse_count
3097 .fetch_add(1, Ordering::Relaxed);
3098 let indices = (|| {
3099 if replacement.trim().is_empty() {
3100 return None;
3101 }
3102 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3106 let mut parser = Parser::new();
3107 parser
3108 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3109 .ok()?;
3110 let tree = parser.parse(&source, None)?;
3111 let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3112 Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3113 let body = ParsedReplacementBody {
3114 source,
3115 tree,
3116 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3117 parameters: parameters.to_vec(),
3118 original_offsets: original_offsets.into_boxed_slice(),
3119 };
3120 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3121 })();
3122 self.macro_type_parameters
3123 .lock()
3124 .expect("C++ macro type-parameter cache poisoned")
3125 .insert(key.clone(), indices.clone());
3126 indices
3127 }
3128
3129 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3130 let replacement = if node.kind() == "preproc_function_def" {
3131 function_macro_replacement_span(node, source)
3132 .and_then(|span| source.get(span))
3133 .map(str::to_owned)
3134 .or_else(|| {
3135 node.child_by_field_name("value")
3136 .map(|value| node_text(value, source).to_string())
3137 })
3138 .unwrap_or_default()
3139 } else {
3140 node.child_by_field_name("value")
3141 .map(|value| node_text(value, source).to_string())
3142 .unwrap_or_default()
3143 };
3144 if node.kind() == "preproc_def" {
3145 return MacroDefinition::Object { replacement };
3146 }
3147 let Some(parameters) = node.child_by_field_name("parameters") else {
3148 return MacroDefinition::Unsupported;
3149 };
3150 let variadic = (0..parameters.child_count()).any(|index| {
3151 parameters
3152 .child(index)
3153 .is_some_and(|child| child.kind() == "...")
3154 });
3155 let parameters = (0..parameters.named_child_count())
3156 .filter_map(|index| parameters.named_child(index))
3157 .map(|parameter| node_text(parameter, source).to_string())
3158 .collect::<Vec<_>>();
3159 if variadic {
3160 MacroDefinition::VariadicFunction {
3161 parameters,
3162 replacement,
3163 }
3164 } else {
3165 MacroDefinition::Function {
3166 parameters,
3167 replacement,
3168 }
3169 }
3170 }
3171
3172 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3173 self.macro_event_cells
3174 .lock()
3175 .expect("C++ macro event cache poisoned")
3176 .entry(file.clone())
3177 .or_default()
3178 .clone()
3179 }
3180
3181 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3182 if let Some(names) = self
3183 .macro_event_name_sets
3184 .lock()
3185 .expect("C++ macro event-name cache poisoned")
3186 .get(file)
3187 .cloned()
3188 {
3189 return names.contains(name);
3190 }
3191 let cell = self.macro_event_cell(file);
3192 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3193 let names = Arc::new(
3194 events
3195 .iter()
3196 .filter_map(|event| match event {
3197 MacroEvent::Define { name, .. } => Some(name.clone()),
3198 MacroEvent::Undef { .. }
3199 | MacroEvent::Include { .. }
3200 | MacroEvent::Invalidate { .. } => None,
3201 })
3202 .collect(),
3203 );
3204 self.macro_event_name_sets
3205 .lock()
3206 .expect("C++ macro event-name cache poisoned")
3207 .insert(file.clone(), Arc::clone(&names));
3208 names.contains(name)
3209 }
3210
3211 fn macro_environment_checkpoint_cell(
3212 &self,
3213 file: &ProjectFile,
3214 ) -> MacroEnvironmentCheckpointCell {
3215 self.macro_environment_checkpoints
3216 .lock()
3217 .expect("C++ macro environment checkpoint cache poisoned")
3218 .entry(file.clone())
3219 .or_default()
3220 .clone()
3221 }
3222
3223 pub fn macro_environment(
3225 &self,
3226 file: &ProjectFile,
3227 before_byte: usize,
3228 ) -> Arc<MacroEnvironment> {
3229 #[cfg(any(test, feature = "test-support"))]
3230 self.macro_environment_request_count
3231 .fetch_add(1, Ordering::Relaxed);
3232 let cell = self.macro_event_cell(file);
3233 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3234 let frontier = events.partition_point(|event| event.byte() < before_byte);
3235 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3236 let checkpoints =
3237 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3238 let checkpoint = checkpoints.at_or_before(frontier);
3239 if checkpoint.frontier == frontier {
3240 return Arc::clone(&checkpoint.environment);
3241 }
3242 #[cfg(any(test, feature = "test-support"))]
3243 self.macro_environment_copy_count
3244 .fetch_add(1, Ordering::Relaxed);
3245 let mut environment = checkpoint.environment.as_ref().clone();
3246 let mut include_stack = HashSet::from_iter([file.clone()]);
3247 for event in &events[checkpoint.frontier..frontier] {
3248 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3249 }
3250 Arc::new(environment)
3251 }
3252
3253 fn build_macro_environment_checkpoints(
3256 &self,
3257 file: &ProjectFile,
3258 events: &[MacroEvent],
3259 ) -> MacroEnvironmentCheckpoints {
3260 #[cfg(any(test, feature = "test-support"))]
3261 self.macro_environment_checkpoint_build_count
3262 .fetch_add(1, Ordering::Relaxed);
3263 let mut environment = MacroEnvironment {
3268 build_proven_defines: self
3269 .compile_proven_guards(file)
3270 .iter()
3271 .filter_map(|guard| match guard {
3272 PreprocessorGuard::Defined(name) => Some(name.clone()),
3273 _ => None,
3274 })
3275 .collect(),
3276 ..MacroEnvironment::default()
3277 };
3278 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3279 frontier: 0,
3280 environment: Arc::new(environment.clone()),
3281 }];
3282 let checkpoint_stride = events
3289 .len()
3290 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3291 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3292 let mut include_stack = HashSet::from_iter([file.clone()]);
3293 for (index, event) in events.iter().enumerate() {
3294 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3295 let frontier = index + 1;
3296 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3297 checkpoints.push(MacroEnvironmentCheckpoint {
3298 frontier,
3299 environment: Arc::new(environment.clone()),
3300 });
3301 }
3302 }
3303 MacroEnvironmentCheckpoints { checkpoints }
3304 }
3305
3306 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3315 self.macro_environment(file, before_byte)
3316 .binding(name)
3317 .is_some()
3318 }
3319
3320 pub fn macro_name_may_be_bound_at(
3321 &self,
3322 file: &ProjectFile,
3323 name: &str,
3324 before_byte: usize,
3325 ) -> bool {
3326 self.macro_environment(file, before_byte).may_bind(name)
3327 }
3328
3329 pub fn macro_binding_matches_target_at(
3333 &self,
3334 analyzer: &CppGraphSource<'_>,
3335 file: &ProjectFile,
3336 name: &str,
3337 before_byte: usize,
3338 target: &CodeUnit,
3339 ) -> bool {
3340 let ranges = analyzer.ranges(target);
3341 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3342 self.macro_binding_matches_target_declaration_at(
3343 file,
3344 name,
3345 before_byte,
3346 target.source(),
3347 &declaration_bytes,
3348 )
3349 }
3350
3351 pub(crate) fn macro_declaration_bytes(
3356 &self,
3357 target: &CodeUnit,
3358 ranges: &[Range],
3359 ) -> Vec<usize> {
3360 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3361 return Vec::new();
3362 };
3363 ranges
3364 .iter()
3365 .filter_map(|range| {
3366 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3367 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3368 node = node.parent()?;
3369 }
3370 Some(node.start_byte())
3371 })
3372 .collect()
3373 }
3374
3375 pub(crate) fn macro_binding_matches_target_declaration_at(
3376 &self,
3377 file: &ProjectFile,
3378 name: &str,
3379 before_byte: usize,
3380 target_source: &ProjectFile,
3381 target_declaration_bytes: &[usize],
3382 ) -> bool {
3383 let environment = self.macro_environment(file, before_byte);
3384 let Some(binding) = environment.binding(name) else {
3385 return false;
3386 };
3387 if binding.definition == MacroDefinition::Unsupported {
3388 return false;
3389 }
3390 if binding.source != *target_source {
3394 return false;
3395 }
3396 target_declaration_bytes.contains(&binding.declaration_byte)
3397 }
3398
3399 pub fn resolve_ordinary_macro_reference(
3406 &self,
3407 analyzer: &CppGraphSource<'_>,
3408 file: &ProjectFile,
3409 node: Node<'_>,
3410 source: &str,
3411 ) -> OrdinaryMacroReferenceResolution {
3412 if !is_ordinary_macro_reference_node(node) {
3413 return OrdinaryMacroReferenceResolution::Missing;
3414 }
3415 let name = node_text(node, source);
3416 if name.is_empty() {
3417 return OrdinaryMacroReferenceResolution::Missing;
3418 }
3419 let visible = self
3420 .visible_identifier_candidates(file, name)
3421 .filter(|candidate| candidate.is_macro())
3422 .cloned()
3423 .collect::<Vec<_>>();
3424 let mut exact = Vec::new();
3425 for candidate in &visible {
3426 if self.macro_binding_matches_target_at(
3427 analyzer,
3428 file,
3429 name,
3430 node.start_byte(),
3431 candidate,
3432 ) && !exact
3433 .iter()
3434 .any(|existing| same_visible_symbol(existing, candidate))
3435 {
3436 exact.push(candidate.clone());
3437 }
3438 }
3439 match exact.len() {
3440 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3441 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3442 0 if !visible.is_empty()
3443 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3444 {
3445 OrdinaryMacroReferenceResolution::Ambiguous
3446 }
3447 0 => OrdinaryMacroReferenceResolution::Missing,
3448 }
3449 }
3450
3451 pub fn recovered_c_reference_ranges(
3459 &self,
3460 file: &ProjectFile,
3461 root: Node<'_>,
3462 source: &str,
3463 limit: usize,
3464 ) -> RecoveredCReferenceRanges {
3465 if !is_c_source_file(file) {
3466 return RecoveredCReferenceRanges::Complete(Vec::new());
3467 }
3468 let mut ranges = Vec::new();
3469 let mut seen = HashSet::default();
3470 let mut stack = vec![(root, root.is_error())];
3471 while let Some((node, inside_error)) = stack.pop() {
3472 let inside_error = inside_error || node.is_error();
3473 if node.kind() == "preproc_arg" {
3474 let macro_value_kind = node.parent().and_then(|parent| {
3479 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3480 });
3481 if matches!(
3482 macro_value_kind,
3483 Some("preproc_def" | "preproc_function_def")
3484 ) {
3485 let name = node_text(node, source);
3486 if !name.is_empty()
3487 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3488 && !push_recovered_c_range(
3489 &mut ranges,
3490 &mut seen,
3491 node.start_byte(),
3492 node.end_byte(),
3493 node,
3494 limit,
3495 )
3496 {
3497 return RecoveredCReferenceRanges::LimitExceeded;
3498 }
3499 }
3500 if macro_value_kind == Some("preproc_def") {
3501 for reference in object_macro_replacement_type_references(node, source) {
3502 for range in reference.component_ranges {
3503 let visible = self
3504 .visible_identifier_candidates(file, &source[range.clone()])
3505 .any(|candidate| candidate.is_class() || candidate.is_module());
3506 if visible
3507 && !push_recovered_c_range(
3508 &mut ranges,
3509 &mut seen,
3510 range.start,
3511 range.end,
3512 node,
3513 limit,
3514 )
3515 {
3516 return RecoveredCReferenceRanges::LimitExceeded;
3517 }
3518 }
3519 }
3520 }
3521 }
3522 if inside_error
3523 && recovered_c_reference_node(self, file, node, source)
3524 && !push_recovered_c_range(
3525 &mut ranges,
3526 &mut seen,
3527 node.start_byte(),
3528 node.end_byte(),
3529 node,
3530 limit,
3531 )
3532 {
3533 return RecoveredCReferenceRanges::LimitExceeded;
3534 }
3535 let mut cursor = node.walk();
3536 for child in node.named_children(&mut cursor) {
3537 stack.push((child, inside_error));
3538 }
3539 }
3540 ranges.sort_unstable();
3541 RecoveredCReferenceRanges::Complete(ranges)
3542 }
3543
3544 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3550 self.visible_identifier_candidates(file, target.identifier())
3551 .filter(|candidate| candidate.is_macro())
3552 .any(|candidate| {
3553 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3554 })
3555 }
3556
3557 pub fn object_macro_replacement_at(
3558 &self,
3559 file: &ProjectFile,
3560 name: &str,
3561 before_byte: usize,
3562 ) -> Option<String> {
3563 let environment = self.macro_environment(file, before_byte);
3564 let binding = environment.binding(name)?;
3565 if !binding.exact {
3566 return None;
3567 }
3568 match &binding.definition {
3569 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3570 MacroDefinition::Function { .. }
3571 | MacroDefinition::VariadicFunction { .. }
3572 | MacroDefinition::Unsupported => None,
3573 }
3574 }
3575
3576 fn apply_macro_events(
3577 &self,
3578 file: &ProjectFile,
3579 before_byte: Option<usize>,
3580 environment: &mut MacroEnvironment,
3581 include_stack: &mut HashSet<ProjectFile>,
3582 ) {
3583 if !include_stack.insert(file.clone()) {
3584 return;
3585 }
3586 if self.cpp.prepared_syntax(self.token, file).is_none() {
3587 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3588 include_stack.remove(file);
3589 return;
3590 }
3591 match self.macro_include_protection(file) {
3592 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3593 Some(binding) if binding.is_exact() => {
3594 include_stack.remove(file);
3595 return;
3596 }
3597 Some(_) | None if environment.unknown_names => {
3598 let mut ambiguous_seen = HashSet::default();
3599 self.mark_macro_events_ambiguous(
3600 file,
3601 environment,
3602 &mut ambiguous_seen,
3603 file,
3604 before_byte.unwrap_or_default(),
3605 );
3606 include_stack.remove(file);
3607 return;
3608 }
3609 Some(_) => {
3610 let mut ambiguous_seen = HashSet::default();
3611 self.mark_macro_events_ambiguous(
3612 file,
3613 environment,
3614 &mut ambiguous_seen,
3615 file,
3616 before_byte.unwrap_or_default(),
3617 );
3618 include_stack.remove(file);
3619 return;
3620 }
3621 None => {}
3622 },
3623 MacroIncludeProtection::PragmaOnce => {
3624 if !environment.applied_pragma_once_files.insert(file.clone()) {
3625 include_stack.remove(file);
3626 return;
3627 }
3628 if environment.maybe_applied_pragma_once_files.remove(file) {
3629 let mut ambiguous_seen = HashSet::default();
3634 environment.applied_pragma_once_files.remove(file);
3635 self.mark_macro_events_ambiguous(
3636 file,
3637 environment,
3638 &mut ambiguous_seen,
3639 file,
3640 before_byte.unwrap_or_default(),
3641 );
3642 environment.maybe_applied_pragma_once_files.remove(file);
3643 environment.applied_pragma_once_files.insert(file.clone());
3644 include_stack.remove(file);
3645 return;
3646 }
3647 }
3648 MacroIncludeProtection::None => {}
3649 }
3650 let cell = self.macro_event_cell(file);
3651 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3652 for event in events {
3653 if before_byte.is_some_and(|limit| event.byte() >= limit) {
3654 break;
3655 }
3656 self.apply_macro_event(file, event, environment, include_stack);
3657 }
3658 include_stack.remove(file);
3659 }
3660
3661 fn apply_macro_event(
3662 &self,
3663 file: &ProjectFile,
3664 event: &MacroEvent,
3665 environment: &mut MacroEnvironment,
3666 include_stack: &mut HashSet<ProjectFile>,
3667 ) {
3668 #[cfg(any(test, feature = "test-support"))]
3669 self.macro_event_application_count
3670 .fetch_add(1, Ordering::Relaxed);
3671 match event {
3672 MacroEvent::Define {
3673 name,
3674 binding,
3675 conditionals,
3676 byte,
3677 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3678 Some(true) => environment.insert(name.clone(), binding.clone()),
3679 Some(false) => {}
3680 None => Self::merge_conditional_macro_definition(
3681 environment,
3682 name,
3683 binding,
3684 file,
3685 *byte,
3686 ),
3687 },
3688 MacroEvent::Undef {
3689 name,
3690 conditionals,
3691 byte,
3692 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3693 Some(true) => environment.remove(name),
3694 Some(false) => {}
3695 None => {
3696 if environment.binding(name).is_some() {
3697 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3698 }
3699 }
3700 },
3701 MacroEvent::Include {
3702 targets,
3703 conditionals,
3704 byte,
3705 } => {
3706 let condition =
3707 self.macro_event_condition_value(file, *byte, environment, conditionals);
3708 if condition == Some(false) {
3709 return;
3710 }
3711 if targets.is_empty() {
3712 environment.mark_unknown_names(file, *byte);
3713 return;
3714 }
3715 if condition.is_none() || targets.len() > 1 {
3716 let mut ambiguous_seen = HashSet::default();
3717 for target in targets {
3718 self.mark_macro_events_ambiguous(
3719 target,
3720 environment,
3721 &mut ambiguous_seen,
3722 file,
3723 *byte,
3724 );
3725 }
3726 } else if let Some(target) = targets.first() {
3727 self.apply_macro_events(target, None, environment, include_stack);
3728 }
3729 }
3730 MacroEvent::Invalidate { byte } => {
3731 for binding in environment.bindings.values_mut() {
3732 *binding = MacroBinding::uncertain_from(binding, file, *byte);
3733 }
3734 }
3735 }
3736 }
3737
3738 fn macro_event_condition_value(
3749 &self,
3750 file: &ProjectFile,
3751 event_byte: usize,
3752 environment: &MacroEnvironment,
3753 conditionals: &OwningPreprocessorConditionals,
3754 ) -> Option<bool> {
3755 if conditionals.is_empty() {
3756 return Some(true);
3757 }
3758 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3759 let source = prepared.source();
3760 let root = prepared.tree().root_node();
3761 let descendant = root.descendant_for_byte_range(
3762 event_byte,
3763 event_byte.saturating_add(1).min(source.len()),
3764 )?;
3765 let mut unknown = false;
3766 let mut current = descendant.parent();
3767 while let Some(conditional) = current {
3768 if matches!(
3769 conditional.kind(),
3770 "preproc_if" | "preproc_ifdef" | "preproc_elif"
3771 ) && conditionals.contains(&conditional.start_byte())
3772 {
3773 let mut value = match conditional.kind() {
3774 "preproc_ifdef" => {
3775 let name = conditional.child_by_field_name("name")?;
3776 let defined =
3777 self.macro_name_defined_value(environment, node_text(name, source));
3778 match conditional.child(0)?.kind() {
3779 "#ifdef" => defined,
3780 "#ifndef" => defined.map(|defined| !defined),
3781 _ => None,
3782 }
3783 }
3784 "preproc_if" | "preproc_elif" => conditional
3785 .child_by_field_name("condition")
3786 .and_then(|condition| {
3787 self.preprocessor_integer_value(
3788 condition,
3789 source,
3790 environment,
3791 &mut Vec::new(),
3792 0,
3793 )
3794 })
3795 .map(|value| value != 0),
3796 _ => unreachable!(),
3797 };
3798 if conditional
3799 .child_by_field_name("alternative")
3800 .is_some_and(|alternative| {
3801 alternative.start_byte() <= descendant.start_byte()
3802 && descendant.end_byte() <= alternative.end_byte()
3803 })
3804 {
3805 value = value.map(|value| !value);
3806 }
3807 match value {
3808 Some(true) => {}
3809 Some(false) => return Some(false),
3810 None => unknown = true,
3811 }
3812 }
3813 current = conditional.parent();
3814 }
3815 (!unknown).then_some(true)
3816 }
3817
3818 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3819 if environment.known_undefined_names.contains(name) {
3820 return Some(false);
3821 }
3822 if let Some(binding) = environment.binding(name) {
3823 return binding.is_exact().then_some(true);
3824 }
3825 environment
3826 .build_proven_defines
3827 .contains(name)
3828 .then_some(true)
3829 }
3830
3831 fn preprocessor_integer_value(
3832 &self,
3833 expression: Node<'_>,
3834 source: &str,
3835 environment: &MacroEnvironment,
3836 expansion_stack: &mut Vec<(ProjectFile, usize)>,
3837 depth: usize,
3838 ) -> Option<i128> {
3839 if depth >= 64 {
3842 return None;
3843 }
3844 match expression.kind() {
3845 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3846 "identifier" | "type_identifier" => {
3847 let binding = environment.binding(node_text(expression, source))?;
3848 if !binding.is_exact() {
3849 return None;
3850 }
3851 let MacroDefinition::Object { replacement } = &binding.definition else {
3852 return None;
3853 };
3854 let identity = (binding.source.clone(), binding.declaration_byte);
3855 if expansion_stack.contains(&identity) {
3856 return None;
3857 }
3858 expansion_stack.push(identity);
3859 let parsed = self.parsed_macro_replacement(binding, replacement);
3860 let value = match parsed.as_ref() {
3861 ParsedMacroReplacement::Parsed {
3862 source: replacement_source,
3863 tree,
3864 } => first_descendant_of_kind(tree.root_node(), "call_expression")
3865 .and_then(|call| call.child_by_field_name("arguments"))
3866 .and_then(|arguments| argument_children(arguments).next())
3867 .and_then(|argument| {
3868 self.preprocessor_integer_value(
3869 argument,
3870 replacement_source,
3871 environment,
3872 expansion_stack,
3873 depth + 1,
3874 )
3875 }),
3876 ParsedMacroReplacement::Unsupported => None,
3877 };
3878 expansion_stack.pop();
3879 value
3880 }
3881 "preproc_defined" => {
3882 let mut cursor = expression.walk();
3883 let name = expression
3884 .named_children(&mut cursor)
3885 .find(|child| child.kind() == "identifier")?;
3886 self.macro_name_defined_value(environment, node_text(name, source))
3887 .map(i128::from)
3888 }
3889 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3890 self.preprocessor_integer_value(
3891 child,
3892 source,
3893 environment,
3894 expansion_stack,
3895 depth + 1,
3896 )
3897 }),
3898 "unary_expression" => {
3899 let operator = expression.child_by_field_name("operator")?.kind();
3900 let argument = expression.child_by_field_name("argument")?;
3901 let value = self.preprocessor_integer_value(
3902 argument,
3903 source,
3904 environment,
3905 expansion_stack,
3906 depth + 1,
3907 )?;
3908 match operator {
3909 "+" => Some(value),
3910 "-" => value.checked_neg(),
3911 "!" => Some(i128::from(value == 0)),
3912 "~" => Some(!value),
3913 _ => None,
3914 }
3915 }
3916 "binary_expression" => {
3917 let left = self.preprocessor_integer_value(
3918 expression.child_by_field_name("left")?,
3919 source,
3920 environment,
3921 expansion_stack,
3922 depth + 1,
3923 )?;
3924 let right = self.preprocessor_integer_value(
3925 expression.child_by_field_name("right")?,
3926 source,
3927 environment,
3928 expansion_stack,
3929 depth + 1,
3930 )?;
3931 match expression.child_by_field_name("operator")?.kind() {
3932 "+" => left.checked_add(right),
3933 "-" => left.checked_sub(right),
3934 "*" => left.checked_mul(right),
3935 "/" => left.checked_div(right),
3936 "%" => left.checked_rem(right),
3937 "<<" => u32::try_from(right)
3938 .ok()
3939 .and_then(|shift| left.checked_shl(shift)),
3940 ">>" => u32::try_from(right)
3941 .ok()
3942 .and_then(|shift| left.checked_shr(shift)),
3943 "<" => Some(i128::from(left < right)),
3944 "<=" => Some(i128::from(left <= right)),
3945 ">" => Some(i128::from(left > right)),
3946 ">=" => Some(i128::from(left >= right)),
3947 "==" => Some(i128::from(left == right)),
3948 "!=" => Some(i128::from(left != right)),
3949 "&" => Some(left & right),
3950 "|" => Some(left | right),
3951 "^" => Some(left ^ right),
3952 "&&" => Some(i128::from(left != 0 && right != 0)),
3953 "||" => Some(i128::from(left != 0 || right != 0)),
3954 _ => None,
3955 }
3956 }
3957 _ => None,
3958 }
3959 }
3960
3961 fn mark_macro_events_ambiguous(
3962 &self,
3963 file: &ProjectFile,
3964 environment: &mut MacroEnvironment,
3965 include_stack: &mut HashSet<ProjectFile>,
3966 conditional_file: &ProjectFile,
3967 conditional_byte: usize,
3968 ) {
3969 if !include_stack.insert(file.clone()) {
3970 return;
3971 }
3972 if self.cpp.prepared_syntax(self.token, file).is_none() {
3973 environment.mark_unknown_names(conditional_file, conditional_byte);
3974 return;
3975 }
3976 match self.macro_include_protection(file) {
3977 MacroIncludeProtection::MacroGuard(guard) => {
3978 if environment
3979 .binding(&guard)
3980 .is_some_and(MacroBinding::is_exact)
3981 {
3982 return;
3983 }
3984 }
3985 MacroIncludeProtection::PragmaOnce => {
3986 if environment.applied_pragma_once_files.contains(file) {
3987 return;
3988 }
3989 environment
3990 .maybe_applied_pragma_once_files
3991 .insert(file.clone());
3992 }
3993 MacroIncludeProtection::None => {}
3994 }
3995 let cell = self.macro_event_cell(file);
3996 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3997 for event in events {
3998 #[cfg(any(test, feature = "test-support"))]
3999 self.macro_event_application_count
4000 .fetch_add(1, Ordering::Relaxed);
4001 match event {
4002 MacroEvent::Define { name, binding, .. } => {
4003 Self::merge_conditional_macro_definition(
4004 environment,
4005 name,
4006 binding,
4007 conditional_file,
4008 conditional_byte,
4009 );
4010 }
4011 MacroEvent::Undef { name, .. } => {
4012 if environment.binding(name).is_some() {
4013 environment.insert(
4014 name.clone(),
4015 MacroBinding::ambiguous(conditional_file, conditional_byte),
4016 );
4017 } else {
4018 environment.remove_known_undefined(name);
4019 }
4020 }
4021 MacroEvent::Include { targets, .. } => {
4022 if targets.is_empty() {
4023 environment.mark_unknown_names(conditional_file, conditional_byte);
4024 continue;
4025 }
4026 for target in targets {
4027 self.mark_macro_events_ambiguous(
4028 target,
4029 environment,
4030 include_stack,
4031 conditional_file,
4032 conditional_byte,
4033 );
4034 }
4035 }
4036 MacroEvent::Invalidate { .. } => {
4037 for binding in environment.bindings.values_mut() {
4038 *binding = MacroBinding::uncertain_from(
4039 binding,
4040 conditional_file,
4041 conditional_byte,
4042 );
4043 }
4044 }
4045 }
4046 }
4047 }
4048
4049 fn merge_conditional_macro_definition(
4050 environment: &mut MacroEnvironment,
4051 name: &str,
4052 possible_binding: &MacroBinding,
4053 conditional_file: &ProjectFile,
4054 conditional_byte: usize,
4055 ) {
4056 if environment.binding(name).is_some_and(|current| {
4061 current.definition != MacroDefinition::Unsupported
4062 && current.definition == possible_binding.definition
4063 }) {
4064 return;
4065 }
4066 environment.insert(
4067 name.to_string(),
4068 MacroBinding::ambiguous(conditional_file, conditional_byte),
4069 );
4070 }
4071
4072 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4073 let cell = self
4074 .macro_include_protection_cells
4075 .lock()
4076 .expect("C++ include protection cache poisoned")
4077 .entry(file.clone())
4078 .or_default()
4079 .clone();
4080 cell.get_or_init(|| {
4081 self.cpp.prepared_syntax(self.token, file).map_or(
4082 MacroIncludeProtection::None,
4083 |prepared| {
4084 top_level_macro_include_protection(
4085 prepared.tree().root_node(),
4086 prepared.source(),
4087 )
4088 },
4089 )
4090 })
4091 .clone()
4092 }
4093
4094 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4095 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4096 return Vec::new();
4097 };
4098 let source = prepared.source();
4099 let mut events = Vec::new();
4100 let root = prepared.tree().root_node();
4101 let mut stack = vec![root];
4102 while let Some(node) = stack.pop() {
4103 match node.kind() {
4104 "preproc_def" | "preproc_function_def" => {
4105 let Some(name) = node.child_by_field_name("name") else {
4106 continue;
4107 };
4108 let name = node_text(name, source).to_string();
4109 events.push(MacroEvent::Define {
4110 name,
4111 binding: MacroBinding {
4112 source: file.clone(),
4113 declaration_byte: node.start_byte(),
4114 definition: Self::decode_macro_definition(node, source),
4115 exact: true,
4116 },
4117 byte: node.start_byte(),
4118 conditionals: owning_preprocessor_conditionals(root, node, source),
4119 });
4120 continue;
4121 }
4122 "preproc_include" => {
4123 let Some(path) = node.child_by_field_name("path") else {
4124 events.push(MacroEvent::Include {
4125 targets: Vec::new(),
4126 byte: node.start_byte(),
4127 conditionals: owning_preprocessor_conditionals(root, node, source),
4128 });
4129 continue;
4130 };
4131 let include = structured_include_path(path, source);
4132 let targets = include.map_or_else(Vec::new, |include| {
4133 resolve_include_targets_with_index(
4134 file,
4135 include,
4136 self.cpp.include_target_index(),
4137 )
4138 });
4139 if targets.is_empty()
4150 && include.is_some_and(|include| {
4151 !self.cpp.include_target_index().names_indexed_file(include)
4152 })
4153 {
4154 continue;
4155 }
4156 events.push(MacroEvent::Include {
4157 targets,
4158 byte: node.start_byte(),
4159 conditionals: owning_preprocessor_conditionals(root, node, source),
4160 });
4161 continue;
4162 }
4163 "preproc_call" => {
4164 let Some(directive) = node.child_by_field_name("directive") else {
4165 continue;
4166 };
4167 if node_text(directive, source) != "#undef" {
4168 continue;
4169 }
4170 let name = node
4171 .child_by_field_name("argument")
4172 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
4173 if let Some(name) = name {
4174 events.push(MacroEvent::Undef {
4175 name,
4176 byte: node.start_byte(),
4177 conditionals: owning_preprocessor_conditionals(root, node, source),
4178 });
4179 } else {
4180 events.push(MacroEvent::Invalidate {
4181 byte: node.start_byte(),
4182 });
4183 }
4184 continue;
4185 }
4186 _ => {}
4187 }
4188 push_named_children_reversed(node, &mut stack);
4189 }
4190 events.sort_by_key(MacroEvent::byte);
4191 events
4192 }
4193
4194 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4195 self.ordinary_type_import_cells
4196 .lock()
4197 .expect("C++ ordinary type import cache poisoned")
4198 .entry(file.clone())
4199 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4200 .clone()
4201 }
4202
4203 pub fn project_using_index(
4204 &self,
4205 build: impl FnOnce() -> ProjectUsingIndex,
4206 ) -> &ProjectUsingIndex {
4207 self.project_using_index.get_or_init(build)
4208 }
4209
4210 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4211 let mut files = self
4212 .visible_source_files_by_root
4213 .values()
4214 .flatten()
4215 .cloned()
4216 .collect::<HashSet<_>>()
4217 .into_iter()
4218 .collect::<Vec<_>>();
4219 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4220 files
4221 }
4222
4223 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4224 self.visible_source_files_by_root
4225 .get(root)
4226 .is_some_and(|files| files.contains(source))
4227 }
4228
4229 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4230 let cached = self
4231 .visible_parser_alias_name_sets
4232 .read()
4233 .expect("visible parser alias-name cache poisoned")
4234 .get(file)
4235 .cloned();
4236 let cell = if let Some(cached) = cached {
4237 cached
4238 } else {
4239 let mut cells = self
4240 .visible_parser_alias_name_sets
4241 .write()
4242 .expect("visible parser alias-name cache poisoned");
4243 Arc::clone(
4244 cells
4245 .entry(file.clone())
4246 .or_insert_with(|| Arc::new(OnceLock::new())),
4247 )
4248 };
4249 if let Some(names) = cell.get() {
4250 return names.contains(name);
4251 }
4252 let mut names = HashSet::default();
4253 let visible_files = self
4254 .visible_source_files_by_root
4255 .get(file)
4256 .cloned()
4257 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4258 for visible_file in visible_files {
4259 let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4260 return false;
4261 };
4262 for alias in aliases.get().expect("published canonical aliases").iter() {
4263 names.insert(alias.name.clone());
4264 }
4265 }
4266 #[cfg(any(test, feature = "test-support"))]
4267 self.visible_parser_alias_name_set_build_count
4268 .fetch_add(1, Ordering::Relaxed);
4269 cell.get_or_init(|| names).contains(name)
4270 }
4271
4272 pub fn parser_alias_name_may_resolve_to_target(
4273 &self,
4274 file: &ProjectFile,
4275 alias_name: &str,
4276 target: &CodeUnit,
4277 ) -> bool {
4278 let started = std::time::Instant::now();
4279 self.parser_alias_fallback_calls
4280 .fetch_add(1, Ordering::Relaxed);
4281 let key = (
4282 file.clone(),
4283 alias_name.to_string(),
4284 logical_symbol_key(target),
4285 );
4286 let cached = self
4287 .parser_alias_target_matches
4288 .read()
4289 .expect("parser alias target-match cache poisoned")
4290 .get(&key)
4291 .cloned();
4292 let cell = if let Some(cached) = cached {
4293 cached
4294 } else {
4295 let mut cells = self
4296 .parser_alias_target_matches
4297 .write()
4298 .expect("parser alias target-match cache poisoned");
4299 Arc::clone(
4300 cells
4301 .entry(key)
4302 .or_insert_with(|| Arc::new(OnceLock::new())),
4303 )
4304 };
4305 let matched = *cell.get_or_init(|| match self.visible_source_files_by_root.get(file) {
4306 None => {
4307 self.parser_alias_fallback_files
4308 .fetch_add(1, Ordering::Relaxed);
4309 self.file_alias_matches(self.cpp, file, alias_name, target)
4310 }
4311 Some(visible_files) => visible_files.iter().any(|visible_file| {
4312 self.parser_alias_fallback_files
4313 .fetch_add(1, Ordering::Relaxed);
4314 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4315 }),
4316 });
4317 self.parser_alias_fallback_elapsed_micros.fetch_add(
4318 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4319 Ordering::Relaxed,
4320 );
4321 matched
4322 }
4323
4324 fn callable_arities_for_target(
4325 &self,
4326 analyzer: &CppGraphSource<'_>,
4327 cpp: &dyn CppSource,
4328 file: &ProjectFile,
4329 prepared: &PreparedSyntaxTree,
4330 spec: &TargetSpec,
4331 ) -> Vec<ActivatedCallableArity> {
4332 let Some(signature) = spec.target.signature() else {
4333 return Vec::new();
4334 };
4335 let Some(candidates) = self
4336 .visible_by_identifier
4337 .get(file)
4338 .and_then(|by_name| by_name.get(&spec.member_name))
4339 else {
4340 return Vec::new();
4341 };
4342 let differing_candidates = candidates
4343 .iter()
4344 .filter(|candidate| {
4345 candidate.is_function()
4346 && candidate.fq_name() == spec.target.fq_name()
4347 && candidate.signature() == Some(signature)
4348 })
4349 .filter_map(|candidate| {
4350 analyzer
4351 .signature_metadata(candidate)
4352 .into_iter()
4353 .find_map(|metadata| metadata.callable_arity())
4354 .filter(|arity| Some(*arity) != spec.callable_arity)
4355 .map(|arity| (candidate, arity))
4356 })
4357 .collect::<Vec<_>>();
4358 if differing_candidates.is_empty() {
4359 return Vec::new();
4360 }
4361 let mut arities = Vec::with_capacity(differing_candidates.len());
4362 let reference = CallableReferenceContext {
4365 file,
4366 position: None,
4367 };
4368 for (candidate, candidate_arity) in differing_candidates {
4369 let declaration_activation =
4370 callable_declaration_activation_in_file(analyzer, candidate, &reference);
4371 let Some(declaration_activation) = declaration_activation else {
4372 continue;
4373 };
4374 let activation_byte = if candidate.source() == file {
4375 Some(declaration_activation)
4376 } else {
4377 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4378 };
4379 if let Some(activation_byte) = activation_byte {
4380 arities.push(ActivatedCallableArity {
4381 activation_byte,
4382 arity: candidate_arity,
4383 });
4384 }
4385 }
4386 arities
4387 }
4388
4389 fn callable_parameter_macro_arity(
4390 &self,
4391 target: &CodeUnit,
4392 parameter_types: Option<&[String]>,
4393 ) -> Option<CallableArity> {
4394 let parameter_types = parameter_types?;
4395 let [macro_name] = parameter_types else {
4396 return None;
4397 };
4398 if macro_name.is_empty()
4399 || !macro_name
4400 .chars()
4401 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4402 {
4403 return None;
4404 }
4405 let cache_key = (target.source().clone(), macro_name.clone());
4406 if let Some(cached) = self
4407 .callable_parameter_macro_arities
4408 .lock()
4409 .expect("C++ callable parameter-macro arity cache poisoned")
4410 .get(&cache_key)
4411 .copied()
4412 {
4413 return cached;
4414 }
4415 let mut visible_files = HashSet::default();
4416 collect_include_closure(
4417 &self.cpp_source(),
4418 self.cpp.include_target_index(),
4419 target.source(),
4420 &mut visible_files,
4421 None,
4422 );
4423 let mut arities = Vec::new();
4424 for visible_file in visible_files {
4425 let cell = self.macro_event_cell(&visible_file);
4426 for event in
4427 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4428 {
4429 let MacroEvent::Define { name, binding, .. } = event else {
4430 continue;
4431 };
4432 if name != macro_name {
4433 continue;
4434 }
4435 let MacroDefinition::Object { replacement } = &binding.definition else {
4436 continue;
4437 };
4438 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4439 continue;
4440 };
4441 if !arities.contains(&arity) {
4442 arities.push(arity);
4443 }
4444 }
4445 }
4446 let resolved = (|| {
4447 let required = arities
4448 .iter()
4449 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4450 .min()?;
4451 let total = arities.iter().map(|arity| arity.total()).max()?;
4452 let repeated = arities
4453 .iter()
4454 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4455 Some(CallableArity::new(required, total, repeated))
4460 })();
4461 self.callable_parameter_macro_arities
4462 .lock()
4463 .expect("C++ callable parameter-macro arity cache poisoned")
4464 .insert(cache_key, resolved);
4465 resolved
4466 }
4467
4468 pub fn include_activation_for_source(
4469 &self,
4470 cpp: &dyn CppSource,
4471 file: &ProjectFile,
4472 prepared: &PreparedSyntaxTree,
4473 donor_source: &ProjectFile,
4474 ) -> Option<usize> {
4475 let key = (file.clone(), donor_source.clone());
4476 if let Some(cached) = self
4477 .include_activation_cells
4478 .lock()
4479 .expect("C++ include activation cache poisoned")
4480 .get(&key)
4481 .copied()
4482 {
4483 return cached;
4484 }
4485 #[cfg(any(test, feature = "test-support"))]
4486 self.include_activation_build_count
4487 .fetch_add(1, Ordering::Relaxed);
4488 let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
4489 let mut cells = self
4490 .include_activation_cells
4491 .lock()
4492 .expect("C++ include activation cache poisoned");
4493 *cells.entry(key).or_insert(activation)
4494 }
4495
4496 pub fn conditional_include_projections_for_source(
4497 &self,
4498 file: &ProjectFile,
4499 prepared: &PreparedSyntaxTree,
4500 donor_source: &ProjectFile,
4501 ) -> Arc<[ConditionalIncludeProjection]> {
4502 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4503 let cell = self
4504 .conditional_include_projection_cells
4505 .lock()
4506 .expect("C++ conditional include projection cache poisoned")
4507 .entry(file.clone())
4508 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4509 .clone();
4510 let index = cell.get_or_build_pool_independent(|| {
4511 #[cfg(any(test, feature = "test-support"))]
4512 self.conditional_include_projection_index_build_count
4513 .fetch_add(1, Ordering::Relaxed);
4514 find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
4515 #[cfg(any(test, feature = "test-support"))]
4516 self.conditional_include_projection_state_count
4517 .fetch_add(1, Ordering::Relaxed);
4518 })
4519 });
4520 index
4521 .get(donor_source)
4522 .cloned()
4523 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4524 }
4525
4526 #[cfg(any(test, feature = "test-support"))]
4527 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4528 (
4529 self.conditional_include_projection_index_build_count
4530 .load(Ordering::Relaxed),
4531 self.conditional_include_projection_state_count
4532 .load(Ordering::Relaxed),
4533 )
4534 }
4535
4536 #[cfg(any(test, feature = "test-support"))]
4537 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4538 self.conditional_include_target_state_count
4539 .load(Ordering::Relaxed)
4540 }
4541
4542 #[cfg(any(test, feature = "test-support"))]
4543 pub fn include_activation_build_count_for_test(&self) -> usize {
4544 self.include_activation_build_count.load(Ordering::Relaxed)
4545 }
4546
4547 #[cfg(any(test, feature = "test-support"))]
4548 pub fn note_using_donor_activation_for_test(&self) {
4549 self.using_donor_activation_count
4550 .fetch_add(1, Ordering::Relaxed);
4551 }
4552
4553 #[cfg(not(any(test, feature = "test-support")))]
4554 pub fn note_using_donor_activation_for_test(&self) {}
4555
4556 #[cfg(any(test, feature = "test-support"))]
4557 pub fn note_using_namespace_lookup_for_test(&self) {
4558 self.using_namespace_lookup_count
4559 .fetch_add(1, Ordering::Relaxed);
4560 }
4561
4562 #[cfg(not(any(test, feature = "test-support")))]
4563 pub fn note_using_namespace_lookup_for_test(&self) {}
4564
4565 #[cfg(any(test, feature = "test-support"))]
4566 pub fn note_using_name_candidate_inspection_for_test(&self) {
4567 self.using_name_candidate_inspection_count
4568 .fetch_add(1, Ordering::Relaxed);
4569 }
4570
4571 #[cfg(not(any(test, feature = "test-support")))]
4572 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4573
4574 #[cfg(any(test, feature = "test-support"))]
4575 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4576 (
4577 self.using_donor_activation_count.load(Ordering::Relaxed),
4578 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4579 self.callable_reference_spec_build_count
4580 .load(Ordering::Relaxed),
4581 self.using_name_candidate_inspection_count
4582 .load(Ordering::Relaxed),
4583 )
4584 }
4585
4586 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4587 file == target.source()
4588 || self
4589 .visible_by_file
4590 .get(file)
4591 .is_some_and(|visible| visible.contains(target))
4592 }
4593
4594 pub fn declaration_visible_at(
4606 &self,
4607 analyzer: &CppGraphSource<'_>,
4608 file: &ProjectFile,
4609 declaration: &CodeUnit,
4610 reference_byte: usize,
4611 ) -> bool {
4612 let reference_guards = OnceCell::new();
4613 self.visible_identifier_candidates(file, declaration.identifier())
4614 .filter(|candidate| {
4615 self.same_logical_callable(analyzer, candidate, declaration)
4616 || flattened_macro_namespace_declaration_matches(
4617 analyzer,
4618 self.cpp,
4619 file,
4620 candidate,
4621 declaration,
4622 reference_byte,
4623 )
4624 })
4625 .any(|candidate| {
4626 self.physical_declaration_visible_at(
4627 analyzer,
4628 file,
4629 candidate,
4630 reference_byte,
4631 &reference_guards,
4632 )
4633 })
4634 }
4635
4636 pub fn declaration_visible_at_reference(
4643 &self,
4644 analyzer: &CppGraphSource<'_>,
4645 file: &ProjectFile,
4646 declaration: &CodeUnit,
4647 reference: Node<'_>,
4648 ) -> bool {
4649 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4650 return false;
4651 };
4652 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4653 let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4654 if !declaration_guards.iter().any(|(_, required)| {
4655 guards_compatible_at_reference(required, reference_guards.as_ref())
4656 }) {
4657 return false;
4658 }
4659 if declaration.source() == file
4660 && !analyzer.reference_uses_c_semantics(file)
4661 && (declaration.is_field() || declaration.is_callable())
4662 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4663 self.indexed_enclosing_owner_scope(analyzer, file, reference)
4664 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4665 })
4666 {
4667 return true;
4671 }
4672 if declaration.is_field() && declaration.source() == file {
4673 let reference_byte = reference.start_byte();
4674 let reference_function =
4675 real_function_definition_ancestor(reference, prepared.source());
4676 let guards = OnceCell::new();
4677 let field_reference = CallableReferenceContext {
4678 file,
4679 position: Some(CallableReferencePosition {
4680 prepared: prepared.as_ref(),
4681 byte: reference_byte,
4682 guards: &guards,
4683 }),
4684 };
4685 let mut has_local_declaration = false;
4686 let mut local_declaration_visible = false;
4687 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4688 {
4689 let Some(declaration_function) =
4690 real_function_definition_ancestor(declaration, prepared.source())
4691 else {
4692 continue;
4693 };
4694 has_local_declaration = true;
4695 if reference_function.is_some_and(|reference_function| {
4696 reference_function.start_byte() == declaration_function.start_byte()
4697 && reference_function.end_byte() == declaration_function.end_byte()
4698 }) && callable_preprocessor_context_is_visible_for_reference(
4699 declaration,
4700 prepared.source(),
4701 &field_reference,
4702 ) && declaration.end_byte() < reference_byte
4703 {
4704 local_declaration_visible = true;
4705 break;
4706 }
4707 }
4708 if has_local_declaration {
4709 return local_declaration_visible;
4710 }
4711 }
4712 let guards = OnceCell::new();
4713 self.physical_declaration_visible_at(
4714 analyzer,
4715 file,
4716 declaration,
4717 reference.start_byte(),
4718 &guards,
4719 )
4720 }
4721
4722 pub fn declaration_visible_for_c_forward_call(
4728 &self,
4729 analyzer: &CppGraphSource<'_>,
4730 file: &ProjectFile,
4731 declaration: &CodeUnit,
4732 reference_byte: usize,
4733 ) -> bool {
4734 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4735 return true;
4736 }
4737 if declaration.source() != file {
4738 return false;
4739 }
4740 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4741 return false;
4742 };
4743 let reference_guards = prepared
4744 .tree()
4745 .root_node()
4746 .descendant_for_byte_range(reference_byte, reference_byte)
4747 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4748 declaration_guard_requirements(analyzer, self.cpp, declaration)
4749 .into_iter()
4750 .any(|(_, required)| {
4751 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4752 })
4753 }
4754
4755 pub fn callable_arity_at_reference(
4756 &self,
4757 analyzer: &CppGraphSource<'_>,
4758 file: &ProjectFile,
4759 candidate: &CodeUnit,
4760 reference_byte: usize,
4761 ) -> Option<CallableArity> {
4762 let key = (file.clone(), logical_symbol_key(candidate));
4763 let cell = self
4764 .callable_reference_specs
4765 .lock()
4766 .expect("C++ callable reference-spec cache poisoned")
4767 .entry(key)
4768 .or_default()
4769 .clone();
4770 let spec = cell.get_or_init(|| {
4771 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4772 let spec = TargetSpec::from_target(analyzer, candidate)?;
4773 let spec = spec
4774 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4775 .into_owned();
4776 #[cfg(any(test, feature = "test-support"))]
4777 self.callable_reference_spec_build_count
4778 .fetch_add(1, Ordering::Relaxed);
4779 Some(spec)
4780 });
4781 spec.as_ref()?.callable_arity_at(reference_byte)
4782 }
4783
4784 fn physical_declaration_visible_at(
4785 &self,
4786 analyzer: &CppGraphSource<'_>,
4787 file: &ProjectFile,
4788 declaration: &CodeUnit,
4789 reference_byte: usize,
4790 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4791 ) -> bool {
4792 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4793 return false;
4794 };
4795 let reference = CallableReferenceContext {
4796 file,
4797 position: Some(CallableReferencePosition {
4798 prepared: prepared.as_ref(),
4799 byte: reference_byte,
4800 guards: reference_guards,
4801 }),
4802 };
4803 if declaration.source() == file {
4804 return callable_declaration_activation_in_file(analyzer, declaration, &reference)
4805 .or_else(|| {
4806 self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
4807 })
4808 .is_some_and(|activation| activation < reference_byte);
4809 }
4810 if self
4811 .foreign_callable_declaration_activation(analyzer, declaration, &reference)
4812 .or_else(|| self.exhaustive_guard_family_activation(analyzer, declaration, &reference))
4813 .is_none()
4814 {
4815 return false;
4816 }
4817 declaration_guard_requirements(analyzer, self.cpp, declaration)
4818 .into_iter()
4819 .any(|(_, declaration_guards)| {
4820 self.foreign_declaration_reachable_at_reference(
4821 file,
4822 prepared.as_ref(),
4823 declaration.source(),
4824 &declaration_guards,
4825 reference.guards(),
4826 reference_byte,
4827 )
4828 })
4829 }
4830
4831 fn foreign_callable_declaration_activation(
4846 &self,
4847 analyzer: &CppGraphSource<'_>,
4848 declaration: &CodeUnit,
4849 reference: &CallableReferenceContext<'_>,
4850 ) -> Option<usize> {
4851 let build_decides = !self.compile_context_is_absent(reference.file);
4852 let proven = self.compile_proven_guards(reference.file);
4853 let augmented;
4854 let active = match reference.guards() {
4855 Some(active) if !proven.is_empty() => {
4856 augmented = active.union(&proven).cloned().collect();
4857 Some(&augmented)
4858 }
4859 other => other,
4860 };
4861 analyzer
4862 .declaration_source_occurrences(declaration)?
4863 .into_iter()
4864 .filter_map(|occurrence| {
4865 let activation = occurrence.fact.callable_activation?;
4866 let mut required =
4867 cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
4868 if required.iter().any(|guard| match guard {
4869 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
4870 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
4871 !reference.is_c()
4872 }
4873 _ => false,
4874 }) {
4875 return None;
4876 }
4877 required.retain(|guard| {
4878 !matches!(guard,
4879 PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
4880 if name == "__cplusplus")
4881 });
4882 let visible = required.is_empty()
4883 || if build_decides {
4884 guard_requirements_hold_at_reference(&required, active)
4885 } else {
4886 guards_compatible_at_reference(&required, reference.guards())
4887 };
4888 visible.then_some(activation)
4889 })
4890 .min()
4891 }
4892
4893 pub fn external_type_candidate_visible_at(
4894 &self,
4895 file: &ProjectFile,
4896 candidate: &CodeUnit,
4897 reference_byte: usize,
4898 ) -> bool {
4899 if candidate.source() == file {
4900 return true;
4901 }
4902 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4903 return false;
4904 };
4905 self.visible_identifier_candidates(file, candidate.identifier())
4906 .filter(|peer| same_logical_symbol(candidate, peer))
4907 .any(|peer| {
4908 peer.source() == file
4909 || self
4910 .include_activation_for_source(
4911 self.cpp,
4912 file,
4913 prepared.as_ref(),
4914 peer.source(),
4915 )
4916 .is_some_and(|activation| activation <= reference_byte)
4917 })
4918 }
4919
4920 pub fn external_type_declaration_visible_at(
4921 &self,
4922 file: &ProjectFile,
4923 candidate: &CodeUnit,
4924 reference_byte: usize,
4925 ) -> bool {
4926 if candidate.source() == file {
4927 return true;
4928 }
4929 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4930 return false;
4931 };
4932 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
4933 .is_some_and(|activation| activation <= reference_byte)
4934 }
4935
4936 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
4955 if let Some(cached) = self
4956 .compile_proven_guard_cells
4957 .lock()
4958 .expect("C++ compile-proven guard cache poisoned")
4959 .get(file)
4960 {
4961 return Arc::clone(cached);
4962 }
4963 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
4964 Some(names) => names,
4965 None => {
4966 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
4967 let seed = translation_units.next().and_then(|translation_unit| {
4968 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
4969 });
4970 match seed {
4971 None => HashSet::default(),
4972 Some(mut names) => {
4973 for translation_unit in translation_units {
4974 let Some(reached) = context_fact_names(
4975 self.cpp.compile_contexts_for(&translation_unit),
4976 ) else {
4977 names.clear();
4978 break;
4979 };
4980 names.retain(|name| reached.contains(name));
4981 if names.is_empty() {
4982 break;
4983 }
4984 }
4985 names
4986 }
4987 }
4988 }
4989 };
4990 let proven = Arc::new(
4991 names
4992 .into_iter()
4993 .map(PreprocessorGuard::Defined)
4994 .collect::<HashSet<_>>(),
4995 );
4996 self.compile_proven_guard_cells
4997 .lock()
4998 .expect("C++ compile-proven guard cache poisoned")
4999 .insert(file.clone(), Arc::clone(&proven));
5000 proven
5001 }
5002
5003 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5013 if let Some(cached) = self
5014 .include_path_admission_cells
5015 .lock()
5016 .expect("C++ include-path admission cache poisoned")
5017 .get(file)
5018 .copied()
5019 {
5020 return cached;
5021 }
5022 let admission = if self.compile_context_is_absent(file) {
5023 IncludePathAdmission::Compatible
5024 } else {
5025 IncludePathAdmission::Proven
5026 };
5027 self.include_path_admission_cells
5028 .lock()
5029 .expect("C++ include-path admission cache poisoned")
5030 .insert(file.clone(), admission);
5031 admission
5032 }
5033
5034 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5041 if !self.cpp.compile_contexts_for(file).is_empty() {
5042 return false;
5043 }
5044 let translation_units = self.cpp.reaching_translation_units(file);
5045 translation_units.is_empty()
5046 || translation_units
5047 .iter()
5048 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5049 }
5050
5051 pub fn miss_requires_compile_context(
5063 &self,
5064 file: &ProjectFile,
5065 identifier: &str,
5066 reference: Node<'_>,
5067 ) -> bool {
5068 if !self.compile_context_is_absent(file) {
5069 return false;
5070 }
5071 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5072 return false;
5073 };
5074 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5075 let reference_byte = reference.start_byte();
5076 let mut sources = self
5077 .visible_identifier_candidates(file, identifier)
5078 .map(CodeUnit::source)
5079 .filter(|source| *source != file)
5080 .collect::<Vec<_>>();
5081 sources.sort();
5082 sources.dedup();
5083 sources.into_iter().any(|declaration_source| {
5084 self.conditional_include_projections_for_source(
5085 file,
5086 prepared.as_ref(),
5087 declaration_source,
5088 )
5089 .iter()
5090 .any(|projection| {
5091 projection.activation_byte <= reference_byte
5092 && !guard_requirements_hold_at_reference(
5093 &projection.required_guards,
5094 reference_guards.as_ref(),
5095 )
5096 && guards_compatible_at_reference(
5097 &projection.required_guards,
5098 reference_guards.as_ref(),
5099 )
5100 })
5101 })
5102 }
5103
5104 fn foreign_declaration_reachable_at_reference(
5116 &self,
5117 file: &ProjectFile,
5118 prepared: &PreparedSyntaxTree,
5119 declaration_source: &ProjectFile,
5120 declaration_guards: &HashSet<PreprocessorGuard>,
5121 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5122 reference_byte: usize,
5123 ) -> bool {
5124 let proven = self.compile_proven_guards(file);
5130 let augmented;
5131 let reference_guards = match reference_guards {
5132 Some(active) if !proven.is_empty() => {
5133 augmented = active.union(&proven).cloned().collect();
5134 Some(&augmented)
5135 }
5136 other => other,
5137 };
5138 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5139 eprintln!(
5140 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5141 declaration_source.rel_path().display(),
5142 );
5143 }
5144 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5145 return false;
5146 }
5147 if self
5148 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5149 .is_some_and(|activation| activation <= reference_byte)
5150 {
5151 return true;
5152 }
5153 let projections =
5154 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5155 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5156 eprintln!(
5157 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5158 declaration_source.rel_path().display(),
5159 declaration_guards.len(),
5160 proven.len(),
5161 projections.len(),
5162 );
5163 }
5164 let admission = self.include_path_admission(file);
5165 projections.iter().any(|projection| {
5166 projection.activation_byte <= reference_byte
5167 && admission.admits(
5168 &projection.required_guards,
5169 &projection.partial_guards,
5170 reference_guards,
5171 )
5172 && self.preprocessor_guards_stable_between(
5173 file,
5174 projection.activation_byte,
5175 reference_byte,
5176 &projection.required_guards,
5177 )
5178 })
5179 }
5180
5181 fn foreign_declaration_may_be_reachable_from_raw_guards(
5182 &self,
5183 file: &ProjectFile,
5184 prepared: &PreparedSyntaxTree,
5185 declaration_source: &ProjectFile,
5186 declaration_guards: &HashSet<PreprocessorGuard>,
5187 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5188 reference_byte: usize,
5189 ) -> bool {
5190 let proven = self.compile_proven_guards(file);
5191 let augmented;
5192 let reference_guards = match reference_guards {
5193 Some(active) if !proven.is_empty() => {
5194 augmented = active.union(&proven).cloned().collect();
5195 Some(&augmented)
5196 }
5197 other => other,
5198 };
5199 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5200 return false;
5201 }
5202 if self
5203 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5204 .is_some_and(|activation| activation <= reference_byte)
5205 {
5206 return true;
5207 }
5208 let reachable = find_conditional_include_projection_for_source(
5209 self.cpp,
5210 self.token,
5211 file,
5212 prepared,
5213 declaration_source,
5214 self.include_path_admission(file),
5215 reference_guards,
5216 reference_byte,
5217 &|| {
5218 #[cfg(any(test, feature = "test-support"))]
5219 self.conditional_include_target_state_count
5220 .fetch_add(1, Ordering::Relaxed);
5221 },
5222 );
5223 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5224 eprintln!(
5225 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5226 declaration_source.rel_path().display(),
5227 declaration_guards.len(),
5228 proven.len(),
5229 reference_guards.map_or(0, HashSet::len),
5230 );
5231 }
5232 reachable
5233 }
5234
5235 fn foreign_declaration_reachable_from_compile_proven_guards(
5236 &self,
5237 file: &ProjectFile,
5238 prepared: &PreparedSyntaxTree,
5239 declaration_source: &ProjectFile,
5240 declaration_guards: &HashSet<PreprocessorGuard>,
5241 reference_byte: usize,
5242 ) -> bool {
5243 let proven = self.compile_proven_guards(file);
5244 if proven.is_empty()
5245 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5246 {
5247 return false;
5248 }
5249 let projections =
5250 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5251 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5252 eprintln!(
5253 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5254 declaration_source.rel_path().display(),
5255 declaration_guards.len(),
5256 proven.len(),
5257 projections.len(),
5258 );
5259 }
5260 projections.iter().any(|projection| {
5261 projection.activation_byte <= reference_byte
5262 && guard_requirements_hold_at_reference(
5263 &projection.required_guards,
5264 Some(proven.as_ref()),
5265 )
5266 && self.preprocessor_guards_stable_between(
5271 file,
5272 0,
5273 projection.activation_byte,
5274 &projection.required_guards,
5275 )
5276 })
5277 }
5278
5279 pub fn external_type_candidate_visible_in_context(
5280 &self,
5281 analyzer: &CppGraphSource<'_>,
5282 file: &ProjectFile,
5283 candidate: &CodeUnit,
5284 reference: Node<'_>,
5285 ) -> bool {
5286 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5287 if report_stats {
5288 eprintln!(
5289 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5290 candidate.fq_name(),
5291 candidate.source().rel_path().display(),
5292 file.rel_path().display(),
5293 reference.start_byte(),
5294 );
5295 }
5296 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5297 return false;
5298 };
5299 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5300 let reference_guards = OnceCell::new();
5301 let reference_guards_at_site = || {
5302 reference_guards.get_or_init(|| {
5303 let started = Instant::now();
5304 if report_stats {
5305 eprintln!(
5306 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5307 file.rel_path().display(),
5308 reference.start_byte(),
5309 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5310 );
5311 }
5312 let macro_environment = self.macro_environment(file, reference.start_byte());
5313 let filtered = raw_reference_guards
5314 .clone()
5315 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5316 if report_stats {
5317 eprintln!(
5318 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5319 filtered.is_some(),
5320 started.elapsed().as_millis(),
5321 );
5322 }
5323 filtered
5324 })
5325 };
5326
5327 let peers = self
5328 .visible_identifier_candidates(file, candidate.identifier())
5329 .filter(|peer| same_logical_symbol(candidate, peer))
5330 .collect::<Vec<_>>();
5331 if report_stats {
5332 let peer_sources = peers
5333 .iter()
5334 .map(|peer| peer.source().rel_path().display().to_string())
5335 .collect::<Vec<_>>();
5336 eprintln!(
5337 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5338 candidate.fq_name(),
5339 );
5340 }
5341 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5342 declaration_guard_requirements(analyzer, self.cpp, peer)
5343 .into_iter()
5344 .any(|(declaration_byte, declaration_guards)| {
5345 if peer.source() == file {
5346 let visible = declaration_byte < reference.start_byte()
5347 && declaration_guards.is_empty();
5348 if report_stats {
5349 eprintln!(
5350 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5351 peer.source().rel_path().display(),
5352 declaration_guards.len(),
5353 );
5354 }
5355 return visible;
5356 }
5357 let direct = declaration_guards.is_empty()
5358 && self
5359 .include_activation_for_source(
5360 self.cpp,
5361 file,
5362 prepared.as_ref(),
5363 peer.source(),
5364 )
5365 .is_some_and(|activation| activation <= reference.start_byte());
5366 let compile_proven = !direct
5367 && self.foreign_declaration_reachable_from_compile_proven_guards(
5368 file,
5369 prepared.as_ref(),
5370 peer.source(),
5371 &declaration_guards,
5372 reference.start_byte(),
5373 );
5374 if report_stats {
5375 eprintln!(
5376 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5377 peer.source().rel_path().display(),
5378 declaration_guards.len(),
5379 );
5380 }
5381 direct || compile_proven
5382 })
5383 });
5384 if directly_visible_without_reference_environment {
5385 if report_stats {
5386 eprintln!(
5387 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5388 candidate.fq_name(),
5389 );
5390 }
5391 return true;
5392 }
5393 let directly_visible = peers.iter().any(|peer| {
5394 declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5395 .into_iter()
5396 .any(|site| {
5397 let declaration_byte = site.byte;
5398 let declaration_guards = site.guards;
5399 if peer.source() == file {
5400 if declaration_byte >= reference.start_byte() {
5401 return false;
5402 }
5403 let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5408 guard_requirements_hold_at_reference(&declaration_guards, guards)
5409 || (site.completed_family_before_reference
5410 && guards_compatible_at_reference(&declaration_guards, guards))
5411 };
5412 if !active(raw_reference_guards.as_ref()) {
5413 return false;
5414 }
5415 return active(reference_guards_at_site().as_ref())
5416 && self.preprocessor_guards_stable_between(
5417 file,
5418 declaration_byte,
5419 reference.start_byte(),
5420 &declaration_guards,
5421 );
5422 }
5423 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5424 file,
5425 prepared.as_ref(),
5426 peer.source(),
5427 &declaration_guards,
5428 raw_reference_guards.as_ref(),
5429 reference.start_byte(),
5430 );
5431 if report_stats {
5432 eprintln!(
5433 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5434 peer.source().rel_path().display(),
5435 declaration_guards.len(),
5436 );
5437 }
5438 if !raw_feasible {
5439 return false;
5440 }
5441 self.foreign_declaration_reachable_at_reference(
5442 file,
5443 prepared.as_ref(),
5444 peer.source(),
5445 &declaration_guards,
5446 reference_guards_at_site().as_ref(),
5447 reference.start_byte(),
5448 )
5449 })
5450 });
5451 if directly_visible {
5452 if report_stats {
5453 eprintln!(
5454 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5455 candidate.fq_name(),
5456 );
5457 }
5458 return true;
5459 }
5460 let complementary = self
5461 .visible_identifier_candidates(file, candidate.identifier())
5462 .filter(|peer| {
5463 peer.kind() == candidate.kind()
5464 && peer.fq_name() == candidate.fq_name()
5465 && peer.source() == candidate.source()
5466 })
5467 .collect::<Vec<_>>();
5468 let complementary_family =
5473 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5474 let raw_candidate_branch_compatible = complementary_family
5475 && raw_reference_guards.as_ref().is_some_and(|active| {
5476 declaration_guard_requirements(analyzer, self.cpp, candidate)
5477 .iter()
5478 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5479 });
5480 if report_stats {
5481 eprintln!(
5482 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5483 candidate.fq_name(),
5484 complementary.len(),
5485 complementary_family,
5486 raw_candidate_branch_compatible,
5487 );
5488 }
5489 let candidate_branch_compatible = raw_candidate_branch_compatible
5490 && reference_guards_at_site().as_ref().is_some_and(|active| {
5491 declaration_guard_requirements(analyzer, self.cpp, candidate)
5492 .iter()
5493 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5494 });
5495 let complementary_visible = candidate_branch_compatible
5496 && if candidate.source() == file {
5497 declaration_guard_requirements(analyzer, self.cpp, candidate)
5498 .iter()
5499 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5500 } else {
5501 self.include_activation_for_source(
5502 self.cpp,
5503 file,
5504 prepared.as_ref(),
5505 candidate.source(),
5506 )
5507 .is_some_and(|activation| activation <= reference.start_byte())
5508 };
5509 if report_stats {
5510 eprintln!(
5511 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5512 if complementary_visible {
5513 "complementary"
5514 } else {
5515 "missing"
5516 },
5517 candidate.fq_name(),
5518 );
5519 }
5520 complementary_visible
5521 }
5522
5523 pub fn is_exhaustive_same_fqn_type_declaration_family(
5524 &self,
5525 analyzer: &CppGraphSource<'_>,
5526 file: &ProjectFile,
5527 candidate: &CodeUnit,
5528 ) -> bool {
5529 let candidates = self
5530 .visible_identifier_candidates(file, candidate.identifier())
5531 .filter(|peer| {
5532 peer.kind() == candidate.kind()
5533 && peer.fq_name() == candidate.fq_name()
5534 && peer.source() == candidate.source()
5535 })
5536 .collect::<Vec<_>>();
5537 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5538 }
5539
5540 pub fn dependent_member_pointer_alias_visible_in_context(
5555 &self,
5556 analyzer: &CppGraphSource<'_>,
5557 file: &ProjectFile,
5558 candidate: &CodeUnit,
5559 owner_components: &[String],
5560 reference: Node<'_>,
5561 ) -> bool {
5562 if !analyzer
5563 .type_alias_provider()
5564 .is_some_and(|provider| provider.is_type_alias(candidate))
5565 {
5566 return false;
5567 }
5568 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5569 return false;
5570 };
5571 if terminal != candidate.identifier()
5572 || canonical_cpp_scope_components(candidate) != owner_components
5573 {
5574 return false;
5575 }
5576 let Some(expected_parent_fq_name) =
5577 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5578 else {
5579 return false;
5580 };
5581 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5582 return false;
5583 };
5584 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5585 || parent_anchor.source() != candidate.source()
5586 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5587 {
5588 return false;
5589 }
5590
5591 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5597 || candidate.source() == file
5598 && !analyzer
5599 .ranges(candidate)
5600 .iter()
5601 .any(|range| range.start_byte < reference.start_byte())
5602 {
5603 return false;
5604 }
5605
5606 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5607 if candidate_guards.is_empty() {
5608 return false;
5609 }
5610 let same_guard_sets =
5611 |left: &[(usize, HashSet<PreprocessorGuard>)],
5612 right: &[(usize, HashSet<PreprocessorGuard>)]| {
5613 left.iter().all(|(_, left_guards)| {
5614 right
5615 .iter()
5616 .any(|(_, right_guards)| left_guards == right_guards)
5617 })
5618 };
5619 let parent_candidates = self
5620 .visible_identifier_candidates(file, parent_anchor.identifier())
5621 .filter(|peer| {
5622 peer.kind() == parent_anchor.kind()
5623 && peer.fq_name() == expected_parent_fq_name.as_str()
5624 && peer.source() == parent_anchor.source()
5625 && canonical_cpp_scope_components(peer) == owner_prefix
5626 })
5627 .filter_map(|peer| {
5628 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5629 (candidate_guards.len() == parent_guards.len()
5630 && same_guard_sets(&candidate_guards, &parent_guards)
5631 && same_guard_sets(&parent_guards, &candidate_guards))
5632 .then(|| (peer.clone(), parent_guards))
5633 })
5634 .collect::<Vec<_>>();
5635 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5636 return false;
5637 };
5638
5639 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5640 return false;
5641 };
5642 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5643 else {
5644 return false;
5645 };
5646 if !candidate_guards.iter().any(|(_, target_guards)| {
5651 guards_compatible_at_reference(target_guards, Some(&reference_guards))
5652 && (candidate.source() != file
5653 || self.preprocessor_guards_stable_between(
5654 file,
5655 0,
5656 reference.start_byte(),
5657 target_guards,
5658 ))
5659 }) {
5660 return false;
5661 }
5662
5663 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5664 }
5665
5666 pub fn external_type_candidate_guard_compatible_in_context(
5676 &self,
5677 analyzer: &CppGraphSource<'_>,
5678 file: &ProjectFile,
5679 candidate: &CodeUnit,
5680 reference: Node<'_>,
5681 ) -> bool {
5682 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5683 return false;
5684 };
5685 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5686
5687 self.visible_identifier_candidates(file, candidate.identifier())
5688 .filter(|peer| same_logical_symbol(candidate, peer))
5689 .any(|peer| {
5690 declaration_guard_requirements(analyzer, self.cpp, peer)
5691 .into_iter()
5692 .any(|(declaration_byte, declaration_guards)| {
5693 if peer.source() == file {
5694 let (start, end) = if declaration_byte <= reference.start_byte() {
5695 (declaration_byte, reference.start_byte())
5696 } else {
5697 (reference.start_byte(), declaration_byte)
5698 };
5699 return guard_requirements_hold_at_reference(
5700 &declaration_guards,
5701 reference_guards.as_ref(),
5702 ) && self.preprocessor_guards_stable_between(
5703 file,
5704 start,
5705 end,
5706 &declaration_guards,
5707 );
5708 }
5709 self.foreign_declaration_reachable_at_reference(
5710 file,
5711 prepared.as_ref(),
5712 peer.source(),
5713 &declaration_guards,
5714 reference_guards.as_ref(),
5715 reference.start_byte(),
5716 )
5717 })
5718 })
5719 }
5720
5721 pub fn same_file_callable_guard_compatible_ignoring_order(
5729 &self,
5730 analyzer: &CppGraphSource<'_>,
5731 file: &ProjectFile,
5732 candidate: &CodeUnit,
5733 reference: Node<'_>,
5734 ) -> bool {
5735 if candidate.source() != file || !candidate.is_callable() {
5736 return false;
5737 }
5738 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5739 return false;
5740 };
5741 let guards = OnceCell::new();
5742 let context = CallableReferenceContext {
5743 file,
5744 position: Some(CallableReferencePosition {
5745 prepared: prepared.as_ref(),
5746 byte: reference.start_byte(),
5747 guards: &guards,
5748 }),
5749 };
5750 analyzer
5751 .declaration_source_occurrences(candidate)
5752 .into_iter()
5753 .flatten()
5754 .filter_map(|occurrence| {
5755 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5756 Some(callable_guard_requirements_are_visible(
5757 &guards,
5758 occurrence.fact.callable_guard_completion_byte,
5759 &context,
5760 ))
5761 })
5762 .any(|visible| visible)
5763 }
5764
5765 pub fn type_candidate_may_be_visible_before_reference(
5766 &self,
5767 analyzer: &CppGraphSource<'_>,
5768 file: &ProjectFile,
5769 candidate: &CodeUnit,
5770 reference_byte: usize,
5771 ) -> bool {
5772 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5773 return false;
5774 };
5775 let root = prepared.tree().root_node();
5776 let end_byte = reference_byte
5777 .saturating_add(1)
5778 .min(prepared.source().len());
5779 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5780 return false;
5781 };
5782 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5783 }
5784
5785 pub fn preprocessor_guards_stable_between(
5786 &self,
5787 file: &ProjectFile,
5788 start_byte: usize,
5789 end_byte: usize,
5790 guards: &HashSet<PreprocessorGuard>,
5791 ) -> bool {
5792 if guards.is_empty() || start_byte >= end_byte {
5793 return true;
5794 }
5795 let cell = self.macro_event_cell(file);
5796 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5797 let mut visited = HashSet::from_iter([file.clone()]);
5798 !events.iter().any(|event| {
5799 event.byte() >= start_byte
5800 && event.byte() < end_byte
5801 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5802 })
5803 }
5804
5805 fn macro_event_may_mutate_guards(
5806 &self,
5807 event: &MacroEvent,
5808 guards: &HashSet<PreprocessorGuard>,
5809 visited: &mut HashSet<ProjectFile>,
5810 ) -> bool {
5811 match event {
5812 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5813 guards.iter().any(|guard| guard.may_depend_on_macro(name))
5814 }
5815 MacroEvent::Include { targets, .. } => {
5816 targets.is_empty()
5817 || targets
5818 .iter()
5819 .any(|target| self.source_may_mutate_guards(target, guards, visited))
5820 }
5821 MacroEvent::Invalidate { .. } => true,
5822 }
5823 }
5824
5825 fn source_may_mutate_guards(
5826 &self,
5827 file: &ProjectFile,
5828 guards: &HashSet<PreprocessorGuard>,
5829 visited: &mut HashSet<ProjectFile>,
5830 ) -> bool {
5831 if !visited.insert(file.clone()) {
5832 return false;
5833 }
5834 let cell = self.macro_event_cell(file);
5835 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5836 events
5837 .iter()
5838 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5839 }
5840
5841 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5842 let normalized = normalize_reference_name(raw_name)?;
5843 self.type_candidates(file, &normalized)
5844 .into_iter()
5845 .next()
5846 .cloned()
5847 }
5848
5849 pub fn unique_visible_parameter_type_fallback(
5858 &self,
5859 analyzer: &CppGraphSource<'_>,
5860 file: &ProjectFile,
5861 node: Node<'_>,
5862 source: &str,
5863 ) -> Option<CodeUnit> {
5864 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5865 return None;
5866 }
5867 let name = node_text(node, source);
5868 let candidates = self
5869 .visible_identifier_candidates(file, name)
5870 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5871 .filter(|candidate| {
5872 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5873 })
5874 .collect::<Vec<_>>();
5875 self.unique_canonical_type_candidate(analyzer, file, &candidates)
5876 }
5877
5878 pub fn resolve_type_node_result(
5879 &self,
5880 file: &ProjectFile,
5881 node: Node<'_>,
5882 source: &str,
5883 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
5884 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
5885 return Ok(None);
5886 };
5887 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
5888 return Ok(Some(primary));
5889 };
5890 self.resolve_template_arguments(file, primary, &arguments)
5891 .map(Some)
5892 }
5893
5894 pub fn resolve_type_node_primary(
5895 &self,
5896 file: &ProjectFile,
5897 node: Node<'_>,
5898 source: &str,
5899 ) -> Option<CodeUnit> {
5900 let components = cpp_type_name_components(node, source)?;
5901 self.resolve_type(file, &components.join("::"))
5902 }
5903
5904 pub fn resolve_template_arguments(
5905 &self,
5906 file: &ProjectFile,
5907 primary: CodeUnit,
5908 arguments: &[CppTemplateExpression],
5909 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5910 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
5911 }
5912
5913 fn resolve_template_arguments_inner(
5914 &self,
5915 file: &ProjectFile,
5916 primary: CodeUnit,
5917 arguments: &[CppTemplateExpression],
5918 seen_aliases: &mut HashSet<CodeUnit>,
5919 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5920 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
5921 && let Some(alias_target) = &metadata.alias_target
5922 {
5923 if !seen_aliases.insert(primary.clone()) {
5924 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
5925 }
5926 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
5927 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
5928 let target_name = alias_target.components.join("::");
5929 let target_primary = if alias_target.global {
5930 unique_logical_type_candidate(self.type_candidates(file, &target_name))
5931 } else {
5932 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
5933 };
5934 let Some(target_primary) = target_primary else {
5935 return Ok(primary);
5939 };
5940 let Some(target_arguments) = &alias_target.arguments else {
5941 return Ok(target_primary);
5942 };
5943 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
5944 .ok_or(CppTemplateResolutionError::Substitution)?;
5945 return self.resolve_template_arguments_inner(
5946 file,
5947 target_primary,
5948 &target_arguments,
5949 seen_aliases,
5950 );
5951 }
5952
5953 let primary_fq_name = self
5954 .cpp_template_metadata
5955 .get(&primary)
5956 .map(|metadata| metadata.primary_fq_name.clone())
5957 .unwrap_or_else(|| primary.fq_name());
5958 let has_specialization_metadata = self
5959 .cpp_template_families
5960 .get(&primary_fq_name)
5961 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
5962 if !has_specialization_metadata {
5963 return Ok(primary);
5964 }
5965 self.select_template_specialization(file, &primary, arguments)
5966 }
5967
5968 fn select_template_specialization(
5969 &self,
5970 file: &ProjectFile,
5971 resolved: &CodeUnit,
5972 explicit_arguments: &[CppTemplateExpression],
5973 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5974 let primary_fq_name = self
5975 .cpp_template_metadata
5976 .get(resolved)
5977 .map(|metadata| metadata.primary_fq_name.clone())
5978 .unwrap_or_else(|| resolved.fq_name());
5979 let family = self
5980 .cpp_template_families
5981 .get(&primary_fq_name)
5982 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
5983 let primary_candidates = family
5984 .iter()
5985 .filter_map(|unit| {
5986 let metadata = self.cpp_template_metadata.get(unit)?;
5987 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
5988 })
5989 .collect::<Vec<_>>();
5990 let primary_unit = primary_candidates
5991 .iter()
5992 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
5993 .or_else(|| {
5994 primary_candidates
5995 .iter()
5996 .map(|(unit, _)| *unit)
5997 .min_by_key(|unit| {
5998 (
5999 unit.source().to_string(),
6000 unit.signature().unwrap_or_default(),
6001 )
6002 })
6003 })
6004 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6005 let primary_parameters =
6006 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6007 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6008 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6009 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6010
6011 let mut applicable = Vec::new();
6012 for unit in family {
6013 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6014 continue;
6015 };
6016 if metadata.is_primary() || !self.is_visible(file, unit) {
6017 continue;
6018 }
6019 if !cpp_specialization_matches(metadata, &expanded) {
6020 continue;
6021 }
6022 applicable.push((unit, metadata));
6023 }
6024 if applicable.is_empty() {
6025 return Ok(primary_unit.clone());
6026 }
6027
6028 let winners = applicable
6033 .iter()
6034 .filter(|(candidate, candidate_metadata)| {
6035 applicable.iter().all(|(other, other_metadata)| {
6036 same_visible_symbol(candidate, other)
6037 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6038 })
6039 })
6040 .copied()
6041 .collect::<Vec<_>>();
6042 let Some((selected, _)) = winners.first() else {
6043 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6046 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6047 });
6048 };
6049 if winners
6050 .iter()
6051 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6052 {
6053 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6054 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6055 });
6056 }
6057 Ok((*selected).clone())
6058 }
6059
6060 pub fn resolve_type_components_lexically(
6061 &self,
6062 analyzer: &CppGraphSource<'_>,
6063 file: &ProjectFile,
6064 components: &[String],
6065 global: bool,
6066 lexical_scope: &[String],
6067 ) -> LexicalTypeResolution {
6068 self.resolve_type_components_lexically_inner(
6069 analyzer,
6070 file,
6071 components,
6072 global,
6073 lexical_scope,
6074 TypeCandidateResolution::Canonical,
6075 )
6076 }
6077
6078 pub fn resolve_type_components_lexically_for_forward(
6079 &self,
6080 analyzer: &CppGraphSource<'_>,
6081 file: &ProjectFile,
6082 components: &[String],
6083 global: bool,
6084 lexical_scope: &[String],
6085 ) -> LexicalTypeResolution {
6086 self.resolve_type_components_lexically_inner(
6087 analyzer,
6088 file,
6089 components,
6090 global,
6091 lexical_scope,
6092 TypeCandidateResolution::PreserveAlias,
6093 )
6094 }
6095
6096 pub fn resolve_type_components_lexically_for_target(
6097 &self,
6098 analyzer: &CppGraphSource<'_>,
6099 file: &ProjectFile,
6100 components: &[String],
6101 global: bool,
6102 lexical_scope: &[String],
6103 target: &CodeUnit,
6104 ) -> LexicalTypeResolution {
6105 #[cfg(any(test, feature = "test-support"))]
6106 self.target_preserving_type_resolution_count
6107 .fetch_add(1, Ordering::Relaxed);
6108 self.resolve_type_components_lexically_inner(
6109 analyzer,
6110 file,
6111 components,
6112 global,
6113 lexical_scope,
6114 TypeCandidateResolution::PreserveTarget(target),
6115 )
6116 }
6117
6118 pub fn coarse_unqualified_type_reference_may_resolve(
6119 &self,
6120 file: &ProjectFile,
6121 name: &str,
6122 ) -> bool {
6123 if name.is_empty() {
6124 return true;
6125 }
6126 self.visible_identifier_candidates(file, name)
6127 .any(|candidate| {
6128 candidate.kind() == CodeUnitType::Class
6129 || declared_type_alias(&self.cpp_source(), candidate)
6130 })
6131 || self.visible_parser_alias_name_is_visible(file, name)
6132 }
6133
6134 #[allow(clippy::too_many_arguments)]
6135 pub fn structured_type_reference_may_resolve_to_target(
6136 &self,
6137 analyzer: &CppGraphSource<'_>,
6138 file: &ProjectFile,
6139 components: &[String],
6140 global: bool,
6141 lexical_scope: &[String],
6142 target: &CodeUnit,
6143 ) -> bool {
6144 if components.is_empty() {
6145 return true;
6146 }
6147 let Some(terminal) = components.last() else {
6148 return true;
6149 };
6150 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6151 .map(|qualified| qualified.join("::"))
6152 .collect::<Vec<_>>();
6153 let target_name = cpp_name_for(target);
6154 if qualified_tiers
6155 .iter()
6156 .any(|qualified| qualified == &target_name)
6157 {
6158 return true;
6159 }
6160
6161 let mut saw_shape_candidate = false;
6162 for candidate in self.visible_identifier_candidates(file, terminal) {
6163 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6164 {
6165 continue;
6166 }
6167 let candidate_name = cpp_name_for(candidate);
6168 let shape_matches = if global || components.len() > 1 {
6169 qualified_tiers
6170 .iter()
6171 .any(|qualified| qualified == &candidate_name)
6172 } else {
6173 true
6174 };
6175 if !shape_matches {
6176 continue;
6177 }
6178 saw_shape_candidate = true;
6179 if same_visible_symbol(candidate, target)
6180 || self.c_tag_declaration_family_matches_target(
6181 analyzer,
6182 file,
6183 std::slice::from_ref(&candidate),
6184 target,
6185 )
6186 || self.compatible_primary_template_redeclarations(candidate, target)
6187 || (declared_type_alias(analyzer, candidate)
6188 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6189 {
6190 return true;
6191 }
6192 }
6193
6194 !saw_shape_candidate
6195 }
6196
6197 fn cached_c_tag_kind(
6204 &self,
6205 analyzer: &CppGraphSource<'_>,
6206 candidate: &CodeUnit,
6207 ) -> Option<CppCTagKind> {
6208 if let Some(kind) = self
6209 .c_tag_kind_cache
6210 .lock()
6211 .expect("C tag kind cache poisoned")
6212 .get(candidate)
6213 {
6214 return *kind;
6215 }
6216 let kind = indexed_c_tag_kind(analyzer, candidate);
6217 self.c_tag_kind_cache
6218 .lock()
6219 .expect("C tag kind cache poisoned")
6220 .insert(candidate.clone(), kind);
6221 kind
6222 }
6223
6224 fn cached_unique_c_tag_complete_definition(
6225 &self,
6226 analyzer: &CppGraphSource<'_>,
6227 target: &CodeUnit,
6228 target_tag: CppCTagKind,
6229 ) -> Option<CodeUnit> {
6230 if let Some(definition) = self
6231 .c_tag_complete_definition_cache
6232 .lock()
6233 .expect("C tag complete-definition cache poisoned")
6234 .get(target)
6235 {
6236 return definition.clone();
6237 }
6238 let complete_definitions = analyzer
6239 .definitions(&target.fq_name())
6240 .filter(|candidate| {
6241 candidate.is_class()
6242 && !declared_type_alias(analyzer, candidate)
6243 && is_c_source_file(candidate.source())
6244 && analyzer.parent_of(candidate).is_none()
6245 && cpp_class_declaration_strength(analyzer, candidate)
6246 == CppClassDeclarationStrength::Full
6247 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6248 })
6249 .collect::<HashSet<_>>();
6250 let definition = (complete_definitions.len() == 1)
6251 .then(|| complete_definitions.into_iter().next())
6252 .flatten()
6253 .filter(|candidate| same_visible_symbol(candidate, target));
6254 self.c_tag_complete_definition_cache
6255 .lock()
6256 .expect("C tag complete-definition cache poisoned")
6257 .insert(target.clone(), definition.clone());
6258 definition
6259 }
6260
6261 pub fn c_tag_declaration_family_matches_target(
6262 &self,
6263 analyzer: &CppGraphSource<'_>,
6264 visible_from: &ProjectFile,
6265 candidates: &[&CodeUnit],
6266 target: &CodeUnit,
6267 ) -> bool {
6268 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6269 let candidate_evidence = candidates
6270 .iter()
6271 .map(|candidate| {
6272 (
6273 candidate.fq_name(),
6274 candidate.source().rel_path().to_path_buf(),
6275 cpp_class_declaration_strength(analyzer, candidate),
6276 indexed_c_tag_kind(analyzer, candidate),
6277 self.is_physically_visible(visible_from, candidate),
6278 )
6279 })
6280 .collect::<Vec<_>>();
6281 eprintln!(
6282 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6283 visible_from.rel_path().display(),
6284 target.fq_name(),
6285 target.source().rel_path().display(),
6286 cpp_class_declaration_strength(analyzer, target),
6287 indexed_c_tag_kind(analyzer, target),
6288 );
6289 }
6290 if candidates.is_empty()
6291 || !target.is_class()
6292 || declared_type_alias(analyzer, target)
6293 || !is_c_source_file(target.source())
6294 || analyzer.parent_of(target).is_some()
6295 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6296 {
6297 return false;
6298 }
6299 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6300 return false;
6301 };
6302 if self
6303 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6304 .is_none()
6305 {
6306 return false;
6307 }
6308 let mut saw_visible_forward = false;
6309 for candidate in candidates.iter().copied() {
6310 if candidate == target {
6311 continue;
6312 }
6313 if !candidate.is_class()
6314 || declared_type_alias(analyzer, candidate)
6315 || candidate.fq_name() != target.fq_name()
6316 || analyzer.parent_of(candidate).is_some()
6317 || cpp_class_declaration_strength(analyzer, candidate)
6318 != CppClassDeclarationStrength::Forward
6319 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6320 || !self.is_physically_visible(visible_from, candidate)
6321 {
6322 return false;
6323 }
6324 saw_visible_forward = true;
6325 }
6326 saw_visible_forward
6327 }
6328
6329 fn unique_c_tag_declaration_family(
6334 &self,
6335 analyzer: &CppGraphSource<'_>,
6336 visible_from: &ProjectFile,
6337 candidates: &[&CodeUnit],
6338 ) -> Option<CodeUnit> {
6339 let first = candidates.first()?;
6340 let target_fq_name = first.fq_name();
6341 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6342 let mut full = None;
6343 let mut saw_forward = false;
6344 for candidate in candidates.iter().copied() {
6345 if !candidate.is_class()
6346 || declared_type_alias(analyzer, candidate)
6347 || candidate.fq_name() != target_fq_name
6348 || analyzer.parent_of(candidate).is_some()
6349 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6350 {
6351 return None;
6352 }
6353 match cpp_class_declaration_strength(analyzer, candidate) {
6354 CppClassDeclarationStrength::Full
6355 if is_c_source_file(candidate.source())
6356 && full.replace(candidate.clone()).is_none() => {}
6357 CppClassDeclarationStrength::Forward
6358 if self.is_physically_visible(visible_from, candidate) =>
6359 {
6360 saw_forward = true
6361 }
6362 _ => return None,
6363 }
6364 }
6365 if saw_forward { full } else { None }
6366 }
6367
6368 pub fn target_preserving_reference_namespace(
6369 &self,
6370 analyzer: &CppGraphSource<'_>,
6371 file: &ProjectFile,
6372 identifier: &str,
6373 target: &CodeUnit,
6374 ) -> Option<Vec<String>> {
6375 let mut namespace = None;
6376 for candidate in self.visible_identifier_candidates(file, identifier) {
6377 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6378 {
6379 continue;
6380 }
6381 if !(same_visible_symbol(candidate, target)
6382 || self.compatible_primary_template_redeclarations(candidate, target)
6383 || declared_type_alias(analyzer, candidate)
6384 && self.structured_alias_primary_preserves_target(
6385 analyzer, file, candidate, target,
6386 ))
6387 {
6388 continue;
6389 }
6390 if namespace
6391 .as_ref()
6392 .is_some_and(|existing| existing != candidate.package_name())
6393 {
6394 return None;
6395 }
6396 namespace = Some(candidate.package_name().to_string());
6397 }
6398 let namespace = namespace?;
6399 Some(
6400 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6401 brokk_bifrost_core::analyzer::Language::Cpp,
6402 &namespace,
6403 ),
6404 )
6405 }
6406
6407 pub fn resolve_imported_type_candidate(
6408 &self,
6409 analyzer: &CppGraphSource<'_>,
6410 file: &ProjectFile,
6411 target: &CodeUnit,
6412 target_components: &[String],
6413 direct_target: Option<&CodeUnit>,
6414 preserve_alias: bool,
6415 ) -> LexicalTypeResolution {
6416 let candidates = [target];
6417 let resolution = if preserve_alias {
6418 TypeCandidateResolution::PreserveAlias
6419 } else {
6420 direct_target.map_or(
6421 TypeCandidateResolution::Canonical,
6422 TypeCandidateResolution::PreserveTarget,
6423 )
6424 };
6425 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6429 Ok(unit) => LexicalTypeResolution::Resolved {
6430 unit,
6431 components: target_components.to_vec(),
6432 candidates: vec![target.clone()],
6433 },
6434 Err(failure) => failure.lexical_resolution(),
6435 }
6436 }
6437
6438 fn resolve_type_components_lexically_inner(
6439 &self,
6440 analyzer: &CppGraphSource<'_>,
6441 file: &ProjectFile,
6442 components: &[String],
6443 global: bool,
6444 lexical_scope: &[String],
6445 resolution: TypeCandidateResolution<'_>,
6446 ) -> LexicalTypeResolution {
6447 if components.is_empty() {
6448 return LexicalTypeResolution::Missing;
6449 }
6450 let mut injected = self.resolve_injected_class_name(
6460 analyzer,
6461 file,
6462 components,
6463 global,
6464 lexical_scope,
6465 resolution,
6466 );
6467 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6468 let prefix_len = qualified.len().saturating_sub(components.len());
6469 if injected
6470 .as_ref()
6471 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6472 {
6473 return injected
6474 .take()
6475 .expect("injected class resolution was just present")
6476 .1;
6477 }
6478 let qualified_name = qualified.join("::");
6479 let candidates = self
6480 .type_candidates(file, &qualified_name)
6481 .into_iter()
6482 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6483 .collect::<Vec<_>>();
6484 if candidates.is_empty() {
6485 if !global && components.len() == 1 {
6486 match self.resolve_inherited_type_for_lexical_scope(
6487 analyzer,
6488 file,
6489 &qualified[..prefix_len],
6490 &components[0],
6491 resolution,
6492 ) {
6493 LexicalTypeResolution::Missing => {}
6494 inherited => return inherited,
6495 }
6496 }
6497 continue;
6498 }
6499 let candidates =
6500 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6501 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6502 Ok(unit) => unit,
6503 Err(failure) => return failure.lexical_resolution(),
6504 };
6505 return LexicalTypeResolution::Resolved {
6506 unit,
6507 components: qualified,
6508 candidates: candidates.into_iter().cloned().collect(),
6509 };
6510 }
6511 LexicalTypeResolution::Missing
6512 }
6513
6514 fn resolve_injected_class_name(
6515 &self,
6516 analyzer: &CppGraphSource<'_>,
6517 file: &ProjectFile,
6518 components: &[String],
6519 global: bool,
6520 lexical_scope: &[String],
6521 resolution: TypeCandidateResolution<'_>,
6522 ) -> Option<(usize, LexicalTypeResolution)> {
6523 if global
6524 || components.len() != 1
6525 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6526 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6527 {
6528 return None;
6529 }
6530 let name = components.first()?;
6531 let mut matches: Vec<&CodeUnit> = Vec::new();
6532 let mut owner_len = 0;
6533 for candidate in self.visible_identifier_candidates(file, name) {
6534 if !candidate.is_class()
6535 || declared_type_alias(analyzer, candidate)
6536 || candidate.identifier() != name
6537 {
6538 continue;
6539 }
6540 let candidate_scope = canonical_cpp_scope_components(candidate);
6541 if candidate_scope.len() > lexical_scope.len()
6542 || !lexical_scope.starts_with(&candidate_scope)
6543 || candidate_scope.last().is_none_or(|last| last != name)
6544 {
6545 continue;
6546 }
6547 if candidate_scope.len() > owner_len {
6548 owner_len = candidate_scope.len();
6549 matches.clear();
6550 }
6551 if candidate_scope.len() == owner_len
6552 && !matches
6553 .iter()
6554 .any(|existing| same_logical_symbol(existing, candidate))
6555 {
6556 matches.push(candidate);
6557 }
6558 }
6559 if matches.is_empty() {
6560 return None;
6561 }
6562 let owner_components = lexical_scope[..owner_len].to_vec();
6569 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6570 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6571 Ok(unit) => LexicalTypeResolution::Resolved {
6572 unit,
6573 components: owner_components,
6574 candidates: matches.into_iter().cloned().collect(),
6575 },
6576 Err(failure) => failure.lexical_resolution(),
6577 };
6578 Some((owner_len, resolution))
6579 }
6580
6581 fn resolve_inherited_type_for_lexical_scope(
6582 &self,
6583 analyzer: &CppGraphSource<'_>,
6584 file: &ProjectFile,
6585 lexical_scope: &[String],
6586 name: &str,
6587 resolution: TypeCandidateResolution<'_>,
6588 ) -> LexicalTypeResolution {
6589 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6590 return LexicalTypeResolution::Missing;
6591 };
6592 let lexical_owner_name = lexical_scope.join("::");
6593 if lexical_owner_name.is_empty() {
6594 return LexicalTypeResolution::Missing;
6595 }
6596 let owner_candidates = self
6597 .type_candidates(file, &lexical_owner_name)
6598 .into_iter()
6599 .filter(|candidate| {
6600 canonical_cpp_name_matches(candidate, &lexical_owner_name)
6601 && !declared_type_alias(analyzer, candidate)
6602 })
6603 .collect::<Vec<_>>();
6604 if owner_candidates.is_empty() {
6605 return LexicalTypeResolution::Missing;
6606 }
6607 let physical_owner_candidates = owner_candidates
6612 .iter()
6613 .copied()
6614 .filter(|candidate| candidate.source() == file)
6615 .collect::<Vec<_>>();
6616 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6617 owner_candidates
6618 } else {
6619 physical_owner_candidates
6620 };
6621 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6622 return LexicalTypeResolution::Ambiguous;
6623 };
6624
6625 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6626 let mut visited_owners = HashSet::default();
6627 while !frontier.is_empty() {
6628 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6629 let mut next_frontier = Vec::new();
6630 for owner in frontier {
6631 if !visited_owners.insert(owner.fq_name()) {
6632 continue;
6633 }
6634 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6635 let candidates = self
6636 .type_candidates(file, &qualified_name)
6637 .into_iter()
6638 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6639 .collect::<Vec<_>>();
6640 if candidates.is_empty() {
6641 for ancestor in hierarchy.get_direct_ancestors(&owner) {
6642 if !next_frontier
6643 .iter()
6644 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6645 {
6646 next_frontier.push(ancestor);
6647 }
6648 }
6649 continue;
6650 }
6651 let candidates =
6652 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6653 let unit =
6654 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6655 Ok(unit) => unit,
6656 Err(failure) => return failure.lexical_resolution(),
6657 };
6658 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6659 }
6660 if let Some((unit, candidates)) = level_matches.first().cloned() {
6661 let Some(first_declaration) = candidates.first() else {
6662 return LexicalTypeResolution::Ambiguous;
6663 };
6664 if !level_matches.iter().all(|(_, declarations)| {
6665 declarations
6666 .iter()
6667 .all(|declaration| same_logical_symbol(first_declaration, declaration))
6668 }) {
6669 return LexicalTypeResolution::Ambiguous;
6670 }
6671 let mut components = lexical_scope.to_vec();
6672 components.push(name.to_string());
6673 return LexicalTypeResolution::Resolved {
6674 unit,
6675 components,
6676 candidates,
6677 };
6678 }
6679 frontier = next_frontier;
6680 }
6681 LexicalTypeResolution::Missing
6682 }
6683
6684 pub fn inherited_injected_class_owner(
6701 &self,
6702 analyzer: &CppGraphSource<'_>,
6703 file: &ProjectFile,
6704 enclosing_owner: &CodeUnit,
6705 injected_name: &str,
6706 ) -> Option<CodeUnit> {
6707 let hierarchy = analyzer.type_hierarchy_provider()?;
6708 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6709 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6710 while !frontier.is_empty() {
6711 let mut level_matches = Vec::new();
6712 let mut next_frontier = Vec::new();
6713 for raw_owner in frontier {
6714 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6715 else {
6716 if raw_owner.identifier() == injected_name {
6717 return None;
6718 }
6719 continue;
6720 };
6721 let propagated = propagated_counts.entry(owner.clone()).or_default();
6722 if *propagated == 2 {
6723 continue;
6724 }
6725 *propagated += 1;
6726 if owner.identifier() == injected_name {
6727 level_matches.push(owner.clone());
6728 }
6729 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6730 }
6731 match level_matches.as_slice() {
6732 [owner] => return Some(owner.clone()),
6733 [_, ..] => return None,
6734 [] => {}
6735 }
6736 frontier = next_frontier;
6737 }
6738 None
6739 }
6740
6741 fn resolve_type_candidates(
6746 &self,
6747 analyzer: &CppGraphSource<'_>,
6748 file: &ProjectFile,
6749 candidates: &[&CodeUnit],
6750 resolution: TypeCandidateResolution<'_>,
6751 ) -> Result<CodeUnit, TypeCandidateFailure> {
6752 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6753 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6754 {
6755 return Ok(unit);
6756 }
6757 match resolution {
6758 TypeCandidateResolution::Canonical => {
6759 self.canonical_type_candidate_resolution(analyzer, file, candidates)
6760 }
6761 TypeCandidateResolution::PreserveAlias => {
6762 let same_fqn_alias_family = candidates.len() > 1
6769 && candidates.iter().all(|candidate| {
6770 declared_type_alias(analyzer, candidate)
6771 && same_logical_symbol(candidates[0], candidate)
6772 })
6773 && candidates
6774 .iter()
6775 .any(|candidate| candidate.source() != candidates[0].source());
6776 if same_fqn_alias_family {
6777 let physically_visible = candidates
6778 .iter()
6779 .copied()
6780 .filter(|candidate| self.is_physically_visible(file, candidate))
6781 .collect::<Vec<_>>();
6782 let one_structured_target = physically_visible.len() > 1
6792 && physically_visible.iter().skip(1).all(|candidate| {
6793 let target = self.structured_alias_target(analyzer, candidate);
6794 target.is_some()
6795 && target
6796 == self.structured_alias_target(analyzer, physically_visible[0])
6797 });
6798 if physically_visible.len() == 1 || one_structured_target {
6799 return Ok(physically_visible[0].clone());
6800 }
6801 }
6802 unique_type_candidate_preserving_alias(analyzer, file, candidates)
6803 .ok_or(TypeCandidateFailure::Ambiguous)
6804 }
6805 TypeCandidateResolution::PreserveTarget(target) => self
6806 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6807 .ok_or(TypeCandidateFailure::Ambiguous),
6808 }
6809 }
6810
6811 fn candidates_for_type_resolution<'b>(
6812 &self,
6813 analyzer: &CppGraphSource<'_>,
6814 file: &ProjectFile,
6815 candidates: &[&'b CodeUnit],
6816 resolution: TypeCandidateResolution<'_>,
6817 ) -> Vec<&'b CodeUnit> {
6818 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6819 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6820 if compile_proven.len() == 1 {
6821 return compile_proven;
6822 }
6823 }
6824 candidates.to_vec()
6825 }
6826
6827 fn compile_proven_type_candidates<'b>(
6834 &self,
6835 analyzer: &CppGraphSource<'_>,
6836 file: &ProjectFile,
6837 candidates: &[&'b CodeUnit],
6838 ) -> Vec<&'b CodeUnit> {
6839 let proven = self.compile_proven_guards(file);
6840 if proven.is_empty() {
6841 return Vec::new();
6842 }
6843 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6844 return Vec::new();
6845 };
6846 candidates
6847 .iter()
6848 .copied()
6849 .filter(|candidate| {
6850 let declaration_guards =
6851 declaration_guard_requirements(analyzer, self.cpp, candidate);
6852 if declaration_guards.is_empty() {
6853 return false;
6854 }
6855 if candidate.source() == file {
6856 return declaration_guards.iter().any(|(_, required)| {
6857 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6858 });
6859 }
6860 declaration_guards.iter().any(|(_, required)| {
6861 self.foreign_declaration_reachable_from_compile_proven_guards(
6862 file,
6863 prepared.as_ref(),
6864 candidate.source(),
6865 required,
6866 usize::MAX,
6867 )
6868 })
6869 })
6870 .collect()
6871 }
6872
6873 pub fn resolve_callable_value_components_lexically(
6874 &self,
6875 analyzer: &CppGraphSource<'_>,
6876 file: &ProjectFile,
6877 owner_components: &[String],
6878 member_name: &str,
6879 global: bool,
6880 lexical_scope: &[String],
6881 ) -> LexicalCallableValueResolution {
6882 if owner_components.is_empty() || member_name.is_empty() {
6883 return LexicalCallableValueResolution::Missing;
6884 }
6885 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
6886 let owner_name = qualified_owner.join("::");
6887 let type_candidates = self
6888 .type_candidates(file, &owner_name)
6889 .into_iter()
6890 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
6891 .collect::<Vec<_>>();
6892 let resolved_type = if type_candidates.is_empty() {
6893 None
6894 } else {
6895 let Some(unit) =
6896 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
6897 else {
6898 return LexicalCallableValueResolution::Ambiguous;
6899 };
6900 Some(unit)
6901 };
6902
6903 let mut qualified_callable = qualified_owner;
6904 qualified_callable.push(member_name.to_string());
6905 let callable_name = qualified_callable.join("::");
6906 let free_function = self
6907 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
6908 .into_iter()
6909 .find(|candidate| {
6910 canonical_cpp_name_matches(candidate, &callable_name)
6911 && type_owner_of(analyzer, candidate).is_none()
6912 })
6913 .cloned();
6914
6915 match (resolved_type, free_function) {
6916 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
6917 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
6918 (None, Some(function)) => {
6919 return LexicalCallableValueResolution::FreeFunction(function);
6920 }
6921 (None, None) => {}
6922 }
6923 }
6924 LexicalCallableValueResolution::Missing
6925 }
6926
6927 fn resolve_type_for_declaration(
6928 &self,
6929 visible_from: &ProjectFile,
6930 declaration: &CodeUnit,
6931 raw_name: &str,
6932 ) -> Option<CodeUnit> {
6933 let normalized = normalize_reference_name(raw_name)?;
6934 if !normalized.contains("::")
6935 && let Some(namespace) = cpp_namespace_for(declaration)
6936 {
6937 for prefix in namespace_prefixes(&namespace) {
6938 let qualified = format!("{prefix}::{normalized}");
6939 if let Some(unit) = self
6940 .type_candidates(visible_from, &qualified)
6941 .into_iter()
6942 .next()
6943 {
6944 return Some(unit.clone());
6945 }
6946 }
6947 }
6948 self.resolve_type(visible_from, raw_name)
6949 }
6950
6951 fn resolve_unique_canonical_type_for_declaration(
6952 &self,
6953 analyzer: &CppGraphSource<'_>,
6954 visible_from: &ProjectFile,
6955 declaration: &CodeUnit,
6956 raw_name: &str,
6957 ) -> Option<CodeUnit> {
6958 let mut current = self.resolve_defining_type_for_declaration(
6959 analyzer,
6960 visible_from,
6961 declaration,
6962 raw_name,
6963 )?;
6964 let mut seen_aliases = HashSet::default();
6965 loop {
6966 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
6967 return current.is_class().then_some(current);
6968 };
6969 if matches!(target, StructuredAliasTarget::Builtin) {
6970 return current.is_class().then_some(current);
6971 }
6972 if !seen_aliases.insert(current.clone()) {
6973 return None;
6974 }
6975 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
6976 }
6977 }
6978
6979 pub fn canonical_type_unit(
6980 &self,
6981 analyzer: &CppGraphSource<'_>,
6982 visible_from: &ProjectFile,
6983 unit: &CodeUnit,
6984 ) -> Option<CodeUnit> {
6985 self.canonical_type_resolution(analyzer, visible_from, unit)
6986 .ok()
6987 }
6988
6989 pub fn canonical_type_unit_in_context(
6997 &self,
6998 analyzer: &CppGraphSource<'_>,
6999 visible_from: &ProjectFile,
7000 reference: Node<'_>,
7001 unit: &CodeUnit,
7002 ) -> Option<CodeUnit> {
7003 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7004 {
7005 return None;
7006 }
7007 self.canonical_type_resolution(analyzer, visible_from, unit)
7008 .ok()
7009 }
7010
7011 fn canonical_type_resolution(
7019 &self,
7020 analyzer: &CppGraphSource<'_>,
7021 visible_from: &ProjectFile,
7022 unit: &CodeUnit,
7023 ) -> Result<CodeUnit, TypeCandidateFailure> {
7024 let mut current = unit.clone();
7025 let mut seen_aliases = HashSet::default();
7026 loop {
7027 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7028 return current
7029 .is_class()
7030 .then_some(current)
7031 .ok_or(TypeCandidateFailure::Unresolvable);
7032 };
7033 if matches!(target, StructuredAliasTarget::Builtin) {
7034 return current
7035 .is_class()
7036 .then_some(current)
7037 .ok_or(TypeCandidateFailure::Unresolvable);
7038 }
7039 if !seen_aliases.insert(current.clone()) {
7040 return Err(TypeCandidateFailure::Unresolvable);
7041 }
7042 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7043 }
7044 }
7045
7046 pub fn canonical_visible_full_type_unit(
7047 &self,
7048 analyzer: &CppGraphSource<'_>,
7049 visible_from: &ProjectFile,
7050 unit: &CodeUnit,
7051 ) -> Option<CodeUnit> {
7052 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7053 if cpp_class_declaration_strength(analyzer, &canonical)
7054 != CppClassDeclarationStrength::Forward
7055 {
7056 return Some(canonical);
7057 }
7058 let mut full = Vec::new();
7059 for candidate in self
7060 .visible_identifier_candidates(visible_from, canonical.identifier())
7061 .filter(|candidate| {
7062 candidate.is_class()
7063 && candidate.fq_name() == canonical.fq_name()
7064 && cpp_class_declaration_strength(analyzer, candidate)
7065 == CppClassDeclarationStrength::Full
7066 })
7067 {
7068 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7069 full.push(candidate.clone());
7070 }
7071 }
7072 match full.len() {
7073 0 => Some(canonical),
7074 1 => full.pop(),
7075 _ => None,
7076 }
7077 }
7078
7079 fn resolve_structured_alias_target(
7080 &self,
7081 visible_from: &ProjectFile,
7082 declaration: &CodeUnit,
7083 target: &StructuredAliasTarget,
7084 ) -> Option<CodeUnit> {
7085 self.structured_alias_target_resolution(visible_from, declaration, target)
7086 .ok()
7087 }
7088
7089 fn structured_alias_target_resolution(
7090 &self,
7091 visible_from: &ProjectFile,
7092 declaration: &CodeUnit,
7093 target: &StructuredAliasTarget,
7094 ) -> Result<CodeUnit, TypeCandidateFailure> {
7095 let primary =
7096 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7097 let StructuredAliasTarget::Named { arguments, .. } = target else {
7098 return Err(TypeCandidateFailure::Unresolvable);
7099 };
7100 match arguments {
7101 Some(arguments) => self
7102 .resolve_template_arguments(visible_from, primary, arguments)
7103 .map_err(|error| match error {
7104 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7105 TypeCandidateFailure::Ambiguous
7106 }
7107 _ => TypeCandidateFailure::Unresolvable,
7108 }),
7109 None => Ok(primary),
7110 }
7111 }
7112
7113 fn resolve_structured_alias_primary(
7114 &self,
7115 visible_from: &ProjectFile,
7116 declaration: &CodeUnit,
7117 target: &StructuredAliasTarget,
7118 ) -> Option<CodeUnit> {
7119 self.structured_alias_primary_resolution(visible_from, declaration, target)
7120 .ok()
7121 }
7122
7123 fn structured_alias_primary_resolution(
7124 &self,
7125 visible_from: &ProjectFile,
7126 declaration: &CodeUnit,
7127 target: &StructuredAliasTarget,
7128 ) -> Result<CodeUnit, TypeCandidateFailure> {
7129 let StructuredAliasTarget::Named {
7130 components, global, ..
7131 } = target
7132 else {
7133 return Err(TypeCandidateFailure::Unresolvable);
7134 };
7135 let qualified = components.join("::");
7136 let candidates = if *global {
7137 let mut candidates = self.type_candidates(visible_from, &qualified);
7144 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7145 candidates
7146 } else {
7147 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7148 };
7149 logical_type_candidate(candidates)
7150 }
7151
7152 pub fn structured_alias_primary_preserves_target(
7153 &self,
7154 analyzer: &CppGraphSource<'_>,
7155 visible_from: &ProjectFile,
7156 candidate: &CodeUnit,
7157 target: &CodeUnit,
7158 ) -> bool {
7159 let mut current = candidate.clone();
7160 let mut seen = HashSet::default();
7161 let mut matched_target = false;
7162 loop {
7163 if same_visible_symbol(¤t, target)
7164 || self.compatible_primary_template_redeclarations(¤t, target)
7165 {
7166 matched_target = true;
7167 }
7168 if !seen.insert(current.clone()) {
7169 return false;
7170 }
7171 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7172 return matched_target;
7173 };
7174 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7175 return matched_target;
7176 };
7177 let Some(primary) =
7178 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7179 else {
7180 return matched_target;
7186 };
7187 current = primary;
7188 }
7189 }
7190
7191 pub fn structured_class_alias_resolves_to_target(
7192 &self,
7193 analyzer: &CppGraphSource<'_>,
7194 visible_from: &ProjectFile,
7195 alias: &CodeUnit,
7196 target: &CodeUnit,
7197 ) -> bool {
7198 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7199 return false;
7200 };
7201 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7202 return false;
7203 };
7204 let StructuredAliasTarget::Named {
7205 components, global, ..
7206 } = &alias_target
7207 else {
7208 return false;
7209 };
7210 let lexical_scope = canonical_cpp_scope_components(&owner);
7211 match self.resolve_type_components_lexically_for_target(
7212 analyzer,
7213 visible_from,
7214 components,
7215 *global,
7216 &lexical_scope,
7217 target,
7218 ) {
7219 LexicalTypeResolution::Resolved {
7220 unit, candidates, ..
7221 } => {
7222 same_visible_symbol(&unit, target)
7223 || self.same_template_member_identity(analyzer, &unit, target)
7224 || candidates.iter().any(|candidate| {
7225 same_visible_symbol(candidate, target)
7226 || self.same_template_member_identity(analyzer, candidate, target)
7227 })
7228 }
7229 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7230 self.structured_alias_primary_preserves_target(
7231 analyzer,
7232 visible_from,
7233 alias,
7234 target,
7235 ) || self.flattened_macro_namespace_alias_target_matches(
7236 analyzer,
7237 visible_from,
7238 alias,
7239 &alias_target,
7240 target,
7241 )
7242 }
7243 }
7244 }
7245
7246 pub fn structured_class_alias_path_preserves_target(
7254 &self,
7255 analyzer: &CppGraphSource<'_>,
7256 visible_from: &ProjectFile,
7257 alias: &CodeUnit,
7258 target: &CodeUnit,
7259 ) -> bool {
7260 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7261 return false;
7262 };
7263 let Some(StructuredAliasTarget::Named {
7264 components, global, ..
7265 }) = self.structured_alias_target(analyzer, alias)
7266 else {
7267 return false;
7268 };
7269 let lexical_scope = canonical_cpp_scope_components(&owner);
7270 (1..components.len()).rev().any(|component_count| {
7271 matches!(
7272 self.resolve_type_components_lexically_for_target(
7273 analyzer,
7274 visible_from,
7275 &components[..component_count],
7276 global,
7277 &lexical_scope,
7278 target,
7279 ),
7280 LexicalTypeResolution::Resolved {
7281 ref unit,
7282 ref candidates,
7283 ..
7284 } if same_visible_symbol(unit, target)
7285 || self.same_template_member_identity(analyzer, unit, target)
7286 || candidates.iter().any(|candidate| {
7287 same_visible_symbol(candidate, target)
7288 || self.same_template_member_identity(analyzer, candidate, target)
7289 })
7290 )
7291 })
7292 }
7293
7294 fn flattened_macro_namespace_alias_target_matches(
7295 &self,
7296 analyzer: &CppGraphSource<'_>,
7297 visible_from: &ProjectFile,
7298 alias: &CodeUnit,
7299 alias_target: &StructuredAliasTarget,
7300 target: &CodeUnit,
7301 ) -> bool {
7302 let StructuredAliasTarget::Named {
7303 components,
7304 global: false,
7305 arguments: None,
7306 } = alias_target
7307 else {
7308 return false;
7309 };
7310 let Some((target_name, namespace_components)) = components.split_last() else {
7311 return false;
7312 };
7313 if namespace_components.is_empty()
7314 || target_name != target.identifier()
7315 || alias.source() != target.source()
7316 || alias.source() != visible_from
7317 || !target.is_class()
7318 || declared_type_alias(analyzer, target)
7319 {
7320 return false;
7321 }
7322 if self
7323 .resolve_structured_alias_target(visible_from, alias, alias_target)
7324 .is_some()
7325 {
7326 return false;
7327 }
7328
7329 let Some(alias_start) = analyzer
7330 .declaration_source_occurrences(alias)
7331 .into_iter()
7332 .flatten()
7333 .map(|occurrence| occurrence.range.start_byte)
7334 .min()
7335 else {
7336 return false;
7337 };
7338 let has_matching_declaration = analyzer
7339 .declaration_source_occurrences(target)
7340 .into_iter()
7341 .flatten()
7342 .any(|occurrence| {
7343 occurrence.range.end_byte <= alias_start
7344 && occurrence.fact.flattened_macro_namespace.as_deref()
7345 == Some(namespace_components)
7346 });
7347 if !has_matching_declaration {
7348 return false;
7349 }
7350
7351 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7352 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7353 guard_requirement_sets_match(&alias_guards, &target_guards)
7354 }
7355
7356 pub fn template_alias_arguments_preserve_target(
7357 &self,
7358 analyzer: &CppGraphSource<'_>,
7359 visible_from: &ProjectFile,
7360 alias: &CodeUnit,
7361 arguments: &[CppTemplateExpression],
7362 target: &CodeUnit,
7363 ) -> bool {
7364 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7365 return false;
7366 };
7367 if metadata.alias_target.is_none()
7368 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7369 {
7370 return false;
7371 }
7372 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7373 }
7374
7375 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7376 self.cpp_template_metadata
7377 .get(unit)
7378 .is_some_and(CppTemplateMetadata::is_primary)
7379 }
7380
7381 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7382 self.cpp_template_metadata
7383 .get(unit)
7384 .is_some_and(CppTemplateMetadata::is_specialization)
7385 }
7386
7387 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7388 same_visible_symbol(left, right)
7389 || self.compatible_primary_template_redeclarations(left, right)
7390 }
7391
7392 pub fn same_template_member_identity(
7393 &self,
7394 analyzer: &CppGraphSource<'_>,
7395 left: &CodeUnit,
7396 right: &CodeUnit,
7397 ) -> bool {
7398 if same_visible_symbol(left, right) {
7399 return true;
7400 }
7401 if left.kind() != right.kind()
7402 || left.identifier() != right.identifier()
7403 || left.signature() != right.signature()
7404 {
7405 return false;
7406 }
7407 let (Some(left_owner), Some(right_owner)) =
7408 (analyzer.parent_of(left), analyzer.parent_of(right))
7409 else {
7410 return false;
7411 };
7412 left_owner.is_class()
7413 && right_owner.is_class()
7414 && self.same_template_owner_identity(&left_owner, &right_owner)
7415 }
7416
7417 fn unique_canonical_type_candidate(
7418 &self,
7419 analyzer: &CppGraphSource<'_>,
7420 visible_from: &ProjectFile,
7421 candidates: &[&CodeUnit],
7422 ) -> Option<CodeUnit> {
7423 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7424 .ok()
7425 }
7426
7427 fn canonical_type_candidate_resolution(
7428 &self,
7429 analyzer: &CppGraphSource<'_>,
7430 visible_from: &ProjectFile,
7431 candidates: &[&CodeUnit],
7432 ) -> Result<CodeUnit, TypeCandidateFailure> {
7433 let mut canonical = Vec::new();
7434 for candidate in candidates {
7435 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7436 if canonical
7437 .iter()
7438 .any(|existing| same_visible_symbol(existing, &resolved))
7439 {
7440 continue;
7441 }
7442 if let Some(existing) = canonical.iter_mut().find(|existing| {
7443 self.compatible_primary_template_redeclarations(existing, &resolved)
7444 }) {
7445 if matches!(
7454 (
7455 cpp_class_declaration_strength(analyzer, existing),
7456 cpp_class_declaration_strength(analyzer, &resolved),
7457 ),
7458 (
7459 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7460 CppClassDeclarationStrength::Full,
7461 ) | (
7462 CppClassDeclarationStrength::Unknown,
7463 CppClassDeclarationStrength::Forward,
7464 )
7465 ) {
7466 *existing = resolved;
7467 }
7468 continue;
7469 }
7470 canonical.push(resolved);
7471 if canonical.len() > 1 {
7472 return Err(TypeCandidateFailure::Ambiguous);
7473 }
7474 }
7475 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7476 }
7477
7478 pub fn unique_type_candidate_preserving_target(
7479 &self,
7480 analyzer: &CppGraphSource<'_>,
7481 visible_from: &ProjectFile,
7482 candidates: &[&CodeUnit],
7483 target: &CodeUnit,
7484 ) -> Option<CodeUnit> {
7485 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7496 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7497 {
7498 return Some(target.clone());
7499 }
7500 let mut resolved_candidates = Vec::new();
7501 for candidate in candidates {
7502 let Some(resolved) =
7508 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7509 else {
7510 continue;
7511 };
7512 if resolved_candidates
7513 .iter()
7514 .any(|existing| same_visible_symbol(existing, &resolved))
7515 {
7516 continue;
7517 }
7518 resolved_candidates.push(resolved);
7519 }
7520 match resolved_candidates.as_slice() {
7521 [] => None,
7522 [single] => Some(single.clone()),
7523 _ => self
7528 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7529 .map(|_| target.clone()),
7530 }
7531 }
7532
7533 pub fn same_fqn_type_spelling_for_target<'b>(
7550 &self,
7551 analyzer: &CppGraphSource<'_>,
7552 visible_from: &ProjectFile,
7553 candidates: &[&'b CodeUnit],
7554 target: &CodeUnit,
7555 ) -> Option<&'b CodeUnit> {
7556 let [first, rest @ ..] = candidates else {
7557 return None;
7558 };
7559 if rest.is_empty()
7560 || !rest.iter().all(|candidate| {
7561 candidate.kind() == first.kind()
7562 && candidate.fq_name() == first.fq_name()
7563 && candidate.source() == first.source()
7564 })
7565 {
7566 return None;
7567 }
7568 candidates
7569 .iter()
7570 .copied()
7571 .find(|candidate| same_symbol(candidate, target))
7572 .or_else(|| {
7573 candidates.iter().copied().find(|candidate| {
7574 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7575 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7576 })
7577 })
7578 }
7579
7580 pub fn alternate_same_fqn_type_declarations(
7581 &self,
7582 analyzer: &CppGraphSource<'_>,
7583 candidates: &[&CodeUnit],
7584 target: &CodeUnit,
7585 ) -> bool {
7586 let Some(first) = candidates.first() else {
7587 return false;
7588 };
7589 let same_api = first.kind() == target.kind()
7590 && first.fq_name() == target.fq_name()
7591 && first.source() == target.source()
7592 && candidates.iter().all(|candidate| {
7593 candidate.kind() == target.kind()
7594 && candidate.fq_name() == target.fq_name()
7595 && candidate.source() == target.source()
7596 })
7597 && candidates
7598 .iter()
7599 .any(|candidate| same_symbol(candidate, target))
7600 && candidates
7601 .iter()
7602 .any(|candidate| !same_logical_symbol(candidate, target));
7603 if !same_api {
7604 return false;
7605 }
7606
7607 let requirements = candidates
7608 .iter()
7609 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7610 .collect::<Vec<_>>();
7611 requirements.len() > 1
7612 && requirements
7613 .iter()
7614 .all(|requirement| !requirement.is_empty())
7615 && requirements.iter().enumerate().all(|(index, left)| {
7616 requirements[index + 1..].iter().all(|right| {
7617 left.iter().all(|(_, left_guards)| {
7618 right.iter().all(|(_, right_guards)| {
7619 merge_preprocessor_guards(left_guards, right_guards).is_none()
7620 })
7621 })
7622 })
7623 })
7624 }
7625
7626 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7627 let mut pending = vec![terms.to_vec()];
7628 while let Some(branch_terms) = pending.pop() {
7629 let mut normalized = Vec::new();
7630 let mut covers_branch = false;
7631 for term in branch_terms {
7632 if term.iter().any(|guard| term.contains(&guard.negated())) {
7633 continue;
7634 }
7635 if term.is_empty() {
7636 covers_branch = true;
7637 break;
7638 }
7639 if !normalized.iter().any(|existing| existing == &term) {
7640 normalized.push(term);
7641 }
7642 }
7643 if covers_branch {
7644 continue;
7645 }
7646 let Some(split_guard) = normalized
7647 .iter()
7648 .flat_map(|term| term.iter())
7649 .next()
7650 .cloned()
7651 else {
7652 return false;
7653 };
7654 let negated_guard = split_guard.negated();
7655 let mut when_defined = Vec::new();
7656 let mut when_undefined = Vec::new();
7657 for term in normalized {
7658 if term.contains(&negated_guard) {
7659 } else if term.contains(&split_guard) {
7661 let mut reduced = term.clone();
7662 reduced.remove(&split_guard);
7663 when_defined.push(reduced);
7664 } else {
7665 when_defined.push(term.clone());
7666 }
7667 if term.contains(&split_guard) {
7668 } else if term.contains(&negated_guard) {
7670 let mut reduced = term;
7671 reduced.remove(&negated_guard);
7672 when_undefined.push(reduced);
7673 } else {
7674 when_undefined.push(term);
7675 }
7676 }
7677 pending.push(when_defined);
7678 pending.push(when_undefined);
7679 }
7680 true
7681 }
7682
7683 fn declarations_share_exhaustive_conditional_family(
7692 &self,
7693 analyzer: &CppGraphSource<'_>,
7694 candidates: &[&CodeUnit],
7695 ) -> Option<(usize, usize)> {
7696 let mut family_range = None;
7697 for candidate in candidates {
7698 let mut candidate_family = None;
7699 for occurrence in analyzer.declaration_source_occurrences(candidate)? {
7700 let key = occurrence.exhaustive_family?;
7701 if candidate_family.is_some_and(|existing| existing != key) {
7702 return None;
7703 }
7704 candidate_family = Some(key);
7705 }
7706 let candidate_family = candidate_family?;
7707 if family_range.is_some_and(|existing| existing != candidate_family) {
7708 return None;
7709 }
7710 family_range = Some(candidate_family);
7711 }
7712 family_range
7713 }
7714
7715 pub fn complementary_same_fqn_type_declarations(
7716 &self,
7717 analyzer: &CppGraphSource<'_>,
7718 candidates: &[&CodeUnit],
7719 target: &CodeUnit,
7720 ) -> bool {
7721 if candidates.len() < 2
7722 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7723 || self
7724 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7725 .is_none()
7726 {
7727 return false;
7728 }
7729 Self::preprocessor_guard_terms_cover_all_paths(
7730 &self.declaration_family_guard_terms(analyzer, candidates),
7731 )
7732 }
7733
7734 fn declaration_family_guard_terms(
7735 &self,
7736 analyzer: &CppGraphSource<'_>,
7737 candidates: &[&CodeUnit],
7738 ) -> Vec<HashSet<PreprocessorGuard>> {
7739 candidates
7740 .iter()
7741 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7742 .map(|(_, guards)| guards)
7743 .collect()
7744 }
7745
7746 fn exhaustive_guard_family_activation(
7762 &self,
7763 analyzer: &CppGraphSource<'_>,
7764 candidate: &CodeUnit,
7765 reference: &CallableReferenceContext<'_>,
7766 ) -> Option<usize> {
7767 if !analyzer
7770 .declaration_source_occurrences(candidate)?
7771 .iter()
7772 .any(|occurrence| occurrence.fact.callable_activation.is_some())
7773 {
7774 return None;
7775 }
7776 let family = self
7777 .visible_identifier_candidates(candidate.source(), candidate.identifier())
7778 .filter(|peer| {
7779 peer.kind() == candidate.kind()
7780 && peer.fq_name() == candidate.fq_name()
7781 && peer.source() == candidate.source()
7782 })
7783 .collect::<Vec<_>>();
7784 let (_, family_end) =
7785 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7786 if !Self::preprocessor_guard_terms_cover_all_paths(
7787 &self.declaration_family_guard_terms(analyzer, &family),
7788 ) {
7789 return None;
7790 }
7791 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7795 .iter()
7796 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7797 {
7798 return None;
7799 }
7800 (first_declaration_byte(analyzer, candidate)?
7801 == family
7802 .iter()
7803 .filter_map(|peer| first_declaration_byte(analyzer, peer))
7804 .min()?)
7805 .then_some(family_end)
7806 }
7807
7808 fn type_candidate_preserving_target(
7809 &self,
7810 analyzer: &CppGraphSource<'_>,
7811 visible_from: &ProjectFile,
7812 candidate: &CodeUnit,
7813 target: &CodeUnit,
7814 ) -> Option<CodeUnit> {
7815 let mut current = candidate.clone();
7816 let mut matched_target = same_visible_symbol(¤t, target)
7817 || self.compatible_primary_template_redeclarations(¤t, target);
7818 let mut seen = HashSet::default();
7819 loop {
7820 if !seen.insert(current.clone()) {
7821 return None;
7822 }
7823 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7824 return matched_target
7825 .then(|| target.clone())
7826 .or_else(|| current.is_class().then_some(current));
7827 };
7828 if self.flattened_macro_namespace_alias_target_matches(
7829 analyzer,
7830 visible_from,
7831 ¤t,
7832 &alias_target,
7833 target,
7834 ) {
7835 return Some(target.clone());
7836 }
7837 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7838 return matched_target
7839 .then(|| target.clone())
7840 .or_else(|| current.is_class().then_some(current));
7841 }
7842 if !self.cpp_template_metadata.contains_key(¤t)
7850 && let Some(primary) =
7851 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7852 && (same_visible_symbol(&primary, target)
7853 || self.compatible_primary_template_redeclarations(&primary, target))
7854 {
7855 return Some(target.clone());
7856 }
7857 if same_visible_symbol(¤t, target) {
7858 return Some(target.clone());
7859 }
7860 if self.cpp_template_metadata.contains_key(¤t) {
7861 return None;
7862 }
7863 let Some(next) =
7864 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
7865 else {
7866 return matched_target.then(|| target.clone());
7867 };
7868 current = next;
7869 matched_target |= same_visible_symbol(¤t, target)
7870 || self.compatible_primary_template_redeclarations(¤t, target);
7871 }
7872 }
7873
7874 fn compatible_primary_template_redeclarations(
7875 &self,
7876 left: &CodeUnit,
7877 right: &CodeUnit,
7878 ) -> bool {
7879 let (Some(left_metadata), Some(right_metadata)) = (
7880 self.cpp_template_metadata.get(left),
7881 self.cpp_template_metadata.get(right),
7882 ) else {
7883 return false;
7884 };
7885 left_metadata.primary_fq_name == right_metadata.primary_fq_name
7886 && left_metadata.is_primary()
7887 && right_metadata.is_primary()
7888 && cpp_reconcile_primary_template_parameters(
7889 &[(left, left_metadata), (right, right_metadata)],
7890 right,
7891 )
7892 .is_some()
7893 }
7894
7895 fn alias_candidate_may_preserve_target(
7896 &self,
7897 analyzer: &CppGraphSource<'_>,
7898 visible_from: &ProjectFile,
7899 candidate: &CodeUnit,
7900 target: &CodeUnit,
7901 ) -> bool {
7902 let mut current = candidate.clone();
7903 let mut seen = HashSet::default();
7904 loop {
7905 if same_visible_symbol(¤t, target)
7906 || self.compatible_primary_template_redeclarations(¤t, target)
7907 {
7908 return true;
7909 }
7910 if self.cpp_template_metadata.contains_key(¤t) {
7911 return true;
7912 }
7913 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7914 return false;
7915 };
7916 let StructuredAliasTarget::Named {
7917 components,
7918 global,
7919 arguments,
7920 } = alias_target
7921 else {
7922 return false;
7923 };
7924 if arguments.is_some() || !seen.insert(current.clone()) {
7925 return true;
7926 }
7927 let qualified = components.join("::");
7928 let next = if global {
7929 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
7930 } else {
7931 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
7932 };
7933 let Some(next) = next else {
7934 return true;
7935 };
7936 current = next;
7937 }
7938 }
7939
7940 fn alias_candidate_structurally_reaches_target(
7941 &self,
7942 analyzer: &CppGraphSource<'_>,
7943 visible_from: &ProjectFile,
7944 candidate: &CodeUnit,
7945 target: &CodeUnit,
7946 ) -> bool {
7947 let mut current = candidate.clone();
7948 let mut seen = HashSet::default();
7949 loop {
7950 if same_visible_symbol(¤t, target)
7951 || self.compatible_primary_template_redeclarations(¤t, target)
7952 || self
7953 .cpp_template_metadata
7954 .get(¤t)
7955 .zip(self.cpp_template_metadata.get(target))
7956 .is_some_and(|(current, target)| {
7957 current.primary_fq_name == target.primary_fq_name
7958 })
7959 {
7960 return true;
7961 }
7962 if !seen.insert(current.clone()) {
7963 return false;
7964 }
7965 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7966 return false;
7967 };
7968 if let StructuredAliasTarget::Named { components, .. } = &alias_target
7969 && components[..components.len().saturating_sub(1)]
7970 .iter()
7971 .any(|component| {
7972 self.type_reference_component_directly_names_target(component, target)
7973 })
7974 && self.structured_class_alias_path_preserves_target(
7975 analyzer,
7976 visible_from,
7977 ¤t,
7978 target,
7979 )
7980 {
7981 return true;
7982 }
7983 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7984 return false;
7985 }
7986 let Some(primary) =
7987 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7988 else {
7989 return false;
7990 };
7991 current = primary;
7992 }
7993 }
7994
7995 pub fn qualified_alias_reference_may_reach_target(
8007 &self,
8008 analyzer: &CppGraphSource<'_>,
8009 file: &ProjectFile,
8010 components: &[String],
8011 global: bool,
8012 target: &CodeUnit,
8013 ) -> bool {
8014 for end in 0..components.len() {
8015 let spelled = &components[..=end];
8016 for candidate in self
8017 .visible_identifier_candidates(file, &components[end])
8018 .filter(|candidate| declared_type_alias(analyzer, candidate))
8019 {
8020 let candidate_components = canonical_cpp_scope_components(candidate);
8021 let shape_matches = if global {
8022 candidate_components == spelled
8023 } else {
8024 candidate_components.ends_with(spelled)
8025 };
8026 if shape_matches
8027 && self.alias_candidate_structurally_reaches_target(
8028 analyzer, file, candidate, target,
8029 )
8030 {
8031 return true;
8032 }
8033 }
8034 }
8035 false
8036 }
8037
8038 fn type_candidates_for_declaration<'b>(
8042 &'b self,
8043 visible_from: &ProjectFile,
8044 declaration: &CodeUnit,
8045 raw_name: &str,
8046 ) -> Vec<&'b CodeUnit> {
8047 let Some(normalized) = normalize_reference_name(raw_name) else {
8048 return Vec::new();
8049 };
8050 if let Some(namespace) = cpp_namespace_for(declaration) {
8051 for prefix in namespace_prefixes(&namespace) {
8052 let qualified = format!("{prefix}::{normalized}");
8053 let candidates = self.type_candidates(visible_from, &qualified);
8054 if !candidates.is_empty() {
8055 return candidates;
8056 }
8057 }
8058 }
8059 self.type_candidates(visible_from, &normalized)
8060 }
8061
8062 fn resolve_unique_type_for_declaration(
8063 &self,
8064 visible_from: &ProjectFile,
8065 declaration: &CodeUnit,
8066 raw_name: &str,
8067 ) -> Option<CodeUnit> {
8068 unique_logical_type_candidate(self.type_candidates_for_declaration(
8069 visible_from,
8070 declaration,
8071 raw_name,
8072 ))
8073 }
8074
8075 fn resolve_defining_type_for_declaration(
8093 &self,
8094 analyzer: &CppGraphSource<'_>,
8095 visible_from: &ProjectFile,
8096 declaration: &CodeUnit,
8097 raw_name: &str,
8098 ) -> Option<CodeUnit> {
8099 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8100 let logical = unique_logical_type_candidate(candidates.clone())?;
8101 let mut defining = candidates.into_iter().filter(|candidate| {
8102 candidate.is_class()
8103 && cpp_class_declaration_strength(analyzer, candidate)
8104 == CppClassDeclarationStrength::Full
8105 });
8106 match (defining.next(), defining.next()) {
8107 (Some(unique_definition), None) => Some(unique_definition.clone()),
8108 _ => Some(logical),
8109 }
8110 }
8111
8112 pub fn resolves_to_type(
8113 &self,
8114 analyzer: &CppGraphSource<'_>,
8115 file: &ProjectFile,
8116 raw_name: &str,
8117 target: &CodeUnit,
8118 ) -> bool {
8119 let Some(normalized) = normalize_reference_name(raw_name) else {
8120 return false;
8121 };
8122 let candidates = self.type_candidates(file, &normalized);
8123 if candidates.is_empty() {
8124 return self.parser_alias_resolves_to_type(file, raw_name, target);
8125 }
8126 let Some(resolved) =
8127 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8128 else {
8129 return false;
8130 };
8131 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8132 }
8133
8134 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8135 let mut current = alias.clone();
8136 let mut visited = HashSet::default();
8137 while visited.insert(current.clone()) {
8138 let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), ¤t)?;
8139 let resolved =
8140 self.resolve_type_for_declaration(current.source(), ¤t, &raw_target)?;
8141 if resolved.is_class() {
8142 return Some(resolved);
8143 }
8144 if !declared_type_alias(&self.cpp_source(), &resolved) {
8145 return None;
8146 }
8147 current = resolved;
8148 }
8149 None
8150 }
8151
8152 pub fn same_logical_callable(
8167 &self,
8168 analyzer: &CppGraphSource<'_>,
8169 left: &CodeUnit,
8170 right: &CodeUnit,
8171 ) -> bool {
8172 if same_logical_symbol(left, right) {
8173 return true;
8174 }
8175 if left.kind() != right.kind()
8176 || !left.is_callable()
8177 || !right.is_callable()
8178 || left.fq_name() != right.fq_name()
8179 {
8180 return false;
8181 }
8182 if self.callable_is_template_declaration(analyzer, left)
8188 || self.callable_is_template_declaration(analyzer, right)
8189 {
8190 return false;
8191 }
8192 let (Some(left_comparable), Some(right_comparable)) = (
8193 self.callable_comparable(analyzer, left),
8194 self.callable_comparable(analyzer, right),
8195 ) else {
8196 return false;
8197 };
8198 if left_comparable.suffix != right_comparable.suffix
8203 || left_comparable.shapes.len() != right_comparable.shapes.len()
8204 {
8205 return false;
8206 }
8207 left_comparable
8208 .shapes
8209 .iter()
8210 .zip(right_comparable.shapes.iter())
8211 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8212 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8213 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8214 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8215 }
8216 _ => false,
8220 })
8221 }
8222
8223 fn comparable_shapes_agree(
8229 &self,
8230 analyzer: &CppGraphSource<'_>,
8231 left: &CppComparableParameter,
8232 right: &CppComparableParameter,
8233 ) -> bool {
8234 let mut stack = vec![(left.root(), right.root())];
8235 while let Some((left_index, right_index)) = stack.pop() {
8236 match (left.node(left_index), right.node(right_index)) {
8237 (
8238 CppComparableNode::Named {
8239 name: left_name,
8240 primitive: left_primitive,
8241 konst: left_konst,
8242 volatil: left_volatil,
8243 },
8244 CppComparableNode::Named {
8245 name: right_name,
8246 primitive: right_primitive,
8247 konst: right_konst,
8248 volatil: right_volatil,
8249 },
8250 ) => {
8251 if left_konst != right_konst
8252 || left_volatil != right_volatil
8253 || left_primitive != right_primitive
8254 || !self.comparable_names_agree(
8255 analyzer,
8256 left_name,
8257 right_name,
8258 *left_primitive,
8259 )
8260 {
8261 return false;
8262 }
8263 }
8264 (
8265 CppComparableNode::Pointer {
8266 inner: left_inner,
8267 konst: left_konst,
8268 volatil: left_volatil,
8269 },
8270 CppComparableNode::Pointer {
8271 inner: right_inner,
8272 konst: right_konst,
8273 volatil: right_volatil,
8274 },
8275 ) => {
8276 if left_konst != right_konst || left_volatil != right_volatil {
8277 return false;
8278 }
8279 stack.push((*left_inner, *right_inner));
8280 }
8281 (
8282 CppComparableNode::Reference { inner: left_inner },
8283 CppComparableNode::Reference { inner: right_inner },
8284 )
8285 | (
8286 CppComparableNode::Array { inner: left_inner },
8287 CppComparableNode::Array { inner: right_inner },
8288 ) => stack.push((*left_inner, *right_inner)),
8289 (
8290 CppComparableNode::Generic {
8291 base: left_base,
8292 arguments: left_arguments,
8293 },
8294 CppComparableNode::Generic {
8295 base: right_base,
8296 arguments: right_arguments,
8297 },
8298 ) => {
8299 if left_arguments.len() != right_arguments.len() {
8300 return false;
8301 }
8302 stack.push((*left_base, *right_base));
8303 stack.extend(
8304 left_arguments.iter().zip(right_arguments.iter()).map(
8305 |(left_argument, right_argument)| (*left_argument, *right_argument),
8306 ),
8307 );
8308 }
8309 _ => return false,
8310 }
8311 }
8312 true
8313 }
8314
8315 fn comparable_names_agree(
8325 &self,
8326 analyzer: &CppGraphSource<'_>,
8327 left: &StructuredTypeName,
8328 right: &StructuredTypeName,
8329 primitive: bool,
8330 ) -> bool {
8331 if primitive {
8332 return left.path() == right.path();
8333 }
8334 match (
8335 self.comparable_name_terminal(analyzer, left),
8336 self.comparable_name_terminal(analyzer, right),
8337 ) {
8338 (Some(left_terminal), Some(right_terminal)) => {
8339 same_logical_symbol(&left_terminal, &right_terminal)
8340 }
8341 (None, None) => {
8342 left.path() == right.path() && left.is_absolute() == right.is_absolute()
8343 }
8344 _ => false,
8345 }
8346 }
8347
8348 fn comparable_name_terminal(
8358 &self,
8359 analyzer: &CppGraphSource<'_>,
8360 name: &StructuredTypeName,
8361 ) -> Option<CodeUnit> {
8362 let mut current = self.comparable_name_declaration(analyzer, name)?;
8363 let mut visited = HashSet::default();
8364 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8365 if !declared_type_alias(analyzer, ¤t) {
8372 return current.is_class().then_some(current);
8373 }
8374 if !visited.insert(current.clone()) {
8375 return None;
8376 }
8377 let facts = analyzer.declaration_source_properties(¤t)?;
8378 if facts.iter().any(|fact| fact.adds_indirection) {
8379 return None;
8380 }
8381 let raw_target = cpp_canonical_alias_target_text(analyzer, ¤t)?;
8382 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
8383 }
8384 None
8385 }
8386
8387 fn comparable_alias_target(
8398 &self,
8399 analyzer: &CppGraphSource<'_>,
8400 alias: &CodeUnit,
8401 raw_target: &str,
8402 ) -> Option<CodeUnit> {
8403 let absolute = raw_target.trim_start().starts_with("::");
8408 let normalized = normalize_reference_name(raw_target)?;
8409 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8410 brokk_bifrost_core::analyzer::Language::Cpp,
8411 &normalized,
8412 );
8413 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8414 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8415 brokk_bifrost_core::analyzer::Language::Cpp,
8416 &namespace,
8417 )
8418 });
8419 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8420 self.comparable_name_declaration(analyzer, &name)
8421 }
8422
8423 fn comparable_name_declaration(
8431 &self,
8432 analyzer: &CppGraphSource<'_>,
8433 name: &StructuredTypeName,
8434 ) -> Option<CodeUnit> {
8435 let mut cache = self
8442 .comparable_name_declarations
8443 .lock()
8444 .expect("C++ comparable name declaration cache poisoned");
8445 if let Some(cached) = cache.get(name) {
8446 return cached.clone();
8447 }
8448 let interner = segment_interner();
8449 let identifier = name.path().last()?;
8450 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8451 let first_depth = if name.is_absolute() {
8452 0
8453 } else {
8454 name.lexical_scope().len()
8455 };
8456 let mut resolved = None;
8457 for depth in (0..=first_depth).rev() {
8458 let mut structured = FqName::new();
8459 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8460 structured.push(interner.intern(component, SegmentKind::Unknown));
8461 }
8462 let mut candidates = candidates_by_identifier
8463 .iter()
8464 .filter(|unit| unit.fq().same_segment_texts(&structured))
8465 .filter(|unit| {
8466 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8467 })
8468 .cloned();
8469 let Some(first) = candidates.next() else {
8470 continue;
8471 };
8472 resolved = candidates
8473 .all(|unit| same_logical_symbol(&unit, &first))
8474 .then_some(first);
8475 break;
8476 }
8477 cache.insert(name.clone(), resolved.clone());
8478 resolved
8479 }
8480
8481 fn callable_comparable(
8487 &self,
8488 analyzer: &CppGraphSource<'_>,
8489 unit: &CodeUnit,
8490 ) -> Option<Arc<ExtractedComparable>> {
8491 if let Some(cached) = self
8492 .callable_comparables
8493 .lock()
8494 .expect("C++ callable comparable cache poisoned")
8495 .get(unit)
8496 .cloned()
8497 {
8498 return cached;
8499 }
8500 let extracted = self
8501 .extract_callable_comparable(analyzer, unit)
8502 .map(Arc::new);
8503 self.callable_comparables
8504 .lock()
8505 .expect("C++ callable comparable cache poisoned")
8506 .insert(unit.clone(), extracted.clone());
8507 extracted
8508 }
8509
8510 fn extract_callable_comparable(
8511 &self,
8512 analyzer: &CppGraphSource<'_>,
8513 unit: &CodeUnit,
8514 ) -> Option<ExtractedComparable> {
8515 let fact = analyzer
8516 .declaration_source_properties(unit)?
8517 .into_iter()
8518 .find(|fact| fact.callable_comparable_shapes.is_some())?;
8519 Some(ExtractedComparable {
8520 shapes: fact.callable_comparable_shapes?,
8521 suffix: fact.callable_identity_suffix?,
8522 })
8523 }
8524
8525 pub fn canonical_type_for_reference(
8526 &self,
8527 file: &ProjectFile,
8528 raw_name: &str,
8529 ) -> Option<CodeUnit> {
8530 let resolved = self.resolve_type(file, raw_name)?;
8531 self.alias_target(&resolved).or(Some(resolved))
8532 }
8533
8534 pub fn parser_alias_resolves_to_type(
8535 &self,
8536 file: &ProjectFile,
8537 raw_name: &str,
8538 target: &CodeUnit,
8539 ) -> bool {
8540 let Some(alias_name) = normalize_reference_name(raw_name) else {
8541 return false;
8542 };
8543 let matches_file = |source_file: &ProjectFile| {
8544 self.file_alias_matches(self.cpp, source_file, &alias_name, target)
8545 };
8546 self.visible_source_files_by_root.get(file).map_or_else(
8547 || matches_file(file),
8548 |files| files.iter().any(matches_file),
8549 )
8550 }
8551
8552 fn file_alias_matches(
8553 &self,
8554 cpp: &dyn CppSource,
8555 file: &ProjectFile,
8556 alias_name: &str,
8557 target: &CodeUnit,
8558 ) -> bool {
8559 self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
8560 cell.get()
8561 .expect("published canonical aliases")
8562 .iter()
8563 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
8564 })
8565 }
8566
8567 fn canonical_alias_cell(
8568 &self,
8569 cpp: &dyn CppSource,
8570 file: &ProjectFile,
8571 ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
8572 let cell = {
8573 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
8574 Arc::clone(
8575 cells
8576 .entry(file.clone())
8577 .or_insert_with(|| Arc::new(OnceLock::new())),
8578 )
8579 };
8580 if cell.get().is_none() {
8581 let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
8582 cell.get_or_init(|| {
8583 self.parser_alias_source_parses
8584 .fetch_add(1, Ordering::Relaxed);
8585 #[cfg(any(test, feature = "test-support"))]
8586 {
8587 *self
8588 .alias_source_parse_counts
8589 .lock()
8590 .expect("alias source parse count lock")
8591 .entry(file.clone())
8592 .or_default() += 1;
8593 }
8594 aliases.into_boxed_slice()
8595 });
8596 }
8597 Some(cell)
8598 }
8599
8600 #[cfg(any(test, feature = "test-support"))]
8601 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8602 self.visible_source_files_by_root
8603 .get(file)
8604 .cloned()
8605 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8606 }
8607
8608 #[cfg(any(test, feature = "test-support"))]
8609 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8610 self.alias_source_parse_counts
8611 .lock()
8612 .expect("alias source parse count lock")
8613 .get(file)
8614 .copied()
8615 .unwrap_or(0)
8616 }
8617
8618 #[cfg(any(test, feature = "test-support"))]
8619 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8620 self.parser_alias_fallback_files.load(Ordering::Relaxed)
8621 }
8622
8623 pub fn resolve_named(
8624 &self,
8625 file: &ProjectFile,
8626 raw_name: &str,
8627 kind: TargetKind,
8628 ) -> Option<CodeUnit> {
8629 let normalized = normalize_reference_name(raw_name)?;
8630 self.named_candidates_for_normalized(file, &normalized, kind)
8631 .into_iter()
8632 .next()
8633 .cloned()
8634 }
8635
8636 pub fn contains_named_symbol(
8637 &self,
8638 file: &ProjectFile,
8639 raw_name: &str,
8640 kind: TargetKind,
8641 target: &CodeUnit,
8642 ) -> bool {
8643 let Some(normalized) = normalize_reference_name(raw_name) else {
8644 return false;
8645 };
8646 self.named_candidates_for_normalized(file, &normalized, kind)
8647 .into_iter()
8648 .any(|unit| {
8649 matches_kind_for_lookup(unit, kind)
8650 && reference_matches_unit(&normalized, unit)
8651 && same_visible_symbol(unit, target)
8652 })
8653 }
8654
8655 pub fn named_candidates(
8656 &self,
8657 file: &ProjectFile,
8658 raw_name: &str,
8659 kind: TargetKind,
8660 ) -> Vec<CodeUnit> {
8661 let Some(normalized) = normalize_reference_name(raw_name) else {
8662 return Vec::new();
8663 };
8664 self.named_candidates_for_normalized(file, &normalized, kind)
8665 .into_iter()
8666 .cloned()
8667 .collect()
8668 }
8669
8670 pub fn resolve_known_non_target(
8671 &self,
8672 file: &ProjectFile,
8673 raw_name: &str,
8674 kind: TargetKind,
8675 target: &CodeUnit,
8676 ) -> bool {
8677 let Some(normalized) = normalize_reference_name(raw_name) else {
8678 return false;
8679 };
8680 normalized.contains("::")
8681 && self
8682 .named_candidates_for_normalized(file, &normalized, kind)
8683 .into_iter()
8684 .any(|unit| {
8685 matches_kind_for_lookup(unit, kind)
8686 && reference_matches_unit(&normalized, unit)
8687 && !same_visible_symbol(unit, target)
8688 })
8689 }
8690
8691 pub fn resolve_call_return_binding(
8692 &self,
8693 analyzer: &CppGraphSource<'_>,
8694 file: &ProjectFile,
8695 raw_name: &str,
8696 arity: usize,
8697 lexical_namespace: Option<&str>,
8698 direct_type: Option<&CodeUnit>,
8699 ) -> Option<CppScanBinding> {
8700 let normalized = normalize_reference_name(raw_name)?;
8701 let mut candidates = Vec::new();
8702 for function in
8703 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8704 {
8705 if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
8706 && !direct_type.is_some_and(|direct_type| {
8707 self.callable_is_constructor_declaration(analyzer, function)
8708 && type_owner_of(analyzer, function)
8709 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8710 })
8711 {
8712 candidates.push(function.clone());
8713 }
8714 }
8715 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8716 unanimous_return_binding(analyzer, self, file, &candidates)
8717 }
8718
8719 pub fn resolve_call_return_binding_without_arity(
8720 &self,
8721 analyzer: &CppGraphSource<'_>,
8722 file: &ProjectFile,
8723 raw_name: &str,
8724 lexical_namespace: Option<&str>,
8725 direct_type: Option<&CodeUnit>,
8726 ) -> (bool, Option<CppScanBinding>) {
8727 let Some(normalized) = normalize_reference_name(raw_name) else {
8728 return (false, None);
8729 };
8730 let mut candidates = self
8731 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8732 .into_iter()
8733 .filter(|function| {
8734 function.is_function()
8735 && !direct_type.is_some_and(|direct_type| {
8736 self.callable_is_constructor_declaration(analyzer, function)
8737 && type_owner_of(analyzer, function)
8738 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8739 })
8740 })
8741 .cloned()
8742 .collect::<Vec<_>>();
8743 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8744 let has_candidates = !candidates.is_empty();
8745 (
8746 has_candidates,
8747 unanimous_return_binding(analyzer, self, file, &candidates),
8748 )
8749 }
8750
8751 pub fn visible_identifier_candidates<'b>(
8752 &'b self,
8753 file: &ProjectFile,
8754 identifier: &str,
8755 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8756 self.visible_by_identifier
8757 .get(file)
8758 .and_then(|by_name| by_name.get(identifier))
8759 .into_iter()
8760 .flatten()
8761 }
8762
8763 pub fn type_reference_component_directly_names_target(
8769 &self,
8770 component: &str,
8771 target: &CodeUnit,
8772 ) -> bool {
8773 component == target.identifier()
8774 || self
8775 .cpp_template_metadata
8776 .get(target)
8777 .is_some_and(|metadata| component == metadata.primary_name)
8778 }
8779
8780 pub fn visible_type_reference_component_names_for_target(
8788 &self,
8789 analyzer: &CppGraphSource<'_>,
8790 file: &ProjectFile,
8791 target: &CodeUnit,
8792 ) -> HashSet<String> {
8793 let mut names = HashSet::from_iter([target.identifier().to_string()]);
8794 if let Some(metadata) = self.cpp_template_metadata.get(target) {
8795 names.insert(metadata.primary_name.clone());
8796 }
8797
8798 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8799 for (identifier, candidates) in by_identifier {
8800 if candidates.iter().any(|candidate| {
8801 (candidate.is_class()
8802 && (same_visible_symbol(candidate, target)
8803 || self.compatible_primary_template_redeclarations(candidate, target)))
8804 || (declared_type_alias(analyzer, candidate)
8805 && self.alias_candidate_structurally_reaches_target(
8806 analyzer, file, candidate, target,
8807 ))
8808 }) {
8809 names.insert(identifier.clone());
8810 }
8811 }
8812 }
8813
8814 names
8815 }
8816
8817 pub fn indexed_structural_class_scope(
8818 &self,
8819 file: &ProjectFile,
8820 class: Node<'_>,
8821 source: &str,
8822 ) -> Option<Vec<String>> {
8823 let key = (file.clone(), class.start_byte(), class.end_byte());
8824 if let Some(cached) = self
8825 .indexed_structural_class_scopes
8826 .lock()
8827 .expect("C++ indexed structural-class scope cache poisoned")
8828 .get(&key)
8829 .cloned()
8830 {
8831 return cached;
8832 }
8833 let resolved = (|| {
8834 let name = class.child_by_field_name("name")?;
8835 let identifier = if name.kind() == "template_type" {
8836 node_text(name.child_by_field_name("name")?, source).to_string()
8837 } else {
8838 let mut components = Vec::new();
8839 append_cpp_name_components(name, source, &mut components)?;
8840 components.last()?.clone()
8841 };
8842 let visible = self
8843 .visible_identifier_candidates(file, &identifier)
8844 .cloned()
8845 .collect::<Vec<_>>();
8846 let mut visible = visible;
8847 for candidate in
8848 self.visible_by_file
8849 .get(file)
8850 .into_iter()
8851 .flatten()
8852 .filter(|candidate| {
8853 self.cpp_template_metadata
8854 .get(candidate)
8855 .is_some_and(|metadata| metadata.primary_name == identifier)
8856 })
8857 {
8858 if !visible
8859 .iter()
8860 .any(|existing| same_logical_symbol(existing, candidate))
8861 {
8862 visible.push(candidate.clone());
8863 }
8864 }
8865 let cpp_source = self.cpp_source();
8868 let candidates = visible
8869 .iter()
8870 .filter(|candidate| {
8871 candidate.source() == file
8872 && candidate.is_class()
8873 && !declared_type_alias(&cpp_source, candidate)
8874 && self.cpp.ranges(candidate).iter().any(|range| {
8875 range.start_byte <= class.start_byte()
8876 && class.end_byte() <= range.end_byte
8877 })
8878 })
8879 .collect::<Vec<_>>();
8880 let owner = if name.kind() == "template_type" {
8881 let expected = normalize_cpp_whitespace(node_text(name, source));
8882 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8883 let exact = candidates
8884 .iter()
8885 .copied()
8886 .filter(|candidate| {
8887 candidate
8888 .fq()
8889 .segments()
8890 .iter()
8891 .rev()
8892 .find_map(|&segment| {
8893 let (text, kind) = interner.resolve(segment);
8894 matches!(
8895 kind,
8896 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8897 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8898 )
8899 .then_some(text)
8900 })
8901 .is_some_and(|text| text == expected)
8902 })
8903 .collect::<Vec<_>>();
8904 unique_logical_type_candidate(exact)
8905 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
8906 } else {
8907 unique_logical_type_candidate(candidates)?
8908 };
8909 Some(canonical_cpp_scope_components(&owner))
8910 })();
8911 self.indexed_structural_class_scopes
8912 .lock()
8913 .expect("C++ indexed structural-class scope cache poisoned")
8914 .insert(key, resolved.clone());
8915 resolved
8916 }
8917
8918 pub fn indexed_enclosing_owner_scope(
8919 &self,
8920 analyzer: &CppGraphSource<'_>,
8921 file: &ProjectFile,
8922 node: Node<'_>,
8923 ) -> Option<Vec<String>> {
8924 let anchor = std::iter::successors(Some(node), |current| current.parent())
8925 .find(|current| {
8926 matches!(
8927 current.kind(),
8928 "function_definition"
8929 | "class_specifier"
8930 | "struct_specifier"
8931 | "union_specifier"
8932 )
8933 })
8934 .unwrap_or(node);
8935 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
8936 if let Some(cached) = self
8937 .indexed_enclosing_owner_scopes
8938 .lock()
8939 .expect("C++ indexed enclosing-owner scope cache poisoned")
8940 .get(&key)
8941 .cloned()
8942 {
8943 return cached;
8944 }
8945 let resolved = (|| {
8946 let range = Range {
8947 start_byte: node.start_byte(),
8948 end_byte: node.end_byte(),
8949 start_line: node.start_position().row,
8950 end_line: node.end_position().row,
8951 };
8952 let start = analyzer.enclosing_code_unit(file, &range)?;
8953 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
8954 start,
8955 |unit| self.cached_precise_parent_of(analyzer, unit),
8956 )
8957 .find(|unit| {
8958 unit.is_class()
8959 && !analyzer
8960 .type_alias_provider()
8961 .is_some_and(|provider| provider.is_type_alias(unit))
8962 })?;
8963 Some(canonical_cpp_scope_components(&owner))
8964 })();
8965 self.indexed_enclosing_owner_scopes
8966 .lock()
8967 .expect("C++ indexed enclosing-owner scope cache poisoned")
8968 .insert(key, resolved.clone());
8969 resolved
8970 }
8971
8972 fn cached_precise_parent_of(
8973 &self,
8974 analyzer: &CppGraphSource<'_>,
8975 code_unit: &CodeUnit,
8976 ) -> Option<CodeUnit> {
8977 if let Some(cached) = self
8978 .precise_parent_cache
8979 .lock()
8980 .expect("C++ precise-parent cache poisoned")
8981 .get(code_unit)
8982 .cloned()
8983 {
8984 return cached;
8985 }
8986 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
8987 self.precise_parent_cache
8988 .lock()
8989 .expect("C++ precise-parent cache poisoned")
8990 .insert(code_unit.clone(), resolved.clone());
8991 resolved
8992 }
8993
8994 pub fn callable_is_constructor_declaration(
8995 &self,
8996 analyzer: &CppGraphSource<'_>,
8997 candidate: &CodeUnit,
8998 ) -> bool {
8999 if !candidate.is_function() {
9000 return false;
9001 }
9002 if analyzer
9003 .declaration_source_properties(candidate)
9004 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9005 {
9006 return true;
9007 }
9008 let metadata = analyzer.signature_metadata(candidate);
9009 !metadata.is_empty()
9010 && metadata
9011 .iter()
9012 .all(|signature| signature.return_type_text().is_none())
9013 }
9014
9015 pub fn callable_is_deduction_guide_declaration(
9023 &self,
9024 analyzer: &CppGraphSource<'_>,
9025 candidate: &CodeUnit,
9026 ) -> bool {
9027 candidate.is_function()
9028 && analyzer
9029 .declaration_source_properties(candidate)
9030 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_deduction_guide))
9031 }
9032
9033 pub fn callable_is_template_declaration(
9037 &self,
9038 analyzer: &CppGraphSource<'_>,
9039 candidate: &CodeUnit,
9040 ) -> bool {
9041 candidate.is_function()
9042 && analyzer
9043 .declaration_source_properties(candidate)
9044 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_template))
9045 }
9046
9047 pub fn type_name_candidates<'b>(
9048 &'b self,
9049 file: &ProjectFile,
9050 normalized: &str,
9051 ) -> Vec<&'b CodeUnit> {
9052 self.candidate_units(file, normalized, TargetKind::Type)
9053 }
9054
9055 pub fn visible_members_for_owner_name<'b>(
9056 &'b self,
9057 file: &ProjectFile,
9058 owner: &CodeUnit,
9059 name: &str,
9060 ) -> Vec<&'b CodeUnit> {
9061 self.visible_identifier_candidates(file, name)
9062 .filter(|unit| {
9063 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9067 .is_some_and(|parent| parent == owner.fq_name())
9068 })
9069 .collect()
9070 }
9071
9072 pub fn visible_member_for_owner_name(
9073 &self,
9074 file: &ProjectFile,
9075 owner: &CodeUnit,
9076 name: &str,
9077 ) -> VisibleMemberResolution {
9078 let candidates = self.visible_members_for_owner_name(file, owner, name);
9079 let mut callables = Vec::new();
9080 let mut non_callable = None;
9081 for candidate in candidates {
9082 if candidate.is_function() {
9083 callables.push(candidate.clone());
9084 } else if non_callable.is_none() {
9085 non_callable = Some(candidate.clone());
9086 }
9087 }
9088 match (callables.is_empty(), non_callable) {
9089 (false, None) => VisibleMemberResolution::Callable(callables),
9090 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9091 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9092 (true, None) => VisibleMemberResolution::Missing,
9093 }
9094 }
9095
9096 fn field_declared_type_fact(
9097 &self,
9098 analyzer: &CppGraphSource<'_>,
9099 field: &CodeUnit,
9100 ) -> Option<DeclaredFieldTypeFact> {
9101 if let Some(cached) = self
9102 .field_type_facts
9103 .lock()
9104 .expect("C++ field type fact cache poisoned")
9105 .get(field)
9106 .cloned()
9107 {
9108 return cached;
9109 }
9110 let decoded = decode_field_declared_type_fact(analyzer, field);
9111 self.field_type_facts
9112 .lock()
9113 .expect("C++ field type fact cache poisoned")
9114 .insert(field.clone(), decoded.clone());
9115 decoded
9116 }
9117
9118 fn structured_alias_target(
9119 &self,
9120 analyzer: &CppGraphSource<'_>,
9121 unit: &CodeUnit,
9122 ) -> Option<StructuredAliasTarget> {
9123 if let Some(cached) = self
9124 .structured_alias_targets
9125 .lock()
9126 .expect("C++ structured alias target cache poisoned")
9127 .get(unit)
9128 .cloned()
9129 {
9130 return cached;
9131 }
9132 let decoded = decode_structured_alias_target(analyzer, unit);
9133 self.structured_alias_targets
9134 .lock()
9135 .expect("C++ structured alias target cache poisoned")
9136 .insert(unit.clone(), decoded.clone());
9137 decoded
9138 }
9139
9140 pub fn type_candidates<'b>(
9141 &'b self,
9142 file: &ProjectFile,
9143 normalized: &str,
9144 ) -> Vec<&'b CodeUnit> {
9145 let mut candidates = self
9146 .candidate_units(file, normalized, TargetKind::Type)
9147 .into_iter()
9148 .filter(|unit| {
9149 unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9150 })
9151 .collect::<Vec<_>>();
9152 dedup_unit_refs(&mut candidates);
9153 candidates
9154 }
9155
9156 pub fn named_candidates_for_normalized<'b>(
9157 &'b self,
9158 file: &ProjectFile,
9159 normalized: &str,
9160 kind: TargetKind,
9161 ) -> Vec<&'b CodeUnit> {
9162 let mut candidates = self
9163 .candidate_units(file, normalized, kind)
9164 .into_iter()
9165 .filter(|unit| {
9166 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9167 })
9168 .collect::<Vec<_>>();
9169 dedup_unit_refs(&mut candidates);
9170 candidates
9171 }
9172
9173 pub fn candidate_units<'b>(
9174 &'b self,
9175 file: &ProjectFile,
9176 normalized: &str,
9177 kind: TargetKind,
9178 ) -> Vec<&'b CodeUnit> {
9179 if normalized.contains("::") {
9180 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9189 brokk_bifrost_core::analyzer::Language::Cpp,
9190 normalized,
9191 )
9192 .pop() else {
9193 return Vec::new();
9194 };
9195 let fqns = cpp_reference_fqn_candidates(normalized, kind);
9196 return self
9197 .visible_identifier_candidates(file, &identifier)
9198 .filter(|unit| {
9199 #[cfg(any(test, feature = "test-support"))]
9200 self.qualified_candidate_inspections
9201 .fetch_add(1, Ordering::Relaxed);
9202 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9203 || canonical_cpp_name_matches(unit, normalized)
9204 })
9205 .collect();
9206 }
9207 self.visible_identifier_candidates(file, normalized)
9208 .collect()
9209 }
9210
9211 #[cfg(any(test, feature = "test-support"))]
9212 pub fn reset_qualified_candidate_inspections(&self) {
9213 self.qualified_candidate_inspections
9214 .store(0, Ordering::Relaxed);
9215 }
9216
9217 #[cfg(any(test, feature = "test-support"))]
9218 pub fn qualified_candidate_inspections(&self) -> usize {
9219 self.qualified_candidate_inspections.load(Ordering::Relaxed)
9220 }
9221
9222 #[cfg(any(test, feature = "test-support"))]
9223 pub fn visibility_identifier_lookup_count(&self) -> usize {
9224 self.visibility_identifier_lookup_count
9225 }
9226
9227 #[cfg(any(test, feature = "test-support"))]
9228 pub fn visibility_identifier_batch_count(&self) -> usize {
9229 self.visibility_identifier_batch_count
9230 }
9231
9232 #[cfg(any(test, feature = "test-support"))]
9233 pub fn reset_target_preserving_type_resolution_count(&self) {
9234 self.target_preserving_type_resolution_count
9235 .store(0, Ordering::Relaxed);
9236 }
9237
9238 #[cfg(any(test, feature = "test-support"))]
9239 pub fn target_preserving_type_resolution_count(&self) -> usize {
9240 self.target_preserving_type_resolution_count
9241 .load(Ordering::Relaxed)
9242 }
9243
9244 #[cfg(any(test, feature = "test-support"))]
9245 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9246 self.visible_parser_alias_name_set_build_count
9247 .load(Ordering::Relaxed)
9248 }
9249}
9250
9251#[derive(Default)]
9252struct IncludeGraph {
9253 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9254}
9255
9256impl IncludeGraph {
9257 fn extend_with<F>(
9258 &mut self,
9259 root: &ProjectFile,
9260 cancellation: Option<&CancellationToken>,
9261 targets_for: &mut F,
9262 ) where
9263 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9264 {
9265 let mut stack = vec![root.clone()];
9266 while let Some(file) = stack.pop() {
9267 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9268 break;
9269 }
9270 if self.targets_by_file.contains_key(&file) {
9271 continue;
9272 }
9273 let targets = targets_for(&file);
9274 stack.extend(targets.iter().cloned());
9275 self.targets_by_file.insert(file, targets);
9276 }
9277 }
9278
9279 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9280 self.targets_by_file.keys()
9281 }
9282
9283 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9284 self.targets_by_file
9285 .get(file)
9286 .map(Vec::as_slice)
9287 .unwrap_or_default()
9288 }
9289
9290 fn reachable_files(
9291 &self,
9292 root: &ProjectFile,
9293 cancellation: Option<&CancellationToken>,
9294 ) -> HashSet<ProjectFile> {
9295 let mut pending = vec![root.clone()];
9296 let mut visited = HashSet::default();
9297 while let Some(file) = pending.pop() {
9298 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9299 break;
9300 }
9301 if visited.insert(file.clone()) {
9302 pending.extend(self.targets(&file).iter().cloned());
9303 }
9304 }
9305 visited
9306 }
9307}
9308
9309fn build_bounded_visible_declarations(
9310 cpp: &dyn CppSource,
9311 token: QueryToken<'_>,
9312 analyzer: &CppGraphSource<'_>,
9313 roots: &HashSet<ProjectFile>,
9314 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9315 cancellation: Option<&CancellationToken>,
9316 stats: &mut BoundedVisibilityStats,
9317) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9318 let mut candidates_by_identifier = HashMap::default();
9319 let mut declarations_by_source_and_reading: HashMap<
9320 (ProjectFile, bool),
9321 BoundedVisibilityDeclarations,
9322 > = HashMap::default();
9323 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9324 roots
9325 .iter()
9326 .map(|root| {
9327 let reading_is_c = analyzer.reference_uses_c_semantics(root);
9328 let root_declarations = declarations_by_source_and_reading
9329 .entry((root.clone(), reading_is_c))
9330 .or_insert_with(|| {
9331 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9332 });
9333 let mut visible = root_declarations
9334 .all
9335 .iter()
9336 .cloned()
9337 .collect::<HashSet<_>>();
9338 let mut pending_names = HashSet::default();
9339 if let Some(prepared) = cpp.prepared_syntax(token, root) {
9340 let mut cursor = prepared.tree().walk();
9345 let mut pending_nodes = vec![prepared.tree().root_node()];
9346 while let Some(node) = pending_nodes.pop() {
9347 if matches!(
9348 node.kind(),
9349 "identifier"
9350 | "type_identifier"
9351 | "field_identifier"
9352 | "namespace_identifier"
9353 ) {
9354 pending_names.insert(node_text(node, prepared.source()).to_string());
9355 }
9356 if node.kind() == "preproc_arg" {
9357 for reference in
9358 object_macro_replacement_type_references(node, prepared.source())
9359 {
9360 pending_names.extend(reference.components);
9361 }
9362 }
9363 pending_nodes.extend(node.named_children(&mut cursor));
9364 }
9365 }
9366 stats.root_names += pending_names.len();
9367 let mut completed_names = HashSet::default();
9368 while !pending_names.is_empty() {
9369 stats.rounds += 1;
9370 let round_names = std::mem::take(&mut pending_names);
9371 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9372 HashMap::default();
9373 let mut identifiers = Vec::new();
9374 for identifier in round_names {
9375 if !completed_names.insert(identifier.clone())
9376 || cancellation.is_some_and(CancellationToken::is_cancelled)
9377 {
9378 continue;
9379 }
9380 identifiers.push(identifier);
9381 }
9382 let missing_identifiers = identifiers
9383 .iter()
9384 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9385 .cloned()
9386 .collect::<HashSet<_>>();
9387 if !missing_identifiers.is_empty() {
9388 let lookup_started = Instant::now();
9389 let mut candidates = cpp
9390 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9391 stats.lookup_elapsed += lookup_started.elapsed();
9392 stats.identifier_lookups += missing_identifiers.len();
9393 stats.identifier_batches += 1;
9394 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9395 break;
9396 }
9397 for identifier in missing_identifiers {
9398 let units = candidates.remove(&identifier).unwrap_or_default();
9399 let candidate_count = units.len();
9400 let candidate_sources = units
9401 .into_iter()
9402 .map(|unit| unit.source().clone())
9403 .collect::<HashSet<_>>();
9404 candidates_by_identifier
9405 .insert(identifier, (candidate_sources, candidate_count));
9406 }
9407 }
9408 for identifier in identifiers {
9409 let (candidate_sources, candidate_count) = candidates_by_identifier
9410 .get(&identifier)
9411 .expect("every missing identifier was inserted after the batch lookup");
9412 stats.candidate_units += *candidate_count;
9413 for source in candidate_sources.iter().cloned() {
9414 if source != *root
9415 && visible_sources
9416 .get(root)
9417 .is_some_and(|files| files.contains(&source))
9418 {
9419 requested_names_by_source
9420 .entry(source)
9421 .or_default()
9422 .insert(identifier.clone());
9423 }
9424 }
9425 }
9426 stats.candidate_sources += requested_names_by_source.len();
9427 for (source, requested_names) in requested_names_by_source {
9428 let declarations = declarations_by_source_and_reading
9429 .entry((source.clone(), reading_is_c))
9430 .or_insert_with(|| {
9431 bounded_visibility_declarations(
9432 cpp,
9433 analyzer,
9434 &source,
9435 reading_is_c,
9436 stats,
9437 )
9438 });
9439 let mut selected = HashSet::default();
9440 for name in requested_names {
9441 if let Some(units) = declarations.by_identifier.get(&name) {
9442 selected.extend(units.iter().cloned());
9443 }
9444 }
9445 for unit in selected {
9446 stats.selected_units += 1;
9447 let dependency_names = dependency_names_by_unit
9448 .entry(unit.clone())
9449 .or_insert_with(|| {
9450 let facts_started = Instant::now();
9451 let mut dependency_names = HashSet::default();
9452 if let Some(facts) = analyzer.declaration_source_properties(&unit) {
9453 for fact in facts {
9454 for name in fact.dependency_type_names {
9455 stats.dependency_source_names += 1;
9456 #[cfg(any(test, feature = "test-support"))]
9457 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9458 .with(|count| count.set(count.get() + 1));
9459 dependency_names.insert(name);
9460 }
9461 }
9462 stats.dependency_source_elapsed += facts_started.elapsed();
9463 }
9464 dependency_names
9465 });
9466 for name in dependency_names.iter() {
9467 if !completed_names.contains(name) && pending_names.insert(name.clone())
9468 {
9469 stats.dependency_names += 1;
9470 }
9471 }
9472 visible.insert(unit);
9473 }
9474 }
9475 }
9476 (root.clone(), visible)
9477 })
9478 .collect()
9479}
9480
9481struct BoundedVisibilityDeclarations {
9482 all: Vec<CodeUnit>,
9483 by_identifier: HashMap<String, Vec<CodeUnit>>,
9484}
9485
9486fn bounded_visibility_declarations(
9487 cpp: &dyn CppSource,
9488 analyzer: &CppGraphSource<'_>,
9489 file: &ProjectFile,
9490 c_semantics: bool,
9491 stats: &mut BoundedVisibilityStats,
9492) -> BoundedVisibilityDeclarations {
9493 let declarations_started = Instant::now();
9494 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9495 .into_iter()
9496 .collect::<Vec<_>>();
9497 stats.declaration_elapsed += declarations_started.elapsed();
9498 stats.declaration_reads += 1;
9499 stats.declaration_units += all.len();
9500
9501 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9502 for unit in &all {
9503 by_identifier
9504 .entry(unit.identifier().to_string())
9505 .or_default()
9506 .push(unit.clone());
9507 if unit.is_class()
9508 && let Some(metadata) = cpp.template_metadata(unit)
9509 && metadata.primary_name != unit.identifier()
9510 {
9511 by_identifier
9512 .entry(metadata.primary_name)
9513 .or_default()
9514 .push(unit.clone());
9515 }
9516 }
9517 BoundedVisibilityDeclarations { all, by_identifier }
9518}
9519
9520#[derive(Default)]
9521struct BoundedVisibilityStats {
9522 rounds: usize,
9523 root_names: usize,
9524 identifier_lookups: usize,
9525 identifier_batches: usize,
9526 candidate_units: usize,
9527 candidate_sources: usize,
9528 declaration_reads: usize,
9529 declaration_units: usize,
9530 selected_units: usize,
9531 dependency_source_names: usize,
9532 dependency_names: usize,
9533 lookup_elapsed: Duration,
9534 declaration_elapsed: Duration,
9535 dependency_source_elapsed: Duration,
9536}
9537
9538fn bounded_visibility_declarations_in_reading(
9539 analyzer: &CppGraphSource<'_>,
9540 file: &ProjectFile,
9541 c_semantics: bool,
9542) -> BTreeSet<CodeUnit> {
9543 #[cfg(any(test, feature = "test-support"))]
9544 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9545 analyzer.declarations_in_reading(file, c_semantics)
9546}
9547
9548#[cfg(any(test, feature = "test-support"))]
9549pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9550 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9551}
9552
9553#[cfg(any(test, feature = "test-support"))]
9554pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9555 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9556}
9557
9558#[cfg(any(test, feature = "test-support"))]
9559pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9560 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9561}
9562
9563#[cfg(any(test, feature = "test-support"))]
9564pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9565 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9566}
9567
9568pub struct VisibilityData {
9569 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9570 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9571}
9572
9573pub fn build_visibility_data<F, R, D>(
9583 roots: &HashSet<ProjectFile>,
9584 cancellation: Option<&CancellationToken>,
9585 mut targets_for: F,
9586 mut reading_is_c_for: R,
9587 mut declarations_for: D,
9588) -> VisibilityData
9589where
9590 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9591 R: FnMut(&ProjectFile) -> bool,
9592 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9593{
9594 let mut include_graph = IncludeGraph::default();
9595 for file in roots {
9596 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9597 break;
9598 }
9599 include_graph.extend_with(file, cancellation, &mut targets_for);
9600 }
9601 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9602 .files()
9603 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9604 .map(|file| (file.clone(), declarations_for(file, false)))
9605 .collect();
9606 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9607 let mut visible_by_file = HashMap::default();
9608 let mut visible_source_files_by_root = HashMap::default();
9609 for file in roots {
9610 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9611 break;
9612 }
9613 let mut visited = HashSet::default();
9614 let mut visible = HashSet::default();
9615 let declarations_by_file = if reading_is_c_for(file) {
9616 for reached in cpp_declarations_by_file.keys() {
9617 if !c_declarations_by_file.contains_key(reached) {
9618 let declarations = declarations_for(reached, true);
9619 c_declarations_by_file.insert(reached.clone(), declarations);
9620 }
9621 }
9622 &c_declarations_by_file
9623 } else {
9624 &cpp_declarations_by_file
9625 };
9626 collect_visible_declarations(
9627 &include_graph,
9628 declarations_by_file,
9629 file,
9630 &mut visited,
9631 &mut visible,
9632 cancellation,
9633 );
9634 visible_by_file.insert(file.clone(), visible);
9635 visible_source_files_by_root.insert(file.clone(), visited);
9636 }
9637 VisibilityData {
9638 visible_by_file,
9639 visible_source_files_by_root,
9640 }
9641}
9642
9643#[derive(Default)]
9662struct OutOfLineOwnerBindingStats {
9663 unseen_owners: usize,
9664 definition_lookups: usize,
9665 admitted: usize,
9666}
9667
9668fn extend_with_out_of_line_owner_bindings(
9669 cpp: &dyn CppSource,
9670 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9671) -> OutOfLineOwnerBindingStats {
9672 let mut stats = OutOfLineOwnerBindingStats::default();
9673 for (file, visible) in visible_by_file.iter_mut() {
9674 let mut unseen_owners: HashSet<String> = visible
9678 .iter()
9679 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9680 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9681 .collect();
9682 if unseen_owners.is_empty() {
9683 continue;
9684 }
9685 for unit in visible.iter().filter(|unit| unit.is_class()) {
9686 unseen_owners.remove(&unit.fq_name());
9687 }
9688 stats.unseen_owners += unseen_owners.len();
9689 stats.definition_lookups += unseen_owners.len();
9690 let admitted = unseen_owners
9691 .iter()
9692 .flat_map(|owner| cpp.definitions(owner))
9693 .filter(CodeUnit::is_class)
9694 .collect::<Vec<_>>();
9695 stats.admitted += admitted.len();
9696 visible.extend(admitted);
9697 }
9698 stats
9699}
9700
9701pub enum VisibleMemberResolution {
9702 Callable(Vec<CodeUnit>),
9703 NonCallable,
9704 AmbiguousKind,
9705 Missing,
9706}
9707
9708#[derive(Clone)]
9709pub enum EnclosingMemberOwnerResolution {
9710 Owner(CodeUnit),
9711 Ambiguous,
9712 Missing,
9713}
9714
9715pub fn resolve_declaring_member_owner(
9716 analyzer: &CppGraphSource<'_>,
9717 visibility: &VisibilityIndex<'_>,
9718 file: &ProjectFile,
9719 receiver_owner: &CodeUnit,
9720 member_name: &str,
9721) -> EnclosingMemberOwnerResolution {
9722 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9723 return EnclosingMemberOwnerResolution::Missing;
9724 };
9725 let Some(receiver_owner) =
9726 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9727 else {
9728 return EnclosingMemberOwnerResolution::Ambiguous;
9729 };
9730 let resolve_level = |frontier: &[CodeUnit]| {
9731 let mut member_owners = Vec::new();
9732 for raw_owner in frontier {
9733 let Some(owner) =
9734 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9735 else {
9736 return EnclosingMemberOwnerResolution::Ambiguous;
9737 };
9738 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9739 if !member.is_field() && !member.is_function() {
9744 continue;
9745 }
9746 let Some(member_owner) = type_owner_of(analyzer, member) else {
9747 return EnclosingMemberOwnerResolution::Ambiguous;
9748 };
9749 if !member_owners
9750 .iter()
9751 .any(|existing| same_visible_symbol(existing, &member_owner))
9752 {
9753 member_owners.push(member_owner);
9754 }
9755 }
9756 }
9757 match member_owners.len() {
9758 0 => EnclosingMemberOwnerResolution::Missing,
9759 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9760 _ => EnclosingMemberOwnerResolution::Ambiguous,
9761 }
9762 };
9763 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9767 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9768 return direct;
9769 }
9770 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9771 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9772 let mut path_matches = Vec::new();
9773 while let Some(raw_owner) = stack.pop() {
9774 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9775 else {
9776 return EnclosingMemberOwnerResolution::Ambiguous;
9777 };
9778 let propagated = propagated_counts.entry(owner.clone()).or_default();
9782 if *propagated == 2 {
9783 continue;
9784 }
9785 *propagated += 1;
9786 match resolve_level(std::slice::from_ref(&owner)) {
9787 EnclosingMemberOwnerResolution::Owner(owner) => {
9788 path_matches.push(owner);
9789 if path_matches.len() == 2 {
9790 return EnclosingMemberOwnerResolution::Ambiguous;
9791 }
9792 }
9793 EnclosingMemberOwnerResolution::Ambiguous => {
9794 return EnclosingMemberOwnerResolution::Ambiguous;
9795 }
9796 EnclosingMemberOwnerResolution::Missing => {
9797 stack.extend(hierarchy.get_direct_ancestors(&owner));
9798 }
9799 }
9800 }
9801 match path_matches.len() {
9802 0 => EnclosingMemberOwnerResolution::Missing,
9803 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9804 _ => unreachable!("base-path matches are capped at one before returning"),
9805 }
9806}
9807
9808pub fn resolve_declaring_callable_owner(
9823 analyzer: &CppGraphSource<'_>,
9824 visibility: &VisibilityIndex<'_>,
9825 file: &ProjectFile,
9826 ordinary: EnclosingMemberOwnerResolution,
9827 member_name: &str,
9828 call_arity: usize,
9829) -> EnclosingMemberOwnerResolution {
9830 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9831 return ordinary;
9832 };
9833 if visibility
9834 .visible_members_for_owner_name(file, ordinary_owner, member_name)
9835 .into_iter()
9836 .any(|unit| {
9837 unit.is_function()
9838 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
9839 })
9840 {
9841 return ordinary;
9842 }
9843
9844 let mut pending = match member_using_declaration_bases(
9845 analyzer,
9846 visibility,
9847 file,
9848 ordinary_owner,
9849 member_name,
9850 ) {
9851 Ok(bases) => bases,
9852 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9853 };
9854 let mut visited = HashSet::default();
9855 let mut introduced_owners = Vec::new();
9856 while let Some(owner) = pending.pop() {
9857 if !visited.insert(owner.clone()) {
9858 continue;
9859 }
9860 let accepts_arity = visibility
9861 .visible_members_for_owner_name(file, &owner, member_name)
9862 .into_iter()
9863 .any(|unit| {
9864 unit.is_function()
9865 && cpp_callable_arity(analyzer, unit)
9866 .is_none_or(|known| known.accepts(call_arity))
9867 });
9868 if accepts_arity {
9869 if !introduced_owners
9870 .iter()
9871 .any(|existing| same_visible_symbol(existing, &owner))
9872 {
9873 introduced_owners.push(owner);
9874 }
9875 continue;
9876 }
9877 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
9878 Ok(bases) => pending.extend(bases),
9879 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9880 }
9881 }
9882 match introduced_owners.as_slice() {
9883 [] => ordinary,
9884 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
9885 _ => EnclosingMemberOwnerResolution::Ambiguous,
9886 }
9887}
9888
9889fn member_using_declaration_bases(
9890 analyzer: &CppGraphSource<'_>,
9891 visibility: &VisibilityIndex<'_>,
9892 file: &ProjectFile,
9893 owner: &CodeUnit,
9894 member_name: &str,
9895) -> Result<Vec<CodeUnit>, ()> {
9896 let Some(facts) = analyzer.declaration_source_properties(owner) else {
9897 return Err(());
9898 };
9899 let scopes: Vec<_> = facts
9900 .iter()
9901 .flat_map(|fact| &fact.member_usings)
9902 .filter(|using| using.member == member_name)
9903 .map(|using| using.scope.join("::"))
9904 .collect();
9905 if scopes.is_empty() {
9906 return Ok(Vec::new());
9907 }
9908 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9909 return Ok(Vec::new());
9910 };
9911 let mut bases = Vec::new();
9912 for raw_ancestor in hierarchy.get_ancestors(owner) {
9913 let Some(ancestor) =
9914 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
9915 else {
9916 return Err(());
9917 };
9918 let qualified = cpp_name_for(&ancestor);
9919 if scopes
9920 .iter()
9921 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
9922 && !bases
9923 .iter()
9924 .any(|existing| same_visible_symbol(existing, &ancestor))
9925 {
9926 bases.push(ancestor);
9927 }
9928 }
9929 Ok(bases)
9930}
9931
9932pub fn lexical_component_tiers<'a>(
9933 components: &'a [String],
9934 global: bool,
9935 lexical_scope: &'a [String],
9936) -> impl Iterator<Item = Vec<String>> + 'a {
9937 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
9938 (0..=first_prefix_len).rev().map(move |prefix_len| {
9939 let mut qualified = Vec::with_capacity(prefix_len + components.len());
9940 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
9941 qualified.extend_from_slice(components);
9942 qualified
9943 })
9944}
9945
9946pub fn build_visible_identifier_index(
9947 analyzer: &CppGraphSource<'_>,
9948 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
9949 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9950 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
9951) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
9952 let mut out = HashMap::default();
9953 for (file, visible) in visible_by_file {
9954 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9955 for unit in visible {
9956 if unit.is_field()
9957 && !visible_source_files_by_root
9958 .get(file)
9959 .is_some_and(|sources| sources.contains(unit.source()))
9960 && cpp_global_field_has_internal_linkage_cached(
9961 analyzer,
9962 global_field_internal_linkage,
9963 unit,
9964 )
9965 {
9966 continue;
9967 }
9968 by_identifier
9969 .entry(unit.identifier().to_string())
9970 .or_default()
9971 .push(unit.clone());
9972 }
9973 for units in by_identifier.values_mut() {
9974 sort_lookup_units(units);
9975 units.dedup();
9976 }
9977 out.insert(file.clone(), by_identifier);
9978 }
9979 out
9980}
9981
9982fn sort_lookup_units(units: &mut [CodeUnit]) {
9983 units.sort_by(|left, right| {
9984 left.fq_name()
9985 .cmp(&right.fq_name())
9986 .then_with(|| left.signature().cmp(&right.signature()))
9987 .then_with(|| left.source().cmp(right.source()))
9988 .then_with(|| left.kind().cmp(&right.kind()))
9989 .then_with(|| {
9990 left.package_segment_count()
9991 .cmp(&right.package_segment_count())
9992 })
9993 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
9994 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
9995 });
9996}
9997
9998fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
9999 let interner = segment_interner();
10000 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10001 let (left_text, left_kind) = interner.resolve(left_id);
10002 let (right_text, right_kind) = interner.resolve(right_id);
10003 let order = left_text
10004 .cmp(right_text)
10005 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10006 if order != CmpOrdering::Equal {
10007 return order;
10008 }
10009 }
10010 left.len().cmp(&right.len())
10011}
10012
10013const fn segment_kind_order(kind: SegmentKind) -> u8 {
10014 match kind {
10015 SegmentKind::Path => 0,
10016 SegmentKind::Package => 1,
10017 SegmentKind::Type => 2,
10018 SegmentKind::Companion => 3,
10019 SegmentKind::Nested => 4,
10020 SegmentKind::Member => 5,
10021 SegmentKind::Unknown => 6,
10022 }
10023}
10024
10025fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10026 let mut deduped = Vec::with_capacity(units.len());
10027 for unit in units.drain(..) {
10028 if !deduped.contains(&unit) {
10029 deduped.push(unit);
10030 }
10031 }
10032 *units = deduped;
10033}
10034
10035pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10036 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10040 brokk_bifrost_core::analyzer::Language::Cpp,
10041 reference,
10042 );
10043 if parts.is_empty() {
10044 return Vec::new();
10045 }
10046
10047 let mut candidates = Vec::new();
10048 for package_len in 0..parts.len() {
10049 let package = parts[..package_len].join("::");
10050 let rest = &parts[package_len..];
10051 if rest.is_empty() {
10052 continue;
10053 }
10054 match kind {
10055 TargetKind::Type | TargetKind::Constructor => {
10056 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10057 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10058 }
10059 TargetKind::FreeFunction
10060 | TargetKind::Method
10061 | TargetKind::GlobalField
10062 | TargetKind::MemberField
10063 | TargetKind::Macro => {
10064 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10065 if rest.len() > 1 {
10066 let owner = rest[..rest.len() - 1].join("$");
10067 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10068 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10069 }
10070 }
10071 }
10072 }
10073 candidates
10074}
10075
10076fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10077 let fqn = if package.is_empty() {
10078 short.to_string()
10079 } else {
10080 format!("{package}.{short}")
10081 };
10082 if !out.contains(&fqn) {
10083 out.push(fqn);
10084 }
10085}
10086
10087pub fn infer_cpp_initializer_type(
10088 analyzer: &CppGraphSource<'_>,
10089 visibility: &VisibilityIndex<'_>,
10090 file: &ProjectFile,
10091 source: &str,
10092 node: Node<'_>,
10093) -> Option<CodeUnit> {
10094 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10095 .and_then(|binding| binding.unit)
10096}
10097
10098pub fn infer_cpp_initializer_binding(
10099 analyzer: &CppGraphSource<'_>,
10100 visibility: &VisibilityIndex<'_>,
10101 file: &ProjectFile,
10102 source: &str,
10103 node: Node<'_>,
10104 receiver_resolver: Option<&ReceiverResolver<'_>>,
10105) -> Option<CppScanBinding> {
10106 match node.kind() {
10107 "new_expression" => {
10108 let text = normalize_cpp_whitespace(node_text(node, source));
10109 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
10110 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
10111 let name = normalize_cpp_type_name(type_text);
10112 Some(CppScanBinding::from_type_name(
10113 name.clone(),
10114 visibility.resolve_type(file, &name),
10115 1,
10116 ))
10117 }
10118 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10119 if function.kind() == "field_expression" {
10127 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10128 return resolve_field_method_call_return_binding(
10129 analyzer,
10130 visibility,
10131 file,
10132 source,
10133 function,
10134 arity,
10135 receiver_resolver,
10136 );
10137 }
10138 let function_text = node_text(function, source);
10139 let direct_type_binding = visibility
10140 .resolve_type(file, function_text)
10141 .map(|unit| CppScanBinding::from_unit(unit, 0));
10142 if function.kind() == "template_function" && direct_type_binding.is_some() {
10143 let lexical_namespace = enclosing_namespace_context(node, source);
10144 let arity = visibility.call_arity_evidence(file, node, source).exact();
10145 if let Some(arity) = arity
10146 && let Some(binding) = visibility.resolve_call_return_binding(
10147 analyzer,
10148 file,
10149 function_text,
10150 arity,
10151 lexical_namespace.as_deref(),
10152 direct_type_binding
10153 .as_ref()
10154 .and_then(|binding| binding.unit.as_ref()),
10155 )
10156 {
10157 return Some(binding);
10158 }
10159 let (has_callable, callable_binding) = visibility
10160 .resolve_call_return_binding_without_arity(
10161 analyzer,
10162 file,
10163 function_text,
10164 lexical_namespace.as_deref(),
10165 direct_type_binding
10166 .as_ref()
10167 .and_then(|binding| binding.unit.as_ref()),
10168 );
10169 if let Some(binding) = callable_binding {
10170 return Some(binding);
10171 }
10172 if has_callable {
10173 return None;
10174 }
10175 return direct_type_binding;
10176 }
10177 let arity = visibility.call_arity_evidence(file, node, source).exact();
10182 if let Some(arity) = arity {
10183 let direct_type_binding_for_call = direct_type_binding.clone();
10184 if let Some(binding) = resolve_static_method_call_return_binding(
10185 analyzer, visibility, file, source, function, arity,
10186 )
10187 .or_else(|| {
10188 visibility.resolve_call_return_binding(
10193 analyzer,
10194 file,
10195 function_text,
10196 arity,
10197 enclosing_namespace_context(node, source).as_deref(),
10198 direct_type_binding_for_call
10199 .as_ref()
10200 .and_then(|binding| binding.unit.as_ref()),
10201 )
10202 }) {
10203 return Some(binding);
10204 }
10205 }
10206 direct_type_binding
10207 }),
10208 _ => None,
10209 }
10210}
10211
10212fn resolve_static_method_call_return_binding(
10213 analyzer: &CppGraphSource<'_>,
10214 visibility: &VisibilityIndex<'_>,
10215 file: &ProjectFile,
10216 source: &str,
10217 function: Node<'_>,
10218 arity: usize,
10219) -> Option<CppScanBinding> {
10220 if function.kind() != "qualified_identifier" {
10221 return None;
10222 }
10223 let qualified = normalize_cpp_reference_text(node_text(function, source));
10224 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10231 brokk_bifrost_core::analyzer::Language::Cpp,
10232 &qualified,
10233 );
10234 let (owner_text, member_name) = match parts.split_last() {
10235 Some((member, owner_parts)) if !owner_parts.is_empty() => {
10236 (owner_parts.join("::"), member.clone())
10237 }
10238 _ => {
10239 let scope = function.child_by_field_name("scope")?;
10240 let name = function.child_by_field_name("name")?;
10241 (
10242 node_text(scope, source).to_string(),
10243 node_text(name, source).to_string(),
10244 )
10245 }
10246 };
10247 let owner = visibility.resolve_type(file, &owner_text)?;
10248 let candidates = visibility
10249 .visible_members_for_owner_name(file, &owner, &member_name)
10250 .into_iter()
10251 .filter(|unit| {
10252 unit.is_function()
10253 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10254 })
10255 .cloned()
10256 .collect::<Vec<_>>();
10257 unanimous_return_binding(analyzer, visibility, file, &candidates)
10258}
10259
10260fn resolve_field_method_call_return_binding(
10261 analyzer: &CppGraphSource<'_>,
10262 visibility: &VisibilityIndex<'_>,
10263 file: &ProjectFile,
10264 source: &str,
10265 function: Node<'_>,
10266 arity: usize,
10267 receiver_resolver: Option<&ReceiverResolver<'_>>,
10268) -> Option<CppScanBinding> {
10269 debug_assert_eq!(
10270 function.kind(),
10271 "field_expression",
10272 "the member-call return binding answers only for a field-expression callee"
10273 );
10274 let receiver_resolver = receiver_resolver?;
10275 let field = function.child_by_field_name("field")?;
10276 let member_name = node_text(function_terminal_node(field), source);
10277 let receiver = function
10278 .child_by_field_name("argument")
10279 .or_else(|| function.named_child(0))?;
10280 let owners = receiver_resolver(receiver, source);
10281 let mut candidates = Vec::new();
10282 for owner in owners {
10283 let declaring_owner =
10284 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10285 EnclosingMemberOwnerResolution::Owner(owner) => owner,
10286 EnclosingMemberOwnerResolution::Missing => continue,
10287 EnclosingMemberOwnerResolution::Ambiguous => return None,
10288 };
10289 candidates.extend(
10290 visibility
10291 .visible_members_for_owner_name(file, &declaring_owner, member_name)
10292 .into_iter()
10293 .filter(|unit| {
10294 unit.is_function()
10295 && cpp_callable_arity(analyzer, unit)
10296 .is_none_or(|known| known.accepts(arity))
10297 })
10298 .cloned(),
10299 );
10300 }
10301 unanimous_return_binding(analyzer, visibility, file, &candidates)
10302}
10303
10304fn unanimous_return_binding(
10305 analyzer: &CppGraphSource<'_>,
10306 visibility: &VisibilityIndex<'_>,
10307 file: &ProjectFile,
10308 candidates: &[CodeUnit],
10309) -> Option<CppScanBinding> {
10310 let mut resolved_return: Option<CppScanBinding> = None;
10311 for function in candidates {
10312 let metadata = analyzer.signature_metadata(function);
10313 let return_types = if metadata.is_empty() {
10314 vec![cpp_function_return_type_text(analyzer, function)?]
10315 } else {
10316 metadata
10317 .iter()
10318 .map(|metadata| metadata.return_type_text().map(str::to_string))
10319 .collect::<Option<Vec<_>>>()?
10320 };
10321 for return_text in return_types {
10322 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10323 let name = normalize_cpp_type_name(&return_text);
10324 let binding = CppScanBinding::from_type_name(
10325 name.clone(),
10326 visibility
10327 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10328 indirection,
10329 );
10330 if let Some(existing) = resolved_return.as_ref()
10331 && (existing.indirection != binding.indirection
10332 || match (&existing.unit, &binding.unit) {
10333 (Some(left), Some(right)) => !same_visible_symbol(left, right),
10334 (None, None) => existing.type_name != binding.type_name,
10335 (Some(_), None) | (None, Some(_)) => true,
10336 })
10337 {
10338 return None;
10339 }
10340 resolved_return = Some(binding);
10341 }
10342 }
10343 resolved_return
10344}
10345
10346fn aliases_from_canonical_source(
10347 cpp: &dyn CppSource,
10348 token: QueryToken<'_>,
10349 file: &ProjectFile,
10350) -> Option<Vec<CppAlias>> {
10351 let source = cpp.declaration_source_facts(token, file)?;
10352 Some(
10353 source
10354 .facts
10355 .declarations
10356 .iter()
10357 .filter_map(|fact| {
10358 let alias = fact.file_scope_alias.as_ref()?;
10359 Some(CppAlias {
10360 name: alias.name.clone(),
10361 target: alias.target.clone(),
10362 namespace: alias.namespace.clone(),
10363 })
10364 })
10365 .collect(),
10366 )
10367}
10368
10369pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10370 let mut current = node.parent();
10371 while let Some(parent) = current {
10372 match parent.kind() {
10373 "translation_unit"
10374 | "namespace_definition"
10375 | "declaration_list"
10376 | "linkage_specification" => current = parent.parent(),
10377 "template_declaration" => current = parent.parent(),
10378 _ => return false,
10379 }
10380 }
10381 true
10382}
10383
10384pub fn collect_include_closure(
10385 analyzer: &CppGraphSource<'_>,
10386 include_targets: &IncludeTargetIndex,
10387 file: &ProjectFile,
10388 out: &mut HashSet<ProjectFile>,
10389 cancellation: Option<&CancellationToken>,
10390) {
10391 let mut stack = vec![file.clone()];
10392 while let Some(file) = stack.pop() {
10393 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10394 break;
10395 }
10396 if !out.insert(file.clone()) {
10397 continue;
10398 }
10399 for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
10400 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10401 stack.push(target);
10402 }
10403 }
10404 }
10405}
10406
10407fn collect_visible_declarations(
10408 include_graph: &IncludeGraph,
10409 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10410 file: &ProjectFile,
10411 visited: &mut HashSet<ProjectFile>,
10412 out: &mut HashSet<CodeUnit>,
10413 cancellation: Option<&CancellationToken>,
10414) {
10415 let mut stack = vec![file.clone()];
10416 while let Some(file) = stack.pop() {
10417 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10418 break;
10419 }
10420 if !visited.insert(file.clone()) {
10421 continue;
10422 }
10423 if let Some(declarations) = declarations_by_file.get(&file) {
10424 out.extend(declarations.iter().cloned());
10425 }
10426 stack.extend(include_graph.targets(&file).iter().cloned());
10427 }
10428}
10429
10430fn parse_macro_parameter_list<T>(
10431 replacement: &str,
10432 read: impl FnOnce(Node<'_>, &str) -> T,
10433) -> Option<T> {
10434 let source = format!("void __bifrost_macro_parameters({replacement});");
10435 let mut parser = Parser::new();
10436 parser
10437 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10438 .ok()?;
10439 let tree = parser.parse(&source, None)?;
10440 let root = tree.root_node();
10441 if root.has_error() {
10442 return None;
10443 }
10444 let declaration = root.named_child(0)?;
10445 let declarator = declaration.child_by_field_name("declarator")?;
10446 let parameters = declarator.child_by_field_name("parameters")?;
10447 Some(read(parameters, &source))
10448}
10449
10450fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10451 parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
10452}
10453
10454pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
10457 parse_macro_parameter_list(
10458 replacement,
10459 crate::declarations::cpp_callable_parameter_types,
10460 )
10461}
10462
10463pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
10464 analyzer
10465 .signature_metadata(unit)
10466 .into_iter()
10467 .find_map(|metadata| metadata.callable_arity())
10468}
10469
10470pub fn cpp_callable_parameter_types(
10471 analyzer: &CppGraphSource<'_>,
10472 unit: &CodeUnit,
10473) -> Option<Vec<String>> {
10474 analyzer
10475 .signature_metadata(unit)
10476 .into_iter()
10477 .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
10478}
10479
10480fn merge_compatible_callable_arities(
10481 left: CallableArity,
10482 right: CallableArity,
10483) -> Option<CallableArity> {
10484 let total = left.total();
10485 let left_repeated = left.accepts(total.saturating_add(1));
10486 let right_repeated = right.accepts(right.total().saturating_add(1));
10487 if total != right.total() || left_repeated != right_repeated {
10488 return None;
10489 }
10490 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10491 Some(CallableArity::new(required, total, left_repeated))
10492}
10493
10494fn find_include_activation(
10495 cpp: &dyn CppSource,
10496 token: QueryToken<'_>,
10497 file: &ProjectFile,
10498 prepared: &PreparedSyntaxTree,
10499 donor_source: &ProjectFile,
10500) -> Option<usize> {
10501 let include_targets = cpp.include_target_index();
10502 let mut direct_includes = Vec::new();
10503 let mut nodes = vec![prepared.tree().root_node()];
10504 let reference = CallableReferenceContext {
10507 file,
10508 position: None,
10509 };
10510 while let Some(node) = nodes.pop() {
10511 if node.kind() == "preproc_include" {
10512 if callable_preprocessor_context_is_visible_for_reference(
10513 node,
10514 prepared.source(),
10515 &reference,
10516 ) {
10517 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10518 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10519 file,
10520 include,
10521 include_targets,
10522 )) {
10523 direct_includes.push((node.end_byte(), target));
10524 }
10525 }
10526 }
10527 continue;
10528 }
10529 push_named_children_reversed(node, &mut nodes);
10530 }
10531 direct_includes.sort_by_key(|(activation, _)| *activation);
10532 let mut known_missing = HashSet::default();
10533 direct_includes
10534 .into_iter()
10535 .find(|(_, direct)| {
10536 unconditional_include_reaches(
10537 cpp,
10538 token,
10539 include_targets,
10540 direct,
10541 donor_source,
10542 file,
10543 &mut known_missing,
10544 )
10545 })
10546 .map(|(activation, _)| activation)
10547}
10548
10549fn find_conditional_include_projection_index(
10550 cpp: &dyn CppSource,
10551 token: QueryToken<'_>,
10552 file: &ProjectFile,
10553 prepared: &PreparedSyntaxTree,
10554 on_state: &dyn Fn(),
10555) -> ConditionalIncludeProjectionIndex {
10556 let reference_is_c = reference_uses_c_semantics(cpp, file);
10557 let include_targets = cpp.include_target_index();
10558 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10559 HashMap::default();
10560 let mut pending = Vec::new();
10561 let mut nodes = vec![prepared.tree().root_node()];
10562 while let Some(node) = nodes.pop() {
10563 if node.kind() == "preproc_include" {
10564 let Some(required_guards) =
10565 include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10566 else {
10567 continue;
10568 };
10569 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10570 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10571 file,
10572 include,
10573 include_targets,
10574 )) else {
10575 continue;
10576 };
10577 pending.push((target, node.end_byte(), required_guards.clone()));
10578 }
10579 continue;
10580 }
10581 push_named_children_reversed(node, &mut nodes);
10582 }
10583
10584 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10596 HashMap::default();
10597 while let Some((current_file, activation_byte, path)) = pending.pop() {
10598 let guard_sets = expanded
10599 .entry((current_file.clone(), activation_byte))
10600 .or_default();
10601 if guard_sets
10605 .iter()
10606 .any(|existing| existing.is_subset(&path.all))
10607 {
10608 continue;
10609 }
10610 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10611 .drain(..)
10612 .partition(|existing| path.all.is_subset(existing));
10613 *guard_sets = kept;
10614 guard_sets.push(path.all.clone());
10615 if !evicted.is_empty()
10616 && let Some(projections) = projections_by_source.get_mut(¤t_file)
10617 {
10618 projections.retain(|projection| {
10619 projection.activation_byte != activation_byte
10620 || !evicted.contains(&projection.required_guards)
10621 });
10622 }
10623 on_state();
10624
10625 projections_by_source
10628 .entry(current_file.clone())
10629 .or_default()
10630 .push(ConditionalIncludeProjection {
10631 activation_byte,
10632 required_guards: path.all.clone(),
10633 partial_guards: path.partial.clone(),
10634 });
10635
10636 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10637 continue;
10638 };
10639 let mut nodes = vec![current_prepared.tree().root_node()];
10640 while let Some(node) = nodes.pop() {
10641 if node.kind() == "preproc_include" {
10642 let Some(include_guards) = include_directive_guard_requirements(
10643 node,
10644 current_prepared.source(),
10645 reference_is_c,
10646 ) else {
10647 continue;
10648 };
10649 let Some(reached) = path.merged(&include_guards) else {
10650 continue;
10651 };
10652 for (include, _) in
10653 include_path_from_node(node, current_prepared.source()).into_iter()
10654 {
10655 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10656 ¤t_file,
10657 include,
10658 include_targets,
10659 )) else {
10660 continue;
10661 };
10662 pending.push((target, activation_byte, reached.clone()));
10663 }
10664 continue;
10665 }
10666 push_named_children_reversed(node, &mut nodes);
10667 }
10668 }
10669
10670 projections_by_source
10671 .into_iter()
10672 .map(|(source, mut projections)| {
10673 projections.sort_by_key(|projection| projection.activation_byte);
10674 (source, Arc::from(projections))
10675 })
10676 .collect()
10677}
10678
10679#[allow(clippy::too_many_arguments)]
10685fn find_conditional_include_projection_for_source(
10686 cpp: &dyn CppSource,
10687 token: QueryToken<'_>,
10688 file: &ProjectFile,
10689 prepared: &PreparedSyntaxTree,
10690 donor_source: &ProjectFile,
10691 admission: IncludePathAdmission,
10692 reference_guards: Option<&HashSet<PreprocessorGuard>>,
10693 reference_byte: usize,
10694 on_state: &dyn Fn(),
10695) -> bool {
10696 let Some(reference_guards) = reference_guards else {
10697 return false;
10698 };
10699 let reference_is_c = reference_uses_c_semantics(cpp, file);
10700 let include_targets = cpp.include_target_index();
10701 let mut pending = Vec::new();
10702 let mut nodes = vec![prepared.tree().root_node()];
10703 while let Some(node) = nodes.pop() {
10704 if node.kind() == "preproc_include" {
10705 let Some(required_guards) =
10706 include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10707 else {
10708 continue;
10709 };
10710 if node.end_byte() > reference_byte
10711 || !admission.admits(
10712 &required_guards.all,
10713 &required_guards.partial,
10714 Some(reference_guards),
10715 )
10716 {
10717 continue;
10718 }
10719 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10720 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10721 file,
10722 include,
10723 include_targets,
10724 )) else {
10725 continue;
10726 };
10727 if &target == donor_source {
10728 return true;
10729 }
10730 pending.push((target, required_guards.clone()));
10731 }
10732 continue;
10733 }
10734 push_named_children_reversed(node, &mut nodes);
10735 }
10736
10737 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
10738 while let Some((current_file, path)) = pending.pop() {
10739 let guard_sets = expanded.entry(current_file.clone()).or_default();
10740 if guard_sets.contains(&path.all) {
10741 continue;
10742 }
10743 guard_sets.push(path.all.clone());
10744 on_state();
10745
10746 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10747 continue;
10748 };
10749 let mut nodes = vec![current_prepared.tree().root_node()];
10750 while let Some(node) = nodes.pop() {
10751 if node.kind() == "preproc_include" {
10752 let Some(include_guards) = include_directive_guard_requirements(
10753 node,
10754 current_prepared.source(),
10755 reference_is_c,
10756 ) else {
10757 continue;
10758 };
10759 let Some(reached) = path.merged(&include_guards) else {
10760 continue;
10761 };
10762 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
10763 continue;
10764 }
10765 for (include, _) in
10766 include_path_from_node(node, current_prepared.source()).into_iter()
10767 {
10768 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10769 ¤t_file,
10770 include,
10771 include_targets,
10772 )) else {
10773 continue;
10774 };
10775 if &target == donor_source {
10776 return true;
10777 }
10778 pending.push((target, reached.clone()));
10779 }
10780 continue;
10781 }
10782 push_named_children_reversed(node, &mut nodes);
10783 }
10784 }
10785 false
10786}
10787
10788pub fn cpp_include_closure_reaches(
10801 cpp: &dyn CppSource,
10802 token: QueryToken<'_>,
10803 translation_unit: &ProjectFile,
10804 header: &ProjectFile,
10805) -> bool {
10806 unconditional_include_reaches(
10807 cpp,
10808 token,
10809 cpp.include_target_index(),
10810 translation_unit,
10811 header,
10812 translation_unit,
10813 &mut HashSet::default(),
10814 )
10815}
10816
10817fn unconditional_include_reaches(
10818 cpp: &dyn CppSource,
10819 token: QueryToken<'_>,
10820 include_targets: &IncludeTargetIndex,
10821 first: &ProjectFile,
10822 donor_source: &ProjectFile,
10823 reference_file: &ProjectFile,
10824 known_missing: &mut HashSet<ProjectFile>,
10825) -> bool {
10826 if first == donor_source {
10827 return true;
10828 }
10829 if known_missing.contains(first) {
10830 return false;
10831 }
10832 let reference_is_c = reference_file
10833 .rel_path()
10834 .extension()
10835 .and_then(|extension| extension.to_str())
10836 == Some("c");
10837 if let Some(reaches) =
10838 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
10839 {
10840 return reaches;
10841 }
10842 let mut visited = HashSet::default();
10843 let mut files = vec![first.clone()];
10844 let reference = CallableReferenceContext {
10847 file: reference_file,
10848 position: None,
10849 };
10850 while let Some(file) = files.pop() {
10851 if file == *donor_source {
10852 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
10853 return true;
10854 }
10855 if known_missing.contains(&file) || !visited.insert(file.clone()) {
10856 continue;
10857 }
10858 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
10859 continue;
10860 };
10861 let mut cursor = prepared.tree().walk();
10863 let mut nodes = vec![prepared.tree().root_node()];
10864 while let Some(node) = nodes.pop() {
10865 if node.kind() == "preproc_include" {
10866 if callable_preprocessor_context_is_visible_for_reference(
10867 node,
10868 prepared.source(),
10869 &reference,
10870 ) {
10871 for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
10872 {
10873 if let Some(target) = unique_include_target(
10874 resolve_include_targets_with_index(&file, include, include_targets),
10875 ) {
10876 files.push(target);
10877 }
10878 }
10879 }
10880 continue;
10881 }
10882 let first_pushed = nodes.len();
10883 nodes.extend(node.named_children(&mut cursor));
10884 nodes[first_pushed..].reverse();
10885 }
10886 }
10887 known_missing.extend(visited);
10888 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
10889 false
10890}
10891
10892fn declaration_guard_requirements(
10893 analyzer: &CppGraphSource<'_>,
10894 cpp: &dyn CppSource,
10895 candidate: &CodeUnit,
10896) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
10897 declaration_guard_sites(analyzer, cpp, candidate, None)
10898 .into_iter()
10899 .map(|site| (site.byte, site.guards))
10900 .collect()
10901}
10902
10903struct DeclarationGuardSite {
10906 byte: usize,
10908 guards: HashSet<PreprocessorGuard>,
10910 completed_family_before_reference: bool,
10914}
10915
10916fn declaration_guard_sites(
10919 analyzer: &CppGraphSource<'_>,
10920 cpp: &dyn CppSource,
10921 candidate: &CodeUnit,
10922 reference_byte: Option<usize>,
10923) -> Vec<DeclarationGuardSite> {
10924 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
10925 return Vec::new();
10926 };
10927 let root = prepared.tree().root_node();
10928 analyzer
10929 .declaration_source_occurrences(candidate)
10930 .into_iter()
10931 .flatten()
10932 .filter_map(|occurrence| {
10933 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
10934 Some(DeclarationGuardSite {
10935 byte: occurrence.range.start_byte,
10936 guards,
10937 completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
10938 root.descendant_for_byte_range(
10939 occurrence.range.start_byte,
10940 occurrence.range.end_byte,
10941 )
10942 .is_some_and(|node| {
10943 declaration_follows_completed_family_before_reference(
10944 node,
10945 prepared.source(),
10946 reference_byte,
10947 )
10948 })
10949 }),
10950 })
10951 })
10952 .collect()
10953}
10954
10955fn declaration_follows_completed_family_before_reference(
10974 node: Node<'_>,
10975 source: &str,
10976 reference_byte: usize,
10977) -> bool {
10978 callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
10979}
10980
10981pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
10984 let mut completion_byte = 0;
10985 let mut ancestor = node.parent();
10986 while let Some(conditional) = ancestor {
10987 if matches!(
10988 conditional.kind(),
10989 "preproc_if" | "preproc_ifdef" | "preproc_elif"
10990 ) && !is_file_covering_include_guard(conditional, source)
10991 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
10992 && preprocessor_conditional_contains_descendant(conditional, node)
10993 {
10994 let family = preprocessor_conditional_family_root(conditional);
10995 if !preprocessor_conditional_family_has_terminal_else(family) {
10996 return None;
10997 }
10998 completion_byte = completion_byte.max(family.end_byte());
10999 }
11000 ancestor = conditional.parent();
11001 }
11002 Some(completion_byte)
11003}
11004
11005fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11006 analyzer
11007 .ranges(candidate)
11008 .into_iter()
11009 .map(|range| range.start_byte)
11010 .min()
11011}
11012
11013fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11019 let (first, rest) = contexts.split_first()?;
11020 Some(
11021 first
11022 .defined_macros
11023 .iter()
11024 .filter(|name| {
11025 rest.iter()
11026 .all(|context| context.defined_macros.contains(*name))
11027 })
11028 .cloned()
11029 .collect(),
11030 )
11031}
11032
11033pub fn guard_requirements_hold_at_reference(
11034 required: &HashSet<PreprocessorGuard>,
11035 reference: Option<&HashSet<PreprocessorGuard>>,
11036) -> bool {
11037 reference.is_some_and(|active| {
11038 required
11039 .iter()
11040 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11041 })
11042}
11043
11044fn preprocessor_guard_holds_at_reference(
11045 required: &PreprocessorGuard,
11046 active: &HashSet<PreprocessorGuard>,
11047) -> bool {
11048 if active.contains(required) {
11049 return true;
11050 }
11051 let active_expression = BooleanGuardExpression::all(
11052 active
11053 .iter()
11054 .filter_map(PreprocessorGuard::as_boolean_expression),
11055 );
11056 required
11057 .as_boolean_expression()
11058 .is_some_and(|required| active_expression.implies(&required))
11059}
11060
11061fn guards_compatible_at_reference(
11066 declaration: &HashSet<PreprocessorGuard>,
11067 reference: Option<&HashSet<PreprocessorGuard>>,
11068) -> bool {
11069 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11070}
11071
11072pub fn preprocessor_conditional_family_range(
11081 root: Node<'_>,
11082 start_byte: usize,
11083 end_byte: usize,
11084) -> Option<(usize, usize)> {
11085 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11086 preprocessor_conditional_family_node(node)
11087 .map(|family| (family.start_byte(), family.end_byte()))
11088}
11089
11090pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11091 let mut ancestor = Some(node);
11092 while let Some(current) = ancestor {
11093 if is_preprocessor_conditional(current)
11094 && preprocessor_conditional_contains_descendant(current, node)
11095 {
11096 return Some(preprocessor_conditional_family_root(current));
11097 }
11098 ancestor = current.parent();
11099 }
11100 None
11101}
11102
11103pub(crate) fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
11104 let mut ancestor = node.parent();
11105 while let Some(current) = ancestor {
11106 if is_preprocessor_conditional(current)
11107 && preprocessor_conditional_contains_descendant(current, node)
11108 {
11109 let family = preprocessor_conditional_family_root(current);
11110 if preprocessor_conditional_family_has_terminal_else(family) {
11111 return Some(family);
11112 }
11113 }
11114 ancestor = current.parent();
11115 }
11116 None
11117}
11118
11119fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11120 while let Some(parent) = conditional.parent() {
11121 let is_alternative = parent
11122 .child_by_field_name("alternative")
11123 .is_some_and(|alternative| {
11124 alternative.start_byte() == conditional.start_byte()
11125 && alternative.end_byte() == conditional.end_byte()
11126 });
11127 if !is_alternative {
11128 break;
11129 }
11130 conditional = parent;
11131 }
11132 conditional
11133}
11134
11135fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11136 loop {
11137 let Some(alternative) = conditional.child_by_field_name("alternative") else {
11138 return false;
11139 };
11140 match alternative.kind() {
11141 "preproc_else" => return true,
11142 "preproc_elif" => conditional = alternative,
11143 _ => return false,
11144 }
11145 }
11146}
11147
11148fn include_directive_guard_requirements(
11161 node: Node<'_>,
11162 source: &str,
11163 reference_is_c: bool,
11164) -> Option<PreprocessorGuardEnvironment> {
11165 let required = preprocessor_guard_environment_by_family(node, source)?;
11166 let excluded_by_language = required.all.iter().any(|guard| match guard {
11167 PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11168 PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11169 _ => false,
11170 });
11171 (!excluded_by_language).then_some(required)
11172}
11173
11174pub fn preprocessor_guard_environment(
11175 node: Node<'_>,
11176 source: &str,
11177) -> Option<HashSet<PreprocessorGuard>> {
11178 preprocessor_guard_environment_by_family(node, source).map(|environment| environment.all)
11179}
11180
11181#[derive(Clone)]
11192struct PreprocessorGuardEnvironment {
11193 all: HashSet<PreprocessorGuard>,
11194 partial: HashSet<PreprocessorGuard>,
11195}
11196
11197impl PreprocessorGuardEnvironment {
11198 fn merged(&self, other: &Self) -> Option<Self> {
11202 Some(Self {
11203 all: merge_preprocessor_guards(&self.all, &other.all)?,
11204 partial: self.partial.union(&other.partial).cloned().collect(),
11205 })
11206 }
11207}
11208
11209fn preprocessor_guard_environment_by_family(
11210 node: Node<'_>,
11211 source: &str,
11212) -> Option<PreprocessorGuardEnvironment> {
11213 let mut all = HashSet::default();
11214 let mut partial = HashSet::default();
11215 let mut ancestor = node.parent();
11216 while let Some(conditional) = ancestor {
11217 if matches!(
11218 conditional.kind(),
11219 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11220 ) && !is_file_covering_include_guard(conditional, source)
11221 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11222 && preprocessor_conditional_contains_descendant(conditional, node)
11223 {
11224 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11225 match guard {
11226 PreprocessorGuard::Constant(true) => {
11227 ancestor = conditional.parent();
11228 continue;
11229 }
11230 PreprocessorGuard::Constant(false) => return None,
11231 _ => {}
11232 }
11233 if all.contains(&guard.negated()) {
11234 return None;
11235 }
11236 if !preprocessor_conditional_family_has_terminal_else(
11237 preprocessor_conditional_family_root(conditional),
11238 ) {
11239 partial.insert(guard.clone());
11240 }
11241 all.insert(guard);
11242 }
11243 ancestor = conditional.parent();
11244 }
11245 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11246 match guard {
11247 PreprocessorGuard::Constant(true) => {}
11248 PreprocessorGuard::Constant(false) => return None,
11249 _ => {
11250 if all.contains(&guard.negated()) {
11251 return None;
11252 }
11253 partial.insert(guard.clone());
11257 all.insert(guard);
11258 }
11259 }
11260 }
11261 Some(PreprocessorGuardEnvironment { all, partial })
11262}
11263
11264pub(crate) fn callable_preprocessor_guard_requirements(
11265 node: Node<'_>,
11266 source: &str,
11267) -> Option<HashSet<PreprocessorGuard>> {
11268 let mut guards = HashSet::default();
11269 let mut ancestor = node.parent();
11270 while let Some(conditional) = ancestor {
11271 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11272 && !is_file_covering_include_guard(conditional, source)
11273 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11274 && preprocessor_conditional_contains_descendant(conditional, node)
11275 {
11276 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11277 match guard {
11278 PreprocessorGuard::Constant(true) => {}
11279 PreprocessorGuard::Constant(false) => return None,
11280 _ => {
11281 if guards.contains(&guard.negated()) {
11282 return None;
11283 }
11284 guards.insert(guard);
11285 }
11286 }
11287 }
11288 ancestor = conditional.parent();
11289 }
11290 Some(guards)
11291}
11292
11293fn fragmented_statement_preprocessor_guard(
11294 descendant: Node<'_>,
11295 source: &str,
11296) -> Option<PreprocessorGuard> {
11297 let mut ancestor = descendant.parent();
11303 while let Some(statement) = ancestor {
11304 if statement.kind() == "if_statement"
11305 && let (Some(consequence), Some(alternative)) = (
11306 statement.child_by_field_name("consequence"),
11307 statement.child_by_field_name("alternative"),
11308 )
11309 && alternative.start_byte() <= descendant.start_byte()
11310 && descendant.end_byte() <= alternative.end_byte()
11311 {
11312 let mut cursor = consequence.walk();
11313 let openers = consequence
11314 .named_children(&mut cursor)
11315 .filter(|child| {
11316 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11317 && child
11318 .child(child.child_count().saturating_sub(1))
11319 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11320 })
11321 .collect::<Vec<_>>();
11322 if openers.len() != 1 {
11323 ancestor = statement.parent();
11324 continue;
11325 }
11326
11327 let mut terminators = Vec::new();
11328 let mut stack = vec![alternative];
11329 while let Some(node) = stack.pop() {
11330 if node.kind() == "preproc_call"
11331 && node.start_byte() >= descendant.end_byte()
11332 && node
11333 .child_by_field_name("directive")
11334 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11335 {
11336 terminators.push(node);
11337 continue;
11338 }
11339 push_named_children_reversed(node, &mut stack);
11340 }
11341 if terminators.len() == 1 {
11342 return simple_preprocessor_guard(openers[0], source);
11343 }
11344 }
11345 ancestor = statement.parent();
11346 }
11347 None
11348}
11349
11350pub(crate) fn preprocessor_guard_for_descendant(
11351 conditional: Node<'_>,
11352 descendant: Node<'_>,
11353 source: &str,
11354) -> Option<PreprocessorGuard> {
11355 let mut guard = simple_preprocessor_guard(conditional, source)?;
11356 if conditional
11357 .child_by_field_name("alternative")
11358 .is_some_and(|alternative| {
11359 alternative.start_byte() <= descendant.start_byte()
11360 && descendant.end_byte() <= alternative.end_byte()
11361 })
11362 {
11363 let alternative = conditional.child_by_field_name("alternative")?;
11364 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11368 return None;
11369 }
11370 guard = guard.negated();
11371 }
11372 Some(guard)
11373}
11374
11375pub(crate) fn preprocessor_conditional_contains_descendant(
11376 conditional: Node<'_>,
11377 descendant: Node<'_>,
11378) -> bool {
11379 cpp_displaced_preprocessor_boundary(conditional)
11380 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11381}
11382
11383pub fn merge_preprocessor_guards(
11384 left: &HashSet<PreprocessorGuard>,
11385 right: &HashSet<PreprocessorGuard>,
11386) -> Option<HashSet<PreprocessorGuard>> {
11387 let mut merged = left.clone();
11388 for guard in right {
11389 let boolean_negation = guard
11390 .as_boolean_expression()
11391 .map(|expression| expression.negated());
11392 if merged.contains(&guard.negated())
11393 || boolean_negation.is_some_and(|negated| {
11394 merged
11395 .iter()
11396 .filter_map(PreprocessorGuard::as_boolean_expression)
11397 .any(|existing| existing == negated)
11398 })
11399 {
11400 return None;
11401 }
11402 merged.insert(guard.clone());
11403 }
11404 Some(merged)
11405}
11406
11407fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11408 if conditional.kind() == "preproc_ifdef" {
11409 let name = conditional.child_by_field_name("name")?;
11410 let name = node_text(name, source).to_string();
11411 return match conditional.child(0)?.kind() {
11412 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11413 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11414 _ => None,
11415 };
11416 }
11417 let condition = conditional.child_by_field_name("condition")?;
11418 simple_preprocessor_expression_guard(condition, source).or_else(|| {
11419 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11420 node_text(condition, source),
11421 )))
11422 })
11423}
11424
11425fn simple_preprocessor_expression_guard(
11426 expression: Node<'_>,
11427 source: &str,
11428) -> Option<PreprocessorGuard> {
11429 match expression.kind() {
11430 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11431 node_text(expression, source).to_string(),
11432 ))),
11433 "number_literal" => match node_text(expression, source).trim() {
11434 "0" => Some(PreprocessorGuard::Constant(false)),
11435 "1" => Some(PreprocessorGuard::Constant(true)),
11436 _ => None,
11437 },
11438 "preproc_defined" => {
11439 let identifier = (0..expression.named_child_count())
11440 .filter_map(|index| expression.named_child(index))
11441 .find(|child| child.kind() == "identifier")?;
11442 Some(PreprocessorGuard::Defined(
11443 node_text(identifier, source).to_string(),
11444 ))
11445 }
11446 "unary_expression"
11447 if expression
11448 .child_by_field_name("operator")
11449 .is_some_and(|operator| operator.kind() == "!") =>
11450 {
11451 simple_preprocessor_expression_guard(
11452 expression.child_by_field_name("argument")?,
11453 source,
11454 )
11455 .map(|guard| guard.negated())
11456 }
11457 "parenthesized_expression" => (0..expression.named_child_count())
11458 .filter_map(|index| expression.named_child(index))
11459 .next()
11460 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11461 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11462 expression, source,
11463 ))),
11464 _ => None,
11465 }
11466}
11467
11468fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11469 match expression.kind() {
11470 "number_literal" => match node_text(expression, source).trim() {
11471 "0" => BooleanGuardExpression::Constant(false),
11472 "1" => BooleanGuardExpression::Constant(true),
11473 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11474 expression, source,
11475 ))),
11476 },
11477 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11478 "preproc_defined" => {
11479 let identifier = (0..expression.named_child_count())
11480 .filter_map(|index| expression.named_child(index))
11481 .find(|child| child.kind() == "identifier");
11482 identifier.map_or_else(
11483 || {
11484 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11485 expression, source,
11486 )))
11487 },
11488 |identifier| {
11489 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11490 },
11491 )
11492 }
11493 "unary_expression"
11494 if expression
11495 .child_by_field_name("operator")
11496 .is_some_and(|operator| operator.kind() == "!") =>
11497 {
11498 expression.child_by_field_name("argument").map_or_else(
11499 || {
11500 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11501 expression, source,
11502 )))
11503 },
11504 |argument| boolean_preprocessor_expression(argument, source).negated(),
11505 )
11506 }
11507 "parenthesized_expression" => (0..expression.named_child_count())
11508 .filter_map(|index| expression.named_child(index))
11509 .next()
11510 .map_or_else(
11511 || {
11512 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11513 expression, source,
11514 )))
11515 },
11516 |child| boolean_preprocessor_expression(child, source),
11517 ),
11518 "binary_expression" => {
11519 let operands = || {
11520 Some((
11521 boolean_preprocessor_expression(
11522 expression.child_by_field_name("left")?,
11523 source,
11524 ),
11525 boolean_preprocessor_expression(
11526 expression.child_by_field_name("right")?,
11527 source,
11528 ),
11529 ))
11530 };
11531 match expression
11532 .child_by_field_name("operator")
11533 .map(|operator| operator.kind())
11534 {
11535 Some("&&") => operands().map_or_else(
11536 || {
11537 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11538 expression, source,
11539 )))
11540 },
11541 |(left, right)| BooleanGuardExpression::all([left, right]),
11542 ),
11543 Some("||") => operands().map_or_else(
11544 || {
11545 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11546 expression, source,
11547 )))
11548 },
11549 |(left, right)| BooleanGuardExpression::any([left, right]),
11550 ),
11551 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11552 expression, source,
11553 ))),
11554 }
11555 }
11556 _ => {
11557 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
11558 }
11559 }
11560}
11561
11562fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
11563 if targets.len() == 1 {
11564 targets.pop()
11565 } else {
11566 None
11567 }
11568}
11569
11570fn callable_declaration_nodes<'tree>(
11571 analyzer: &CppGraphSource<'_>,
11572 prepared: &'tree PreparedSyntaxTree,
11573 candidate: &CodeUnit,
11574) -> Vec<Node<'tree>> {
11575 let root = prepared.tree().root_node();
11576 analyzer
11577 .ranges(candidate)
11578 .into_iter()
11579 .filter_map(|range| {
11580 let mut declaration =
11581 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11582 while !matches!(
11595 declaration.kind(),
11596 "declaration" | "field_declaration" | "function_definition"
11597 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
11598 declaration,
11599 prepared.source(),
11600 ) {
11601 let Some(parent) = declaration.parent() else {
11602 break;
11603 };
11604 declaration = parent;
11605 }
11606 Some(declaration)
11607 })
11608 .collect()
11609}
11610
11611fn real_function_definition_ancestor<'tree>(
11612 node: Node<'tree>,
11613 source: &str,
11614) -> Option<Node<'tree>> {
11615 let mut ancestor = node.parent();
11616 while let Some(node) = ancestor {
11617 if node.kind() == "function_definition"
11618 && !is_recovered_declaration_scope_container(node, source)
11619 {
11620 return Some(node);
11621 }
11622 ancestor = node.parent();
11623 }
11624 None
11625}
11626
11627fn callable_declaration_activation_in_file(
11628 analyzer: &CppGraphSource<'_>,
11629 candidate: &CodeUnit,
11630 reference: &CallableReferenceContext<'_>,
11631) -> Option<usize> {
11632 analyzer
11633 .declaration_source_occurrences(candidate)?
11634 .into_iter()
11635 .filter_map(|occurrence| {
11636 let activation = occurrence.fact.callable_activation?;
11637 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
11638 callable_guard_requirements_are_visible(
11639 &guards,
11640 occurrence.fact.callable_guard_completion_byte,
11641 reference,
11642 )
11643 .then_some(activation)
11644 })
11645 .min()
11646}
11647
11648fn callable_guard_requirements_are_visible(
11649 required: &HashSet<PreprocessorGuard>,
11650 completion_byte: Option<usize>,
11651 reference: &CallableReferenceContext<'_>,
11652) -> bool {
11653 if required.iter().all(|guard| match guard {
11654 PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
11655 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
11656 guard => reference
11657 .guards()
11658 .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
11659 }) {
11660 return true;
11661 }
11662 if required.iter().any(|guard| match guard {
11663 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
11664 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
11665 _ => false,
11666 }) {
11667 return false;
11668 }
11669 let undecided: HashSet<_> = required
11670 .iter()
11671 .filter(|guard| {
11672 !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11673 if name == "__cplusplus")
11674 })
11675 .cloned()
11676 .collect();
11677 reference.position.as_ref().is_some_and(|position| {
11678 completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
11679 && guards_compatible_at_reference(&undecided, reference.guards())
11680 })
11681}
11682
11683struct CallableReferenceContext<'a> {
11689 file: &'a ProjectFile,
11690 position: Option<CallableReferencePosition<'a>>,
11691}
11692
11693struct CallableReferencePosition<'a> {
11697 prepared: &'a PreparedSyntaxTree,
11698 byte: usize,
11699 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
11700}
11701
11702impl CallableReferenceContext<'_> {
11703 fn is_c(&self) -> bool {
11704 self.file
11705 .rel_path()
11706 .extension()
11707 .and_then(|extension| extension.to_str())
11708 == Some("c")
11709 }
11710
11711 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
11712 let position = self.position.as_ref()?;
11713 position
11714 .guards
11715 .get_or_init(|| {
11716 position
11717 .prepared
11718 .tree()
11719 .root_node()
11720 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
11721 .and_then(|node| {
11722 preprocessor_guard_environment(node, position.prepared.source())
11723 })
11724 })
11725 .as_ref()
11726 }
11727}
11728
11729fn callable_declaration_guard_requirements(
11741 node: Node<'_>,
11742 source: &str,
11743 reference: &CallableReferenceContext<'_>,
11744) -> Option<HashSet<PreprocessorGuard>> {
11745 let reference_is_c = reference.is_c();
11746 let mut required = HashSet::default();
11747 let mut ancestor = node.parent();
11748 while let Some(conditional) = ancestor {
11749 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11750 && !is_file_covering_include_guard(conditional, source)
11751 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11752 && preprocessor_conditional_contains_descendant(conditional, node)
11753 {
11754 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11755 match guard {
11756 PreprocessorGuard::Constant(true) => {}
11757 PreprocessorGuard::Constant(false) => return None,
11758 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
11759 if reference_is_c {
11760 return None;
11761 }
11762 }
11763 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
11764 if !reference_is_c {
11765 return None;
11766 }
11767 }
11768 guard => {
11769 required.insert(guard);
11770 }
11771 }
11772 }
11773 ancestor = conditional.parent();
11774 }
11775 Some(required)
11776}
11777
11778fn callable_preprocessor_context_is_visible_for_reference(
11789 node: Node<'_>,
11790 source: &str,
11791 reference: &CallableReferenceContext<'_>,
11792) -> bool {
11793 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
11794 return false;
11795 };
11796 if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
11797 return true;
11798 }
11799 reference.position.as_ref().is_some_and(|position| {
11800 guards_compatible_at_reference(&required, reference.guards())
11801 && declaration_follows_completed_family_before_reference(node, source, position.byte)
11802 })
11803}
11804
11805fn flattened_macro_namespace_declaration_matches(
11806 analyzer: &CppGraphSource<'_>,
11807 _cpp: &dyn CppSource,
11808 reference_file: &ProjectFile,
11809 visible_declaration: &CodeUnit,
11810 qualified_candidate: &CodeUnit,
11811 reference_byte: usize,
11812) -> bool {
11813 if visible_declaration.kind() != qualified_candidate.kind()
11819 || visible_declaration.identifier() != qualified_candidate.identifier()
11820 || visible_declaration.signature() != qualified_candidate.signature()
11821 || !visible_declaration.package_name().is_empty()
11822 || qualified_candidate.package_name().is_empty()
11823 {
11824 return false;
11825 }
11826
11827 let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
11828 return false;
11829 };
11830 occurrences.into_iter().any(|occurrence| {
11831 occurrence
11832 .displaced_namespace_closing_brace
11833 .is_some_and(|closing_brace| {
11834 visible_declaration.source() != reference_file || closing_brace < reference_byte
11835 })
11836 })
11837}
11838
11839pub(crate) fn flattened_macro_namespace_components(
11840 declaration: Node<'_>,
11841 source: &str,
11842) -> Option<Vec<String>> {
11843 flattened_macro_function_namespace_components(declaration, source)
11844 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
11845}
11846
11847fn flattened_macro_function_namespace_components(
11848 declaration: Node<'_>,
11849 source: &str,
11850) -> Option<Vec<String>> {
11851 let body = declaration
11852 .parent()
11853 .filter(|parent| parent.kind() == "compound_statement")?;
11854 let function = body.parent()?;
11855 if function.child_by_field_name("body") != Some(body) {
11856 return None;
11857 }
11858 let namespace_name = recovered_macro_namespace_name(function, source)?;
11859 let mut components = enclosing_namespace_components(declaration, source)?;
11860 components.push(namespace_name);
11861 Some(components)
11862}
11863
11864fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
11875 if function.kind() != "function_definition" || !function.has_error() {
11876 return None;
11877 }
11878 let body = function
11879 .child_by_field_name("body")
11880 .filter(|body| body.kind() == "compound_statement")?;
11881 let mut cursor = function.walk();
11882 let prefix = function
11883 .named_children(&mut cursor)
11884 .take_while(|child| child.start_byte() < body.start_byte())
11885 .filter(|child| child.kind() != "comment")
11886 .collect::<Vec<_>>();
11887 let begin_index = prefix.iter().rposition(|child| {
11888 flattened_macro_sentinel_name(*child, source)
11889 .is_some_and(|name| is_namespace_begin_sentinel(&name))
11890 })?;
11891 let mut identifiers = Vec::new();
11892 let mut stack = prefix[begin_index + 1..]
11893 .iter()
11894 .rev()
11895 .copied()
11896 .collect::<Vec<_>>();
11897 while let Some(current) = stack.pop() {
11898 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
11899 identifiers.push(identifier);
11900 continue;
11901 }
11902 let mut cursor = current.walk();
11903 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
11904 stack.extend(children.into_iter().rev());
11905 }
11906 let [keyword, namespace_name] = identifiers.as_slice() else {
11907 return None;
11908 };
11909 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
11910 {
11911 return None;
11912 }
11913 let mut next = function.next_named_sibling();
11914 let next = loop {
11915 let candidate = next?;
11916 next = candidate.next_named_sibling();
11917 if candidate.kind() != "comment" {
11918 break candidate;
11919 }
11920 };
11921 flattened_macro_sentinel_name(next, source)
11922 .is_some_and(|name| is_namespace_end_sentinel(&name))
11923 .then(|| namespace_name.clone())
11924}
11925
11926pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
11931 crate::declarations::is_recovered_exported_class_container(node, source)
11932 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
11933 node, source,
11934 )
11935 || recovered_macro_namespace_name(node, source).is_some()
11936}
11937
11938fn flattened_macro_error_namespace_components(
11939 declaration: Node<'_>,
11940 source: &str,
11941) -> Option<Vec<String>> {
11942 let parent = declaration
11943 .parent()
11944 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
11945 let mut cursor = parent.walk();
11946 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
11947 let declaration_index = siblings
11948 .iter()
11949 .position(|candidate| same_node(*candidate, declaration))?;
11950 let begin_index = (0..declaration_index).rev().find(|index| {
11951 flattened_macro_sentinel_name(siblings[*index], source)
11952 .is_some_and(|name| is_namespace_begin_sentinel(&name))
11953 })?;
11954
11955 let significant = siblings[begin_index + 1..declaration_index]
11956 .iter()
11957 .copied()
11958 .filter(|node| node.kind() != "comment")
11959 .collect::<Vec<_>>();
11960 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
11961 return None;
11962 };
11963 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
11964 return None;
11965 }
11966 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
11967 if significant[2..].iter().any(|node| {
11968 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
11969 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
11970 })
11971 }) {
11972 return None;
11973 }
11974
11975 let mut saw_namespace_close = false;
11976 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
11977 if sibling.kind() == "comment" {
11978 continue;
11979 }
11980 if !saw_namespace_close {
11981 if direct_unmatched_closing_brace(sibling) {
11982 saw_namespace_close = true;
11983 continue;
11984 }
11985 if flattened_macro_sentinel_name(sibling, source).is_some() {
11986 return None;
11987 }
11988 continue;
11989 }
11990 if !flattened_macro_sentinel_name(sibling, source)
11991 .is_some_and(|name| is_namespace_end_sentinel(&name))
11992 {
11993 return None;
11994 }
11995 let mut components = enclosing_namespace_components(declaration, source)?;
11996 components.push(namespace_name);
11997 return Some(components);
11998 }
11999 None
12000}
12001
12002fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12003 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12007 node.named_child(0)?
12008 } else {
12009 node
12010 };
12011 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12012 node.child_by_field_name("type")
12013 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12014 })?;
12015 (cpp_export_macro_token(&candidate)
12016 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12017 .then_some(candidate)
12018}
12019
12020fn is_namespace_begin_sentinel(name: &str) -> bool {
12023 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
12024}
12025
12026fn is_namespace_end_sentinel(name: &str) -> bool {
12027 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
12028}
12029
12030fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12031 if node.kind() != "ERROR" || node.named_child_count() != 1 {
12032 return None;
12033 }
12034 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12035 (!cpp_export_macro_token(&name)).then_some(name)
12036}
12037
12038fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12039 if !matches!(
12040 node.kind(),
12041 "identifier" | "namespace_identifier" | "type_identifier"
12042 ) {
12043 return None;
12044 }
12045 let name = normalize_cpp_whitespace(node_text(node, source));
12046 (!name.is_empty()).then_some(name)
12047}
12048
12049fn guard_requirement_sets_match(
12050 left: &[(usize, HashSet<PreprocessorGuard>)],
12051 right: &[(usize, HashSet<PreprocessorGuard>)],
12052) -> bool {
12053 left.len() == right.len()
12054 && left.iter().all(|(_, left_guards)| {
12055 right
12056 .iter()
12057 .any(|(_, right_guards)| left_guards == right_guards)
12058 })
12059 && right.iter().all(|(_, right_guards)| {
12060 left.iter()
12061 .any(|(_, left_guards)| right_guards == left_guards)
12062 })
12063}
12064
12065pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12066 node.kind() == "ERROR"
12067 && (0..node.child_count())
12068 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12069}
12070
12071pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12072 let mut ancestor = node.parent();
12073 while let Some(parent) = ancestor {
12074 if is_preprocessor_conditional(parent)
12075 && !is_file_covering_include_guard(parent, source)
12076 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12077 {
12078 return false;
12079 }
12080 ancestor = parent.parent();
12081 }
12082 true
12083}
12084
12085fn is_split_cpp_language_linkage_wrapper(
12086 conditional: Node<'_>,
12087 descendant: Node<'_>,
12088 source: &str,
12089) -> bool {
12090 if conditional.child_by_field_name("alternative").is_some()
12091 || !matches!(
12092 simple_preprocessor_guard(conditional, source),
12093 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12094 )
12095 {
12096 return false;
12097 }
12098 let mut current = descendant.parent();
12099 let linkage = loop {
12100 let Some(node) = current else {
12101 return false;
12102 };
12103 if node == conditional {
12104 return false;
12105 }
12106 if node.kind() == "linkage_specification" {
12107 break node;
12108 }
12109 current = node.parent();
12110 };
12111 if linkage
12112 .child_by_field_name("value")
12113 .is_none_or(|value| node_text(value, source) != "\"C\"")
12114 {
12115 return false;
12116 }
12117 let Some(body) = linkage.child_by_field_name("body") else {
12118 return false;
12119 };
12120 let closes_opening_branch = (0..body.named_child_count())
12121 .filter_map(|index| body.named_child(index))
12122 .take_while(|child| child.end_byte() <= descendant.start_byte())
12123 .any(|child| {
12124 child.kind() == "preproc_call"
12125 && child
12126 .child_by_field_name("directive")
12127 .is_some_and(|directive| node_text(directive, source) == "#endif")
12128 });
12129 let reopens_for_closing_brace = (0..body.named_child_count())
12130 .filter_map(|index| body.named_child(index))
12131 .skip_while(|child| child.start_byte() < descendant.end_byte())
12132 .any(|child| {
12133 matches!(
12134 simple_preprocessor_guard(child, source),
12135 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12136 ) && (0..child.child_count()).any(|index| {
12137 child
12138 .child(index)
12139 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
12140 })
12141 });
12142 closes_opening_branch && reopens_for_closing_brace
12143}
12144
12145pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12149 node.child_by_field_name("arguments")
12150 .or_else(|| node.child_by_field_name("parameters"))
12151 .or_else(|| node.child_by_field_name("value"))
12152 .or_else(|| first_named_child_of_kind(node, "argument_list"))
12153 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12154}
12155
12156pub fn call_arity(node: Node<'_>) -> usize {
12157 call_arguments_node(node)
12158 .map(|args| argument_children(args).count())
12159 .unwrap_or(0)
12160}
12161
12162pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12163 let recovered_block_arguments = recovered_block_literal_arguments(node);
12164 (0..node.child_count())
12165 .filter_map(move |index| node.child(index))
12166 .filter(|child| child.is_named() && !child.is_extra())
12167 .flat_map(move |child| {
12168 if let Some((raw, left, right)) = recovered_block_arguments
12169 && child == raw
12170 {
12171 [Some(left), Some(right)]
12172 } else {
12173 [Some(child), None]
12174 }
12175 })
12176 .flatten()
12177}
12178
12179pub fn recovered_c_new_expression_arguments(
12188 node: Node<'_>,
12189 uses_c_semantics: bool,
12190) -> Option<[Node<'_>; 2]> {
12191 if !uses_c_semantics || node.kind() != "new_expression" {
12192 return None;
12193 }
12194 let parent = node.parent()?;
12195 if parent.kind() != "argument_list" {
12196 return None;
12197 }
12198 let keyword = node.child(0)?;
12199 let error = node.child(1)?;
12200 let trailing = node.child(2)?;
12201 if node.child(3).is_some()
12202 || keyword.kind() != "new"
12203 || keyword.is_named()
12204 || keyword.child_count() != 0
12205 || error.kind() != "ERROR"
12206 || !error.is_extra()
12207 || error.child_count() != 1
12208 || error.child(0).is_none_or(|comma| comma.kind() != ",")
12209 || node.child_by_field_name("type") != Some(trailing)
12210 || trailing.kind() != "type_identifier"
12211 {
12212 return None;
12213 }
12214 Some([keyword, trailing])
12215}
12216
12217pub fn recovered_c_new_expression_argument_at(
12220 mut node: Node<'_>,
12221 start_byte: usize,
12222 end_byte: usize,
12223 uses_c_semantics: bool,
12224) -> Option<Node<'_>> {
12225 if !uses_c_semantics {
12231 return None;
12232 }
12233 loop {
12234 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12235 return arguments.into_iter().find(|argument| {
12236 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12237 });
12238 }
12239 node = node.parent()?;
12240 }
12241}
12242
12243fn recovered_c_keyword_argument_count(
12244 file: &ProjectFile,
12245 call: Node<'_>,
12246 arguments: Node<'_>,
12247 source: &str,
12248) -> usize {
12249 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12254 return 0;
12255 }
12256 let mut ancestor = Some(call);
12257 let function = loop {
12258 let Some(current) = ancestor else {
12259 return 0;
12260 };
12261 if current.kind() == "function_definition" {
12262 break current;
12263 }
12264 ancestor = current.parent();
12265 };
12266 let Some(parameters) = function
12267 .child_by_field_name("declarator")
12268 .and_then(|declarator| declarator.child_by_field_name("parameters"))
12269 else {
12270 return 0;
12271 };
12272 let displaced_parameter_keywords = (0..parameters.child_count())
12273 .filter_map(|index| parameters.child(index))
12274 .filter(|error| error.kind() == "ERROR")
12275 .filter_map(|error| {
12276 let parameter = error.prev_named_sibling()?;
12277 if parameter.kind() != "parameter_declaration"
12278 || parameter.end_byte() != error.start_byte()
12279 || extract_variable_name(parameter, source).is_some()
12280 {
12281 return None;
12282 }
12283 let mut children = (0..error.child_count())
12284 .filter_map(|index| error.child(index))
12285 .filter(|child| !child.is_extra() && !child.is_missing());
12286 let keyword = children.next()?;
12287 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12288 .then_some(keyword)
12289 })
12290 .collect::<Vec<_>>();
12291 if displaced_parameter_keywords.is_empty() {
12292 return 0;
12293 }
12294
12295 (0..arguments.child_count())
12296 .filter_map(|index| arguments.child(index))
12297 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12298 .filter(|error| {
12299 let mut children = (0..error.child_count())
12300 .filter_map(|index| error.child(index))
12301 .filter(|child| !child.is_extra() && !child.is_missing());
12302 let Some(comma) = children.next() else {
12303 return false;
12304 };
12305 let Some(keyword) = children.next() else {
12306 return false;
12307 };
12308 children.next().is_none()
12309 && comma.kind() == ","
12310 && !keyword.is_named()
12311 && keyword.child_count() == 0
12312 && displaced_parameter_keywords
12313 .iter()
12314 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12315 })
12316 .count()
12317}
12318
12319fn recovered_block_literal_arguments<'tree>(
12320 arguments: Node<'tree>,
12321) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12322 if arguments.kind() != "argument_list" {
12323 return None;
12324 }
12325 let mut raw_arguments = (0..arguments.child_count())
12326 .filter_map(|index| arguments.child(index))
12327 .filter(|child| child.is_named() && !child.is_extra());
12328 let raw = raw_arguments.next()?;
12329 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12330 return None;
12331 }
12332
12333 let left = raw.child_by_field_name("left")?;
12334 if left.is_missing() || left.start_byte() == left.end_byte() {
12335 return None;
12336 }
12337 let right = raw.child_by_field_name("right")?;
12338 if right.kind() != "compound_literal_expression"
12339 || right.is_missing()
12340 || right
12341 .child_by_field_name("type")
12342 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12343 || right
12344 .child_by_field_name("value")
12345 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12346 {
12347 return None;
12348 }
12349 let has_intervening_error = (0..raw.child_count())
12350 .filter_map(|index| raw.child(index))
12351 .any(|child| {
12352 child.kind() == "ERROR"
12353 && !child.is_missing()
12354 && child.start_byte() >= left.end_byte()
12355 && child.end_byte() <= right.start_byte()
12356 });
12357 has_intervening_error.then_some((raw, left, right))
12358}
12359
12360pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12361 match node.kind() {
12362 "new_expression" => node
12363 .child_by_field_name("type")
12364 .or_else(|| node.named_child(0)),
12365 "compound_literal_expression" => node.child_by_field_name("type"),
12366 "call_expression" => node.child_by_field_name("function"),
12367 _ => None,
12368 }
12369}
12370
12371pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12377 if node.kind() != "cast_expression" {
12378 return None;
12379 }
12380 let descriptor = node.child_by_field_name("type")?;
12381 if descriptor.kind() == "type_descriptor" {
12382 descriptor.child_by_field_name("type")
12383 } else {
12384 Some(descriptor)
12385 }
12386}
12387
12388pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
12403 debug_assert_eq!(field.kind(), "field_expression");
12404 let operator = field.child_by_field_name("operator")?;
12405 let mut cursor = field.walk();
12406 let receiver = field
12407 .named_children(&mut cursor)
12408 .filter(|child| child.end_byte() <= operator.start_byte())
12409 .last()?;
12410 if receiver.kind() != "ERROR" {
12411 return Some(receiver);
12412 }
12413 (receiver.named_child_count() == 1)
12414 .then(|| receiver.named_child(0))
12415 .flatten()
12416}
12417
12418pub fn field_initializer_constructs_target(
12419 node: Node<'_>,
12420 ctx: &ScanCtx<'_>,
12421 owner: &CodeUnit,
12422) -> bool {
12423 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
12432 return qualified_base_initializer_constructs_target(node, ctx, owner);
12433 }
12434 let Some(name) = node
12435 .child_by_field_name("name")
12436 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
12437 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
12438 else {
12439 return false;
12440 };
12441 let field_name = node_text(name, ctx.source);
12442 ctx.visibility
12443 .visible_identifier_candidates(ctx.file, field_name)
12444 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
12445 .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
12446}
12447
12448fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
12458 ctx.visibility
12459 .field_declared_type_fact(&ctx.analyzer, unit)
12460 .is_none_or(|fact| !fact.binds_indirectly)
12461}
12462
12463fn qualified_base_initializer_constructs_target(
12464 node: Node<'_>,
12465 ctx: &ScanCtx<'_>,
12466 owner: &CodeUnit,
12467) -> bool {
12468 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
12469 return false;
12470 };
12471 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
12472 return false;
12473 };
12474 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
12475 return false;
12476 };
12477 let resolves_target = |components: &[String]| {
12478 matches!(
12479 ctx.visibility.resolve_type_components_lexically_for_target(
12480 &ctx.analyzer,
12481 ctx.file,
12482 components,
12483 is_globally_qualified_cpp_name(qualified),
12484 &lexical_scope,
12485 owner,
12486 ),
12487 LexicalTypeResolution::Resolved { unit, .. }
12488 if same_visible_symbol(&unit, owner)
12489 )
12490 };
12491 if resolves_target(&components) {
12492 return true;
12493 }
12494
12495 components
12501 .last()
12502 .is_some_and(|terminal| terminal == owner.identifier())
12503 && resolves_target(&components[..components.len() - 1])
12504}
12505
12506fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12507 ctx.visibility
12508 .field_declared_type_fact(&ctx.analyzer, unit)
12509 .is_some_and(|fact| {
12510 ctx.visibility
12511 .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
12512 || ctx.visibility.resolves_to_type(
12513 &ctx.analyzer,
12514 ctx.file,
12515 &normalize_field_type_text(&fact.type_text),
12516 owner,
12517 )
12518 })
12519}
12520
12521pub fn field_declared_binding(
12522 analyzer: &CppGraphSource<'_>,
12523 visibility: &VisibilityIndex<'_>,
12524 visible_from: &ProjectFile,
12525 field: &CodeUnit,
12526) -> Option<CppScanBinding> {
12527 let fact = visibility.field_declared_type_fact(analyzer, field)?;
12528 let normalized = normalize_field_type_text(&fact.type_text);
12529 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
12530 analyzer,
12531 visible_from,
12532 field,
12533 &normalized,
12534 );
12535 let resolved = match (resolved, fact.template_arguments.as_deref()) {
12536 (Some(primary), Some(arguments)) => visibility
12537 .resolve_template_arguments(visible_from, primary, arguments)
12538 .ok(),
12539 (resolved, None) => resolved,
12540 (None, Some(_)) => None,
12541 }
12542 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
12543 Some(CppScanBinding::from_type_name(
12544 normalized,
12545 resolved,
12546 fact.indirection,
12547 ))
12548}
12549
12550pub fn field_initializer_holds_subobject(
12557 analyzer: &CppGraphSource<'_>,
12558 visibility: &VisibilityIndex<'_>,
12559 field: &CodeUnit,
12560) -> bool {
12561 visibility
12562 .field_declared_type_fact(analyzer, field)
12563 .is_none_or(|fact| !fact.binds_indirectly)
12564}
12565
12566fn anonymous_aggregate_field_owner(
12573 analyzer: &CppGraphSource<'_>,
12574 visibility: &VisibilityIndex<'_>,
12575 visible_from: &ProjectFile,
12576 field: &CodeUnit,
12577) -> Option<CodeUnit> {
12578 let owner = type_owner_of(analyzer, field)?;
12579 if !owner.is_class() {
12580 return None;
12581 }
12582 let declaration = analyzer.get_source(field, false)?;
12583 let mut parser = Parser::new();
12584 parser
12585 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12586 .ok()?;
12587 let tree = parser.parse(&declaration, None)?;
12588 let mut stack = vec![tree.root_node()];
12589 while let Some(node) = stack.pop() {
12590 if matches!(node.kind(), "declaration" | "field_declaration")
12591 && let Some(type_node) = node
12592 .child_by_field_name("type")
12593 .or_else(|| first_type_child(node))
12594 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12595 && type_node.child_by_field_name("name").is_none()
12596 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
12597 {
12598 let matches = visibility
12599 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12600 .into_iter()
12601 .filter(|child| child.is_class() && child.identifier() == field.identifier())
12602 .collect::<Vec<_>>();
12603 return match matches.as_slice() {
12604 [child] => Some((*child).clone()),
12605 _ => None,
12606 };
12607 }
12608 let mut cursor = node.walk();
12609 stack.extend(node.named_children(&mut cursor));
12610 }
12611 None
12612}
12613
12614pub fn anonymous_aggregate_owner(
12621 analyzer: &CppGraphSource<'_>,
12622 file: &ProjectFile,
12623 node: Node<'_>,
12624) -> Option<CodeUnit> {
12625 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12626 || node.child_by_field_name("name").is_some()
12627 {
12628 return None;
12629 }
12630 let mut candidates = analyzer
12631 .declarations(file)
12632 .into_iter()
12633 .filter(|candidate| {
12634 candidate.is_class()
12635 && analyzer.ranges(candidate).into_iter().any(|range| {
12636 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12637 })
12638 })
12639 .collect::<Vec<_>>();
12640 candidates.sort_by_key(|candidate| candidate.fq_name());
12641 candidates.dedup();
12642 match candidates.as_slice() {
12643 [candidate] => Some(candidate.clone()),
12644 _ => None,
12645 }
12646}
12647
12648fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12650 let Some(first) = candidates.first() else {
12651 return Err(TypeCandidateFailure::Unresolvable);
12652 };
12653 if candidates
12654 .iter()
12655 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12656 {
12657 Ok((*first).clone())
12658 } else {
12659 Err(TypeCandidateFailure::Ambiguous)
12660 }
12661}
12662
12663fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12664 logical_type_candidate(candidates).ok()
12665}
12666
12667fn unique_type_candidate_preserving_alias(
12668 analyzer: &CppGraphSource<'_>,
12669 file: &ProjectFile,
12670 candidates: &[&CodeUnit],
12671) -> Option<CodeUnit> {
12672 let first = *candidates.first()?;
12673 if declared_type_alias(analyzer, first) {
12674 return candidates
12675 .iter()
12676 .all(|candidate| {
12677 declared_type_alias(analyzer, candidate)
12678 && candidate.kind() == first.kind()
12679 && candidate.fq_name() == first.fq_name()
12680 && candidate.source() == first.source()
12681 })
12682 .then(|| first.clone());
12683 }
12684 if analyzer.reference_uses_c_semantics(file)
12685 && first.is_class()
12686 && indexed_c_tag_kind(analyzer, first).is_some()
12687 {
12688 let mut full_source = None;
12689 let mut tag_kind = None;
12690 for candidate in candidates.iter().copied() {
12691 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12692 if tag_kind
12693 .replace(candidate_tag_kind)
12694 .is_some_and(|existing| existing != candidate_tag_kind)
12695 {
12696 return None;
12697 }
12698 if cpp_class_declaration_strength(analyzer, candidate)
12699 == CppClassDeclarationStrength::Full
12700 && full_source
12701 .replace(candidate.source())
12702 .is_some_and(|existing| existing != candidate.source())
12703 {
12704 return None;
12705 }
12706 }
12707 }
12708 candidates
12709 .iter()
12710 .all(|candidate| {
12711 !declared_type_alias(analyzer, candidate)
12712 && candidate.kind() == first.kind()
12713 && candidate.fq_name() == first.fq_name()
12714 })
12715 .then(|| first.clone())
12716}
12717
12718pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
12719 analyzer
12720 .type_alias_provider()
12721 .is_some_and(|provider| provider.is_type_alias(unit))
12722}
12723
12724pub fn field_declared_type_binding(
12725 analyzer: &CppGraphSource<'_>,
12726 visibility: &VisibilityIndex<'_>,
12727 visible_from: &ProjectFile,
12728 field: &CodeUnit,
12729) -> Option<(String, Option<CodeUnit>, i32)> {
12730 let fact = visibility.field_declared_type_fact(analyzer, field)?;
12731 let normalized = normalize_field_type_text(&fact.type_text);
12732 let primary = visibility.resolve_unique_canonical_type_for_declaration(
12733 analyzer,
12734 visible_from,
12735 field,
12736 &normalized,
12737 );
12738 let resolved = match (primary, fact.template_arguments.as_deref()) {
12739 (Some(primary), Some(arguments)) => visibility
12740 .resolve_template_arguments(visible_from, primary, arguments)
12741 .ok(),
12742 (resolved, None) => resolved,
12743 (None, Some(_)) => None,
12744 };
12745 Some((normalized, resolved, fact.indirection))
12746}
12747
12748fn decode_field_declared_type_fact(
12749 analyzer: &CppGraphSource<'_>,
12750 field: &CodeUnit,
12751) -> Option<DeclaredFieldTypeFact> {
12752 if let Some(facts) = analyzer.declaration_source_properties(field)
12753 && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
12754 && facts
12755 .iter()
12756 .all(|fact| fact.field_type.as_ref() == Some(first))
12757 {
12758 return Some(first.clone());
12759 }
12760 let Some(declaration) = analyzer.get_source(field, false) else {
12761 return decode_indexed_field_declared_type_fact(analyzer, field);
12762 };
12763 let mut parser = Parser::new();
12764 parser
12765 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12766 .ok()?;
12767 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
12771 let contextual_tree = parser.parse(&contextual_declaration, None)?;
12772 let mut stack = vec![contextual_tree.root_node()];
12773 while let Some(node) = stack.pop() {
12774 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
12775 && node_text(recovered.name, &contextual_declaration) == field.identifier()
12776 {
12777 return Some(DeclaredFieldTypeFact {
12778 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
12779 indirection: recovered.pointer_depth(),
12780 binds_indirectly: recovered.pointer_depth() > 0,
12781 template_arguments: None,
12782 });
12783 }
12784 if let Some(recovered) =
12785 recovered_function_like_field_declarator(node, &contextual_declaration)
12786 && node_text(recovered.name, &contextual_declaration) == field.identifier()
12787 {
12788 let type_node = node
12789 .child_by_field_name("type")
12790 .or_else(|| first_type_child(node))?;
12791 return Some(DeclaredFieldTypeFact {
12792 type_text: node_text(type_node, &contextual_declaration).to_string(),
12793 indirection: recovered.pointer_depth(),
12794 binds_indirectly: true,
12797 template_arguments: cpp_template_reference_arguments(
12798 type_node,
12799 &contextual_declaration,
12800 ),
12801 });
12802 }
12803 if let Some(fact) =
12804 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
12805 {
12806 return Some(fact);
12807 }
12808 let mut cursor = node.walk();
12809 stack.extend(node.named_children(&mut cursor));
12810 }
12811 let tree = parser.parse(&declaration, None)?;
12812 let mut stack = vec![tree.root_node()];
12813 while let Some(node) = stack.pop() {
12814 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
12815 {
12816 return Some(fact);
12817 }
12818 let mut cursor = node.walk();
12819 stack.extend(node.named_children(&mut cursor));
12820 }
12821 None
12822}
12823
12824fn decode_indexed_field_declared_type_fact(
12829 analyzer: &CppGraphSource<'_>,
12830 field: &CodeUnit,
12831) -> Option<DeclaredFieldTypeFact> {
12832 let cpp = analyzer.cpp?;
12833 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
12834 let source = prepared.source();
12835 let root = prepared.tree().root_node();
12836 for range in analyzer.ranges(field) {
12837 let end = range.start_byte.saturating_add(1).min(source.len());
12838 let mut current = root.descendant_for_byte_range(range.start_byte, end);
12839 while let Some(node) = current {
12840 if matches!(node.kind(), "declaration" | "field_declaration")
12841 && let Some(fact) =
12842 decode_declared_field_type_node(node, field.identifier(), source)
12843 {
12844 return Some(fact);
12845 }
12846 current = node.parent();
12847 }
12848 }
12849 None
12850}
12851
12852fn decode_declared_field_type_node(
12853 node: Node<'_>,
12854 field_name: &str,
12855 source: &str,
12856) -> Option<DeclaredFieldTypeFact> {
12857 if !matches!(node.kind(), "declaration" | "field_declaration") {
12858 return None;
12859 }
12860 let type_node = node
12861 .child_by_field_name("type")
12862 .or_else(|| first_type_child(node))?;
12863 let binding = declared_name_binding(node, type_node, field_name, source)?;
12864 let declared_type = if matches!(
12865 type_node.kind(),
12866 "class_specifier" | "struct_specifier" | "union_specifier"
12867 ) {
12868 type_node.child_by_field_name("name")
12869 } else {
12870 Some(type_node)
12871 };
12872 Some(DeclaredFieldTypeFact {
12873 type_text: declared_type.map_or_else(
12874 || field_name.to_string(),
12875 |declared_type| node_text(declared_type, source).to_string(),
12876 ),
12877 indirection: binding.pointer_depth,
12878 binds_indirectly: binding.indirect,
12879 template_arguments: declared_type
12880 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
12881 })
12882}
12883
12884pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
12898 let mut parser = Parser::new();
12899 parser
12900 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12901 .ok()?;
12902 let tree = parser.parse(declaration, None)?;
12903 let mut stack = vec![tree.root_node()];
12904 while let Some(node) = stack.pop() {
12905 let type_node = match node.kind() {
12906 "type_definition" => {
12907 let mut cursor = node.walk();
12908 if node
12909 .children_by_field_name("declarator", &mut cursor)
12910 .any(declarator_names_function_type)
12911 {
12912 return None;
12913 }
12914 node.child_by_field_name("type")?
12915 }
12916 "alias_declaration" => {
12917 let type_node = node.child_by_field_name("type")?;
12918 if type_node
12919 .child_by_field_name("declarator")
12920 .is_some_and(declarator_names_function_type)
12921 {
12922 return None;
12923 }
12924 type_node
12925 }
12926 _ => {
12927 let mut cursor = node.walk();
12928 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
12929 stack.extend(children.into_iter().rev());
12930 continue;
12931 }
12932 };
12933 return Some(node_text(type_node, declaration).to_string());
12934 }
12935 None
12936}
12937
12938pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
12944 let mut current = Some(declarator);
12945 while let Some(node) = current {
12946 match node.kind() {
12947 "function_declarator" | "abstract_function_declarator" => return true,
12948 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
12949 current = node.named_child(0);
12950 }
12951 _ => current = node.child_by_field_name("declarator"),
12952 }
12953 }
12954 false
12955}
12956
12957fn decode_structured_alias_target(
12958 analyzer: &CppGraphSource<'_>,
12959 unit: &CodeUnit,
12960) -> Option<StructuredAliasTarget> {
12961 let facts = analyzer.declaration_source_properties(unit)?;
12962 let first = facts.first()?.alias_target.as_ref()?;
12963 facts
12964 .iter()
12965 .all(|fact| fact.alias_target.as_ref() == Some(first))
12966 .then(|| first.clone())
12967}
12968
12969pub(crate) fn structured_alias_type_target(
12970 mut type_node: Node<'_>,
12971 source: &str,
12972) -> Option<StructuredAliasTarget> {
12973 while type_node.kind() == "type_descriptor" {
12974 type_node = type_node.child_by_field_name("type")?;
12975 }
12976 if type_node.kind() == "primitive_type" {
12977 return Some(StructuredAliasTarget::Builtin);
12978 }
12979 if matches!(
12980 type_node.kind(),
12981 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
12982 ) {
12983 type_node = type_node.child_by_field_name("name")?;
12984 }
12985 let global = type_node.child_by_field_name("scope").is_none()
12986 && type_node.child(0).is_some_and(|child| child.kind() == "::");
12987 let mut components = Vec::new();
12988 append_structured_type_components(type_node, source, &mut components)?;
12989 let arguments = cpp_template_reference_arguments(type_node, source);
12990 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
12991 components,
12992 global,
12993 arguments,
12994 })
12995}
12996
12997fn append_structured_type_components(
12998 node: Node<'_>,
12999 source: &str,
13000 out: &mut Vec<String>,
13001) -> Option<()> {
13002 match node.kind() {
13003 "identifier" | "namespace_identifier" | "type_identifier" => {
13004 out.push(node_text(node, source).to_string());
13005 Some(())
13006 }
13007 "template_type" | "template_function" => {
13008 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13009 }
13010 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13011 if let Some(scope) = node.child_by_field_name("scope") {
13012 append_structured_type_components(scope, source, out)?;
13013 }
13014 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13015 }
13016 _ => None,
13017 }
13018}
13019
13020pub(crate) struct DeclaredNameBinding {
13023 pub pointer_depth: i32,
13025 pub indirect: bool,
13029}
13030
13031pub(crate) fn declared_name_binding(
13032 declaration: Node<'_>,
13033 type_node: Node<'_>,
13034 field_name: &str,
13035 source: &str,
13036) -> Option<DeclaredNameBinding> {
13037 let mut stack = Vec::new();
13038 let mut cursor = declaration.walk();
13039 stack.extend(
13040 declaration
13041 .named_children(&mut cursor)
13042 .filter(|child| !same_node(*child, type_node)),
13043 );
13044 while let Some(node) = stack.pop() {
13045 if matches!(node.kind(), "identifier" | "field_identifier")
13046 && node_text(node, source) == field_name
13047 {
13048 let mut binding = DeclaredNameBinding {
13049 pointer_depth: 0,
13050 indirect: false,
13051 };
13052 let mut current = node.parent();
13053 while let Some(parent) = current {
13054 if same_node(parent, declaration) {
13055 return Some(binding);
13056 }
13057 match parent.kind() {
13058 "pointer_declarator" => {
13059 binding.pointer_depth += 1;
13060 binding.indirect = true;
13061 }
13062 "reference_declarator" => binding.indirect = true,
13063 _ => {}
13064 }
13065 current = parent.parent();
13066 }
13067 return None;
13068 }
13069 let mut cursor = node.walk();
13070 stack.extend(node.named_children(&mut cursor));
13071 }
13072 None
13073}
13074
13075fn normalize_field_type_text(type_text: &str) -> String {
13076 const FIELD_SPECIFIERS: [&str; 8] = [
13077 "extern ",
13078 "static ",
13079 "mutable ",
13080 "constexpr ",
13081 "constinit ",
13082 "inline ",
13083 "volatile ",
13084 "const ",
13085 ];
13086
13087 let mut normalized = normalize_type_text(type_text);
13088 loop {
13089 let Some(stripped) = FIELD_SPECIFIERS
13090 .iter()
13091 .find_map(|specifier| normalized.strip_prefix(specifier))
13092 else {
13093 return normalized;
13094 };
13095 normalized = normalize_type_text(stripped);
13096 }
13097}
13098
13099pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13100 let Some(type_node) = node.child_by_field_name("type") else {
13101 return false;
13102 };
13103 ctx.visibility.resolves_to_type(
13104 &ctx.analyzer,
13105 ctx.file,
13106 node_text(type_node, ctx.source),
13107 owner,
13108 )
13109}
13110
13111pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13112 !ctx.analyzer
13113 .declarations(ctx.file)
13114 .into_iter()
13115 .filter(|unit| unit.is_function())
13116 .any(|unit| {
13117 ctx.analyzer.ranges(&unit).iter().any(|range| {
13118 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13119 })
13120 })
13121}
13122
13123pub enum DeclarationConstructorInitializer<'tree> {
13125 Arguments(Node<'tree>),
13128 Expression(Node<'tree>),
13131 Empty,
13133}
13134
13135pub fn declaration_constructor_initializer(
13136 node: Node<'_>,
13137) -> DeclarationConstructorInitializer<'_> {
13138 let mut cursor = node.walk();
13139 for child in node.named_children(&mut cursor) {
13140 if child.kind() == "init_declarator" {
13141 let Some(value) = child
13142 .child_by_field_name("value")
13143 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13144 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13145 else {
13146 return DeclarationConstructorInitializer::Empty;
13147 };
13148 return match value.kind() {
13149 "argument_list" | "initializer_list" => {
13150 DeclarationConstructorInitializer::Arguments(value)
13151 }
13152 "compound_literal_expression" => call_arguments_node(value)
13153 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13154 DeclarationConstructorInitializer::Arguments(arguments)
13155 }),
13156 _ => DeclarationConstructorInitializer::Expression(value),
13157 };
13158 }
13159 if let Some(declarator) = declaration_declarator(node, child) {
13160 return declarator_parameters(declarator)
13161 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13162 DeclarationConstructorInitializer::Arguments(parameters)
13163 });
13164 }
13165 }
13166 DeclarationConstructorInitializer::Empty
13167}
13168
13169pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13170 match declaration_constructor_initializer(node) {
13171 DeclarationConstructorInitializer::Arguments(arguments) => {
13172 argument_children(arguments).count()
13173 }
13174 DeclarationConstructorInitializer::Expression(_) => 1,
13175 DeclarationConstructorInitializer::Empty => 0,
13176 }
13177}
13178
13179fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13183 let mut current = node;
13184 loop {
13185 if let Some(parameters) = current.child_by_field_name("parameters") {
13186 return Some(parameters);
13187 }
13188 current = current.child_by_field_name("declarator")?;
13189 }
13190}
13191
13192pub(super) fn first_named_child_of_kind<'tree>(
13193 node: Node<'tree>,
13194 kind: &str,
13195) -> Option<Node<'tree>> {
13196 let mut cursor = node.walk();
13197 node.named_children(&mut cursor)
13198 .find(|child| child.kind() == kind)
13199}
13200
13201fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13202 let mut stack = vec![root];
13203 while let Some(node) = stack.pop() {
13204 if node.kind() == kind {
13205 return Some(node);
13206 }
13207 push_named_children_reversed(node, &mut stack);
13208 }
13209 None
13210}
13211
13212fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13213 if node.kind() == "identifier" {
13214 return true;
13215 }
13216 if node.kind() == "parenthesized_expression" {
13217 return false;
13218 }
13219 if node.kind() == "call_expression" {
13220 return node
13221 .child_by_field_name("function")
13222 .is_some_and(|function| function.kind() == "identifier");
13223 }
13224 let mut stack = vec![node];
13225 while let Some(descendant) = stack.pop() {
13226 if descendant != node && descendant.kind() == "parenthesized_expression" {
13227 continue;
13228 }
13229 if descendant.kind() == "identifier" {
13230 return true;
13231 }
13232 if descendant.kind() == "call_expression" {
13233 if descendant
13234 .child_by_field_name("function")
13235 .is_some_and(|function| function.kind() == "identifier")
13236 {
13237 return true;
13238 }
13239 continue;
13240 }
13241 push_named_children_reversed(descendant, &mut stack);
13242 }
13243 false
13244}
13245
13246fn macro_expansion_shape_is_safe(
13247 node: Node<'_>,
13248 source: &str,
13249 parameters: &[String],
13250 environment: &MacroEnvironment,
13251) -> bool {
13252 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13253 return true;
13254 }
13255 if node.kind() == "call_expression" {
13256 let Some(function) = node.child_by_field_name("function") else {
13257 return true;
13258 };
13259 if function.kind() != "identifier" {
13260 return true;
13261 }
13262 let function_name = node_text(function, source);
13263 if parameters
13264 .iter()
13265 .any(|parameter| parameter == function_name)
13266 {
13267 return false;
13268 }
13269 if !environment.may_bind(function_name) {
13270 return true;
13271 }
13272 let Some(arguments) = node.child_by_field_name("arguments") else {
13273 return false;
13274 };
13275 return argument_children(arguments).all(|argument| {
13276 if argument.kind() == "identifier"
13277 && parameters
13278 .iter()
13279 .any(|parameter| parameter == node_text(argument, source))
13280 {
13281 return false;
13282 }
13283 macro_expansion_shape_is_safe(argument, source, parameters, environment)
13284 });
13285 }
13286 let mut stack = vec![node];
13287 while let Some(descendant) = stack.pop() {
13288 if descendant != node {
13289 if descendant.kind() == "parenthesized_expression" {
13290 continue;
13291 }
13292 if descendant.kind() == "call_expression" {
13293 let expands = descendant
13294 .child_by_field_name("function")
13295 .filter(|function| function.kind() == "identifier")
13296 .is_some_and(|function| environment.may_bind(node_text(function, source)));
13297 if expands {
13298 return false;
13299 }
13300 continue;
13301 }
13302 }
13303 if descendant.kind() == "identifier" {
13304 let identifier = node_text(descendant, source);
13305 if parameters.iter().any(|parameter| parameter == identifier)
13306 || environment.may_bind(identifier)
13307 {
13308 return false;
13309 }
13310 }
13311 push_named_children_reversed(descendant, &mut stack);
13312 }
13313 true
13314}
13315
13316fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13317 let text = node_text(path, source);
13318 match path.kind() {
13319 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13320 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13321 _ => None,
13322 }
13323}
13324
13325fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13326 let source = prepared.source();
13327 let mut facts = Vec::new();
13328 let mut nodes = vec![prepared.tree().root_node()];
13329 while let Some(node) = nodes.pop() {
13330 if node.kind() == "preproc_include" {
13331 let Some(path) = node
13332 .child_by_field_name("path")
13333 .and_then(|path| structured_include_path(path, source))
13334 .map(str::to_owned)
13335 else {
13336 continue;
13337 };
13338 facts.push(StructuredIncludeFact {
13339 start_byte: node.start_byte(),
13340 end_byte: node.end_byte(),
13341 path,
13342 });
13343 continue;
13344 }
13345 push_named_children_reversed(node, &mut nodes);
13346 }
13347 Arc::from(facts.into_boxed_slice())
13348}
13349
13350fn has_unresolved_include_visible_before_in_prepared(
13351 file: &ProjectFile,
13352 prepared: &PreparedSyntaxTree,
13353 include_targets: &IncludeTargetIndex,
13354 facts: &[StructuredIncludeFact],
13355 before_byte: usize,
13356) -> bool {
13357 let guards = OnceCell::new();
13358 let reference = CallableReferenceContext {
13359 file,
13360 position: Some(CallableReferencePosition {
13361 prepared,
13362 byte: before_byte,
13363 guards: &guards,
13364 }),
13365 };
13366 let root = prepared.tree().root_node();
13367 facts
13368 .iter()
13369 .filter(|fact| fact.end_byte <= before_byte)
13370 .any(|fact| {
13371 let node = root
13372 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13373 .expect("structured include fact range must be in prepared tree");
13374 assert_eq!(
13375 node.kind(),
13376 "preproc_include",
13377 "structured include fact range must identify its include node"
13378 );
13379 callable_preprocessor_context_is_visible_for_reference(
13380 node,
13381 prepared.source(),
13382 &reference,
13383 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13384 })
13385}
13386
13387fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13388 let descendant = node;
13389 while let Some(parent) = node.parent() {
13390 if is_preprocessor_conditional(parent)
13391 && !is_file_covering_include_guard(parent, source)
13392 && preprocessor_conditional_contains_descendant(parent, descendant)
13393 {
13394 return true;
13395 }
13396 node = parent;
13397 }
13398 false
13399}
13400
13401fn owning_preprocessor_conditionals(
13413 root: Node<'_>,
13414 event: Node<'_>,
13415 source: &str,
13416) -> OwningPreprocessorConditionals {
13417 if !has_preprocessor_conditional_ancestor(event, source) {
13418 return OwningPreprocessorConditionals::default();
13419 }
13420 let start = event.start_byte();
13421 let descendant = root
13422 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13423 .expect("a byte inside the parsed tree names a descendant");
13424 let mut owners = Vec::new();
13425 let mut current = descendant.parent();
13426 while let Some(conditional) = current {
13427 if is_preprocessor_conditional(conditional)
13428 && !is_file_covering_include_guard(conditional, source)
13429 && preprocessor_conditional_contains_descendant(conditional, descendant)
13430 {
13431 owners.push(conditional.start_byte());
13432 }
13433 current = conditional.parent();
13434 }
13435 owners.into_boxed_slice()
13436}
13437
13438fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13439 matches!(
13440 node.kind(),
13441 "preproc_if"
13442 | "preproc_ifdef"
13443 | "preproc_ifndef"
13444 | "preproc_elif"
13445 | "preproc_elifdef"
13446 | "preproc_else"
13447 )
13448}
13449
13450fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13451 node.parent()
13452 .filter(|parent| parent.kind() == "translation_unit")
13453 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13454 && is_canonical_include_guard(node, source)
13455}
13456
13457fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13458 if node.kind() != "preproc_ifdef"
13459 || node
13460 .child(0)
13461 .is_none_or(|directive| directive.kind() != "#ifndef")
13462 || node.child_by_field_name("alternative").is_some()
13463 {
13464 return false;
13465 }
13466 let Some(guard_name) = node.child_by_field_name("name") else {
13467 return false;
13468 };
13469 let mut cursor = node.walk();
13470 node.named_children(&mut cursor)
13471 .find(|child| *child != guard_name && child.kind() != "comment")
13472 .filter(|child| child.kind() == "preproc_def")
13473 .and_then(|definition| definition.child_by_field_name("name"))
13474 .is_some_and(|defined_name| {
13475 node_text(defined_name, source) == node_text(guard_name, source)
13476 })
13477}
13478
13479fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
13480 let mut guard = None;
13481 for child in named_children_iter(root) {
13482 if child.kind() == "comment" || is_pragma_once(child, source) {
13483 continue;
13484 }
13485 if guard.is_none() && is_canonical_include_guard(child, source) {
13486 guard = Some(child);
13487 } else {
13488 return None;
13489 }
13490 }
13491 guard
13492 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
13493 .map(|name| node_text(name, source).to_string())
13494}
13495
13496fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
13497 if (0..root.named_child_count())
13498 .filter_map(|index| root.named_child(index))
13499 .any(|child| is_pragma_once(child, source))
13500 {
13501 return MacroIncludeProtection::PragmaOnce;
13502 }
13503 top_level_canonical_include_guard_name(root, source)
13504 .map(MacroIncludeProtection::MacroGuard)
13505 .unwrap_or(MacroIncludeProtection::None)
13506}
13507
13508fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
13509 node.kind() == "preproc_call"
13510 && node
13511 .child_by_field_name("directive")
13512 .is_some_and(|directive| node_text(directive, source) == "#pragma")
13513 && node
13514 .child_by_field_name("argument")
13515 .is_some_and(|argument| node_text(argument, source).trim() == "once")
13516}
13517
13518fn parse_preproc_identifier(argument: &str) -> Option<String> {
13519 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
13520 let mut parser = Parser::new();
13521 parser
13522 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13523 .ok()?;
13524 let tree = parser.parse(&sentinel, None)?;
13525 if tree.root_node().has_error() {
13526 return None;
13527 }
13528 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
13529 let identifier = statement.named_child(0)?;
13530 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
13531 .then(|| node_text(identifier, &sentinel).to_string())
13532}
13533
13534pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
13535 match node.kind() {
13536 "identifier" | "field_identifier" => {
13537 let name = node_text(node, source).trim();
13538 (!name.is_empty()).then(|| name.to_string())
13539 }
13540 "abstract_array_declarator"
13541 | "abstract_function_declarator"
13542 | "abstract_parenthesized_declarator"
13543 | "abstract_pointer_declarator"
13544 | "abstract_reference_declarator" => None,
13545 "function_declarator" => node
13546 .child_by_field_name("declarator")
13547 .or_else(|| node.child_by_field_name("name"))
13548 .and_then(|child| extract_variable_name(child, source)),
13549 _ => node
13550 .child_by_field_name("declarator")
13551 .or_else(|| node.child_by_field_name("name"))
13552 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
13553 .and_then(|child| extract_variable_name(child, source)),
13554 }
13555}
13556
13557pub fn is_c_source_file(file: &ProjectFile) -> bool {
13568 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
13569}
13570
13571pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
13578 if !is_c_source_file(file) || node.kind() != "identifier" {
13579 return false;
13580 }
13581 let mut operand = node;
13582 while let Some(parent) = operand.parent().filter(|parent| {
13583 parent.kind() == "parenthesized_expression"
13584 && parent.named_child_count() == 1
13585 && parent.named_child(0) == Some(operand)
13586 }) {
13587 operand = parent;
13588 }
13589 operand.parent().is_some_and(|parent| {
13590 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
13591 })
13592}
13593
13594pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
13603 if node.kind() != "field_identifier" {
13604 return None;
13605 }
13606 let expression = node.parent().filter(|parent| {
13607 parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
13608 })?;
13609 if expression.has_error() {
13610 return None;
13611 }
13612 let closing = expression.child(expression.child_count().saturating_sub(1))?;
13613 if closing.kind() != ")" || closing.is_missing() {
13614 return None;
13615 }
13616 let type_descriptor = expression.child_by_field_name("type")?;
13617 if type_descriptor.kind() != "type_descriptor"
13618 || type_descriptor.is_missing()
13619 || type_descriptor.has_error()
13620 {
13621 return None;
13622 }
13623 let type_specifier = type_descriptor.child_by_field_name("type")?;
13624 if type_specifier.is_missing() || type_specifier.has_error() {
13625 return None;
13626 }
13627 let type_reference = match type_specifier.kind() {
13628 "class_specifier" | "struct_specifier" | "union_specifier" => {
13629 type_specifier.child_by_field_name("name")?
13630 }
13631 _ => type_specifier,
13632 };
13633 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
13634}
13635
13636pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
13641 node.kind() == "field_identifier"
13642 && node.parent().is_some_and(|parent| {
13643 parent.kind() == "offsetof_expression"
13644 && parent.child_by_field_name("member") == Some(node)
13645 })
13646}
13647
13648pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
13664 if node.kind() != "type_identifier" {
13665 return false;
13666 }
13667 let Some(descriptor) = node
13668 .parent()
13669 .filter(|parent| parent.kind() == "type_descriptor")
13670 else {
13671 return false;
13672 };
13673 if descriptor.child_by_field_name("type") != Some(node) {
13674 return false;
13675 }
13676 let Some(arguments) = descriptor
13677 .parent()
13678 .filter(|parent| parent.kind() == "template_argument_list")
13679 else {
13680 return false;
13681 };
13682 arguments.parent().is_some_and(|owner| {
13683 matches!(
13684 owner.kind(),
13685 "template_type" | "template_function" | "template_method"
13686 ) && owner.child_by_field_name("arguments") == Some(arguments)
13687 })
13688}
13689
13690pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
13703 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
13704}
13705
13706pub fn is_declarator_node(node: Node<'_>) -> bool {
13707 matches!(
13708 node.kind(),
13709 "identifier"
13710 | "field_identifier"
13711 | "qualified_identifier"
13712 | "scoped_identifier"
13713 | "pointer_declarator"
13714 | "reference_declarator"
13715 | "array_declarator"
13716 | "parenthesized_declarator"
13717 | "function_declarator"
13718 )
13719}
13720
13721pub fn declaration_declarator<'tree>(
13730 declaration: Node<'tree>,
13731 child: Node<'tree>,
13732) -> Option<Node<'tree>> {
13733 if !matches!(
13734 declaration.kind(),
13735 "declaration"
13736 | "field_declaration"
13737 | "parameter_declaration"
13738 | "optional_parameter_declaration"
13739 | "function_definition"
13740 | "type_definition"
13741 | "alias_declaration"
13742 | "template_instantiation"
13743 ) {
13744 return None;
13745 }
13746 if declaration
13747 .child_by_field_name("type")
13748 .is_some_and(|type_node| same_node(type_node, child))
13749 {
13750 return None;
13751 }
13752 if child.kind() == "init_declarator" {
13753 return child.child_by_field_name("declarator");
13754 }
13755 let field = field_name_in_parent(declaration, child);
13756 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
13757 || (declaration.kind() == "type_definition"
13758 && child.kind() == "type_identifier"
13759 && matches!(field, Some("declarator") | None))
13760 {
13761 Some(child)
13762 } else {
13763 None
13764 }
13765}
13766
13767#[derive(Clone, Debug, PartialEq, Eq)]
13770pub struct RecoveredNamespaceRegion {
13771 pub start: usize,
13773 pub end: usize,
13775 pub components: Vec<String>,
13778 pub component_ranges: Vec<Range>,
13787 pub component_closes: Vec<Option<Range>>,
13793}
13794
13795#[derive(Clone, Debug, Default)]
13857pub struct OrphanedNamespaceScopeIndex {
13858 regions: Vec<RecoveredNamespaceRegion>,
13859 brace_closes: HashMap<usize, Range>,
13862}
13863
13864impl OrphanedNamespaceScopeIndex {
13865 pub fn build(root: Node<'_>, source: &str) -> Self {
13866 if !root.has_error() {
13867 return Self::default();
13868 }
13869 #[derive(Clone, Copy)]
13875 struct OpenHead {
13876 head: Range,
13877 open: usize,
13878 }
13879 struct PendingRegion {
13882 start: usize,
13883 end: usize,
13884 components: Vec<String>,
13885 heads: Vec<OpenHead>,
13886 }
13887 struct Frame<'tree> {
13888 node: Node<'tree>,
13889 children: Vec<Node<'tree>>,
13890 next: usize,
13891 parsed_scope: Vec<String>,
13892 run: Option<PendingRegion>,
13893 }
13894 fn frame<'tree>(
13895 node: Node<'tree>,
13896 mut parsed_scope: Vec<String>,
13897 source: &str,
13898 ) -> Frame<'tree> {
13899 if node.kind() == "namespace_definition"
13900 && let Some(name) = node.child_by_field_name("name")
13901 {
13902 let mut components = Vec::new();
13903 if append_cpp_name_components(name, source, &mut components).is_some() {
13904 parsed_scope.extend(components);
13905 }
13906 }
13907 let mut cursor = node.walk();
13908 Frame {
13909 node,
13910 children: node.children(&mut cursor).collect(),
13911 next: 0,
13912 parsed_scope,
13913 run: None,
13914 }
13915 }
13916 let mut regions = Vec::new();
13917 let mut brace_closes = HashMap::default();
13918 let mut unpaired_closes: Vec<Range> = Vec::new();
13924 let mut open = Vec::new();
13928 let mut lexical_scope = Vec::new();
13929 let mut lexical_heads: Vec<OpenHead> = Vec::new();
13934 let mut frames = vec![frame(root, Vec::new(), source)];
13935 while !frames.is_empty() {
13940 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
13941 let current = frames.last_mut().expect("frames is non-empty");
13942 if current.next == current.children.len() {
13943 regions.extend(frames.pop().expect("the frame just borrowed").run);
13944 continue;
13945 }
13946 let child = current.children[current.next];
13947 current.next += 1;
13948 match child.kind() {
13949 "{" if !child.is_missing() => {
13950 regions.extend(current.run.take());
13951 let mut named = parent_node.and_then(|parent| {
13956 let components =
13957 namespace_body_name_components(parent, current.node, source);
13958 (!components.is_empty()).then_some((parent, components))
13959 });
13960 if named.is_none() {
13961 named = recovered_namespace_open_components(
13962 ¤t.children[..current.next - 1],
13963 source,
13964 );
13965 }
13966 open.push((child.start_byte(), lexical_scope.len()));
13967 if let Some((written_at, components)) = named {
13968 let head = OpenHead {
13969 head: Range {
13970 start_byte: written_at.start_byte(),
13971 end_byte: child.end_byte(),
13972 start_line: written_at.start_position().row + 1,
13973 end_line: child.end_position().row + 1,
13974 },
13975 open: child.start_byte(),
13976 };
13977 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
13978 lexical_scope.extend(components);
13979 }
13980 continue;
13981 }
13982 "}" if !child.is_missing() => {
13983 regions.extend(current.run.take());
13984 let close = Range {
13985 start_byte: child.start_byte(),
13986 end_byte: child.end_byte(),
13987 start_line: child.start_position().row + 1,
13988 end_line: child.end_position().row + 1,
13989 };
13990 match open.pop() {
13991 Some((start, namespace_len)) => {
13992 debug_assert!(
13993 start < close.start_byte,
13994 "a close pairs with an open before it: {start} then {close:?}"
13995 );
13996 lexical_scope.truncate(namespace_len);
13997 lexical_heads.truncate(namespace_len);
13998 brace_closes.insert(start, close);
13999 }
14000 None => unpaired_closes.push(close),
14001 }
14002 continue;
14003 }
14004 _ => {}
14005 }
14006 if current.node.kind() != "namespace_definition"
14010 && lexical_scope != current.parsed_scope
14011 {
14012 match &mut current.run {
14013 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14014 run => {
14015 regions.extend(run.take());
14016 *run = Some(PendingRegion {
14017 start: child.start_byte(),
14018 end: child.end_byte(),
14019 components: lexical_scope.clone(),
14020 heads: lexical_heads.clone(),
14021 });
14022 }
14023 }
14024 } else {
14025 regions.extend(current.run.take());
14026 }
14027 if child.has_error() {
14031 let parsed_scope = current.parsed_scope.clone();
14032 frames.push(frame(child, parsed_scope, source));
14033 }
14034 }
14035 if !unpaired_closes.is_empty() {
14044 brace_closes.clear();
14045 }
14046 let regions = regions
14051 .into_iter()
14052 .map(|region| {
14053 debug_assert_eq!(
14054 region.components.len(),
14055 region.heads.len(),
14056 "every recovered namespace level carries the head that writes it: {:?}",
14057 region.components
14058 );
14059 let component_closes: Vec<_> = region
14060 .heads
14061 .iter()
14062 .map(|level| brace_closes.get(&level.open).copied())
14063 .collect();
14064 let component_ranges = region
14065 .heads
14066 .iter()
14067 .zip(&component_closes)
14068 .map(|(level, close)| match close {
14069 Some(close) => Range {
14070 start_byte: level.head.start_byte,
14071 end_byte: close.end_byte,
14072 start_line: level.head.start_line,
14073 end_line: close.end_line,
14074 },
14075 None => level.head,
14076 })
14077 .collect();
14078 RecoveredNamespaceRegion {
14079 start: region.start,
14080 end: region.end,
14081 components: region.components,
14082 component_ranges,
14083 component_closes,
14084 }
14085 })
14086 .collect();
14087 Self {
14088 regions,
14089 brace_closes,
14090 }
14091 }
14092
14093 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14100 self.brace_closes.get(&open).copied()
14101 }
14102
14103 pub fn is_empty(&self) -> bool {
14104 self.regions.is_empty()
14105 }
14106
14107 pub fn approximate_size(&self) -> usize {
14109 self.regions.iter().fold(
14110 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14111 |total, region| {
14112 total
14113 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14114 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14115 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14116 .saturating_add(
14117 region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
14118 )
14119 },
14120 )
14121 }
14122
14123 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14125 self.regions
14126 .iter()
14127 .filter(|region| region.start <= byte && byte < region.end)
14128 .min_by_key(|region| region.end - region.start)
14129 }
14130
14131 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14135 self.enclosing_namespace_levels(node, source)
14136 .into_iter()
14137 .map(|(component, _)| component)
14138 .collect()
14139 }
14140
14141 pub fn enclosing_namespace_levels(&self, node: Node<'_>, source: &str) -> Vec<(String, Range)> {
14148 let mut parsed = Vec::new();
14149 let mut current = node.parent();
14150 while let Some(parent) = current {
14151 if parent.kind() == "namespace_definition"
14152 && let Some(name) = parent.child_by_field_name("name")
14153 {
14154 let mut components = Vec::new();
14155 if append_cpp_name_components(name, source, &mut components).is_some() {
14156 let range = node_range(parent);
14157 parsed.push((
14158 parent.start_byte(),
14159 components
14160 .into_iter()
14161 .map(|component| (component, range))
14162 .collect(),
14163 namespace_definition_has_real_braces(parent),
14164 ));
14165 }
14166 }
14167 current = parent.parent();
14168 }
14169 parsed.reverse();
14170 self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
14171 region
14172 .components
14173 .iter()
14174 .take(active_components)
14175 .cloned()
14176 .zip(
14177 region
14178 .component_ranges
14179 .iter()
14180 .take(active_components)
14181 .copied(),
14182 )
14183 .collect()
14184 })
14185 }
14186
14187 pub fn restore_enclosing_namespaces(
14193 &self,
14194 parsed: Vec<(usize, Vec<String>, bool)>,
14195 node_start: usize,
14196 ) -> Vec<String> {
14197 self.splice_recovered_path(parsed, node_start, |region, active_components| {
14198 region
14199 .components
14200 .iter()
14201 .take(active_components)
14202 .cloned()
14203 .collect()
14204 })
14205 }
14206
14207 fn splice_recovered_path<T>(
14213 &self,
14214 parsed: Vec<(usize, Vec<T>, bool)>,
14215 node_start: usize,
14216 recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
14217 ) -> Vec<T> {
14218 let Some(region) = self.region_at(node_start) else {
14219 return parsed
14220 .into_iter()
14221 .flat_map(|(_, levels, _)| levels)
14222 .collect();
14223 };
14224 debug_assert_eq!(
14225 region.components.len(),
14226 region.component_closes.len(),
14227 "every recovered namespace component carries its close proof: {region:?}"
14228 );
14229 if region.components.is_empty() {
14230 return parsed
14231 .into_iter()
14232 .filter(|(_, _, complete)| *complete)
14233 .flat_map(|(_, levels, _)| levels)
14234 .collect();
14235 }
14236 let active_components = region
14237 .component_closes
14238 .iter()
14239 .take_while(|close| {
14240 close
14241 .as_ref()
14242 .is_none_or(|close| node_start < close.end_byte)
14243 })
14244 .count();
14245 recovered(region, active_components)
14246 .into_iter()
14247 .chain(
14248 parsed
14249 .into_iter()
14250 .filter(|(start, _, _)| *start >= region.start)
14251 .flat_map(|(_, levels, _)| levels),
14252 )
14253 .collect()
14254 }
14255}
14256
14257pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
14261 debug_assert_eq!(namespace.kind(), "namespace_definition");
14262 let Some(body) = namespace.child_by_field_name("body") else {
14263 return false;
14264 };
14265 let mut opening = false;
14266 let mut closing = false;
14267 let mut cursor = body.walk();
14268 for child in body.children(&mut cursor) {
14269 match child.kind() {
14270 "{" if !child.is_missing() => opening = true,
14271 "}" if !child.is_missing() => closing = true,
14272 _ => {}
14273 }
14274 }
14275 opening && closing
14276}
14277
14278fn recovered_namespace_open_components<'tree>(
14294 preceding: &[Node<'tree>],
14295 source: &str,
14296) -> Option<(Node<'tree>, Vec<String>)> {
14297 let mut head = Vec::new();
14298 for &sibling in preceding.iter().rev() {
14299 if sibling.kind() != "comment" {
14300 head.push(sibling);
14301 if head.len() == 2 {
14302 break;
14303 }
14304 }
14305 }
14306 let [name, keyword] = head[..] else {
14307 return None;
14308 };
14309 if keyword.kind() != "namespace" {
14310 return None;
14311 }
14312 let mut components = Vec::new();
14313 append_cpp_name_components(name, source, &mut components)?;
14314 (!components.is_empty()).then_some((keyword, components))
14315}
14316
14317fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14320 let mut components = Vec::new();
14321 if body.kind() == "declaration_list"
14322 && parent.kind() == "namespace_definition"
14323 && parent.child_by_field_name("body") == Some(body)
14324 && let Some(name) = parent.child_by_field_name("name")
14325 && append_cpp_name_components(name, source, &mut components).is_none()
14326 {
14327 components.clear();
14328 }
14329 components
14330}
14331
14332#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14333pub enum RecoveredDeclaratorTypeContext {
14334 Declaration,
14335 FunctionDefinition,
14336 Parameter,
14337}
14338
14339pub fn recovered_macro_decorated_declarator_type(
14354 node: Node<'_>,
14355) -> Option<RecoveredDeclaratorTypeContext> {
14356 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14357}
14358
14359pub fn recovered_macro_decorated_type_node(
14364 node: Node<'_>,
14365) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14366 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14367 return None;
14368 }
14369 let qualified = node.parent()?;
14370 if qualified.kind() != "qualified_identifier"
14371 || qualified.child_by_field_name("scope") != Some(node)
14372 || !(0..qualified.child_count())
14373 .filter_map(|index| qualified.child(index))
14374 .any(|child| child.kind() == "::" && child.is_missing())
14375 {
14376 return None;
14377 }
14378 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14379 return None;
14380 }
14381
14382 let (declaration, context) = recovered_declarator_container(qualified)?;
14383 let type_node = declaration
14384 .child_by_field_name("type")
14385 .filter(|type_node| {
14386 *type_node != qualified
14387 && !type_node.is_missing()
14388 && type_node.start_byte() != type_node.end_byte()
14389 })?;
14390 Some((type_node, context))
14391}
14392
14393fn recovered_declarator_container(
14394 mut declarator: Node<'_>,
14395) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14396 loop {
14397 let parent = declarator.parent()?;
14398 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14399 return Some((
14400 parent
14401 .parent()
14402 .filter(|declaration| declaration.kind() == "declaration")?,
14403 RecoveredDeclaratorTypeContext::Declaration,
14404 ));
14405 }
14406 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14407 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14408 }
14409 if parent.kind() == "function_definition"
14410 && has_field_child(parent, "declarator", declarator)
14411 {
14412 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14413 }
14414 if matches!(
14420 parent.kind(),
14421 "parameter_declaration" | "optional_parameter_declaration"
14422 ) && has_field_child(parent, "declarator", declarator)
14423 {
14424 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14425 }
14426 if !matches!(
14427 parent.kind(),
14428 "array_declarator"
14429 | "function_declarator"
14430 | "parenthesized_declarator"
14431 | "pointer_declarator"
14432 | "pointer_type_declarator"
14433 | "reference_declarator"
14434 ) || !has_field_child(parent, "declarator", declarator)
14435 {
14436 return None;
14437 }
14438 declarator = parent;
14439 }
14440}
14441
14442fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14443 let mut cursor = parent.walk();
14444 parent
14445 .children_by_field_name(field, &mut cursor)
14446 .any(|child| child == target)
14447}
14448
14449fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14450 loop {
14451 if node.is_missing() || node.start_byte() == node.end_byte() {
14452 return false;
14453 }
14454 match node.kind() {
14455 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14456 return true;
14457 }
14458 "array_declarator"
14459 | "function_declarator"
14460 | "parenthesized_declarator"
14461 | "pointer_declarator"
14462 | "pointer_type_declarator"
14463 | "reference_declarator" => {
14464 let Some(declarator) = node.child_by_field_name("declarator") else {
14465 return false;
14466 };
14467 node = declarator;
14468 }
14469 _ => return false,
14470 }
14471 }
14472}
14473
14474pub enum DesignatedInitializerOwner {
14476 Resolved(CodeUnit),
14477 Unresolved,
14478}
14479
14480enum InitializerOwnerStep {
14481 Field(String),
14482 AggregateWrapper,
14483}
14484
14485pub fn designated_initializer_owner(
14495 analyzer: &CppGraphSource<'_>,
14496 visibility: &VisibilityIndex<'_>,
14497 file: &ProjectFile,
14498 source: &str,
14499 node: Node<'_>,
14500) -> Option<DesignatedInitializerOwner> {
14501 if let Some(designator) = node
14502 .parent()
14503 .filter(|parent| parent.kind() == "field_designator")
14504 {
14505 let pair = designator.parent()?;
14506 if pair.kind() != "initializer_pair" {
14507 return None;
14508 }
14509 let mut cursor = pair.walk();
14510 let designators = pair
14511 .children_by_field_name("designator", &mut cursor)
14512 .collect::<Vec<_>>();
14513 let position = designators
14514 .iter()
14515 .position(|candidate| same_node(*candidate, designator))?;
14516 let initializer = pair.parent()?;
14517 if initializer.kind() != "initializer_list" {
14518 return None;
14519 }
14520 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14521 for prior in &designators[..position] {
14522 let field = prior
14523 .child_by_field_name("field")
14524 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14525 owner = owner.and_then(|owner| {
14526 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14527 });
14528 }
14529 return Some(classified_designated_owner(owner));
14530 }
14531
14532 let init_declarator = node.parent()?;
14533 if init_declarator.child_by_field_name("declarator") != Some(node)
14534 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14535 {
14536 return None;
14537 }
14538 Some(classified_designated_owner(declaration_owner(
14539 analyzer,
14540 visibility,
14541 file,
14542 source,
14543 init_declarator.parent()?,
14544 )))
14545}
14546
14547fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14548 owner.map_or(
14549 DesignatedInitializerOwner::Unresolved,
14550 DesignatedInitializerOwner::Resolved,
14551 )
14552}
14553
14554fn initializer_list_owner(
14555 analyzer: &CppGraphSource<'_>,
14556 visibility: &VisibilityIndex<'_>,
14557 file: &ProjectFile,
14558 source: &str,
14559 initializer: Node<'_>,
14560) -> Option<CodeUnit> {
14561 let mut current = initializer;
14562 let mut steps = Vec::new();
14563 loop {
14564 let parent = current.parent()?;
14565 match parent.kind() {
14566 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14567 let designator = parent.child_by_field_name("designator")?;
14568 let step = designator
14569 .child_by_field_name("field")
14570 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14571 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14572 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14573 steps.push(step);
14574 current = parent.parent()?;
14575 }
14576 "initializer_list" => {
14577 current = parent;
14578 }
14579 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14580 let declaration = parent.parent()?;
14581 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14582 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14583 }
14584 "compound_literal_expression"
14585 if parent.child_by_field_name("value") == Some(current) =>
14586 {
14587 let type_node = parent.child_by_field_name("type")?;
14588 let owner =
14589 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
14590 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14591 }
14592 "ERROR" => current = parent,
14593 _ => return None,
14594 }
14595 }
14596}
14597
14598fn apply_initializer_owner_steps(
14599 analyzer: &CppGraphSource<'_>,
14600 visibility: &VisibilityIndex<'_>,
14601 file: &ProjectFile,
14602 mut owner: CodeUnit,
14603 steps: Vec<InitializerOwnerStep>,
14604) -> Option<CodeUnit> {
14605 for step in steps.into_iter().rev() {
14606 if let InitializerOwnerStep::Field(field_name) = step {
14607 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
14608 }
14609 }
14610 Some(owner)
14611}
14612
14613fn initializer_field_owner(
14614 analyzer: &CppGraphSource<'_>,
14615 visibility: &VisibilityIndex<'_>,
14616 file: &ProjectFile,
14617 owner: CodeUnit,
14618 field_name: &str,
14619) -> Option<CodeUnit> {
14620 let fields = visibility
14621 .visible_members_for_owner_name(file, &owner, field_name)
14622 .into_iter()
14623 .filter(|field| field.is_field())
14624 .collect::<Vec<_>>();
14625 let field = match fields.as_slice() {
14626 [field] => *field,
14627 _ => return None,
14628 };
14629 field_declared_binding(analyzer, visibility, file, field)?.unit
14630}
14631
14632fn declaration_owner(
14633 analyzer: &CppGraphSource<'_>,
14634 visibility: &VisibilityIndex<'_>,
14635 file: &ProjectFile,
14636 source: &str,
14637 declaration: Node<'_>,
14638) -> Option<CodeUnit> {
14639 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
14640 return None;
14641 }
14642 let type_node = declaration
14643 .child_by_field_name("type")
14644 .or_else(|| first_type_child(declaration))?;
14645 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
14646}
14647
14648fn resolve_designated_owner_type(
14649 analyzer: &CppGraphSource<'_>,
14650 visibility: &VisibilityIndex<'_>,
14651 file: &ProjectFile,
14652 source: &str,
14653 type_node: Node<'_>,
14654) -> Option<CodeUnit> {
14655 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
14656 return Some(owner);
14657 }
14658 let type_name = normalize_type_text(node_text(type_node, source));
14659 visibility
14660 .resolve_type(file, &type_name)
14661 .filter(CodeUnit::is_class)
14662}
14663
14664pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
14665 let mut cursor = node.walk();
14666 node.named_children(&mut cursor).find(|child| {
14667 matches!(
14668 child.kind(),
14669 "type_identifier"
14670 | "primitive_type"
14671 | "qualified_identifier"
14672 | "scoped_type_identifier"
14673 | "struct_specifier"
14674 | "union_specifier"
14675 | "enum_specifier"
14676 )
14677 })
14678}
14679
14680pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
14681 visibility: &VisibilityIndex<'_>,
14682 file: &ProjectFile,
14683 source: &str,
14684 declarator: Node<'_>,
14685 type_text: Option<&str>,
14686 bindings: &LocalInferenceEngine<T>,
14687) -> bool {
14688 if !has_ancestor_kind(declarator, "compound_statement") {
14689 return false;
14690 }
14691 if declarator
14692 .child_by_field_name("declarator")
14693 .is_none_or(|declarator| declarator.kind() != "identifier")
14694 {
14695 return false;
14696 }
14697 if !type_text
14698 .and_then(|text| visibility.resolve_type(file, text))
14699 .is_some_and(|unit| unit.is_class())
14700 {
14701 return false;
14702 }
14703 declarator
14704 .child_by_field_name("parameters")
14705 .is_some_and(|parameters| {
14706 constructor_parameters_look_like_expressions(parameters, source, bindings)
14707 })
14708}
14709
14710fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
14711 parameters: Node<'_>,
14712 source: &str,
14713 bindings: &LocalInferenceEngine<T>,
14714) -> bool {
14715 let mut cursor = parameters.walk();
14716 parameters.named_children(&mut cursor).any(|parameter| {
14717 !matches!(
14718 parameter.kind(),
14719 "parameter_declaration" | "optional_parameter_declaration"
14720 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
14721 })
14722}
14723
14724fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
14725 parameter: Node<'_>,
14726 source: &str,
14727 bindings: &LocalInferenceEngine<T>,
14728) -> bool {
14729 let text = node_text(parameter, source).trim();
14730 if text
14731 .chars()
14732 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
14733 && bindings.is_shadowed(text)
14734 {
14735 return true;
14736 }
14737
14738 let Some(base) = parameter
14739 .child_by_field_name("type")
14740 .filter(|base| base.kind() == "type_identifier")
14741 else {
14742 return false;
14743 };
14744 let Some(subscript) = parameter
14745 .child_by_field_name("declarator")
14746 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
14747 else {
14748 return false;
14749 };
14750 subscript.child_by_field_name("size").is_some()
14751 && bindings.is_shadowed(node_text(base, source).trim())
14752}
14753
14754pub fn is_declaration_name(node: Node<'_>) -> bool {
14755 let Some(parent) = node.parent() else {
14756 return false;
14757 };
14758 if parent
14759 .child_by_field_name("name")
14760 .is_some_and(|name| same_node(name, node))
14761 {
14762 if matches!(
14763 parent.kind(),
14764 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14765 ) {
14766 return cpp_tag_specifier_declares_name(parent);
14767 }
14768 if matches!(
14769 parent.kind(),
14770 "namespace_definition"
14771 | "namespace_alias_definition"
14772 | "alias_declaration"
14773 | "enumerator"
14774 ) {
14775 return true;
14776 }
14777 }
14778
14779 let mut current = Some(parent);
14780 while let Some(ancestor) = current {
14781 let type_definition = ancestor.kind() == "type_definition";
14782 let mut child_cursor = ancestor.walk();
14783 if ancestor.named_children(&mut child_cursor).any(|child| {
14784 declaration_declarator(ancestor, child).is_some_and(|declarator| {
14785 declarator_name_path_contains(declarator, node, type_definition)
14786 })
14787 }) {
14788 return true;
14789 }
14790 if matches!(
14791 ancestor.kind(),
14792 "declaration"
14793 | "field_declaration"
14794 | "parameter_declaration"
14795 | "optional_parameter_declaration"
14796 | "function_definition"
14797 | "type_definition"
14798 | "alias_declaration"
14799 | "template_instantiation"
14800 | "class_specifier"
14801 | "struct_specifier"
14802 | "union_specifier"
14803 | "enum_specifier"
14804 ) {
14805 return false;
14806 }
14807 current = ancestor.parent();
14808 }
14809 false
14810}
14811
14812pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
14821 if !matches!(
14822 node.kind(),
14823 "qualified_identifier" | "scoped_type_identifier"
14824 ) {
14825 return false;
14826 }
14827 let Some(declaration) = node
14828 .parent()
14829 .filter(|parent| parent.kind() == "declaration")
14830 else {
14831 return false;
14832 };
14833 if declaration.child_by_field_name("declarator") != Some(node)
14834 || !declaration
14835 .child_by_field_name("type")
14836 .is_some_and(|friend| {
14837 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
14838 })
14839 {
14840 return false;
14841 }
14842 let mut cursor = declaration.walk();
14843 let mut errors = declaration
14844 .named_children(&mut cursor)
14845 .filter(|child| child.kind() == "ERROR");
14846 let Some(error) = errors.next() else {
14847 return false;
14848 };
14849 errors.next().is_none()
14850 && error.named_child_count() == 1
14851 && error.named_child(0).is_some_and(|class| {
14852 class.kind() == "identifier" && node_text(class, source) == "class"
14853 })
14854}
14855
14856pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
14857 if !matches!(node.kind(), "identifier" | "field_identifier") {
14858 return false;
14859 }
14860 if let Some(parent) = node.parent() {
14861 if parent.kind() == "call_expression"
14862 && parent.child_by_field_name("function") == Some(node)
14863 {
14864 return false;
14865 }
14866 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
14867 && parent.child_by_field_name("label") == Some(node)
14868 {
14869 return false;
14870 }
14871 }
14872 if is_declaration_name(node) {
14873 return false;
14874 }
14875 let mut current = node.parent();
14876 while let Some(ancestor) = current {
14877 match ancestor.kind() {
14878 "preproc_ifdef" | "preproc_ifndef" => {
14879 if ancestor
14880 .child_by_field_name("name")
14881 .is_some_and(|name| node_range_contains(name, node))
14882 {
14883 return false;
14884 }
14885 }
14886 "preproc_if" | "preproc_elif" => {
14887 if ancestor
14888 .child_by_field_name("condition")
14889 .is_some_and(|condition| node_range_contains(condition, node))
14890 {
14891 return false;
14892 }
14893 }
14894 "preproc_else" => {}
14895 kind if kind.starts_with("preproc_") => return false,
14896 _ => {}
14897 }
14898 if matches!(
14899 ancestor.kind(),
14900 "translation_unit" | "function_definition" | "compound_statement"
14901 ) {
14902 break;
14903 }
14904 current = ancestor.parent();
14905 }
14906 true
14907}
14908
14909fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
14910 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
14911}
14912
14913fn recovered_c_reference_node(
14914 visibility: &VisibilityIndex<'_>,
14915 file: &ProjectFile,
14916 node: Node<'_>,
14917 source: &str,
14918) -> bool {
14919 if node.start_byte() >= node.end_byte()
14920 || node.is_error()
14921 || node.is_missing()
14922 || !matches!(
14923 node.kind(),
14924 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
14925 )
14926 || recovered_c_macro_binding_role(node)
14927 || recovered_c_label_role(node)
14928 {
14929 return false;
14930 }
14931 let name = node_text(node, source);
14939 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
14940 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
14941 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
14942 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
14943 if is_declaration_name(node)
14944 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
14945 && !recovered_function_call
14946 && !recovered_macro_call
14947 && !recovered_parenthesized_reference
14948 {
14949 return false;
14950 }
14951
14952 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
14953 return true;
14954 }
14955 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
14956 return true;
14957 }
14958 if recovered_parenthesized_reference {
14959 return true;
14960 }
14961 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
14962 if recovered_function_call {
14963 return true;
14964 }
14965 return visibility
14966 .visible_identifier_candidates(file, name)
14967 .any(|candidate| candidate.is_class() || candidate.is_module());
14968 }
14969 let visible = visibility
14970 .visible_identifier_candidates(file, name)
14971 .next()
14972 .is_some();
14973 visible
14974 && (recovered_c_reference_anchor(node)
14975 || recovered_c_error_expression_leaf(node)
14976 || recovered_function_call)
14977}
14978
14979fn push_recovered_c_range(
14980 ranges: &mut Vec<Range>,
14981 seen: &mut HashSet<(usize, usize)>,
14982 start_byte: usize,
14983 end_byte: usize,
14984 node: Node<'_>,
14985 limit: usize,
14986) -> bool {
14987 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
14988 return true;
14989 }
14990 if ranges.len() >= limit {
14991 return false;
14992 }
14993 ranges.push(Range {
14994 start_byte,
14995 end_byte,
14996 start_line: node.start_position().row,
14997 end_line: node.end_position().row,
14998 });
14999 true
15000}
15001
15002fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15007 let mut current = node.parent();
15008 while let Some(parent) = current {
15009 if parent.is_error() {
15010 let Some(anchor) = parent.parent() else {
15011 return false;
15012 };
15013 return anchor.kind().ends_with("_expression")
15014 || matches!(
15015 anchor.kind(),
15016 "argument_list"
15017 | "return_statement"
15018 | "expression_statement"
15019 | "case_statement"
15020 | "initializer_list"
15021 | "field_designator"
15022 | "enumerator"
15023 );
15024 }
15025 if matches!(
15026 parent.kind(),
15027 "translation_unit" | "function_definition" | "compound_statement"
15028 ) {
15029 return false;
15030 }
15031 current = parent.parent();
15032 }
15033 false
15034}
15035
15036fn recovered_c_function_call(
15043 visibility: &VisibilityIndex<'_>,
15044 file: &ProjectFile,
15045 node: Node<'_>,
15046 name: &str,
15047) -> bool {
15048 if !matches!(
15049 node.kind(),
15050 "identifier" | "field_identifier" | "type_identifier"
15051 ) {
15052 return false;
15053 }
15054 let error_call_prefix = node.parent().is_some_and(|error| {
15059 error.is_error()
15060 && error
15061 .parent()
15062 .is_some_and(|parent| parent.kind() == "compound_statement")
15063 }) && node
15064 .prev_sibling()
15065 .is_none_or(|previous| previous.kind() == ";")
15066 && node.next_sibling().is_some_and(|open| {
15067 open.kind() == "("
15068 && open.next_named_sibling().is_some_and(|argument| {
15069 argument.kind() == "parameter_declaration" && !argument.has_error()
15070 })
15071 });
15072 (error_call_prefix || recovered_c_function_declarator_invocation(node))
15073 && visibility
15074 .visible_identifier_candidates(file, name)
15075 .any(CodeUnit::is_function)
15076}
15077
15078fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15088 let mut function_declarator = if node.parent().is_some_and(|parent| {
15089 parent.kind() == "function_declarator"
15090 && parent.child_by_field_name("declarator") == Some(node)
15091 }) {
15092 node.parent().expect("checked function declarator parent")
15093 } else {
15094 let Some(parameter) = node.parent().filter(|parent| {
15095 parent.kind() == "parameter_declaration"
15096 && parent.child_by_field_name("type") == Some(node)
15097 }) else {
15098 return false;
15099 };
15100 if !parameter
15101 .child_by_field_name("declarator")
15102 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15103 {
15104 return false;
15105 }
15106 let Some(parameters) = parameter
15107 .parent()
15108 .filter(|parent| parent.kind() == "parameter_list")
15109 else {
15110 return false;
15111 };
15112 let Some(function_declarator) = parameters
15113 .parent()
15114 .filter(|parent| parent.kind() == "function_declarator")
15115 else {
15116 return false;
15117 };
15118 function_declarator
15119 };
15120
15121 while let Some(parent) = function_declarator.parent().filter(|parent| {
15124 parent.kind() == "function_declarator"
15125 && parent.child_by_field_name("declarator") == Some(function_declarator)
15126 }) {
15127 function_declarator = parent;
15128 }
15129 let Some(mut current) = function_declarator
15130 .parent()
15131 .filter(|parent| parent.is_error())
15132 else {
15133 return false;
15134 };
15135 loop {
15136 let Some(parent) = current.parent() else {
15137 return false;
15138 };
15139 if matches!(
15140 parent.kind(),
15141 "translation_unit"
15142 | "compound_statement"
15143 | "preproc_if"
15144 | "preproc_ifdef"
15145 | "preproc_ifndef"
15146 | "preproc_else"
15147 | "preproc_elif"
15148 ) {
15149 return true;
15150 }
15151 if parent.kind() == "function_definition"
15152 && parent.child_by_field_name("declarator") == Some(current)
15153 && parent.named_child(0) == Some(current)
15154 && parent.child_by_field_name("body").is_some()
15155 {
15156 return true;
15157 }
15158 if parent.is_error()
15159 || matches!(
15160 parent.kind(),
15161 "parameter_declaration"
15162 | "parameter_list"
15163 | "function_declarator"
15164 | "abstract_function_declarator"
15165 | "parenthesized_declarator"
15166 )
15167 {
15168 current = parent;
15169 continue;
15170 }
15171 return false;
15172 }
15173}
15174
15175fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15181 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15182 return false;
15183 };
15184 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15185 return false;
15186 }
15187 let Some(declarator) = error
15188 .parent()
15189 .filter(|parent| parent.kind() == "parenthesized_declarator")
15190 else {
15191 return false;
15192 };
15193 let Some(declaration) = declarator
15194 .parent()
15195 .filter(|parent| parent.kind() == "declaration")
15196 else {
15197 return false;
15198 };
15199 if declaration.child_by_field_name("declarator") != Some(declarator) {
15200 return false;
15201 }
15202 let Some(type_node) = declaration.child_by_field_name("type") else {
15203 return false;
15204 };
15205 type_node.kind() == "dependent_type"
15206 && type_node
15207 .child(0)
15208 .is_some_and(|keyword| keyword.kind() == "typename")
15209}
15210
15211fn recovered_c_explicit_assignment_callee(
15212 visibility: &VisibilityIndex<'_>,
15213 file: &ProjectFile,
15214 node: Node<'_>,
15215 name: &str,
15216) -> bool {
15217 let mut current = node;
15218 let error = loop {
15219 let Some(parent) = current.parent() else {
15220 return false;
15221 };
15222 if parent.is_error() {
15223 break parent;
15224 }
15225 current = parent;
15226 };
15227 let mut cursor = error.walk();
15228 let explicit_recovery_precedes_callee = error
15229 .named_children(&mut cursor)
15230 .take_while(|child| child.start_byte() < node.start_byte())
15231 .any(|child| child.kind() == "explicit_function_specifier");
15232 if !explicit_recovery_precedes_callee {
15233 return false;
15234 }
15235 visibility
15236 .visible_identifier_candidates(file, name)
15237 .any(CodeUnit::is_function)
15238}
15239
15240fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15241 while let Some(parent) = node.parent() {
15242 if matches!(
15243 parent.kind(),
15244 "preproc_def" | "preproc_function_def" | "preproc_params"
15245 ) {
15246 return true;
15247 }
15248 if parent.is_error()
15249 || matches!(
15250 parent.kind(),
15251 "translation_unit" | "function_definition" | "compound_statement"
15252 )
15253 {
15254 return false;
15255 }
15256 node = parent;
15257 }
15258 false
15259}
15260
15261fn recovered_c_label_role(node: Node<'_>) -> bool {
15262 node.parent().is_some_and(|parent| {
15263 matches!(parent.kind(), "labeled_statement" | "goto_statement")
15264 && parent.child_by_field_name("label") == Some(node)
15265 })
15266}
15267
15268fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15269 while let Some(parent) = node.parent() {
15270 if parent.is_error() {
15271 return false;
15272 }
15273 if parent.kind() == "optional_parameter_declaration"
15278 && parent
15279 .child_by_field_name("default_value")
15280 .is_some_and(|value| node_range_contains(value, node))
15281 {
15282 return true;
15283 }
15284 if parent.kind().ends_with("_expression")
15285 || matches!(
15286 parent.kind(),
15287 "argument_list"
15288 | "return_statement"
15289 | "expression_statement"
15290 | "case_statement"
15291 | "initializer_list"
15292 | "init_declarator"
15293 | "array_declarator"
15294 | "field_designator"
15295 | "enumerator"
15296 )
15297 {
15298 return true;
15299 }
15300 if matches!(
15301 parent.kind(),
15302 "translation_unit"
15303 | "function_definition"
15304 | "compound_statement"
15305 | "declaration"
15306 | "field_declaration"
15307 | "parameter_declaration"
15308 ) {
15309 return false;
15310 }
15311 node = parent;
15312 }
15313 false
15314}
15315
15316pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15324 let mut current = parameter.parent();
15325 while let Some(ancestor) = current {
15326 if ancestor.kind() == "lambda_expression" {
15327 return ancestor
15328 .child_by_field_name("declarator")
15329 .is_some_and(|declarator| {
15330 declarator.start_byte() <= parameter.start_byte()
15331 && parameter.end_byte() <= declarator.end_byte()
15332 });
15333 }
15334 if ancestor.kind() == "function_definition" {
15335 return ancestor
15336 .child_by_field_name("declarator")
15337 .is_some_and(|declarator| {
15338 declarator.start_byte() <= parameter.start_byte()
15339 && parameter.end_byte() <= declarator.end_byte()
15340 });
15341 }
15342 current = ancestor.parent();
15343 }
15344 false
15345}
15346
15347pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15348 let mut current = node.parent();
15349 while let Some(ancestor) = current {
15350 if matches!(
15351 ancestor.kind(),
15352 "parameter_declaration" | "optional_parameter_declaration"
15353 ) {
15354 return ancestor
15355 .child_by_field_name("type")
15356 .is_some_and(|type_node| {
15357 type_node.start_byte() <= node.start_byte()
15358 && node.end_byte() <= type_node.end_byte()
15359 });
15360 }
15361 if matches!(
15362 ancestor.kind(),
15363 "function_definition" | "lambda_expression" | "compound_statement"
15364 ) {
15365 return false;
15366 }
15367 current = ancestor.parent();
15368 }
15369 false
15370}
15371
15372fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15373 if specifier.child_by_field_name("body").is_some() {
15374 return true;
15375 }
15376 let mut current = specifier.parent();
15377 while let Some(ancestor) = current {
15378 match ancestor.kind() {
15379 "type_descriptor"
15380 | "parameter_declaration"
15381 | "optional_parameter_declaration"
15382 | "template_argument_list"
15383 | "cast_expression" => return false,
15384 "declaration" | "field_declaration" => {
15385 let mut cursor = ancestor.walk();
15386 return ancestor
15387 .children_by_field_name("declarator", &mut cursor)
15388 .next()
15389 .is_none();
15390 }
15391 "translation_unit" => return true,
15392 _ => current = ancestor.parent(),
15393 }
15394 }
15395 false
15396}
15397
15398pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15399 match node.kind() {
15400 "identifier"
15401 | "field_identifier"
15402 | "qualified_identifier"
15403 | "scoped_identifier"
15404 | "operator_name"
15405 | "destructor_name"
15406 | "literal_operator_name" => Some(node),
15407 "reference_declarator" | "parenthesized_declarator" => {
15408 node.named_child(0).and_then(declarator_name_node)
15409 }
15410 _ => node
15411 .child_by_field_name("declarator")
15412 .or_else(|| node.child_by_field_name("name"))
15413 .or_else(|| node.child_by_field_name("field"))
15414 .and_then(declarator_name_node),
15415 }
15416}
15417
15418fn declarator_name_path_contains(
15419 declarator: Node<'_>,
15420 candidate: Node<'_>,
15421 allow_type_identifier: bool,
15422) -> bool {
15423 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15424 return false;
15425 };
15426 let mut current = Some(declarator);
15427 while let Some(node) = current {
15428 if same_node(node, candidate) {
15429 return true;
15430 }
15431 if same_node(node, name) {
15432 return false;
15433 }
15434 current = node
15435 .child_by_field_name("declarator")
15436 .or_else(|| node.child_by_field_name("name"))
15437 .or_else(|| node.child_by_field_name("field"));
15438 }
15439 false
15440}
15441
15442fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15443 match node.kind() {
15444 "identifier"
15445 | "field_identifier"
15446 | "operator_name"
15447 | "destructor_name"
15448 | "literal_operator_name" => Some(node),
15449 "type_identifier" if allow_type_identifier => Some(node),
15450 _ => node
15451 .child_by_field_name("declarator")
15452 .or_else(|| node.child_by_field_name("name"))
15453 .or_else(|| node.child_by_field_name("field"))
15454 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15455 }
15456}
15457
15458pub fn is_nested_type_node(node: Node<'_>) -> bool {
15461 node.parent().is_some_and(|parent| {
15462 matches!(
15463 parent.kind(),
15464 "qualified_identifier" | "scoped_type_identifier" | "template_type"
15465 )
15466 })
15467}
15468
15469pub struct OutOfLineMemberDefinitionOwners<'tree> {
15470 pub owners: Vec<(Node<'tree>, CodeUnit)>,
15471 innermost: Option<(Node<'tree>, CodeUnit)>,
15472}
15473
15474impl OutOfLineMemberDefinitionOwners<'_> {
15475 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15476 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15477 }
15478}
15479
15480pub struct QualifiedOwnerComponents<'tree> {
15481 pub nodes: Vec<Node<'tree>>,
15482 pub names: Vec<String>,
15483 pub global: bool,
15484}
15485
15486pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15491 let mut stack = vec![node];
15492 let mut found_separator = false;
15493 while let Some(current) = stack.pop() {
15494 if !matches!(
15495 current.kind(),
15496 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15497 ) {
15498 continue;
15499 }
15500 let mut current_has_separator = false;
15501 for child in children_iter(current) {
15502 if child.kind() == "::" {
15503 if child.is_missing() {
15504 return false;
15505 }
15506 current_has_separator = true;
15507 found_separator = true;
15508 }
15509 }
15510 if !current_has_separator {
15511 return false;
15512 }
15513 for field in ["scope", "name"] {
15514 if let Some(child) = current.child_by_field_name(field)
15515 && matches!(
15516 child.kind(),
15517 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15518 )
15519 {
15520 stack.push(child);
15521 }
15522 }
15523 }
15524 found_separator
15525}
15526
15527pub fn qualified_owner_components<'tree>(
15528 node: Node<'tree>,
15529 source: &str,
15530) -> Option<QualifiedOwnerComponents<'tree>> {
15531 if !qualified_name_has_concrete_scope_separators(node) {
15532 return None;
15533 }
15534 let mut nodes = cpp_name_component_nodes(node)?;
15535 nodes.pop()?;
15536 if nodes.is_empty() {
15537 return None;
15538 }
15539 let names = nodes
15540 .iter()
15541 .map(|component| node_text(*component, source).to_string())
15542 .collect();
15543 Some(QualifiedOwnerComponents {
15544 nodes,
15545 names,
15546 global: is_globally_qualified_cpp_name(node),
15547 })
15548}
15549
15550pub fn out_of_line_member_definition_owner<'tree>(
15551 analyzer: &CppGraphSource<'_>,
15552 visibility: &VisibilityIndex<'_>,
15553 file: &ProjectFile,
15554 source: &str,
15555 node: Node<'tree>,
15556) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15557 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15558 || !has_ancestor_kind(node, "function_definition")
15559 || !is_function_declarator_name_root(node)
15560 {
15561 return None;
15562 }
15563 let qualified = qualified_owner_components(node, source)?;
15564 let lexical_scope = enclosing_namespace_components(node, source)?;
15565 let mut owners = Vec::new();
15566 let mut innermost = None;
15567
15568 for component_count in 1..=qualified.names.len() {
15569 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15570 .resolve_type_components_lexically(
15571 analyzer,
15572 file,
15573 &qualified.names[..component_count],
15574 qualified.global,
15575 &lexical_scope,
15576 )
15577 && !owners
15578 .iter()
15579 .any(|(_, existing)| same_visible_symbol(existing, &unit))
15580 {
15581 if component_count == qualified.names.len() {
15582 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
15583 }
15584 owners.push((qualified.nodes[component_count - 1], unit));
15585 }
15586 }
15587
15588 if innermost.is_none() {
15598 let indexed_owner_components = visibility
15599 .indexed_enclosing_owner_scope(analyzer, file, node)
15600 .or_else(|| {
15601 if qualified.names.len() <= 1 {
15606 return None;
15607 }
15608 let range = Range {
15609 start_byte: node.start_byte(),
15610 end_byte: node.end_byte(),
15611 start_line: node.start_position().row,
15612 end_line: node.end_position().row,
15613 };
15614 let start = analyzer.enclosing_code_unit(file, &range)?;
15615 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15616 brokk_bifrost_core::analyzer::Language::Cpp,
15617 &cpp_name_for(&start),
15618 );
15619 components.pop();
15620 Some(components)
15621 });
15622 if let Some(indexed_owner_components) = indexed_owner_components
15623 && indexed_owner_components.len() > qualified.names.len()
15624 && indexed_owner_components.ends_with(&qualified.names)
15625 && indexed_namespace_path_is_recoverable(
15626 &lexical_scope,
15627 &indexed_owner_components,
15628 qualified.names.len(),
15629 )
15630 && (qualified.names.len() > 1 || !qualified.global)
15635 {
15636 let namespace_count = indexed_owner_components.len() - qualified.names.len();
15637 for component_count in 1..=qualified.names.len() {
15638 let expected = &indexed_owner_components[..namespace_count + component_count];
15639 let owner_node = qualified.nodes[component_count - 1];
15640 for owner in visibility
15641 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
15642 .filter(|candidate| candidate.is_class())
15643 .filter(|candidate| {
15644 canonical_cpp_scope_components(candidate) == expected
15645 && visibility.external_type_candidate_visible_in_context(
15646 analyzer, file, candidate, node,
15647 )
15648 })
15649 {
15650 if component_count == qualified.names.len() && innermost.is_none() {
15651 innermost = Some((owner_node, owner.clone()));
15652 }
15653 if !owners
15654 .iter()
15655 .any(|(_, existing)| same_symbol(existing, owner))
15656 {
15657 owners.push((owner_node, owner.clone()));
15658 }
15659 }
15660 }
15661 }
15662 }
15663 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
15664}
15665
15666fn is_function_declarator_name_root(node: Node<'_>) -> bool {
15667 let mut current = node;
15668 while let Some(parent) = current.parent() {
15669 if parent.kind() == "function_declarator" {
15670 return parent.child_by_field_name("declarator") == Some(current);
15671 }
15672 if matches!(
15673 parent.kind(),
15674 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
15675 ) && parent.child_by_field_name("declarator") == Some(current)
15676 {
15677 current = parent;
15678 continue;
15679 }
15680 return false;
15681 }
15682 false
15683}
15684
15685pub fn append_cpp_name_components(
15686 node: Node<'_>,
15687 source: &str,
15688 out: &mut Vec<String>,
15689) -> Option<()> {
15690 out.extend(
15691 cpp_name_component_nodes(node)?
15692 .into_iter()
15693 .map(|component| node_text(component, source).to_string()),
15694 );
15695 Some(())
15696}
15697
15698pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
15699 let mut components = Vec::new();
15700 append_cpp_name_components(node, source, &mut components)?;
15701 Some(components)
15702}
15703
15704pub fn unique_macro_replacement_type_candidate(
15713 analyzer: &CppGraphSource<'_>,
15714 visibility: &VisibilityIndex<'_>,
15715 file: &ProjectFile,
15716 components: &[String],
15717) -> Option<CodeUnit> {
15718 let terminal = components.last()?;
15719 let mut candidates = Vec::new();
15720 for candidate in visibility
15721 .visible_identifier_candidates(file, terminal)
15722 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
15723 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
15724 {
15725 if !candidates
15726 .iter()
15727 .any(|existing| same_logical_symbol(existing, candidate))
15728 {
15729 candidates.push(candidate.clone());
15730 }
15731 }
15732 (candidates.len() == 1).then(|| candidates.remove(0))
15733}
15734
15735pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
15743 let mut parser = Parser::new();
15744 if parser
15745 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15746 .is_err()
15747 {
15748 return Vec::new();
15749 }
15750 let Some(tree) = parser.parse(source, None) else {
15751 return Vec::new();
15752 };
15753 let mut scopes = Vec::new();
15754 let mut pending = vec![tree.root_node()];
15755 while let Some(node) = pending.pop() {
15756 if node.kind() == "using_declaration" {
15757 let Some(imported) = node.named_child(0) else {
15758 continue;
15759 };
15760 let Some(mut components) = cpp_type_name_components(imported, source) else {
15761 continue;
15762 };
15763 if components.pop().as_deref() == Some(member) && !components.is_empty() {
15764 scopes.push(components.join("::"));
15765 }
15766 continue;
15767 }
15768 push_named_children_reversed(node, &mut pending);
15769 }
15770 scopes
15771}
15772
15773pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
15777 qualified == scope
15778 || qualified
15779 .strip_suffix(scope)
15780 .is_some_and(|prefix| prefix.ends_with("::"))
15781}
15782
15783pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
15788 let Some(type_descriptor) = node.parent() else {
15789 return false;
15790 };
15791 if type_descriptor.kind() != "type_descriptor"
15792 || type_descriptor.child_by_field_name("type") != Some(node)
15793 {
15794 return false;
15795 }
15796 let Some(arguments) = type_descriptor.parent() else {
15797 return false;
15798 };
15799 if arguments.kind() != "template_argument_list" {
15800 return false;
15801 }
15802 arguments.parent().is_some_and(|parent| {
15803 matches!(parent.kind(), "template_type" | "template_function")
15804 && parent.child_by_field_name("arguments") == Some(arguments)
15805 })
15806}
15807
15808pub fn cpp_template_reference_arguments(
15809 mut node: Node<'_>,
15810 source: &str,
15811) -> Option<Vec<CppTemplateExpression>> {
15812 loop {
15813 match node.kind() {
15814 "template_type" | "template_function" => {
15815 let arguments = node.child_by_field_name("arguments")?;
15816 let mut cursor = arguments.walk();
15817 return Some(
15818 arguments
15819 .named_children(&mut cursor)
15820 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
15821 .map(|argument| CppTemplateExpression {
15822 text: normalize_cpp_whitespace(node_text(argument, source)),
15823 term: cpp_template_term(
15825 argument,
15826 source,
15827 &[],
15828 &ParentIndex::unindexed(),
15829 ),
15830 })
15831 .collect(),
15832 );
15833 }
15834 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
15835 node = node
15836 .child_by_field_name("name")
15837 .or_else(|| node.child_by_field_name("type"))?;
15838 }
15839 _ => return None,
15840 }
15841 }
15842}
15843
15844fn cpp_reconcile_primary_template_parameters(
15845 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
15846 preferred: &CodeUnit,
15847) -> Option<Vec<CppTemplateParameterMetadata>> {
15848 let canonical = candidates
15849 .iter()
15850 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
15851 let mut merged = canonical
15852 .parameters
15853 .iter()
15854 .map(|parameter| CppTemplateParameterMetadata {
15855 name: parameter.name.clone(),
15856 kind: parameter.kind,
15857 variadic: parameter.variadic,
15858 default: None,
15859 })
15860 .collect::<Vec<_>>();
15861
15862 for (_, metadata) in candidates {
15863 if metadata.parameters.len() != merged.len() {
15864 return None;
15865 }
15866 let rename_bindings = metadata
15867 .parameters
15868 .iter()
15869 .zip(&merged)
15870 .map(|(parameter, canonical)| {
15871 (
15872 parameter.name.clone(),
15873 CppTemplateTerm::Parameter(canonical.name.clone()),
15874 )
15875 })
15876 .collect::<HashMap<_, _>>();
15877 for ((parameter, canonical), merged_parameter) in metadata
15878 .parameters
15879 .iter()
15880 .zip(&canonical.parameters)
15881 .zip(&mut merged)
15882 {
15883 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
15884 return None;
15885 }
15886 let Some(default) = ¶meter.default else {
15887 continue;
15888 };
15889 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
15890 if let Some(existing) = &merged_parameter.default {
15891 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
15892 return None;
15893 }
15894 } else {
15895 merged_parameter.default = Some(CppTemplateExpression {
15896 text: default.text.clone(),
15897 term: normalized_term,
15898 });
15899 }
15900 }
15901 }
15902 Some(merged)
15903}
15904
15905pub fn cpp_bind_template_arguments(
15906 parameters: &[CppTemplateParameterMetadata],
15907 explicit_arguments: &[CppTemplateExpression],
15908) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
15909 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
15910 if variadic_index.is_some_and(|index| {
15911 index + 1 != parameters.len()
15912 || parameters[index + 1..]
15913 .iter()
15914 .any(|parameter| parameter.variadic)
15915 }) {
15916 return None;
15917 }
15918 let fixed_count = variadic_index.unwrap_or(parameters.len());
15919 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
15920 return None;
15921 }
15922 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
15923 let mut expanded = explicit_arguments[..explicit_fixed_count]
15924 .iter()
15925 .map(cpp_clone_template_expression_iterative)
15926 .collect::<Vec<_>>();
15927 let mut bindings = HashMap::default();
15928 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
15929 bindings.insert(
15930 parameter.name.clone(),
15931 cpp_clone_template_term_iterative(&argument.term),
15932 );
15933 }
15934 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
15935 let default = parameter.default.as_ref()?;
15936 let term = cpp_substitute_template_term(&default.term, &bindings)?;
15937 bindings.insert(parameter.name.clone(), term.clone());
15938 expanded.push(CppTemplateExpression {
15939 text: default.text.clone(),
15940 term,
15941 });
15942 }
15943 if let Some(index) = variadic_index {
15944 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
15945 expanded.extend(
15946 packed_arguments
15947 .iter()
15948 .map(cpp_clone_template_expression_iterative),
15949 );
15950 bindings.insert(
15951 parameters[index].name.clone(),
15952 CppTemplateTerm::Node {
15953 kind: "parameter_pack".to_string(),
15954 children: packed_arguments
15955 .iter()
15956 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
15957 .collect(),
15958 },
15959 );
15960 }
15961 Some((expanded, bindings))
15962}
15963
15964fn cpp_specialization_matches(
15965 metadata: &CppTemplateMetadata,
15966 arguments: &[CppTemplateExpression],
15967) -> bool {
15968 if metadata.specialization_arguments.len() != arguments.len() {
15969 return false;
15970 }
15971 let parameter_names = metadata
15972 .parameters
15973 .iter()
15974 .map(|parameter| parameter.name.as_str())
15975 .collect::<HashSet<_>>();
15976 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
15977 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
15978 if !cpp_unify_template_term(
15979 &pattern.term,
15980 &argument.term,
15981 ¶meter_names,
15982 &mut bindings,
15983 ) {
15984 return false;
15985 }
15986 }
15987 true
15988}
15989
15990fn cpp_specialization_more_specialized(
15991 candidate: &CppTemplateMetadata,
15992 other: &CppTemplateMetadata,
15993) -> bool {
15994 cpp_specialization_pattern_accepts(other, candidate)
15995 && !cpp_specialization_pattern_accepts(candidate, other)
15996}
15997
15998fn cpp_specialization_pattern_accepts(
15999 broader: &CppTemplateMetadata,
16000 narrower: &CppTemplateMetadata,
16001) -> bool {
16002 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
16003 return false;
16004 }
16005 let parameter_names = broader
16006 .parameters
16007 .iter()
16008 .map(|parameter| parameter.name.as_str())
16009 .collect::<HashSet<_>>();
16010 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16011 broader
16012 .specialization_arguments
16013 .iter()
16014 .zip(&narrower.specialization_arguments)
16015 .all(|(pattern, argument)| {
16016 cpp_unify_template_term(
16017 &pattern.term,
16018 &argument.term,
16019 ¶meter_names,
16020 &mut bindings,
16021 )
16022 })
16023}
16024
16025pub fn cpp_substitute_template_term(
16026 term: &CppTemplateTerm,
16027 bindings: &HashMap<String, CppTemplateTerm>,
16028) -> Option<CppTemplateTerm> {
16029 enum Work<'a> {
16030 Visit(&'a CppTemplateTerm),
16031 Build { kind: String, child_count: usize },
16032 }
16033
16034 let mut work = vec![Work::Visit(term)];
16035 let mut substituted = Vec::new();
16036 while let Some(next) = work.pop() {
16037 match next {
16038 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16039 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16040 }
16041 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16042 substituted.push(CppTemplateTerm::Atom {
16043 kind: kind.clone(),
16044 text: text.clone(),
16045 });
16046 }
16047 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16048 work.push(Work::Build {
16049 kind: kind.clone(),
16050 child_count: children.len(),
16051 });
16052 work.extend(children.iter().rev().map(Work::Visit));
16053 }
16054 Work::Build { kind, child_count } => {
16055 let children = substituted.split_off(substituted.len() - child_count);
16056 substituted.push(CppTemplateTerm::Node { kind, children });
16057 }
16058 }
16059 }
16060 substituted.pop()
16061}
16062
16063pub fn cpp_substitute_template_arguments(
16064 arguments: &[CppTemplateExpression],
16065 bindings: &HashMap<String, CppTemplateTerm>,
16066) -> Option<Vec<CppTemplateExpression>> {
16067 let mut substituted = Vec::new();
16068 for argument in arguments {
16069 let CppTemplateTerm::Node { kind, children } = &argument.term else {
16070 substituted.push(CppTemplateExpression {
16071 text: argument.text.clone(),
16072 term: cpp_substitute_template_term(&argument.term, bindings)?,
16073 });
16074 continue;
16075 };
16076 if kind != "parameter_pack_expansion" {
16077 substituted.push(CppTemplateExpression {
16078 text: argument.text.clone(),
16079 term: cpp_substitute_template_term(&argument.term, bindings)?,
16080 });
16081 continue;
16082 }
16083 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16084 return None;
16085 };
16086 if ellipsis != "..." {
16087 return None;
16088 }
16089
16090 let mut pack_names = Vec::new();
16091 let mut work = vec![pattern];
16092 while let Some(term) = work.pop() {
16093 match term {
16094 CppTemplateTerm::Parameter(name)
16095 if matches!(
16096 bindings.get(name),
16097 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16098 ) =>
16099 {
16100 if !pack_names.contains(name) {
16101 pack_names.push(name.clone());
16102 }
16103 }
16104 CppTemplateTerm::Node { children, .. } => work.extend(children),
16105 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16106 }
16107 }
16108 let first_pack = pack_names.first()?;
16109 let CppTemplateTerm::Node {
16110 children: first_elements,
16111 ..
16112 } = bindings.get(first_pack)?
16113 else {
16114 return None;
16115 };
16116 let pack_len = first_elements.len();
16117 for pack_name in &pack_names {
16118 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16119 return None;
16120 };
16121 if children.len() != pack_len {
16122 return None;
16123 }
16124 }
16125 for index in 0..pack_len {
16126 let mut element_bindings = bindings.clone();
16127 for pack_name in &pack_names {
16128 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16129 return None;
16130 };
16131 element_bindings.insert(
16132 pack_name.clone(),
16133 cpp_clone_template_term_iterative(&children[index]),
16134 );
16135 }
16136 substituted.push(CppTemplateExpression {
16137 text: argument.text.clone(),
16138 term: cpp_substitute_template_term(pattern, &element_bindings)?,
16139 });
16140 }
16141 }
16142 Some(substituted)
16143}
16144
16145fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16146 enum Work<'a> {
16147 Visit(&'a CppTemplateTerm),
16148 Build { kind: String, child_count: usize },
16149 }
16150
16151 let mut work = vec![Work::Visit(term)];
16152 let mut cloned = Vec::new();
16153 while let Some(next) = work.pop() {
16154 match next {
16155 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16156 cloned.push(CppTemplateTerm::Parameter(name.clone()));
16157 }
16158 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16159 cloned.push(CppTemplateTerm::Atom {
16160 kind: kind.clone(),
16161 text: text.clone(),
16162 });
16163 }
16164 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16165 work.push(Work::Build {
16166 kind: kind.clone(),
16167 child_count: children.len(),
16168 });
16169 work.extend(children.iter().rev().map(Work::Visit));
16170 }
16171 Work::Build { kind, child_count } => {
16172 let children = cloned.split_off(cloned.len() - child_count);
16173 cloned.push(CppTemplateTerm::Node { kind, children });
16174 }
16175 }
16176 }
16177 cloned
16178 .pop()
16179 .expect("template term traversal emits one root")
16180}
16181
16182fn cpp_clone_template_expression_iterative(
16183 expression: &CppTemplateExpression,
16184) -> CppTemplateExpression {
16185 CppTemplateExpression {
16186 text: expression.text.clone(),
16187 term: cpp_clone_template_term_iterative(&expression.term),
16188 }
16189}
16190
16191pub fn cpp_unify_template_term(
16192 pattern: &CppTemplateTerm,
16193 argument: &CppTemplateTerm,
16194 parameters: &HashSet<&str>,
16195 bindings: &mut HashMap<String, CppTemplateTerm>,
16196) -> bool {
16197 let mut work = vec![(pattern, argument)];
16198 while let Some((pattern, argument)) = work.pop() {
16199 match pattern {
16200 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16201 if let Some(bound) = bindings.get(name) {
16202 if !cpp_template_terms_equal(bound, argument) {
16203 return false;
16204 }
16205 } else {
16206 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16207 }
16208 }
16209 CppTemplateTerm::Atom {
16210 kind: pattern_kind,
16211 text: pattern_text,
16212 } => {
16213 if !matches!(
16214 argument,
16215 CppTemplateTerm::Atom { kind, text }
16216 if kind == pattern_kind && text == pattern_text
16217 ) {
16218 return false;
16219 }
16220 }
16221 CppTemplateTerm::Node {
16222 kind: pattern_kind,
16223 children: pattern_children,
16224 } => {
16225 let CppTemplateTerm::Node { kind, children } = argument else {
16226 return false;
16227 };
16228 if kind != pattern_kind || children.len() != pattern_children.len() {
16229 return false;
16230 }
16231 work.extend(pattern_children.iter().zip(children).rev());
16232 }
16233 CppTemplateTerm::Parameter(_) => return false,
16234 }
16235 }
16236 true
16237}
16238
16239fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16240 let mut work = vec![(left, right)];
16241 while let Some((left, right)) = work.pop() {
16242 match (left, right) {
16243 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16244 if left != right {
16245 return false;
16246 }
16247 }
16248 (
16249 CppTemplateTerm::Atom {
16250 kind: left_kind,
16251 text: left_text,
16252 },
16253 CppTemplateTerm::Atom {
16254 kind: right_kind,
16255 text: right_text,
16256 },
16257 ) => {
16258 if left_kind != right_kind || left_text != right_text {
16259 return false;
16260 }
16261 }
16262 (
16263 CppTemplateTerm::Node {
16264 kind: left_kind,
16265 children: left_children,
16266 },
16267 CppTemplateTerm::Node {
16268 kind: right_kind,
16269 children: right_children,
16270 },
16271 ) => {
16272 if left_kind != right_kind || left_children.len() != right_children.len() {
16273 return false;
16274 }
16275 work.extend(left_children.iter().zip(right_children).rev());
16276 }
16277 _ => return false,
16278 }
16279 }
16280 true
16281}
16282
16283pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16284 let mut components = Vec::new();
16285 let mut stack = vec![node];
16286 while let Some(current) = stack.pop() {
16287 match current.kind() {
16288 "identifier"
16289 | "field_identifier"
16290 | "namespace_identifier"
16291 | "type_identifier"
16292 | "operator_name"
16293 | "destructor_name" => components.push(current),
16294 "template_type" | "template_function" => {
16295 stack.push(current.child_by_field_name("name")?);
16296 }
16297 "dependent_name" => stack.push(current.named_child(0)?),
16298 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16299 stack.push(current.child_by_field_name("name")?);
16300 if let Some(scope) = current.child_by_field_name("scope") {
16301 stack.push(scope);
16302 }
16303 }
16304 "nested_namespace_specifier" => {
16305 for index in (0..current.named_child_count()).rev() {
16306 stack.push(current.named_child(index)?);
16307 }
16308 }
16309 _ => return None,
16310 }
16311 }
16312 Some(components)
16313}
16314
16315pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16316 node.child_by_field_name("scope").is_none()
16317 && node.child(0).is_some_and(|child| child.kind() == "::")
16318}
16319
16320fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16321 let mut namespaces = Vec::new();
16322 let mut current = node.parent();
16323 while let Some(parent) = current {
16324 if parent.kind() == "namespace_definition"
16325 && let Some(name) = parent.child_by_field_name("name")
16326 {
16327 let mut components = Vec::new();
16328 append_cpp_name_components(name, source, &mut components)?;
16329 namespaces.push(components);
16330 }
16331 current = parent.parent();
16332 }
16333 namespaces.reverse();
16334 Some(namespaces.into_iter().flatten().collect())
16335}
16336
16337fn indexed_namespace_path_is_recoverable(
16348 lexical_scope: &[String],
16349 indexed_owner_scope: &[String],
16350 explicit_owner_component_count: usize,
16351) -> bool {
16352 if lexical_scope.is_empty() {
16353 return explicit_owner_component_count > 1;
16354 }
16355 if lexical_scope.len() >= indexed_owner_scope.len() {
16356 return false;
16357 }
16358 let mut indexed = indexed_owner_scope.iter();
16359 lexical_scope
16360 .iter()
16361 .all(|component| indexed.any(|candidate| candidate == component))
16362}
16363
16364pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16365 let mut current = node.parent();
16366 while let Some(parent) = current {
16367 if parent.kind() == kind {
16368 return true;
16369 }
16370 current = parent.parent();
16371 }
16372 false
16373}
16374
16375pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16381 let mut current = Some(node);
16382 while let Some(candidate) = current {
16383 if candidate.kind() == "declaration" {
16384 let Some(type_node) = candidate.child_by_field_name("type") else {
16385 return false;
16386 };
16387 if !(type_node.start_byte() <= node.start_byte()
16388 && node.end_byte() <= type_node.end_byte())
16389 {
16390 return false;
16391 }
16392 let mut cursor = candidate.walk();
16393 return candidate.named_children(&mut cursor).any(|child| {
16394 child.kind() == "init_declarator"
16395 && child
16396 .child_by_field_name("value")
16397 .is_some_and(|value| value.kind() == "cast_expression")
16398 });
16399 }
16400 current = candidate.parent();
16401 }
16402 false
16403}
16404
16405#[derive(Clone, Copy, PartialEq, Eq)]
16406pub(crate) enum QualifiedAliasReferenceKind {
16407 Ordinary,
16408 ConstructorWithExpressionArgument,
16409 ExhaustiveTemplate,
16410}
16411
16412pub(crate) fn qualified_alias_reference_preserves_target(
16419 node: Node<'_>,
16420 target: &CodeUnit,
16421 analyzer: &CppGraphSource<'_>,
16422 visibility: &VisibilityIndex<'_>,
16423 file: &ProjectFile,
16424 source: &str,
16425) -> Option<QualifiedAliasReferenceKind> {
16426 if !matches!(
16427 node.kind(),
16428 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16429 ) {
16430 return None;
16431 }
16432 let components = cpp_type_name_components(node, source)?;
16433 let name = components.last()?;
16434 analyzer.type_alias_provider().and_then(|provider| {
16435 visibility
16436 .visible_identifier_candidates(file, name)
16437 .find_map(|candidate| {
16438 let proof = provider.is_type_alias(candidate)
16439 && canonical_cpp_scope_components(candidate) == components
16440 && visibility.external_type_candidate_visible_in_context(
16441 analyzer, file, candidate, node,
16442 )
16443 && match cpp_template_reference_arguments(node, source) {
16444 Some(arguments) => visibility.template_alias_arguments_preserve_target(
16445 analyzer, file, candidate, &arguments, target,
16446 ),
16447 None => visibility.structured_alias_primary_preserves_target(
16448 analyzer, file, candidate, target,
16449 ),
16450 };
16451 proof.then(|| {
16452 if cpp_template_reference_arguments(node, source).is_some()
16453 && visibility.is_exhaustive_same_fqn_type_declaration_family(
16454 analyzer, file, candidate,
16455 )
16456 {
16457 QualifiedAliasReferenceKind::ExhaustiveTemplate
16458 } else if qualified_alias_constructor_has_expression_argument(node)
16459 || qualified_alias_local_constructor_declaration(node)
16460 {
16461 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16462 } else {
16463 QualifiedAliasReferenceKind::Ordinary
16464 }
16465 })
16466 })
16467 })
16468}
16469
16470pub(crate) fn qualified_alias_reference_requires_terminal(
16471 reference: Option<QualifiedAliasReferenceKind>,
16472) -> bool {
16473 matches!(
16474 reference,
16475 Some(
16476 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16477 | QualifiedAliasReferenceKind::ExhaustiveTemplate
16478 )
16479 )
16480}
16481
16482fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16483 let Some(declaration) = node.parent().filter(|parent| {
16484 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16485 }) else {
16486 return false;
16487 };
16488 let mut cursor = declaration.walk();
16489 declaration.named_children(&mut cursor).any(|child| {
16490 child.kind() == "init_declarator"
16491 && child
16492 .child_by_field_name("value")
16493 .filter(|value| value.kind() == "argument_list")
16494 .is_some_and(|arguments| {
16495 let mut cursor = arguments.walk();
16496 arguments.named_children(&mut cursor).any(|argument| {
16497 let is_parameter = matches!(
16498 argument.kind(),
16499 "parameter_declaration" | "optional_parameter_declaration"
16500 );
16501 if is_parameter {
16502 argument
16503 .child_by_field_name("type")
16504 .is_some_and(|type_node| {
16505 type_node.kind() == "type_identifier"
16506 && argument.child_by_field_name("declarator").is_none()
16507 })
16508 } else {
16509 !argument.kind().ends_with("_literal")
16510 && !matches!(argument.kind(), "true" | "false" | "nullptr")
16511 }
16512 })
16513 })
16514 })
16515}
16516
16517fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16522 let Some(declaration) = node.parent().filter(|parent| {
16523 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16524 }) else {
16525 return false;
16526 };
16527 if declaration
16528 .parent()
16529 .is_none_or(|parent| parent.kind() != "compound_statement")
16530 {
16531 return false;
16532 }
16533 let mut cursor = declaration.walk();
16534 declaration
16535 .named_children(&mut cursor)
16536 .any(|child| child.kind() == "function_declarator")
16537}
16538
16539pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16545 loop {
16546 let next = match node.kind() {
16547 "qualified_identifier"
16548 | "scoped_identifier"
16549 | "template_method"
16550 | "template_function"
16551 | "template_type" => node.child_by_field_name("name"),
16552 "field_expression" => node.child_by_field_name("field"),
16553 _ => None,
16554 };
16555 let Some(next) = next else {
16556 return node;
16557 };
16558 node = next;
16559 }
16560}
16561
16562#[derive(Clone, Copy)]
16563pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16564 pub receiver: Node<'tree>,
16565 pub member: Node<'tree>,
16566 pub arity: usize,
16567}
16568
16569pub fn recovered_relational_template_member_call(
16577 field: Node<'_>,
16578) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16579 if field.kind() != "field_expression" {
16580 return None;
16581 }
16582 let receiver = field
16583 .child_by_field_name("argument")
16584 .or_else(|| field.child_by_field_name("object"))?;
16585 let member = field.child_by_field_name("field")?;
16586 let less = field.parent()?;
16587 if less.kind() != "binary_expression"
16588 || less.child_by_field_name("left") != Some(field)
16589 || less
16590 .child_by_field_name("operator")
16591 .is_none_or(|operator| operator.kind() != "<")
16592 || less.child_by_field_name("right").is_none()
16593 {
16594 return None;
16595 }
16596 let greater = less.parent()?;
16597 if greater.kind() != "binary_expression"
16598 || greater.child_by_field_name("left") != Some(less)
16599 || greater
16600 .child_by_field_name("operator")
16601 .is_none_or(|operator| operator.kind() != ">")
16602 {
16603 return None;
16604 }
16605 let arguments = greater.child_by_field_name("right")?;
16606 if arguments.kind() != "parenthesized_expression" {
16607 return None;
16608 }
16609 let arity = parenthesized_call_argument_arity(arguments)?;
16610 Some(RecoveredRelationalTemplateMemberCall {
16611 receiver,
16612 member,
16613 arity,
16614 })
16615}
16616
16617fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
16618 let expression = arguments.named_child(0)?;
16619 if expression.kind() != "comma_expression" {
16620 return Some(1);
16621 }
16622 let mut arity = 0usize;
16623 let mut stack = vec![expression];
16624 while let Some(node) = stack.pop() {
16625 if node.kind() == "comma_expression" {
16626 stack.push(node.child_by_field_name("right")?);
16627 stack.push(node.child_by_field_name("left")?);
16628 } else {
16629 arity += 1;
16630 }
16631 }
16632 Some(arity)
16633}
16634
16635pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
16638 while let Some(parent) = node.parent() {
16639 match parent.kind() {
16640 "call_expression" => {
16641 return parent
16642 .child_by_field_name("function")
16643 .or_else(|| parent.named_child(0))
16644 == Some(node);
16645 }
16646 "qualified_identifier"
16647 | "scoped_identifier"
16648 | "template_function"
16649 | "template_type"
16650 | "field_expression" => node = parent,
16651 _ => return false,
16652 }
16653 }
16654 false
16655}
16656
16657pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
16658 if is_call_callee_node(node) {
16659 function_terminal_node(node)
16660 } else {
16661 node
16662 }
16663}
16664
16665pub fn normalize_type_text(value: &str) -> String {
16666 strip_tag_type_prefix(
16667 normalize_cpp_whitespace(value)
16668 .trim_start_matches("const ")
16669 .trim_end_matches('*')
16670 .trim_end_matches('&')
16671 .trim(),
16672 )
16673 .to_string()
16674}
16675
16676fn strip_tag_type_prefix(value: &str) -> &str {
16677 let value = value.trim_start_matches("const ");
16678 value
16679 .strip_prefix("struct ")
16680 .or_else(|| value.strip_prefix("class "))
16681 .or_else(|| value.strip_prefix("enum "))
16682 .unwrap_or(value)
16683 .trim()
16684}
16685
16686pub fn normalize_reference_name(value: &str) -> Option<String> {
16687 let normalized = normalize_cpp_reference_text(value);
16688 (!normalized.is_empty()).then_some(normalized)
16689}
16690
16691pub fn normalize_cpp_reference_text(value: &str) -> String {
16692 let mut text = normalize_cpp_whitespace(value)
16693 .trim_start_matches("new ")
16694 .trim()
16695 .to_string();
16696 if let Some(index) = text.find(['(', '{']) {
16697 text.truncate(index);
16698 }
16699 if let Some(index) = text.find('<') {
16700 text.truncate(index);
16701 }
16702 let normalized = text
16703 .trim()
16704 .trim_start_matches("const ")
16705 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
16706 .trim_matches(':')
16707 .trim();
16708 strip_tag_type_prefix(normalized).to_string()
16709}
16710
16711pub fn cpp_name_for(unit: &CodeUnit) -> String {
16712 let short = unit.short_name().replace(['.', '$'], "::");
16713 if unit.package_name().is_empty() {
16714 short
16715 } else {
16716 format!("{}::{}", unit.package_name(), short)
16717 }
16718}
16719
16720fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
16724 let fq = unit.fq();
16725 if fq.is_empty() {
16726 return None;
16727 }
16728 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16729 Some(
16730 fq.segments()
16731 .iter()
16732 .map(|&segment| interner.resolve(segment).0)
16733 .collect::<Vec<_>>()
16734 .join("::"),
16735 )
16736}
16737
16738fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
16739 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
16740 || unit.fq().is_empty() && cpp_name_for(unit) == expected
16741}
16742
16743pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
16752 let fq = unit.fq();
16753 if !fq.is_empty() {
16754 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16755 let scope = fq
16756 .segments()
16757 .iter()
16758 .filter_map(|&segment| {
16759 let (text, kind) = interner.resolve(segment);
16760 matches!(
16761 kind,
16762 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
16763 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
16764 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
16765 )
16766 .then(|| text.to_string())
16767 })
16768 .collect();
16769 return scope;
16770 }
16771 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16772 brokk_bifrost_core::analyzer::Language::Cpp,
16773 &cpp_name_for(unit),
16774 )
16775}
16776
16777pub fn terminal_name(value: &str) -> &str {
16788 value
16789 .rsplit("::")
16790 .next()
16791 .unwrap_or(value)
16792 .rsplit(['.', '-', '>'])
16793 .next()
16794 .unwrap_or(value)
16795 .trim()
16796}
16797
16798pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
16799 terminal_name(&normalize_cpp_reference_text(value)) == expected
16800}
16801
16802pub fn name_matches_callable(value: &str, expected: &str) -> bool {
16803 name_matches_terminal(value, expected)
16804 || expected.starts_with("operator")
16805 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
16806}
16807
16808pub fn name_mentions(value: &str, expected: &str) -> bool {
16809 normalize_cpp_reference_text(value)
16810 .split("::")
16811 .any(|part| part == expected)
16812}
16813
16814pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
16815 let cpp_name = cpp_name_for(unit);
16816 if reference.contains("::") {
16817 return reference == cpp_name;
16818 }
16819 reference == cpp_name
16820 || terminal_name(reference) == unit.identifier()
16821 && (unit.package_name().is_empty() || reference == unit.identifier())
16822}
16823
16824pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
16825 match kind {
16826 TargetKind::Type
16827 | TargetKind::Constructor
16828 | TargetKind::Method
16829 | TargetKind::MemberField => true,
16830 TargetKind::FreeFunction => unit.is_function(),
16831 TargetKind::GlobalField => unit.is_field(),
16832 TargetKind::Macro => unit.is_macro(),
16833 }
16834}
16835
16836fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
16837 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
16838 let target_name = cpp_name_for(target);
16839 if normalized.contains("::") {
16840 return normalized == target_name;
16841 }
16842 if let Some(namespace) = alias.namespace.as_deref() {
16843 return namespace_prefixes(namespace)
16844 .into_iter()
16845 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
16846 }
16847 target.package_name().is_empty() && normalized == target.identifier()
16848}
16849
16850pub fn cpp_function_return_type_text(
16853 analyzer: &CppGraphSource<'_>,
16854 function: &CodeUnit,
16855) -> Option<String> {
16856 let metadata = analyzer.signature_metadata(function);
16857 let first = metadata.first()?.return_type_text()?;
16858 metadata
16859 .iter()
16860 .all(|metadata| metadata.return_type_text() == Some(first))
16861 .then(|| first.to_owned())
16862}
16863
16864pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
16865 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
16875 namespace
16876 .strip_prefix("anonymous_namespace::")
16877 .unwrap_or(namespace)
16878 .to_string()
16879 })
16880}
16881
16882fn namespace_prefixes(namespace: &str) -> Vec<String> {
16883 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16889 brokk_bifrost_core::analyzer::Language::Cpp,
16890 namespace,
16891 );
16892 let mut prefixes = Vec::new();
16893 while !parts.is_empty() {
16894 prefixes.push(parts.join("::"));
16895 parts.pop();
16896 }
16897 prefixes
16898}
16899
16900fn nearest_namespace_candidates(
16901 candidates: Vec<CodeUnit>,
16902 normalized: &str,
16903 lexical_namespace: Option<&str>,
16904) -> Vec<CodeUnit> {
16905 if normalized.contains("::") {
16906 return candidates;
16907 }
16908 if let Some(namespace) = lexical_namespace {
16909 for prefix in namespace_prefixes(namespace) {
16910 let scoped = candidates
16911 .iter()
16912 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
16913 .cloned()
16914 .collect::<Vec<_>>();
16915 if !scoped.is_empty() {
16916 return scoped;
16917 }
16918 }
16919 }
16920 candidates
16921 .into_iter()
16922 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
16923 .collect()
16924}
16925
16926pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
16927 let mut namespaces = Vec::new();
16928 let mut current = node.parent();
16929 while let Some(parent) = current {
16930 if parent.kind() == "namespace_definition"
16931 && let Some(name) = parent.child_by_field_name("name")
16932 {
16933 let namespace = normalize_cpp_reference_text(node_text(name, source));
16934 if !namespace.is_empty() {
16935 namespaces.push(namespace);
16936 }
16937 }
16938 current = parent.parent();
16939 }
16940 if namespaces.is_empty() {
16941 None
16942 } else {
16943 namespaces.reverse();
16944 Some(namespaces.join("::"))
16945 }
16946}
16947
16948pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
16952 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
16953}
16954
16955fn type_owner_resolution(
16956 analyzer: &CppGraphSource<'_>,
16957 code_unit: &CodeUnit,
16958) -> Option<ResolvedTypeOwner> {
16959 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
16960}
16961
16962fn target_type_owner_resolution(
16963 analyzer: &CppGraphSource<'_>,
16964 code_unit: &CodeUnit,
16965) -> Option<ResolvedTypeOwner> {
16966 match type_owner_resolution(analyzer, code_unit) {
16967 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
16968 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
16969 }
16970}
16971
16972fn target_forward_owner_resolution(
16984 analyzer: &CppGraphSource<'_>,
16985 code_unit: &CodeUnit,
16986) -> Option<ResolvedTypeOwner> {
16987 if !code_unit.is_function() {
16988 return None;
16989 }
16990 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
16996 let cpp = analyzer.cpp?;
16997 let mut visible_files = HashSet::default();
16998 collect_include_closure(
16999 analyzer,
17000 cpp.include_target_index(),
17001 code_unit.source(),
17002 &mut visible_files,
17003 None,
17004 );
17005 let candidates = analyzer.workspace_definitions().exact(&owner_name);
17006 let visible_candidates = candidates
17007 .iter()
17008 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17009 .cloned()
17010 .collect::<Vec<_>>();
17011 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17012 DirectOwnerResolution::UniqueFull(unit) => {
17013 return Some(ResolvedTypeOwner {
17014 unit,
17015 is_forward_declaration: false,
17016 });
17017 }
17018 DirectOwnerResolution::ForwardsOnly(forwards) => {
17019 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17020 unit: forwards.into_iter().next().unwrap(),
17021 is_forward_declaration: true,
17022 });
17023 }
17024 DirectOwnerResolution::Ambiguous => return None,
17025 DirectOwnerResolution::None => {}
17026 }
17027
17028 let candidates = candidates
17029 .into_iter()
17030 .filter(|candidate| candidate.is_class())
17031 .collect::<Vec<_>>();
17032 let (unit, is_forward_declaration) =
17033 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17034 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17035 DirectOwnerResolution::ForwardsOnly(forwards) => {
17036 (unique_logical_forward_owner(forwards)?, true)
17037 }
17038 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17039 };
17040 Some(ResolvedTypeOwner {
17041 unit,
17042 is_forward_declaration,
17043 })
17044}
17045
17046pub fn precise_parent_of(
17047 analyzer: &CppGraphSource<'_>,
17048 visibility: &VisibilityIndex<'_>,
17049 code_unit: &CodeUnit,
17050) -> Option<CodeUnit> {
17051 visibility.cached_precise_parent_of(analyzer, code_unit)
17052}
17053
17054fn precise_parent_resolution(
17055 analyzer: &CppGraphSource<'_>,
17056 code_unit: &CodeUnit,
17057) -> Option<ResolvedTypeOwner> {
17058 #[cfg(any(test, feature = "test-support"))]
17059 if let Some(cpp) = analyzer.cpp {
17060 cpp.record_cpp_parent_resolution_for_test();
17061 }
17062 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17063 return Some(ResolvedTypeOwner {
17064 unit,
17065 is_forward_declaration: false,
17066 });
17067 }
17068 let fallback = analyzer.parent_of(code_unit);
17069 if !code_unit.owner_is_type_scope() {
17070 return fallback.map(|unit| ResolvedTypeOwner {
17071 unit,
17072 is_forward_declaration: false,
17073 });
17074 }
17075 let owner_fq = code_unit
17076 .fq()
17077 .parent()
17078 .expect("a unit with an owner identifier has a structured parent");
17079 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17080 match same_source_owner(analyzer, code_unit, &owner_candidates) {
17081 DirectOwnerResolution::UniqueFull(owner) => {
17082 return Some(ResolvedTypeOwner {
17083 unit: owner,
17084 is_forward_declaration: false,
17085 });
17086 }
17087 DirectOwnerResolution::Ambiguous => return None,
17088 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17089 }
17090 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17091 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17092 unit: owner,
17093 is_forward_declaration: false,
17094 }),
17095 DirectOwnerResolution::Ambiguous => None,
17096 DirectOwnerResolution::ForwardsOnly(forwards) => {
17097 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17098 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17099 unit: owner,
17100 is_forward_declaration: false,
17101 }),
17102 FullOwnerResolution::None => {
17103 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17104 unit,
17105 is_forward_declaration: true,
17106 })
17107 }
17108 FullOwnerResolution::Ambiguous => None,
17109 }
17110 }
17111 DirectOwnerResolution::None => {
17112 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17113 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17114 unit: owner,
17115 is_forward_declaration: false,
17116 }),
17117 FullOwnerResolution::Ambiguous => None,
17118 FullOwnerResolution::None => fallback
17119 .filter(|parent| {
17120 parent.source() == code_unit.source()
17121 && parent.fq() == &owner_fq
17122 && (!parent.is_class()
17123 || cpp_class_declaration_strength(analyzer, parent)
17124 == CppClassDeclarationStrength::Full)
17125 })
17126 .map(|unit| ResolvedTypeOwner {
17127 unit,
17128 is_forward_declaration: false,
17129 }),
17130 }
17131 }
17132 }
17133}
17134
17135fn exact_structural_type_parent(
17136 analyzer: &CppGraphSource<'_>,
17137 code_unit: &CodeUnit,
17138) -> Option<CodeUnit> {
17139 if !code_unit.is_function() && !code_unit.is_field() {
17140 return None;
17141 }
17142 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
17144 let parent = cpp.structural_parent_of(code_unit)?;
17145 (!parent.is_module()
17146 && parent.source() == code_unit.source()
17147 && parent.package_name() == code_unit.package_name()
17148 && parent.short_name() == encoded_owner)
17149 .then_some(parent)
17150}
17151
17152fn same_source_owner(
17153 analyzer: &CppGraphSource<'_>,
17154 code_unit: &CodeUnit,
17155 owner_candidates: &[CodeUnit],
17156) -> DirectOwnerResolution {
17157 let candidates = owner_candidates
17158 .iter()
17159 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17160 .cloned()
17161 .collect::<Vec<_>>();
17162 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17163 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17164}
17165
17166fn visible_full_cpp_owner(
17167 analyzer: &CppGraphSource<'_>,
17168 code_unit: &CodeUnit,
17169 owner_candidates: &[CodeUnit],
17170) -> FullOwnerResolution {
17171 let Some(cpp) = analyzer.cpp else {
17172 return FullOwnerResolution::None;
17173 };
17174 let mut visible_files = HashSet::default();
17175 collect_include_closure(
17176 analyzer,
17177 cpp.include_target_index(),
17178 code_unit.source(),
17179 &mut visible_files,
17180 None,
17181 );
17182 let candidates = owner_candidates
17183 .iter()
17184 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17185 .cloned()
17186 .collect::<Vec<_>>();
17187 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17188 let mut full_definition = None;
17189 for candidate in candidates {
17190 match cpp_class_declaration_strength(analyzer, &candidate) {
17191 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17192 return FullOwnerResolution::Ambiguous;
17193 }
17194 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17195 CppClassDeclarationStrength::Forward => {}
17196 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17197 }
17198 }
17199 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17200}
17201
17202pub enum DirectOwnerResolution {
17203 None,
17204 ForwardsOnly(Vec<CodeUnit>),
17205 UniqueFull(CodeUnit),
17206 Ambiguous,
17207}
17208
17209enum FullOwnerResolution {
17210 None,
17211 Unique(CodeUnit),
17212 Ambiguous,
17213}
17214
17215fn directly_included_owner(
17216 analyzer: &CppGraphSource<'_>,
17217 code_unit: &CodeUnit,
17218 owner_candidates: &[CodeUnit],
17219) -> DirectOwnerResolution {
17220 let Some(cpp) = analyzer.cpp else {
17221 return DirectOwnerResolution::None;
17222 };
17223 let direct_includes: HashSet<ProjectFile> = analyzer
17224 .canonical_include_paths(code_unit.source())
17225 .unwrap_or_default()
17226 .into_iter()
17227 .flat_map(|include| {
17228 resolve_include_targets_with_index(
17229 code_unit.source(),
17230 &include,
17231 cpp.include_target_index(),
17232 )
17233 })
17234 .collect();
17235 let candidates = owner_candidates
17236 .iter()
17237 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17238 .cloned()
17239 .collect::<Vec<_>>();
17240 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17241 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17242}
17243
17244fn prefer_member_declaring_owners(
17245 analyzer: &CppGraphSource<'_>,
17246 member: &CodeUnit,
17247 candidates: Vec<CodeUnit>,
17248) -> Vec<CodeUnit> {
17249 let matching = candidates
17250 .iter()
17251 .filter(|owner| owner_declares_member(analyzer, owner, member))
17252 .cloned()
17253 .collect::<Vec<_>>();
17254 if matching.is_empty() {
17255 candidates
17256 } else {
17257 matching
17258 }
17259}
17260
17261fn owner_declares_member(
17262 analyzer: &CppGraphSource<'_>,
17263 owner: &CodeUnit,
17264 member: &CodeUnit,
17265) -> bool {
17266 analyzer.direct_children(owner).into_iter().any(|child| {
17267 child.kind() == member.kind()
17268 && child.identifier() == member.identifier()
17269 && child.signature() == member.signature()
17270 })
17271}
17272
17273fn classify_direct_owner_candidates(
17274 analyzer: &CppGraphSource<'_>,
17275 candidates: impl Iterator<Item = CodeUnit>,
17276) -> DirectOwnerResolution {
17277 collapse_owner_candidates(candidates.map(|candidate| {
17278 let strength = cpp_class_declaration_strength(analyzer, &candidate);
17279 (candidate, strength)
17280 }))
17281}
17282
17283pub fn collapse_owner_candidates(
17284 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17285) -> DirectOwnerResolution {
17286 let mut full_definition = None;
17287 let mut forwards = Vec::new();
17288 for (candidate, strength) in candidates {
17289 match strength {
17290 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17291 return DirectOwnerResolution::Ambiguous;
17292 }
17293 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17294 CppClassDeclarationStrength::Forward => forwards.push(candidate),
17295 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17296 }
17297 }
17298 if let Some(owner) = full_definition {
17299 DirectOwnerResolution::UniqueFull(owner)
17300 } else if !forwards.is_empty() {
17301 DirectOwnerResolution::ForwardsOnly(forwards)
17302 } else {
17303 DirectOwnerResolution::None
17304 }
17305}
17306
17307#[cfg(any(test, feature = "test-support"))]
17308pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17309 unique_logical_forward_owner(forwards)
17310}
17311
17312fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17313 let first = forwards.pop()?;
17314 forwards
17315 .iter()
17316 .all(|forward| same_logical_symbol(forward, &first))
17317 .then_some(first)
17318}
17319
17320pub fn cpp_class_declaration_strength(
17321 analyzer: &CppGraphSource<'_>,
17322 candidate: &CodeUnit,
17323) -> CppClassDeclarationStrength {
17324 let Some(cpp) = analyzer.cpp else {
17332 return uncached_cpp_class_declaration_strength(analyzer, candidate);
17333 };
17334 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17335 return strength;
17336 }
17337 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17338 cpp.cache_class_declaration_strength(candidate, strength);
17339 strength
17340}
17341
17342fn uncached_cpp_class_declaration_strength(
17343 analyzer: &CppGraphSource<'_>,
17344 candidate: &CodeUnit,
17345) -> CppClassDeclarationStrength {
17346 if let Some(facts) = analyzer.declaration_source_properties(candidate) {
17347 if facts
17348 .iter()
17349 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
17350 {
17351 return CppClassDeclarationStrength::Full;
17352 }
17353 if facts
17354 .iter()
17355 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
17356 {
17357 return CppClassDeclarationStrength::Forward;
17358 }
17359 }
17360 if let Some(cpp) = analyzer.cpp
17361 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17362 {
17363 return cpp_class_declaration_strength_in_tree(
17364 analyzer,
17365 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17366 candidate,
17367 prepared.source(),
17368 prepared.tree().root_node(),
17369 );
17370 }
17371 let Some(source) = analyzer.indexed_source(candidate.source()) else {
17372 return CppClassDeclarationStrength::Unknown;
17373 };
17374 let mut parser = Parser::new();
17375 if parser
17376 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17377 .is_err()
17378 {
17379 return CppClassDeclarationStrength::Unknown;
17380 }
17381 let Some(tree) = parser.parse(&source, None) else {
17382 return CppClassDeclarationStrength::Unknown;
17383 };
17384 let recovered_export_classes =
17387 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17388 cpp_class_declaration_strength_in_tree(
17389 analyzer,
17390 &recovered_export_classes,
17391 candidate,
17392 &source,
17393 tree.root_node(),
17394 )
17395}
17396
17397fn cpp_class_declaration_strength_in_tree(
17398 analyzer: &CppGraphSource<'_>,
17399 recovered_export_classes: &CppRecoveredExportClassIndex,
17400 candidate: &CodeUnit,
17401 source: &str,
17402 root: Node<'_>,
17403) -> CppClassDeclarationStrength {
17404 let ranges = analyzer.ranges(candidate);
17405 let mut saw_forward = false;
17406 for range in ranges {
17407 match recovered_class_body_at(
17410 recovered_export_classes,
17411 root,
17412 source,
17413 candidate.identifier(),
17414 &range,
17415 ) {
17416 Some(true) => return CppClassDeclarationStrength::Full,
17417 Some(false) => {
17418 saw_forward = true;
17419 continue;
17420 }
17421 None => {}
17422 }
17423 let covers_range_start = |node: &Node<'_>| {
17430 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17431 };
17432 let mut stack = Vec::new();
17433 if covers_range_start(&root) {
17434 stack.push(root);
17435 }
17436 while let Some(node) = stack.pop() {
17437 if node.start_byte() == range.start_byte
17438 && node.end_byte() == range.end_byte
17439 && matches!(
17440 node.kind(),
17441 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17442 )
17443 {
17444 if cpp_class_node_has_body(node) {
17445 return CppClassDeclarationStrength::Full;
17446 }
17447 saw_forward = true;
17448 }
17449 let mut cursor = node.walk();
17450 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17451 }
17452 }
17453 if saw_forward {
17454 CppClassDeclarationStrength::Forward
17455 } else {
17456 CppClassDeclarationStrength::Unknown
17457 }
17458}
17459
17460fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17461 node.child_by_field_name("body").is_some() || {
17462 let mut cursor = node.walk();
17463 node.named_children(&mut cursor).any(|child| {
17464 matches!(
17465 child.kind(),
17466 "declaration_list" | "field_declaration_list" | "enumerator_list"
17467 )
17468 })
17469 }
17470}
17471
17472#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17473enum CppCTagKind {
17474 Struct,
17475 Union,
17476}
17477
17478fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
17479 let declaration = analyzer.get_source(code_unit, false)?;
17480 let mut parser = Parser::new();
17481 parser
17482 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17483 .ok()?;
17484 let tree = parser.parse(&declaration, None)?;
17485 let mut stack = vec![tree.root_node()];
17486 while let Some(node) = stack.pop() {
17487 let kind = match node.kind() {
17488 "struct_specifier" => CppCTagKind::Struct,
17489 "union_specifier" => CppCTagKind::Union,
17490 _ => {
17491 let mut cursor = node.walk();
17492 stack.extend(node.named_children(&mut cursor));
17493 continue;
17494 }
17495 };
17496 if node
17497 .child_by_field_name("name")
17498 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
17499 {
17500 return Some(kind);
17501 }
17502 let mut cursor = node.walk();
17503 stack.extend(node.named_children(&mut cursor));
17504 }
17505 None
17506}
17507
17508pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17509 if !code_unit.owner_is_type_scope() {
17510 return None;
17511 }
17512 let owner_fq = code_unit.fq().parent()?;
17513 ctx.analyzer
17514 .workspace_definitions()
17515 .exact(&owner_fq)
17516 .into_iter()
17517 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
17518}
17519
17520pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17521 left.kind() == right.kind()
17522 && left.fq_name() == right.fq_name()
17523 && left.signature() == right.signature()
17524 && left.source() == right.source()
17525}
17526
17527pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17528 same_symbol(left, right) || same_logical_symbol(left, right)
17529}
17530
17531pub fn same_visible_global_field_symbol(
17532 analyzer: &CppGraphSource<'_>,
17533 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
17534 left: &CodeUnit,
17535 right: &CodeUnit,
17536) -> bool {
17537 if same_symbol(left, right) {
17538 return true;
17539 }
17540 if !same_logical_symbol(left, right) {
17541 return false;
17542 }
17543 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
17544 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
17545 {
17546 left.source() == right.source()
17547 } else {
17548 true
17549 }
17550}
17551
17552fn cpp_global_field_has_internal_linkage_cached(
17553 analyzer: &CppGraphSource<'_>,
17554 cache: &mut HashMap<CodeUnit, bool>,
17555 candidate: &CodeUnit,
17556) -> bool {
17557 if let Some(internal) = cache.get(candidate) {
17558 return *internal;
17559 }
17560 #[cfg(any(test, feature = "test-support"))]
17561 note_cpp_global_field_internal_linkage_classification_for_test();
17562 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
17563 cache.insert(candidate.clone(), internal);
17564 internal
17565}
17566
17567#[cfg(any(test, feature = "test-support"))]
17568thread_local! {
17569 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17570}
17571
17572#[cfg(any(test, feature = "test-support"))]
17573fn note_cpp_global_field_internal_linkage_classification_for_test() {
17574 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17575 count.set(count.get() + 1);
17576 });
17577}
17578
17579#[cfg(any(test, feature = "test-support"))]
17580pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
17581 body: impl FnOnce() -> T,
17582) -> (T, usize) {
17583 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17584 count.set(0);
17585 let result = body();
17586 let observed = count.get();
17587 count.set(0);
17588 (result, observed)
17589 })
17590}
17591
17592pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17593 left.kind() == right.kind()
17594 && left.fq_name() == right.fq_name()
17595 && left.signature() == right.signature()
17596}
17597
17598pub fn cpp_global_field_has_internal_linkage(
17599 analyzer: &CppGraphSource<'_>,
17600 candidate: &CodeUnit,
17601) -> bool {
17602 if !candidate.is_field() || candidate.short_name().contains('.') {
17603 return false;
17604 }
17605 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
17606 return false;
17607 };
17608 match local_linkage {
17609 CppFieldLinkage::Internal => true,
17610 CppFieldLinkage::External => false,
17611 CppFieldLinkage::InternalUnlessExternalPeer => {
17612 !cpp_global_field_linkage_peers(analyzer, candidate)
17613 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
17614 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
17615 }
17616 }
17617}
17618
17619fn cpp_global_field_linkage_peers<'a>(
17620 analyzer: &CppGraphSource<'a>,
17621 candidate: &'a CodeUnit,
17622) -> impl Iterator<Item = CodeUnit> + 'a {
17623 let name = candidate.fq().clone();
17624 analyzer
17625 .workspace_definitions()
17626 .exact(&name)
17627 .into_iter()
17628 .filter(move |peer| {
17629 if peer == candidate {
17630 return false;
17631 }
17632 #[cfg(any(test, feature = "test-support"))]
17633 note_cpp_global_field_linkage_peer_inspection_for_test();
17634 same_logical_symbol(peer, candidate)
17635 })
17636}
17637
17638#[cfg(any(test, feature = "test-support"))]
17639thread_local! {
17640 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17641}
17642
17643#[cfg(any(test, feature = "test-support"))]
17644fn note_cpp_global_field_linkage_peer_inspection_for_test() {
17645 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17646 count.set(count.get() + 1);
17647 });
17648}
17649
17650#[cfg(any(test, feature = "test-support"))]
17651pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
17652 body: impl FnOnce() -> T,
17653) -> (T, usize) {
17654 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17655 count.set(0);
17656 let result = body();
17657 let observed = count.get();
17658 count.set(0);
17659 (result, observed)
17660 })
17661}
17662
17663fn cpp_global_field_declaration_linkage(
17664 analyzer: &CppGraphSource<'_>,
17665 candidate: &CodeUnit,
17666) -> Option<CppFieldLinkage> {
17667 analyzer.cpp_field_linkage(candidate)
17668}
17669
17670pub fn cpp_canonical_alias_target_text(
17672 analyzer: &CppGraphSource<'_>,
17673 unit: &CodeUnit,
17674) -> Option<String> {
17675 let facts = analyzer.declaration_source_properties(unit)?;
17676 let first = facts.first()?.alias_target_text.as_ref()?;
17677 facts
17678 .iter()
17679 .all(|fact| fact.alias_target_text.as_ref() == Some(first))
17680 .then(|| first.clone())
17681}
17682
17683#[cfg(test)]
17684mod tests {
17685 #[test]
17686 fn issue_3089_statement_formal_at_end_of_replacement() {
17687 let parameters = vec!["handle".to_owned(), "block".to_owned()];
17688 for replacement in [
17689 "do { header_event_t* event; if ((handle)->active) block } while (0)",
17690 "do { header_event_t* event; block } while (0)",
17691 ] {
17692 assert!(
17693 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
17694 .is_some(),
17695 "{replacement}"
17696 );
17697 }
17698 }
17699 use super::*;
17700
17701 #[test]
17702 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
17703 let source = "int size(void) { return sizeof(((Payload))); }\n";
17704 let mut parser = Parser::new();
17705 parser
17706 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17707 .expect("C++ grammar");
17708 let tree = parser.parse(source, None).expect("fixture tree");
17709 let start = source.find("Payload").expect("sizeof operand");
17710 let node = tree
17711 .root_node()
17712 .named_descendant_for_byte_range(start, start + "Payload".len())
17713 .expect("focused operand");
17714 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
17715 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
17716
17717 assert_eq!(node.kind(), "identifier");
17718 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
17719 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
17720 }
17721
17722 fn parse_cpp(source: &str) -> Tree {
17723 let mut parser = Parser::new();
17724 parser
17725 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17726 .expect("C++ grammar");
17727 parser.parse(source, None).expect("fixture tree")
17728 }
17729
17730 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
17731 let start = source.find(needle).expect("fixture needle");
17732 tree.root_node()
17733 .named_descendant_for_byte_range(start, start + needle.len())
17734 .expect("node at needle")
17735 }
17736
17737 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
17738 let mut parser = Parser::new();
17739 parser
17740 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17741 .expect("C++ grammar");
17742 let tree = parser.parse(source, None).expect("fixture tree");
17743 PreparedSyntaxTree::new(
17744 PreparedSyntaxSource::Exact(Arc::from(source)),
17745 tree,
17746 compute_line_starts(source),
17747 LanguageDialect::Standard(Language::Cpp),
17748 PreparedSourceOrigin::Disk,
17749 None,
17750 )
17751 }
17752
17753 fn unresolved_include_before(source: &str, reference: &str) -> bool {
17754 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
17755 let prepared = prepared_cpp(source);
17756 let facts = collect_structured_include_facts(&prepared);
17757 let include_targets = IncludeTargetIndex::build([&file]);
17758 has_unresolved_include_visible_before_in_prepared(
17759 &file,
17760 &prepared,
17761 &include_targets,
17762 &facts,
17763 source.find(reference).expect("reference fixture"),
17764 )
17765 }
17766
17767 #[test]
17768 fn unresolved_include_before_reference_is_visible() {
17769 let source = "#include \"missing.h\"\nint use = Missing;\n";
17770 assert!(unresolved_include_before(source, "Missing"));
17771 }
17772
17773 #[test]
17774 fn unresolved_include_after_reference_is_not_visible() {
17775 let source = "int use = Missing;\n#include \"missing.h\"\n";
17776 assert!(!unresolved_include_before(source, "Missing"));
17777 }
17778
17779 #[test]
17780 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
17781 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
17782 assert!(!unresolved_include_before(source, "Missing"));
17783 }
17784
17785 #[test]
17786 fn unresolved_include_in_current_branch_is_visible() {
17787 let source =
17788 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
17789 assert!(unresolved_include_before(source, "Missing"));
17790 }
17791
17792 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
17797namespace matchers {
17798 namespace detail {
17799 class API [[nodiscard]] First {
17800 public:
17801 int value() const { return count_ + 1; }
17802 private:
17803 int count_;
17804 };
17805 class API [[nodiscard]] Second {
17806 public:
17807 int value() const { return count_ + 2; }
17808 private:
17809 int count_;
17810 };
17811 } // namespace detail
17812
17813 template <typename T>
17814 void tail_function(MatcherBase<T> const& value);
17815
17816 class TailClass {};
17817} // namespace matchers
17818} // namespace app
17819
17820struct AfterAll {};
17821"#;
17822
17823 #[test]
17824 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
17825 let source = STOLEN_BRACE_CASCADE;
17826 let tree = parse_cpp(source);
17827 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17828
17829 let tail_class = named_node_at(&tree, source, "TailClass");
17830 assert!(
17831 !has_ancestor_kind(tail_class, "namespace_definition"),
17832 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
17833 );
17834 let displaced = named_node_at(&tree, source, "Second");
17835 assert_eq!(
17836 enclosing_namespace_components(displaced, source),
17837 Some(vec!["app".to_string(), "matchers".to_string()]),
17838 "the fixture must displace the second class out of detail"
17839 );
17840
17841 let components = |needle: &str| {
17842 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
17843 };
17844 assert_eq!(components("First"), ["app", "matchers", "detail"]);
17845 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
17846 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
17847 assert_eq!(components("tail_function"), ["app", "matchers"]);
17848 assert_eq!(components("TailClass"), ["app", "matchers"]);
17849 assert!(components("AfterAll").is_empty());
17850 }
17851
17852 #[test]
17853 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
17854 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
17855 let tree = parse_cpp(clean);
17856 assert!(!tree.root_node().has_error());
17857 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
17858
17859 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
17862 let tree = parse_cpp(damaged);
17863 assert!(tree.root_node().has_error());
17864 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
17865 assert_eq!(
17866 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
17867 ["a"]
17868 );
17869 }
17870
17871 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
17878
17879 class Target {
17880 int value_;
17881 };
17882
17883 template <typename T>
17884 class Holder {
17885 public:
17886 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
17887
17888#define HOLDER_DEFINE_OP( id, op ) \
17889 template <typename U> \
17890 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
17891 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
17892 return Target{}; \
17893 }
17894
17895 HOLDER_DEFINE_OP( equal, == )
17896#undef HOLDER_DEFINE_OP
17897 T m_lhs;
17898 };
17899
17900 class Tail {};
17901}
17902"#;
17903
17904 #[test]
17905 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
17906 let source = COLLAPSED_NAMESPACE_HEAD;
17907 let tree = parse_cpp(source);
17908 let target = named_node_at(&tree, source, "class Target");
17909
17910 assert!(
17911 !has_ancestor_kind(target, "namespace_definition"),
17912 "the fixture must reproduce the collapse: the class has no namespace ancestor"
17913 );
17914 let head = target.parent().expect("the collapsed namespace envelope");
17915 assert_eq!(
17916 head.kind(),
17917 "ERROR",
17918 "the fixture must keep the namespace head in an ERROR node"
17919 );
17920
17921 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17922 assert_eq!(
17923 index.enclosing_namespace_components(target, source),
17924 ["app"]
17925 );
17926 assert_eq!(
17927 index.enclosing_namespace_levels(target, source),
17928 vec![(
17929 "app".to_string(),
17930 Range {
17931 start_byte: 0,
17932 end_byte: source.len() - 1,
17933 start_line: 1,
17934 end_line: 25,
17935 },
17936 )],
17937 "the recovered namespace owns its full construct, not its first member or just its header"
17938 );
17939 }
17940
17941 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
17950
17951namespace app {
17952namespace detail {
17953
17954bool check(bool active) {
17955 APP_IF_ACTIVE
17956 return true;
17957 }
17958 return false;
17959}
17960
17961class Tail {};
17962
17963} // namespace detail
17964} // namespace app
17965"#;
17966
17967 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
17977namespace compile_time {
17978namespace number_parsing {
17979consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
17980 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
17981 auto digit = [](uint8_t c) -> uint32_t {
17982 }
17983 }
17984 if (a) {
17985 while (a) {
17986 }
17987 }
17988 switch (c) {
17989 case '{': {
17990 }
17991 }
17992 }
17993 while (a) {
17994 cursor += field.second;
17995 if (a) {
17996 case '{': {
17997 if (a) {
17998 }
17999 }
18000 case 'n': {
18001 if (a) {
18002 }
18003 }
18004 }
18005 }
18006 if (a) {
18007 simdjson_consteval_error("Expected '}'");
18008 }
18009}
18010}
18011}
18012"#;
18013
18014 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
18016 let mut stack = vec![root];
18017 let mut counts = (0, 0);
18018 while let Some(node) = stack.pop() {
18019 match (node.kind(), node.is_missing()) {
18020 ("{", false) => counts.0 += 1,
18021 ("}", false) => counts.1 += 1,
18022 _ => {}
18023 }
18024 let mut cursor = node.walk();
18025 stack.extend(node.children(&mut cursor));
18026 }
18027 counts
18028 }
18029
18030 fn namespace_open_byte(source: &str, head: &str) -> usize {
18032 let at = source.find(head).expect("fixture namespace head");
18033 at + head.len() - 1
18034 }
18035
18036 #[test]
18037 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
18038 let source = MACRO_OPENED_SCOPE;
18039 let tree = parse_cpp(source);
18040 let root = tree.root_node();
18041 assert_eq!(
18042 real_brace_token_counts(root),
18043 (4, 5),
18044 "the fixture must reproduce the loss: one scope the source opens \
18045 has no `{{` token, so the stream holds an extra real close"
18046 );
18047
18048 let index = OrphanedNamespaceScopeIndex::build(root, source);
18049 for head in ["namespace app {", "namespace detail {"] {
18050 assert_eq!(
18051 index.matching_close_brace(namespace_open_byte(source, head)),
18052 None,
18053 "`{head}` must not claim a close: the stack had to give it one that \
18054 belongs to an inner scope (line 15 for `app`, line 11 for \
18055 `detail`) once the macro's `{{` went missing"
18056 );
18057 }
18058 }
18059
18060 #[test]
18061 fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
18062 let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
18066 let tree = parse_cpp(&source);
18067 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
18068 let after_all = named_node_at(&tree, &source, "struct AfterAll");
18069 assert!(
18070 index
18071 .enclosing_namespace_components(after_all, &source)
18072 .is_empty()
18073 );
18074 }
18075
18076 #[test]
18077 fn orphaned_namespace_scope_index_pairs_nothing_on_the_reflection_recovery() {
18078 let source = REFLECTION_CASE_LABEL_RECOVERY;
18079 let tree = parse_cpp(source);
18080 let root = tree.root_node();
18081 let mut stack = vec![root];
18082 let mut mis_lexed_opens = Vec::new();
18083 while let Some(node) = stack.pop() {
18084 if node.kind() == "character"
18085 && &source[node.start_byte()..node.end_byte()] == "{"
18086 && node
18087 .parent()
18088 .is_some_and(|parent| parent.kind() != "char_literal")
18089 {
18090 mis_lexed_opens.push(node.start_position().row + 1);
18091 }
18092 let mut cursor = node.walk();
18093 stack.extend(node.children(&mut cursor));
18094 }
18095 assert!(
18096 !mis_lexed_opens.is_empty(),
18097 "the fixture must reproduce the mis-lex: a scope-opening `{{` \
18098 consumed as a `character` of a char literal recovery never closed"
18099 );
18100 assert_eq!(
18101 real_brace_token_counts(root),
18102 (17, 18),
18103 "which is what leaves the brace token stream short one open: \
18104 mis-lexed at {mis_lexed_opens:?}"
18105 );
18106
18107 let index = OrphanedNamespaceScopeIndex::build(root, source);
18108 for head in [
18109 "namespace simdjson {",
18110 "namespace compile_time {",
18111 "namespace number_parsing {",
18112 ] {
18113 assert_eq!(
18114 index.matching_close_brace(namespace_open_byte(source, head)),
18115 None,
18116 "`{head}` must not claim a close: every close after the lost open \
18117 belongs one level in, so the stack paired the three heads on \
18118 lines 1..3 with the closes on lines 33..35 instead of 34..36"
18119 );
18120 }
18121 }
18122
18123 #[test]
18124 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18125 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18126 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18127 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18128 assert!(indexed_namespace_path_is_recoverable(
18129 &["cache".to_string()],
18130 &indexed,
18131 1,
18132 ));
18133 }
18134
18135 #[test]
18136 fn sort_lookup_units_totally_orders_every_identity_field() {
18137 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18138 let base = CodeUnit::with_signature(
18139 file.clone(),
18140 CodeUnitType::Function,
18141 "scope",
18142 "value",
18143 Some("()".to_string()),
18144 false,
18145 );
18146 let different_kind = CodeUnit::with_signature(
18147 file.clone(),
18148 CodeUnitType::Field,
18149 "scope",
18150 "value",
18151 Some("()".to_string()),
18152 false,
18153 );
18154 let synthetic = base.with_synthetic(true);
18155
18156 let interner = segment_interner();
18157 let mut member_fq = FqName::new();
18158 member_fq.push(interner.intern("scope", SegmentKind::Package));
18159 member_fq.push(interner.intern("value", SegmentKind::Member));
18160 let different_package_boundary = CodeUnit::from_fq(
18161 file.clone(),
18162 CodeUnitType::Function,
18163 member_fq,
18164 0,
18165 Some("()".to_string()),
18166 false,
18167 );
18168
18169 let mut unknown_fq = FqName::new();
18170 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18171 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18172 let different_segment_kind = CodeUnit::from_fq(
18173 file,
18174 CodeUnitType::Function,
18175 unknown_fq,
18176 1,
18177 Some("()".to_string()),
18178 false,
18179 );
18180
18181 let input = vec![
18182 base,
18183 different_kind,
18184 synthetic,
18185 different_package_boundary,
18186 different_segment_kind,
18187 ];
18188 let mut expected = input.clone();
18189 sort_lookup_units(&mut expected);
18190 assert!(expected.windows(2).all(|pair| {
18191 let mut ordered = pair.to_vec();
18192 sort_lookup_units(&mut ordered);
18193 ordered == pair && pair[0] != pair[1]
18194 }));
18195
18196 let mut reversed = input.clone();
18197 reversed.reverse();
18198 sort_lookup_units(&mut reversed);
18199 assert_eq!(reversed, expected);
18200
18201 let mut rotated = input;
18202 rotated.rotate_left(2);
18203 sort_lookup_units(&mut rotated);
18204 assert_eq!(rotated, expected);
18205 }
18206
18207 #[test]
18208 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18209 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";
18210 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18211 let parse = |source: &str| {
18212 let mut parser = Parser::new();
18213 parser
18214 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18215 .expect("C++ grammar");
18216 parser.parse(source, None).expect("fixture tree")
18217 };
18218
18219 let tree = parse(damaged);
18220 let root = tree.root_node();
18221 let target = damaged.find("target").expect("target byte");
18222 let declaration = root
18223 .descendant_for_byte_range(target, target + "target".len())
18224 .and_then(|mut node| {
18225 loop {
18226 if node.kind() == "declaration" {
18227 break Some(node);
18228 }
18229 node = node.parent()?;
18230 }
18231 })
18232 .expect("declaration after the displaced terminator");
18233 let conditional = declaration
18234 .parent()
18235 .filter(|node| node.kind() == "preproc_ifdef")
18236 .expect("damaged inner conditional");
18237 let outer = conditional
18238 .parent()
18239 .filter(|node| node.kind() == "preproc_ifdef")
18240 .expect("ordinary outer include guard");
18241 let terminator = cpp_displaced_preprocessor_terminator(conditional)
18242 .expect("structured displaced #endif");
18243 assert_eq!(node_text(terminator, damaged), "#endif");
18244 assert!(terminator.end_byte() <= declaration.start_byte());
18245 assert!(!preprocessor_conditional_contains_descendant(
18246 conditional,
18247 declaration
18248 ));
18249 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18250 assert!(preprocessor_conditional_contains_descendant(
18251 outer,
18252 declaration
18253 ));
18254
18255 let tree = parse(guarded);
18256 let conditional = tree
18257 .root_node()
18258 .named_child(0)
18259 .filter(|node| node.kind() == "preproc_ifdef")
18260 .expect("ordinary conditional");
18261 let declaration = conditional
18262 .named_children(&mut conditional.walk())
18263 .find(|node| node.kind() == "declaration")
18264 .expect("guarded declaration");
18265 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18266 assert!(preprocessor_conditional_contains_descendant(
18267 conditional,
18268 declaration
18269 ));
18270
18271 let damaged_alternative = format!(
18272 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18273 "UNUSED(value)\n".repeat(64)
18274 );
18275 let tree = parse(&damaged_alternative);
18276 let conditional = tree
18277 .root_node()
18278 .named_child(0)
18279 .filter(|node| node.kind() == "preproc_ifdef")
18280 .expect("outer conditional with an alternative");
18281 assert!(conditional.has_error());
18282 assert!(conditional.child_by_field_name("alternative").is_some());
18283 assert!(
18284 conditional
18285 .child(conditional.child_count() - 1)
18286 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18287 );
18288 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18289
18290 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";
18291 let tree = parse(split_declaration);
18292 let root = tree.root_node();
18293 let conditional = root
18294 .named_children(&mut root.walk())
18295 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18296 .expect("split declaration conditional");
18297 let target = split_declaration
18298 .find("static int target")
18299 .expect("target byte");
18300 let boundary =
18301 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18302 assert!(boundary.end_byte <= target, "{boundary:?}");
18303 assert_eq!(boundary.end_line, 9, "{boundary:?}");
18304 let target_node = root
18305 .descendant_for_byte_range(target, target + "static".len())
18306 .expect("target node");
18307 assert!(!preprocessor_conditional_contains_descendant(
18308 conditional,
18309 target_node
18310 ));
18311 }
18312
18313 #[test]
18314 fn fragmented_reference_guard_is_recovered() {
18315 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";
18316 let mut parser = Parser::new();
18317 parser
18318 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18319 .expect("C++ grammar");
18320 let tree = parser.parse(source, None).expect("fixture tree");
18321 let start = source.rfind("helper").expect("reference byte");
18322 let node = tree
18323 .root_node()
18324 .descendant_for_byte_range(start, start + "helper".len())
18325 .expect("reference node");
18326 let mut expected = HashSet::default();
18327 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18328 vec![
18329 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18330 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18331 ],
18332 )));
18333 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18334 }
18335
18336 #[test]
18337 fn expression_defined_and_ifndef_guards_are_incompatible() {
18338 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18339 let mut parser = Parser::new();
18340 parser
18341 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18342 .expect("C++ grammar");
18343 let tree = parser.parse(source, None).expect("fixture tree");
18344 let root = tree.root_node();
18345 let selected_start = source.find("selected").expect("selected declaration");
18346 let rejected_start = source.find("rejected").expect("rejected declaration");
18347 let selected = root
18348 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18349 .expect("selected node");
18350 let rejected = root
18351 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18352 .expect("rejected node");
18353 let selected_guards =
18354 preprocessor_guard_environment(selected, source).expect("selected guards");
18355 let rejected_guards =
18356 preprocessor_guard_environment(rejected, source).expect("rejected guards");
18357
18358 assert!(
18359 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18360 "opposite spellings of one macro guard must contradict"
18361 );
18362 }
18363
18364 #[test]
18365 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18366 let source = r#"#ifdef _WIN32
18367#if defined(__cplusplus)
18368extern "C"
18369#endif
18370int platform_api(void);
18371#endif
18372
18373#ifdef _WIN32
18374static int entropy_target(void) { return 0; }
18375#else
18376#ifdef HAVE_COMMON_RANDOM
18377static int other_target(void) { return 0; }
18378#elif defined(HAVE_GETENTROPY)
18379static int entropy_target(void) { return 1; }
18380static int use_entropy(void) { return entropy_target(); }
18381#endif
18382#endif
18383"#;
18384 let mut parser = Parser::new();
18385 parser
18386 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18387 .expect("C++ grammar");
18388 let tree = parser.parse(source, None).expect("fixture tree");
18389 let start = source.rfind("entropy_target()").expect("reference");
18390 let node = tree
18391 .root_node()
18392 .descendant_for_byte_range(start, start + "entropy_target".len())
18393 .expect("reference node");
18394 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18395 assert!(
18396 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18397 "{guards:#?}"
18398 );
18399 assert!(
18400 guards.contains(&PreprocessorGuard::Undefined(
18401 "HAVE_COMMON_RANDOM".to_string()
18402 )),
18403 "{guards:#?}"
18404 );
18405 assert!(
18406 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18407 "{guards:#?}"
18408 );
18409 assert!(
18410 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18411 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18412 );
18413 }
18414
18415 #[test]
18416 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18417 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";
18418 let mut parser = Parser::new();
18419 parser
18420 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18421 .expect("C++ grammar");
18422 let tree = parser.parse(source, None).expect("fixture tree");
18423 let root = tree.root_node();
18424 let node_at = |text: &str, start: usize| {
18425 root.descendant_for_byte_range(start, start + text.len())
18426 .expect("token node")
18427 };
18428
18429 let key_start = source.find("case KEY").expect("case label") + "case ".len();
18430 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18431 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18432 assert!(!is_ordinary_macro_reference_node(node_at(
18433 "ENABLE_KEYS",
18434 guard_start,
18435 )));
18436 }
18437
18438 #[test]
18439 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18440 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";
18441 let mut parser = Parser::new();
18442 parser
18443 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18444 .expect("C++ grammar");
18445 let tree = parser.parse(source, None).expect("fixture tree");
18446 let root = tree.root_node();
18447 let definition_start = source.find("target(void)").expect("definition");
18448 let reference_start = source.rfind("target()").expect("reference");
18449 let definition = root
18450 .descendant_for_byte_range(definition_start, definition_start + "target".len())
18451 .expect("definition node");
18452 let reference = root
18453 .descendant_for_byte_range(reference_start, reference_start + "target".len())
18454 .expect("reference node");
18455 let required =
18456 preprocessor_guard_environment(definition, source).expect("definition guard");
18457 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
18458 assert!(guard_requirements_hold_at_reference(
18459 &required,
18460 Some(&active)
18461 ));
18462 }
18463
18464 #[test]
18465 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
18466 let source = "g_autoptr(FuChunkArray) self = make_array();";
18467 let mut parser = Parser::new();
18468 parser
18469 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18470 .expect("C++ grammar");
18471 let tree = parser.parse(source, None).expect("fixture tree");
18472 let statement = tree.root_node().named_child(0).expect("statement");
18473 let binding =
18474 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
18475 assert_eq!(binding.name, "self");
18476 assert_eq!(binding.type_name, "FuChunkArray");
18477 assert_eq!(binding.pointer_depth, 1);
18478
18479 let near_miss = "holder(FuChunkArray) self = make_array();";
18480 let tree = parser.parse(near_miss, None).expect("near-miss tree");
18481 let statement = tree.root_node().named_child(0).expect("statement");
18482 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
18483 }
18484
18485 #[test]
18486 fn boolean_guard_normalization_proves_equivalence_and_implication() {
18487 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
18488 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
18489 let negated_windows_branch =
18490 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
18491 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
18492 assert_eq!(negated_windows_branch, portable);
18493
18494 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
18495 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
18496 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
18497 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
18498 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
18499 assert!(fallback_branch.implies(&fallback_declaration));
18500 assert!(
18501 BooleanGuardExpression::Truthy("FEATURE".to_string())
18502 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
18503 );
18504 assert!(
18505 BooleanGuardExpression::Undefined("FEATURE".to_string())
18506 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
18507 );
18508 assert!(
18509 !BooleanGuardExpression::Defined("FEATURE".to_string())
18510 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
18511 );
18512 assert!(!fallback_declaration.implies(&fallback_branch));
18513 }
18514
18515 #[test]
18516 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
18517 let source = "static int helper(const char *left, wchar_t *right) { return 0; }\nint caller(wchar_t *template) {\n return helper(NULL, template); /* bound */\n}\nint unbound(void) {\n return helper(NULL, template); /* unbound */\n}\n";
18518 let mut parser = Parser::new();
18519 parser
18520 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18521 .expect("C++ grammar");
18522 let tree = parser.parse(source, None).expect("fixture tree");
18523 let root = tree.root_node();
18524 let call = |marker: &str| {
18525 let start = source.find(marker).expect("call marker");
18526 let mut node = root
18527 .descendant_for_byte_range(start, start + "helper".len())
18528 .expect("call name node");
18529 loop {
18530 if node.kind() == "call_expression" {
18531 break node;
18532 }
18533 node = node.parent().expect("call expression ancestor");
18534 }
18535 };
18536 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
18537 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
18538 let keyword_call = call("helper(NULL, template); /* bound */");
18539 let keyword_arguments = keyword_call
18540 .child_by_field_name("arguments")
18541 .expect("keyword argument list");
18542 assert_eq!(
18543 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
18544 1
18545 );
18546 assert_eq!(
18547 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
18548 0
18549 );
18550
18551 let unbound_call = call("helper(NULL, template); /* unbound */");
18552 let unbound_arguments = unbound_call
18553 .child_by_field_name("arguments")
18554 .expect("unbound argument list");
18555 assert_eq!(
18556 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
18557 0
18558 );
18559 }
18560
18561 #[test]
18562 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
18563 let source = r#"#define MAKE(type) type *value
18564MAKE(int *);
18565typedef struct Item Item;
18566struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
18567void gen_op_movl(void *s, int first, int second) { }
18568const char *strZ(const char *value) { return value; }
18569int body(void *s) {
18570 MAKE(int *);
18571 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
18572 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
18573 execvp(strZ(value), UNCONSTIFY(char **, args));
18574}
18575 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
18576 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
18577int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
18578 if (d->units) {
18579 switch (target->nodetype) {
18580 case 1:
18581 case 2:
18582 break;
18583 default:
18584 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
18585 }
18586 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
18587 lysdict_remove(ctx, ((struct Item *)target)->units);
18588 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
18589 }
18590 return 0;
18591 }
18592STATIC EFI_STATUS Encode () { return 0; }
18593"#;
18594 let tree = parse_cpp(source);
18595 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
18596 let top_macro = tree
18597 .root_node()
18598 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
18599 .expect("top macro node");
18600 let body_macro_start = source
18601 .match_indices("MAKE(int *);")
18602 .nth(1)
18603 .expect("body macro")
18604 .0;
18605 let body_macro = tree
18606 .root_node()
18607 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
18608 .expect("body macro node");
18609 let function_call_start = source
18610 .find("gen_op_movl(s, offsetof(CPUX86State")
18611 .expect("function call");
18612 let function_call = tree
18613 .root_node()
18614 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
18615 .expect("function call node");
18616 let strz_start = source.find("strZ(value)").expect("nested function call");
18617 let strz = tree
18618 .root_node()
18619 .named_descendant_for_byte_range(strz_start, strz_start + 4)
18620 .expect("nested function call node");
18621 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
18622 let recovered_call = tree
18623 .root_node()
18624 .named_descendant_for_byte_range(
18625 recovered_call_start,
18626 recovered_call_start + "lysdict_remove".len(),
18627 )
18628 .expect("recovered call node");
18629 let binder_start = source.find("Encode").expect("binder");
18630 let binder = tree
18631 .root_node()
18632 .named_descendant_for_byte_range(binder_start, binder_start + 6)
18633 .expect("binder node");
18634
18635 assert!(recovered_c_function_declarator_invocation(top_macro));
18636 assert!(recovered_c_function_declarator_invocation(body_macro));
18637 assert!(recovered_c_function_declarator_invocation(function_call));
18638 assert!(recovered_c_function_declarator_invocation(strz));
18639 assert!(recovered_c_function_declarator_invocation(recovered_call));
18640 assert!(!recovered_c_function_declarator_invocation(binder));
18641 }
18642
18643 #[test]
18644 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
18645 let source = r#"typedef int krb5_context;
18646int helper(int first, int second) { return first + second; }
18647static krb5_context ctx;
18648int main(int argc, char **argv) {
18649 int ccinitial;
18650 const char *collection_name, *typename;
18651 typename = helper(ctx, ccinitial);
18652 return 0;
18653}
18654"#;
18655 let tree = parse_cpp(source);
18656 let ctx = tree
18657 .root_node()
18658 .descendant_for_byte_range(
18659 source.find("ctx, ccinitial").expect("ctx argument"),
18660 source.find("ctx, ccinitial").expect("ctx argument") + 3,
18661 )
18662 .expect("ctx node");
18663 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
18664 let ccinitial = tree
18665 .root_node()
18666 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
18667 .expect("sibling node");
18668 let typename = named_node_at(&tree, source, "typename = helper");
18669 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
18670
18671 assert_eq!(ctx.kind(), "identifier");
18672 assert!(recovered_c_parenthesized_declarator_reference(ctx));
18673 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
18674 assert!(!recovered_c_parenthesized_declarator_reference(typename));
18675 assert!(!recovered_c_parenthesized_declarator_reference(helper));
18676 }
18677
18678 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
18679 let mut parser = Parser::new();
18680 parser
18681 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18682 .expect("C++ grammar");
18683 let tree = parser.parse(source, None).expect("C++ fixture tree");
18684 let mut stack = vec![tree.root_node()];
18685 while let Some(node) = stack.pop() {
18686 if node.kind() == "enum_specifier" {
18687 return flattened_macro_namespace_components(node, source);
18688 }
18689 let mut cursor = node.walk();
18690 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
18691 stack.extend(children.into_iter().rev());
18692 }
18693 None
18694 }
18695
18696 #[test]
18697 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
18698 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18699namespace detail
18700{
18701enum class value_t { null };
18702}
18703NLOHMANN_JSON_NAMESPACE_END
18704NLOHMANN_JSON_NAMESPACE_BEGIN
18705namespace next
18706{
18707struct next_type {};
18708}
18709NLOHMANN_JSON_NAMESPACE_END
18710"#;
18711 assert_eq!(
18712 first_enum_flattened_namespace(complete),
18713 Some(vec!["detail".to_string()])
18714 );
18715
18716 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
18717 assert_eq!(
18718 first_enum_flattened_namespace(&stale_end),
18719 Some(vec!["detail".to_string()]),
18720 "a stale end marker before the begin marker must not replace the intended namespace"
18721 );
18722
18723 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18724namespace detail
18725{
18726enum class value_t { null };
18727}
18728struct next_type {};
18729"#;
18730 assert_eq!(first_enum_flattened_namespace(incomplete), None);
18731 }
18732}
18733
18734#[cfg(test)]
18750mod lookup_order_properties {
18751 use super::*;
18752 use proptest::prelude::*;
18753
18754 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
18758 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
18759 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
18763 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
18764 const KINDS: [CodeUnitType; 6] = [
18765 CodeUnitType::Class,
18766 CodeUnitType::Function,
18767 CodeUnitType::Field,
18768 CodeUnitType::Module,
18769 CodeUnitType::Macro,
18770 CodeUnitType::FileScope,
18771 ];
18772
18773 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18776 enum ProbedOrder {
18777 Before,
18778 Tied,
18779 After,
18780 Contradictory,
18783 }
18784
18785 impl ProbedOrder {
18786 fn mirror(self) -> Self {
18787 match self {
18788 ProbedOrder::Before => ProbedOrder::After,
18789 ProbedOrder::After => ProbedOrder::Before,
18790 other => other,
18791 }
18792 }
18793
18794 fn signum(self) -> i8 {
18796 match self {
18797 ProbedOrder::Before => -1,
18798 ProbedOrder::Tied => 0,
18799 ProbedOrder::After => 1,
18800 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
18801 }
18802 }
18803 }
18804
18805 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
18813 if left == right {
18814 return ProbedOrder::Tied;
18817 }
18818 let mut forward = vec![left.clone(), right.clone()];
18819 sort_lookup_units(&mut forward);
18820 let mut backward = vec![right.clone(), left.clone()];
18821 sort_lookup_units(&mut backward);
18822 let left_first = backward[0] == *left;
18823 let right_first = forward[0] == *right;
18824 match (left_first, right_first) {
18825 (true, true) => ProbedOrder::Contradictory,
18826 (true, false) => ProbedOrder::Before,
18827 (false, true) => ProbedOrder::After,
18828 (false, false) => ProbedOrder::Tied,
18829 }
18830 }
18831
18832 fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
18835 let interner = segment_interner();
18836 unit.fq()
18837 .segments()
18838 .iter()
18839 .map(|&id| {
18840 let (text, kind) = interner.resolve(id);
18841 (kind.name(), text)
18842 })
18843 .collect()
18844 }
18845
18846 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
18847 (
18848 0..ROOT_NAMES.len(),
18849 0..REL_PATHS.len(),
18850 0..KINDS.len(),
18851 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
18852 0..3usize,
18853 0..SIGNATURES.len(),
18854 any::<bool>(),
18855 )
18856 .prop_map(
18857 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
18858 let source = ProjectFile::new(
18859 std::env::temp_dir().join(ROOT_NAMES[root]),
18860 REL_PATHS[rel_path],
18861 );
18862 let interner = segment_interner();
18863 let mut fq = FqName::new();
18864 for (atom, segment_kind) in &segments {
18865 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
18866 }
18867 let package_segment_count = package_prefix % fq.len();
18869 CodeUnit::from_fq(
18870 source,
18871 KINDS[kind],
18872 fq,
18873 package_segment_count,
18874 SIGNATURES[signature].map(str::to_string),
18875 synthetic,
18876 )
18877 },
18878 )
18879 }
18880
18881 proptest! {
18882 #![proptest_config(ProptestConfig::with_cases(256))]
18883
18884 #[test]
18887 fn lookup_order_is_reflexive_and_dual(
18888 left in code_unit_strategy(),
18889 right in code_unit_strategy(),
18890 ) {
18891 prop_assert_eq!(
18892 probe_order(&left, &left),
18893 ProbedOrder::Tied,
18894 "a unit must tie with itself: {:?}",
18895 left
18896 );
18897 let forward = probe_order(&left, &right);
18898 prop_assert_ne!(
18899 forward,
18900 ProbedOrder::Contradictory,
18901 "comparator put each of these strictly first: left={:?} right={:?}",
18902 left,
18903 right
18904 );
18905 prop_assert_eq!(
18906 probe_order(&right, &left),
18907 forward.mirror(),
18908 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
18909 left,
18910 right
18911 );
18912 }
18913
18914 #[test]
18916 fn lookup_order_is_transitive(
18917 a in code_unit_strategy(),
18918 b in code_unit_strategy(),
18919 c in code_unit_strategy(),
18920 ) {
18921 let ab = probe_order(&a, &b);
18922 let bc = probe_order(&b, &c);
18923 let ac = probe_order(&a, &c);
18924 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
18925 prop_assert_ne!(
18926 probed,
18927 ProbedOrder::Contradictory,
18928 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
18929 pair,
18930 a,
18931 b,
18932 c
18933 );
18934 }
18935 if ab.signum() <= 0 && bc.signum() <= 0 {
18936 prop_assert!(
18937 ac.signum() <= 0,
18938 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
18939 a={:?} b={:?} c={:?}",
18940 ab,
18941 bc,
18942 ac,
18943 a,
18944 b,
18945 c
18946 );
18947 }
18948 }
18949
18950 #[test]
18953 fn lookup_order_separates_distinct_identities(
18954 left in code_unit_strategy(),
18955 right in code_unit_strategy(),
18956 ) {
18957 if probe_order(&left, &right) == ProbedOrder::Tied {
18958 prop_assert_eq!(
18959 &left,
18960 &right,
18961 "distinct identities tied, so their order is whatever order they \
18962 arrived in: left_segments={:?} right_segments={:?}",
18963 fq_segments(&left),
18964 fq_segments(&right)
18965 );
18966 }
18967 }
18968
18969 #[test]
18972 fn lookup_sort_is_permutation_invariant(
18973 units in prop::collection::vec(code_unit_strategy(), 1..=8),
18974 ) {
18975 let mut sorted = units.clone();
18976 sort_lookup_units(&mut sorted);
18977 for rotation in 0..units.len() {
18978 for reversed in [false, true] {
18979 let mut permuted = units.clone();
18980 permuted.rotate_left(rotation);
18981 if reversed {
18982 permuted.reverse();
18983 }
18984 sort_lookup_units(&mut permuted);
18985 prop_assert_eq!(
18986 &permuted,
18987 &sorted,
18988 "sorting a permutation gave a different list \
18989 (rotation={}, reversed={}): input={:?}",
18990 rotation,
18991 reversed,
18992 units
18993 );
18994 }
18995 }
18996 }
18997 }
18998}