1use crate::call_match::{
2 CppArgType, cpp_signature_param_types, cpp_split_top_level_commas, normalize_cpp_type_name,
3};
4use crate::compile_context::CppCompileContext;
5#[cfg(test)]
6use crate::declarations::cpp_displaced_preprocessor_terminator;
7use crate::declarations::{
8 CppComparableNode, CppComparableParameter, CppComparableSlot, cpp_callable_identity_suffix,
9 cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection,
10 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_field_declaration_linkage,
11 cpp_function_declarator_at, cpp_template_term, node_text, normalize_cpp_whitespace,
12 recovered_embedded_function_like_export_class_has_body, recovered_exported_class_has_body,
13 recovered_fragmented_plain_class_has_body, recovered_function_like_export_class_pair_has_body,
14};
15use crate::graph::CppGraphSource;
16use crate::graph::extractor::ScanCtx;
17use crate::graph::syntax::object_macro_replacement_type_references;
18use crate::graph_support::CppSource;
19use crate::imports::{
20 IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
21};
22use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
23use brokk_bifrost_core::analyzer::model::{
24 CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
25 CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
26};
27use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
28use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
29use brokk_bifrost_core::analyzer::query_token::QueryToken;
30use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, node_for_exact_range};
31use brokk_bifrost_core::analyzer::usages::common::same_node;
32use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
33use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
34use brokk_bifrost_core::cancellation::CancellationToken;
35use brokk_bifrost_core::hash::{HashMap, HashSet};
36use std::borrow::Cow;
37#[cfg(any(test, feature = "test-support"))]
38use std::cell::Cell;
39use std::cell::OnceCell;
40use std::cmp::Ordering as CmpOrdering;
41use std::collections::BTreeSet;
42use std::hash::Hash;
43use std::sync::atomic::{AtomicUsize, Ordering};
44use std::sync::{Arc, Mutex, OnceLock, RwLock};
45use std::thread::ThreadId;
46use std::time::{Duration, Instant};
47use tree_sitter::{Node, Parser, Tree};
48
49#[cfg(any(test, feature = "test-support"))]
50thread_local! {
51 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
52}
53
54#[derive(Clone, Copy, PartialEq, Eq)]
55pub enum TargetKind {
56 Type,
57 Constructor,
58 FreeFunction,
59 Method,
60 GlobalField,
61 MemberField,
62 Macro,
63}
64
65pub enum LexicalTypeResolution {
66 Resolved {
67 unit: CodeUnit,
68 components: Vec<String>,
69 candidates: Vec<CodeUnit>,
70 },
71 Ambiguous,
72 Missing,
73}
74
75#[derive(Clone, Copy)]
76enum TypeCandidateResolution<'a> {
77 Canonical,
78 PreserveAlias,
79 PreserveTarget(&'a CodeUnit),
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91enum TypeCandidateFailure {
92 Ambiguous,
93 Unresolvable,
94}
95
96impl TypeCandidateFailure {
97 fn lexical_resolution(self) -> LexicalTypeResolution {
98 match self {
99 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
100 Self::Unresolvable => LexicalTypeResolution::Missing,
101 }
102 }
103}
104
105pub enum LexicalCallableValueResolution {
106 Type(CodeUnit),
107 FreeFunction(CodeUnit),
108 Ambiguous,
109 Missing,
110}
111
112pub enum UsingEnumMemberResolution {
113 Resolved { owner: CodeUnit, member: CodeUnit },
114 Ambiguous,
115 Missing,
116}
117
118pub enum NamespaceValueResolution {
119 Resolved,
120 Ambiguous,
121 Missing,
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
125pub enum OrdinaryMacroReferenceResolution {
126 Resolved(CodeUnit),
127 Ambiguous,
128 Missing,
129}
130
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub enum RecoveredCReferenceRanges {
133 Complete(Vec<Range>),
134 LimitExceeded,
135}
136
137pub fn resolve_namespace_value(
138 analyzer: &CppGraphSource<'_>,
139 visibility: &VisibilityIndex<'_>,
140 file: &ProjectFile,
141 namespace: &str,
142 name: &str,
143 before_byte: usize,
144) -> NamespaceValueResolution {
145 let mut matches = Vec::new();
146 for candidate in visibility.visible_identifier_candidates(file, name) {
147 if type_owner_of(analyzer, candidate).is_some()
148 || candidate.package_name() != namespace
149 || (candidate.source() == file
150 && !analyzer
151 .ranges(candidate)
152 .iter()
153 .any(|range| range.start_byte < before_byte))
154 || matches
155 .iter()
156 .any(|existing| same_visible_symbol(existing, candidate))
157 {
158 continue;
159 }
160 matches.push(candidate.clone());
161 if matches.len() > 1 {
162 return NamespaceValueResolution::Ambiguous;
163 }
164 }
165 matches
166 .pop()
167 .map(|_| NamespaceValueResolution::Resolved)
168 .unwrap_or(NamespaceValueResolution::Missing)
169}
170
171pub(crate) struct ScopedUsingEnumOwners {
172 scopes: Vec<Vec<CodeUnit>>,
173}
174
175pub(crate) struct SemanticUsingEnumOwners {
180 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
181 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
182}
183
184pub(crate) enum SemanticUsingEnumMemberResolution {
185 Class(UsingEnumMemberResolution),
186 Namespace(UsingEnumMemberResolution),
187 Missing,
188}
189
190impl SemanticUsingEnumOwners {
191 pub(crate) fn new() -> Self {
192 Self {
193 class_imports: HashMap::default(),
194 namespace_imports: HashMap::default(),
195 }
196 }
197
198 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
199 let imports = self.class_imports.entry(class).or_default();
200 if !imports
201 .iter()
202 .any(|existing| same_visible_symbol(existing, &enum_owner))
203 {
204 imports.push(enum_owner);
205 }
206 }
207
208 pub fn import_namespace(
209 &mut self,
210 namespace: Vec<String>,
211 declaration_byte: usize,
212 enum_owner: CodeUnit,
213 ) {
214 let imports = self.namespace_imports.entry(namespace).or_default();
215 if !imports
216 .iter()
217 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
218 {
219 imports.push((declaration_byte, enum_owner));
220 }
221 }
222
223 pub fn resolve_member(
224 &self,
225 visibility: &VisibilityIndex<'_>,
226 file: &ProjectFile,
227 class: Option<&CodeUnit>,
228 namespace: &[String],
229 before_byte: usize,
230 name: &str,
231 ) -> SemanticUsingEnumMemberResolution {
232 if let Some(class) = class
233 && let Some((_, imports)) = self
234 .class_imports
235 .iter()
236 .find(|(owner, _)| same_visible_symbol(owner, class))
237 {
238 let resolution =
239 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
240 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
241 return SemanticUsingEnumMemberResolution::Class(resolution);
242 }
243 }
244 for prefix_len in (0..=namespace.len()).rev() {
245 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
246 continue;
247 };
248 let owners = imports
249 .iter()
250 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
251 .map(|(_, owner)| owner);
252 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
253 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
254 return SemanticUsingEnumMemberResolution::Namespace(resolution);
255 }
256 }
257 SemanticUsingEnumMemberResolution::Missing
258 }
259}
260
261fn resolve_using_enum_member_for_owners<'a>(
262 visibility: &VisibilityIndex<'_>,
263 file: &ProjectFile,
264 owners: impl IntoIterator<Item = &'a CodeUnit>,
265 name: &str,
266) -> UsingEnumMemberResolution {
267 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
268 for owner in owners {
269 for member in visibility.visible_members_for_owner_name(file, owner, name) {
270 if !member.is_field()
271 || matches.iter().any(|(existing_owner, existing_member)| {
272 same_visible_symbol(existing_owner, owner)
273 && same_visible_symbol(existing_member, member)
274 })
275 {
276 continue;
277 }
278 matches.push((owner.clone(), member.clone()));
279 }
280 }
281 match matches.len() {
282 0 => UsingEnumMemberResolution::Missing,
283 1 => {
284 let (owner, member) = matches.pop().expect("one using-enum match");
285 UsingEnumMemberResolution::Resolved { owner, member }
286 }
287 _ => UsingEnumMemberResolution::Ambiguous,
288 }
289}
290
291impl ScopedUsingEnumOwners {
292 pub(crate) fn new() -> Self {
293 Self {
294 scopes: vec![Vec::new()],
295 }
296 }
297
298 pub fn enter_scope(&mut self) {
299 self.scopes.push(Vec::new());
300 }
301
302 pub fn exit_scope(&mut self) {
303 if self.scopes.len() > 1 {
304 self.scopes.pop();
305 }
306 }
307
308 pub fn import(&mut self, owner: CodeUnit) {
309 let scope = self
310 .scopes
311 .last_mut()
312 .expect("using-enum scope stack is never empty");
313 if !scope
314 .iter()
315 .any(|existing| same_visible_symbol(existing, &owner))
316 {
317 scope.push(owner);
318 }
319 }
320
321 pub fn resolve_member(
322 &self,
323 visibility: &VisibilityIndex<'_>,
324 file: &ProjectFile,
325 name: &str,
326 ) -> UsingEnumMemberResolution {
327 for scope in self.scopes.iter().rev() {
328 let resolution =
329 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
330 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
331 return resolution;
332 }
333 }
334 UsingEnumMemberResolution::Missing
335 }
336}
337
338#[derive(Clone)]
339pub struct TargetSpec {
340 pub target: CodeUnit,
341 pub kind: TargetKind,
342 pub owner: Option<CodeUnit>,
343 pub member_name: String,
344 pub callable_arity: Option<CallableArity>,
345 pub activated_callable_arities: Vec<ActivatedCallableArity>,
346 pub param_types: Option<Vec<String>>,
347 pub enum_owner_kind: EnumOwnerKind,
348 pub owner_is_forward_declaration: bool,
349 pub callable_has_definition_body: bool,
350}
351
352#[derive(Clone, Copy)]
353pub struct ActivatedCallableArity {
354 pub activation_byte: usize,
355 pub arity: CallableArity,
356}
357
358#[derive(Debug, PartialEq, Eq, Hash)]
359pub struct TypeScanKey {
360 target: LogicalSymbolKey,
361 member_name: String,
362}
363
364#[derive(Clone, Debug, PartialEq, Eq, Hash)]
365struct LogicalSymbolKey {
366 kind: CodeUnitType,
367 fq_name: String,
368 signature: Option<String>,
369}
370
371struct ResolvedTypeOwner {
372 unit: CodeUnit,
373 is_forward_declaration: bool,
374}
375
376#[derive(Clone, Copy, PartialEq, Eq)]
377pub enum EnumOwnerKind {
378 Scoped,
379 Unscoped,
380 NonEnum,
381}
382
383impl TargetSpec {
384 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
385 (self.kind == TargetKind::Type).then(|| TypeScanKey {
386 target: logical_symbol_key(&self.target),
387 member_name: self.member_name.clone(),
388 })
389 }
390
391 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
392 if target.is_class() {
393 return Some(Self::new(
394 target.clone(),
395 TargetKind::Type,
396 Some(target.clone()),
397 target.identifier().to_string(),
398 None,
399 None,
400 ));
401 }
402
403 if target.is_field() {
404 let owner = type_owner_of(analyzer, target);
410 let kind = if owner.is_some() {
411 TargetKind::MemberField
412 } else {
413 TargetKind::GlobalField
414 };
415 let enum_owner_kind = owner
416 .as_ref()
417 .map(|owner| classify_enum_owner(analyzer, owner))
418 .unwrap_or(EnumOwnerKind::NonEnum);
419 let mut spec = Self::new(
420 target.clone(),
421 kind,
422 owner,
423 target.identifier().to_string(),
424 None,
425 None,
426 );
427 spec.enum_owner_kind = enum_owner_kind;
428 return Some(spec);
429 }
430
431 if target.is_function() {
432 let owner_resolution = target_type_owner_resolution(analyzer, target);
435 let owner_is_forward_declaration = owner_resolution
436 .as_ref()
437 .is_some_and(|owner| owner.is_forward_declaration);
438 let owner = owner_resolution.map(|owner| owner.unit);
439 let kind = if owner.as_ref().is_some_and(|owner| {
440 target.identifier() == owner.identifier()
441 || analyzer
442 .cpp
443 .and_then(|cpp| cpp.template_metadata(owner))
444 .is_some_and(|metadata| metadata.primary_name == target.identifier())
445 }) {
446 TargetKind::Constructor
447 } else if owner.is_some() {
448 TargetKind::Method
449 } else {
450 TargetKind::FreeFunction
451 };
452 let mut spec = Self::new(
453 target.clone(),
454 kind,
455 owner,
456 target.identifier().to_string(),
457 Some(cpp_callable_arity(analyzer, target)),
458 cpp_callable_parameter_types(analyzer, target),
459 );
460 spec.owner_is_forward_declaration = owner_is_forward_declaration;
461 spec.callable_has_definition_body =
462 callable_target_has_definition_body(analyzer, target);
463 return Some(spec);
464 }
465
466 if target.is_macro() {
467 return Some(Self::new(
468 target.clone(),
469 TargetKind::Macro,
470 None,
471 target.identifier().to_string(),
472 None,
473 None,
474 ));
475 }
476
477 None
478 }
479
480 pub fn with_visible_callable_arities<'a>(
481 &'a self,
482 analyzer: &CppGraphSource<'_>,
483 cpp: &dyn CppSource,
484 visibility: &VisibilityIndex<'_>,
485 file: &ProjectFile,
486 prepared: &PreparedSyntaxTree,
487 ) -> Cow<'a, Self> {
488 let macro_parameter_arity =
489 visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
490 let activated_callable_arities =
491 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
492 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
493 return Cow::Borrowed(self);
494 }
495 let mut effective = self.clone();
496 if let Some(macro_parameter_arity) = macro_parameter_arity {
497 effective.callable_arity = Some(macro_parameter_arity);
498 }
499 effective.activated_callable_arities = activated_callable_arities;
500 Cow::Owned(effective)
501 }
502
503 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
504 let base = self.callable_arity?;
505 Some(
506 self.activated_callable_arities
507 .iter()
508 .filter(|candidate| candidate.activation_byte <= byte)
509 .fold(base, |arity, candidate| {
510 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
511 }),
512 )
513 }
514
515 pub fn new(
516 target: CodeUnit,
517 kind: TargetKind,
518 owner: Option<CodeUnit>,
519 member_name: String,
520 callable_arity: Option<CallableArity>,
521 param_types: Option<Vec<String>>,
522 ) -> Self {
523 Self {
524 target,
525 kind,
526 owner,
527 member_name,
528 callable_arity,
529 activated_callable_arities: Vec::new(),
530 param_types,
531 enum_owner_kind: EnumOwnerKind::NonEnum,
532 owner_is_forward_declaration: false,
533 callable_has_definition_body: false,
534 }
535 }
536}
537
538fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
539 let Some(cpp) = analyzer.cpp else {
540 return false;
541 };
542 let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
543 return false;
544 };
545 analyzer.ranges(target).into_iter().any(|range| {
546 let end = range
547 .start_byte
548 .saturating_add(1)
549 .min(prepared.source().len());
550 let mut current = prepared
551 .tree()
552 .root_node()
553 .descendant_for_byte_range(range.start_byte, end);
554 while let Some(node) = current {
555 match node.kind() {
556 "function_definition" => return true,
557 "declaration" => return false,
558 _ => current = node.parent(),
559 }
560 }
561 false
562 })
563}
564
565fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
566 LogicalSymbolKey {
567 kind: unit.kind(),
568 fq_name: unit.fq_name(),
569 signature: unit.signature().map(str::to_string),
570 }
571}
572
573fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
574 let classify = |source: &str| {
575 let source = source.trim_start();
576 if source.starts_with("enum class ") || source.starts_with("enum struct ") {
577 Some(EnumOwnerKind::Scoped)
578 } else if source.starts_with("enum ") {
579 Some(EnumOwnerKind::Unscoped)
580 } else {
581 None
582 }
583 };
584 owner
585 .signature()
586 .and_then(classify)
587 .or_else(|| {
588 analyzer
589 .get_source(owner, false)
590 .as_deref()
591 .and_then(classify)
592 })
593 .unwrap_or(EnumOwnerKind::NonEnum)
594}
595
596#[derive(Clone, PartialEq, Eq, Hash)]
597pub struct CppScanBinding {
598 pub unit: Option<CodeUnit>,
599 pub type_name: Option<String>,
600 pub indirection: i32,
601}
602
603impl CppScanBinding {
604 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
605 Self {
606 type_name: Some(cpp_name_for(&unit)),
607 unit: Some(unit),
608 indirection,
609 }
610 }
611
612 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
613 Self {
614 type_name: Some(type_name),
615 unit,
616 indirection,
617 }
618 }
619
620 pub fn as_arg_type(&self) -> Option<CppArgType> {
621 let name = self
622 .type_name
623 .clone()
624 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
625 Some(CppArgType {
626 name,
627 unit: self.unit.clone(),
628 indirection: self.indirection,
629 pointee_const: false,
630 })
631 }
632}
633
634type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
635pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
636pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
637type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
638pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
639type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
640type MacroLocalBindingTemplateCache =
641 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
642
643#[derive(Clone, Default)]
644pub struct MacroEnvironment {
645 bindings: HashMap<String, MacroBinding>,
646 known_undefined_names: HashSet<String>,
647 build_proven_defines: HashSet<String>,
652 unknown_names: bool,
653 applied_pragma_once_files: HashSet<ProjectFile>,
654 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
655}
656
657#[derive(Default)]
658pub struct MacroEnvironmentCursor {
659 frontier: usize,
660 environment: Arc<MacroEnvironment>,
661}
662
663impl MacroEnvironment {
664 fn binding(&self, name: &str) -> Option<&MacroBinding> {
665 self.bindings.get(name)
666 }
667
668 fn may_bind(&self, name: &str) -> bool {
669 self.bindings.contains_key(name) || self.unknown_names
670 }
671
672 fn insert(&mut self, name: String, binding: MacroBinding) {
673 self.known_undefined_names.remove(&name);
674 self.bindings.insert(name, binding);
675 }
676
677 fn remove(&mut self, name: &str) {
678 self.bindings.remove(name);
679 self.known_undefined_names.insert(name.to_string());
680 }
681
682 fn remove_known_undefined(&mut self, name: &str) {
683 self.known_undefined_names.remove(name);
684 }
685
686 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
687 for binding in self.bindings.values_mut() {
688 *binding = MacroBinding::uncertain_from(binding, source, byte);
689 }
690 self.known_undefined_names.clear();
691 self.build_proven_defines.clear();
696 self.unknown_names = true;
697 }
698
699 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
700 guards.iter().all(|guard| self.guard_may_hold(guard))
701 }
702
703 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
704 let Some(expression) = guard.as_boolean_expression() else {
705 return true;
706 };
707 self.boolean_guard_may_hold(&expression)
708 }
709
710 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
711 match expression {
712 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
713 BooleanGuardExpression::Undefined(name) => {
714 self.bindings
715 .get(name)
716 .is_none_or(|binding| !binding.is_exact())
717 && (!self.build_proven_defines.contains(name)
718 || self.known_undefined_names.contains(name))
719 }
720 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
721 BooleanGuardExpression::Opaque(_)
722 | BooleanGuardExpression::NegatedOpaque(_)
723 | BooleanGuardExpression::Constant(true) => true,
724 BooleanGuardExpression::Constant(false) => false,
725 BooleanGuardExpression::All(expressions) => expressions
726 .iter()
727 .all(|expression| self.boolean_guard_may_hold(expression)),
728 BooleanGuardExpression::Any(expressions) => expressions
729 .iter()
730 .any(|expression| self.boolean_guard_may_hold(expression)),
731 }
732 }
733}
734
735#[derive(Clone)]
736pub enum EffectiveUsingTarget {
737 Ordinary {
738 name: String,
739 target_components: Vec<String>,
740 global: bool,
741 },
742 Namespace {
743 namespace_components: Vec<String>,
744 global: bool,
745 },
746}
747
748#[derive(Clone)]
749pub struct OrdinaryTypeImport {
750 pub target: EffectiveUsingTarget,
751 pub source: ProjectFile,
752 pub declaration_byte: usize,
753 pub scope_start: usize,
754 pub scope_end: usize,
755 pub scope_depth: usize,
756 pub block_scope: bool,
757 pub lexical_depth: usize,
758 pub declaration_namespace: Vec<String>,
759 pub namespace_scope: Option<Vec<String>>,
760 pub resolved_target_components: Option<Vec<String>>,
761 pub required_guards: HashSet<PreprocessorGuard>,
762}
763
764#[derive(Clone)]
765pub struct ConditionalIncludeProjection {
766 pub activation_byte: usize,
767 pub required_guards: HashSet<PreprocessorGuard>,
768}
769
770#[derive(Default)]
771pub struct SourceUsingIndex {
772 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
773 pub directives: Vec<OrdinaryTypeImport>,
774}
775
776#[derive(Default)]
777pub struct ProjectUsingIndex {
778 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
779 pub directives: Vec<OrdinaryTypeImport>,
780}
781
782type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
783
784pub struct EffectiveUsingIndex {
785 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
786}
787
788impl EffectiveUsingIndex {
789 fn new(_root: ProjectFile) -> Self {
790 Self {
791 projected_by_name: Mutex::new(HashMap::default()),
792 }
793 }
794
795 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
796 self.projected_by_name
797 .lock()
798 .expect("C++ effective-using projection cache poisoned")
799 .entry(name.to_string())
800 .or_default()
801 .clone()
802 }
803}
804
805pub enum OrdinaryTypeImportResolution {
806 Resolved {
807 target: CodeUnit,
808 target_components: Vec<String>,
809 lexical_depth: usize,
810 is_direct: bool,
811 },
812 Ambiguous {
813 lexical_depth: usize,
814 },
815 Missing,
816}
817
818type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
819type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
820type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
821type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
822type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
823type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
824type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
825
826struct ExtractedComparable {
831 shapes: Vec<CppComparableSlot>,
832 suffix: String,
833}
834
835const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
839
840pub struct VisibilityIndex<'a> {
851 cpp: &'a dyn CppSource,
852 token: QueryToken<'a>,
857 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
858 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
859 global_field_internal_linkage: HashMap<CodeUnit, bool>,
860 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
861 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
862 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
863 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
864 project_using_index: OnceLock<ProjectUsingIndex>,
865 callable_reference_specs:
866 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
867 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
868 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
869 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
870 #[cfg(any(test, feature = "test-support"))]
871 conditional_include_projection_index_build_count: AtomicUsize,
872 #[cfg(any(test, feature = "test-support"))]
873 conditional_include_projection_state_count: AtomicUsize,
874 #[cfg(any(test, feature = "test-support"))]
875 conditional_include_target_state_count: AtomicUsize,
876 #[cfg(any(test, feature = "test-support"))]
877 include_activation_build_count: AtomicUsize,
878 #[cfg(any(test, feature = "test-support"))]
879 using_donor_activation_count: AtomicUsize,
880 #[cfg(any(test, feature = "test-support"))]
881 using_namespace_lookup_count: AtomicUsize,
882 #[cfg(any(test, feature = "test-support"))]
883 using_name_candidate_inspection_count: AtomicUsize,
884 #[cfg(any(test, feature = "test-support"))]
885 callable_reference_spec_build_count: AtomicUsize,
886 #[cfg(any(test, feature = "test-support"))]
887 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
888 #[cfg(any(test, feature = "test-support"))]
889 visible_parser_alias_name_set_build_count: AtomicUsize,
890 parser_alias_fallback_calls: AtomicUsize,
891 parser_alias_fallback_files: AtomicUsize,
892 parser_alias_source_parses: AtomicUsize,
893 parser_alias_fallback_elapsed_micros: AtomicUsize,
894 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
895 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
896 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
897 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
898 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
899 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
900 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
901 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
902 pub macro_environment_cursors:
908 Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
909 macro_replacements: Mutex<MacroReplacementCache>,
910 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
911 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
912 #[cfg(any(test, feature = "test-support"))]
913 pub macro_replacement_parse_count: AtomicUsize,
914 #[cfg(any(test, feature = "test-support"))]
915 pub macro_event_application_count: AtomicUsize,
916 #[cfg(any(test, feature = "test-support"))]
917 pub macro_environment_copy_count: AtomicUsize,
918 #[cfg(any(test, feature = "test-support"))]
919 pub macro_environment_request_count: AtomicUsize,
920 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
921 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
922 #[cfg(any(test, feature = "test-support"))]
923 qualified_candidate_inspections: AtomicUsize,
924 #[cfg(any(test, feature = "test-support"))]
925 target_preserving_type_resolution_count: AtomicUsize,
926}
927
928impl Drop for VisibilityIndex<'_> {
929 fn drop(&mut self) {
930 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
931 return;
932 }
933 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
934 if calls == 0 {
935 return;
936 }
937 eprintln!(
938 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
939 calls,
940 self.parser_alias_fallback_files.load(Ordering::Relaxed),
941 self.parser_alias_source_parses.load(Ordering::Relaxed),
942 self.parser_alias_fallback_elapsed_micros
943 .load(Ordering::Relaxed)
944 / 1_000,
945 );
946 }
947}
948
949#[derive(Clone, Debug, PartialEq, Eq, Hash)]
950pub enum PreprocessorGuard {
951 Defined(String),
952 Undefined(String),
953 Boolean(BooleanGuardExpression),
954 Expression(String),
955 NegatedExpression(String),
956 Constant(bool),
957}
958
959#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
960pub enum BooleanGuardExpression {
961 Defined(String),
962 Undefined(String),
963 Truthy(String),
964 Falsy(String),
965 Opaque(String),
966 NegatedOpaque(String),
967 All(Vec<BooleanGuardExpression>),
968 Any(Vec<BooleanGuardExpression>),
969 Constant(bool),
970}
971
972impl BooleanGuardExpression {
973 fn negated(&self) -> Self {
974 match self {
975 Self::Defined(name) => Self::Undefined(name.clone()),
976 Self::Undefined(name) => Self::Defined(name.clone()),
977 Self::Truthy(name) => Self::Falsy(name.clone()),
978 Self::Falsy(name) => Self::Truthy(name.clone()),
979 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
980 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
981 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
982 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
983 Self::Constant(value) => Self::Constant(!value),
984 }
985 }
986
987 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
988 Self::normalized(expressions, true)
989 }
990
991 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
992 Self::normalized(expressions, false)
993 }
994
995 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
996 let mut normalized = Vec::new();
997 for expression in expressions {
998 match expression {
999 Self::All(nested) if conjunction => normalized.extend(nested),
1000 Self::Any(nested) if !conjunction => normalized.extend(nested),
1001 Self::Constant(value) if value == conjunction => {}
1002 Self::Constant(value) => return Self::Constant(value),
1003 expression => normalized.push(expression),
1004 }
1005 }
1006 normalized.sort_unstable();
1007 normalized.dedup();
1008 match normalized.len() {
1009 0 => Self::Constant(conjunction),
1010 1 => normalized.pop().expect("one Boolean guard expression"),
1011 _ if conjunction => Self::All(normalized),
1012 _ => Self::Any(normalized),
1013 }
1014 }
1015
1016 fn implies(&self, required: &Self) -> bool {
1017 if self == required
1018 || matches!(self, Self::Constant(false))
1019 || matches!(required, Self::Constant(true))
1020 {
1021 return true;
1022 }
1023 if matches!(
1024 (self, required),
1025 (Self::Truthy(active), Self::Defined(required))
1026 | (Self::Undefined(active), Self::Falsy(required))
1027 if active == required
1028 ) {
1029 return true;
1030 }
1031 match self {
1032 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1033 Self::All(active) => match required {
1034 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1035 _ => active.iter().any(|expression| expression.implies(required)),
1036 },
1037 _ => match required {
1038 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1039 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1040 _ => false,
1041 },
1042 }
1043 }
1044
1045 pub fn heap_size(&self) -> usize {
1046 match self {
1047 Self::Defined(value)
1048 | Self::Undefined(value)
1049 | Self::Truthy(value)
1050 | Self::Falsy(value)
1051 | Self::Opaque(value)
1052 | Self::NegatedOpaque(value) => value.len(),
1053 Self::All(expressions) | Self::Any(expressions) => {
1054 expressions
1055 .iter()
1056 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1057 size.saturating_add(std::mem::size_of::<Self>())
1058 .saturating_add(expression.heap_size())
1059 })
1060 }
1061 Self::Constant(_) => 0,
1062 }
1063 }
1064}
1065
1066impl PreprocessorGuard {
1067 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1068 match self {
1069 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1070 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1071 Self::Boolean(expression) => Some(expression.clone()),
1072 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1073 Self::Expression(_) | Self::NegatedExpression(_) => None,
1074 }
1075 }
1076
1077 fn negated(&self) -> Self {
1078 match self {
1079 Self::Defined(name) => Self::Undefined(name.clone()),
1080 Self::Undefined(name) => Self::Defined(name.clone()),
1081 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1082 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1083 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1084 Self::Constant(value) => Self::Constant(!value),
1085 }
1086 }
1087
1088 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1089 match self {
1090 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1091 Self::Boolean(_) | Self::Expression(_) | Self::NegatedExpression(_) => true,
1096 Self::Constant(_) => false,
1097 }
1098 }
1099}
1100
1101#[derive(Clone, PartialEq, Eq)]
1102pub enum MacroDefinition {
1103 Object {
1104 replacement: String,
1105 },
1106 Function {
1107 parameters: Vec<String>,
1108 replacement: String,
1109 },
1110 Unsupported,
1111}
1112
1113#[derive(Clone, Debug, PartialEq, Eq)]
1114pub enum MacroIncludeProtection {
1115 MacroGuard(String),
1116 PragmaOnce,
1117 None,
1118}
1119
1120enum ParsedMacroReplacement {
1121 Parsed { source: String, tree: Tree },
1122 Unsupported,
1123}
1124
1125fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1126 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1127 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1128 if compact.starts_with("0x") || compact.starts_with("0X") {
1129 (16, 2, |ch| ch.is_ascii_hexdigit())
1130 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1131 (2, 2, |ch| matches!(ch, '0' | '1'))
1132 } else if compact.starts_with('0') && compact.len() > 1 {
1133 (8, 0, |ch| matches!(ch, '0'..='7'))
1134 } else {
1135 (10, 0, |ch| ch.is_ascii_digit())
1136 };
1137 let digit_len = compact[digits_start..]
1138 .chars()
1139 .take_while(|ch| digit_matches(*ch))
1140 .map(char::len_utf8)
1141 .sum::<usize>();
1142 if digit_len == 0 {
1143 return None;
1144 }
1145 let digits_end = digits_start + digit_len;
1146 if !compact[digits_end..]
1147 .chars()
1148 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1149 {
1150 return None;
1151 }
1152 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1153}
1154
1155#[derive(Clone)]
1156enum MacroLocalBindingTypeTemplate {
1157 Parameter(usize),
1158 Fixed(String),
1159}
1160
1161#[derive(Clone)]
1162struct MacroLocalBindingTemplate {
1163 name: String,
1164 declared_type: MacroLocalBindingTypeTemplate,
1165 pointer_depth: i32,
1166}
1167
1168pub struct MacroLocalBinding<'tree> {
1174 pub name: String,
1175 pub type_name: String,
1176 pub type_node: Option<Node<'tree>>,
1177 pub pointer_depth: i32,
1178}
1179
1180fn recognized_c_macro_declarator_binding<'tree>(
1185 statement: Node<'tree>,
1186 source: &str,
1187) -> Option<MacroLocalBinding<'tree>> {
1188 let assignment = match statement.kind() {
1189 "assignment_expression" => statement,
1190 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1191 _ => return None,
1192 };
1193 if assignment.kind() != "assignment_expression" {
1194 return None;
1195 }
1196 let call = assignment.child_by_field_name("left")?;
1197 if call.kind() != "call_expression" {
1198 return None;
1199 }
1200 let function = call.child_by_field_name("function")?;
1201 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1202 return None;
1203 }
1204 let arguments = call.child_by_field_name("arguments")?;
1205 let mut actuals = argument_children(arguments);
1206 let type_node = actuals.next()?;
1207 if actuals.next().is_some()
1208 || !matches!(
1209 type_node.kind(),
1210 "identifier"
1211 | "type_identifier"
1212 | "qualified_identifier"
1213 | "scoped_type_identifier"
1214 | "template_type"
1215 )
1216 {
1217 return None;
1218 }
1219 let name_node = (0..assignment.named_child_count())
1220 .filter_map(|index| assignment.named_child(index))
1221 .filter(|child| child.kind() == "ERROR")
1222 .filter_map(|error| {
1223 (error.named_child_count() == 1)
1224 .then(|| error.named_child(0))
1225 .flatten()
1226 })
1227 .find(|node| node.kind() == "identifier")?;
1228 let name = node_text(name_node, source).trim();
1229 let type_name = node_text(type_node, source).trim();
1230 if name.is_empty() || type_name.is_empty() {
1231 return None;
1232 }
1233 Some(MacroLocalBinding {
1234 name: name.to_string(),
1235 type_name: type_name.to_string(),
1236 type_node: Some(type_node),
1237 pointer_depth: 1,
1238 })
1239}
1240
1241#[derive(Clone, PartialEq, Eq)]
1242pub struct MacroBinding {
1243 source: ProjectFile,
1244 declaration_byte: usize,
1245 definition: MacroDefinition,
1246 exact: bool,
1247}
1248
1249impl MacroBinding {
1250 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1251 Self {
1252 source: source.clone(),
1253 declaration_byte,
1254 definition: MacroDefinition::Unsupported,
1255 exact: false,
1256 }
1257 }
1258
1259 fn is_exact(&self) -> bool {
1260 self.exact
1261 }
1262
1263 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1264 Self {
1265 source: source.clone(),
1266 declaration_byte,
1267 definition: current.definition.clone(),
1268 exact: false,
1269 }
1270 }
1271}
1272
1273#[derive(Clone)]
1274pub enum MacroEvent {
1275 Define {
1276 name: String,
1277 binding: MacroBinding,
1278 byte: usize,
1279 conditional: bool,
1280 },
1281 Undef {
1282 name: String,
1283 byte: usize,
1284 conditional: bool,
1285 },
1286 Include {
1287 targets: Vec<ProjectFile>,
1288 byte: usize,
1289 conditional: bool,
1290 },
1291 Invalidate {
1292 byte: usize,
1293 },
1294}
1295
1296impl MacroEvent {
1297 pub fn byte(&self) -> usize {
1298 match self {
1299 Self::Define { byte, .. }
1300 | Self::Undef { byte, .. }
1301 | Self::Include { byte, .. }
1302 | Self::Invalidate { byte } => *byte,
1303 }
1304 }
1305}
1306
1307#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1308pub enum CallArityEvidence {
1309 Exact(usize),
1310 Unknown,
1311}
1312
1313impl CallArityEvidence {
1314 pub fn exact(self) -> Option<usize> {
1315 match self {
1316 Self::Exact(arity) => Some(arity),
1317 Self::Unknown => None,
1318 }
1319 }
1320
1321 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1322 self.exact().map(|arity| expected.accepts(arity))
1323 }
1324}
1325
1326#[derive(Clone)]
1327struct DeclaredFieldTypeFact {
1328 type_text: String,
1329 indirection: i32,
1330 template_arguments: Option<Vec<CppTemplateExpression>>,
1331}
1332
1333#[derive(Clone, PartialEq, Eq)]
1334enum StructuredAliasTarget {
1335 Builtin,
1336 Named {
1337 components: Vec<String>,
1338 global: bool,
1339 arguments: Option<Vec<CppTemplateExpression>>,
1340 },
1341}
1342
1343struct CppAlias {
1344 name: String,
1345 target: String,
1346 namespace: Option<String>,
1347}
1348
1349type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1350
1351#[derive(Debug, Clone, PartialEq, Eq)]
1355pub enum CppTemplateResolutionError {
1356 AliasCycle { alias: CodeUnit },
1358 ArgumentBinding,
1360 Substitution,
1362 PrimarySelection,
1365 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1368}
1369
1370fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1373 let mut distinct: Vec<CodeUnit> = Vec::new();
1374 for unit in units {
1375 if !distinct
1376 .iter()
1377 .any(|existing| same_visible_symbol(existing, unit))
1378 {
1379 distinct.push(unit.clone());
1380 }
1381 }
1382 distinct
1383}
1384
1385impl<'a> VisibilityIndex<'a> {
1386 pub fn cpp(&self) -> &'a dyn CppSource {
1387 self.cpp
1388 }
1389
1390 pub fn token(&self) -> QueryToken<'a> {
1392 self.token
1393 }
1394
1395 #[cfg(any(test, feature = "test-support"))]
1403 pub fn from_visible_files_for_test(
1404 cpp: &'a dyn CppSource,
1405 token: QueryToken<'a>,
1406 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1407 ) -> Self {
1408 let visible_source_files_by_root = visible_by_file
1409 .iter()
1410 .map(|(file, visible)| {
1411 (
1412 file.clone(),
1413 visible
1414 .iter()
1415 .map(|unit| unit.source().clone())
1416 .chain(std::iter::once(file.clone()))
1417 .collect(),
1418 )
1419 })
1420 .collect();
1421 let mut global_field_internal_linkage = HashMap::default();
1422 Self {
1423 cpp,
1424 token,
1425 visible_by_identifier: build_visible_identifier_index(
1426 &CppGraphSource::from_source(cpp, token),
1427 &visible_by_file,
1428 &visible_source_files_by_root,
1429 &mut global_field_internal_linkage,
1430 ),
1431 global_field_internal_linkage,
1432 visible_by_file,
1433 visible_source_files_by_root,
1434 alias_cells: Mutex::new(HashMap::default()),
1435 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1436 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1437 project_using_index: OnceLock::new(),
1438 callable_reference_specs: Mutex::new(HashMap::default()),
1439 include_activation_cells: Mutex::new(HashMap::default()),
1440 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1441 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1442 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1443 conditional_include_projection_state_count: AtomicUsize::new(0),
1444 conditional_include_target_state_count: AtomicUsize::new(0),
1445 include_activation_build_count: AtomicUsize::new(0),
1446 using_donor_activation_count: AtomicUsize::new(0),
1447 using_namespace_lookup_count: AtomicUsize::new(0),
1448 using_name_candidate_inspection_count: AtomicUsize::new(0),
1449 callable_reference_spec_build_count: AtomicUsize::new(0),
1450 alias_source_parse_counts: Mutex::new(HashMap::default()),
1451 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1452 parser_alias_fallback_calls: AtomicUsize::new(0),
1453 parser_alias_fallback_files: AtomicUsize::new(0),
1454 parser_alias_source_parses: AtomicUsize::new(0),
1455 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1456 field_type_facts: Mutex::new(HashMap::default()),
1457 structured_alias_targets: Mutex::new(HashMap::default()),
1458 callable_comparables: Mutex::new(HashMap::default()),
1459 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1460 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1461 precise_parent_cache: Mutex::new(HashMap::default()),
1462 macro_event_cells: Mutex::new(HashMap::default()),
1463 macro_include_protection_cells: Mutex::new(HashMap::default()),
1464 macro_environment_cursors: Mutex::new(HashMap::default()),
1465 macro_replacements: Mutex::new(HashMap::default()),
1466 macro_local_binding_templates: Mutex::new(HashMap::default()),
1467 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1468 macro_replacement_parse_count: AtomicUsize::new(0),
1469 macro_event_application_count: AtomicUsize::new(0),
1470 macro_environment_copy_count: AtomicUsize::new(0),
1471 macro_environment_request_count: AtomicUsize::new(0),
1472 cpp_template_metadata: HashMap::default(),
1473 cpp_template_families: HashMap::default(),
1474 qualified_candidate_inspections: AtomicUsize::new(0),
1475 target_preserving_type_resolution_count: AtomicUsize::new(0),
1476 }
1477 }
1478
1479 fn cpp_source(&self) -> CppGraphSource<'a> {
1486 CppGraphSource::from_source(self.cpp, self.token)
1487 }
1488
1489 pub fn build(
1490 cpp: &'a dyn CppSource,
1491 token: QueryToken<'a>,
1492 analyzer: &CppGraphSource<'_>,
1493 roots: &HashSet<ProjectFile>,
1494 ) -> Self {
1495 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1496 }
1497
1498 pub fn build_with_cancellation(
1499 cpp: &'a dyn CppSource,
1500 token: QueryToken<'a>,
1501 analyzer: &CppGraphSource<'_>,
1502 roots: &HashSet<ProjectFile>,
1503 cancellation: Option<&CancellationToken>,
1504 ) -> Self {
1505 let visibility_started = Instant::now();
1506 let include_targets = cpp.include_target_index();
1507 let includes_started = Instant::now();
1508 let mut include_graph = IncludeGraph::default();
1509 for root in roots {
1510 include_graph.extend_with(root, cancellation, &mut |file| {
1511 cpp_include_paths(&cpp.visibility_import_statements(token, file))
1512 .into_iter()
1513 .flat_map(|include| {
1514 resolve_include_targets_with_index(file, &include, include_targets)
1515 })
1516 .collect()
1517 });
1518 }
1519 let include_elapsed = includes_started.elapsed();
1520 let include_file_count = include_graph.files().count();
1521 let visible_source_files_by_root = roots
1522 .iter()
1523 .map(|root| {
1524 (
1525 root.clone(),
1526 include_graph.reachable_files(root, cancellation),
1527 )
1528 })
1529 .collect::<HashMap<_, _>>();
1530 let mut visibility_stats = BoundedVisibilityStats::default();
1531 let mut visible_by_file = build_bounded_visible_declarations(
1532 cpp,
1533 token,
1534 analyzer,
1535 roots,
1536 &visible_source_files_by_root,
1537 cancellation,
1538 &mut visibility_stats,
1539 );
1540 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
1541 eprintln!(
1542 "BIFROST_CPP_VISIBILITY_STATS total_ms={} include_ms={} include_files={} rounds={} root_names={} identifier_lookups={} candidate_units={} candidate_sources={} declaration_reads={} declaration_units={} selected_units={} dependency_ast_nodes={} dependency_names={} lookup_ms={} declaration_ms={} dependency_ast_ms={}",
1543 visibility_started.elapsed().as_millis(),
1544 include_elapsed.as_millis(),
1545 include_file_count,
1546 visibility_stats.rounds,
1547 visibility_stats.root_names,
1548 visibility_stats.identifier_lookups,
1549 visibility_stats.candidate_units,
1550 visibility_stats.candidate_sources,
1551 visibility_stats.declaration_reads,
1552 visibility_stats.declaration_units,
1553 visibility_stats.selected_units,
1554 visibility_stats.dependency_ast_nodes,
1555 visibility_stats.dependency_names,
1556 visibility_stats.lookup_elapsed.as_millis(),
1557 visibility_stats.declaration_elapsed.as_millis(),
1558 visibility_stats.dependency_ast_elapsed.as_millis(),
1559 );
1560 }
1561 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
1562 let finalize_started = Instant::now();
1563 if report_stats {
1564 eprintln!(
1565 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
1566 visible_by_file.len(),
1567 visible_by_file.values().map(HashSet::len).sum::<usize>(),
1568 );
1569 }
1570 let owner_started = Instant::now();
1571 if report_stats {
1572 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
1573 }
1574 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1575 if report_stats {
1576 eprintln!(
1577 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
1578 owner_stats.unseen_owners,
1579 owner_stats.definition_lookups,
1580 owner_stats.admitted,
1581 owner_started.elapsed().as_millis(),
1582 );
1583 }
1584 let mut global_field_internal_linkage = HashMap::default();
1585 let identifier_started = Instant::now();
1586 if report_stats {
1587 eprintln!(
1588 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
1589 );
1590 }
1591 let visible_by_identifier = build_visible_identifier_index(
1592 analyzer,
1593 &visible_by_file,
1594 &visible_source_files_by_root,
1595 &mut global_field_internal_linkage,
1596 );
1597 if report_stats {
1598 eprintln!(
1599 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
1600 visible_by_identifier.len(),
1601 visible_by_identifier
1602 .values()
1603 .map(HashMap::len)
1604 .sum::<usize>(),
1605 visible_by_identifier
1606 .values()
1607 .flat_map(HashMap::values)
1608 .map(Vec::len)
1609 .sum::<usize>(),
1610 identifier_started.elapsed().as_millis(),
1611 );
1612 }
1613 let mut cpp_template_metadata = HashMap::default();
1614 let metadata_started = Instant::now();
1615 let mut template_classes = 0usize;
1616 if report_stats {
1617 eprintln!(
1618 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
1619 );
1620 }
1621 for unit in visible_by_file
1622 .values()
1623 .flatten()
1624 .filter(|unit| unit.is_class())
1625 {
1626 template_classes += 1;
1627 if cpp_template_metadata.contains_key(unit) {
1628 continue;
1629 }
1630 if let Some(metadata) = cpp.template_metadata(unit) {
1631 cpp_template_metadata.insert(unit.clone(), metadata);
1632 }
1633 }
1634 if report_stats {
1635 eprintln!(
1636 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
1637 template_classes,
1638 cpp_template_metadata.len(),
1639 metadata_started.elapsed().as_millis(),
1640 );
1641 }
1642 let families_started = Instant::now();
1643 if report_stats {
1644 eprintln!(
1645 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
1646 );
1647 }
1648 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1649 for (unit, metadata) in &cpp_template_metadata {
1650 cpp_template_families
1651 .entry(metadata.primary_fq_name.clone())
1652 .or_default()
1653 .push(unit.clone());
1654 }
1655 for family in cpp_template_families.values_mut() {
1664 sort_lookup_units(family);
1665 }
1666 if report_stats {
1667 eprintln!(
1668 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
1669 cpp_template_families.len(),
1670 cpp_template_families.values().map(Vec::len).sum::<usize>(),
1671 families_started.elapsed().as_millis(),
1672 );
1673 eprintln!(
1674 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
1675 visible_by_file.len(),
1676 visible_by_file.values().map(HashSet::len).sum::<usize>(),
1677 finalize_started.elapsed().as_millis(),
1678 visibility_started.elapsed().as_millis(),
1679 );
1680 }
1681 Self {
1682 cpp,
1683 token,
1684 visible_by_file,
1685 visible_by_identifier,
1686 global_field_internal_linkage,
1687 visible_source_files_by_root,
1688 alias_cells: Mutex::new(HashMap::default()),
1689 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1690 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1691 project_using_index: OnceLock::new(),
1692 callable_reference_specs: Mutex::new(HashMap::default()),
1693 include_activation_cells: Mutex::new(HashMap::default()),
1694 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1695 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1696 #[cfg(any(test, feature = "test-support"))]
1697 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1698 #[cfg(any(test, feature = "test-support"))]
1699 conditional_include_projection_state_count: AtomicUsize::new(0),
1700 #[cfg(any(test, feature = "test-support"))]
1701 conditional_include_target_state_count: AtomicUsize::new(0),
1702 #[cfg(any(test, feature = "test-support"))]
1703 include_activation_build_count: AtomicUsize::new(0),
1704 #[cfg(any(test, feature = "test-support"))]
1705 using_donor_activation_count: AtomicUsize::new(0),
1706 #[cfg(any(test, feature = "test-support"))]
1707 using_namespace_lookup_count: AtomicUsize::new(0),
1708 #[cfg(any(test, feature = "test-support"))]
1709 using_name_candidate_inspection_count: AtomicUsize::new(0),
1710 #[cfg(any(test, feature = "test-support"))]
1711 callable_reference_spec_build_count: AtomicUsize::new(0),
1712 #[cfg(any(test, feature = "test-support"))]
1713 alias_source_parse_counts: Mutex::new(HashMap::default()),
1714 #[cfg(any(test, feature = "test-support"))]
1715 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1716 parser_alias_fallback_calls: AtomicUsize::new(0),
1717 parser_alias_fallback_files: AtomicUsize::new(0),
1718 parser_alias_source_parses: AtomicUsize::new(0),
1719 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1720 field_type_facts: Mutex::new(HashMap::default()),
1721 structured_alias_targets: Mutex::new(HashMap::default()),
1722 callable_comparables: Mutex::new(HashMap::default()),
1723 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1724 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1725 precise_parent_cache: Mutex::new(HashMap::default()),
1726 macro_event_cells: Mutex::new(HashMap::default()),
1727 macro_include_protection_cells: Mutex::new(HashMap::default()),
1728 macro_environment_cursors: Mutex::new(HashMap::default()),
1729 macro_replacements: Mutex::new(HashMap::default()),
1730 macro_local_binding_templates: Mutex::new(HashMap::default()),
1731 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1732 #[cfg(any(test, feature = "test-support"))]
1733 macro_replacement_parse_count: AtomicUsize::new(0),
1734 #[cfg(any(test, feature = "test-support"))]
1735 macro_event_application_count: AtomicUsize::new(0),
1736 #[cfg(any(test, feature = "test-support"))]
1737 macro_environment_copy_count: AtomicUsize::new(0),
1738 #[cfg(any(test, feature = "test-support"))]
1739 macro_environment_request_count: AtomicUsize::new(0),
1740 cpp_template_metadata,
1741 cpp_template_families,
1742 #[cfg(any(test, feature = "test-support"))]
1743 qualified_candidate_inspections: AtomicUsize::new(0),
1744 #[cfg(any(test, feature = "test-support"))]
1745 target_preserving_type_resolution_count: AtomicUsize::new(0),
1746 }
1747 }
1748
1749 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1750 if file == target.source() {
1751 return true;
1752 }
1753 if self.global_field_has_internal_linkage(target) {
1754 return self
1755 .visible_source_files_by_root
1756 .get(file)
1757 .is_some_and(|sources| sources.contains(target.source()));
1758 }
1759 self.visible_by_file
1760 .get(file)
1761 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1762 }
1763
1764 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1765 self.global_field_internal_linkage
1766 .get(unit)
1767 .copied()
1768 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1769 }
1770
1771 pub fn call_arity_evidence(
1772 &self,
1773 file: &ProjectFile,
1774 call: Node<'_>,
1775 source: &str,
1776 ) -> CallArityEvidence {
1777 let Some(arguments) = call
1778 .child_by_field_name("arguments")
1779 .or_else(|| call.child_by_field_name("parameters"))
1780 .or_else(|| call.child_by_field_name("value"))
1781 .or_else(|| first_named_child_of_kind(call, "argument_list"))
1782 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1783 else {
1784 return CallArityEvidence::Exact(0);
1785 };
1786 let recovered_c_keyword_arguments =
1787 recovered_c_keyword_argument_count(file, call, arguments, source);
1788 let arguments = argument_children(arguments).collect::<Vec<_>>();
1789 if arguments
1790 .iter()
1791 .all(|argument| !argument_shape_may_change_arity(*argument))
1792 {
1793 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1794 }
1795 let environment = self.macro_environment(file, call.start_byte());
1796 let mut stack = Vec::new();
1797 let mut total = recovered_c_keyword_arguments;
1798 for argument in arguments {
1799 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1800 return CallArityEvidence::Unknown;
1801 }
1802 let CallArityEvidence::Exact(spread) =
1803 self.argument_arity_evidence(argument, source, &environment, &mut stack)
1804 else {
1805 return CallArityEvidence::Unknown;
1806 };
1807 total += spread;
1808 }
1809 CallArityEvidence::Exact(total)
1810 }
1811
1812 fn argument_arity_evidence(
1813 &self,
1814 argument: Node<'_>,
1815 source: &str,
1816 environment: &MacroEnvironment,
1817 stack: &mut Vec<(ProjectFile, usize)>,
1818 ) -> CallArityEvidence {
1819 let (name, invocation_arguments, function_like) = match argument.kind() {
1820 "identifier" => (node_text(argument, source), None, false),
1821 "call_expression" => {
1822 let Some(function) = argument.child_by_field_name("function") else {
1823 return CallArityEvidence::Exact(1);
1824 };
1825 if function.kind() != "identifier" {
1826 return CallArityEvidence::Exact(1);
1827 }
1828 let Some(arguments) = argument.child_by_field_name("arguments") else {
1829 return CallArityEvidence::Exact(1);
1830 };
1831 (node_text(function, source), Some(arguments), true)
1832 }
1833 _ => return CallArityEvidence::Exact(1),
1834 };
1835 let Some(binding) = environment.binding(name) else {
1836 return if environment.unknown_names {
1837 CallArityEvidence::Unknown
1838 } else {
1839 CallArityEvidence::Exact(1)
1840 };
1841 };
1842 if !binding.is_exact() {
1843 return CallArityEvidence::Unknown;
1844 }
1845 match (&binding.definition, invocation_arguments, function_like) {
1846 (MacroDefinition::Object { replacement }, None, false) => self
1847 .replacement_arity_evidence(
1848 replacement,
1849 &[],
1850 &[],
1851 source,
1852 environment,
1853 stack,
1854 binding,
1855 ),
1856 (
1857 MacroDefinition::Function {
1858 parameters,
1859 replacement,
1860 },
1861 Some(arguments),
1862 true,
1863 ) => {
1864 let actuals = argument_children(arguments).collect::<Vec<_>>();
1865 if actuals.len() != parameters.len() {
1866 CallArityEvidence::Unknown
1867 } else {
1868 self.replacement_arity_evidence(
1869 replacement,
1870 parameters,
1871 &actuals,
1872 source,
1873 environment,
1874 stack,
1875 binding,
1876 )
1877 }
1878 }
1879 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1880 _ => CallArityEvidence::Unknown,
1881 }
1882 }
1883
1884 #[allow(clippy::too_many_arguments)]
1885 fn replacement_arity_evidence(
1886 &self,
1887 replacement: &str,
1888 parameters: &[String],
1889 actuals: &[Node<'_>],
1890 actual_source: &str,
1891 environment: &MacroEnvironment,
1892 stack: &mut Vec<(ProjectFile, usize)>,
1893 binding: &MacroBinding,
1894 ) -> CallArityEvidence {
1895 let identity = (binding.source.clone(), binding.declaration_byte);
1896 if stack.contains(&identity) || replacement.trim().is_empty() {
1897 return CallArityEvidence::Unknown;
1898 }
1899 stack.push(identity);
1900 let parsed = self.parsed_macro_replacement(binding, replacement);
1901 let evidence = (|| {
1902 let ParsedMacroReplacement::Parsed {
1903 source: sentinel,
1904 tree,
1905 } = parsed.as_ref()
1906 else {
1907 return None;
1908 };
1909 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1910 let arguments = call.child_by_field_name("arguments")?;
1911 let mut total = 0usize;
1912 for argument in argument_children(arguments) {
1913 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1914 return None;
1915 }
1916 if argument.kind() == "identifier"
1917 && let Some(parameter_index) = parameters
1918 .iter()
1919 .position(|parameter| parameter == node_text(argument, sentinel))
1920 {
1921 if !macro_expansion_shape_is_safe(
1922 actuals[parameter_index],
1923 actual_source,
1924 &[],
1925 environment,
1926 ) {
1927 return None;
1928 }
1929 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1930 actuals[parameter_index],
1931 actual_source,
1932 environment,
1933 stack,
1934 ) else {
1935 return None;
1936 };
1937 total += spread;
1938 continue;
1939 }
1940 let CallArityEvidence::Exact(spread) =
1941 self.argument_arity_evidence(argument, sentinel, environment, stack)
1942 else {
1943 return None;
1944 };
1945 total += spread;
1946 }
1947 Some(CallArityEvidence::Exact(total))
1948 })()
1949 .unwrap_or(CallArityEvidence::Unknown);
1950 stack.pop();
1951 evidence
1952 }
1953
1954 fn parsed_macro_replacement(
1955 &self,
1956 binding: &MacroBinding,
1957 replacement: &str,
1958 ) -> Arc<ParsedMacroReplacement> {
1959 let key = (binding.source.clone(), binding.declaration_byte);
1960 let mut cache = self
1961 .macro_replacements
1962 .lock()
1963 .expect("C++ macro replacement cache poisoned");
1964 if let Some(parsed) = cache.get(&key) {
1965 return Arc::clone(parsed);
1966 }
1967 #[cfg(any(test, feature = "test-support"))]
1968 self.macro_replacement_parse_count
1969 .fetch_add(1, Ordering::Relaxed);
1970 let source =
1971 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1972 let mut parser = Parser::new();
1973 let parsed = parser
1974 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1975 .ok()
1976 .and_then(|()| parser.parse(&source, None))
1977 .filter(|tree| !tree.root_node().has_error())
1978 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1979 ParsedMacroReplacement::Parsed { source, tree }
1980 });
1981 let parsed = Arc::new(parsed);
1982 cache.insert(key, Arc::clone(&parsed));
1983 parsed
1984 }
1985
1986 pub fn function_macro_local_binding<'tree>(
1996 &self,
1997 file: &ProjectFile,
1998 statement: Node<'tree>,
1999 source: &str,
2000 ) -> Option<MacroLocalBinding<'tree>> {
2001 if !is_c_source_file(file) {
2002 return None;
2003 }
2004 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2005 return Some(binding);
2006 }
2007 let call = match statement.kind() {
2008 "call_expression" => statement,
2009 "expression_statement" if statement.named_child_count() == 1 => {
2010 statement.named_child(0)?
2011 }
2012 _ => return None,
2013 };
2014 if call.kind() != "call_expression" {
2015 return None;
2016 }
2017 let function = call.child_by_field_name("function")?;
2018 if function.kind() != "identifier" {
2019 return None;
2020 }
2021 let arguments = call.child_by_field_name("arguments")?;
2022 let actuals = argument_children(arguments).collect::<Vec<_>>();
2023 let environment = self.macro_environment(file, call.start_byte());
2024 let function_name = node_text(function, source);
2025 let binding = environment.binding(function_name)?;
2026 let MacroDefinition::Function {
2027 parameters,
2028 replacement,
2029 } = &binding.definition
2030 else {
2031 return None;
2032 };
2033 if actuals.len() != parameters.len() {
2034 return None;
2035 }
2036 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2037 let (type_name, type_node) = match &template.declared_type {
2038 MacroLocalBindingTypeTemplate::Parameter(index) => {
2039 let actual = *actuals.get(*index)?;
2040 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2041 return None;
2042 }
2043 (node_text(actual, source).trim().to_string(), Some(actual))
2044 }
2045 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2046 };
2047 if type_name.is_empty() {
2048 return None;
2049 }
2050 Some(MacroLocalBinding {
2051 name: template.name.clone(),
2052 type_name,
2053 type_node,
2054 pointer_depth: template.pointer_depth,
2055 })
2056 }
2057
2058 fn macro_local_binding_template(
2059 &self,
2060 binding: &MacroBinding,
2061 parameters: &[String],
2062 replacement: &str,
2063 ) -> Option<Arc<MacroLocalBindingTemplate>> {
2064 let key = (binding.source.clone(), binding.declaration_byte);
2065 let mut cache = self
2066 .macro_local_binding_templates
2067 .lock()
2068 .expect("C++ macro local-binding cache poisoned");
2069 if let Some(template) = cache.get(&key) {
2070 return template.clone();
2071 }
2072 let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
2073 let template = (|| {
2074 let mut parser = Parser::new();
2075 parser
2076 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2077 .ok()?;
2078 let tree = parser.parse(&sentinel, None)?;
2079 if tree.root_node().has_error() {
2080 return None;
2081 }
2082 let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
2083 let body = function.child_by_field_name("body")?;
2084 if body.named_child_count() != 1 {
2085 return None;
2086 }
2087 let declaration = body.named_child(0)?;
2088 if declaration.kind() != "declaration" {
2089 return None;
2090 }
2091 let type_node = declaration
2092 .child_by_field_name("type")
2093 .or_else(|| first_type_child(declaration))?;
2094 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2095 let mut cursor = declaration.walk();
2096 declaration.named_children(&mut cursor).find_map(|child| {
2097 if child.kind() == "init_declarator" {
2098 child.child_by_field_name("declarator")
2099 } else {
2100 is_declarator_node(child).then_some(child)
2101 }
2102 })
2103 })?;
2104 let name = extract_variable_name(declarator, &sentinel)?;
2105 let pointer_depth =
2106 declared_name_indirection(declaration, type_node, &name, &sentinel)?;
2107 let type_text = node_text(type_node, &sentinel).trim();
2108 let declared_type = parameters
2109 .iter()
2110 .position(|parameter| parameter == type_text)
2111 .map(MacroLocalBindingTypeTemplate::Parameter)
2112 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2113 Some(Arc::new(MacroLocalBindingTemplate {
2114 name,
2115 declared_type,
2116 pointer_depth,
2117 }))
2118 })();
2119 cache.insert(key, template.clone());
2120 template
2121 }
2122
2123 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
2124 let replacement = node
2125 .child_by_field_name("value")
2126 .map(|value| node_text(value, source).to_string())
2127 .unwrap_or_default();
2128 if node.kind() == "preproc_def" {
2129 return MacroDefinition::Object { replacement };
2130 }
2131 let Some(parameters) = node.child_by_field_name("parameters") else {
2132 return MacroDefinition::Unsupported;
2133 };
2134 if (0..parameters.child_count()).any(|index| {
2135 parameters
2136 .child(index)
2137 .is_some_and(|child| child.kind() == "...")
2138 }) {
2139 return MacroDefinition::Unsupported;
2140 }
2141 let parameters = (0..parameters.named_child_count())
2142 .filter_map(|index| parameters.named_child(index))
2143 .map(|parameter| node_text(parameter, source).to_string())
2144 .collect();
2145 MacroDefinition::Function {
2146 parameters,
2147 replacement,
2148 }
2149 }
2150
2151 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
2152 self.macro_event_cells
2153 .lock()
2154 .expect("C++ macro event cache poisoned")
2155 .entry(file.clone())
2156 .or_default()
2157 .clone()
2158 }
2159
2160 pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
2161 let key = (file.clone(), std::thread::current().id());
2162 self.macro_environment_cursors
2163 .lock()
2164 .expect("C++ macro environment cursor cache poisoned")
2165 .entry(key)
2166 .or_default()
2167 .clone()
2168 }
2169
2170 pub fn macro_environment(
2171 &self,
2172 file: &ProjectFile,
2173 before_byte: usize,
2174 ) -> Arc<MacroEnvironment> {
2175 #[cfg(any(test, feature = "test-support"))]
2176 self.macro_environment_request_count
2177 .fetch_add(1, Ordering::Relaxed);
2178 let cell = self.macro_event_cell(file);
2179 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2180 let frontier = events.partition_point(|event| event.byte() < before_byte);
2181 let cursor_cell = self.macro_environment_cursor_cell(file);
2182 let mut cursor = cursor_cell
2183 .lock()
2184 .expect("C++ macro environment cursor poisoned");
2185 if frontier < cursor.frontier {
2186 *cursor = MacroEnvironmentCursor::default();
2187 }
2188 if cursor.frontier == 0 {
2193 let proven = self.compile_proven_guards(file);
2194 if !proven.is_empty() && cursor.environment.build_proven_defines.len() != proven.len() {
2195 Arc::make_mut(&mut cursor.environment).build_proven_defines = proven
2196 .iter()
2197 .filter_map(|guard| match guard {
2198 PreprocessorGuard::Defined(name) => Some(name.clone()),
2199 _ => None,
2200 })
2201 .collect();
2202 }
2203 }
2204 if frontier > cursor.frontier {
2205 #[cfg(any(test, feature = "test-support"))]
2206 if Arc::strong_count(&cursor.environment) > 1 {
2207 self.macro_environment_copy_count
2208 .fetch_add(1, Ordering::Relaxed);
2209 }
2210 let start = cursor.frontier;
2211 let environment = Arc::make_mut(&mut cursor.environment);
2212 let mut include_stack = HashSet::from_iter([file.clone()]);
2213 for event in &events[start..frontier] {
2214 self.apply_macro_event(file, event, environment, &mut include_stack);
2215 }
2216 cursor.frontier = frontier;
2217 }
2218 Arc::clone(&cursor.environment)
2219 }
2220
2221 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2230 self.macro_environment(file, before_byte)
2231 .binding(name)
2232 .is_some()
2233 }
2234
2235 pub fn macro_name_may_be_bound_at(
2236 &self,
2237 file: &ProjectFile,
2238 name: &str,
2239 before_byte: usize,
2240 ) -> bool {
2241 self.macro_environment(file, before_byte).may_bind(name)
2242 }
2243
2244 pub fn macro_binding_matches_target_at(
2248 &self,
2249 analyzer: &CppGraphSource<'_>,
2250 file: &ProjectFile,
2251 name: &str,
2252 before_byte: usize,
2253 target: &CodeUnit,
2254 ) -> bool {
2255 let environment = self.macro_environment(file, before_byte);
2256 let Some(binding) = environment.binding(name) else {
2257 return false;
2258 };
2259 if binding.definition == MacroDefinition::Unsupported {
2260 return false;
2261 }
2262 if binding.source != *target.source() {
2266 return false;
2267 }
2268 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2269 return false;
2270 };
2271 analyzer.ranges(target).iter().any(|range| {
2272 let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
2273 return false;
2274 };
2275 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2276 let Some(parent) = node.parent() else {
2277 return false;
2278 };
2279 node = parent;
2280 }
2281 node.start_byte() == binding.declaration_byte
2282 })
2283 }
2284
2285 pub fn resolve_ordinary_macro_reference(
2292 &self,
2293 analyzer: &CppGraphSource<'_>,
2294 file: &ProjectFile,
2295 node: Node<'_>,
2296 source: &str,
2297 ) -> OrdinaryMacroReferenceResolution {
2298 if !is_ordinary_macro_reference_node(node) {
2299 return OrdinaryMacroReferenceResolution::Missing;
2300 }
2301 let name = node_text(node, source);
2302 if name.is_empty() {
2303 return OrdinaryMacroReferenceResolution::Missing;
2304 }
2305 let visible = self
2306 .visible_identifier_candidates(file, name)
2307 .filter(|candidate| candidate.is_macro())
2308 .cloned()
2309 .collect::<Vec<_>>();
2310 let mut exact = Vec::new();
2311 for candidate in &visible {
2312 if self.macro_binding_matches_target_at(
2313 analyzer,
2314 file,
2315 name,
2316 node.start_byte(),
2317 candidate,
2318 ) && !exact
2319 .iter()
2320 .any(|existing| same_visible_symbol(existing, candidate))
2321 {
2322 exact.push(candidate.clone());
2323 }
2324 }
2325 match exact.len() {
2326 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2327 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2328 0 if !visible.is_empty()
2329 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2330 {
2331 OrdinaryMacroReferenceResolution::Ambiguous
2332 }
2333 0 => OrdinaryMacroReferenceResolution::Missing,
2334 }
2335 }
2336
2337 pub fn recovered_c_reference_ranges(
2345 &self,
2346 file: &ProjectFile,
2347 root: Node<'_>,
2348 source: &str,
2349 limit: usize,
2350 ) -> RecoveredCReferenceRanges {
2351 if !is_c_source_file(file) {
2352 return RecoveredCReferenceRanges::Complete(Vec::new());
2353 }
2354 let mut ranges = Vec::new();
2355 let mut seen = HashSet::default();
2356 let mut stack = vec![(root, root.is_error())];
2357 while let Some((node, inside_error)) = stack.pop() {
2358 let inside_error = inside_error || node.is_error();
2359 if inside_error
2360 && recovered_c_reference_node(self, file, node, source)
2361 && seen.insert((node.start_byte(), node.end_byte()))
2362 {
2363 if ranges.len() == limit {
2364 return RecoveredCReferenceRanges::LimitExceeded;
2365 }
2366 ranges.push(Range {
2367 start_byte: node.start_byte(),
2368 end_byte: node.end_byte(),
2369 start_line: node.start_position().row,
2370 end_line: node.end_position().row,
2371 });
2372 }
2373 let mut cursor = node.walk();
2374 for child in node.named_children(&mut cursor) {
2375 stack.push((child, inside_error));
2376 }
2377 }
2378 ranges.sort_unstable();
2379 RecoveredCReferenceRanges::Complete(ranges)
2380 }
2381
2382 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2388 self.visible_identifier_candidates(file, target.identifier())
2389 .filter(|candidate| candidate.is_macro())
2390 .any(|candidate| {
2391 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2392 })
2393 }
2394
2395 pub fn object_macro_replacement_at(
2396 &self,
2397 file: &ProjectFile,
2398 name: &str,
2399 before_byte: usize,
2400 ) -> Option<String> {
2401 let environment = self.macro_environment(file, before_byte);
2402 let binding = environment.binding(name)?;
2403 if !binding.exact {
2404 return None;
2405 }
2406 match &binding.definition {
2407 MacroDefinition::Object { replacement } => Some(replacement.clone()),
2408 MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2409 }
2410 }
2411
2412 fn apply_macro_events(
2413 &self,
2414 file: &ProjectFile,
2415 before_byte: Option<usize>,
2416 environment: &mut MacroEnvironment,
2417 include_stack: &mut HashSet<ProjectFile>,
2418 ) {
2419 if !include_stack.insert(file.clone()) {
2420 return;
2421 }
2422 if self.cpp.prepared_syntax(self.token, file).is_none() {
2423 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2424 include_stack.remove(file);
2425 return;
2426 }
2427 match self.macro_include_protection(file) {
2428 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2429 Some(binding) if binding.is_exact() => {
2430 include_stack.remove(file);
2431 return;
2432 }
2433 Some(_) | None if environment.unknown_names => {
2434 let mut ambiguous_seen = HashSet::default();
2435 self.mark_macro_events_ambiguous(
2436 file,
2437 environment,
2438 &mut ambiguous_seen,
2439 file,
2440 before_byte.unwrap_or_default(),
2441 );
2442 include_stack.remove(file);
2443 return;
2444 }
2445 Some(_) => {
2446 let mut ambiguous_seen = HashSet::default();
2447 self.mark_macro_events_ambiguous(
2448 file,
2449 environment,
2450 &mut ambiguous_seen,
2451 file,
2452 before_byte.unwrap_or_default(),
2453 );
2454 include_stack.remove(file);
2455 return;
2456 }
2457 None => {}
2458 },
2459 MacroIncludeProtection::PragmaOnce => {
2460 if !environment.applied_pragma_once_files.insert(file.clone()) {
2461 include_stack.remove(file);
2462 return;
2463 }
2464 if environment.maybe_applied_pragma_once_files.remove(file) {
2465 let mut ambiguous_seen = HashSet::default();
2470 environment.applied_pragma_once_files.remove(file);
2471 self.mark_macro_events_ambiguous(
2472 file,
2473 environment,
2474 &mut ambiguous_seen,
2475 file,
2476 before_byte.unwrap_or_default(),
2477 );
2478 environment.maybe_applied_pragma_once_files.remove(file);
2479 environment.applied_pragma_once_files.insert(file.clone());
2480 include_stack.remove(file);
2481 return;
2482 }
2483 }
2484 MacroIncludeProtection::None => {}
2485 }
2486 let cell = self.macro_event_cell(file);
2487 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2488 for event in events {
2489 if before_byte.is_some_and(|limit| event.byte() >= limit) {
2490 break;
2491 }
2492 self.apply_macro_event(file, event, environment, include_stack);
2493 }
2494 include_stack.remove(file);
2495 }
2496
2497 fn apply_macro_event(
2498 &self,
2499 file: &ProjectFile,
2500 event: &MacroEvent,
2501 environment: &mut MacroEnvironment,
2502 include_stack: &mut HashSet<ProjectFile>,
2503 ) {
2504 #[cfg(any(test, feature = "test-support"))]
2505 self.macro_event_application_count
2506 .fetch_add(1, Ordering::Relaxed);
2507 match event {
2508 MacroEvent::Define {
2509 name,
2510 binding,
2511 conditional,
2512 byte,
2513 } => {
2514 match conditional
2515 .then(|| self.macro_event_condition_value(file, *byte, environment))
2516 .unwrap_or(Some(true))
2517 {
2518 Some(true) => environment.insert(name.clone(), binding.clone()),
2519 Some(false) => {}
2520 None => Self::merge_conditional_macro_definition(
2521 environment,
2522 name,
2523 binding,
2524 file,
2525 *byte,
2526 ),
2527 }
2528 }
2529 MacroEvent::Undef {
2530 name,
2531 conditional,
2532 byte,
2533 } => {
2534 match conditional
2535 .then(|| self.macro_event_condition_value(file, *byte, environment))
2536 .unwrap_or(Some(true))
2537 {
2538 Some(true) => environment.remove(name),
2539 Some(false) => {}
2540 None => {
2541 if environment.binding(name).is_some() {
2542 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2543 }
2544 }
2545 }
2546 }
2547 MacroEvent::Include {
2548 targets,
2549 conditional,
2550 byte,
2551 } => {
2552 let condition = conditional
2553 .then(|| self.macro_event_condition_value(file, *byte, environment))
2554 .unwrap_or(Some(true));
2555 if condition == Some(false) {
2556 return;
2557 }
2558 if targets.is_empty() {
2559 environment.mark_unknown_names(file, *byte);
2560 return;
2561 }
2562 if condition.is_none() || targets.len() > 1 {
2563 let mut ambiguous_seen = HashSet::default();
2564 for target in targets {
2565 self.mark_macro_events_ambiguous(
2566 target,
2567 environment,
2568 &mut ambiguous_seen,
2569 file,
2570 *byte,
2571 );
2572 }
2573 } else if let Some(target) = targets.first() {
2574 self.apply_macro_events(target, None, environment, include_stack);
2575 }
2576 }
2577 MacroEvent::Invalidate { byte } => {
2578 for binding in environment.bindings.values_mut() {
2579 *binding = MacroBinding::uncertain_from(binding, file, *byte);
2580 }
2581 }
2582 }
2583 }
2584
2585 fn macro_event_condition_value(
2591 &self,
2592 file: &ProjectFile,
2593 event_byte: usize,
2594 environment: &MacroEnvironment,
2595 ) -> Option<bool> {
2596 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2597 let source = prepared.source();
2598 let root = prepared.tree().root_node();
2599 let descendant = root.descendant_for_byte_range(
2600 event_byte,
2601 event_byte.saturating_add(1).min(source.len()),
2602 )?;
2603 let mut unknown = false;
2604 let mut current = descendant.parent();
2605 while let Some(conditional) = current {
2606 if matches!(
2607 conditional.kind(),
2608 "preproc_if" | "preproc_ifdef" | "preproc_elif"
2609 ) && !is_file_covering_include_guard(conditional, source)
2610 && preprocessor_conditional_contains_descendant(conditional, descendant)
2611 {
2612 let mut value = match conditional.kind() {
2613 "preproc_ifdef" => {
2614 let name = conditional.child_by_field_name("name")?;
2615 let defined =
2616 self.macro_name_defined_value(environment, node_text(name, source));
2617 match conditional.child(0)?.kind() {
2618 "#ifdef" => defined,
2619 "#ifndef" => defined.map(|defined| !defined),
2620 _ => None,
2621 }
2622 }
2623 "preproc_if" | "preproc_elif" => conditional
2624 .child_by_field_name("condition")
2625 .and_then(|condition| {
2626 self.preprocessor_integer_value(
2627 condition,
2628 source,
2629 environment,
2630 &mut Vec::new(),
2631 0,
2632 )
2633 })
2634 .map(|value| value != 0),
2635 _ => unreachable!(),
2636 };
2637 if conditional
2638 .child_by_field_name("alternative")
2639 .is_some_and(|alternative| {
2640 alternative.start_byte() <= descendant.start_byte()
2641 && descendant.end_byte() <= alternative.end_byte()
2642 })
2643 {
2644 value = value.map(|value| !value);
2645 }
2646 match value {
2647 Some(true) => {}
2648 Some(false) => return Some(false),
2649 None => unknown = true,
2650 }
2651 }
2652 current = conditional.parent();
2653 }
2654 (!unknown).then_some(true)
2655 }
2656
2657 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
2658 if environment.known_undefined_names.contains(name) {
2659 return Some(false);
2660 }
2661 if let Some(binding) = environment.binding(name) {
2662 return binding.is_exact().then_some(true);
2663 }
2664 environment
2665 .build_proven_defines
2666 .contains(name)
2667 .then_some(true)
2668 }
2669
2670 fn preprocessor_integer_value(
2671 &self,
2672 expression: Node<'_>,
2673 source: &str,
2674 environment: &MacroEnvironment,
2675 expansion_stack: &mut Vec<(ProjectFile, usize)>,
2676 depth: usize,
2677 ) -> Option<i128> {
2678 if depth >= 64 {
2681 return None;
2682 }
2683 match expression.kind() {
2684 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
2685 "identifier" | "type_identifier" => {
2686 let binding = environment.binding(node_text(expression, source))?;
2687 if !binding.is_exact() {
2688 return None;
2689 }
2690 let MacroDefinition::Object { replacement } = &binding.definition else {
2691 return None;
2692 };
2693 let identity = (binding.source.clone(), binding.declaration_byte);
2694 if expansion_stack.contains(&identity) {
2695 return None;
2696 }
2697 expansion_stack.push(identity);
2698 let parsed = self.parsed_macro_replacement(binding, replacement);
2699 let value = match parsed.as_ref() {
2700 ParsedMacroReplacement::Parsed {
2701 source: replacement_source,
2702 tree,
2703 } => first_descendant_of_kind(tree.root_node(), "call_expression")
2704 .and_then(|call| call.child_by_field_name("arguments"))
2705 .and_then(|arguments| argument_children(arguments).next())
2706 .and_then(|argument| {
2707 self.preprocessor_integer_value(
2708 argument,
2709 replacement_source,
2710 environment,
2711 expansion_stack,
2712 depth + 1,
2713 )
2714 }),
2715 ParsedMacroReplacement::Unsupported => None,
2716 };
2717 expansion_stack.pop();
2718 value
2719 }
2720 "preproc_defined" => {
2721 let mut cursor = expression.walk();
2722 let name = expression
2723 .named_children(&mut cursor)
2724 .find(|child| child.kind() == "identifier")?;
2725 self.macro_name_defined_value(environment, node_text(name, source))
2726 .map(i128::from)
2727 }
2728 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
2729 self.preprocessor_integer_value(
2730 child,
2731 source,
2732 environment,
2733 expansion_stack,
2734 depth + 1,
2735 )
2736 }),
2737 "unary_expression" => {
2738 let operator = expression.child_by_field_name("operator")?.kind();
2739 let argument = expression.child_by_field_name("argument")?;
2740 let value = self.preprocessor_integer_value(
2741 argument,
2742 source,
2743 environment,
2744 expansion_stack,
2745 depth + 1,
2746 )?;
2747 match operator {
2748 "+" => Some(value),
2749 "-" => value.checked_neg(),
2750 "!" => Some(i128::from(value == 0)),
2751 "~" => Some(!value),
2752 _ => None,
2753 }
2754 }
2755 "binary_expression" => {
2756 let left = self.preprocessor_integer_value(
2757 expression.child_by_field_name("left")?,
2758 source,
2759 environment,
2760 expansion_stack,
2761 depth + 1,
2762 )?;
2763 let right = self.preprocessor_integer_value(
2764 expression.child_by_field_name("right")?,
2765 source,
2766 environment,
2767 expansion_stack,
2768 depth + 1,
2769 )?;
2770 match expression.child_by_field_name("operator")?.kind() {
2771 "+" => left.checked_add(right),
2772 "-" => left.checked_sub(right),
2773 "*" => left.checked_mul(right),
2774 "/" => left.checked_div(right),
2775 "%" => left.checked_rem(right),
2776 "<<" => u32::try_from(right)
2777 .ok()
2778 .and_then(|shift| left.checked_shl(shift)),
2779 ">>" => u32::try_from(right)
2780 .ok()
2781 .and_then(|shift| left.checked_shr(shift)),
2782 "<" => Some(i128::from(left < right)),
2783 "<=" => Some(i128::from(left <= right)),
2784 ">" => Some(i128::from(left > right)),
2785 ">=" => Some(i128::from(left >= right)),
2786 "==" => Some(i128::from(left == right)),
2787 "!=" => Some(i128::from(left != right)),
2788 "&" => Some(left & right),
2789 "|" => Some(left | right),
2790 "^" => Some(left ^ right),
2791 "&&" => Some(i128::from(left != 0 && right != 0)),
2792 "||" => Some(i128::from(left != 0 || right != 0)),
2793 _ => None,
2794 }
2795 }
2796 _ => None,
2797 }
2798 }
2799
2800 fn mark_macro_events_ambiguous(
2801 &self,
2802 file: &ProjectFile,
2803 environment: &mut MacroEnvironment,
2804 include_stack: &mut HashSet<ProjectFile>,
2805 conditional_file: &ProjectFile,
2806 conditional_byte: usize,
2807 ) {
2808 if !include_stack.insert(file.clone()) {
2809 return;
2810 }
2811 if self.cpp.prepared_syntax(self.token, file).is_none() {
2812 environment.mark_unknown_names(conditional_file, conditional_byte);
2813 return;
2814 }
2815 match self.macro_include_protection(file) {
2816 MacroIncludeProtection::MacroGuard(guard) => {
2817 if environment
2818 .binding(&guard)
2819 .is_some_and(MacroBinding::is_exact)
2820 {
2821 return;
2822 }
2823 }
2824 MacroIncludeProtection::PragmaOnce => {
2825 if environment.applied_pragma_once_files.contains(file) {
2826 return;
2827 }
2828 environment
2829 .maybe_applied_pragma_once_files
2830 .insert(file.clone());
2831 }
2832 MacroIncludeProtection::None => {}
2833 }
2834 let cell = self.macro_event_cell(file);
2835 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2836 for event in events {
2837 #[cfg(any(test, feature = "test-support"))]
2838 self.macro_event_application_count
2839 .fetch_add(1, Ordering::Relaxed);
2840 match event {
2841 MacroEvent::Define { name, binding, .. } => {
2842 Self::merge_conditional_macro_definition(
2843 environment,
2844 name,
2845 binding,
2846 conditional_file,
2847 conditional_byte,
2848 );
2849 }
2850 MacroEvent::Undef { name, .. } => {
2851 if environment.binding(name).is_some() {
2852 environment.insert(
2853 name.clone(),
2854 MacroBinding::ambiguous(conditional_file, conditional_byte),
2855 );
2856 } else {
2857 environment.remove_known_undefined(name);
2858 }
2859 }
2860 MacroEvent::Include { targets, .. } => {
2861 if targets.is_empty() {
2862 environment.mark_unknown_names(conditional_file, conditional_byte);
2863 continue;
2864 }
2865 for target in targets {
2866 self.mark_macro_events_ambiguous(
2867 target,
2868 environment,
2869 include_stack,
2870 conditional_file,
2871 conditional_byte,
2872 );
2873 }
2874 }
2875 MacroEvent::Invalidate { .. } => {
2876 for binding in environment.bindings.values_mut() {
2877 *binding = MacroBinding::uncertain_from(
2878 binding,
2879 conditional_file,
2880 conditional_byte,
2881 );
2882 }
2883 }
2884 }
2885 }
2886 }
2887
2888 fn merge_conditional_macro_definition(
2889 environment: &mut MacroEnvironment,
2890 name: &str,
2891 possible_binding: &MacroBinding,
2892 conditional_file: &ProjectFile,
2893 conditional_byte: usize,
2894 ) {
2895 if environment.binding(name).is_some_and(|current| {
2900 current.definition != MacroDefinition::Unsupported
2901 && current.definition == possible_binding.definition
2902 }) {
2903 return;
2904 }
2905 environment.insert(
2906 name.to_string(),
2907 MacroBinding::ambiguous(conditional_file, conditional_byte),
2908 );
2909 }
2910
2911 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2912 let cell = self
2913 .macro_include_protection_cells
2914 .lock()
2915 .expect("C++ include protection cache poisoned")
2916 .entry(file.clone())
2917 .or_default()
2918 .clone();
2919 cell.get_or_init(|| {
2920 self.cpp.prepared_syntax(self.token, file).map_or(
2921 MacroIncludeProtection::None,
2922 |prepared| {
2923 top_level_macro_include_protection(
2924 prepared.tree().root_node(),
2925 prepared.source(),
2926 )
2927 },
2928 )
2929 })
2930 .clone()
2931 }
2932
2933 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2934 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2935 return Vec::new();
2936 };
2937 let source = prepared.source();
2938 let mut events = Vec::new();
2939 let mut stack = vec![prepared.tree().root_node()];
2940 while let Some(node) = stack.pop() {
2941 let conditional = has_preprocessor_conditional_ancestor(node, source);
2942 match node.kind() {
2943 "preproc_def" | "preproc_function_def" => {
2944 let Some(name) = node.child_by_field_name("name") else {
2945 continue;
2946 };
2947 let name = node_text(name, source).to_string();
2948 events.push(MacroEvent::Define {
2949 name,
2950 binding: MacroBinding {
2951 source: file.clone(),
2952 declaration_byte: node.start_byte(),
2953 definition: Self::decode_macro_definition(node, source),
2954 exact: true,
2955 },
2956 byte: node.start_byte(),
2957 conditional,
2958 });
2959 continue;
2960 }
2961 "preproc_include" => {
2962 let Some(path) = node.child_by_field_name("path") else {
2963 events.push(MacroEvent::Include {
2964 targets: Vec::new(),
2965 byte: node.start_byte(),
2966 conditional,
2967 });
2968 continue;
2969 };
2970 let targets =
2971 structured_include_path(path, source).map_or_else(Vec::new, |path| {
2972 resolve_include_targets_with_index(
2973 file,
2974 path,
2975 self.cpp.include_target_index(),
2976 )
2977 });
2978 if targets.is_empty() && path.kind() == "system_lib_string" {
2983 continue;
2984 }
2985 events.push(MacroEvent::Include {
2986 targets,
2987 byte: node.start_byte(),
2988 conditional,
2989 });
2990 continue;
2991 }
2992 "preproc_call" => {
2993 let Some(directive) = node.child_by_field_name("directive") else {
2994 continue;
2995 };
2996 if node_text(directive, source) != "#undef" {
2997 continue;
2998 }
2999 let name = node
3000 .child_by_field_name("argument")
3001 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
3002 if let Some(name) = name {
3003 events.push(MacroEvent::Undef {
3004 name,
3005 byte: node.start_byte(),
3006 conditional,
3007 });
3008 } else {
3009 events.push(MacroEvent::Invalidate {
3010 byte: node.start_byte(),
3011 });
3012 }
3013 continue;
3014 }
3015 _ => {}
3016 }
3017 for index in (0..node.named_child_count()).rev() {
3018 if let Some(child) = node.named_child(index) {
3019 stack.push(child);
3020 }
3021 }
3022 }
3023 events.sort_by_key(MacroEvent::byte);
3024 events
3025 }
3026
3027 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
3028 self.ordinary_type_import_cells
3029 .lock()
3030 .expect("C++ ordinary type import cache poisoned")
3031 .entry(file.clone())
3032 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
3033 .clone()
3034 }
3035
3036 pub fn project_using_index(
3037 &self,
3038 build: impl FnOnce() -> ProjectUsingIndex,
3039 ) -> &ProjectUsingIndex {
3040 self.project_using_index.get_or_init(build)
3041 }
3042
3043 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
3044 let mut files = self
3045 .visible_source_files_by_root
3046 .values()
3047 .flatten()
3048 .cloned()
3049 .collect::<HashSet<_>>()
3050 .into_iter()
3051 .collect::<Vec<_>>();
3052 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
3053 files
3054 }
3055
3056 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
3057 self.visible_source_files_by_root
3058 .get(root)
3059 .is_some_and(|files| files.contains(source))
3060 }
3061
3062 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
3063 let cached = self
3064 .visible_parser_alias_name_sets
3065 .read()
3066 .expect("visible parser alias-name cache poisoned")
3067 .get(file)
3068 .cloned();
3069 let cell = if let Some(cached) = cached {
3070 cached
3071 } else {
3072 let mut cells = self
3073 .visible_parser_alias_name_sets
3074 .write()
3075 .expect("visible parser alias-name cache poisoned");
3076 Arc::clone(
3077 cells
3078 .entry(file.clone())
3079 .or_insert_with(|| Arc::new(OnceLock::new())),
3080 )
3081 };
3082 cell.get_or_init(|| {
3083 #[cfg(any(test, feature = "test-support"))]
3084 self.visible_parser_alias_name_set_build_count
3085 .fetch_add(1, Ordering::Relaxed);
3086 let mut names = HashSet::default();
3087 let visible_files = self
3088 .visible_source_files_by_root
3089 .get(file)
3090 .cloned()
3091 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
3092 for visible_file in visible_files {
3093 let aliases = {
3094 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
3095 Arc::clone(
3096 cells
3097 .entry(visible_file.clone())
3098 .or_insert_with(|| Arc::new(OnceLock::new())),
3099 )
3100 };
3101 for alias in aliases
3102 .get_or_init(|| {
3103 self.parser_alias_source_parses
3104 .fetch_add(1, Ordering::Relaxed);
3105 #[cfg(any(test, feature = "test-support"))]
3106 {
3107 *self
3108 .alias_source_parse_counts
3109 .lock()
3110 .expect("alias source parse count lock")
3111 .entry(visible_file.clone())
3112 .or_default() += 1;
3113 }
3114 aliases_from_prepared_source(self.cpp, self.token, &visible_file)
3115 .into_boxed_slice()
3116 })
3117 .iter()
3118 {
3119 names.insert(alias.name.clone());
3120 }
3121 }
3122 names
3123 })
3124 .contains(name)
3125 }
3126
3127 pub fn parser_alias_name_may_resolve_to_target(
3128 &self,
3129 file: &ProjectFile,
3130 alias_name: &str,
3131 target: &CodeUnit,
3132 ) -> bool {
3133 let started = std::time::Instant::now();
3134 self.parser_alias_fallback_calls
3135 .fetch_add(1, Ordering::Relaxed);
3136 let mut files = 0usize;
3137 let matched = match self.visible_source_files_by_root.get(file) {
3138 None => {
3139 files = 1;
3140 self.file_alias_matches(self.cpp, file, alias_name, target)
3141 }
3142 Some(visible_files) => visible_files.iter().any(|visible_file| {
3143 files += 1;
3144 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
3145 }),
3146 };
3147 self.parser_alias_fallback_files
3148 .fetch_add(files, Ordering::Relaxed);
3149 self.parser_alias_fallback_elapsed_micros.fetch_add(
3150 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
3151 Ordering::Relaxed,
3152 );
3153 matched
3154 }
3155
3156 fn callable_arities_for_target(
3157 &self,
3158 analyzer: &CppGraphSource<'_>,
3159 cpp: &dyn CppSource,
3160 file: &ProjectFile,
3161 prepared: &PreparedSyntaxTree,
3162 spec: &TargetSpec,
3163 ) -> Vec<ActivatedCallableArity> {
3164 let Some(signature) = spec.target.signature() else {
3165 return Vec::new();
3166 };
3167 let Some(candidates) = self
3168 .visible_by_identifier
3169 .get(file)
3170 .and_then(|by_name| by_name.get(&spec.member_name))
3171 else {
3172 return Vec::new();
3173 };
3174 let differing_candidates = candidates
3175 .iter()
3176 .filter(|candidate| {
3177 candidate.is_function()
3178 && candidate.fq_name() == spec.target.fq_name()
3179 && candidate.signature() == Some(signature)
3180 })
3181 .filter_map(|candidate| {
3182 analyzer
3183 .signature_metadata(candidate)
3184 .into_iter()
3185 .find_map(|metadata| metadata.callable_arity())
3186 .filter(|arity| Some(*arity) != spec.callable_arity)
3187 .map(|arity| (candidate, arity))
3188 })
3189 .collect::<Vec<_>>();
3190 if differing_candidates.is_empty() {
3191 return Vec::new();
3192 }
3193 let mut arities = Vec::with_capacity(differing_candidates.len());
3194 let reference = CallableReferenceContext {
3197 file,
3198 position: None,
3199 };
3200 for (candidate, candidate_arity) in differing_candidates {
3201 let declaration_activation = if candidate.source() == file {
3202 callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
3203 } else {
3204 cpp.prepared_syntax(self.token, candidate.source())
3205 .and_then(|syntax| {
3206 callable_declaration_activation_in_file(
3207 analyzer,
3208 syntax.as_ref(),
3209 candidate,
3210 &reference,
3211 )
3212 })
3213 };
3214 let Some(declaration_activation) = declaration_activation else {
3215 continue;
3216 };
3217 let activation_byte = if candidate.source() == file {
3218 Some(declaration_activation)
3219 } else {
3220 self.include_activation_for_source(cpp, file, prepared, candidate.source())
3221 };
3222 if let Some(activation_byte) = activation_byte {
3223 arities.push(ActivatedCallableArity {
3224 activation_byte,
3225 arity: candidate_arity,
3226 });
3227 }
3228 }
3229 arities
3230 }
3231
3232 fn callable_parameter_macro_arity(
3233 &self,
3234 target: &CodeUnit,
3235 signature: Option<&str>,
3236 ) -> Option<CallableArity> {
3237 let parameter_types = cpp_signature_param_types(signature?)?;
3238 let [macro_name] = parameter_types.as_slice() else {
3239 return None;
3240 };
3241 if macro_name.is_empty()
3242 || !macro_name
3243 .chars()
3244 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
3245 {
3246 return None;
3247 }
3248 let cache_key = (target.source().clone(), macro_name.clone());
3249 if let Some(cached) = self
3250 .callable_parameter_macro_arities
3251 .lock()
3252 .expect("C++ callable parameter-macro arity cache poisoned")
3253 .get(&cache_key)
3254 .copied()
3255 {
3256 return cached;
3257 }
3258 let mut visible_files = HashSet::default();
3259 collect_include_closure(
3260 &self.cpp_source(),
3261 self.cpp.include_target_index(),
3262 target.source(),
3263 &mut visible_files,
3264 None,
3265 );
3266 let mut arities = Vec::new();
3267 for visible_file in visible_files {
3268 let cell = self.macro_event_cell(&visible_file);
3269 for event in
3270 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
3271 {
3272 let MacroEvent::Define { name, binding, .. } = event else {
3273 continue;
3274 };
3275 if name != macro_name {
3276 continue;
3277 }
3278 let MacroDefinition::Object { replacement } = &binding.definition else {
3279 continue;
3280 };
3281 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
3282 continue;
3283 };
3284 if !arities.contains(&arity) {
3285 arities.push(arity);
3286 }
3287 }
3288 }
3289 let resolved = (|| {
3290 let required = arities
3291 .iter()
3292 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
3293 .min()?;
3294 let total = arities.iter().map(|arity| arity.total()).max()?;
3295 let repeated = arities
3296 .iter()
3297 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
3298 Some(CallableArity::new(required, total, repeated))
3303 })();
3304 self.callable_parameter_macro_arities
3305 .lock()
3306 .expect("C++ callable parameter-macro arity cache poisoned")
3307 .insert(cache_key, resolved);
3308 resolved
3309 }
3310
3311 pub fn include_activation_for_source(
3312 &self,
3313 cpp: &dyn CppSource,
3314 file: &ProjectFile,
3315 prepared: &PreparedSyntaxTree,
3316 donor_source: &ProjectFile,
3317 ) -> Option<usize> {
3318 let key = (file.clone(), donor_source.clone());
3319 if let Some(cached) = self
3320 .include_activation_cells
3321 .lock()
3322 .expect("C++ include activation cache poisoned")
3323 .get(&key)
3324 .copied()
3325 {
3326 return cached;
3327 }
3328 #[cfg(any(test, feature = "test-support"))]
3329 self.include_activation_build_count
3330 .fetch_add(1, Ordering::Relaxed);
3331 let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
3332 let mut cells = self
3333 .include_activation_cells
3334 .lock()
3335 .expect("C++ include activation cache poisoned");
3336 *cells.entry(key).or_insert(activation)
3337 }
3338
3339 pub fn conditional_include_projections_for_source(
3340 &self,
3341 file: &ProjectFile,
3342 prepared: &PreparedSyntaxTree,
3343 donor_source: &ProjectFile,
3344 ) -> Arc<[ConditionalIncludeProjection]> {
3345 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
3346 let cell = self
3347 .conditional_include_projection_cells
3348 .lock()
3349 .expect("C++ conditional include projection cache poisoned")
3350 .entry(file.clone())
3351 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
3352 .clone();
3353 let index = cell.get_or_build_pool_independent(|| {
3354 #[cfg(any(test, feature = "test-support"))]
3355 self.conditional_include_projection_index_build_count
3356 .fetch_add(1, Ordering::Relaxed);
3357 find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
3358 #[cfg(any(test, feature = "test-support"))]
3359 self.conditional_include_projection_state_count
3360 .fetch_add(1, Ordering::Relaxed);
3361 })
3362 });
3363 index
3364 .get(donor_source)
3365 .cloned()
3366 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
3367 }
3368
3369 #[cfg(any(test, feature = "test-support"))]
3370 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
3371 (
3372 self.conditional_include_projection_index_build_count
3373 .load(Ordering::Relaxed),
3374 self.conditional_include_projection_state_count
3375 .load(Ordering::Relaxed),
3376 )
3377 }
3378
3379 #[cfg(any(test, feature = "test-support"))]
3380 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
3381 self.conditional_include_target_state_count
3382 .load(Ordering::Relaxed)
3383 }
3384
3385 #[cfg(any(test, feature = "test-support"))]
3386 pub fn include_activation_build_count_for_test(&self) -> usize {
3387 self.include_activation_build_count.load(Ordering::Relaxed)
3388 }
3389
3390 #[cfg(any(test, feature = "test-support"))]
3391 pub fn note_using_donor_activation_for_test(&self) {
3392 self.using_donor_activation_count
3393 .fetch_add(1, Ordering::Relaxed);
3394 }
3395
3396 #[cfg(not(any(test, feature = "test-support")))]
3397 pub fn note_using_donor_activation_for_test(&self) {}
3398
3399 #[cfg(any(test, feature = "test-support"))]
3400 pub fn note_using_namespace_lookup_for_test(&self) {
3401 self.using_namespace_lookup_count
3402 .fetch_add(1, Ordering::Relaxed);
3403 }
3404
3405 #[cfg(not(any(test, feature = "test-support")))]
3406 pub fn note_using_namespace_lookup_for_test(&self) {}
3407
3408 #[cfg(any(test, feature = "test-support"))]
3409 pub fn note_using_name_candidate_inspection_for_test(&self) {
3410 self.using_name_candidate_inspection_count
3411 .fetch_add(1, Ordering::Relaxed);
3412 }
3413
3414 #[cfg(not(any(test, feature = "test-support")))]
3415 pub fn note_using_name_candidate_inspection_for_test(&self) {}
3416
3417 #[cfg(any(test, feature = "test-support"))]
3418 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
3419 (
3420 self.using_donor_activation_count.load(Ordering::Relaxed),
3421 self.using_namespace_lookup_count.load(Ordering::Relaxed),
3422 self.callable_reference_spec_build_count
3423 .load(Ordering::Relaxed),
3424 self.using_name_candidate_inspection_count
3425 .load(Ordering::Relaxed),
3426 )
3427 }
3428
3429 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3430 file == target.source()
3431 || self
3432 .visible_by_file
3433 .get(file)
3434 .is_some_and(|visible| visible.contains(target))
3435 }
3436
3437 pub fn declaration_visible_at(
3449 &self,
3450 analyzer: &CppGraphSource<'_>,
3451 file: &ProjectFile,
3452 declaration: &CodeUnit,
3453 reference_byte: usize,
3454 ) -> bool {
3455 let reference_guards = OnceCell::new();
3456 self.visible_identifier_candidates(file, declaration.identifier())
3457 .filter(|candidate| {
3458 self.same_logical_callable(analyzer, candidate, declaration)
3459 || flattened_macro_namespace_declaration_matches(
3460 analyzer,
3461 self.cpp,
3462 file,
3463 candidate,
3464 declaration,
3465 reference_byte,
3466 )
3467 })
3468 .any(|candidate| {
3469 self.physical_declaration_visible_at(
3470 analyzer,
3471 file,
3472 candidate,
3473 reference_byte,
3474 &reference_guards,
3475 )
3476 })
3477 }
3478
3479 pub fn declaration_visible_for_c_forward_call(
3485 &self,
3486 analyzer: &CppGraphSource<'_>,
3487 file: &ProjectFile,
3488 declaration: &CodeUnit,
3489 reference_byte: usize,
3490 ) -> bool {
3491 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
3492 return true;
3493 }
3494 if declaration.source() != file {
3495 return false;
3496 }
3497 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3498 return false;
3499 };
3500 let reference_guards = prepared
3501 .tree()
3502 .root_node()
3503 .descendant_for_byte_range(reference_byte, reference_byte)
3504 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
3505 declaration_guard_requirements(analyzer, self.cpp, declaration)
3506 .into_iter()
3507 .any(|(_, required)| {
3508 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
3509 })
3510 }
3511
3512 pub fn callable_arity_at_reference(
3513 &self,
3514 analyzer: &CppGraphSource<'_>,
3515 file: &ProjectFile,
3516 candidate: &CodeUnit,
3517 reference_byte: usize,
3518 ) -> Option<CallableArity> {
3519 let key = (file.clone(), logical_symbol_key(candidate));
3520 let cell = self
3521 .callable_reference_specs
3522 .lock()
3523 .expect("C++ callable reference-spec cache poisoned")
3524 .entry(key)
3525 .or_default()
3526 .clone();
3527 let spec = cell.get_or_init(|| {
3528 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3529 let spec = TargetSpec::from_target(analyzer, candidate)?;
3530 let spec = spec
3531 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
3532 .into_owned();
3533 #[cfg(any(test, feature = "test-support"))]
3534 self.callable_reference_spec_build_count
3535 .fetch_add(1, Ordering::Relaxed);
3536 Some(spec)
3537 });
3538 spec.as_ref()?.callable_arity_at(reference_byte)
3539 }
3540
3541 fn physical_declaration_visible_at(
3542 &self,
3543 analyzer: &CppGraphSource<'_>,
3544 file: &ProjectFile,
3545 declaration: &CodeUnit,
3546 reference_byte: usize,
3547 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
3548 ) -> bool {
3549 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3550 return false;
3551 };
3552 let reference = CallableReferenceContext {
3553 file,
3554 position: Some(CallableReferencePosition {
3555 prepared: prepared.as_ref(),
3556 byte: reference_byte,
3557 guards: reference_guards,
3558 }),
3559 };
3560 if declaration.source() == file {
3561 return callable_declaration_activation_in_file(
3562 analyzer,
3563 prepared.as_ref(),
3564 declaration,
3565 &reference,
3566 )
3567 .or_else(|| {
3568 self.exhaustive_guard_family_activation(
3569 analyzer,
3570 prepared.as_ref(),
3571 declaration,
3572 &reference,
3573 )
3574 })
3575 .is_some_and(|activation| activation < reference_byte);
3576 }
3577 let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
3578 return false;
3579 };
3580 if callable_declaration_activation_in_file(
3581 analyzer,
3582 donor_syntax.as_ref(),
3583 declaration,
3584 &reference,
3585 )
3586 .or_else(|| {
3587 self.exhaustive_guard_family_activation(
3588 analyzer,
3589 donor_syntax.as_ref(),
3590 declaration,
3591 &reference,
3592 )
3593 })
3594 .is_none()
3595 {
3596 return false;
3597 }
3598 declaration_guard_requirements(analyzer, self.cpp, declaration)
3599 .into_iter()
3600 .any(|(_, declaration_guards)| {
3601 self.foreign_declaration_reachable_at_reference(
3602 file,
3603 prepared.as_ref(),
3604 declaration.source(),
3605 &declaration_guards,
3606 reference.guards(),
3607 reference_byte,
3608 )
3609 })
3610 }
3611
3612 pub fn external_type_candidate_visible_at(
3613 &self,
3614 file: &ProjectFile,
3615 candidate: &CodeUnit,
3616 reference_byte: usize,
3617 ) -> bool {
3618 if candidate.source() == file {
3619 return true;
3620 }
3621 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3622 return false;
3623 };
3624 self.visible_identifier_candidates(file, candidate.identifier())
3625 .filter(|peer| same_logical_symbol(candidate, peer))
3626 .any(|peer| {
3627 peer.source() == file
3628 || self
3629 .include_activation_for_source(
3630 self.cpp,
3631 file,
3632 prepared.as_ref(),
3633 peer.source(),
3634 )
3635 .is_some_and(|activation| activation <= reference_byte)
3636 })
3637 }
3638
3639 pub fn external_type_declaration_visible_at(
3640 &self,
3641 file: &ProjectFile,
3642 candidate: &CodeUnit,
3643 reference_byte: usize,
3644 ) -> bool {
3645 if candidate.source() == file {
3646 return true;
3647 }
3648 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3649 return false;
3650 };
3651 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3652 .is_some_and(|activation| activation <= reference_byte)
3653 }
3654
3655 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
3674 if let Some(cached) = self
3675 .compile_proven_guard_cells
3676 .lock()
3677 .expect("C++ compile-proven guard cache poisoned")
3678 .get(file)
3679 {
3680 return Arc::clone(cached);
3681 }
3682 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
3683 Some(names) => names,
3684 None => {
3685 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
3686 let seed = translation_units.next().and_then(|translation_unit| {
3687 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
3688 });
3689 match seed {
3690 None => HashSet::default(),
3691 Some(mut names) => {
3692 for translation_unit in translation_units {
3693 let Some(reached) = context_fact_names(
3694 self.cpp.compile_contexts_for(&translation_unit),
3695 ) else {
3696 names.clear();
3697 break;
3698 };
3699 names.retain(|name| reached.contains(name));
3700 if names.is_empty() {
3701 break;
3702 }
3703 }
3704 names
3705 }
3706 }
3707 }
3708 };
3709 let proven = Arc::new(
3710 names
3711 .into_iter()
3712 .map(PreprocessorGuard::Defined)
3713 .collect::<HashSet<_>>(),
3714 );
3715 self.compile_proven_guard_cells
3716 .lock()
3717 .expect("C++ compile-proven guard cache poisoned")
3718 .insert(file.clone(), Arc::clone(&proven));
3719 proven
3720 }
3721
3722 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
3729 if !self.cpp.compile_contexts_for(file).is_empty() {
3730 return false;
3731 }
3732 let translation_units = self.cpp.reaching_translation_units(file);
3733 translation_units.is_empty()
3734 || translation_units
3735 .iter()
3736 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
3737 }
3738
3739 pub fn miss_requires_compile_context(
3751 &self,
3752 file: &ProjectFile,
3753 identifier: &str,
3754 reference: Node<'_>,
3755 ) -> bool {
3756 if !self.compile_context_is_absent(file) {
3757 return false;
3758 }
3759 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3760 return false;
3761 };
3762 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3763 let reference_byte = reference.start_byte();
3764 let mut sources = self
3765 .visible_identifier_candidates(file, identifier)
3766 .map(CodeUnit::source)
3767 .filter(|source| *source != file)
3768 .collect::<Vec<_>>();
3769 sources.sort();
3770 sources.dedup();
3771 sources.into_iter().any(|declaration_source| {
3772 self.conditional_include_projections_for_source(
3773 file,
3774 prepared.as_ref(),
3775 declaration_source,
3776 )
3777 .iter()
3778 .any(|projection| {
3779 projection.activation_byte <= reference_byte
3780 && !guard_requirements_hold_at_reference(
3781 &projection.required_guards,
3782 reference_guards.as_ref(),
3783 )
3784 && guards_compatible_at_reference(
3785 &projection.required_guards,
3786 reference_guards.as_ref(),
3787 )
3788 })
3789 })
3790 }
3791
3792 fn foreign_declaration_reachable_at_reference(
3803 &self,
3804 file: &ProjectFile,
3805 prepared: &PreparedSyntaxTree,
3806 declaration_source: &ProjectFile,
3807 declaration_guards: &HashSet<PreprocessorGuard>,
3808 reference_guards: Option<&HashSet<PreprocessorGuard>>,
3809 reference_byte: usize,
3810 ) -> bool {
3811 let proven = self.compile_proven_guards(file);
3817 let augmented;
3818 let reference_guards = match reference_guards {
3819 Some(active) if !proven.is_empty() => {
3820 augmented = active.union(&proven).cloned().collect();
3821 Some(&augmented)
3822 }
3823 other => other,
3824 };
3825 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3826 return false;
3827 }
3828 if self
3829 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3830 .is_some_and(|activation| activation <= reference_byte)
3831 {
3832 return true;
3833 }
3834 let projections =
3835 self.conditional_include_projections_for_source(file, prepared, declaration_source);
3836 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
3837 eprintln!(
3838 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
3839 declaration_source.rel_path().display(),
3840 declaration_guards.len(),
3841 proven.len(),
3842 projections.len(),
3843 );
3844 }
3845 projections.iter().any(|projection| {
3846 projection.activation_byte <= reference_byte
3847 && guard_requirements_hold_at_reference(
3848 &projection.required_guards,
3849 reference_guards,
3850 )
3851 && self.preprocessor_guards_stable_between(
3852 file,
3853 projection.activation_byte,
3854 reference_byte,
3855 &projection.required_guards,
3856 )
3857 })
3858 }
3859
3860 fn foreign_declaration_may_be_reachable_from_raw_guards(
3861 &self,
3862 file: &ProjectFile,
3863 prepared: &PreparedSyntaxTree,
3864 declaration_source: &ProjectFile,
3865 declaration_guards: &HashSet<PreprocessorGuard>,
3866 reference_guards: Option<&HashSet<PreprocessorGuard>>,
3867 reference_byte: usize,
3868 ) -> bool {
3869 let proven = self.compile_proven_guards(file);
3870 let augmented;
3871 let reference_guards = match reference_guards {
3872 Some(active) if !proven.is_empty() => {
3873 augmented = active.union(&proven).cloned().collect();
3874 Some(&augmented)
3875 }
3876 other => other,
3877 };
3878 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3879 return false;
3880 }
3881 if self
3882 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3883 .is_some_and(|activation| activation <= reference_byte)
3884 {
3885 return true;
3886 }
3887 let reachable = find_conditional_include_projection_for_source(
3888 self.cpp,
3889 self.token,
3890 file,
3891 prepared,
3892 declaration_source,
3893 reference_guards,
3894 reference_byte,
3895 &|| {
3896 #[cfg(any(test, feature = "test-support"))]
3897 self.conditional_include_target_state_count
3898 .fetch_add(1, Ordering::Relaxed);
3899 },
3900 );
3901 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
3902 eprintln!(
3903 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
3904 declaration_source.rel_path().display(),
3905 declaration_guards.len(),
3906 proven.len(),
3907 reference_guards.map_or(0, HashSet::len),
3908 );
3909 }
3910 reachable
3911 }
3912
3913 fn foreign_declaration_reachable_from_compile_proven_guards(
3914 &self,
3915 file: &ProjectFile,
3916 prepared: &PreparedSyntaxTree,
3917 declaration_source: &ProjectFile,
3918 declaration_guards: &HashSet<PreprocessorGuard>,
3919 reference_byte: usize,
3920 ) -> bool {
3921 let proven = self.compile_proven_guards(file);
3922 if proven.is_empty()
3923 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
3924 {
3925 return false;
3926 }
3927 let projections =
3928 self.conditional_include_projections_for_source(file, prepared, declaration_source);
3929 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
3930 eprintln!(
3931 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
3932 declaration_source.rel_path().display(),
3933 declaration_guards.len(),
3934 proven.len(),
3935 projections.len(),
3936 );
3937 }
3938 projections.iter().any(|projection| {
3939 projection.activation_byte <= reference_byte
3940 && guard_requirements_hold_at_reference(
3941 &projection.required_guards,
3942 Some(proven.as_ref()),
3943 )
3944 && self.preprocessor_guards_stable_between(
3949 file,
3950 0,
3951 projection.activation_byte,
3952 &projection.required_guards,
3953 )
3954 })
3955 }
3956
3957 pub fn external_type_candidate_visible_in_context(
3958 &self,
3959 analyzer: &CppGraphSource<'_>,
3960 file: &ProjectFile,
3961 candidate: &CodeUnit,
3962 reference: Node<'_>,
3963 ) -> bool {
3964 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
3965 if report_stats {
3966 eprintln!(
3967 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
3968 candidate.fq_name(),
3969 candidate.source().rel_path().display(),
3970 file.rel_path().display(),
3971 reference.start_byte(),
3972 );
3973 }
3974 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3975 return false;
3976 };
3977 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
3978 let reference_guards = OnceCell::new();
3979 let reference_guards_at_site = || {
3980 reference_guards.get_or_init(|| {
3981 let started = Instant::now();
3982 if report_stats {
3983 eprintln!(
3984 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
3985 file.rel_path().display(),
3986 reference.start_byte(),
3987 raw_reference_guards.as_ref().map_or(0, HashSet::len),
3988 );
3989 }
3990 let macro_environment = self.macro_environment(file, reference.start_byte());
3991 let filtered = raw_reference_guards
3992 .clone()
3993 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
3994 if report_stats {
3995 eprintln!(
3996 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
3997 filtered.is_some(),
3998 started.elapsed().as_millis(),
3999 );
4000 }
4001 filtered
4002 })
4003 };
4004
4005 let peers = self
4006 .visible_identifier_candidates(file, candidate.identifier())
4007 .filter(|peer| same_logical_symbol(candidate, peer))
4008 .collect::<Vec<_>>();
4009 if report_stats {
4010 let peer_sources = peers
4011 .iter()
4012 .map(|peer| peer.source().rel_path().display().to_string())
4013 .collect::<Vec<_>>();
4014 eprintln!(
4015 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
4016 candidate.fq_name(),
4017 );
4018 }
4019 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
4020 declaration_guard_requirements(analyzer, self.cpp, peer)
4021 .into_iter()
4022 .any(|(declaration_byte, declaration_guards)| {
4023 if peer.source() == file {
4024 let visible = declaration_byte < reference.start_byte()
4025 && declaration_guards.is_empty();
4026 if report_stats {
4027 eprintln!(
4028 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
4029 peer.source().rel_path().display(),
4030 declaration_guards.len(),
4031 );
4032 }
4033 return visible;
4034 }
4035 let direct = declaration_guards.is_empty()
4036 && self
4037 .include_activation_for_source(
4038 self.cpp,
4039 file,
4040 prepared.as_ref(),
4041 peer.source(),
4042 )
4043 .is_some_and(|activation| activation <= reference.start_byte());
4044 let compile_proven = !direct
4045 && self.foreign_declaration_reachable_from_compile_proven_guards(
4046 file,
4047 prepared.as_ref(),
4048 peer.source(),
4049 &declaration_guards,
4050 reference.start_byte(),
4051 );
4052 if report_stats {
4053 eprintln!(
4054 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
4055 peer.source().rel_path().display(),
4056 declaration_guards.len(),
4057 );
4058 }
4059 direct || compile_proven
4060 })
4061 });
4062 if directly_visible_without_reference_environment {
4063 if report_stats {
4064 eprintln!(
4065 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
4066 candidate.fq_name(),
4067 );
4068 }
4069 return true;
4070 }
4071 let directly_visible = peers.iter().any(|peer| {
4072 declaration_guard_requirements(analyzer, self.cpp, peer)
4073 .into_iter()
4074 .any(|(declaration_byte, declaration_guards)| {
4075 if peer.source() == file {
4076 if declaration_byte >= reference.start_byte() {
4077 return false;
4078 }
4079 if !guard_requirements_hold_at_reference(
4080 &declaration_guards,
4081 raw_reference_guards.as_ref(),
4082 ) {
4083 return false;
4084 }
4085 return guard_requirements_hold_at_reference(
4086 &declaration_guards,
4087 reference_guards_at_site().as_ref(),
4088 ) && self.preprocessor_guards_stable_between(
4089 file,
4090 declaration_byte,
4091 reference.start_byte(),
4092 &declaration_guards,
4093 );
4094 }
4095 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
4096 file,
4097 prepared.as_ref(),
4098 peer.source(),
4099 &declaration_guards,
4100 raw_reference_guards.as_ref(),
4101 reference.start_byte(),
4102 );
4103 if report_stats {
4104 eprintln!(
4105 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
4106 peer.source().rel_path().display(),
4107 declaration_guards.len(),
4108 );
4109 }
4110 if !raw_feasible {
4111 return false;
4112 }
4113 self.foreign_declaration_reachable_at_reference(
4114 file,
4115 prepared.as_ref(),
4116 peer.source(),
4117 &declaration_guards,
4118 reference_guards_at_site().as_ref(),
4119 reference.start_byte(),
4120 )
4121 })
4122 });
4123 if directly_visible {
4124 if report_stats {
4125 eprintln!(
4126 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
4127 candidate.fq_name(),
4128 );
4129 }
4130 return true;
4131 }
4132 let complementary = self
4133 .visible_identifier_candidates(file, candidate.identifier())
4134 .filter(|peer| {
4135 peer.kind() == candidate.kind()
4136 && peer.fq_name() == candidate.fq_name()
4137 && peer.source() == candidate.source()
4138 })
4139 .collect::<Vec<_>>();
4140 let complementary_family =
4145 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
4146 let raw_candidate_branch_compatible = complementary_family
4147 && raw_reference_guards.as_ref().is_some_and(|active| {
4148 declaration_guard_requirements(analyzer, self.cpp, candidate)
4149 .iter()
4150 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4151 });
4152 if report_stats {
4153 eprintln!(
4154 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
4155 candidate.fq_name(),
4156 complementary.len(),
4157 complementary_family,
4158 raw_candidate_branch_compatible,
4159 );
4160 }
4161 let candidate_branch_compatible = raw_candidate_branch_compatible
4162 && reference_guards_at_site().as_ref().is_some_and(|active| {
4163 declaration_guard_requirements(analyzer, self.cpp, candidate)
4164 .iter()
4165 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4166 });
4167 let complementary_visible = candidate_branch_compatible
4168 && if candidate.source() == file {
4169 declaration_guard_requirements(analyzer, self.cpp, candidate)
4170 .iter()
4171 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
4172 } else {
4173 self.include_activation_for_source(
4174 self.cpp,
4175 file,
4176 prepared.as_ref(),
4177 candidate.source(),
4178 )
4179 .is_some_and(|activation| activation <= reference.start_byte())
4180 };
4181 if report_stats {
4182 eprintln!(
4183 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
4184 if complementary_visible {
4185 "complementary"
4186 } else {
4187 "missing"
4188 },
4189 candidate.fq_name(),
4190 );
4191 }
4192 complementary_visible
4193 }
4194
4195 pub fn is_exhaustive_same_fqn_type_declaration_family(
4196 &self,
4197 analyzer: &CppGraphSource<'_>,
4198 file: &ProjectFile,
4199 candidate: &CodeUnit,
4200 ) -> bool {
4201 let candidates = self
4202 .visible_identifier_candidates(file, candidate.identifier())
4203 .filter(|peer| {
4204 peer.kind() == candidate.kind()
4205 && peer.fq_name() == candidate.fq_name()
4206 && peer.source() == candidate.source()
4207 })
4208 .collect::<Vec<_>>();
4209 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
4210 }
4211
4212 pub fn dependent_member_pointer_alias_visible_in_context(
4227 &self,
4228 analyzer: &CppGraphSource<'_>,
4229 file: &ProjectFile,
4230 candidate: &CodeUnit,
4231 owner_components: &[String],
4232 reference: Node<'_>,
4233 ) -> bool {
4234 if !analyzer
4235 .type_alias_provider()
4236 .is_some_and(|provider| provider.is_type_alias(candidate))
4237 {
4238 return false;
4239 }
4240 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
4241 return false;
4242 };
4243 if terminal != candidate.identifier()
4244 || canonical_cpp_scope_components(candidate) != owner_components
4245 {
4246 return false;
4247 }
4248 let Some(expected_parent_fq_name) =
4249 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
4250 else {
4251 return false;
4252 };
4253 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
4254 return false;
4255 };
4256 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
4257 || parent_anchor.source() != candidate.source()
4258 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
4259 {
4260 return false;
4261 }
4262
4263 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
4269 || candidate.source() == file
4270 && !analyzer
4271 .ranges(candidate)
4272 .iter()
4273 .any(|range| range.start_byte < reference.start_byte())
4274 {
4275 return false;
4276 }
4277
4278 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
4279 if candidate_guards.is_empty() {
4280 return false;
4281 }
4282 let same_guard_sets =
4283 |left: &[(usize, HashSet<PreprocessorGuard>)],
4284 right: &[(usize, HashSet<PreprocessorGuard>)]| {
4285 left.iter().all(|(_, left_guards)| {
4286 right
4287 .iter()
4288 .any(|(_, right_guards)| left_guards == right_guards)
4289 })
4290 };
4291 let parent_candidates = self
4292 .visible_identifier_candidates(file, parent_anchor.identifier())
4293 .filter(|peer| {
4294 peer.kind() == parent_anchor.kind()
4295 && peer.fq_name() == expected_parent_fq_name.as_str()
4296 && peer.source() == parent_anchor.source()
4297 && canonical_cpp_scope_components(peer) == owner_prefix
4298 })
4299 .filter_map(|peer| {
4300 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
4301 (candidate_guards.len() == parent_guards.len()
4302 && same_guard_sets(&candidate_guards, &parent_guards)
4303 && same_guard_sets(&parent_guards, &candidate_guards))
4304 .then(|| (peer.clone(), parent_guards))
4305 })
4306 .collect::<Vec<_>>();
4307 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
4308 return false;
4309 };
4310
4311 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4312 return false;
4313 };
4314 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
4315 else {
4316 return false;
4317 };
4318 if !candidate_guards.iter().any(|(_, target_guards)| {
4323 guards_compatible_at_reference(target_guards, Some(&reference_guards))
4324 && (candidate.source() != file
4325 || self.preprocessor_guards_stable_between(
4326 file,
4327 0,
4328 reference.start_byte(),
4329 target_guards,
4330 ))
4331 }) {
4332 return false;
4333 }
4334
4335 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
4336 }
4337
4338 pub fn external_type_candidate_guard_compatible_in_context(
4348 &self,
4349 analyzer: &CppGraphSource<'_>,
4350 file: &ProjectFile,
4351 candidate: &CodeUnit,
4352 reference: Node<'_>,
4353 ) -> bool {
4354 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4355 return false;
4356 };
4357 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4358
4359 self.visible_identifier_candidates(file, candidate.identifier())
4360 .filter(|peer| same_logical_symbol(candidate, peer))
4361 .any(|peer| {
4362 declaration_guard_requirements(analyzer, self.cpp, peer)
4363 .into_iter()
4364 .any(|(declaration_byte, declaration_guards)| {
4365 if peer.source() == file {
4366 let (start, end) = if declaration_byte <= reference.start_byte() {
4367 (declaration_byte, reference.start_byte())
4368 } else {
4369 (reference.start_byte(), declaration_byte)
4370 };
4371 return guard_requirements_hold_at_reference(
4372 &declaration_guards,
4373 reference_guards.as_ref(),
4374 ) && self.preprocessor_guards_stable_between(
4375 file,
4376 start,
4377 end,
4378 &declaration_guards,
4379 );
4380 }
4381 self.foreign_declaration_reachable_at_reference(
4382 file,
4383 prepared.as_ref(),
4384 peer.source(),
4385 &declaration_guards,
4386 reference_guards.as_ref(),
4387 reference.start_byte(),
4388 )
4389 })
4390 })
4391 }
4392
4393 pub fn same_file_callable_guard_compatible_ignoring_order(
4401 &self,
4402 analyzer: &CppGraphSource<'_>,
4403 file: &ProjectFile,
4404 candidate: &CodeUnit,
4405 reference: Node<'_>,
4406 ) -> bool {
4407 if candidate.source() != file || !candidate.is_callable() {
4408 return false;
4409 }
4410 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4411 return false;
4412 };
4413 let guards = OnceCell::new();
4414 let context = CallableReferenceContext {
4415 file,
4416 position: Some(CallableReferencePosition {
4417 prepared: prepared.as_ref(),
4418 byte: reference.start_byte(),
4419 guards: &guards,
4420 }),
4421 };
4422 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
4423 .into_iter()
4424 .any(|declaration| {
4425 callable_preprocessor_context_is_visible_for_reference(
4426 declaration,
4427 prepared.source(),
4428 &context,
4429 )
4430 })
4431 }
4432
4433 pub fn type_candidate_may_be_visible_before_reference(
4434 &self,
4435 analyzer: &CppGraphSource<'_>,
4436 file: &ProjectFile,
4437 candidate: &CodeUnit,
4438 reference_byte: usize,
4439 ) -> bool {
4440 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4441 return false;
4442 };
4443 let root = prepared.tree().root_node();
4444 let end_byte = reference_byte
4445 .saturating_add(1)
4446 .min(prepared.source().len());
4447 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
4448 return false;
4449 };
4450 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
4451 }
4452
4453 pub fn preprocessor_guards_stable_between(
4454 &self,
4455 file: &ProjectFile,
4456 start_byte: usize,
4457 end_byte: usize,
4458 guards: &HashSet<PreprocessorGuard>,
4459 ) -> bool {
4460 if guards.is_empty() || start_byte >= end_byte {
4461 return true;
4462 }
4463 let cell = self.macro_event_cell(file);
4464 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4465 let mut visited = HashSet::from_iter([file.clone()]);
4466 !events.iter().any(|event| {
4467 event.byte() >= start_byte
4468 && event.byte() < end_byte
4469 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
4470 })
4471 }
4472
4473 fn macro_event_may_mutate_guards(
4474 &self,
4475 event: &MacroEvent,
4476 guards: &HashSet<PreprocessorGuard>,
4477 visited: &mut HashSet<ProjectFile>,
4478 ) -> bool {
4479 match event {
4480 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
4481 guards.iter().any(|guard| guard.may_depend_on_macro(name))
4482 }
4483 MacroEvent::Include { targets, .. } => {
4484 targets.is_empty()
4485 || targets
4486 .iter()
4487 .any(|target| self.source_may_mutate_guards(target, guards, visited))
4488 }
4489 MacroEvent::Invalidate { .. } => true,
4490 }
4491 }
4492
4493 fn source_may_mutate_guards(
4494 &self,
4495 file: &ProjectFile,
4496 guards: &HashSet<PreprocessorGuard>,
4497 visited: &mut HashSet<ProjectFile>,
4498 ) -> bool {
4499 if !visited.insert(file.clone()) {
4500 return false;
4501 }
4502 let cell = self.macro_event_cell(file);
4503 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4504 events
4505 .iter()
4506 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
4507 }
4508
4509 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
4510 let normalized = normalize_reference_name(raw_name)?;
4511 self.type_candidates(file, &normalized)
4512 .into_iter()
4513 .next()
4514 .cloned()
4515 }
4516
4517 pub fn unique_visible_parameter_type_fallback(
4526 &self,
4527 analyzer: &CppGraphSource<'_>,
4528 file: &ProjectFile,
4529 node: Node<'_>,
4530 source: &str,
4531 ) -> Option<CodeUnit> {
4532 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
4533 return None;
4534 }
4535 let name = node_text(node, source);
4536 let candidates = self
4537 .visible_identifier_candidates(file, name)
4538 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
4539 .filter(|candidate| {
4540 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
4541 })
4542 .collect::<Vec<_>>();
4543 self.unique_canonical_type_candidate(analyzer, file, &candidates)
4544 }
4545
4546 pub fn resolve_type_node_result(
4547 &self,
4548 file: &ProjectFile,
4549 node: Node<'_>,
4550 source: &str,
4551 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
4552 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
4553 return Ok(None);
4554 };
4555 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4556 return Ok(Some(primary));
4557 };
4558 self.resolve_template_arguments(file, primary, &arguments)
4559 .map(Some)
4560 }
4561
4562 pub fn resolve_type_node_primary(
4563 &self,
4564 file: &ProjectFile,
4565 node: Node<'_>,
4566 source: &str,
4567 ) -> Option<CodeUnit> {
4568 let components = cpp_type_name_components(node, source)?;
4569 self.resolve_type(file, &components.join("::"))
4570 }
4571
4572 pub fn resolve_template_arguments(
4573 &self,
4574 file: &ProjectFile,
4575 primary: CodeUnit,
4576 arguments: &[CppTemplateExpression],
4577 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4578 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
4579 }
4580
4581 fn resolve_template_arguments_inner(
4582 &self,
4583 file: &ProjectFile,
4584 primary: CodeUnit,
4585 arguments: &[CppTemplateExpression],
4586 seen_aliases: &mut HashSet<CodeUnit>,
4587 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4588 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
4589 && let Some(alias_target) = &metadata.alias_target
4590 {
4591 if !seen_aliases.insert(primary.clone()) {
4592 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
4593 }
4594 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
4595 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
4596 let target_name = alias_target.components.join("::");
4597 let target_primary = if alias_target.global {
4598 unique_logical_type_candidate(self.type_candidates(file, &target_name))
4599 } else {
4600 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
4601 };
4602 let Some(target_primary) = target_primary else {
4603 return Ok(primary);
4607 };
4608 let Some(target_arguments) = &alias_target.arguments else {
4609 return Ok(target_primary);
4610 };
4611 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
4612 .ok_or(CppTemplateResolutionError::Substitution)?;
4613 return self.resolve_template_arguments_inner(
4614 file,
4615 target_primary,
4616 &target_arguments,
4617 seen_aliases,
4618 );
4619 }
4620
4621 let primary_fq_name = self
4622 .cpp_template_metadata
4623 .get(&primary)
4624 .map(|metadata| metadata.primary_fq_name.clone())
4625 .unwrap_or_else(|| primary.fq_name());
4626 let has_specialization_metadata = self
4627 .cpp_template_families
4628 .get(&primary_fq_name)
4629 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
4630 if !has_specialization_metadata {
4631 return Ok(primary);
4632 }
4633 self.select_template_specialization(file, &primary, arguments)
4634 }
4635
4636 fn select_template_specialization(
4637 &self,
4638 file: &ProjectFile,
4639 resolved: &CodeUnit,
4640 explicit_arguments: &[CppTemplateExpression],
4641 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4642 let primary_fq_name = self
4643 .cpp_template_metadata
4644 .get(resolved)
4645 .map(|metadata| metadata.primary_fq_name.clone())
4646 .unwrap_or_else(|| resolved.fq_name());
4647 let family = self
4648 .cpp_template_families
4649 .get(&primary_fq_name)
4650 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4651 let primary_candidates = family
4652 .iter()
4653 .filter_map(|unit| {
4654 let metadata = self.cpp_template_metadata.get(unit)?;
4655 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
4656 })
4657 .collect::<Vec<_>>();
4658 let primary_unit = primary_candidates
4659 .iter()
4660 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
4661 .or_else(|| {
4662 primary_candidates
4663 .iter()
4664 .map(|(unit, _)| *unit)
4665 .min_by_key(|unit| {
4666 (
4667 unit.source().to_string(),
4668 unit.signature().unwrap_or_default(),
4669 )
4670 })
4671 })
4672 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4673 let primary_parameters =
4674 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
4675 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4676 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
4677 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
4678
4679 let mut applicable = Vec::new();
4680 for unit in family {
4681 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
4682 continue;
4683 };
4684 if metadata.is_primary() || !self.is_visible(file, unit) {
4685 continue;
4686 }
4687 if !cpp_specialization_matches(metadata, &expanded) {
4688 continue;
4689 }
4690 applicable.push((unit, metadata));
4691 }
4692 if applicable.is_empty() {
4693 return Ok(primary_unit.clone());
4694 }
4695
4696 let winners = applicable
4701 .iter()
4702 .filter(|(candidate, candidate_metadata)| {
4703 applicable.iter().all(|(other, other_metadata)| {
4704 same_visible_symbol(candidate, other)
4705 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
4706 })
4707 })
4708 .copied()
4709 .collect::<Vec<_>>();
4710 let Some((selected, _)) = winners.first() else {
4711 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4714 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
4715 });
4716 };
4717 if winners
4718 .iter()
4719 .any(|(unit, _)| !same_visible_symbol(unit, selected))
4720 {
4721 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4722 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
4723 });
4724 }
4725 Ok((*selected).clone())
4726 }
4727
4728 pub fn resolve_type_components_lexically(
4729 &self,
4730 analyzer: &CppGraphSource<'_>,
4731 file: &ProjectFile,
4732 components: &[String],
4733 global: bool,
4734 lexical_scope: &[String],
4735 ) -> LexicalTypeResolution {
4736 self.resolve_type_components_lexically_inner(
4737 analyzer,
4738 file,
4739 components,
4740 global,
4741 lexical_scope,
4742 TypeCandidateResolution::Canonical,
4743 )
4744 }
4745
4746 pub fn resolve_type_components_lexically_for_forward(
4747 &self,
4748 analyzer: &CppGraphSource<'_>,
4749 file: &ProjectFile,
4750 components: &[String],
4751 global: bool,
4752 lexical_scope: &[String],
4753 ) -> LexicalTypeResolution {
4754 self.resolve_type_components_lexically_inner(
4755 analyzer,
4756 file,
4757 components,
4758 global,
4759 lexical_scope,
4760 TypeCandidateResolution::PreserveAlias,
4761 )
4762 }
4763
4764 pub fn resolve_type_components_lexically_for_target(
4765 &self,
4766 analyzer: &CppGraphSource<'_>,
4767 file: &ProjectFile,
4768 components: &[String],
4769 global: bool,
4770 lexical_scope: &[String],
4771 target: &CodeUnit,
4772 ) -> LexicalTypeResolution {
4773 #[cfg(any(test, feature = "test-support"))]
4774 self.target_preserving_type_resolution_count
4775 .fetch_add(1, Ordering::Relaxed);
4776 self.resolve_type_components_lexically_inner(
4777 analyzer,
4778 file,
4779 components,
4780 global,
4781 lexical_scope,
4782 TypeCandidateResolution::PreserveTarget(target),
4783 )
4784 }
4785
4786 pub fn coarse_unqualified_type_reference_may_resolve(
4787 &self,
4788 file: &ProjectFile,
4789 name: &str,
4790 ) -> bool {
4791 if name.is_empty() {
4792 return true;
4793 }
4794 self.visible_identifier_candidates(file, name)
4795 .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
4796 || self.visible_parser_alias_name_is_visible(file, name)
4797 }
4798
4799 #[allow(clippy::too_many_arguments)]
4800 pub fn structured_type_reference_may_resolve_to_target(
4801 &self,
4802 analyzer: &CppGraphSource<'_>,
4803 file: &ProjectFile,
4804 components: &[String],
4805 global: bool,
4806 lexical_scope: &[String],
4807 target: &CodeUnit,
4808 ) -> bool {
4809 if components.is_empty() {
4810 return true;
4811 }
4812 let Some(terminal) = components.last() else {
4813 return true;
4814 };
4815 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
4816 .map(|qualified| qualified.join("::"))
4817 .collect::<Vec<_>>();
4818 let target_name = cpp_name_for(target);
4819 if qualified_tiers
4820 .iter()
4821 .any(|qualified| qualified == &target_name)
4822 {
4823 return true;
4824 }
4825
4826 let mut saw_shape_candidate = false;
4827 for candidate in self.visible_identifier_candidates(file, terminal) {
4828 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4829 {
4830 continue;
4831 }
4832 let candidate_name = cpp_name_for(candidate);
4833 let shape_matches = if global || components.len() > 1 {
4834 qualified_tiers
4835 .iter()
4836 .any(|qualified| qualified == &candidate_name)
4837 } else {
4838 true
4839 };
4840 if !shape_matches {
4841 continue;
4842 }
4843 saw_shape_candidate = true;
4844 if same_visible_symbol(candidate, target)
4845 || self.compatible_primary_template_redeclarations(candidate, target)
4846 || (declared_type_alias(analyzer, candidate)
4847 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
4848 {
4849 return true;
4850 }
4851 }
4852
4853 !saw_shape_candidate
4854 }
4855
4856 pub fn target_preserving_reference_namespace(
4857 &self,
4858 analyzer: &CppGraphSource<'_>,
4859 file: &ProjectFile,
4860 identifier: &str,
4861 target: &CodeUnit,
4862 ) -> Option<Vec<String>> {
4863 let mut namespace = None;
4864 for candidate in self.visible_identifier_candidates(file, identifier) {
4865 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4866 {
4867 continue;
4868 }
4869 if !(same_visible_symbol(candidate, target)
4870 || self.compatible_primary_template_redeclarations(candidate, target)
4871 || declared_type_alias(analyzer, candidate)
4872 && self.structured_alias_primary_preserves_target(
4873 analyzer, file, candidate, target,
4874 ))
4875 {
4876 continue;
4877 }
4878 if namespace
4879 .as_ref()
4880 .is_some_and(|existing| existing != candidate.package_name())
4881 {
4882 return None;
4883 }
4884 namespace = Some(candidate.package_name().to_string());
4885 }
4886 let namespace = namespace?;
4887 Some(
4888 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4889 brokk_bifrost_core::analyzer::Language::Cpp,
4890 &namespace,
4891 ),
4892 )
4893 }
4894
4895 pub fn resolve_imported_type_candidate(
4896 &self,
4897 analyzer: &CppGraphSource<'_>,
4898 file: &ProjectFile,
4899 target: &CodeUnit,
4900 target_components: &[String],
4901 direct_target: Option<&CodeUnit>,
4902 preserve_alias: bool,
4903 ) -> LexicalTypeResolution {
4904 let candidates = [target];
4905 let resolution = if preserve_alias {
4906 TypeCandidateResolution::PreserveAlias
4907 } else {
4908 direct_target.map_or(
4909 TypeCandidateResolution::Canonical,
4910 TypeCandidateResolution::PreserveTarget,
4911 )
4912 };
4913 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4917 Ok(unit) => LexicalTypeResolution::Resolved {
4918 unit,
4919 components: target_components.to_vec(),
4920 candidates: vec![target.clone()],
4921 },
4922 Err(failure) => failure.lexical_resolution(),
4923 }
4924 }
4925
4926 fn resolve_type_components_lexically_inner(
4927 &self,
4928 analyzer: &CppGraphSource<'_>,
4929 file: &ProjectFile,
4930 components: &[String],
4931 global: bool,
4932 lexical_scope: &[String],
4933 resolution: TypeCandidateResolution<'_>,
4934 ) -> LexicalTypeResolution {
4935 if components.is_empty() {
4936 return LexicalTypeResolution::Missing;
4937 }
4938 let mut injected = self.resolve_injected_class_name(
4948 analyzer,
4949 file,
4950 components,
4951 global,
4952 lexical_scope,
4953 resolution,
4954 );
4955 for qualified in lexical_component_tiers(components, global, lexical_scope) {
4956 let prefix_len = qualified.len().saturating_sub(components.len());
4957 if injected
4958 .as_ref()
4959 .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
4960 {
4961 return injected
4962 .take()
4963 .expect("injected class resolution was just present")
4964 .1;
4965 }
4966 let qualified_name = qualified.join("::");
4967 let candidates = self
4968 .type_candidates(file, &qualified_name)
4969 .into_iter()
4970 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4971 .collect::<Vec<_>>();
4972 if candidates.is_empty() {
4973 if !global && components.len() == 1 {
4974 match self.resolve_inherited_type_for_lexical_scope(
4975 analyzer,
4976 file,
4977 &qualified[..prefix_len],
4978 &components[0],
4979 resolution,
4980 ) {
4981 LexicalTypeResolution::Missing => {}
4982 inherited => return inherited,
4983 }
4984 }
4985 continue;
4986 }
4987 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4988 Ok(unit) => unit,
4989 Err(failure) => return failure.lexical_resolution(),
4990 };
4991 return LexicalTypeResolution::Resolved {
4992 unit,
4993 components: qualified,
4994 candidates: candidates.into_iter().cloned().collect(),
4995 };
4996 }
4997 LexicalTypeResolution::Missing
4998 }
4999
5000 fn resolve_injected_class_name(
5001 &self,
5002 analyzer: &CppGraphSource<'_>,
5003 file: &ProjectFile,
5004 components: &[String],
5005 global: bool,
5006 lexical_scope: &[String],
5007 resolution: TypeCandidateResolution<'_>,
5008 ) -> Option<(usize, LexicalTypeResolution)> {
5009 if global
5010 || components.len() != 1
5011 || file.rel_path().extension().is_some_and(|ext| ext == "c")
5012 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
5013 {
5014 return None;
5015 }
5016 let name = components.first()?;
5017 let mut matches: Vec<&CodeUnit> = Vec::new();
5018 let mut owner_len = 0;
5019 for candidate in self.visible_identifier_candidates(file, name) {
5020 if !candidate.is_class()
5021 || declared_type_alias(analyzer, candidate)
5022 || candidate.identifier() != name
5023 {
5024 continue;
5025 }
5026 let candidate_scope = canonical_cpp_scope_components(candidate);
5027 if candidate_scope.len() > lexical_scope.len()
5028 || !lexical_scope.starts_with(&candidate_scope)
5029 || candidate_scope.last().is_none_or(|last| last != name)
5030 {
5031 continue;
5032 }
5033 if candidate_scope.len() > owner_len {
5034 owner_len = candidate_scope.len();
5035 matches.clear();
5036 }
5037 if candidate_scope.len() == owner_len
5038 && !matches
5039 .iter()
5040 .any(|existing| same_logical_symbol(existing, candidate))
5041 {
5042 matches.push(candidate);
5043 }
5044 }
5045 if matches.is_empty() {
5046 return None;
5047 }
5048 if owner_len >= lexical_scope.len() {
5056 return None;
5057 }
5058 let owner_components = lexical_scope[..owner_len].to_vec();
5059 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
5060 Ok(unit) => LexicalTypeResolution::Resolved {
5061 unit,
5062 components: owner_components,
5063 candidates: matches.into_iter().cloned().collect(),
5064 },
5065 Err(failure) => failure.lexical_resolution(),
5066 };
5067 Some((owner_len, resolution))
5068 }
5069
5070 fn resolve_inherited_type_for_lexical_scope(
5071 &self,
5072 analyzer: &CppGraphSource<'_>,
5073 file: &ProjectFile,
5074 lexical_scope: &[String],
5075 name: &str,
5076 resolution: TypeCandidateResolution<'_>,
5077 ) -> LexicalTypeResolution {
5078 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5079 return LexicalTypeResolution::Missing;
5080 };
5081 let lexical_owner_name = lexical_scope.join("::");
5082 if lexical_owner_name.is_empty() {
5083 return LexicalTypeResolution::Missing;
5084 }
5085 let owner_candidates = self
5086 .type_candidates(file, &lexical_owner_name)
5087 .into_iter()
5088 .filter(|candidate| {
5089 canonical_cpp_name_matches(candidate, &lexical_owner_name)
5090 && !declared_type_alias(analyzer, candidate)
5091 })
5092 .collect::<Vec<_>>();
5093 if owner_candidates.is_empty() {
5094 return LexicalTypeResolution::Missing;
5095 }
5096 let physical_owner_candidates = owner_candidates
5101 .iter()
5102 .copied()
5103 .filter(|candidate| candidate.source() == file)
5104 .collect::<Vec<_>>();
5105 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
5106 owner_candidates
5107 } else {
5108 physical_owner_candidates
5109 };
5110 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
5111 return LexicalTypeResolution::Ambiguous;
5112 };
5113
5114 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
5115 let mut visited_owners = HashSet::default();
5116 while !frontier.is_empty() {
5117 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
5118 let mut next_frontier = Vec::new();
5119 for owner in frontier {
5120 if !visited_owners.insert(owner.fq_name()) {
5121 continue;
5122 }
5123 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
5124 let candidates = self
5125 .type_candidates(file, &qualified_name)
5126 .into_iter()
5127 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5128 .collect::<Vec<_>>();
5129 if candidates.is_empty() {
5130 for ancestor in hierarchy.get_direct_ancestors(&owner) {
5131 if !next_frontier
5132 .iter()
5133 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
5134 {
5135 next_frontier.push(ancestor);
5136 }
5137 }
5138 continue;
5139 }
5140 let unit =
5141 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5142 Ok(unit) => unit,
5143 Err(failure) => return failure.lexical_resolution(),
5144 };
5145 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
5146 }
5147 if let Some((unit, candidates)) = level_matches.first().cloned() {
5148 let Some(first_declaration) = candidates.first() else {
5149 return LexicalTypeResolution::Ambiguous;
5150 };
5151 if !level_matches.iter().all(|(_, declarations)| {
5152 declarations
5153 .iter()
5154 .all(|declaration| same_logical_symbol(first_declaration, declaration))
5155 }) {
5156 return LexicalTypeResolution::Ambiguous;
5157 }
5158 let mut components = lexical_scope.to_vec();
5159 components.push(name.to_string());
5160 return LexicalTypeResolution::Resolved {
5161 unit,
5162 components,
5163 candidates,
5164 };
5165 }
5166 frontier = next_frontier;
5167 }
5168 LexicalTypeResolution::Missing
5169 }
5170
5171 pub fn inherited_injected_class_owner(
5174 &self,
5175 analyzer: &CppGraphSource<'_>,
5176 file: &ProjectFile,
5177 enclosing_owner: &CodeUnit,
5178 injected_name: &str,
5179 ) -> Option<CodeUnit> {
5180 let hierarchy = analyzer.type_hierarchy_provider()?;
5181 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
5182 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5183 while !frontier.is_empty() {
5184 let mut level_matches = Vec::new();
5185 let mut next_frontier = Vec::new();
5186 for raw_owner in frontier {
5187 let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
5188 let propagated = propagated_counts.entry(owner.clone()).or_default();
5189 if *propagated == 2 {
5190 continue;
5191 }
5192 *propagated += 1;
5193 if owner.identifier() == injected_name {
5194 level_matches.push(owner.clone());
5195 }
5196 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
5197 }
5198 match level_matches.as_slice() {
5199 [owner] => return Some(owner.clone()),
5200 [_, ..] => return None,
5201 [] => {}
5202 }
5203 frontier = next_frontier;
5204 }
5205 None
5206 }
5207
5208 fn resolve_type_candidates(
5213 &self,
5214 analyzer: &CppGraphSource<'_>,
5215 file: &ProjectFile,
5216 candidates: &[&CodeUnit],
5217 resolution: TypeCandidateResolution<'_>,
5218 ) -> Result<CodeUnit, TypeCandidateFailure> {
5219 match resolution {
5220 TypeCandidateResolution::Canonical => {
5221 self.canonical_type_candidate_resolution(analyzer, file, candidates)
5222 }
5223 TypeCandidateResolution::PreserveAlias => {
5224 let same_fqn_alias_family = candidates.len() > 1
5231 && candidates.iter().all(|candidate| {
5232 declared_type_alias(analyzer, candidate)
5233 && same_logical_symbol(candidates[0], candidate)
5234 })
5235 && candidates
5236 .iter()
5237 .any(|candidate| candidate.source() != candidates[0].source());
5238 if same_fqn_alias_family {
5239 let physically_visible = candidates
5240 .iter()
5241 .copied()
5242 .filter(|candidate| self.is_physically_visible(file, candidate))
5243 .collect::<Vec<_>>();
5244 let one_structured_target = physically_visible.len() > 1
5254 && physically_visible.iter().skip(1).all(|candidate| {
5255 let target = self.structured_alias_target(analyzer, candidate);
5256 target.is_some()
5257 && target
5258 == self.structured_alias_target(analyzer, physically_visible[0])
5259 });
5260 if physically_visible.len() == 1 || one_structured_target {
5261 return Ok(physically_visible[0].clone());
5262 }
5263 }
5264 unique_type_candidate_preserving_alias(analyzer, candidates)
5265 .ok_or(TypeCandidateFailure::Ambiguous)
5266 }
5267 TypeCandidateResolution::PreserveTarget(target) => self
5268 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
5269 .ok_or(TypeCandidateFailure::Ambiguous),
5270 }
5271 }
5272
5273 pub fn resolve_callable_value_components_lexically(
5274 &self,
5275 analyzer: &CppGraphSource<'_>,
5276 file: &ProjectFile,
5277 owner_components: &[String],
5278 member_name: &str,
5279 global: bool,
5280 lexical_scope: &[String],
5281 ) -> LexicalCallableValueResolution {
5282 if owner_components.is_empty() || member_name.is_empty() {
5283 return LexicalCallableValueResolution::Missing;
5284 }
5285 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
5286 let owner_name = qualified_owner.join("::");
5287 let type_candidates = self
5288 .type_candidates(file, &owner_name)
5289 .into_iter()
5290 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
5291 .collect::<Vec<_>>();
5292 let resolved_type = if type_candidates.is_empty() {
5293 None
5294 } else {
5295 let Some(unit) =
5296 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
5297 else {
5298 return LexicalCallableValueResolution::Ambiguous;
5299 };
5300 Some(unit)
5301 };
5302
5303 let mut qualified_callable = qualified_owner;
5304 qualified_callable.push(member_name.to_string());
5305 let callable_name = qualified_callable.join("::");
5306 let free_function = self
5307 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
5308 .into_iter()
5309 .find(|candidate| {
5310 canonical_cpp_name_matches(candidate, &callable_name)
5311 && type_owner_of(analyzer, candidate).is_none()
5312 })
5313 .cloned();
5314
5315 match (resolved_type, free_function) {
5316 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
5317 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
5318 (None, Some(function)) => {
5319 return LexicalCallableValueResolution::FreeFunction(function);
5320 }
5321 (None, None) => {}
5322 }
5323 }
5324 LexicalCallableValueResolution::Missing
5325 }
5326
5327 fn resolve_type_for_declaration(
5328 &self,
5329 visible_from: &ProjectFile,
5330 declaration: &CodeUnit,
5331 raw_name: &str,
5332 ) -> Option<CodeUnit> {
5333 let normalized = normalize_reference_name(raw_name)?;
5334 if !normalized.contains("::")
5335 && let Some(namespace) = cpp_namespace_for(declaration)
5336 {
5337 for prefix in namespace_prefixes(&namespace) {
5338 let qualified = format!("{prefix}::{normalized}");
5339 if let Some(unit) = self
5340 .type_candidates(visible_from, &qualified)
5341 .into_iter()
5342 .next()
5343 {
5344 return Some(unit.clone());
5345 }
5346 }
5347 }
5348 self.resolve_type(visible_from, raw_name)
5349 }
5350
5351 fn resolve_unique_canonical_type_for_declaration(
5352 &self,
5353 analyzer: &CppGraphSource<'_>,
5354 visible_from: &ProjectFile,
5355 declaration: &CodeUnit,
5356 raw_name: &str,
5357 ) -> Option<CodeUnit> {
5358 let mut current =
5359 self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
5360 let mut seen_aliases = HashSet::default();
5361 loop {
5362 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
5363 return current.is_class().then_some(current);
5364 };
5365 if matches!(target, StructuredAliasTarget::Builtin) {
5366 return current.is_class().then_some(current);
5367 }
5368 if !seen_aliases.insert(current.clone()) {
5369 return None;
5370 }
5371 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
5372 }
5373 }
5374
5375 pub fn canonical_type_unit(
5376 &self,
5377 analyzer: &CppGraphSource<'_>,
5378 visible_from: &ProjectFile,
5379 unit: &CodeUnit,
5380 ) -> Option<CodeUnit> {
5381 self.canonical_type_resolution(analyzer, visible_from, unit)
5382 .ok()
5383 }
5384
5385 fn canonical_type_resolution(
5393 &self,
5394 analyzer: &CppGraphSource<'_>,
5395 visible_from: &ProjectFile,
5396 unit: &CodeUnit,
5397 ) -> Result<CodeUnit, TypeCandidateFailure> {
5398 let mut current = unit.clone();
5399 let mut seen_aliases = HashSet::default();
5400 loop {
5401 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
5402 return current
5403 .is_class()
5404 .then_some(current)
5405 .ok_or(TypeCandidateFailure::Unresolvable);
5406 };
5407 if matches!(target, StructuredAliasTarget::Builtin) {
5408 return current
5409 .is_class()
5410 .then_some(current)
5411 .ok_or(TypeCandidateFailure::Unresolvable);
5412 }
5413 if !seen_aliases.insert(current.clone()) {
5414 return Err(TypeCandidateFailure::Unresolvable);
5415 }
5416 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
5417 }
5418 }
5419
5420 pub fn canonical_visible_full_type_unit(
5421 &self,
5422 analyzer: &CppGraphSource<'_>,
5423 visible_from: &ProjectFile,
5424 unit: &CodeUnit,
5425 ) -> Option<CodeUnit> {
5426 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
5427 if cpp_class_declaration_strength(analyzer, &canonical)
5428 != CppClassDeclarationStrength::Forward
5429 {
5430 return Some(canonical);
5431 }
5432 let mut full = Vec::new();
5433 for candidate in self
5434 .visible_identifier_candidates(visible_from, canonical.identifier())
5435 .filter(|candidate| {
5436 candidate.is_class()
5437 && candidate.fq_name() == canonical.fq_name()
5438 && cpp_class_declaration_strength(analyzer, candidate)
5439 == CppClassDeclarationStrength::Full
5440 })
5441 {
5442 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
5443 full.push(candidate.clone());
5444 }
5445 }
5446 match full.len() {
5447 0 => Some(canonical),
5448 1 => full.pop(),
5449 _ => None,
5450 }
5451 }
5452
5453 fn resolve_structured_alias_target(
5454 &self,
5455 visible_from: &ProjectFile,
5456 declaration: &CodeUnit,
5457 target: &StructuredAliasTarget,
5458 ) -> Option<CodeUnit> {
5459 self.structured_alias_target_resolution(visible_from, declaration, target)
5460 .ok()
5461 }
5462
5463 fn structured_alias_target_resolution(
5464 &self,
5465 visible_from: &ProjectFile,
5466 declaration: &CodeUnit,
5467 target: &StructuredAliasTarget,
5468 ) -> Result<CodeUnit, TypeCandidateFailure> {
5469 let primary =
5470 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
5471 let StructuredAliasTarget::Named { arguments, .. } = target else {
5472 return Err(TypeCandidateFailure::Unresolvable);
5473 };
5474 match arguments {
5475 Some(arguments) => self
5476 .resolve_template_arguments(visible_from, primary, arguments)
5477 .map_err(|error| match error {
5478 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
5479 TypeCandidateFailure::Ambiguous
5480 }
5481 _ => TypeCandidateFailure::Unresolvable,
5482 }),
5483 None => Ok(primary),
5484 }
5485 }
5486
5487 fn resolve_structured_alias_primary(
5488 &self,
5489 visible_from: &ProjectFile,
5490 declaration: &CodeUnit,
5491 target: &StructuredAliasTarget,
5492 ) -> Option<CodeUnit> {
5493 self.structured_alias_primary_resolution(visible_from, declaration, target)
5494 .ok()
5495 }
5496
5497 fn structured_alias_primary_resolution(
5498 &self,
5499 visible_from: &ProjectFile,
5500 declaration: &CodeUnit,
5501 target: &StructuredAliasTarget,
5502 ) -> Result<CodeUnit, TypeCandidateFailure> {
5503 let StructuredAliasTarget::Named {
5504 components, global, ..
5505 } = target
5506 else {
5507 return Err(TypeCandidateFailure::Unresolvable);
5508 };
5509 let qualified = components.join("::");
5510 let candidates = if *global {
5511 let mut candidates = self.type_candidates(visible_from, &qualified);
5518 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
5519 candidates
5520 } else {
5521 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
5522 };
5523 logical_type_candidate(candidates)
5524 }
5525
5526 pub fn structured_alias_primary_preserves_target(
5527 &self,
5528 analyzer: &CppGraphSource<'_>,
5529 visible_from: &ProjectFile,
5530 candidate: &CodeUnit,
5531 target: &CodeUnit,
5532 ) -> bool {
5533 let mut current = candidate.clone();
5534 let mut seen = HashSet::default();
5535 let mut matched_target = false;
5536 loop {
5537 if same_visible_symbol(¤t, target)
5538 || self.compatible_primary_template_redeclarations(¤t, target)
5539 {
5540 matched_target = true;
5541 }
5542 if !seen.insert(current.clone()) {
5543 return false;
5544 }
5545 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
5546 return matched_target;
5547 };
5548 if matches!(alias_target, StructuredAliasTarget::Builtin) {
5549 return matched_target;
5550 };
5551 let Some(primary) =
5552 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
5553 else {
5554 return matched_target;
5560 };
5561 current = primary;
5562 }
5563 }
5564
5565 pub fn structured_class_alias_resolves_to_target(
5566 &self,
5567 analyzer: &CppGraphSource<'_>,
5568 visible_from: &ProjectFile,
5569 alias: &CodeUnit,
5570 target: &CodeUnit,
5571 ) -> bool {
5572 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
5573 return false;
5574 };
5575 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
5576 return false;
5577 };
5578 let StructuredAliasTarget::Named {
5579 components, global, ..
5580 } = &alias_target
5581 else {
5582 return false;
5583 };
5584 let lexical_scope = canonical_cpp_scope_components(&owner);
5585 match self.resolve_type_components_lexically_for_target(
5586 analyzer,
5587 visible_from,
5588 components,
5589 *global,
5590 &lexical_scope,
5591 target,
5592 ) {
5593 LexicalTypeResolution::Resolved {
5594 unit, candidates, ..
5595 } => {
5596 same_visible_symbol(&unit, target)
5597 || self.same_template_member_identity(analyzer, &unit, target)
5598 || candidates.iter().any(|candidate| {
5599 same_visible_symbol(candidate, target)
5600 || self.same_template_member_identity(analyzer, candidate, target)
5601 })
5602 }
5603 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
5604 self.structured_alias_primary_preserves_target(
5605 analyzer,
5606 visible_from,
5607 alias,
5608 target,
5609 ) || self.flattened_macro_namespace_alias_target_matches(
5610 analyzer,
5611 visible_from,
5612 alias,
5613 &alias_target,
5614 target,
5615 )
5616 }
5617 }
5618 }
5619
5620 pub fn structured_class_alias_path_preserves_target(
5628 &self,
5629 analyzer: &CppGraphSource<'_>,
5630 visible_from: &ProjectFile,
5631 alias: &CodeUnit,
5632 target: &CodeUnit,
5633 ) -> bool {
5634 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
5635 return false;
5636 };
5637 let Some(StructuredAliasTarget::Named {
5638 components, global, ..
5639 }) = self.structured_alias_target(analyzer, alias)
5640 else {
5641 return false;
5642 };
5643 let lexical_scope = canonical_cpp_scope_components(&owner);
5644 (1..components.len()).rev().any(|component_count| {
5645 matches!(
5646 self.resolve_type_components_lexically_for_target(
5647 analyzer,
5648 visible_from,
5649 &components[..component_count],
5650 global,
5651 &lexical_scope,
5652 target,
5653 ),
5654 LexicalTypeResolution::Resolved {
5655 ref unit,
5656 ref candidates,
5657 ..
5658 } if same_visible_symbol(unit, target)
5659 || self.same_template_member_identity(analyzer, unit, target)
5660 || candidates.iter().any(|candidate| {
5661 same_visible_symbol(candidate, target)
5662 || self.same_template_member_identity(analyzer, candidate, target)
5663 })
5664 )
5665 })
5666 }
5667
5668 fn flattened_macro_namespace_alias_target_matches(
5669 &self,
5670 analyzer: &CppGraphSource<'_>,
5671 visible_from: &ProjectFile,
5672 alias: &CodeUnit,
5673 alias_target: &StructuredAliasTarget,
5674 target: &CodeUnit,
5675 ) -> bool {
5676 let StructuredAliasTarget::Named {
5677 components,
5678 global: false,
5679 arguments: None,
5680 } = alias_target
5681 else {
5682 return false;
5683 };
5684 let Some((target_name, namespace_components)) = components.split_last() else {
5685 return false;
5686 };
5687 if namespace_components.is_empty()
5688 || target_name != target.identifier()
5689 || alias.source() != target.source()
5690 || alias.source() != visible_from
5691 || !target.is_class()
5692 || declared_type_alias(analyzer, target)
5693 {
5694 return false;
5695 }
5696 if self
5697 .resolve_structured_alias_target(visible_from, alias, alias_target)
5698 .is_some()
5699 {
5700 return false;
5701 }
5702
5703 let alias_ranges = analyzer.ranges(alias);
5704 let target_ranges = analyzer.ranges(target);
5705 if alias_ranges.is_empty() || target_ranges.is_empty() {
5706 return false;
5707 }
5708 let alias_start = alias_ranges
5709 .iter()
5710 .map(|range| range.start_byte)
5711 .min()
5712 .expect("non-empty alias ranges have a minimum");
5713 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
5714 return false;
5715 };
5716 let root = prepared.tree().root_node();
5717 let has_matching_declaration = target_ranges
5718 .iter()
5719 .filter(|range| range.end_byte <= alias_start)
5720 .filter_map(|range| node_for_exact_range(root, range))
5721 .any(|node| {
5722 flattened_macro_namespace_components(node, prepared.source())
5723 .is_some_and(|recovered| recovered == namespace_components)
5724 });
5725 if !has_matching_declaration {
5726 return false;
5727 }
5728
5729 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
5730 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
5731 guard_requirement_sets_match(&alias_guards, &target_guards)
5732 }
5733
5734 pub fn template_alias_arguments_preserve_target(
5735 &self,
5736 analyzer: &CppGraphSource<'_>,
5737 visible_from: &ProjectFile,
5738 alias: &CodeUnit,
5739 arguments: &[CppTemplateExpression],
5740 target: &CodeUnit,
5741 ) -> bool {
5742 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
5743 return false;
5744 };
5745 if metadata.alias_target.is_none()
5746 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
5747 {
5748 return false;
5749 }
5750 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
5751 }
5752
5753 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
5754 self.cpp_template_metadata
5755 .get(unit)
5756 .is_some_and(CppTemplateMetadata::is_primary)
5757 }
5758
5759 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
5760 self.cpp_template_metadata
5761 .get(unit)
5762 .is_some_and(CppTemplateMetadata::is_specialization)
5763 }
5764
5765 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
5766 same_visible_symbol(left, right)
5767 || self.compatible_primary_template_redeclarations(left, right)
5768 }
5769
5770 pub fn same_template_member_identity(
5771 &self,
5772 analyzer: &CppGraphSource<'_>,
5773 left: &CodeUnit,
5774 right: &CodeUnit,
5775 ) -> bool {
5776 if same_visible_symbol(left, right) {
5777 return true;
5778 }
5779 if left.kind() != right.kind()
5780 || left.identifier() != right.identifier()
5781 || left.signature() != right.signature()
5782 {
5783 return false;
5784 }
5785 let (Some(left_owner), Some(right_owner)) =
5786 (analyzer.parent_of(left), analyzer.parent_of(right))
5787 else {
5788 return false;
5789 };
5790 left_owner.is_class()
5791 && right_owner.is_class()
5792 && self.same_template_owner_identity(&left_owner, &right_owner)
5793 }
5794
5795 fn unique_canonical_type_candidate(
5796 &self,
5797 analyzer: &CppGraphSource<'_>,
5798 visible_from: &ProjectFile,
5799 candidates: &[&CodeUnit],
5800 ) -> Option<CodeUnit> {
5801 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
5802 .ok()
5803 }
5804
5805 fn canonical_type_candidate_resolution(
5806 &self,
5807 analyzer: &CppGraphSource<'_>,
5808 visible_from: &ProjectFile,
5809 candidates: &[&CodeUnit],
5810 ) -> Result<CodeUnit, TypeCandidateFailure> {
5811 let mut canonical = Vec::new();
5812 for candidate in candidates {
5813 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
5814 if canonical
5815 .iter()
5816 .any(|existing| same_visible_symbol(existing, &resolved))
5817 {
5818 continue;
5819 }
5820 if let Some(existing) = canonical.iter_mut().find(|existing| {
5821 self.compatible_primary_template_redeclarations(existing, &resolved)
5822 }) {
5823 if matches!(
5832 (
5833 cpp_class_declaration_strength(analyzer, existing),
5834 cpp_class_declaration_strength(analyzer, &resolved),
5835 ),
5836 (
5837 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
5838 CppClassDeclarationStrength::Full,
5839 ) | (
5840 CppClassDeclarationStrength::Unknown,
5841 CppClassDeclarationStrength::Forward,
5842 )
5843 ) {
5844 *existing = resolved;
5845 }
5846 continue;
5847 }
5848 canonical.push(resolved);
5849 if canonical.len() > 1 {
5850 return Err(TypeCandidateFailure::Ambiguous);
5851 }
5852 }
5853 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
5854 }
5855
5856 pub fn unique_type_candidate_preserving_target(
5857 &self,
5858 analyzer: &CppGraphSource<'_>,
5859 visible_from: &ProjectFile,
5860 candidates: &[&CodeUnit],
5861 target: &CodeUnit,
5862 ) -> Option<CodeUnit> {
5863 if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
5874 return Some(target.clone());
5875 }
5876 let mut resolved_candidates = Vec::new();
5877 for candidate in candidates {
5878 let Some(resolved) =
5884 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5885 else {
5886 continue;
5887 };
5888 if resolved_candidates
5889 .iter()
5890 .any(|existing| same_visible_symbol(existing, &resolved))
5891 {
5892 continue;
5893 }
5894 resolved_candidates.push(resolved);
5895 }
5896 match resolved_candidates.as_slice() {
5897 [] => None,
5898 [single] => Some(single.clone()),
5899 _ => self
5904 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
5905 .map(|_| target.clone()),
5906 }
5907 }
5908
5909 pub fn same_fqn_type_spelling_for_target<'b>(
5926 &self,
5927 analyzer: &CppGraphSource<'_>,
5928 visible_from: &ProjectFile,
5929 candidates: &[&'b CodeUnit],
5930 target: &CodeUnit,
5931 ) -> Option<&'b CodeUnit> {
5932 let [first, rest @ ..] = candidates else {
5933 return None;
5934 };
5935 if rest.is_empty()
5936 || !rest.iter().all(|candidate| {
5937 candidate.kind() == first.kind()
5938 && candidate.fq_name() == first.fq_name()
5939 && candidate.source() == first.source()
5940 })
5941 {
5942 return None;
5943 }
5944 candidates
5945 .iter()
5946 .copied()
5947 .find(|candidate| same_symbol(candidate, target))
5948 .or_else(|| {
5949 candidates.iter().copied().find(|candidate| {
5950 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5951 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
5952 })
5953 })
5954 }
5955
5956 pub fn alternate_same_fqn_type_declarations(
5957 &self,
5958 analyzer: &CppGraphSource<'_>,
5959 candidates: &[&CodeUnit],
5960 target: &CodeUnit,
5961 ) -> bool {
5962 let Some(first) = candidates.first() else {
5963 return false;
5964 };
5965 let same_api = first.kind() == target.kind()
5966 && first.fq_name() == target.fq_name()
5967 && first.source() == target.source()
5968 && candidates.iter().all(|candidate| {
5969 candidate.kind() == target.kind()
5970 && candidate.fq_name() == target.fq_name()
5971 && candidate.source() == target.source()
5972 })
5973 && candidates
5974 .iter()
5975 .any(|candidate| same_symbol(candidate, target))
5976 && candidates
5977 .iter()
5978 .any(|candidate| !same_logical_symbol(candidate, target));
5979 if !same_api {
5980 return false;
5981 }
5982
5983 let requirements = candidates
5984 .iter()
5985 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5986 .collect::<Vec<_>>();
5987 requirements.len() > 1
5988 && requirements
5989 .iter()
5990 .all(|requirement| !requirement.is_empty())
5991 && requirements.iter().enumerate().all(|(index, left)| {
5992 requirements[index + 1..].iter().all(|right| {
5993 left.iter().all(|(_, left_guards)| {
5994 right.iter().all(|(_, right_guards)| {
5995 merge_preprocessor_guards(left_guards, right_guards).is_none()
5996 })
5997 })
5998 })
5999 })
6000 }
6001
6002 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
6003 let mut pending = vec![terms.to_vec()];
6004 while let Some(branch_terms) = pending.pop() {
6005 let mut normalized = Vec::new();
6006 let mut covers_branch = false;
6007 for term in branch_terms {
6008 if term.iter().any(|guard| term.contains(&guard.negated())) {
6009 continue;
6010 }
6011 if term.is_empty() {
6012 covers_branch = true;
6013 break;
6014 }
6015 if !normalized.iter().any(|existing| existing == &term) {
6016 normalized.push(term);
6017 }
6018 }
6019 if covers_branch {
6020 continue;
6021 }
6022 let Some(split_guard) = normalized
6023 .iter()
6024 .flat_map(|term| term.iter())
6025 .next()
6026 .cloned()
6027 else {
6028 return false;
6029 };
6030 let negated_guard = split_guard.negated();
6031 let mut when_defined = Vec::new();
6032 let mut when_undefined = Vec::new();
6033 for term in normalized {
6034 if term.contains(&negated_guard) {
6035 } else if term.contains(&split_guard) {
6037 let mut reduced = term.clone();
6038 reduced.remove(&split_guard);
6039 when_defined.push(reduced);
6040 } else {
6041 when_defined.push(term.clone());
6042 }
6043 if term.contains(&split_guard) {
6044 } else if term.contains(&negated_guard) {
6046 let mut reduced = term;
6047 reduced.remove(&negated_guard);
6048 when_undefined.push(reduced);
6049 } else {
6050 when_undefined.push(term);
6051 }
6052 }
6053 pending.push(when_defined);
6054 pending.push(when_undefined);
6055 }
6056 true
6057 }
6058
6059 fn declarations_share_exhaustive_conditional_family(
6068 &self,
6069 analyzer: &CppGraphSource<'_>,
6070 candidates: &[&CodeUnit],
6071 ) -> Option<(usize, usize)> {
6072 let mut family_range = None;
6073 for candidate in candidates {
6074 let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
6075 let root = prepared.tree().root_node();
6076 let mut candidate_family = None;
6077 for range in analyzer.ranges(candidate) {
6078 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
6079 let family = preprocessor_conditional_family_for_declaration(node)?;
6080 let key = (family.start_byte(), family.end_byte());
6081 if candidate_family.is_some_and(|existing| existing != key) {
6082 return None;
6083 }
6084 candidate_family = Some(key);
6085 }
6086 let candidate_family = candidate_family?;
6087 if family_range.is_some_and(|existing| existing != candidate_family) {
6088 return None;
6089 }
6090 family_range = Some(candidate_family);
6091 }
6092 family_range
6093 }
6094
6095 pub fn complementary_same_fqn_type_declarations(
6096 &self,
6097 analyzer: &CppGraphSource<'_>,
6098 candidates: &[&CodeUnit],
6099 target: &CodeUnit,
6100 ) -> bool {
6101 if candidates.len() < 2
6102 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
6103 || self
6104 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
6105 .is_none()
6106 {
6107 return false;
6108 }
6109 Self::preprocessor_guard_terms_cover_all_paths(
6110 &self.declaration_family_guard_terms(analyzer, candidates),
6111 )
6112 }
6113
6114 fn declaration_family_guard_terms(
6115 &self,
6116 analyzer: &CppGraphSource<'_>,
6117 candidates: &[&CodeUnit],
6118 ) -> Vec<HashSet<PreprocessorGuard>> {
6119 candidates
6120 .iter()
6121 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
6122 .map(|(_, guards)| guards)
6123 .collect()
6124 }
6125
6126 fn exhaustive_guard_family_activation(
6142 &self,
6143 analyzer: &CppGraphSource<'_>,
6144 prepared: &PreparedSyntaxTree,
6145 candidate: &CodeUnit,
6146 reference: &CallableReferenceContext<'_>,
6147 ) -> Option<usize> {
6148 if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
6151 return None;
6152 }
6153 let family = self
6154 .visible_identifier_candidates(candidate.source(), candidate.identifier())
6155 .filter(|peer| {
6156 peer.kind() == candidate.kind()
6157 && peer.fq_name() == candidate.fq_name()
6158 && peer.source() == candidate.source()
6159 })
6160 .collect::<Vec<_>>();
6161 let (_, family_end) =
6162 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
6163 if !Self::preprocessor_guard_terms_cover_all_paths(
6164 &self.declaration_family_guard_terms(analyzer, &family),
6165 ) {
6166 return None;
6167 }
6168 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
6172 .iter()
6173 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
6174 {
6175 return None;
6176 }
6177 (first_declaration_byte(analyzer, candidate)?
6178 == family
6179 .iter()
6180 .filter_map(|peer| first_declaration_byte(analyzer, peer))
6181 .min()?)
6182 .then_some(family_end)
6183 }
6184
6185 fn type_candidate_preserving_target(
6186 &self,
6187 analyzer: &CppGraphSource<'_>,
6188 visible_from: &ProjectFile,
6189 candidate: &CodeUnit,
6190 target: &CodeUnit,
6191 ) -> Option<CodeUnit> {
6192 let mut current = candidate.clone();
6193 let mut matched_target = same_visible_symbol(¤t, target)
6194 || self.compatible_primary_template_redeclarations(¤t, target);
6195 let mut seen = HashSet::default();
6196 loop {
6197 if !seen.insert(current.clone()) {
6198 return None;
6199 }
6200 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
6201 return matched_target
6202 .then(|| target.clone())
6203 .or_else(|| current.is_class().then_some(current));
6204 };
6205 if self.flattened_macro_namespace_alias_target_matches(
6206 analyzer,
6207 visible_from,
6208 ¤t,
6209 &alias_target,
6210 target,
6211 ) {
6212 return Some(target.clone());
6213 }
6214 if matches!(alias_target, StructuredAliasTarget::Builtin) {
6215 return matched_target
6216 .then(|| target.clone())
6217 .or_else(|| current.is_class().then_some(current));
6218 }
6219 if !self.cpp_template_metadata.contains_key(¤t)
6227 && let Some(primary) =
6228 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
6229 && (same_visible_symbol(&primary, target)
6230 || self.compatible_primary_template_redeclarations(&primary, target))
6231 {
6232 return Some(target.clone());
6233 }
6234 if same_visible_symbol(¤t, target) {
6235 return Some(target.clone());
6236 }
6237 if self.cpp_template_metadata.contains_key(¤t) {
6238 return None;
6239 }
6240 let Some(next) =
6241 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
6242 else {
6243 return matched_target.then(|| target.clone());
6244 };
6245 current = next;
6246 matched_target |= same_visible_symbol(¤t, target)
6247 || self.compatible_primary_template_redeclarations(¤t, target);
6248 }
6249 }
6250
6251 fn compatible_primary_template_redeclarations(
6252 &self,
6253 left: &CodeUnit,
6254 right: &CodeUnit,
6255 ) -> bool {
6256 let (Some(left_metadata), Some(right_metadata)) = (
6257 self.cpp_template_metadata.get(left),
6258 self.cpp_template_metadata.get(right),
6259 ) else {
6260 return false;
6261 };
6262 left_metadata.primary_fq_name == right_metadata.primary_fq_name
6263 && left_metadata.is_primary()
6264 && right_metadata.is_primary()
6265 && cpp_reconcile_primary_template_parameters(
6266 &[(left, left_metadata), (right, right_metadata)],
6267 right,
6268 )
6269 .is_some()
6270 }
6271
6272 fn alias_candidate_may_preserve_target(
6273 &self,
6274 analyzer: &CppGraphSource<'_>,
6275 visible_from: &ProjectFile,
6276 candidate: &CodeUnit,
6277 target: &CodeUnit,
6278 ) -> bool {
6279 let mut current = candidate.clone();
6280 let mut seen = HashSet::default();
6281 loop {
6282 if same_visible_symbol(¤t, target)
6283 || self.compatible_primary_template_redeclarations(¤t, target)
6284 {
6285 return true;
6286 }
6287 if self.cpp_template_metadata.contains_key(¤t) {
6288 return true;
6289 }
6290 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
6291 return false;
6292 };
6293 let StructuredAliasTarget::Named {
6294 components,
6295 global,
6296 arguments,
6297 } = alias_target
6298 else {
6299 return false;
6300 };
6301 if arguments.is_some() || !seen.insert(current.clone()) {
6302 return true;
6303 }
6304 let qualified = components.join("::");
6305 let next = if global {
6306 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
6307 } else {
6308 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
6309 };
6310 let Some(next) = next else {
6311 return true;
6312 };
6313 current = next;
6314 }
6315 }
6316
6317 fn type_candidates_for_declaration<'b>(
6321 &'b self,
6322 visible_from: &ProjectFile,
6323 declaration: &CodeUnit,
6324 raw_name: &str,
6325 ) -> Vec<&'b CodeUnit> {
6326 let Some(normalized) = normalize_reference_name(raw_name) else {
6327 return Vec::new();
6328 };
6329 if let Some(namespace) = cpp_namespace_for(declaration) {
6330 for prefix in namespace_prefixes(&namespace) {
6331 let qualified = format!("{prefix}::{normalized}");
6332 let candidates = self.type_candidates(visible_from, &qualified);
6333 if !candidates.is_empty() {
6334 return candidates;
6335 }
6336 }
6337 }
6338 self.type_candidates(visible_from, &normalized)
6339 }
6340
6341 fn resolve_unique_type_for_declaration(
6342 &self,
6343 visible_from: &ProjectFile,
6344 declaration: &CodeUnit,
6345 raw_name: &str,
6346 ) -> Option<CodeUnit> {
6347 unique_logical_type_candidate(self.type_candidates_for_declaration(
6348 visible_from,
6349 declaration,
6350 raw_name,
6351 ))
6352 }
6353
6354 pub fn resolves_to_type(
6355 &self,
6356 analyzer: &CppGraphSource<'_>,
6357 file: &ProjectFile,
6358 raw_name: &str,
6359 target: &CodeUnit,
6360 ) -> bool {
6361 let Some(normalized) = normalize_reference_name(raw_name) else {
6362 return false;
6363 };
6364 let candidates = self.type_candidates(file, &normalized);
6365 if candidates.is_empty() {
6366 return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
6367 }
6368 let Some(resolved) =
6369 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
6370 else {
6371 return false;
6372 };
6373 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
6374 }
6375
6376 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
6377 let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
6378 let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
6379 match resolved.kind() {
6380 CodeUnitType::Class => Some(resolved),
6381 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
6382 _ => None,
6383 }
6384 }
6385
6386 pub fn same_logical_callable(
6401 &self,
6402 analyzer: &CppGraphSource<'_>,
6403 left: &CodeUnit,
6404 right: &CodeUnit,
6405 ) -> bool {
6406 if same_logical_symbol(left, right) {
6407 return true;
6408 }
6409 if left.kind() != right.kind()
6410 || !left.is_callable()
6411 || !right.is_callable()
6412 || left.fq_name() != right.fq_name()
6413 {
6414 return false;
6415 }
6416 if self.callable_is_template_declaration(analyzer, left)
6422 || self.callable_is_template_declaration(analyzer, right)
6423 {
6424 return false;
6425 }
6426 let (Some(left_comparable), Some(right_comparable)) = (
6427 self.callable_comparable(analyzer, left),
6428 self.callable_comparable(analyzer, right),
6429 ) else {
6430 return false;
6431 };
6432 if left_comparable.suffix != right_comparable.suffix
6437 || left_comparable.shapes.len() != right_comparable.shapes.len()
6438 {
6439 return false;
6440 }
6441 left_comparable
6442 .shapes
6443 .iter()
6444 .zip(right_comparable.shapes.iter())
6445 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
6446 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
6447 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
6448 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
6449 }
6450 _ => false,
6454 })
6455 }
6456
6457 fn comparable_shapes_agree(
6463 &self,
6464 analyzer: &CppGraphSource<'_>,
6465 left: &CppComparableParameter,
6466 right: &CppComparableParameter,
6467 ) -> bool {
6468 let mut stack = vec![(left.root(), right.root())];
6469 while let Some((left_index, right_index)) = stack.pop() {
6470 match (left.node(left_index), right.node(right_index)) {
6471 (
6472 CppComparableNode::Named {
6473 name: left_name,
6474 primitive: left_primitive,
6475 konst: left_konst,
6476 volatil: left_volatil,
6477 },
6478 CppComparableNode::Named {
6479 name: right_name,
6480 primitive: right_primitive,
6481 konst: right_konst,
6482 volatil: right_volatil,
6483 },
6484 ) => {
6485 if left_konst != right_konst
6486 || left_volatil != right_volatil
6487 || left_primitive != right_primitive
6488 || !self.comparable_names_agree(
6489 analyzer,
6490 left_name,
6491 right_name,
6492 *left_primitive,
6493 )
6494 {
6495 return false;
6496 }
6497 }
6498 (
6499 CppComparableNode::Pointer {
6500 inner: left_inner,
6501 konst: left_konst,
6502 volatil: left_volatil,
6503 },
6504 CppComparableNode::Pointer {
6505 inner: right_inner,
6506 konst: right_konst,
6507 volatil: right_volatil,
6508 },
6509 ) => {
6510 if left_konst != right_konst || left_volatil != right_volatil {
6511 return false;
6512 }
6513 stack.push((*left_inner, *right_inner));
6514 }
6515 (
6516 CppComparableNode::Reference { inner: left_inner },
6517 CppComparableNode::Reference { inner: right_inner },
6518 )
6519 | (
6520 CppComparableNode::Array { inner: left_inner },
6521 CppComparableNode::Array { inner: right_inner },
6522 ) => stack.push((*left_inner, *right_inner)),
6523 (
6524 CppComparableNode::Generic {
6525 base: left_base,
6526 arguments: left_arguments,
6527 },
6528 CppComparableNode::Generic {
6529 base: right_base,
6530 arguments: right_arguments,
6531 },
6532 ) => {
6533 if left_arguments.len() != right_arguments.len() {
6534 return false;
6535 }
6536 stack.push((*left_base, *right_base));
6537 stack.extend(
6538 left_arguments.iter().zip(right_arguments.iter()).map(
6539 |(left_argument, right_argument)| (*left_argument, *right_argument),
6540 ),
6541 );
6542 }
6543 _ => return false,
6544 }
6545 }
6546 true
6547 }
6548
6549 fn comparable_names_agree(
6559 &self,
6560 analyzer: &CppGraphSource<'_>,
6561 left: &StructuredTypeName,
6562 right: &StructuredTypeName,
6563 primitive: bool,
6564 ) -> bool {
6565 if primitive {
6566 return left.path() == right.path();
6567 }
6568 match (
6569 self.comparable_name_terminal(analyzer, left),
6570 self.comparable_name_terminal(analyzer, right),
6571 ) {
6572 (Some(left_terminal), Some(right_terminal)) => {
6573 same_logical_symbol(&left_terminal, &right_terminal)
6574 }
6575 (None, None) => {
6576 left.path() == right.path() && left.is_absolute() == right.is_absolute()
6577 }
6578 _ => false,
6579 }
6580 }
6581
6582 fn comparable_name_terminal(
6593 &self,
6594 analyzer: &CppGraphSource<'_>,
6595 name: &StructuredTypeName,
6596 ) -> Option<CodeUnit> {
6597 let mut current = self.comparable_name_declaration(analyzer, name)?;
6598 let mut visited = HashSet::default();
6599 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
6600 if !declared_type_alias(analyzer, ¤t) {
6607 return current.is_class().then_some(current);
6608 }
6609 if !visited.insert(current.clone()) {
6610 return None;
6611 }
6612 let signature = current.signature()?;
6613 if cpp_alias_declaration_adds_indirection(signature) {
6618 return None;
6619 }
6620 let raw_target = cpp_alias_declaration_target_text(signature)?;
6621 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
6622 }
6623 None
6624 }
6625
6626 fn comparable_alias_target(
6637 &self,
6638 analyzer: &CppGraphSource<'_>,
6639 alias: &CodeUnit,
6640 raw_target: &str,
6641 ) -> Option<CodeUnit> {
6642 let absolute = raw_target.trim_start().starts_with("::");
6647 let normalized = normalize_reference_name(raw_target)?;
6648 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6649 brokk_bifrost_core::analyzer::Language::Cpp,
6650 &normalized,
6651 );
6652 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
6653 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6654 brokk_bifrost_core::analyzer::Language::Cpp,
6655 &namespace,
6656 )
6657 });
6658 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
6659 self.comparable_name_declaration(analyzer, &name)
6660 }
6661
6662 fn comparable_name_declaration(
6670 &self,
6671 analyzer: &CppGraphSource<'_>,
6672 name: &StructuredTypeName,
6673 ) -> Option<CodeUnit> {
6674 let definitions = analyzer.workspace_definitions();
6675 let interner = segment_interner();
6676 let first_depth = if name.is_absolute() {
6677 0
6678 } else {
6679 name.lexical_scope().len()
6680 };
6681 for depth in (0..=first_depth).rev() {
6682 let mut structured = FqName::new();
6683 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
6684 structured.push(interner.intern(component, SegmentKind::Unknown));
6685 }
6686 let mut candidates = definitions
6687 .identifier(&structured)
6688 .into_iter()
6689 .filter(|unit| unit.fq().same_segment_texts(&structured))
6690 .filter(|unit| {
6691 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
6692 });
6693 let Some(first) = candidates.next() else {
6694 continue;
6695 };
6696 return candidates
6697 .all(|unit| same_logical_symbol(&unit, &first))
6698 .then_some(first);
6699 }
6700 None
6701 }
6702
6703 fn callable_comparable(
6709 &self,
6710 analyzer: &CppGraphSource<'_>,
6711 unit: &CodeUnit,
6712 ) -> Option<Arc<ExtractedComparable>> {
6713 if let Some(cached) = self
6714 .callable_comparables
6715 .lock()
6716 .expect("C++ callable comparable cache poisoned")
6717 .get(unit)
6718 .cloned()
6719 {
6720 return cached;
6721 }
6722 let extracted = self
6723 .extract_callable_comparable(analyzer, unit)
6724 .map(Arc::new);
6725 self.callable_comparables
6726 .lock()
6727 .expect("C++ callable comparable cache poisoned")
6728 .insert(unit.clone(), extracted.clone());
6729 extracted
6730 }
6731
6732 fn extract_callable_comparable(
6733 &self,
6734 analyzer: &CppGraphSource<'_>,
6735 unit: &CodeUnit,
6736 ) -> Option<ExtractedComparable> {
6737 let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
6738 let root = prepared.tree().root_node();
6739 let declarator = analyzer
6740 .ranges(unit)
6741 .into_iter()
6742 .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
6743 Some(ExtractedComparable {
6744 shapes: cpp_comparable_parameter_shapes(
6747 declarator,
6748 prepared.source(),
6749 &ParentIndex::unindexed(),
6750 ),
6751 suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
6752 })
6753 }
6754
6755 pub fn canonical_type_for_reference(
6756 &self,
6757 file: &ProjectFile,
6758 raw_name: &str,
6759 ) -> Option<CodeUnit> {
6760 let resolved = self.resolve_type(file, raw_name)?;
6761 self.alias_target(&resolved).or(Some(resolved))
6762 }
6763
6764 pub fn parser_alias_resolves_to_type(
6765 &self,
6766 analyzer: &CppGraphSource<'_>,
6767 file: &ProjectFile,
6768 raw_name: &str,
6769 target: &CodeUnit,
6770 ) -> bool {
6771 let Some(alias_name) = normalize_reference_name(raw_name) else {
6772 return false;
6773 };
6774 let Some(cpp) = analyzer.cpp else {
6775 return false;
6776 };
6777 let matches_file = |source_file: &ProjectFile| {
6778 self.file_alias_matches(cpp, source_file, &alias_name, target)
6779 };
6780 self.visible_source_files_by_root.get(file).map_or_else(
6781 || matches_file(file),
6782 |files| files.iter().any(matches_file),
6783 )
6784 }
6785
6786 fn file_alias_matches(
6787 &self,
6788 cpp: &dyn CppSource,
6789 file: &ProjectFile,
6790 alias_name: &str,
6791 target: &CodeUnit,
6792 ) -> bool {
6793 let cell = {
6794 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
6795 Arc::clone(
6796 cells
6797 .entry(file.clone())
6798 .or_insert_with(|| Arc::new(OnceLock::new())),
6799 )
6800 };
6801 cell.get_or_init(|| {
6802 self.parser_alias_source_parses
6803 .fetch_add(1, Ordering::Relaxed);
6804 #[cfg(any(test, feature = "test-support"))]
6805 {
6806 *self
6807 .alias_source_parse_counts
6808 .lock()
6809 .expect("alias source parse count lock")
6810 .entry(file.clone())
6811 .or_default() += 1;
6812 }
6813 aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
6814 })
6815 .iter()
6816 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
6817 }
6818
6819 #[cfg(any(test, feature = "test-support"))]
6820 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
6821 self.visible_source_files_by_root
6822 .get(file)
6823 .cloned()
6824 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
6825 }
6826
6827 #[cfg(any(test, feature = "test-support"))]
6828 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
6829 self.alias_source_parse_counts
6830 .lock()
6831 .expect("alias source parse count lock")
6832 .get(file)
6833 .copied()
6834 .unwrap_or(0)
6835 }
6836
6837 pub fn resolve_named(
6838 &self,
6839 file: &ProjectFile,
6840 raw_name: &str,
6841 kind: TargetKind,
6842 ) -> Option<CodeUnit> {
6843 let normalized = normalize_reference_name(raw_name)?;
6844 self.named_candidates_for_normalized(file, &normalized, kind)
6845 .into_iter()
6846 .next()
6847 .cloned()
6848 }
6849
6850 pub fn contains_named_symbol(
6851 &self,
6852 file: &ProjectFile,
6853 raw_name: &str,
6854 kind: TargetKind,
6855 target: &CodeUnit,
6856 ) -> bool {
6857 let Some(normalized) = normalize_reference_name(raw_name) else {
6858 return false;
6859 };
6860 self.named_candidates_for_normalized(file, &normalized, kind)
6861 .into_iter()
6862 .any(|unit| {
6863 matches_kind_for_lookup(unit, kind)
6864 && reference_matches_unit(&normalized, unit)
6865 && same_visible_symbol(unit, target)
6866 })
6867 }
6868
6869 pub fn named_candidates(
6870 &self,
6871 file: &ProjectFile,
6872 raw_name: &str,
6873 kind: TargetKind,
6874 ) -> Vec<CodeUnit> {
6875 let Some(normalized) = normalize_reference_name(raw_name) else {
6876 return Vec::new();
6877 };
6878 self.named_candidates_for_normalized(file, &normalized, kind)
6879 .into_iter()
6880 .cloned()
6881 .collect()
6882 }
6883
6884 pub fn resolve_known_non_target(
6885 &self,
6886 file: &ProjectFile,
6887 raw_name: &str,
6888 kind: TargetKind,
6889 target: &CodeUnit,
6890 ) -> bool {
6891 let Some(normalized) = normalize_reference_name(raw_name) else {
6892 return false;
6893 };
6894 normalized.contains("::")
6895 && self
6896 .named_candidates_for_normalized(file, &normalized, kind)
6897 .into_iter()
6898 .any(|unit| {
6899 matches_kind_for_lookup(unit, kind)
6900 && reference_matches_unit(&normalized, unit)
6901 && !same_visible_symbol(unit, target)
6902 })
6903 }
6904
6905 pub fn resolve_call_return_binding(
6906 &self,
6907 analyzer: &CppGraphSource<'_>,
6908 file: &ProjectFile,
6909 raw_name: &str,
6910 arity: usize,
6911 lexical_namespace: Option<&str>,
6912 direct_type: Option<&CodeUnit>,
6913 ) -> Option<CppScanBinding> {
6914 let normalized = normalize_reference_name(raw_name)?;
6915 let mut candidates = Vec::new();
6916 for function in
6917 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6918 {
6919 if cpp_callable_arity(analyzer, function).accepts(arity)
6920 && !direct_type.is_some_and(|direct_type| {
6921 self.callable_is_constructor_declaration(analyzer, function)
6922 && type_owner_of(analyzer, function)
6923 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6924 })
6925 {
6926 candidates.push(function.clone());
6927 }
6928 }
6929 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6930 unanimous_return_binding(analyzer, self, file, &candidates)
6931 }
6932
6933 pub fn resolve_call_return_binding_without_arity(
6934 &self,
6935 analyzer: &CppGraphSource<'_>,
6936 file: &ProjectFile,
6937 raw_name: &str,
6938 lexical_namespace: Option<&str>,
6939 direct_type: Option<&CodeUnit>,
6940 ) -> (bool, Option<CppScanBinding>) {
6941 let Some(normalized) = normalize_reference_name(raw_name) else {
6942 return (false, None);
6943 };
6944 let mut candidates = self
6945 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6946 .into_iter()
6947 .filter(|function| {
6948 function.is_function()
6949 && !direct_type.is_some_and(|direct_type| {
6950 self.callable_is_constructor_declaration(analyzer, function)
6951 && type_owner_of(analyzer, function)
6952 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6953 })
6954 })
6955 .cloned()
6956 .collect::<Vec<_>>();
6957 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6958 let has_candidates = !candidates.is_empty();
6959 (
6960 has_candidates,
6961 unanimous_return_binding(analyzer, self, file, &candidates),
6962 )
6963 }
6964
6965 pub fn visible_identifier_candidates<'b>(
6966 &'b self,
6967 file: &ProjectFile,
6968 identifier: &str,
6969 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
6970 self.visible_by_identifier
6971 .get(file)
6972 .and_then(|by_name| by_name.get(identifier))
6973 .into_iter()
6974 .flatten()
6975 }
6976
6977 pub fn visible_type_reference_component_names_for_target(
6985 &self,
6986 analyzer: &CppGraphSource<'_>,
6987 file: &ProjectFile,
6988 target: &CodeUnit,
6989 ) -> HashSet<String> {
6990 let mut names = HashSet::from_iter([target.identifier().to_string()]);
6991 if let Some(metadata) = self.cpp_template_metadata.get(target) {
6992 names.insert(metadata.primary_name.clone());
6993 }
6994
6995 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
6996 for (identifier, candidates) in by_identifier {
6997 if candidates.iter().any(|candidate| {
6998 (candidate.is_class()
6999 && (same_visible_symbol(candidate, target)
7000 || self.compatible_primary_template_redeclarations(candidate, target)))
7001 || (declared_type_alias(analyzer, candidate)
7002 && self.alias_candidate_may_preserve_target(
7003 analyzer, file, candidate, target,
7004 ))
7005 }) {
7006 names.insert(identifier.clone());
7007 }
7008 }
7009 }
7010
7011 names
7012 }
7013
7014 pub fn indexed_structural_class_scope(
7015 &self,
7016 file: &ProjectFile,
7017 class: Node<'_>,
7018 source: &str,
7019 ) -> Option<Vec<String>> {
7020 let key = (file.clone(), class.start_byte(), class.end_byte());
7021 if let Some(cached) = self
7022 .indexed_structural_class_scopes
7023 .lock()
7024 .expect("C++ indexed structural-class scope cache poisoned")
7025 .get(&key)
7026 .cloned()
7027 {
7028 return cached;
7029 }
7030 let resolved = (|| {
7031 let name = class.child_by_field_name("name")?;
7032 let identifier = if name.kind() == "template_type" {
7033 node_text(name.child_by_field_name("name")?, source).to_string()
7034 } else {
7035 let mut components = Vec::new();
7036 append_cpp_name_components(name, source, &mut components)?;
7037 components.last()?.clone()
7038 };
7039 let visible = self
7040 .visible_identifier_candidates(file, &identifier)
7041 .cloned()
7042 .collect::<Vec<_>>();
7043 let mut visible = visible;
7044 for candidate in
7045 self.visible_by_file
7046 .get(file)
7047 .into_iter()
7048 .flatten()
7049 .filter(|candidate| {
7050 self.cpp_template_metadata
7051 .get(candidate)
7052 .is_some_and(|metadata| metadata.primary_name == identifier)
7053 })
7054 {
7055 if !visible
7056 .iter()
7057 .any(|existing| same_logical_symbol(existing, candidate))
7058 {
7059 visible.push(candidate.clone());
7060 }
7061 }
7062 let cpp_source = self.cpp_source();
7065 let candidates = visible
7066 .iter()
7067 .filter(|candidate| {
7068 candidate.source() == file
7069 && candidate.is_class()
7070 && !declared_type_alias(&cpp_source, candidate)
7071 && self.cpp.ranges(candidate).iter().any(|range| {
7072 range.start_byte <= class.start_byte()
7073 && class.end_byte() <= range.end_byte
7074 })
7075 })
7076 .collect::<Vec<_>>();
7077 let owner = if name.kind() == "template_type" {
7078 let expected = normalize_cpp_whitespace(node_text(name, source));
7079 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
7080 let exact = candidates
7081 .iter()
7082 .copied()
7083 .filter(|candidate| {
7084 candidate
7085 .fq()
7086 .segments()
7087 .iter()
7088 .rev()
7089 .find_map(|&segment| {
7090 let (text, kind) = interner.resolve(segment);
7091 matches!(
7092 kind,
7093 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
7094 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
7095 )
7096 .then_some(text)
7097 })
7098 .is_some_and(|text| text == expected)
7099 })
7100 .collect::<Vec<_>>();
7101 unique_logical_type_candidate(exact)
7102 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
7103 } else {
7104 unique_logical_type_candidate(candidates)?
7105 };
7106 Some(canonical_cpp_scope_components(&owner))
7107 })();
7108 self.indexed_structural_class_scopes
7109 .lock()
7110 .expect("C++ indexed structural-class scope cache poisoned")
7111 .insert(key, resolved.clone());
7112 resolved
7113 }
7114
7115 pub fn indexed_enclosing_owner_scope(
7116 &self,
7117 analyzer: &CppGraphSource<'_>,
7118 file: &ProjectFile,
7119 node: Node<'_>,
7120 ) -> Option<Vec<String>> {
7121 let anchor = std::iter::successors(Some(node), |current| current.parent())
7122 .find(|current| {
7123 matches!(
7124 current.kind(),
7125 "function_definition"
7126 | "class_specifier"
7127 | "struct_specifier"
7128 | "union_specifier"
7129 )
7130 })
7131 .unwrap_or(node);
7132 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
7133 if let Some(cached) = self
7134 .indexed_enclosing_owner_scopes
7135 .lock()
7136 .expect("C++ indexed enclosing-owner scope cache poisoned")
7137 .get(&key)
7138 .cloned()
7139 {
7140 return cached;
7141 }
7142 let resolved = (|| {
7143 let range = Range {
7144 start_byte: node.start_byte(),
7145 end_byte: node.end_byte(),
7146 start_line: node.start_position().row,
7147 end_line: node.end_position().row,
7148 };
7149 let start = analyzer.enclosing_code_unit(file, &range)?;
7150 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
7151 start,
7152 |unit| self.cached_precise_parent_of(analyzer, unit),
7153 )
7154 .find(|unit| {
7155 unit.is_class()
7156 && !analyzer
7157 .type_alias_provider()
7158 .is_some_and(|provider| provider.is_type_alias(unit))
7159 })?;
7160 Some(canonical_cpp_scope_components(&owner))
7161 })();
7162 self.indexed_enclosing_owner_scopes
7163 .lock()
7164 .expect("C++ indexed enclosing-owner scope cache poisoned")
7165 .insert(key, resolved.clone());
7166 resolved
7167 }
7168
7169 fn cached_precise_parent_of(
7170 &self,
7171 analyzer: &CppGraphSource<'_>,
7172 code_unit: &CodeUnit,
7173 ) -> Option<CodeUnit> {
7174 if let Some(cached) = self
7175 .precise_parent_cache
7176 .lock()
7177 .expect("C++ precise-parent cache poisoned")
7178 .get(code_unit)
7179 .cloned()
7180 {
7181 return cached;
7182 }
7183 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
7184 self.precise_parent_cache
7185 .lock()
7186 .expect("C++ precise-parent cache poisoned")
7187 .insert(code_unit.clone(), resolved.clone());
7188 resolved
7189 }
7190
7191 pub fn callable_is_constructor_declaration(
7192 &self,
7193 analyzer: &CppGraphSource<'_>,
7194 candidate: &CodeUnit,
7195 ) -> bool {
7196 if !candidate.is_function() {
7197 return false;
7198 }
7199 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7200 return false;
7201 };
7202 let root = prepared.tree().root_node();
7203 let candidate_ranges = analyzer.ranges(candidate);
7204 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
7205 let mut current = root
7206 .descendant_for_byte_range(range.start_byte, range.end_byte)
7207 .and_then(|node| node.parent());
7208 while let Some(node) = current {
7209 if matches!(
7210 node.kind(),
7211 "class_specifier" | "struct_specifier" | "union_specifier"
7212 ) {
7213 return node
7214 .child_by_field_name("name")
7215 .map(|name| terminal_name(node_text(name, prepared.source())))
7216 .is_some_and(|name| name == candidate.identifier());
7217 }
7218 current = node.parent();
7219 }
7220 false
7221 });
7222 if enclosed_by_matching_type {
7223 return true;
7224 }
7225 let indexed_containment = analyzer
7226 .declarations(candidate.source())
7227 .into_iter()
7228 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
7229 .any(|owner| {
7230 analyzer.ranges(&owner).iter().any(|owner_range| {
7231 candidate_ranges.iter().any(|candidate_range| {
7232 owner_range.start_byte <= candidate_range.start_byte
7233 && candidate_range.end_byte <= owner_range.end_byte
7234 })
7235 })
7236 });
7237 if indexed_containment {
7238 return true;
7239 }
7240 let metadata = analyzer.signature_metadata(candidate);
7241 !metadata.is_empty()
7242 && metadata
7243 .iter()
7244 .all(|signature| signature.return_type_text().is_none())
7245 }
7246
7247 pub fn callable_is_deduction_guide_declaration(
7255 &self,
7256 analyzer: &CppGraphSource<'_>,
7257 candidate: &CodeUnit,
7258 ) -> bool {
7259 if !candidate.is_function() {
7260 return false;
7261 }
7262 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7263 return false;
7264 };
7265 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
7266 .into_iter()
7267 .any(|declaration| {
7268 if declaration.kind() != "declaration"
7269 || declaration.child_by_field_name("type").is_some()
7270 {
7271 return false;
7272 }
7273 let Some(declarator) = declaration.child_by_field_name("declarator") else {
7274 return false;
7275 };
7276 if declarator.kind() != "function_declarator" {
7277 return false;
7278 }
7279 let mut cursor = declarator.walk();
7280 let has_trailing_return = declarator
7281 .named_children(&mut cursor)
7282 .any(|child| child.kind() == "trailing_return_type");
7283 has_trailing_return
7284 && declarator_name_node(declarator).is_some_and(|name| {
7285 node_text(name, prepared.source()) == candidate.identifier()
7286 })
7287 })
7288 }
7289
7290 pub fn callable_is_template_declaration(
7294 &self,
7295 analyzer: &CppGraphSource<'_>,
7296 candidate: &CodeUnit,
7297 ) -> bool {
7298 if !candidate.is_function() {
7299 return false;
7300 }
7301 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7302 return false;
7303 };
7304 let root = prepared.tree().root_node();
7305 analyzer.ranges(candidate).iter().any(|range| {
7306 let Some(node) = node_for_exact_range(root, range)
7307 .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
7308 else {
7309 return false;
7310 };
7311 node.parent().is_some_and(|parent| {
7312 parent.kind() == "template_declaration"
7313 && parent
7314 .named_child(parent.named_child_count().saturating_sub(1))
7315 .is_some_and(|declaration| same_node(declaration, node))
7316 })
7317 })
7318 }
7319
7320 pub fn type_name_candidates<'b>(
7321 &'b self,
7322 file: &ProjectFile,
7323 normalized: &str,
7324 ) -> Vec<&'b CodeUnit> {
7325 self.candidate_units(file, normalized, TargetKind::Type)
7326 }
7327
7328 pub fn visible_members_for_owner_name<'b>(
7329 &'b self,
7330 file: &ProjectFile,
7331 owner: &CodeUnit,
7332 name: &str,
7333 ) -> Vec<&'b CodeUnit> {
7334 self.visible_identifier_candidates(file, name)
7335 .filter(|unit| {
7336 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
7340 .is_some_and(|parent| parent == owner.fq_name())
7341 })
7342 .collect()
7343 }
7344
7345 pub fn visible_member_for_owner_name(
7346 &self,
7347 file: &ProjectFile,
7348 owner: &CodeUnit,
7349 name: &str,
7350 ) -> VisibleMemberResolution {
7351 let candidates = self.visible_members_for_owner_name(file, owner, name);
7352 let mut callables = Vec::new();
7353 let mut non_callable = None;
7354 for candidate in candidates {
7355 if candidate.is_function() {
7356 callables.push(candidate.clone());
7357 } else if non_callable.is_none() {
7358 non_callable = Some(candidate.clone());
7359 }
7360 }
7361 match (callables.is_empty(), non_callable) {
7362 (false, None) => VisibleMemberResolution::Callable(callables),
7363 (true, Some(_)) => VisibleMemberResolution::NonCallable,
7364 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
7365 (true, None) => VisibleMemberResolution::Missing,
7366 }
7367 }
7368
7369 fn field_declared_type_fact(
7370 &self,
7371 analyzer: &CppGraphSource<'_>,
7372 field: &CodeUnit,
7373 ) -> Option<DeclaredFieldTypeFact> {
7374 if let Some(cached) = self
7375 .field_type_facts
7376 .lock()
7377 .expect("C++ field type fact cache poisoned")
7378 .get(field)
7379 .cloned()
7380 {
7381 return cached;
7382 }
7383 let decoded = decode_field_declared_type_fact(analyzer, field);
7384 self.field_type_facts
7385 .lock()
7386 .expect("C++ field type fact cache poisoned")
7387 .insert(field.clone(), decoded.clone());
7388 decoded
7389 }
7390
7391 fn structured_alias_target(
7392 &self,
7393 analyzer: &CppGraphSource<'_>,
7394 unit: &CodeUnit,
7395 ) -> Option<StructuredAliasTarget> {
7396 if let Some(cached) = self
7397 .structured_alias_targets
7398 .lock()
7399 .expect("C++ structured alias target cache poisoned")
7400 .get(unit)
7401 .cloned()
7402 {
7403 return cached;
7404 }
7405 let decoded = decode_structured_alias_target(analyzer, unit);
7406 self.structured_alias_targets
7407 .lock()
7408 .expect("C++ structured alias target cache poisoned")
7409 .insert(unit.clone(), decoded.clone());
7410 decoded
7411 }
7412
7413 pub fn type_candidates<'b>(
7414 &'b self,
7415 file: &ProjectFile,
7416 normalized: &str,
7417 ) -> Vec<&'b CodeUnit> {
7418 let mut candidates = self
7419 .candidate_units(file, normalized, TargetKind::Type)
7420 .into_iter()
7421 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
7422 .collect::<Vec<_>>();
7423 dedup_unit_refs(&mut candidates);
7424 candidates
7425 }
7426
7427 pub fn named_candidates_for_normalized<'b>(
7428 &'b self,
7429 file: &ProjectFile,
7430 normalized: &str,
7431 kind: TargetKind,
7432 ) -> Vec<&'b CodeUnit> {
7433 let mut candidates = self
7434 .candidate_units(file, normalized, kind)
7435 .into_iter()
7436 .filter(|unit| {
7437 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
7438 })
7439 .collect::<Vec<_>>();
7440 dedup_unit_refs(&mut candidates);
7441 candidates
7442 }
7443
7444 pub fn candidate_units<'b>(
7445 &'b self,
7446 file: &ProjectFile,
7447 normalized: &str,
7448 kind: TargetKind,
7449 ) -> Vec<&'b CodeUnit> {
7450 if normalized.contains("::") {
7451 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7460 brokk_bifrost_core::analyzer::Language::Cpp,
7461 normalized,
7462 )
7463 .pop() else {
7464 return Vec::new();
7465 };
7466 let fqns = cpp_reference_fqn_candidates(normalized, kind);
7467 return self
7468 .visible_identifier_candidates(file, &identifier)
7469 .filter(|unit| {
7470 #[cfg(any(test, feature = "test-support"))]
7471 self.qualified_candidate_inspections
7472 .fetch_add(1, Ordering::Relaxed);
7473 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
7474 || canonical_cpp_name_matches(unit, normalized)
7475 })
7476 .collect();
7477 }
7478 self.visible_identifier_candidates(file, normalized)
7479 .collect()
7480 }
7481
7482 #[cfg(any(test, feature = "test-support"))]
7483 pub fn reset_qualified_candidate_inspections(&self) {
7484 self.qualified_candidate_inspections
7485 .store(0, Ordering::Relaxed);
7486 }
7487
7488 #[cfg(any(test, feature = "test-support"))]
7489 pub fn qualified_candidate_inspections(&self) -> usize {
7490 self.qualified_candidate_inspections.load(Ordering::Relaxed)
7491 }
7492
7493 #[cfg(any(test, feature = "test-support"))]
7494 pub fn reset_target_preserving_type_resolution_count(&self) {
7495 self.target_preserving_type_resolution_count
7496 .store(0, Ordering::Relaxed);
7497 }
7498
7499 #[cfg(any(test, feature = "test-support"))]
7500 pub fn target_preserving_type_resolution_count(&self) -> usize {
7501 self.target_preserving_type_resolution_count
7502 .load(Ordering::Relaxed)
7503 }
7504
7505 #[cfg(any(test, feature = "test-support"))]
7506 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
7507 self.visible_parser_alias_name_set_build_count
7508 .load(Ordering::Relaxed)
7509 }
7510}
7511
7512#[derive(Default)]
7513struct IncludeGraph {
7514 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
7515}
7516
7517impl IncludeGraph {
7518 fn extend_with<F>(
7519 &mut self,
7520 root: &ProjectFile,
7521 cancellation: Option<&CancellationToken>,
7522 targets_for: &mut F,
7523 ) where
7524 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
7525 {
7526 let mut stack = vec![root.clone()];
7527 while let Some(file) = stack.pop() {
7528 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7529 break;
7530 }
7531 if self.targets_by_file.contains_key(&file) {
7532 continue;
7533 }
7534 let targets = targets_for(&file);
7535 stack.extend(targets.iter().cloned());
7536 self.targets_by_file.insert(file, targets);
7537 }
7538 }
7539
7540 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
7541 self.targets_by_file.keys()
7542 }
7543
7544 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
7545 self.targets_by_file
7546 .get(file)
7547 .map(Vec::as_slice)
7548 .unwrap_or_default()
7549 }
7550
7551 fn reachable_files(
7552 &self,
7553 root: &ProjectFile,
7554 cancellation: Option<&CancellationToken>,
7555 ) -> HashSet<ProjectFile> {
7556 let mut pending = vec![root.clone()];
7557 let mut visited = HashSet::default();
7558 while let Some(file) = pending.pop() {
7559 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7560 break;
7561 }
7562 if visited.insert(file.clone()) {
7563 pending.extend(self.targets(&file).iter().cloned());
7564 }
7565 }
7566 visited
7567 }
7568}
7569
7570fn build_bounded_visible_declarations(
7571 cpp: &dyn CppSource,
7572 token: QueryToken<'_>,
7573 analyzer: &CppGraphSource<'_>,
7574 roots: &HashSet<ProjectFile>,
7575 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
7576 cancellation: Option<&CancellationToken>,
7577 stats: &mut BoundedVisibilityStats,
7578) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
7579 roots
7580 .iter()
7581 .map(|root| {
7582 let reading_is_c = analyzer.reference_uses_c_semantics(root);
7583 let declarations_started = Instant::now();
7584 let root_declarations =
7585 bounded_visibility_declarations_in_reading(analyzer, root, reading_is_c);
7586 stats.declaration_elapsed += declarations_started.elapsed();
7587 stats.declaration_reads += 1;
7588 stats.declaration_units += root_declarations.len();
7589 let mut visible = root_declarations.into_iter().collect::<HashSet<_>>();
7590 let mut pending_names = HashSet::default();
7591 if let Some(prepared) = cpp.prepared_syntax(token, root) {
7592 let mut pending_nodes = vec![prepared.tree().root_node()];
7593 while let Some(node) = pending_nodes.pop() {
7594 if matches!(
7595 node.kind(),
7596 "identifier"
7597 | "type_identifier"
7598 | "field_identifier"
7599 | "namespace_identifier"
7600 ) {
7601 pending_names.insert(node_text(node, prepared.source()).to_string());
7602 }
7603 if node.kind() == "preproc_arg" {
7604 for reference in
7605 object_macro_replacement_type_references(node, prepared.source())
7606 {
7607 pending_names.extend(reference.components);
7608 }
7609 }
7610 for index in 0..node.named_child_count() {
7611 if let Some(child) = node.named_child(index) {
7612 pending_nodes.push(child);
7613 }
7614 }
7615 }
7616 }
7617 stats.root_names += pending_names.len();
7618 let mut completed_names = HashSet::default();
7619 while !pending_names.is_empty() {
7620 stats.rounds += 1;
7621 let round_names = std::mem::take(&mut pending_names);
7622 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
7623 HashMap::default();
7624 for identifier in round_names {
7625 if !completed_names.insert(identifier.clone())
7626 || cancellation.is_some_and(CancellationToken::is_cancelled)
7627 {
7628 continue;
7629 }
7630 let lookup_started = Instant::now();
7631 let candidates = cpp.visibility_identifier_candidates(&identifier);
7632 stats.lookup_elapsed += lookup_started.elapsed();
7633 stats.identifier_lookups += 1;
7634 stats.candidate_units += candidates.len();
7635 for source in candidates
7636 .into_iter()
7637 .map(|unit| unit.source().clone())
7638 .collect::<HashSet<_>>()
7639 {
7640 if source != *root
7641 && visible_sources
7642 .get(root)
7643 .is_some_and(|files| files.contains(&source))
7644 {
7645 requested_names_by_source
7646 .entry(source)
7647 .or_default()
7648 .insert(identifier.clone());
7649 }
7650 }
7651 }
7652 stats.candidate_sources += requested_names_by_source.len();
7653 for (source, requested_names) in requested_names_by_source {
7654 let declarations_started = Instant::now();
7655 let declarations =
7656 bounded_visibility_declarations_in_reading(analyzer, &source, reading_is_c);
7657 stats.declaration_elapsed += declarations_started.elapsed();
7658 stats.declaration_reads += 1;
7659 stats.declaration_units += declarations.len();
7660 for unit in declarations {
7661 let template_metadata = unit
7662 .is_class()
7663 .then(|| cpp.template_metadata(&unit))
7664 .flatten();
7665 if !requested_names.contains(unit.identifier())
7666 && !template_metadata.as_ref().is_some_and(|metadata| {
7667 requested_names.contains(&metadata.primary_name)
7668 })
7669 {
7670 continue;
7671 }
7672 stats.selected_units += 1;
7673 if let Some(prepared) = cpp.prepared_syntax(token, &source) {
7674 let ast_started = Instant::now();
7675 for range in analyzer.ranges(&unit) {
7676 let Some(declaration) =
7677 node_for_exact_range(prepared.tree().root_node(), &range)
7678 else {
7679 continue;
7680 };
7681 let mut pending_nodes = vec![declaration];
7682 while let Some(node) = pending_nodes.pop() {
7683 stats.dependency_ast_nodes += 1;
7684 if matches!(
7685 node.kind(),
7686 "type_identifier" | "namespace_identifier"
7687 ) {
7688 let name = node_text(node, prepared.source());
7689 if !completed_names.contains(name)
7690 && pending_names.insert(name.to_string())
7691 {
7692 stats.dependency_names += 1;
7693 }
7694 }
7695 for index in 0..node.named_child_count() {
7696 if let Some(child) = node.named_child(index) {
7697 pending_nodes.push(child);
7698 }
7699 }
7700 }
7701 }
7702 stats.dependency_ast_elapsed += ast_started.elapsed();
7703 }
7704 if let Some(metadata) = template_metadata
7705 && !completed_names.contains(&metadata.primary_name)
7706 {
7707 pending_names.insert(metadata.primary_name);
7708 }
7709 visible.insert(unit);
7710 }
7711 }
7712 }
7713 (root.clone(), visible)
7714 })
7715 .collect()
7716}
7717
7718#[derive(Default)]
7719struct BoundedVisibilityStats {
7720 rounds: usize,
7721 root_names: usize,
7722 identifier_lookups: usize,
7723 candidate_units: usize,
7724 candidate_sources: usize,
7725 declaration_reads: usize,
7726 declaration_units: usize,
7727 selected_units: usize,
7728 dependency_ast_nodes: usize,
7729 dependency_names: usize,
7730 lookup_elapsed: Duration,
7731 declaration_elapsed: Duration,
7732 dependency_ast_elapsed: Duration,
7733}
7734
7735fn bounded_visibility_declarations_in_reading(
7736 analyzer: &CppGraphSource<'_>,
7737 file: &ProjectFile,
7738 c_semantics: bool,
7739) -> BTreeSet<CodeUnit> {
7740 #[cfg(any(test, feature = "test-support"))]
7741 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
7742 analyzer.declarations_in_reading(file, c_semantics)
7743}
7744
7745#[cfg(any(test, feature = "test-support"))]
7746pub fn reset_bounded_visibility_declaration_read_count_for_test() {
7747 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
7748}
7749
7750#[cfg(any(test, feature = "test-support"))]
7751pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
7752 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
7753}
7754
7755pub struct VisibilityData {
7756 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
7757 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
7758}
7759
7760pub fn build_visibility_data<F, R, D>(
7770 roots: &HashSet<ProjectFile>,
7771 cancellation: Option<&CancellationToken>,
7772 mut targets_for: F,
7773 mut reading_is_c_for: R,
7774 mut declarations_for: D,
7775) -> VisibilityData
7776where
7777 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
7778 R: FnMut(&ProjectFile) -> bool,
7779 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
7780{
7781 let mut include_graph = IncludeGraph::default();
7782 for file in roots {
7783 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7784 break;
7785 }
7786 include_graph.extend_with(file, cancellation, &mut targets_for);
7787 }
7788 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
7789 .files()
7790 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
7791 .map(|file| (file.clone(), declarations_for(file, false)))
7792 .collect();
7793 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
7794 let mut visible_by_file = HashMap::default();
7795 let mut visible_source_files_by_root = HashMap::default();
7796 for file in roots {
7797 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7798 break;
7799 }
7800 let mut visited = HashSet::default();
7801 let mut visible = HashSet::default();
7802 let declarations_by_file = if reading_is_c_for(file) {
7803 for reached in cpp_declarations_by_file.keys() {
7804 if !c_declarations_by_file.contains_key(reached) {
7805 let declarations = declarations_for(reached, true);
7806 c_declarations_by_file.insert(reached.clone(), declarations);
7807 }
7808 }
7809 &c_declarations_by_file
7810 } else {
7811 &cpp_declarations_by_file
7812 };
7813 collect_visible_declarations(
7814 &include_graph,
7815 declarations_by_file,
7816 file,
7817 &mut visited,
7818 &mut visible,
7819 cancellation,
7820 );
7821 visible_by_file.insert(file.clone(), visible);
7822 visible_source_files_by_root.insert(file.clone(), visited);
7823 }
7824 VisibilityData {
7825 visible_by_file,
7826 visible_source_files_by_root,
7827 }
7828}
7829
7830#[derive(Default)]
7849struct OutOfLineOwnerBindingStats {
7850 unseen_owners: usize,
7851 definition_lookups: usize,
7852 admitted: usize,
7853}
7854
7855fn extend_with_out_of_line_owner_bindings(
7856 cpp: &dyn CppSource,
7857 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
7858) -> OutOfLineOwnerBindingStats {
7859 let mut stats = OutOfLineOwnerBindingStats::default();
7860 for (file, visible) in visible_by_file.iter_mut() {
7861 let mut unseen_owners: HashSet<String> = visible
7865 .iter()
7866 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
7867 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
7868 .collect();
7869 if unseen_owners.is_empty() {
7870 continue;
7871 }
7872 for unit in visible.iter().filter(|unit| unit.is_class()) {
7873 unseen_owners.remove(&unit.fq_name());
7874 }
7875 stats.unseen_owners += unseen_owners.len();
7876 stats.definition_lookups += unseen_owners.len();
7877 let admitted = unseen_owners
7878 .iter()
7879 .flat_map(|owner| cpp.definitions(owner))
7880 .filter(CodeUnit::is_class)
7881 .collect::<Vec<_>>();
7882 stats.admitted += admitted.len();
7883 visible.extend(admitted);
7884 }
7885 stats
7886}
7887
7888pub enum VisibleMemberResolution {
7889 Callable(Vec<CodeUnit>),
7890 NonCallable,
7891 AmbiguousKind,
7892 Missing,
7893}
7894
7895#[derive(Clone)]
7896pub enum EnclosingMemberOwnerResolution {
7897 Owner(CodeUnit),
7898 Ambiguous,
7899 Missing,
7900}
7901
7902pub fn resolve_declaring_member_owner(
7903 analyzer: &CppGraphSource<'_>,
7904 visibility: &VisibilityIndex<'_>,
7905 file: &ProjectFile,
7906 receiver_owner: &CodeUnit,
7907 member_name: &str,
7908) -> EnclosingMemberOwnerResolution {
7909 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7910 return EnclosingMemberOwnerResolution::Missing;
7911 };
7912 let Some(receiver_owner) =
7913 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
7914 else {
7915 return EnclosingMemberOwnerResolution::Ambiguous;
7916 };
7917 let resolve_level = |frontier: &[CodeUnit]| {
7918 let mut member_owners = Vec::new();
7919 for raw_owner in frontier {
7920 let Some(owner) =
7921 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
7922 else {
7923 return EnclosingMemberOwnerResolution::Ambiguous;
7924 };
7925 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
7926 let Some(member_owner) = type_owner_of(analyzer, member) else {
7927 return EnclosingMemberOwnerResolution::Ambiguous;
7928 };
7929 if !member_owners
7930 .iter()
7931 .any(|existing| same_visible_symbol(existing, &member_owner))
7932 {
7933 member_owners.push(member_owner);
7934 }
7935 }
7936 }
7937 match member_owners.len() {
7938 0 => EnclosingMemberOwnerResolution::Missing,
7939 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
7940 _ => EnclosingMemberOwnerResolution::Ambiguous,
7941 }
7942 };
7943 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
7947 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
7948 return direct;
7949 }
7950 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
7951 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7952 let mut path_matches = Vec::new();
7953 while let Some(raw_owner) = stack.pop() {
7954 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7955 else {
7956 return EnclosingMemberOwnerResolution::Ambiguous;
7957 };
7958 let propagated = propagated_counts.entry(owner.clone()).or_default();
7962 if *propagated == 2 {
7963 continue;
7964 }
7965 *propagated += 1;
7966 match resolve_level(std::slice::from_ref(&owner)) {
7967 EnclosingMemberOwnerResolution::Owner(owner) => {
7968 path_matches.push(owner);
7969 if path_matches.len() == 2 {
7970 return EnclosingMemberOwnerResolution::Ambiguous;
7971 }
7972 }
7973 EnclosingMemberOwnerResolution::Ambiguous => {
7974 return EnclosingMemberOwnerResolution::Ambiguous;
7975 }
7976 EnclosingMemberOwnerResolution::Missing => {
7977 stack.extend(hierarchy.get_direct_ancestors(&owner));
7978 }
7979 }
7980 }
7981 match path_matches.len() {
7982 0 => EnclosingMemberOwnerResolution::Missing,
7983 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
7984 _ => unreachable!("base-path matches are capped at one before returning"),
7985 }
7986}
7987
7988pub fn resolve_declaring_callable_owner(
8003 analyzer: &CppGraphSource<'_>,
8004 visibility: &VisibilityIndex<'_>,
8005 file: &ProjectFile,
8006 ordinary: EnclosingMemberOwnerResolution,
8007 member_name: &str,
8008 call_arity: usize,
8009) -> EnclosingMemberOwnerResolution {
8010 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
8011 return ordinary;
8012 };
8013 if visibility
8014 .visible_members_for_owner_name(file, ordinary_owner, member_name)
8015 .into_iter()
8016 .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
8017 {
8018 return ordinary;
8019 }
8020
8021 let mut pending = match member_using_declaration_bases(
8022 analyzer,
8023 visibility,
8024 file,
8025 ordinary_owner,
8026 member_name,
8027 ) {
8028 Ok(bases) => bases,
8029 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8030 };
8031 let mut visited = HashSet::default();
8032 let mut introduced_owners = Vec::new();
8033 while let Some(owner) = pending.pop() {
8034 if !visited.insert(owner.clone()) {
8035 continue;
8036 }
8037 let accepts_arity = visibility
8038 .visible_members_for_owner_name(file, &owner, member_name)
8039 .into_iter()
8040 .any(|unit| {
8041 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
8042 });
8043 if accepts_arity {
8044 if !introduced_owners
8045 .iter()
8046 .any(|existing| same_visible_symbol(existing, &owner))
8047 {
8048 introduced_owners.push(owner);
8049 }
8050 continue;
8051 }
8052 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
8053 Ok(bases) => pending.extend(bases),
8054 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8055 }
8056 }
8057 match introduced_owners.as_slice() {
8058 [] => ordinary,
8059 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
8060 _ => EnclosingMemberOwnerResolution::Ambiguous,
8061 }
8062}
8063
8064fn member_using_declaration_bases(
8065 analyzer: &CppGraphSource<'_>,
8066 visibility: &VisibilityIndex<'_>,
8067 file: &ProjectFile,
8068 owner: &CodeUnit,
8069 member_name: &str,
8070) -> Result<Vec<CodeUnit>, ()> {
8071 let Some(source) = analyzer.get_source(owner, false) else {
8072 return Ok(Vec::new());
8073 };
8074 let scopes = cpp_member_using_declaration_scopes(&source, member_name);
8075 if scopes.is_empty() {
8076 return Ok(Vec::new());
8077 }
8078 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8079 return Ok(Vec::new());
8080 };
8081 let mut bases = Vec::new();
8082 for raw_ancestor in hierarchy.get_ancestors(owner) {
8083 let Some(ancestor) =
8084 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
8085 else {
8086 return Err(());
8087 };
8088 let qualified = cpp_name_for(&ancestor);
8089 if scopes
8090 .iter()
8091 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
8092 && !bases
8093 .iter()
8094 .any(|existing| same_visible_symbol(existing, &ancestor))
8095 {
8096 bases.push(ancestor);
8097 }
8098 }
8099 Ok(bases)
8100}
8101
8102pub fn lexical_component_tiers<'a>(
8103 components: &'a [String],
8104 global: bool,
8105 lexical_scope: &'a [String],
8106) -> impl Iterator<Item = Vec<String>> + 'a {
8107 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
8108 (0..=first_prefix_len).rev().map(move |prefix_len| {
8109 let mut qualified = Vec::with_capacity(prefix_len + components.len());
8110 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
8111 qualified.extend_from_slice(components);
8112 qualified
8113 })
8114}
8115
8116pub fn build_visible_identifier_index(
8117 analyzer: &CppGraphSource<'_>,
8118 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
8119 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
8120 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
8121) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
8122 let mut out = HashMap::default();
8123 for (file, visible) in visible_by_file {
8124 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
8125 for unit in visible {
8126 if unit.is_field()
8127 && !visible_source_files_by_root
8128 .get(file)
8129 .is_some_and(|sources| sources.contains(unit.source()))
8130 && cpp_global_field_has_internal_linkage_cached(
8131 analyzer,
8132 global_field_internal_linkage,
8133 unit,
8134 )
8135 {
8136 continue;
8137 }
8138 by_identifier
8139 .entry(unit.identifier().to_string())
8140 .or_default()
8141 .push(unit.clone());
8142 }
8143 for units in by_identifier.values_mut() {
8144 sort_lookup_units(units);
8145 units.dedup();
8146 }
8147 out.insert(file.clone(), by_identifier);
8148 }
8149 out
8150}
8151
8152fn sort_lookup_units(units: &mut [CodeUnit]) {
8153 units.sort_by(|left, right| {
8154 left.fq_name()
8155 .cmp(&right.fq_name())
8156 .then_with(|| left.signature().cmp(&right.signature()))
8157 .then_with(|| left.source().cmp(right.source()))
8158 .then_with(|| left.kind().cmp(&right.kind()))
8159 .then_with(|| {
8160 left.package_segment_count()
8161 .cmp(&right.package_segment_count())
8162 })
8163 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
8164 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
8165 });
8166}
8167
8168fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
8169 let interner = segment_interner();
8170 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
8171 let (left_text, left_kind) = interner.resolve(left_id);
8172 let (right_text, right_kind) = interner.resolve(right_id);
8173 let order = left_text
8174 .cmp(right_text)
8175 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
8176 if order != CmpOrdering::Equal {
8177 return order;
8178 }
8179 }
8180 left.len().cmp(&right.len())
8181}
8182
8183const fn segment_kind_order(kind: SegmentKind) -> u8 {
8184 match kind {
8185 SegmentKind::Path => 0,
8186 SegmentKind::Package => 1,
8187 SegmentKind::Type => 2,
8188 SegmentKind::Companion => 3,
8189 SegmentKind::Nested => 4,
8190 SegmentKind::Member => 5,
8191 SegmentKind::Unknown => 6,
8192 }
8193}
8194
8195fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
8196 let mut deduped = Vec::with_capacity(units.len());
8197 for unit in units.drain(..) {
8198 if !deduped.contains(&unit) {
8199 deduped.push(unit);
8200 }
8201 }
8202 *units = deduped;
8203}
8204
8205pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
8206 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8210 brokk_bifrost_core::analyzer::Language::Cpp,
8211 reference,
8212 );
8213 if parts.is_empty() {
8214 return Vec::new();
8215 }
8216
8217 let mut candidates = Vec::new();
8218 for package_len in 0..parts.len() {
8219 let package = parts[..package_len].join("::");
8220 let rest = &parts[package_len..];
8221 if rest.is_empty() {
8222 continue;
8223 }
8224 match kind {
8225 TargetKind::Type | TargetKind::Constructor => {
8226 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
8227 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
8228 }
8229 TargetKind::FreeFunction
8230 | TargetKind::Method
8231 | TargetKind::GlobalField
8232 | TargetKind::MemberField
8233 | TargetKind::Macro => {
8234 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
8235 if rest.len() > 1 {
8236 let owner = rest[..rest.len() - 1].join("$");
8237 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
8238 push_cpp_fqn_candidate(&mut candidates, &package, &short);
8239 }
8240 }
8241 }
8242 }
8243 candidates
8244}
8245
8246fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
8247 let fqn = if package.is_empty() {
8248 short.to_string()
8249 } else {
8250 format!("{package}.{short}")
8251 };
8252 if !out.contains(&fqn) {
8253 out.push(fqn);
8254 }
8255}
8256
8257pub fn infer_cpp_initializer_type(
8258 analyzer: &CppGraphSource<'_>,
8259 visibility: &VisibilityIndex<'_>,
8260 file: &ProjectFile,
8261 source: &str,
8262 node: Node<'_>,
8263) -> Option<CodeUnit> {
8264 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
8265 .and_then(|binding| binding.unit)
8266}
8267
8268pub fn infer_cpp_initializer_binding(
8269 analyzer: &CppGraphSource<'_>,
8270 visibility: &VisibilityIndex<'_>,
8271 file: &ProjectFile,
8272 source: &str,
8273 node: Node<'_>,
8274 receiver_resolver: Option<&ReceiverResolver<'_>>,
8275) -> Option<CppScanBinding> {
8276 match node.kind() {
8277 "new_expression" => {
8278 let text = normalize_cpp_whitespace(node_text(node, source));
8279 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
8280 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
8281 let name = normalize_cpp_type_name(type_text);
8282 Some(CppScanBinding::from_type_name(
8283 name.clone(),
8284 visibility.resolve_type(file, &name),
8285 1,
8286 ))
8287 }
8288 "call_expression" => node.child_by_field_name("function").and_then(|function| {
8289 let function_text = node_text(function, source);
8290 let direct_type_binding = visibility
8291 .resolve_type(file, function_text)
8292 .map(|unit| CppScanBinding::from_unit(unit, 0));
8293 if function.kind() == "template_function" && direct_type_binding.is_some() {
8294 let lexical_namespace = enclosing_namespace_context(node, source);
8295 let arity = visibility.call_arity_evidence(file, node, source).exact();
8296 if let Some(arity) = arity
8297 && let Some(binding) = visibility.resolve_call_return_binding(
8298 analyzer,
8299 file,
8300 function_text,
8301 arity,
8302 lexical_namespace.as_deref(),
8303 direct_type_binding
8304 .as_ref()
8305 .and_then(|binding| binding.unit.as_ref()),
8306 )
8307 {
8308 return Some(binding);
8309 }
8310 let (has_callable, callable_binding) = visibility
8311 .resolve_call_return_binding_without_arity(
8312 analyzer,
8313 file,
8314 function_text,
8315 lexical_namespace.as_deref(),
8316 direct_type_binding
8317 .as_ref()
8318 .and_then(|binding| binding.unit.as_ref()),
8319 );
8320 if let Some(binding) = callable_binding {
8321 return Some(binding);
8322 }
8323 if has_callable {
8324 return None;
8325 }
8326 return direct_type_binding;
8327 }
8328 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
8329 let direct_type_binding_for_call = direct_type_binding.clone();
8330 resolve_static_method_call_return_binding(
8331 analyzer, visibility, file, source, function, arity,
8332 )
8333 .or_else(|| {
8334 visibility.resolve_call_return_binding(
8339 analyzer,
8340 file,
8341 function_text,
8342 arity,
8343 enclosing_namespace_context(node, source).as_deref(),
8344 direct_type_binding_for_call
8345 .as_ref()
8346 .and_then(|binding| binding.unit.as_ref()),
8347 )
8348 })
8349 .or(direct_type_binding)
8350 .or_else(|| {
8351 resolve_field_method_call_return_binding(
8352 analyzer,
8353 visibility,
8354 file,
8355 source,
8356 function,
8357 arity,
8358 receiver_resolver,
8359 )
8360 })
8361 }),
8362 _ => None,
8363 }
8364}
8365
8366fn resolve_static_method_call_return_binding(
8367 analyzer: &CppGraphSource<'_>,
8368 visibility: &VisibilityIndex<'_>,
8369 file: &ProjectFile,
8370 source: &str,
8371 function: Node<'_>,
8372 arity: usize,
8373) -> Option<CppScanBinding> {
8374 if function.kind() != "qualified_identifier" {
8375 return None;
8376 }
8377 let qualified = normalize_cpp_reference_text(node_text(function, source));
8378 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8385 brokk_bifrost_core::analyzer::Language::Cpp,
8386 &qualified,
8387 );
8388 let (owner_text, member_name) = match parts.split_last() {
8389 Some((member, owner_parts)) if !owner_parts.is_empty() => {
8390 (owner_parts.join("::"), member.clone())
8391 }
8392 _ => {
8393 let scope = function.child_by_field_name("scope")?;
8394 let name = function.child_by_field_name("name")?;
8395 (
8396 node_text(scope, source).to_string(),
8397 node_text(name, source).to_string(),
8398 )
8399 }
8400 };
8401 let owner = visibility.resolve_type(file, &owner_text)?;
8402 let candidates = visibility
8403 .visible_members_for_owner_name(file, &owner, &member_name)
8404 .into_iter()
8405 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
8406 .cloned()
8407 .collect::<Vec<_>>();
8408 unanimous_return_binding(analyzer, visibility, file, &candidates)
8409}
8410
8411fn resolve_field_method_call_return_binding(
8412 analyzer: &CppGraphSource<'_>,
8413 visibility: &VisibilityIndex<'_>,
8414 file: &ProjectFile,
8415 source: &str,
8416 function: Node<'_>,
8417 arity: usize,
8418 receiver_resolver: Option<&ReceiverResolver<'_>>,
8419) -> Option<CppScanBinding> {
8420 if function.kind() != "field_expression" {
8421 return None;
8422 }
8423 let receiver_resolver = receiver_resolver?;
8424 let field = function.child_by_field_name("field")?;
8425 let member_name = node_text(function_terminal_node(field), source);
8426 let receiver = function
8427 .child_by_field_name("argument")
8428 .or_else(|| function.named_child(0))?;
8429 let owners = receiver_resolver(receiver, source);
8430 let mut candidates = Vec::new();
8431 for owner in owners {
8432 let declaring_owner =
8433 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
8434 EnclosingMemberOwnerResolution::Owner(owner) => owner,
8435 EnclosingMemberOwnerResolution::Missing => continue,
8436 EnclosingMemberOwnerResolution::Ambiguous => return None,
8437 };
8438 candidates.extend(
8439 visibility
8440 .visible_members_for_owner_name(file, &declaring_owner, member_name)
8441 .into_iter()
8442 .filter(|unit| {
8443 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
8444 })
8445 .cloned(),
8446 );
8447 }
8448 unanimous_return_binding(analyzer, visibility, file, &candidates)
8449}
8450
8451fn unanimous_return_binding(
8452 analyzer: &CppGraphSource<'_>,
8453 visibility: &VisibilityIndex<'_>,
8454 file: &ProjectFile,
8455 candidates: &[CodeUnit],
8456) -> Option<CppScanBinding> {
8457 let mut resolved_return: Option<CppScanBinding> = None;
8458 for function in candidates {
8459 let metadata = analyzer.signature_metadata(function);
8460 let return_types = if metadata.is_empty() {
8461 vec![cpp_function_return_type_text(analyzer, function)?]
8462 } else {
8463 metadata
8464 .iter()
8465 .map(|metadata| metadata.return_type_text().map(str::to_string))
8466 .collect::<Option<Vec<_>>>()?
8467 };
8468 for return_text in return_types {
8469 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
8470 let name = normalize_cpp_type_name(&return_text);
8471 let binding = CppScanBinding::from_type_name(
8472 name.clone(),
8473 visibility
8474 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
8475 indirection,
8476 );
8477 if let Some(existing) = resolved_return.as_ref()
8478 && (existing.indirection != binding.indirection
8479 || match (&existing.unit, &binding.unit) {
8480 (Some(left), Some(right)) => !same_visible_symbol(left, right),
8481 (None, None) => existing.type_name != binding.type_name,
8482 (Some(_), None) | (None, Some(_)) => true,
8483 })
8484 {
8485 return None;
8486 }
8487 resolved_return = Some(binding);
8488 }
8489 }
8490 resolved_return
8491}
8492
8493fn aliases_from_prepared_source(
8494 cpp: &dyn CppSource,
8495 token: QueryToken<'_>,
8496 file: &ProjectFile,
8497) -> Vec<CppAlias> {
8498 let Some(prepared) = cpp.prepared_syntax(token, file) else {
8499 return Vec::new();
8500 };
8501 let mut aliases = Vec::new();
8502 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
8503 aliases
8504}
8505
8506fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
8507 let mut stack = vec![root];
8508 while let Some(node) = stack.pop() {
8509 match node.kind() {
8510 "alias_declaration" if alias_has_visible_file_scope(node) => {
8511 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
8512 out.push(alias);
8513 }
8514 }
8515 "type_definition" if alias_has_visible_file_scope(node) => {
8516 collect_typedef_aliases(node, source, out)
8517 }
8518 _ => {}
8519 }
8520
8521 for index in (0..node.named_child_count()).rev() {
8522 if let Some(child) = node.named_child(index) {
8523 stack.push(child);
8524 }
8525 }
8526 }
8527}
8528
8529fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
8530 let mut current = node.parent();
8531 while let Some(parent) = current {
8532 match parent.kind() {
8533 "translation_unit"
8534 | "namespace_definition"
8535 | "declaration_list"
8536 | "linkage_specification" => current = parent.parent(),
8537 "template_declaration" => current = parent.parent(),
8538 _ => return false,
8539 }
8540 }
8541 true
8542}
8543
8544fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
8545 let name = node
8546 .child_by_field_name("name")
8547 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
8548 let target = node
8549 .child_by_field_name("type")
8550 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
8551 Some(CppAlias {
8552 name,
8553 target,
8554 namespace: enclosing_namespace_context(node, source),
8555 })
8556}
8557
8558fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
8559 let Some(type_node) = node.child_by_field_name("type") else {
8560 return;
8561 };
8562 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
8563 return;
8564 };
8565
8566 let mut cursor = node.walk();
8567 for child in node.named_children(&mut cursor) {
8568 if same_node(child, type_node) {
8569 continue;
8570 }
8571 if let Some(name) = extract_typedef_declarator_name(child, source) {
8572 out.push(CppAlias {
8573 name,
8574 target: target.clone(),
8575 namespace: enclosing_namespace_context(node, source),
8576 });
8577 }
8578 }
8579}
8580
8581fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
8582 match node.kind() {
8583 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
8584 normalize_reference_name(node_text(node, source))
8585 }
8586 _ => node
8587 .child_by_field_name("declarator")
8588 .or_else(|| node.child_by_field_name("name"))
8589 .or_else(|| last_named_child(node))
8590 .and_then(|child| extract_typedef_declarator_name(child, source)),
8591 }
8592}
8593
8594fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
8595 let count = node.named_child_count();
8596 if count == 0 {
8597 None
8598 } else {
8599 node.named_child(count - 1)
8600 }
8601}
8602
8603pub fn collect_include_closure(
8604 analyzer: &CppGraphSource<'_>,
8605 include_targets: &IncludeTargetIndex,
8606 file: &ProjectFile,
8607 out: &mut HashSet<ProjectFile>,
8608 cancellation: Option<&CancellationToken>,
8609) {
8610 let mut stack = vec![file.clone()];
8611 while let Some(file) = stack.pop() {
8612 if cancellation.is_some_and(CancellationToken::is_cancelled) {
8613 break;
8614 }
8615 if !out.insert(file.clone()) {
8616 continue;
8617 }
8618 let imports = analyzer.import_statements(&file);
8619 for include in cpp_include_paths(&imports) {
8620 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
8621 stack.push(target);
8622 }
8623 }
8624 }
8625}
8626
8627fn collect_visible_declarations(
8628 include_graph: &IncludeGraph,
8629 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
8630 file: &ProjectFile,
8631 visited: &mut HashSet<ProjectFile>,
8632 out: &mut HashSet<CodeUnit>,
8633 cancellation: Option<&CancellationToken>,
8634) {
8635 let mut stack = vec![file.clone()];
8636 while let Some(file) = stack.pop() {
8637 if cancellation.is_some_and(CancellationToken::is_cancelled) {
8638 break;
8639 }
8640 if !visited.insert(file.clone()) {
8641 continue;
8642 }
8643 if let Some(declarations) = declarations_by_file.get(&file) {
8644 out.extend(declarations.iter().cloned());
8645 }
8646 stack.extend(include_graph.targets(&file).iter().cloned());
8647 }
8648}
8649
8650pub fn signature_arity(signature: Option<&str>) -> usize {
8651 let Some(signature) = signature else {
8652 return 0;
8653 };
8654 let inner = signature
8655 .find('(')
8656 .and_then(|open| {
8657 signature[open + 1..]
8658 .find(')')
8659 .map(|close| &signature[open + 1..open + 1 + close])
8660 })
8661 .unwrap_or(signature)
8662 .trim();
8663 if inner.is_empty() || inner == "void" {
8664 return 0;
8665 }
8666 cpp_split_top_level_commas(inner).count()
8667}
8668
8669fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
8670 let source = format!("void __bifrost_macro_parameters({replacement});");
8671 let mut parser = Parser::new();
8672 parser
8673 .set_language(&tree_sitter_cpp::LANGUAGE.into())
8674 .ok()?;
8675 let tree = parser.parse(&source, None)?;
8676 let root = tree.root_node();
8677 if root.has_error() {
8678 return None;
8679 }
8680 let declaration = root.named_child(0)?;
8681 let declarator = declaration.child_by_field_name("declarator")?;
8682 let parameters = declarator.child_by_field_name("parameters")?;
8683 let mut required = 0;
8684 let mut total = 0;
8685 let mut repeated = false;
8686 let mut cursor = parameters.walk();
8687 for parameter in parameters.children(&mut cursor) {
8688 match parameter.kind() {
8689 "parameter_declaration" => {
8690 if parameter.child_by_field_name("declarator").is_none()
8691 && parameter
8692 .child_by_field_name("type")
8693 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
8694 {
8695 continue;
8696 }
8697 required += 1;
8698 total += 1;
8699 }
8700 "optional_parameter_declaration" => total += 1,
8701 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
8702 repeated = true;
8703 }
8704 _ => {}
8705 }
8706 }
8707 Some(CallableArity::new(required, total, repeated))
8708}
8709
8710pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
8711 analyzer
8712 .signature_metadata(unit)
8713 .into_iter()
8714 .find_map(|metadata| metadata.callable_arity())
8715 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
8716}
8717
8718pub fn cpp_callable_parameter_types(
8719 analyzer: &CppGraphSource<'_>,
8720 unit: &CodeUnit,
8721) -> Option<Vec<String>> {
8722 analyzer
8723 .signature_metadata(unit)
8724 .into_iter()
8725 .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
8726 .or_else(|| unit.signature().and_then(cpp_signature_param_types))
8727}
8728
8729fn merge_compatible_callable_arities(
8730 left: CallableArity,
8731 right: CallableArity,
8732) -> Option<CallableArity> {
8733 let total = left.total();
8734 let left_repeated = left.accepts(total.saturating_add(1));
8735 let right_repeated = right.accepts(right.total().saturating_add(1));
8736 if total != right.total() || left_repeated != right_repeated {
8737 return None;
8738 }
8739 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
8740 Some(CallableArity::new(required, total, left_repeated))
8741}
8742
8743fn find_include_activation(
8744 cpp: &dyn CppSource,
8745 token: QueryToken<'_>,
8746 file: &ProjectFile,
8747 prepared: &PreparedSyntaxTree,
8748 donor_source: &ProjectFile,
8749) -> Option<usize> {
8750 let include_targets = cpp.include_target_index();
8751 let mut direct_includes = Vec::new();
8752 let mut nodes = vec![prepared.tree().root_node()];
8753 let reference = CallableReferenceContext {
8756 file,
8757 position: None,
8758 };
8759 while let Some(node) = nodes.pop() {
8760 if node.kind() == "preproc_include" {
8761 if callable_preprocessor_context_is_visible_for_reference(
8762 node,
8763 prepared.source(),
8764 &reference,
8765 ) {
8766 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8767 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8768 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
8769 file,
8770 &include,
8771 include_targets,
8772 )) {
8773 direct_includes.push((node.end_byte(), target));
8774 }
8775 }
8776 }
8777 continue;
8778 }
8779 for index in (0..node.named_child_count()).rev() {
8780 if let Some(child) = node.named_child(index) {
8781 nodes.push(child);
8782 }
8783 }
8784 }
8785 direct_includes.sort_by_key(|(activation, _)| *activation);
8786 let mut known_missing = HashSet::default();
8787 direct_includes
8788 .into_iter()
8789 .find(|(_, direct)| {
8790 unconditional_include_reaches(
8791 cpp,
8792 token,
8793 include_targets,
8794 direct,
8795 donor_source,
8796 file,
8797 &mut known_missing,
8798 )
8799 })
8800 .map(|(activation, _)| activation)
8801}
8802
8803fn find_conditional_include_projection_index(
8804 cpp: &dyn CppSource,
8805 token: QueryToken<'_>,
8806 file: &ProjectFile,
8807 prepared: &PreparedSyntaxTree,
8808 on_state: &dyn Fn(),
8809) -> ConditionalIncludeProjectionIndex {
8810 let include_targets = cpp.include_target_index();
8811 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
8812 HashMap::default();
8813 let mut pending = Vec::new();
8814 let mut nodes = vec![prepared.tree().root_node()];
8815 while let Some(node) = nodes.pop() {
8816 if node.kind() == "preproc_include" {
8817 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
8818 else {
8819 continue;
8820 };
8821 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8822 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8823 let Some(target) = unique_include_target(resolve_include_targets_with_index(
8824 file,
8825 &include,
8826 include_targets,
8827 )) else {
8828 continue;
8829 };
8830 pending.push((target, node.end_byte(), required_guards.clone()));
8831 }
8832 continue;
8833 }
8834 for index in (0..node.named_child_count()).rev() {
8835 if let Some(child) = node.named_child(index) {
8836 nodes.push(child);
8837 }
8838 }
8839 }
8840
8841 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
8853 HashMap::default();
8854 while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
8855 let guard_sets = expanded
8856 .entry((current_file.clone(), activation_byte))
8857 .or_default();
8858 if guard_sets
8859 .iter()
8860 .any(|existing| existing.is_subset(&required_guards))
8861 {
8862 continue;
8863 }
8864 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
8865 .drain(..)
8866 .partition(|existing| required_guards.is_subset(existing));
8867 *guard_sets = kept;
8868 guard_sets.push(required_guards.clone());
8869 if !evicted.is_empty()
8870 && let Some(projections) = projections_by_source.get_mut(¤t_file)
8871 {
8872 projections.retain(|projection| {
8873 projection.activation_byte != activation_byte
8874 || !evicted.contains(&projection.required_guards)
8875 });
8876 }
8877 on_state();
8878
8879 projections_by_source
8882 .entry(current_file.clone())
8883 .or_default()
8884 .push(ConditionalIncludeProjection {
8885 activation_byte,
8886 required_guards: required_guards.clone(),
8887 });
8888
8889 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
8890 continue;
8891 };
8892 let mut nodes = vec![current_prepared.tree().root_node()];
8893 while let Some(node) = nodes.pop() {
8894 if node.kind() == "preproc_include" {
8895 let Some(include_guards) =
8896 preprocessor_guard_environment(node, current_prepared.source())
8897 else {
8898 continue;
8899 };
8900 let Some(path_guards) =
8901 merge_preprocessor_guards(&required_guards, &include_guards)
8902 else {
8903 continue;
8904 };
8905 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
8906 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8907 let Some(target) = unique_include_target(resolve_include_targets_with_index(
8908 ¤t_file,
8909 &include,
8910 include_targets,
8911 )) else {
8912 continue;
8913 };
8914 pending.push((target, activation_byte, path_guards.clone()));
8915 }
8916 continue;
8917 }
8918 for index in (0..node.named_child_count()).rev() {
8919 if let Some(child) = node.named_child(index) {
8920 nodes.push(child);
8921 }
8922 }
8923 }
8924 }
8925
8926 projections_by_source
8927 .into_iter()
8928 .map(|(source, mut projections)| {
8929 projections.sort_by_key(|projection| projection.activation_byte);
8930 (source, Arc::from(projections))
8931 })
8932 .collect()
8933}
8934
8935#[allow(clippy::too_many_arguments)]
8940fn find_conditional_include_projection_for_source(
8941 cpp: &dyn CppSource,
8942 token: QueryToken<'_>,
8943 file: &ProjectFile,
8944 prepared: &PreparedSyntaxTree,
8945 donor_source: &ProjectFile,
8946 reference_guards: Option<&HashSet<PreprocessorGuard>>,
8947 reference_byte: usize,
8948 on_state: &dyn Fn(),
8949) -> bool {
8950 let Some(reference_guards) = reference_guards else {
8951 return false;
8952 };
8953 let include_targets = cpp.include_target_index();
8954 let mut pending = Vec::new();
8955 let mut nodes = vec![prepared.tree().root_node()];
8956 while let Some(node) = nodes.pop() {
8957 if node.kind() == "preproc_include" {
8958 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
8959 else {
8960 continue;
8961 };
8962 if node.end_byte() > reference_byte
8963 || !guard_requirements_hold_at_reference(&required_guards, Some(reference_guards))
8964 {
8965 continue;
8966 }
8967 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8968 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8969 let Some(target) = unique_include_target(resolve_include_targets_with_index(
8970 file,
8971 &include,
8972 include_targets,
8973 )) else {
8974 continue;
8975 };
8976 if &target == donor_source {
8977 return true;
8978 }
8979 pending.push((target, required_guards.clone()));
8980 }
8981 continue;
8982 }
8983 for index in (0..node.named_child_count()).rev() {
8984 if let Some(child) = node.named_child(index) {
8985 nodes.push(child);
8986 }
8987 }
8988 }
8989
8990 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
8991 while let Some((current_file, required_guards)) = pending.pop() {
8992 let guard_sets = expanded.entry(current_file.clone()).or_default();
8993 if guard_sets.contains(&required_guards) {
8994 continue;
8995 }
8996 guard_sets.push(required_guards.clone());
8997 on_state();
8998
8999 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
9000 continue;
9001 };
9002 let mut nodes = vec![current_prepared.tree().root_node()];
9003 while let Some(node) = nodes.pop() {
9004 if node.kind() == "preproc_include" {
9005 let Some(include_guards) =
9006 preprocessor_guard_environment(node, current_prepared.source())
9007 else {
9008 continue;
9009 };
9010 let Some(path_guards) =
9011 merge_preprocessor_guards(&required_guards, &include_guards)
9012 else {
9013 continue;
9014 };
9015 if !guard_requirements_hold_at_reference(&path_guards, Some(reference_guards)) {
9016 continue;
9017 }
9018 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9019 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9020 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9021 ¤t_file,
9022 &include,
9023 include_targets,
9024 )) else {
9025 continue;
9026 };
9027 if &target == donor_source {
9028 return true;
9029 }
9030 pending.push((target, path_guards.clone()));
9031 }
9032 continue;
9033 }
9034 for index in (0..node.named_child_count()).rev() {
9035 if let Some(child) = node.named_child(index) {
9036 nodes.push(child);
9037 }
9038 }
9039 }
9040 }
9041 false
9042}
9043
9044fn unconditional_include_reaches(
9045 cpp: &dyn CppSource,
9046 token: QueryToken<'_>,
9047 include_targets: &IncludeTargetIndex,
9048 first: &ProjectFile,
9049 donor_source: &ProjectFile,
9050 reference_file: &ProjectFile,
9051 known_missing: &mut HashSet<ProjectFile>,
9052) -> bool {
9053 if first == donor_source {
9054 return true;
9055 }
9056 if known_missing.contains(first) {
9057 return false;
9058 }
9059 let reference_is_c = reference_file
9060 .rel_path()
9061 .extension()
9062 .and_then(|extension| extension.to_str())
9063 == Some("c");
9064 if let Some(reaches) =
9065 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
9066 {
9067 return reaches;
9068 }
9069 let mut visited = HashSet::default();
9070 let mut files = vec![first.clone()];
9071 let reference = CallableReferenceContext {
9074 file: reference_file,
9075 position: None,
9076 };
9077 while let Some(file) = files.pop() {
9078 if file == *donor_source {
9079 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
9080 return true;
9081 }
9082 if known_missing.contains(&file) || !visited.insert(file.clone()) {
9083 continue;
9084 }
9085 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
9086 continue;
9087 };
9088 let mut nodes = vec![prepared.tree().root_node()];
9089 while let Some(node) = nodes.pop() {
9090 if node.kind() == "preproc_include" {
9091 if callable_preprocessor_context_is_visible_for_reference(
9092 node,
9093 prepared.source(),
9094 &reference,
9095 ) {
9096 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9097 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9098 if let Some(target) = unique_include_target(
9099 resolve_include_targets_with_index(&file, &include, include_targets),
9100 ) {
9101 files.push(target);
9102 }
9103 }
9104 }
9105 continue;
9106 }
9107 for index in (0..node.named_child_count()).rev() {
9108 if let Some(child) = node.named_child(index) {
9109 nodes.push(child);
9110 }
9111 }
9112 }
9113 }
9114 known_missing.extend(visited);
9115 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
9116 false
9117}
9118
9119fn declaration_guard_requirements(
9120 analyzer: &CppGraphSource<'_>,
9121 cpp: &dyn CppSource,
9122 candidate: &CodeUnit,
9123) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
9124 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
9125 return Vec::new();
9126 };
9127 let root = prepared.tree().root_node();
9128 analyzer
9129 .ranges(candidate)
9130 .into_iter()
9131 .filter_map(|range| {
9132 root.descendant_for_byte_range(range.start_byte, range.end_byte)
9133 .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
9134 .map(|required| (range.start_byte, required))
9138 })
9139 .collect()
9140}
9141
9142fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
9143 analyzer
9144 .ranges(candidate)
9145 .into_iter()
9146 .map(|range| range.start_byte)
9147 .min()
9148}
9149
9150fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
9156 let (first, rest) = contexts.split_first()?;
9157 Some(
9158 first
9159 .defined_macros
9160 .iter()
9161 .filter(|name| {
9162 rest.iter()
9163 .all(|context| context.defined_macros.contains(*name))
9164 })
9165 .cloned()
9166 .collect(),
9167 )
9168}
9169
9170fn guard_requirements_hold_at_reference(
9171 required: &HashSet<PreprocessorGuard>,
9172 reference: Option<&HashSet<PreprocessorGuard>>,
9173) -> bool {
9174 reference.is_some_and(|active| {
9175 required
9176 .iter()
9177 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
9178 })
9179}
9180
9181fn preprocessor_guard_holds_at_reference(
9182 required: &PreprocessorGuard,
9183 active: &HashSet<PreprocessorGuard>,
9184) -> bool {
9185 if active.contains(required) {
9186 return true;
9187 }
9188 let active_expression = BooleanGuardExpression::all(
9189 active
9190 .iter()
9191 .filter_map(PreprocessorGuard::as_boolean_expression),
9192 );
9193 required
9194 .as_boolean_expression()
9195 .is_some_and(|required| active_expression.implies(&required))
9196}
9197
9198fn guards_compatible_at_reference(
9203 declaration: &HashSet<PreprocessorGuard>,
9204 reference: Option<&HashSet<PreprocessorGuard>>,
9205) -> bool {
9206 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
9207}
9208
9209pub fn preprocessor_conditional_family_range(
9218 root: Node<'_>,
9219 start_byte: usize,
9220 end_byte: usize,
9221) -> Option<(usize, usize)> {
9222 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
9223 let mut ancestor = Some(node);
9224 while let Some(current) = ancestor {
9225 if is_preprocessor_conditional(current)
9226 && preprocessor_conditional_contains_descendant(current, node)
9227 {
9228 let family = preprocessor_conditional_family_root(current);
9229 return Some((family.start_byte(), family.end_byte()));
9230 }
9231 ancestor = current.parent();
9232 }
9233 None
9234}
9235
9236fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
9237 let mut ancestor = node.parent();
9238 while let Some(current) = ancestor {
9239 if is_preprocessor_conditional(current)
9240 && preprocessor_conditional_contains_descendant(current, node)
9241 {
9242 let family = preprocessor_conditional_family_root(current);
9243 if preprocessor_conditional_family_has_terminal_else(family) {
9244 return Some(family);
9245 }
9246 }
9247 ancestor = current.parent();
9248 }
9249 None
9250}
9251
9252fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
9253 while let Some(parent) = conditional.parent() {
9254 let is_alternative = parent
9255 .child_by_field_name("alternative")
9256 .is_some_and(|alternative| {
9257 alternative.start_byte() == conditional.start_byte()
9258 && alternative.end_byte() == conditional.end_byte()
9259 });
9260 if !is_alternative {
9261 break;
9262 }
9263 conditional = parent;
9264 }
9265 conditional
9266}
9267
9268fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
9269 loop {
9270 let Some(alternative) = conditional.child_by_field_name("alternative") else {
9271 return false;
9272 };
9273 match alternative.kind() {
9274 "preproc_else" => return true,
9275 "preproc_elif" => conditional = alternative,
9276 _ => return false,
9277 }
9278 }
9279}
9280
9281pub fn preprocessor_guard_environment(
9282 node: Node<'_>,
9283 source: &str,
9284) -> Option<HashSet<PreprocessorGuard>> {
9285 let mut guards = HashSet::default();
9286 let mut ancestor = node.parent();
9287 while let Some(conditional) = ancestor {
9288 if matches!(
9289 conditional.kind(),
9290 "preproc_if" | "preproc_ifdef" | "preproc_elif"
9291 ) && !is_file_covering_include_guard(conditional, source)
9292 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
9293 && preprocessor_conditional_contains_descendant(conditional, node)
9294 {
9295 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
9296 match guard {
9297 PreprocessorGuard::Constant(true) => {
9298 ancestor = conditional.parent();
9299 continue;
9300 }
9301 PreprocessorGuard::Constant(false) => return None,
9302 _ => {}
9303 }
9304 if guards.contains(&guard.negated()) {
9305 return None;
9306 }
9307 guards.insert(guard);
9308 }
9309 ancestor = conditional.parent();
9310 }
9311 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
9312 match guard {
9313 PreprocessorGuard::Constant(true) => {}
9314 PreprocessorGuard::Constant(false) => return None,
9315 _ => {
9316 if guards.contains(&guard.negated()) {
9317 return None;
9318 }
9319 guards.insert(guard);
9320 }
9321 }
9322 }
9323 Some(guards)
9324}
9325
9326fn fragmented_statement_preprocessor_guard(
9327 descendant: Node<'_>,
9328 source: &str,
9329) -> Option<PreprocessorGuard> {
9330 let mut ancestor = descendant.parent();
9336 while let Some(statement) = ancestor {
9337 if statement.kind() == "if_statement"
9338 && let (Some(consequence), Some(alternative)) = (
9339 statement.child_by_field_name("consequence"),
9340 statement.child_by_field_name("alternative"),
9341 )
9342 && alternative.start_byte() <= descendant.start_byte()
9343 && descendant.end_byte() <= alternative.end_byte()
9344 {
9345 let mut cursor = consequence.walk();
9346 let openers = consequence
9347 .named_children(&mut cursor)
9348 .filter(|child| {
9349 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
9350 && child
9351 .child(child.child_count().saturating_sub(1))
9352 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
9353 })
9354 .collect::<Vec<_>>();
9355 if openers.len() != 1 {
9356 ancestor = statement.parent();
9357 continue;
9358 }
9359
9360 let mut terminators = Vec::new();
9361 let mut stack = vec![alternative];
9362 while let Some(node) = stack.pop() {
9363 if node.kind() == "preproc_call"
9364 && node.start_byte() >= descendant.end_byte()
9365 && node
9366 .child_by_field_name("directive")
9367 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
9368 {
9369 terminators.push(node);
9370 continue;
9371 }
9372 for index in (0..node.named_child_count()).rev() {
9373 if let Some(child) = node.named_child(index) {
9374 stack.push(child);
9375 }
9376 }
9377 }
9378 if terminators.len() == 1 {
9379 return simple_preprocessor_guard(openers[0], source);
9380 }
9381 }
9382 ancestor = statement.parent();
9383 }
9384 None
9385}
9386
9387fn preprocessor_guard_for_descendant(
9388 conditional: Node<'_>,
9389 descendant: Node<'_>,
9390 source: &str,
9391) -> Option<PreprocessorGuard> {
9392 let mut guard = simple_preprocessor_guard(conditional, source)?;
9393 if conditional
9394 .child_by_field_name("alternative")
9395 .is_some_and(|alternative| {
9396 alternative.start_byte() <= descendant.start_byte()
9397 && descendant.end_byte() <= alternative.end_byte()
9398 })
9399 {
9400 let alternative = conditional.child_by_field_name("alternative")?;
9401 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
9405 return None;
9406 }
9407 guard = guard.negated();
9408 }
9409 Some(guard)
9410}
9411
9412fn preprocessor_conditional_contains_descendant(
9413 conditional: Node<'_>,
9414 descendant: Node<'_>,
9415) -> bool {
9416 cpp_displaced_preprocessor_boundary(conditional)
9417 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
9418}
9419
9420pub fn merge_preprocessor_guards(
9421 left: &HashSet<PreprocessorGuard>,
9422 right: &HashSet<PreprocessorGuard>,
9423) -> Option<HashSet<PreprocessorGuard>> {
9424 let mut merged = left.clone();
9425 for guard in right {
9426 if merged.contains(&guard.negated()) {
9427 return None;
9428 }
9429 merged.insert(guard.clone());
9430 }
9431 Some(merged)
9432}
9433
9434fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
9435 if conditional.kind() == "preproc_ifdef" {
9436 let name = conditional.child_by_field_name("name")?;
9437 let name = node_text(name, source).to_string();
9438 return match conditional.child(0)?.kind() {
9439 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
9440 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
9441 _ => None,
9442 };
9443 }
9444 let condition = conditional.child_by_field_name("condition")?;
9445 simple_preprocessor_expression_guard(condition, source).or_else(|| {
9446 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
9447 node_text(condition, source),
9448 )))
9449 })
9450}
9451
9452fn simple_preprocessor_expression_guard(
9453 expression: Node<'_>,
9454 source: &str,
9455) -> Option<PreprocessorGuard> {
9456 match expression.kind() {
9457 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
9458 node_text(expression, source).to_string(),
9459 ))),
9460 "number_literal" => match node_text(expression, source).trim() {
9461 "0" => Some(PreprocessorGuard::Constant(false)),
9462 "1" => Some(PreprocessorGuard::Constant(true)),
9463 _ => None,
9464 },
9465 "preproc_defined" => {
9466 let identifier = (0..expression.named_child_count())
9467 .filter_map(|index| expression.named_child(index))
9468 .find(|child| child.kind() == "identifier")?;
9469 Some(PreprocessorGuard::Defined(
9470 node_text(identifier, source).to_string(),
9471 ))
9472 }
9473 "unary_expression"
9474 if expression
9475 .child_by_field_name("operator")
9476 .is_some_and(|operator| operator.kind() == "!") =>
9477 {
9478 simple_preprocessor_expression_guard(
9479 expression.child_by_field_name("argument")?,
9480 source,
9481 )
9482 .map(|guard| guard.negated())
9483 }
9484 "parenthesized_expression" => (0..expression.named_child_count())
9485 .filter_map(|index| expression.named_child(index))
9486 .next()
9487 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
9488 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
9489 expression, source,
9490 ))),
9491 _ => None,
9492 }
9493}
9494
9495fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
9496 match expression.kind() {
9497 "number_literal" => match node_text(expression, source).trim() {
9498 "0" => BooleanGuardExpression::Constant(false),
9499 "1" => BooleanGuardExpression::Constant(true),
9500 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9501 expression, source,
9502 ))),
9503 },
9504 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
9505 "preproc_defined" => {
9506 let identifier = (0..expression.named_child_count())
9507 .filter_map(|index| expression.named_child(index))
9508 .find(|child| child.kind() == "identifier");
9509 identifier.map_or_else(
9510 || {
9511 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9512 expression, source,
9513 )))
9514 },
9515 |identifier| {
9516 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
9517 },
9518 )
9519 }
9520 "unary_expression"
9521 if expression
9522 .child_by_field_name("operator")
9523 .is_some_and(|operator| operator.kind() == "!") =>
9524 {
9525 expression.child_by_field_name("argument").map_or_else(
9526 || {
9527 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9528 expression, source,
9529 )))
9530 },
9531 |argument| boolean_preprocessor_expression(argument, source).negated(),
9532 )
9533 }
9534 "parenthesized_expression" => (0..expression.named_child_count())
9535 .filter_map(|index| expression.named_child(index))
9536 .next()
9537 .map_or_else(
9538 || {
9539 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9540 expression, source,
9541 )))
9542 },
9543 |child| boolean_preprocessor_expression(child, source),
9544 ),
9545 "binary_expression" => {
9546 let operands = || {
9547 Some((
9548 boolean_preprocessor_expression(
9549 expression.child_by_field_name("left")?,
9550 source,
9551 ),
9552 boolean_preprocessor_expression(
9553 expression.child_by_field_name("right")?,
9554 source,
9555 ),
9556 ))
9557 };
9558 match expression
9559 .child_by_field_name("operator")
9560 .map(|operator| operator.kind())
9561 {
9562 Some("&&") => operands().map_or_else(
9563 || {
9564 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9565 expression, source,
9566 )))
9567 },
9568 |(left, right)| BooleanGuardExpression::all([left, right]),
9569 ),
9570 Some("||") => operands().map_or_else(
9571 || {
9572 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9573 expression, source,
9574 )))
9575 },
9576 |(left, right)| BooleanGuardExpression::any([left, right]),
9577 ),
9578 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9579 expression, source,
9580 ))),
9581 }
9582 }
9583 _ => {
9584 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
9585 }
9586 }
9587}
9588
9589fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
9590 if targets.len() == 1 {
9591 targets.pop()
9592 } else {
9593 None
9594 }
9595}
9596
9597fn nameable_callable_declaration_nodes<'tree>(
9606 analyzer: &CppGraphSource<'_>,
9607 prepared: &'tree PreparedSyntaxTree,
9608 candidate: &CodeUnit,
9609) -> Vec<Node<'tree>> {
9610 let root = prepared.tree().root_node();
9611 analyzer
9612 .ranges(candidate)
9613 .into_iter()
9614 .filter_map(|range| {
9615 let mut declaration =
9616 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
9617 while !matches!(
9618 declaration.kind(),
9619 "declaration" | "field_declaration" | "function_definition"
9620 ) {
9621 declaration = declaration.parent()?;
9622 }
9623 let mut ancestor = declaration.parent();
9624 while let Some(node) = ancestor {
9625 if node.kind() == "function_definition"
9626 && is_recovered_declaration_scope_container(node, prepared.source())
9627 {
9628 ancestor = node.parent();
9629 continue;
9630 }
9631 if node.kind() == "compound_statement"
9632 && node.parent().is_some_and(|parent| {
9633 is_recovered_declaration_scope_container(parent, prepared.source())
9634 })
9635 {
9636 ancestor = node.parent().and_then(|parent| parent.parent());
9637 continue;
9638 }
9639 if matches!(
9640 node.kind(),
9641 "compound_statement" | "function_definition" | "lambda_expression"
9642 ) {
9643 return None;
9644 }
9645 ancestor = node.parent();
9646 }
9647 Some(declaration)
9648 })
9649 .collect()
9650}
9651
9652fn callable_declaration_activation_in_file(
9653 analyzer: &CppGraphSource<'_>,
9654 prepared: &PreparedSyntaxTree,
9655 candidate: &CodeUnit,
9656 reference: &CallableReferenceContext<'_>,
9657) -> Option<usize> {
9658 nameable_callable_declaration_nodes(analyzer, prepared, candidate)
9659 .into_iter()
9660 .filter(|declaration| {
9661 callable_preprocessor_context_is_visible_for_reference(
9662 *declaration,
9663 prepared.source(),
9664 reference,
9665 )
9666 })
9667 .map(callable_declaration_activation_byte)
9668 .min()
9669}
9670
9671fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
9676 if declaration.kind() != "function_definition" {
9677 return declaration.end_byte();
9678 }
9679 declaration
9680 .child_by_field_name("declarator")
9681 .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
9682}
9683
9684struct CallableReferenceContext<'a> {
9690 file: &'a ProjectFile,
9691 position: Option<CallableReferencePosition<'a>>,
9692}
9693
9694struct CallableReferencePosition<'a> {
9698 prepared: &'a PreparedSyntaxTree,
9699 byte: usize,
9700 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
9701}
9702
9703impl CallableReferenceContext<'_> {
9704 fn is_c(&self) -> bool {
9705 self.file
9706 .rel_path()
9707 .extension()
9708 .and_then(|extension| extension.to_str())
9709 == Some("c")
9710 }
9711
9712 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
9713 let position = self.position.as_ref()?;
9714 position
9715 .guards
9716 .get_or_init(|| {
9717 position
9718 .prepared
9719 .tree()
9720 .root_node()
9721 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
9722 .and_then(|node| {
9723 preprocessor_guard_environment(node, position.prepared.source())
9724 })
9725 })
9726 .as_ref()
9727 }
9728}
9729
9730fn callable_preprocessor_context_is_visible_for_reference(
9731 node: Node<'_>,
9732 source: &str,
9733 reference: &CallableReferenceContext<'_>,
9734) -> bool {
9735 let reference_is_c = reference.is_c();
9736 let mut ancestor = node.parent();
9737 while let Some(conditional) = ancestor {
9738 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
9739 && !is_file_covering_include_guard(conditional, source)
9740 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
9741 && preprocessor_conditional_contains_descendant(conditional, node)
9742 {
9743 let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
9744 return false;
9745 };
9746 match guard {
9747 PreprocessorGuard::Constant(true) => {}
9748 PreprocessorGuard::Constant(false) => return false,
9749 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
9750 if reference_is_c {
9751 return false;
9752 }
9753 }
9754 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
9755 if !reference_is_c {
9756 return false;
9757 }
9758 }
9759 guard => {
9765 if !reference
9766 .guards()
9767 .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
9768 {
9769 return false;
9770 }
9771 }
9772 }
9773 }
9774 ancestor = conditional.parent();
9775 }
9776 true
9777}
9778
9779fn flattened_macro_namespace_declaration_matches(
9780 analyzer: &CppGraphSource<'_>,
9781 cpp: &dyn CppSource,
9782 reference_file: &ProjectFile,
9783 visible_declaration: &CodeUnit,
9784 qualified_candidate: &CodeUnit,
9785 reference_byte: usize,
9786) -> bool {
9787 if visible_declaration.kind() != qualified_candidate.kind()
9793 || visible_declaration.identifier() != qualified_candidate.identifier()
9794 || visible_declaration.signature() != qualified_candidate.signature()
9795 || !visible_declaration.package_name().is_empty()
9796 || qualified_candidate.package_name().is_empty()
9797 {
9798 return false;
9799 }
9800
9801 let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
9802 return false;
9803 };
9804 let root = prepared.tree().root_node();
9805 let closing_brace_limit = if visible_declaration.source() == reference_file {
9806 reference_byte
9807 } else {
9808 usize::MAX
9809 };
9810
9811 analyzer
9812 .ranges(visible_declaration)
9813 .into_iter()
9814 .any(|range| {
9815 let Some(mut declaration) =
9816 root.descendant_for_byte_range(range.start_byte, range.end_byte)
9817 else {
9818 return false;
9819 };
9820 while !matches!(
9821 declaration.kind(),
9822 "declaration" | "field_declaration" | "function_definition"
9823 ) {
9824 let Some(parent) = declaration.parent() else {
9825 return false;
9826 };
9827 declaration = parent;
9828 }
9829 if declaration
9830 .parent()
9831 .is_none_or(|parent| parent.kind() != "translation_unit")
9832 || !macro_displaced_cpp_return_type(declaration, prepared.source())
9833 {
9834 return false;
9835 }
9836
9837 let mut cursor = root.walk();
9838 root.named_children(&mut cursor).any(|sibling| {
9839 sibling.start_byte() >= declaration.end_byte()
9840 && sibling.start_byte() < closing_brace_limit
9841 && direct_unmatched_closing_brace(sibling)
9842 })
9843 })
9844}
9845
9846fn flattened_macro_namespace_components(
9847 declaration: Node<'_>,
9848 source: &str,
9849) -> Option<Vec<String>> {
9850 flattened_macro_function_namespace_components(declaration, source)
9851 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
9852}
9853
9854fn flattened_macro_function_namespace_components(
9855 declaration: Node<'_>,
9856 source: &str,
9857) -> Option<Vec<String>> {
9858 let body = declaration
9859 .parent()
9860 .filter(|parent| parent.kind() == "compound_statement")?;
9861 let function = body.parent()?;
9862 if function.child_by_field_name("body") != Some(body) {
9863 return None;
9864 }
9865 let namespace_name = recovered_macro_namespace_name(function, source)?;
9866 let mut components = enclosing_namespace_components(declaration, source)?;
9867 components.push(namespace_name);
9868 Some(components)
9869}
9870
9871fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
9882 if function.kind() != "function_definition" || !function.has_error() {
9883 return None;
9884 }
9885 let body = function
9886 .child_by_field_name("body")
9887 .filter(|body| body.kind() == "compound_statement")?;
9888 let mut cursor = function.walk();
9889 let prefix = function
9890 .named_children(&mut cursor)
9891 .take_while(|child| child.start_byte() < body.start_byte())
9892 .filter(|child| child.kind() != "comment")
9893 .collect::<Vec<_>>();
9894 let begin_index = prefix.iter().rposition(|child| {
9895 flattened_macro_sentinel_name(*child, source)
9896 .is_some_and(|name| is_namespace_begin_sentinel(&name))
9897 })?;
9898 let mut identifiers = Vec::new();
9899 let mut stack = prefix[begin_index + 1..]
9900 .iter()
9901 .rev()
9902 .copied()
9903 .collect::<Vec<_>>();
9904 while let Some(current) = stack.pop() {
9905 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
9906 identifiers.push(identifier);
9907 continue;
9908 }
9909 let mut cursor = current.walk();
9910 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
9911 stack.extend(children.into_iter().rev());
9912 }
9913 let [keyword, namespace_name] = identifiers.as_slice() else {
9914 return None;
9915 };
9916 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
9917 {
9918 return None;
9919 }
9920 let mut next = function.next_named_sibling();
9921 let next = loop {
9922 let candidate = next?;
9923 next = candidate.next_named_sibling();
9924 if candidate.kind() != "comment" {
9925 break candidate;
9926 }
9927 };
9928 flattened_macro_sentinel_name(next, source)
9929 .is_some_and(|name| is_namespace_end_sentinel(&name))
9930 .then(|| namespace_name.clone())
9931}
9932
9933fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
9938 crate::declarations::is_recovered_exported_class_container(node, source)
9939 || recovered_macro_namespace_name(node, source).is_some()
9940}
9941
9942fn flattened_macro_error_namespace_components(
9943 declaration: Node<'_>,
9944 source: &str,
9945) -> Option<Vec<String>> {
9946 let parent = declaration
9947 .parent()
9948 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
9949 let mut cursor = parent.walk();
9950 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
9951 let declaration_index = siblings
9952 .iter()
9953 .position(|candidate| same_node(*candidate, declaration))?;
9954 let begin_index = (0..declaration_index).rev().find(|index| {
9955 flattened_macro_sentinel_name(siblings[*index], source)
9956 .is_some_and(|name| is_namespace_begin_sentinel(&name))
9957 })?;
9958
9959 let significant = siblings[begin_index + 1..declaration_index]
9960 .iter()
9961 .copied()
9962 .filter(|node| node.kind() != "comment")
9963 .collect::<Vec<_>>();
9964 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
9965 return None;
9966 };
9967 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
9968 return None;
9969 }
9970 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
9971 if significant[2..].iter().any(|node| {
9972 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
9973 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
9974 })
9975 }) {
9976 return None;
9977 }
9978
9979 let mut saw_namespace_close = false;
9980 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
9981 if sibling.kind() == "comment" {
9982 continue;
9983 }
9984 if !saw_namespace_close {
9985 if direct_unmatched_closing_brace(sibling) {
9986 saw_namespace_close = true;
9987 continue;
9988 }
9989 if flattened_macro_sentinel_name(sibling, source).is_some() {
9990 return None;
9991 }
9992 continue;
9993 }
9994 if !flattened_macro_sentinel_name(sibling, source)
9995 .is_some_and(|name| is_namespace_end_sentinel(&name))
9996 {
9997 return None;
9998 }
9999 let mut components = enclosing_namespace_components(declaration, source)?;
10000 components.push(namespace_name);
10001 return Some(components);
10002 }
10003 None
10004}
10005
10006fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
10007 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
10011 node.named_child(0)?
10012 } else {
10013 node
10014 };
10015 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
10016 node.child_by_field_name("type")
10017 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
10018 })?;
10019 (cpp_export_macro_token(&candidate)
10020 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
10021 .then_some(candidate)
10022}
10023
10024fn is_namespace_begin_sentinel(name: &str) -> bool {
10027 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
10028}
10029
10030fn is_namespace_end_sentinel(name: &str) -> bool {
10031 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
10032}
10033
10034fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
10035 if node.kind() != "ERROR" || node.named_child_count() != 1 {
10036 return None;
10037 }
10038 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
10039 (!cpp_export_macro_token(&name)).then_some(name)
10040}
10041
10042fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
10043 if !matches!(
10044 node.kind(),
10045 "identifier" | "namespace_identifier" | "type_identifier"
10046 ) {
10047 return None;
10048 }
10049 let name = normalize_cpp_whitespace(node_text(node, source));
10050 (!name.is_empty()).then_some(name)
10051}
10052
10053fn guard_requirement_sets_match(
10054 left: &[(usize, HashSet<PreprocessorGuard>)],
10055 right: &[(usize, HashSet<PreprocessorGuard>)],
10056) -> bool {
10057 left.len() == right.len()
10058 && left.iter().all(|(_, left_guards)| {
10059 right
10060 .iter()
10061 .any(|(_, right_guards)| left_guards == right_guards)
10062 })
10063 && right.iter().all(|(_, right_guards)| {
10064 left.iter()
10065 .any(|(_, left_guards)| right_guards == left_guards)
10066 })
10067}
10068
10069fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
10070 let Some(type_node) = declaration.child_by_field_name("type") else {
10071 return false;
10072 };
10073 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
10074 !type_name.is_empty()
10075 && type_name
10076 .chars()
10077 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
10078 && (0..declaration.named_child_count()).any(|index| {
10079 declaration
10080 .named_child(index)
10081 .is_some_and(|child| child.kind() == "ERROR")
10082 })
10083}
10084
10085fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
10086 node.kind() == "ERROR"
10087 && (0..node.child_count())
10088 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
10089}
10090
10091fn unmatched_closing_brace_is_followed_by_semicolon(node: Node<'_>) -> bool {
10092 let mut following = node.next_named_sibling();
10093 let following = loop {
10094 match following {
10095 Some(candidate) if candidate.kind() == "comment" => {
10096 following = candidate.next_named_sibling();
10097 continue;
10098 }
10099 candidate => break candidate,
10100 }
10101 };
10102 following.is_some_and(|candidate| {
10103 candidate.kind() == "expression_statement"
10104 && candidate.named_child_count() == 0
10105 && (0..candidate.child_count()).any(|index| {
10106 candidate
10107 .child(index)
10108 .is_some_and(|child| child.kind() == ";")
10109 })
10110 })
10111}
10112
10113pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
10114 let mut ancestor = node.parent();
10115 while let Some(parent) = ancestor {
10116 if is_preprocessor_conditional(parent)
10117 && !is_file_covering_include_guard(parent, source)
10118 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
10119 {
10120 return false;
10121 }
10122 ancestor = parent.parent();
10123 }
10124 true
10125}
10126
10127fn is_split_cpp_language_linkage_wrapper(
10128 conditional: Node<'_>,
10129 descendant: Node<'_>,
10130 source: &str,
10131) -> bool {
10132 if conditional.child_by_field_name("alternative").is_some()
10133 || !matches!(
10134 simple_preprocessor_guard(conditional, source),
10135 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
10136 )
10137 {
10138 return false;
10139 }
10140 let mut current = descendant.parent();
10141 let linkage = loop {
10142 let Some(node) = current else {
10143 return false;
10144 };
10145 if node == conditional {
10146 return false;
10147 }
10148 if node.kind() == "linkage_specification" {
10149 break node;
10150 }
10151 current = node.parent();
10152 };
10153 if linkage
10154 .child_by_field_name("value")
10155 .is_none_or(|value| node_text(value, source) != "\"C\"")
10156 {
10157 return false;
10158 }
10159 let Some(body) = linkage.child_by_field_name("body") else {
10160 return false;
10161 };
10162 let closes_opening_branch = (0..body.named_child_count())
10163 .filter_map(|index| body.named_child(index))
10164 .take_while(|child| child.end_byte() <= descendant.start_byte())
10165 .any(|child| {
10166 child.kind() == "preproc_call"
10167 && child
10168 .child_by_field_name("directive")
10169 .is_some_and(|directive| node_text(directive, source) == "#endif")
10170 });
10171 let reopens_for_closing_brace = (0..body.named_child_count())
10172 .filter_map(|index| body.named_child(index))
10173 .skip_while(|child| child.start_byte() < descendant.end_byte())
10174 .any(|child| {
10175 matches!(
10176 simple_preprocessor_guard(child, source),
10177 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
10178 ) && (0..child.child_count()).any(|index| {
10179 child
10180 .child(index)
10181 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
10182 })
10183 });
10184 closes_opening_branch && reopens_for_closing_brace
10185}
10186
10187pub fn call_arity(node: Node<'_>) -> usize {
10188 node.child_by_field_name("arguments")
10189 .or_else(|| node.child_by_field_name("parameters"))
10190 .or_else(|| node.child_by_field_name("value"))
10191 .or_else(|| first_named_child_of_kind(node, "argument_list"))
10192 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
10193 .map(|args| argument_children(args).count())
10194 .unwrap_or(0)
10195}
10196
10197pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
10198 let recovered_block_arguments = recovered_block_literal_arguments(node);
10199 (0..node.child_count())
10200 .filter_map(move |index| node.child(index))
10201 .filter(|child| child.is_named() && !child.is_extra())
10202 .flat_map(move |child| {
10203 if let Some((raw, left, right)) = recovered_block_arguments
10204 && child == raw
10205 {
10206 [Some(left), Some(right)]
10207 } else {
10208 [Some(child), None]
10209 }
10210 })
10211 .flatten()
10212}
10213
10214fn recovered_c_keyword_argument_count(
10215 file: &ProjectFile,
10216 call: Node<'_>,
10217 arguments: Node<'_>,
10218 source: &str,
10219) -> usize {
10220 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
10225 return 0;
10226 }
10227 let mut ancestor = Some(call);
10228 let function = loop {
10229 let Some(current) = ancestor else {
10230 return 0;
10231 };
10232 if current.kind() == "function_definition" {
10233 break current;
10234 }
10235 ancestor = current.parent();
10236 };
10237 let Some(parameters) = function
10238 .child_by_field_name("declarator")
10239 .and_then(|declarator| declarator.child_by_field_name("parameters"))
10240 else {
10241 return 0;
10242 };
10243 let displaced_parameter_keywords = (0..parameters.child_count())
10244 .filter_map(|index| parameters.child(index))
10245 .filter(|error| error.kind() == "ERROR")
10246 .filter_map(|error| {
10247 let parameter = error.prev_named_sibling()?;
10248 if parameter.kind() != "parameter_declaration"
10249 || parameter.end_byte() != error.start_byte()
10250 || extract_variable_name(parameter, source).is_some()
10251 {
10252 return None;
10253 }
10254 let mut children = (0..error.child_count())
10255 .filter_map(|index| error.child(index))
10256 .filter(|child| !child.is_extra() && !child.is_missing());
10257 let keyword = children.next()?;
10258 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
10259 .then_some(keyword)
10260 })
10261 .collect::<Vec<_>>();
10262 if displaced_parameter_keywords.is_empty() {
10263 return 0;
10264 }
10265
10266 (0..arguments.child_count())
10267 .filter_map(|index| arguments.child(index))
10268 .filter(|error| error.kind() == "ERROR" && error.is_extra())
10269 .filter(|error| {
10270 let mut children = (0..error.child_count())
10271 .filter_map(|index| error.child(index))
10272 .filter(|child| !child.is_extra() && !child.is_missing());
10273 let Some(comma) = children.next() else {
10274 return false;
10275 };
10276 let Some(keyword) = children.next() else {
10277 return false;
10278 };
10279 children.next().is_none()
10280 && comma.kind() == ","
10281 && !keyword.is_named()
10282 && keyword.child_count() == 0
10283 && displaced_parameter_keywords
10284 .iter()
10285 .any(|parameter| parameter.kind_id() == keyword.kind_id())
10286 })
10287 .count()
10288}
10289
10290fn recovered_block_literal_arguments<'tree>(
10291 arguments: Node<'tree>,
10292) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
10293 if arguments.kind() != "argument_list" {
10294 return None;
10295 }
10296 let mut raw_arguments = (0..arguments.child_count())
10297 .filter_map(|index| arguments.child(index))
10298 .filter(|child| child.is_named() && !child.is_extra());
10299 let raw = raw_arguments.next()?;
10300 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
10301 return None;
10302 }
10303
10304 let left = raw.child_by_field_name("left")?;
10305 if left.is_missing() || left.start_byte() == left.end_byte() {
10306 return None;
10307 }
10308 let right = raw.child_by_field_name("right")?;
10309 if right.kind() != "compound_literal_expression"
10310 || right.is_missing()
10311 || right
10312 .child_by_field_name("type")
10313 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
10314 || right
10315 .child_by_field_name("value")
10316 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
10317 {
10318 return None;
10319 }
10320 let has_intervening_error = (0..raw.child_count())
10321 .filter_map(|index| raw.child(index))
10322 .any(|child| {
10323 child.kind() == "ERROR"
10324 && !child.is_missing()
10325 && child.start_byte() >= left.end_byte()
10326 && child.end_byte() <= right.start_byte()
10327 });
10328 has_intervening_error.then_some((raw, left, right))
10329}
10330
10331pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
10332 match node.kind() {
10333 "new_expression" => node
10334 .child_by_field_name("type")
10335 .or_else(|| node.named_child(0)),
10336 "compound_literal_expression" => node.child_by_field_name("type"),
10337 "call_expression" => node.child_by_field_name("function"),
10338 _ => None,
10339 }
10340}
10341
10342pub fn field_initializer_constructs_target(
10343 node: Node<'_>,
10344 ctx: &ScanCtx<'_>,
10345 owner: &CodeUnit,
10346) -> bool {
10347 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
10356 return qualified_base_initializer_constructs_target(node, ctx, owner);
10357 }
10358 let Some(name) = node
10359 .child_by_field_name("name")
10360 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
10361 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
10362 else {
10363 return false;
10364 };
10365 let field_name = node_text(name, ctx.source);
10366 ctx.visibility
10367 .visible_identifier_candidates(ctx.file, field_name)
10368 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
10369 .any(|unit| field_declares_type(unit, ctx, owner))
10370}
10371
10372fn qualified_base_initializer_constructs_target(
10373 node: Node<'_>,
10374 ctx: &ScanCtx<'_>,
10375 owner: &CodeUnit,
10376) -> bool {
10377 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
10378 return false;
10379 };
10380 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
10381 return false;
10382 };
10383 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
10384 return false;
10385 };
10386 let resolves_target = |components: &[String]| {
10387 matches!(
10388 ctx.visibility.resolve_type_components_lexically_for_target(
10389 &ctx.analyzer,
10390 ctx.file,
10391 components,
10392 is_globally_qualified_cpp_name(qualified),
10393 &lexical_scope,
10394 owner,
10395 ),
10396 LexicalTypeResolution::Resolved { unit, .. }
10397 if same_visible_symbol(&unit, owner)
10398 )
10399 };
10400 if resolves_target(&components) {
10401 return true;
10402 }
10403
10404 components
10410 .last()
10411 .is_some_and(|terminal| terminal == owner.identifier())
10412 && resolves_target(&components[..components.len() - 1])
10413}
10414
10415fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
10416 unit.signature()
10417 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
10418 || ctx
10419 .analyzer
10420 .get_source(unit, false)
10421 .is_some_and(|declaration| {
10422 field_declaration_type_matches(&declaration, unit, ctx, owner)
10423 })
10424}
10425
10426pub fn field_declared_binding(
10427 analyzer: &CppGraphSource<'_>,
10428 visibility: &VisibilityIndex<'_>,
10429 visible_from: &ProjectFile,
10430 field: &CodeUnit,
10431) -> Option<CppScanBinding> {
10432 let fact = visibility.field_declared_type_fact(analyzer, field)?;
10433 let normalized = normalize_field_type_text(&fact.type_text);
10434 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
10435 analyzer,
10436 visible_from,
10437 field,
10438 &normalized,
10439 );
10440 let resolved = match (resolved, fact.template_arguments.as_deref()) {
10441 (Some(primary), Some(arguments)) => visibility
10442 .resolve_template_arguments(visible_from, primary, arguments)
10443 .ok(),
10444 (resolved, None) => resolved,
10445 (None, Some(_)) => None,
10446 };
10447 Some(CppScanBinding::from_type_name(
10448 normalized,
10449 resolved,
10450 fact.indirection,
10451 ))
10452}
10453
10454fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
10456 let Some(first) = candidates.first() else {
10457 return Err(TypeCandidateFailure::Unresolvable);
10458 };
10459 if candidates
10460 .iter()
10461 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
10462 {
10463 Ok((*first).clone())
10464 } else {
10465 Err(TypeCandidateFailure::Ambiguous)
10466 }
10467}
10468
10469fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
10470 logical_type_candidate(candidates).ok()
10471}
10472
10473fn unique_type_candidate_preserving_alias(
10474 analyzer: &CppGraphSource<'_>,
10475 candidates: &[&CodeUnit],
10476) -> Option<CodeUnit> {
10477 let first = *candidates.first()?;
10478 if declared_type_alias(analyzer, first) {
10479 return candidates
10480 .iter()
10481 .all(|candidate| {
10482 declared_type_alias(analyzer, candidate)
10483 && candidate.kind() == first.kind()
10484 && candidate.fq_name() == first.fq_name()
10485 && candidate.source() == first.source()
10486 })
10487 .then(|| first.clone());
10488 }
10489 candidates
10490 .iter()
10491 .all(|candidate| {
10492 !declared_type_alias(analyzer, candidate)
10493 && candidate.kind() == first.kind()
10494 && candidate.fq_name() == first.fq_name()
10495 })
10496 .then(|| first.clone())
10497}
10498
10499fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10500 is_type_alias(unit)
10501 || analyzer
10502 .type_alias_provider()
10503 .is_some_and(|provider| provider.is_type_alias(unit))
10504}
10505
10506pub fn field_declared_type_binding(
10507 analyzer: &CppGraphSource<'_>,
10508 visibility: &VisibilityIndex<'_>,
10509 visible_from: &ProjectFile,
10510 field: &CodeUnit,
10511) -> Option<(String, Option<CodeUnit>, i32)> {
10512 let fact = visibility.field_declared_type_fact(analyzer, field)?;
10513 let normalized = normalize_field_type_text(&fact.type_text);
10514 let primary = visibility.resolve_unique_canonical_type_for_declaration(
10515 analyzer,
10516 visible_from,
10517 field,
10518 &normalized,
10519 );
10520 let resolved = match (primary, fact.template_arguments.as_deref()) {
10521 (Some(primary), Some(arguments)) => visibility
10522 .resolve_template_arguments(visible_from, primary, arguments)
10523 .ok(),
10524 (resolved, None) => resolved,
10525 (None, Some(_)) => None,
10526 };
10527 Some((normalized, resolved, fact.indirection))
10528}
10529
10530fn decode_field_declared_type_fact(
10531 analyzer: &CppGraphSource<'_>,
10532 field: &CodeUnit,
10533) -> Option<DeclaredFieldTypeFact> {
10534 let declaration = analyzer.get_source(field, false)?;
10535 let mut parser = Parser::new();
10536 parser
10537 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10538 .ok()?;
10539 let tree = parser.parse(&declaration, None)?;
10540 let mut stack = vec![tree.root_node()];
10541 while let Some(node) = stack.pop() {
10542 if matches!(node.kind(), "declaration" | "field_declaration")
10543 && let Some(type_node) = node
10544 .child_by_field_name("type")
10545 .or_else(|| first_type_child(node))
10546 && let Some(indirection) =
10547 declared_name_indirection(node, type_node, field.identifier(), &declaration)
10548 {
10549 let declared_type = if matches!(
10550 type_node.kind(),
10551 "class_specifier" | "struct_specifier" | "union_specifier"
10552 ) {
10553 type_node.child_by_field_name("name")
10554 } else {
10555 Some(type_node)
10556 };
10557 let type_text = declared_type.map_or_else(
10558 || field.identifier().to_string(),
10559 |declared_type| node_text(declared_type, &declaration).to_string(),
10560 );
10561 return Some(DeclaredFieldTypeFact {
10562 type_text,
10563 indirection,
10564 template_arguments: declared_type.and_then(|declared_type| {
10565 cpp_template_reference_arguments(declared_type, &declaration)
10566 }),
10567 });
10568 }
10569 let mut cursor = node.walk();
10570 stack.extend(node.named_children(&mut cursor));
10571 }
10572 None
10573}
10574
10575pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
10589 let mut parser = Parser::new();
10590 parser
10591 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10592 .ok()?;
10593 let tree = parser.parse(declaration, None)?;
10594 let mut stack = vec![tree.root_node()];
10595 while let Some(node) = stack.pop() {
10596 let type_node = match node.kind() {
10597 "type_definition" => {
10598 let mut cursor = node.walk();
10599 if node
10600 .children_by_field_name("declarator", &mut cursor)
10601 .any(declarator_names_function_type)
10602 {
10603 return None;
10604 }
10605 node.child_by_field_name("type")?
10606 }
10607 "alias_declaration" => {
10608 let type_node = node.child_by_field_name("type")?;
10609 if type_node
10610 .child_by_field_name("declarator")
10611 .is_some_and(declarator_names_function_type)
10612 {
10613 return None;
10614 }
10615 type_node
10616 }
10617 _ => {
10618 let mut cursor = node.walk();
10619 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10620 stack.extend(children.into_iter().rev());
10621 continue;
10622 }
10623 };
10624 return Some(node_text(type_node, declaration).to_string());
10625 }
10626 None
10627}
10628
10629fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
10638 let mut parser = Parser::new();
10639 if parser
10640 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10641 .is_err()
10642 {
10643 return true;
10644 }
10645 let Some(tree) = parser.parse(declaration, None) else {
10646 return true;
10647 };
10648 let mut stack = vec![tree.root_node()];
10649 while let Some(node) = stack.pop() {
10650 let declarators = match node.kind() {
10651 "type_definition" => {
10652 let mut cursor = node.walk();
10653 node.children_by_field_name("declarator", &mut cursor)
10654 .collect::<Vec<_>>()
10655 }
10656 "alias_declaration" => node
10657 .child_by_field_name("type")
10658 .and_then(|type_node| type_node.child_by_field_name("declarator"))
10659 .into_iter()
10660 .collect::<Vec<_>>(),
10661 _ => {
10662 let mut cursor = node.walk();
10663 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10664 stack.extend(children.into_iter().rev());
10665 continue;
10666 }
10667 };
10668 return declarators.into_iter().any(cpp_declarator_adds_indirection);
10669 }
10670 true
10671}
10672
10673fn declarator_names_function_type(declarator: Node<'_>) -> bool {
10679 let mut current = Some(declarator);
10680 while let Some(node) = current {
10681 match node.kind() {
10682 "function_declarator" | "abstract_function_declarator" => return true,
10683 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
10684 current = node.named_child(0);
10685 }
10686 _ => current = node.child_by_field_name("declarator"),
10687 }
10688 }
10689 false
10690}
10691
10692pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
10696 let mut parser = Parser::new();
10697 if parser
10698 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10699 .is_err()
10700 {
10701 return false;
10702 }
10703 let Some(tree) = parser.parse(declaration, None) else {
10704 return false;
10705 };
10706 let mut stack = vec![tree.root_node()];
10707 while let Some(node) = stack.pop() {
10708 if matches!(node.kind(), "declaration" | "field_declaration") {
10709 let mut cursor = node.walk();
10710 if node
10711 .children_by_field_name("declarator", &mut cursor)
10712 .any(|declarator| {
10713 declarator_name_node(declarator).is_some_and(|name| {
10714 node_text(name, declaration) == field_name
10715 && declarator_names_function_type(declarator)
10716 })
10717 })
10718 {
10719 return true;
10720 }
10721 }
10722 let mut cursor = node.walk();
10723 stack.extend(node.named_children(&mut cursor));
10724 }
10725 false
10726}
10727
10728pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
10732 let mut parser = Parser::new();
10733 if parser
10734 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10735 .is_err()
10736 {
10737 return false;
10738 }
10739 let Some(tree) = parser.parse(declaration, None) else {
10740 return false;
10741 };
10742 let mut stack = vec![tree.root_node()];
10743 while let Some(node) = stack.pop() {
10744 match node.kind() {
10745 "type_definition" => {
10746 let mut cursor = node.walk();
10747 if node
10748 .children_by_field_name("declarator", &mut cursor)
10749 .any(|declarator| {
10750 extract_typedef_declarator_name(declarator, declaration)
10751 .is_some_and(|name| name == alias_name)
10752 && declarator_names_function_type(declarator)
10753 })
10754 {
10755 return true;
10756 }
10757 }
10758 "alias_declaration" => {
10759 let names_alias = node
10760 .child_by_field_name("name")
10761 .is_some_and(|name| node_text(name, declaration) == alias_name);
10762 if names_alias
10763 && node
10764 .child_by_field_name("type")
10765 .and_then(|type_node| type_node.child_by_field_name("declarator"))
10766 .is_some_and(declarator_names_function_type)
10767 {
10768 return true;
10769 }
10770 }
10771 _ => {}
10772 }
10773 let mut cursor = node.walk();
10774 stack.extend(node.named_children(&mut cursor));
10775 }
10776 false
10777}
10778
10779fn decode_structured_alias_target(
10780 analyzer: &CppGraphSource<'_>,
10781 unit: &CodeUnit,
10782) -> Option<StructuredAliasTarget> {
10783 analyzer
10784 .get_source(unit, false)
10785 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
10786 .or_else(|| {
10787 let signature = unit.signature()?;
10788 decode_structured_alias_target_source(unit, signature, false)
10789 })
10790}
10791
10792fn decode_structured_alias_target_source(
10793 unit: &CodeUnit,
10794 declaration: &str,
10795 require_top_level: bool,
10796) -> Option<StructuredAliasTarget> {
10797 let mut parser = Parser::new();
10798 parser
10799 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10800 .ok()?;
10801 let tree = parser.parse(declaration, None)?;
10802 let mut stack = vec![tree.root_node()];
10803 while let Some(node) = stack.pop() {
10804 let type_node = match node.kind() {
10805 "type_definition" => {
10806 if require_top_level
10807 && node
10808 .parent()
10809 .is_none_or(|parent| parent.kind() != "translation_unit")
10810 {
10811 let mut cursor = node.walk();
10812 stack.extend(node.named_children(&mut cursor));
10813 continue;
10814 }
10815 let mut declarator_cursor = node.walk();
10816 let declarator = node
10817 .children_by_field_name("declarator", &mut declarator_cursor)
10818 .find(|declarator| {
10819 extract_typedef_declarator_name(*declarator, declaration)
10820 .is_some_and(|name| name == unit.identifier())
10821 })?;
10822 if declarator_names_function_type(declarator) {
10823 return None;
10824 }
10825 node.child_by_field_name("type")?
10826 }
10827 "alias_declaration" => {
10828 if require_top_level
10829 && node
10830 .parent()
10831 .is_none_or(|parent| parent.kind() != "translation_unit")
10832 {
10833 let mut cursor = node.walk();
10834 stack.extend(node.named_children(&mut cursor));
10835 continue;
10836 }
10837 let name = node.child_by_field_name("name")?;
10838 if node_text(name, declaration) != unit.identifier() {
10839 return None;
10840 }
10841 let type_node = node.child_by_field_name("type")?;
10842 if type_node
10843 .child_by_field_name("declarator")
10844 .is_some_and(declarator_names_function_type)
10845 {
10846 return None;
10847 }
10848 type_node
10849 }
10850 _ => {
10851 let mut cursor = node.walk();
10852 stack.extend(node.named_children(&mut cursor));
10853 continue;
10854 }
10855 };
10856 return structured_alias_type_target(type_node, declaration);
10857 }
10858 None
10859}
10860
10861fn structured_alias_type_target(
10862 mut type_node: Node<'_>,
10863 source: &str,
10864) -> Option<StructuredAliasTarget> {
10865 while type_node.kind() == "type_descriptor" {
10866 type_node = type_node.child_by_field_name("type")?;
10867 }
10868 if type_node.kind() == "primitive_type" {
10869 return Some(StructuredAliasTarget::Builtin);
10870 }
10871 if matches!(
10872 type_node.kind(),
10873 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
10874 ) {
10875 type_node = type_node.child_by_field_name("name")?;
10876 }
10877 let global = type_node.child_by_field_name("scope").is_none()
10878 && type_node.child(0).is_some_and(|child| child.kind() == "::");
10879 let mut components = Vec::new();
10880 append_structured_type_components(type_node, source, &mut components)?;
10881 let arguments = cpp_template_reference_arguments(type_node, source);
10882 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
10883 components,
10884 global,
10885 arguments,
10886 })
10887}
10888
10889fn append_structured_type_components(
10890 node: Node<'_>,
10891 source: &str,
10892 out: &mut Vec<String>,
10893) -> Option<()> {
10894 match node.kind() {
10895 "identifier" | "namespace_identifier" | "type_identifier" => {
10896 out.push(node_text(node, source).to_string());
10897 Some(())
10898 }
10899 "template_type" => {
10900 append_structured_type_components(node.child_by_field_name("name")?, source, out)
10901 }
10902 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
10903 if let Some(scope) = node.child_by_field_name("scope") {
10904 append_structured_type_components(scope, source, out)?;
10905 }
10906 append_structured_type_components(node.child_by_field_name("name")?, source, out)
10907 }
10908 _ => None,
10909 }
10910}
10911
10912fn declared_name_indirection(
10913 declaration: Node<'_>,
10914 type_node: Node<'_>,
10915 field_name: &str,
10916 source: &str,
10917) -> Option<i32> {
10918 let mut stack = Vec::new();
10919 let mut cursor = declaration.walk();
10920 stack.extend(
10921 declaration
10922 .named_children(&mut cursor)
10923 .filter(|child| !same_node(*child, type_node)),
10924 );
10925 while let Some(node) = stack.pop() {
10926 if matches!(node.kind(), "identifier" | "field_identifier")
10927 && node_text(node, source) == field_name
10928 {
10929 let mut indirection = 0;
10930 let mut current = node.parent();
10931 while let Some(parent) = current {
10932 if same_node(parent, declaration) {
10933 return Some(indirection);
10934 }
10935 if parent.kind() == "pointer_declarator" {
10936 indirection += 1;
10937 }
10938 current = parent.parent();
10939 }
10940 return None;
10941 }
10942 let mut cursor = node.walk();
10943 stack.extend(node.named_children(&mut cursor));
10944 }
10945 None
10946}
10947
10948fn field_declaration_type_matches(
10949 declaration: &str,
10950 unit: &CodeUnit,
10951 ctx: &ScanCtx<'_>,
10952 owner: &CodeUnit,
10953) -> bool {
10954 ctx.visibility
10955 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
10956 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
10957 let normalized = normalize_field_type_text(type_text);
10958 ctx.visibility
10959 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
10960 || ctx.visibility.resolves_to_type(
10961 &ctx.analyzer,
10962 ctx.file,
10963 normalized.as_str(),
10964 owner,
10965 )
10966 })
10967}
10968
10969fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
10970 let declaration = declaration
10971 .split(['=', ';'])
10972 .next()
10973 .unwrap_or(declaration)
10974 .trim();
10975 let index = declaration.rfind(field_name)?;
10976 let before = &declaration[..index];
10977 let after = &declaration[index + field_name.len()..];
10978 if before.chars().next_back().is_some_and(is_identifier_char)
10979 || after.chars().next().is_some_and(is_identifier_char)
10980 {
10981 return None;
10982 }
10983 Some(before.trim())
10984}
10985
10986fn normalize_field_type_text(type_text: &str) -> String {
10987 const FIELD_SPECIFIERS: [&str; 8] = [
10988 "extern ",
10989 "static ",
10990 "mutable ",
10991 "constexpr ",
10992 "constinit ",
10993 "inline ",
10994 "volatile ",
10995 "const ",
10996 ];
10997
10998 let mut normalized = normalize_type_text(type_text);
10999 loop {
11000 let Some(stripped) = FIELD_SPECIFIERS
11001 .iter()
11002 .find_map(|specifier| normalized.strip_prefix(specifier))
11003 else {
11004 return normalized;
11005 };
11006 normalized = normalize_type_text(stripped);
11007 }
11008}
11009
11010fn is_identifier_char(ch: char) -> bool {
11011 ch == '_' || ch.is_ascii_alphanumeric()
11012}
11013
11014pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
11015 let Some(type_node) = node.child_by_field_name("type") else {
11016 return false;
11017 };
11018 ctx.visibility.resolves_to_type(
11019 &ctx.analyzer,
11020 ctx.file,
11021 node_text(type_node, ctx.source),
11022 owner,
11023 )
11024}
11025
11026pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
11027 !ctx.analyzer
11028 .declarations(ctx.file)
11029 .into_iter()
11030 .filter(|unit| unit.is_function())
11031 .any(|unit| {
11032 ctx.analyzer.ranges(&unit).iter().any(|range| {
11033 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
11034 })
11035 })
11036}
11037
11038pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
11039 let mut cursor = node.walk();
11040 for child in node.named_children(&mut cursor) {
11041 if child.kind() == "init_declarator" {
11042 return child
11043 .child_by_field_name("value")
11044 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
11045 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
11046 .map(declaration_init_value_arity)
11047 .unwrap_or(0);
11048 }
11049 if is_declarator_node(child) {
11050 return declaration_declarator_arity(child);
11051 }
11052 }
11053 0
11054}
11055
11056fn declaration_init_value_arity(value: Node<'_>) -> usize {
11057 match value.kind() {
11058 "argument_list" | "initializer_list" => argument_children(value).count(),
11059 "compound_literal_expression" => call_arity(value),
11060 _ => 1,
11061 }
11062}
11063
11064fn declaration_declarator_arity(node: Node<'_>) -> usize {
11065 if let Some(parameters) = node.child_by_field_name("parameters") {
11066 return argument_children(parameters).count();
11067 }
11068 node.child_by_field_name("declarator")
11069 .map(declaration_declarator_arity)
11070 .unwrap_or(0)
11071}
11072
11073pub(super) fn first_named_child_of_kind<'tree>(
11074 node: Node<'tree>,
11075 kind: &str,
11076) -> Option<Node<'tree>> {
11077 let mut cursor = node.walk();
11078 node.named_children(&mut cursor)
11079 .find(|child| child.kind() == kind)
11080}
11081
11082fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
11083 let mut stack = vec![root];
11084 while let Some(node) = stack.pop() {
11085 if node.kind() == kind {
11086 return Some(node);
11087 }
11088 for index in (0..node.named_child_count()).rev() {
11089 if let Some(child) = node.named_child(index) {
11090 stack.push(child);
11091 }
11092 }
11093 }
11094 None
11095}
11096
11097fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
11098 if node.kind() == "identifier" {
11099 return true;
11100 }
11101 if node.kind() == "parenthesized_expression" {
11102 return false;
11103 }
11104 if node.kind() == "call_expression" {
11105 return node
11106 .child_by_field_name("function")
11107 .is_some_and(|function| function.kind() == "identifier");
11108 }
11109 let mut stack = vec![node];
11110 while let Some(descendant) = stack.pop() {
11111 if descendant != node && descendant.kind() == "parenthesized_expression" {
11112 continue;
11113 }
11114 if descendant.kind() == "identifier" {
11115 return true;
11116 }
11117 if descendant.kind() == "call_expression" {
11118 if descendant
11119 .child_by_field_name("function")
11120 .is_some_and(|function| function.kind() == "identifier")
11121 {
11122 return true;
11123 }
11124 continue;
11125 }
11126 for index in (0..descendant.named_child_count()).rev() {
11127 if let Some(child) = descendant.named_child(index) {
11128 stack.push(child);
11129 }
11130 }
11131 }
11132 false
11133}
11134
11135fn macro_expansion_shape_is_safe(
11136 node: Node<'_>,
11137 source: &str,
11138 parameters: &[String],
11139 environment: &MacroEnvironment,
11140) -> bool {
11141 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
11142 return true;
11143 }
11144 if node.kind() == "call_expression" {
11145 let Some(function) = node.child_by_field_name("function") else {
11146 return true;
11147 };
11148 if function.kind() != "identifier" {
11149 return true;
11150 }
11151 let function_name = node_text(function, source);
11152 if parameters
11153 .iter()
11154 .any(|parameter| parameter == function_name)
11155 {
11156 return false;
11157 }
11158 if !environment.may_bind(function_name) {
11159 return true;
11160 }
11161 let Some(arguments) = node.child_by_field_name("arguments") else {
11162 return false;
11163 };
11164 return argument_children(arguments).all(|argument| {
11165 if argument.kind() == "identifier"
11166 && parameters
11167 .iter()
11168 .any(|parameter| parameter == node_text(argument, source))
11169 {
11170 return false;
11171 }
11172 macro_expansion_shape_is_safe(argument, source, parameters, environment)
11173 });
11174 }
11175 let mut stack = vec![node];
11176 while let Some(descendant) = stack.pop() {
11177 if descendant != node {
11178 if descendant.kind() == "parenthesized_expression" {
11179 continue;
11180 }
11181 if descendant.kind() == "call_expression" {
11182 let expands = descendant
11183 .child_by_field_name("function")
11184 .filter(|function| function.kind() == "identifier")
11185 .is_some_and(|function| environment.may_bind(node_text(function, source)));
11186 if expands {
11187 return false;
11188 }
11189 continue;
11190 }
11191 }
11192 if descendant.kind() == "identifier" {
11193 let identifier = node_text(descendant, source);
11194 if parameters.iter().any(|parameter| parameter == identifier)
11195 || environment.may_bind(identifier)
11196 {
11197 return false;
11198 }
11199 }
11200 for index in (0..descendant.named_child_count()).rev() {
11201 if let Some(child) = descendant.named_child(index) {
11202 stack.push(child);
11203 }
11204 }
11205 }
11206 true
11207}
11208
11209fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
11210 let text = node_text(path, source);
11211 match path.kind() {
11212 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
11213 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
11214 _ => None,
11215 }
11216}
11217
11218fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
11219 let descendant = node;
11220 while let Some(parent) = node.parent() {
11221 if is_preprocessor_conditional(parent)
11222 && !is_file_covering_include_guard(parent, source)
11223 && preprocessor_conditional_contains_descendant(parent, descendant)
11224 {
11225 return true;
11226 }
11227 node = parent;
11228 }
11229 false
11230}
11231
11232fn is_preprocessor_conditional(node: Node<'_>) -> bool {
11233 matches!(
11234 node.kind(),
11235 "preproc_if"
11236 | "preproc_ifdef"
11237 | "preproc_ifndef"
11238 | "preproc_elif"
11239 | "preproc_elifdef"
11240 | "preproc_else"
11241 )
11242}
11243
11244fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
11245 node.parent()
11246 .filter(|parent| parent.kind() == "translation_unit")
11247 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
11248 && is_canonical_include_guard(node, source)
11249}
11250
11251fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
11252 if node.kind() != "preproc_ifdef"
11253 || node
11254 .child(0)
11255 .is_none_or(|directive| directive.kind() != "#ifndef")
11256 || node.child_by_field_name("alternative").is_some()
11257 {
11258 return false;
11259 }
11260 let Some(guard_name) = node.child_by_field_name("name") else {
11261 return false;
11262 };
11263 let mut cursor = node.walk();
11264 node.named_children(&mut cursor)
11265 .find(|child| *child != guard_name && child.kind() != "comment")
11266 .filter(|child| child.kind() == "preproc_def")
11267 .and_then(|definition| definition.child_by_field_name("name"))
11268 .is_some_and(|defined_name| {
11269 node_text(defined_name, source) == node_text(guard_name, source)
11270 })
11271}
11272
11273fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
11274 let mut guard = None;
11275 for index in 0..root.named_child_count() {
11276 let Some(child) = root.named_child(index) else {
11277 continue;
11278 };
11279 if child.kind() == "comment" || is_pragma_once(child, source) {
11280 continue;
11281 }
11282 if guard.is_none() && is_canonical_include_guard(child, source) {
11283 guard = Some(child);
11284 } else {
11285 return None;
11286 }
11287 }
11288 guard
11289 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
11290 .map(|name| node_text(name, source).to_string())
11291}
11292
11293fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
11294 if (0..root.named_child_count())
11295 .filter_map(|index| root.named_child(index))
11296 .any(|child| is_pragma_once(child, source))
11297 {
11298 return MacroIncludeProtection::PragmaOnce;
11299 }
11300 top_level_canonical_include_guard_name(root, source)
11301 .map(MacroIncludeProtection::MacroGuard)
11302 .unwrap_or(MacroIncludeProtection::None)
11303}
11304
11305fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
11306 node.kind() == "preproc_call"
11307 && node
11308 .child_by_field_name("directive")
11309 .is_some_and(|directive| node_text(directive, source) == "#pragma")
11310 && node
11311 .child_by_field_name("argument")
11312 .is_some_and(|argument| node_text(argument, source).trim() == "once")
11313}
11314
11315fn parse_preproc_identifier(argument: &str) -> Option<String> {
11316 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
11317 let mut parser = Parser::new();
11318 parser
11319 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11320 .ok()?;
11321 let tree = parser.parse(&sentinel, None)?;
11322 if tree.root_node().has_error() {
11323 return None;
11324 }
11325 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
11326 let identifier = statement.named_child(0)?;
11327 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
11328 .then(|| node_text(identifier, &sentinel).to_string())
11329}
11330
11331pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
11332 match node.kind() {
11333 "identifier" | "field_identifier" => {
11334 let name = node_text(node, source).trim();
11335 (!name.is_empty()).then(|| name.to_string())
11336 }
11337 "abstract_array_declarator"
11338 | "abstract_function_declarator"
11339 | "abstract_parenthesized_declarator"
11340 | "abstract_pointer_declarator"
11341 | "abstract_reference_declarator" => None,
11342 "function_declarator" => node
11343 .child_by_field_name("declarator")
11344 .or_else(|| node.child_by_field_name("name"))
11345 .and_then(|child| extract_variable_name(child, source)),
11346 _ => node
11347 .child_by_field_name("declarator")
11348 .or_else(|| node.child_by_field_name("name"))
11349 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
11350 .and_then(|child| extract_variable_name(child, source)),
11351 }
11352}
11353
11354pub fn is_c_source_file(file: &ProjectFile) -> bool {
11365 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
11366}
11367
11368pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
11375 if !is_c_source_file(file) || node.kind() != "identifier" {
11376 return false;
11377 }
11378 let mut operand = node;
11379 while let Some(parent) = operand.parent().filter(|parent| {
11380 parent.kind() == "parenthesized_expression"
11381 && parent.named_child_count() == 1
11382 && parent.named_child(0) == Some(operand)
11383 }) {
11384 operand = parent;
11385 }
11386 operand.parent().is_some_and(|parent| {
11387 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
11388 })
11389}
11390
11391pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
11404 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
11405}
11406
11407pub fn is_declarator_node(node: Node<'_>) -> bool {
11408 matches!(
11409 node.kind(),
11410 "identifier"
11411 | "field_identifier"
11412 | "pointer_declarator"
11413 | "reference_declarator"
11414 | "array_declarator"
11415 | "parenthesized_declarator"
11416 | "function_declarator"
11417 )
11418}
11419
11420#[derive(Clone, Default)]
11421pub struct OrphanedNamespaceTypeScopeIndex {
11422 scopes: Vec<OrphanedNamespaceTypeScope>,
11423}
11424
11425#[derive(Clone)]
11426struct OrphanedNamespaceTypeScope {
11427 body_end: usize,
11428 scope_end: usize,
11429 components: Vec<String>,
11430}
11431
11432impl OrphanedNamespaceTypeScopeIndex {
11433 pub fn build(root: Node<'_>, source: &str) -> Self {
11440 let mut scopes = Vec::new();
11441 let mut stack = vec![root];
11442 while let Some(current) = stack.pop() {
11443 if current.kind() == "namespace_definition"
11444 && current.has_error()
11445 && let Some(body) = current.child_by_field_name("body")
11446 && current.end_byte() == body.end_byte()
11447 && let Some(name) = current.child_by_field_name("name")
11448 {
11449 let mut components =
11450 enclosing_namespace_components(current, source).unwrap_or_default();
11451 if append_cpp_name_components(name, source, &mut components).is_some()
11452 && !components.is_empty()
11453 {
11454 let mut scope_end = None;
11455 let mut following = current.next_named_sibling();
11456 while let Some(candidate) = following {
11457 if direct_unmatched_closing_brace(candidate)
11458 && !unmatched_closing_brace_is_followed_by_semicolon(candidate)
11459 {
11460 scope_end = Some(candidate.start_byte());
11461 break;
11462 }
11463 following = candidate.next_named_sibling();
11464 }
11465 let scope_end = scope_end.or_else(|| {
11466 std::iter::successors(current.parent(), |ancestor| ancestor.parent())
11467 .filter(|ancestor| ancestor.kind() == "namespace_definition")
11468 .filter_map(|ancestor| ancestor.child_by_field_name("body"))
11469 .map(|body| body.end_byte())
11470 .find(|end| *end > body.end_byte())
11471 });
11472 if let Some(scope_end) = scope_end {
11473 scopes.push(OrphanedNamespaceTypeScope {
11474 body_end: body.end_byte(),
11475 scope_end,
11476 components,
11477 });
11478 }
11479 }
11480 }
11481 if !current.has_error() {
11482 continue;
11483 }
11484 let mut cursor = current.walk();
11485 stack.extend(
11486 current
11487 .named_children(&mut cursor)
11488 .filter(|child| child.has_error()),
11489 );
11490 }
11491 Self { scopes }
11492 }
11493
11494 pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
11495 self.scopes
11496 .iter()
11497 .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
11498 .max_by_key(|scope| (scope.components.len(), scope.body_end))
11499 .map(|scope| (scope.body_end, scope.components.as_slice()))
11500 }
11501}
11502
11503#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11504pub enum RecoveredDeclaratorTypeContext {
11505 Declaration,
11506 FunctionDefinition,
11507 Parameter,
11508}
11509
11510pub fn recovered_macro_decorated_declarator_type(
11525 node: Node<'_>,
11526) -> Option<RecoveredDeclaratorTypeContext> {
11527 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
11528}
11529
11530pub fn recovered_macro_decorated_type_node(
11535 node: Node<'_>,
11536) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
11537 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
11538 return None;
11539 }
11540 let qualified = node.parent()?;
11541 if qualified.kind() != "qualified_identifier"
11542 || qualified.child_by_field_name("scope") != Some(node)
11543 || !(0..qualified.child_count())
11544 .filter_map(|index| qualified.child(index))
11545 .any(|child| child.kind() == "::" && child.is_missing())
11546 {
11547 return None;
11548 }
11549 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
11550 return None;
11551 }
11552
11553 let (declaration, context) = recovered_declarator_container(qualified)?;
11554 let type_node = declaration
11555 .child_by_field_name("type")
11556 .filter(|type_node| {
11557 *type_node != qualified
11558 && !type_node.is_missing()
11559 && type_node.start_byte() != type_node.end_byte()
11560 })?;
11561 Some((type_node, context))
11562}
11563
11564fn recovered_declarator_container(
11565 mut declarator: Node<'_>,
11566) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
11567 loop {
11568 let parent = declarator.parent()?;
11569 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
11570 return Some((
11571 parent
11572 .parent()
11573 .filter(|declaration| declaration.kind() == "declaration")?,
11574 RecoveredDeclaratorTypeContext::Declaration,
11575 ));
11576 }
11577 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
11578 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
11579 }
11580 if parent.kind() == "function_definition"
11581 && has_field_child(parent, "declarator", declarator)
11582 {
11583 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
11584 }
11585 if matches!(
11591 parent.kind(),
11592 "parameter_declaration" | "optional_parameter_declaration"
11593 ) && has_field_child(parent, "declarator", declarator)
11594 {
11595 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
11596 }
11597 if !matches!(
11598 parent.kind(),
11599 "array_declarator"
11600 | "function_declarator"
11601 | "parenthesized_declarator"
11602 | "pointer_declarator"
11603 | "pointer_type_declarator"
11604 | "reference_declarator"
11605 ) || !has_field_child(parent, "declarator", declarator)
11606 {
11607 return None;
11608 }
11609 declarator = parent;
11610 }
11611}
11612
11613fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
11614 let mut cursor = parent.walk();
11615 parent
11616 .children_by_field_name(field, &mut cursor)
11617 .any(|child| child == target)
11618}
11619
11620fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
11621 loop {
11622 if node.is_missing() || node.start_byte() == node.end_byte() {
11623 return false;
11624 }
11625 match node.kind() {
11626 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
11627 return true;
11628 }
11629 "array_declarator"
11630 | "function_declarator"
11631 | "parenthesized_declarator"
11632 | "pointer_declarator"
11633 | "pointer_type_declarator"
11634 | "reference_declarator" => {
11635 let Some(declarator) = node.child_by_field_name("declarator") else {
11636 return false;
11637 };
11638 node = declarator;
11639 }
11640 _ => return false,
11641 }
11642 }
11643}
11644
11645pub enum DesignatedInitializerOwner {
11647 Resolved(CodeUnit),
11648 Unresolved,
11649}
11650
11651pub fn designated_initializer_owner(
11662 visibility: &VisibilityIndex<'_>,
11663 file: &ProjectFile,
11664 source: &str,
11665 node: Node<'_>,
11666) -> Option<DesignatedInitializerOwner> {
11667 if let Some(designator) = node
11668 .parent()
11669 .filter(|parent| parent.kind() == "field_designator")
11670 {
11671 let pair = designator.parent()?;
11672 if pair.kind() != "initializer_pair"
11673 || pair.child_by_field_name("designator") != Some(designator)
11674 {
11675 return None;
11676 }
11677 let initializer = pair.parent()?;
11678 if initializer.kind() != "initializer_list" {
11679 return None;
11680 }
11681 return Some(classified_designated_owner(initializer_list_owner(
11682 visibility,
11683 file,
11684 source,
11685 initializer,
11686 )));
11687 }
11688
11689 let init_declarator = node.parent()?;
11690 if init_declarator.child_by_field_name("declarator") != Some(node)
11691 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
11692 {
11693 return None;
11694 }
11695 Some(classified_designated_owner(declaration_owner(
11696 visibility,
11697 file,
11698 source,
11699 init_declarator.parent()?,
11700 )))
11701}
11702
11703fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
11704 owner.map_or(
11705 DesignatedInitializerOwner::Unresolved,
11706 DesignatedInitializerOwner::Resolved,
11707 )
11708}
11709
11710fn initializer_list_owner(
11711 visibility: &VisibilityIndex<'_>,
11712 file: &ProjectFile,
11713 source: &str,
11714 initializer: Node<'_>,
11715) -> Option<CodeUnit> {
11716 let mut current = initializer;
11717 let mut outer_initializer_lists = 0usize;
11718 loop {
11719 let parent = current.parent()?;
11720 match parent.kind() {
11721 "initializer_pair" => return None,
11722 "initializer_list" => {
11723 outer_initializer_lists += 1;
11724 if outer_initializer_lists > 1 {
11725 return None;
11726 }
11727 current = parent;
11728 }
11729 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
11730 let declaration = parent.parent()?;
11731 if outer_initializer_lists == 1
11732 && !parent
11733 .child_by_field_name("declarator")
11734 .is_some_and(contains_array_declarator)
11735 {
11736 return None;
11737 }
11738 return declaration_owner(visibility, file, source, declaration);
11739 }
11740 "compound_literal_expression"
11741 if parent.child_by_field_name("value") == Some(current)
11742 && outer_initializer_lists == 0 =>
11743 {
11744 let type_node = parent.child_by_field_name("type")?;
11745 return resolve_designated_owner_type(visibility, file, source, type_node);
11746 }
11747 "ERROR" => current = parent,
11748 _ => return None,
11749 }
11750 }
11751}
11752
11753fn declaration_owner(
11754 visibility: &VisibilityIndex<'_>,
11755 file: &ProjectFile,
11756 source: &str,
11757 declaration: Node<'_>,
11758) -> Option<CodeUnit> {
11759 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
11760 return None;
11761 }
11762 let type_node = declaration
11763 .child_by_field_name("type")
11764 .or_else(|| first_type_child(declaration))?;
11765 resolve_designated_owner_type(visibility, file, source, type_node)
11766}
11767
11768fn resolve_designated_owner_type(
11769 visibility: &VisibilityIndex<'_>,
11770 file: &ProjectFile,
11771 source: &str,
11772 type_node: Node<'_>,
11773) -> Option<CodeUnit> {
11774 let type_name = normalize_type_text(node_text(type_node, source));
11775 visibility
11776 .resolve_type(file, &type_name)
11777 .filter(CodeUnit::is_class)
11778}
11779
11780fn contains_array_declarator(declarator: Node<'_>) -> bool {
11781 let mut stack = vec![declarator];
11782 while let Some(node) = stack.pop() {
11783 if node.kind() == "array_declarator" {
11784 return true;
11785 }
11786 if matches!(node.kind(), "initializer_list" | "compound_statement") {
11787 continue;
11788 }
11789 let mut cursor = node.walk();
11790 stack.extend(node.named_children(&mut cursor));
11791 }
11792 false
11793}
11794
11795pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
11796 let mut cursor = node.walk();
11797 node.named_children(&mut cursor).find(|child| {
11798 matches!(
11799 child.kind(),
11800 "type_identifier"
11801 | "primitive_type"
11802 | "qualified_identifier"
11803 | "scoped_type_identifier"
11804 | "struct_specifier"
11805 | "union_specifier"
11806 | "enum_specifier"
11807 )
11808 })
11809}
11810
11811pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
11812 visibility: &VisibilityIndex<'_>,
11813 file: &ProjectFile,
11814 source: &str,
11815 declarator: Node<'_>,
11816 type_text: Option<&str>,
11817 bindings: &LocalInferenceEngine<T>,
11818) -> bool {
11819 if !has_ancestor_kind(declarator, "compound_statement") {
11820 return false;
11821 }
11822 if declarator
11823 .child_by_field_name("declarator")
11824 .is_none_or(|declarator| declarator.kind() != "identifier")
11825 {
11826 return false;
11827 }
11828 if !type_text
11829 .and_then(|text| visibility.resolve_type(file, text))
11830 .is_some_and(|unit| unit.is_class())
11831 {
11832 return false;
11833 }
11834 declarator
11835 .child_by_field_name("parameters")
11836 .is_some_and(|parameters| {
11837 constructor_parameters_look_like_expressions(parameters, source, bindings)
11838 })
11839}
11840
11841fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
11842 parameters: Node<'_>,
11843 source: &str,
11844 bindings: &LocalInferenceEngine<T>,
11845) -> bool {
11846 let mut cursor = parameters.walk();
11847 parameters.named_children(&mut cursor).any(|parameter| {
11848 !matches!(
11849 parameter.kind(),
11850 "parameter_declaration" | "optional_parameter_declaration"
11851 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
11852 })
11853}
11854
11855fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
11856 parameter: Node<'_>,
11857 source: &str,
11858 bindings: &LocalInferenceEngine<T>,
11859) -> bool {
11860 let text = node_text(parameter, source).trim();
11861 if text
11862 .chars()
11863 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
11864 && bindings.is_shadowed(text)
11865 {
11866 return true;
11867 }
11868
11869 let Some(base) = parameter
11870 .child_by_field_name("type")
11871 .filter(|base| base.kind() == "type_identifier")
11872 else {
11873 return false;
11874 };
11875 let Some(subscript) = parameter
11876 .child_by_field_name("declarator")
11877 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
11878 else {
11879 return false;
11880 };
11881 subscript.child_by_field_name("size").is_some()
11882 && bindings.is_shadowed(node_text(base, source).trim())
11883}
11884
11885pub fn is_declaration_name(node: Node<'_>) -> bool {
11886 let Some(parent) = node.parent() else {
11887 return false;
11888 };
11889 if parent
11890 .child_by_field_name("name")
11891 .is_some_and(|name| same_node(name, node))
11892 {
11893 if matches!(
11894 parent.kind(),
11895 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
11896 ) {
11897 return cpp_tag_specifier_declares_name(parent);
11898 }
11899 if matches!(
11900 parent.kind(),
11901 "namespace_definition"
11902 | "namespace_alias_definition"
11903 | "alias_declaration"
11904 | "enumerator"
11905 ) {
11906 return true;
11907 }
11908 }
11909
11910 let mut current = Some(parent);
11911 while let Some(ancestor) = current {
11912 let type_definition = ancestor.kind() == "type_definition";
11913 let mut declarator_cursor = ancestor.walk();
11914 if ancestor
11915 .children_by_field_name("declarator", &mut declarator_cursor)
11916 .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
11917 {
11918 return true;
11919 }
11920 if matches!(
11921 ancestor.kind(),
11922 "declaration"
11923 | "field_declaration"
11924 | "parameter_declaration"
11925 | "optional_parameter_declaration"
11926 | "function_definition"
11927 | "type_definition"
11928 | "alias_declaration"
11929 | "class_specifier"
11930 | "struct_specifier"
11931 | "union_specifier"
11932 | "enum_specifier"
11933 ) {
11934 return false;
11935 }
11936 current = ancestor.parent();
11937 }
11938 false
11939}
11940
11941pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
11950 if !matches!(
11951 node.kind(),
11952 "qualified_identifier" | "scoped_type_identifier"
11953 ) {
11954 return false;
11955 }
11956 let Some(declaration) = node
11957 .parent()
11958 .filter(|parent| parent.kind() == "declaration")
11959 else {
11960 return false;
11961 };
11962 if declaration.child_by_field_name("declarator") != Some(node)
11963 || !declaration
11964 .child_by_field_name("type")
11965 .is_some_and(|friend| {
11966 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
11967 })
11968 {
11969 return false;
11970 }
11971 let mut cursor = declaration.walk();
11972 let mut errors = declaration
11973 .named_children(&mut cursor)
11974 .filter(|child| child.kind() == "ERROR");
11975 let Some(error) = errors.next() else {
11976 return false;
11977 };
11978 errors.next().is_none()
11979 && error.named_child_count() == 1
11980 && error.named_child(0).is_some_and(|class| {
11981 class.kind() == "identifier" && node_text(class, source) == "class"
11982 })
11983}
11984
11985pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
11986 if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
11987 return false;
11988 }
11989 if let Some(parent) = node.parent() {
11990 if parent.kind() == "call_expression"
11991 && parent.child_by_field_name("function") == Some(node)
11992 {
11993 return false;
11994 }
11995 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
11996 && parent.child_by_field_name("label") == Some(node)
11997 {
11998 return false;
11999 }
12000 }
12001 let mut current = node.parent();
12002 while let Some(ancestor) = current {
12003 match ancestor.kind() {
12004 "preproc_ifdef" | "preproc_ifndef" => {
12005 if ancestor
12006 .child_by_field_name("name")
12007 .is_some_and(|name| node_range_contains(name, node))
12008 {
12009 return false;
12010 }
12011 }
12012 "preproc_if" | "preproc_elif" => {
12013 if ancestor
12014 .child_by_field_name("condition")
12015 .is_some_and(|condition| node_range_contains(condition, node))
12016 {
12017 return false;
12018 }
12019 }
12020 "preproc_else" => {}
12021 kind if kind.starts_with("preproc_") => return false,
12022 _ => {}
12023 }
12024 if matches!(
12025 ancestor.kind(),
12026 "translation_unit" | "function_definition" | "compound_statement"
12027 ) {
12028 break;
12029 }
12030 current = ancestor.parent();
12031 }
12032 true
12033}
12034
12035fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
12036 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
12037}
12038
12039fn recovered_c_reference_node(
12040 visibility: &VisibilityIndex<'_>,
12041 file: &ProjectFile,
12042 node: Node<'_>,
12043 source: &str,
12044) -> bool {
12045 if node.start_byte() >= node.end_byte()
12046 || node.is_error()
12047 || node.is_missing()
12048 || !matches!(
12049 node.kind(),
12050 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
12051 )
12052 || recovered_c_macro_binding_role(node)
12053 || recovered_c_label_role(node)
12054 {
12055 return false;
12056 }
12057
12058 let name = node_text(node, source);
12059 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
12060 return true;
12061 }
12062 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
12063 return true;
12064 }
12065 if is_declaration_name(node) {
12066 return false;
12067 }
12068 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
12069 return true;
12070 }
12071 recovered_c_reference_anchor(node)
12072}
12073
12074fn recovered_c_explicit_assignment_callee(
12075 visibility: &VisibilityIndex<'_>,
12076 file: &ProjectFile,
12077 node: Node<'_>,
12078 name: &str,
12079) -> bool {
12080 let mut current = node;
12081 let error = loop {
12082 let Some(parent) = current.parent() else {
12083 return false;
12084 };
12085 if parent.is_error() {
12086 break parent;
12087 }
12088 current = parent;
12089 };
12090 let mut cursor = error.walk();
12091 let explicit_recovery_precedes_callee = error
12092 .named_children(&mut cursor)
12093 .take_while(|child| child.start_byte() < node.start_byte())
12094 .any(|child| child.kind() == "explicit_function_specifier");
12095 if !explicit_recovery_precedes_callee {
12096 return false;
12097 }
12098 visibility
12099 .cpp
12100 .declarations(file)
12101 .iter()
12102 .chain(visibility.visible_by_file.get(file).into_iter().flatten())
12103 .any(|candidate| candidate.identifier() == name && candidate.is_function())
12104}
12105
12106fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
12107 while let Some(parent) = node.parent() {
12108 if matches!(
12109 parent.kind(),
12110 "preproc_def" | "preproc_function_def" | "preproc_params"
12111 ) {
12112 return true;
12113 }
12114 if parent.is_error()
12115 || matches!(
12116 parent.kind(),
12117 "translation_unit" | "function_definition" | "compound_statement"
12118 )
12119 {
12120 return false;
12121 }
12122 node = parent;
12123 }
12124 false
12125}
12126
12127fn recovered_c_label_role(node: Node<'_>) -> bool {
12128 node.parent().is_some_and(|parent| {
12129 matches!(parent.kind(), "labeled_statement" | "goto_statement")
12130 && parent.child_by_field_name("label") == Some(node)
12131 })
12132}
12133
12134fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
12135 while let Some(parent) = node.parent() {
12136 if parent.is_error() {
12137 return false;
12138 }
12139 if parent.kind().ends_with("_expression")
12140 || matches!(
12141 parent.kind(),
12142 "argument_list"
12143 | "return_statement"
12144 | "expression_statement"
12145 | "case_statement"
12146 | "initializer_list"
12147 | "init_declarator"
12148 | "array_declarator"
12149 | "field_designator"
12150 | "enumerator"
12151 )
12152 {
12153 return true;
12154 }
12155 if matches!(
12156 parent.kind(),
12157 "translation_unit"
12158 | "function_definition"
12159 | "compound_statement"
12160 | "declaration"
12161 | "field_declaration"
12162 | "parameter_declaration"
12163 ) {
12164 return false;
12165 }
12166 node = parent;
12167 }
12168 false
12169}
12170
12171pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
12179 let mut current = parameter.parent();
12180 while let Some(ancestor) = current {
12181 if ancestor.kind() == "lambda_expression" {
12182 return ancestor
12183 .child_by_field_name("declarator")
12184 .is_some_and(|declarator| {
12185 declarator.start_byte() <= parameter.start_byte()
12186 && parameter.end_byte() <= declarator.end_byte()
12187 });
12188 }
12189 if ancestor.kind() == "function_definition" {
12190 return ancestor
12191 .child_by_field_name("declarator")
12192 .is_some_and(|declarator| {
12193 declarator.start_byte() <= parameter.start_byte()
12194 && parameter.end_byte() <= declarator.end_byte()
12195 });
12196 }
12197 current = ancestor.parent();
12198 }
12199 false
12200}
12201
12202pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
12203 let mut current = node.parent();
12204 while let Some(ancestor) = current {
12205 if matches!(
12206 ancestor.kind(),
12207 "parameter_declaration" | "optional_parameter_declaration"
12208 ) {
12209 return ancestor
12210 .child_by_field_name("type")
12211 .is_some_and(|type_node| {
12212 type_node.start_byte() <= node.start_byte()
12213 && node.end_byte() <= type_node.end_byte()
12214 });
12215 }
12216 if matches!(
12217 ancestor.kind(),
12218 "function_definition" | "lambda_expression" | "compound_statement"
12219 ) {
12220 return false;
12221 }
12222 current = ancestor.parent();
12223 }
12224 false
12225}
12226
12227fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
12228 if specifier.child_by_field_name("body").is_some() {
12229 return true;
12230 }
12231 let mut current = specifier.parent();
12232 while let Some(ancestor) = current {
12233 match ancestor.kind() {
12234 "type_descriptor"
12235 | "parameter_declaration"
12236 | "optional_parameter_declaration"
12237 | "template_argument_list"
12238 | "cast_expression" => return false,
12239 "declaration" | "field_declaration" => {
12240 let mut cursor = ancestor.walk();
12241 return ancestor
12242 .children_by_field_name("declarator", &mut cursor)
12243 .next()
12244 .is_none();
12245 }
12246 "translation_unit" => return true,
12247 _ => current = ancestor.parent(),
12248 }
12249 }
12250 false
12251}
12252
12253pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
12254 match node.kind() {
12255 "identifier"
12256 | "field_identifier"
12257 | "qualified_identifier"
12258 | "scoped_identifier"
12259 | "operator_name"
12260 | "destructor_name"
12261 | "literal_operator_name" => Some(node),
12262 "reference_declarator" | "parenthesized_declarator" => {
12263 node.named_child(0).and_then(declarator_name_node)
12264 }
12265 _ => node
12266 .child_by_field_name("declarator")
12267 .or_else(|| node.child_by_field_name("name"))
12268 .or_else(|| node.child_by_field_name("field"))
12269 .and_then(declarator_name_node),
12270 }
12271}
12272
12273fn declarator_name_path_contains(
12274 declarator: Node<'_>,
12275 candidate: Node<'_>,
12276 allow_type_identifier: bool,
12277) -> bool {
12278 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
12279 return false;
12280 };
12281 let mut current = Some(declarator);
12282 while let Some(node) = current {
12283 if same_node(node, candidate) {
12284 return true;
12285 }
12286 if same_node(node, name) {
12287 return false;
12288 }
12289 current = node
12290 .child_by_field_name("declarator")
12291 .or_else(|| node.child_by_field_name("name"))
12292 .or_else(|| node.child_by_field_name("field"));
12293 }
12294 false
12295}
12296
12297fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
12298 match node.kind() {
12299 "identifier"
12300 | "field_identifier"
12301 | "operator_name"
12302 | "destructor_name"
12303 | "literal_operator_name" => Some(node),
12304 "type_identifier" if allow_type_identifier => Some(node),
12305 _ => node
12306 .child_by_field_name("declarator")
12307 .or_else(|| node.child_by_field_name("name"))
12308 .or_else(|| node.child_by_field_name("field"))
12309 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
12310 }
12311}
12312
12313pub fn is_nested_type_node(node: Node<'_>) -> bool {
12316 node.parent().is_some_and(|parent| {
12317 matches!(
12318 parent.kind(),
12319 "qualified_identifier" | "scoped_type_identifier" | "template_type"
12320 )
12321 })
12322}
12323
12324pub struct OutOfLineMemberDefinitionOwners<'tree> {
12325 pub owners: Vec<(Node<'tree>, CodeUnit)>,
12326 innermost: Option<(Node<'tree>, CodeUnit)>,
12327}
12328
12329impl OutOfLineMemberDefinitionOwners<'_> {
12330 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
12331 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
12332 }
12333}
12334
12335pub struct QualifiedOwnerComponents<'tree> {
12336 pub nodes: Vec<Node<'tree>>,
12337 pub names: Vec<String>,
12338 pub global: bool,
12339}
12340
12341pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
12346 let mut stack = vec![node];
12347 let mut found_separator = false;
12348 while let Some(current) = stack.pop() {
12349 if !matches!(
12350 current.kind(),
12351 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12352 ) {
12353 continue;
12354 }
12355 let mut current_has_separator = false;
12356 for index in 0..current.child_count() {
12357 let Some(child) = current.child(index) else {
12358 continue;
12359 };
12360 if child.kind() == "::" {
12361 if child.is_missing() {
12362 return false;
12363 }
12364 current_has_separator = true;
12365 found_separator = true;
12366 }
12367 }
12368 if !current_has_separator {
12369 return false;
12370 }
12371 for field in ["scope", "name"] {
12372 if let Some(child) = current.child_by_field_name(field)
12373 && matches!(
12374 child.kind(),
12375 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12376 )
12377 {
12378 stack.push(child);
12379 }
12380 }
12381 }
12382 found_separator
12383}
12384
12385pub fn qualified_owner_components<'tree>(
12386 node: Node<'tree>,
12387 source: &str,
12388) -> Option<QualifiedOwnerComponents<'tree>> {
12389 if !qualified_name_has_concrete_scope_separators(node) {
12390 return None;
12391 }
12392 let mut nodes = cpp_name_component_nodes(node)?;
12393 nodes.pop()?;
12394 if nodes.is_empty() {
12395 return None;
12396 }
12397 let names = nodes
12398 .iter()
12399 .map(|component| node_text(*component, source).to_string())
12400 .collect();
12401 Some(QualifiedOwnerComponents {
12402 nodes,
12403 names,
12404 global: is_globally_qualified_cpp_name(node),
12405 })
12406}
12407
12408pub fn out_of_line_member_definition_owner<'tree>(
12409 analyzer: &CppGraphSource<'_>,
12410 visibility: &VisibilityIndex<'_>,
12411 file: &ProjectFile,
12412 source: &str,
12413 node: Node<'tree>,
12414) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
12415 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
12416 || !has_ancestor_kind(node, "function_definition")
12417 || !is_function_declarator_name_root(node)
12418 {
12419 return None;
12420 }
12421 let qualified = qualified_owner_components(node, source)?;
12422 let lexical_scope = enclosing_namespace_components(node, source)?;
12423 let mut owners = Vec::new();
12424 let mut innermost = None;
12425
12426 for component_count in 1..=qualified.names.len() {
12427 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
12428 .resolve_type_components_lexically(
12429 analyzer,
12430 file,
12431 &qualified.names[..component_count],
12432 qualified.global,
12433 &lexical_scope,
12434 )
12435 && !owners
12436 .iter()
12437 .any(|(_, existing)| same_visible_symbol(existing, &unit))
12438 {
12439 if component_count == qualified.names.len() {
12440 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
12441 }
12442 owners.push((qualified.nodes[component_count - 1], unit));
12443 }
12444 }
12445
12446 if innermost.is_none() {
12456 let indexed_owner_components = visibility
12457 .indexed_enclosing_owner_scope(analyzer, file, node)
12458 .or_else(|| {
12459 if qualified.names.len() <= 1 {
12464 return None;
12465 }
12466 let range = Range {
12467 start_byte: node.start_byte(),
12468 end_byte: node.end_byte(),
12469 start_line: node.start_position().row,
12470 end_line: node.end_position().row,
12471 };
12472 let start = analyzer.enclosing_code_unit(file, &range)?;
12473 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12474 brokk_bifrost_core::analyzer::Language::Cpp,
12475 &cpp_name_for(&start),
12476 );
12477 components.pop();
12478 Some(components)
12479 });
12480 if let Some(indexed_owner_components) = indexed_owner_components
12481 && indexed_owner_components.len() > qualified.names.len()
12482 && indexed_owner_components.ends_with(&qualified.names)
12483 && indexed_namespace_path_is_recoverable(
12484 &lexical_scope,
12485 &indexed_owner_components,
12486 qualified.names.len(),
12487 )
12488 && (qualified.names.len() > 1 || !qualified.global)
12493 {
12494 let namespace_count = indexed_owner_components.len() - qualified.names.len();
12495 for component_count in 1..=qualified.names.len() {
12496 let expected = &indexed_owner_components[..namespace_count + component_count];
12497 let owner_node = qualified.nodes[component_count - 1];
12498 for owner in visibility
12499 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
12500 .filter(|candidate| candidate.is_class())
12501 .filter(|candidate| {
12502 canonical_cpp_scope_components(candidate) == expected
12503 && visibility.external_type_candidate_visible_in_context(
12504 analyzer, file, candidate, node,
12505 )
12506 })
12507 {
12508 if component_count == qualified.names.len() && innermost.is_none() {
12509 innermost = Some((owner_node, owner.clone()));
12510 }
12511 if !owners
12512 .iter()
12513 .any(|(_, existing)| same_symbol(existing, owner))
12514 {
12515 owners.push((owner_node, owner.clone()));
12516 }
12517 }
12518 }
12519 }
12520 }
12521 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
12522}
12523
12524fn is_function_declarator_name_root(node: Node<'_>) -> bool {
12525 let mut current = node;
12526 while let Some(parent) = current.parent() {
12527 if parent.kind() == "function_declarator" {
12528 return parent.child_by_field_name("declarator") == Some(current);
12529 }
12530 if matches!(
12531 parent.kind(),
12532 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
12533 ) && parent.child_by_field_name("declarator") == Some(current)
12534 {
12535 current = parent;
12536 continue;
12537 }
12538 return false;
12539 }
12540 false
12541}
12542
12543pub fn append_cpp_name_components(
12544 node: Node<'_>,
12545 source: &str,
12546 out: &mut Vec<String>,
12547) -> Option<()> {
12548 out.extend(
12549 cpp_name_component_nodes(node)?
12550 .into_iter()
12551 .map(|component| node_text(component, source).to_string()),
12552 );
12553 Some(())
12554}
12555
12556pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
12557 let mut components = Vec::new();
12558 append_cpp_name_components(node, source, &mut components)?;
12559 Some(components)
12560}
12561
12562pub fn unique_macro_replacement_type_candidate(
12571 analyzer: &CppGraphSource<'_>,
12572 visibility: &VisibilityIndex<'_>,
12573 file: &ProjectFile,
12574 components: &[String],
12575) -> Option<CodeUnit> {
12576 let terminal = components.last()?;
12577 let mut candidates = Vec::new();
12578 for candidate in visibility
12579 .visible_identifier_candidates(file, terminal)
12580 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
12581 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
12582 {
12583 if !candidates
12584 .iter()
12585 .any(|existing| same_logical_symbol(existing, candidate))
12586 {
12587 candidates.push(candidate.clone());
12588 }
12589 }
12590 (candidates.len() == 1).then(|| candidates.remove(0))
12591}
12592
12593pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
12601 let mut parser = Parser::new();
12602 if parser
12603 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12604 .is_err()
12605 {
12606 return Vec::new();
12607 }
12608 let Some(tree) = parser.parse(source, None) else {
12609 return Vec::new();
12610 };
12611 let mut scopes = Vec::new();
12612 let mut pending = vec![tree.root_node()];
12613 while let Some(node) = pending.pop() {
12614 if node.kind() == "using_declaration" {
12615 let Some(imported) = node.named_child(0) else {
12616 continue;
12617 };
12618 let Some(mut components) = cpp_type_name_components(imported, source) else {
12619 continue;
12620 };
12621 if components.pop().as_deref() == Some(member) && !components.is_empty() {
12622 scopes.push(components.join("::"));
12623 }
12624 continue;
12625 }
12626 for index in (0..node.named_child_count()).rev() {
12627 if let Some(child) = node.named_child(index) {
12628 pending.push(child);
12629 }
12630 }
12631 }
12632 scopes
12633}
12634
12635pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
12639 qualified == scope
12640 || qualified
12641 .strip_suffix(scope)
12642 .is_some_and(|prefix| prefix.ends_with("::"))
12643}
12644
12645pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
12650 let Some(type_descriptor) = node.parent() else {
12651 return false;
12652 };
12653 if type_descriptor.kind() != "type_descriptor"
12654 || type_descriptor.child_by_field_name("type") != Some(node)
12655 {
12656 return false;
12657 }
12658 let Some(arguments) = type_descriptor.parent() else {
12659 return false;
12660 };
12661 if arguments.kind() != "template_argument_list" {
12662 return false;
12663 }
12664 arguments.parent().is_some_and(|parent| {
12665 matches!(parent.kind(), "template_type" | "template_function")
12666 && parent.child_by_field_name("arguments") == Some(arguments)
12667 })
12668}
12669
12670pub fn cpp_template_reference_arguments(
12671 mut node: Node<'_>,
12672 source: &str,
12673) -> Option<Vec<CppTemplateExpression>> {
12674 loop {
12675 match node.kind() {
12676 "template_type" | "template_function" => {
12677 let arguments = node.child_by_field_name("arguments")?;
12678 let mut cursor = arguments.walk();
12679 return Some(
12680 arguments
12681 .named_children(&mut cursor)
12682 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
12683 .map(|argument| CppTemplateExpression {
12684 text: normalize_cpp_whitespace(node_text(argument, source)),
12685 term: cpp_template_term(
12687 argument,
12688 source,
12689 &[],
12690 &ParentIndex::unindexed(),
12691 ),
12692 })
12693 .collect(),
12694 );
12695 }
12696 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
12697 node = node
12698 .child_by_field_name("name")
12699 .or_else(|| node.child_by_field_name("type"))?;
12700 }
12701 _ => return None,
12702 }
12703 }
12704}
12705
12706fn cpp_reconcile_primary_template_parameters(
12707 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
12708 preferred: &CodeUnit,
12709) -> Option<Vec<CppTemplateParameterMetadata>> {
12710 let canonical = candidates
12711 .iter()
12712 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
12713 let mut merged = canonical
12714 .parameters
12715 .iter()
12716 .map(|parameter| CppTemplateParameterMetadata {
12717 name: parameter.name.clone(),
12718 kind: parameter.kind,
12719 variadic: parameter.variadic,
12720 default: None,
12721 })
12722 .collect::<Vec<_>>();
12723
12724 for (_, metadata) in candidates {
12725 if metadata.parameters.len() != merged.len() {
12726 return None;
12727 }
12728 let rename_bindings = metadata
12729 .parameters
12730 .iter()
12731 .zip(&merged)
12732 .map(|(parameter, canonical)| {
12733 (
12734 parameter.name.clone(),
12735 CppTemplateTerm::Parameter(canonical.name.clone()),
12736 )
12737 })
12738 .collect::<HashMap<_, _>>();
12739 for ((parameter, canonical), merged_parameter) in metadata
12740 .parameters
12741 .iter()
12742 .zip(&canonical.parameters)
12743 .zip(&mut merged)
12744 {
12745 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
12746 return None;
12747 }
12748 let Some(default) = ¶meter.default else {
12749 continue;
12750 };
12751 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
12752 if let Some(existing) = &merged_parameter.default {
12753 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
12754 return None;
12755 }
12756 } else {
12757 merged_parameter.default = Some(CppTemplateExpression {
12758 text: default.text.clone(),
12759 term: normalized_term,
12760 });
12761 }
12762 }
12763 }
12764 Some(merged)
12765}
12766
12767pub fn cpp_bind_template_arguments(
12768 parameters: &[CppTemplateParameterMetadata],
12769 explicit_arguments: &[CppTemplateExpression],
12770) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
12771 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
12772 if variadic_index.is_some_and(|index| {
12773 index + 1 != parameters.len()
12774 || parameters[index + 1..]
12775 .iter()
12776 .any(|parameter| parameter.variadic)
12777 }) {
12778 return None;
12779 }
12780 let fixed_count = variadic_index.unwrap_or(parameters.len());
12781 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
12782 return None;
12783 }
12784 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
12785 let mut expanded = explicit_arguments[..explicit_fixed_count]
12786 .iter()
12787 .map(cpp_clone_template_expression_iterative)
12788 .collect::<Vec<_>>();
12789 let mut bindings = HashMap::default();
12790 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
12791 bindings.insert(
12792 parameter.name.clone(),
12793 cpp_clone_template_term_iterative(&argument.term),
12794 );
12795 }
12796 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
12797 let default = parameter.default.as_ref()?;
12798 let term = cpp_substitute_template_term(&default.term, &bindings)?;
12799 bindings.insert(parameter.name.clone(), term.clone());
12800 expanded.push(CppTemplateExpression {
12801 text: default.text.clone(),
12802 term,
12803 });
12804 }
12805 if let Some(index) = variadic_index {
12806 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
12807 expanded.extend(
12808 packed_arguments
12809 .iter()
12810 .map(cpp_clone_template_expression_iterative),
12811 );
12812 bindings.insert(
12813 parameters[index].name.clone(),
12814 CppTemplateTerm::Node {
12815 kind: "parameter_pack".to_string(),
12816 children: packed_arguments
12817 .iter()
12818 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
12819 .collect(),
12820 },
12821 );
12822 }
12823 Some((expanded, bindings))
12824}
12825
12826fn cpp_specialization_matches(
12827 metadata: &CppTemplateMetadata,
12828 arguments: &[CppTemplateExpression],
12829) -> bool {
12830 if metadata.specialization_arguments.len() != arguments.len() {
12831 return false;
12832 }
12833 let parameter_names = metadata
12834 .parameters
12835 .iter()
12836 .map(|parameter| parameter.name.as_str())
12837 .collect::<HashSet<_>>();
12838 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
12839 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
12840 if !cpp_unify_template_term(
12841 &pattern.term,
12842 &argument.term,
12843 ¶meter_names,
12844 &mut bindings,
12845 ) {
12846 return false;
12847 }
12848 }
12849 true
12850}
12851
12852fn cpp_specialization_more_specialized(
12853 candidate: &CppTemplateMetadata,
12854 other: &CppTemplateMetadata,
12855) -> bool {
12856 cpp_specialization_pattern_accepts(other, candidate)
12857 && !cpp_specialization_pattern_accepts(candidate, other)
12858}
12859
12860fn cpp_specialization_pattern_accepts(
12861 broader: &CppTemplateMetadata,
12862 narrower: &CppTemplateMetadata,
12863) -> bool {
12864 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
12865 return false;
12866 }
12867 let parameter_names = broader
12868 .parameters
12869 .iter()
12870 .map(|parameter| parameter.name.as_str())
12871 .collect::<HashSet<_>>();
12872 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
12873 broader
12874 .specialization_arguments
12875 .iter()
12876 .zip(&narrower.specialization_arguments)
12877 .all(|(pattern, argument)| {
12878 cpp_unify_template_term(
12879 &pattern.term,
12880 &argument.term,
12881 ¶meter_names,
12882 &mut bindings,
12883 )
12884 })
12885}
12886
12887pub fn cpp_substitute_template_term(
12888 term: &CppTemplateTerm,
12889 bindings: &HashMap<String, CppTemplateTerm>,
12890) -> Option<CppTemplateTerm> {
12891 enum Work<'a> {
12892 Visit(&'a CppTemplateTerm),
12893 Build { kind: String, child_count: usize },
12894 }
12895
12896 let mut work = vec![Work::Visit(term)];
12897 let mut substituted = Vec::new();
12898 while let Some(next) = work.pop() {
12899 match next {
12900 Work::Visit(CppTemplateTerm::Parameter(name)) => {
12901 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
12902 }
12903 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
12904 substituted.push(CppTemplateTerm::Atom {
12905 kind: kind.clone(),
12906 text: text.clone(),
12907 });
12908 }
12909 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
12910 work.push(Work::Build {
12911 kind: kind.clone(),
12912 child_count: children.len(),
12913 });
12914 work.extend(children.iter().rev().map(Work::Visit));
12915 }
12916 Work::Build { kind, child_count } => {
12917 let children = substituted.split_off(substituted.len() - child_count);
12918 substituted.push(CppTemplateTerm::Node { kind, children });
12919 }
12920 }
12921 }
12922 substituted.pop()
12923}
12924
12925pub fn cpp_substitute_template_arguments(
12926 arguments: &[CppTemplateExpression],
12927 bindings: &HashMap<String, CppTemplateTerm>,
12928) -> Option<Vec<CppTemplateExpression>> {
12929 let mut substituted = Vec::new();
12930 for argument in arguments {
12931 let CppTemplateTerm::Node { kind, children } = &argument.term else {
12932 substituted.push(CppTemplateExpression {
12933 text: argument.text.clone(),
12934 term: cpp_substitute_template_term(&argument.term, bindings)?,
12935 });
12936 continue;
12937 };
12938 if kind != "parameter_pack_expansion" {
12939 substituted.push(CppTemplateExpression {
12940 text: argument.text.clone(),
12941 term: cpp_substitute_template_term(&argument.term, bindings)?,
12942 });
12943 continue;
12944 }
12945 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
12946 return None;
12947 };
12948 if ellipsis != "..." {
12949 return None;
12950 }
12951
12952 let mut pack_names = Vec::new();
12953 let mut work = vec![pattern];
12954 while let Some(term) = work.pop() {
12955 match term {
12956 CppTemplateTerm::Parameter(name)
12957 if matches!(
12958 bindings.get(name),
12959 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
12960 ) =>
12961 {
12962 if !pack_names.contains(name) {
12963 pack_names.push(name.clone());
12964 }
12965 }
12966 CppTemplateTerm::Node { children, .. } => work.extend(children),
12967 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
12968 }
12969 }
12970 let first_pack = pack_names.first()?;
12971 let CppTemplateTerm::Node {
12972 children: first_elements,
12973 ..
12974 } = bindings.get(first_pack)?
12975 else {
12976 return None;
12977 };
12978 let pack_len = first_elements.len();
12979 for pack_name in &pack_names {
12980 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
12981 return None;
12982 };
12983 if children.len() != pack_len {
12984 return None;
12985 }
12986 }
12987 for index in 0..pack_len {
12988 let mut element_bindings = bindings.clone();
12989 for pack_name in &pack_names {
12990 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
12991 return None;
12992 };
12993 element_bindings.insert(
12994 pack_name.clone(),
12995 cpp_clone_template_term_iterative(&children[index]),
12996 );
12997 }
12998 substituted.push(CppTemplateExpression {
12999 text: argument.text.clone(),
13000 term: cpp_substitute_template_term(pattern, &element_bindings)?,
13001 });
13002 }
13003 }
13004 Some(substituted)
13005}
13006
13007fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
13008 enum Work<'a> {
13009 Visit(&'a CppTemplateTerm),
13010 Build { kind: String, child_count: usize },
13011 }
13012
13013 let mut work = vec![Work::Visit(term)];
13014 let mut cloned = Vec::new();
13015 while let Some(next) = work.pop() {
13016 match next {
13017 Work::Visit(CppTemplateTerm::Parameter(name)) => {
13018 cloned.push(CppTemplateTerm::Parameter(name.clone()));
13019 }
13020 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
13021 cloned.push(CppTemplateTerm::Atom {
13022 kind: kind.clone(),
13023 text: text.clone(),
13024 });
13025 }
13026 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
13027 work.push(Work::Build {
13028 kind: kind.clone(),
13029 child_count: children.len(),
13030 });
13031 work.extend(children.iter().rev().map(Work::Visit));
13032 }
13033 Work::Build { kind, child_count } => {
13034 let children = cloned.split_off(cloned.len() - child_count);
13035 cloned.push(CppTemplateTerm::Node { kind, children });
13036 }
13037 }
13038 }
13039 cloned
13040 .pop()
13041 .expect("template term traversal emits one root")
13042}
13043
13044fn cpp_clone_template_expression_iterative(
13045 expression: &CppTemplateExpression,
13046) -> CppTemplateExpression {
13047 CppTemplateExpression {
13048 text: expression.text.clone(),
13049 term: cpp_clone_template_term_iterative(&expression.term),
13050 }
13051}
13052
13053pub fn cpp_unify_template_term(
13054 pattern: &CppTemplateTerm,
13055 argument: &CppTemplateTerm,
13056 parameters: &HashSet<&str>,
13057 bindings: &mut HashMap<String, CppTemplateTerm>,
13058) -> bool {
13059 let mut work = vec![(pattern, argument)];
13060 while let Some((pattern, argument)) = work.pop() {
13061 match pattern {
13062 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
13063 if let Some(bound) = bindings.get(name) {
13064 if !cpp_template_terms_equal(bound, argument) {
13065 return false;
13066 }
13067 } else {
13068 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
13069 }
13070 }
13071 CppTemplateTerm::Atom {
13072 kind: pattern_kind,
13073 text: pattern_text,
13074 } => {
13075 if !matches!(
13076 argument,
13077 CppTemplateTerm::Atom { kind, text }
13078 if kind == pattern_kind && text == pattern_text
13079 ) {
13080 return false;
13081 }
13082 }
13083 CppTemplateTerm::Node {
13084 kind: pattern_kind,
13085 children: pattern_children,
13086 } => {
13087 let CppTemplateTerm::Node { kind, children } = argument else {
13088 return false;
13089 };
13090 if kind != pattern_kind || children.len() != pattern_children.len() {
13091 return false;
13092 }
13093 work.extend(pattern_children.iter().zip(children).rev());
13094 }
13095 CppTemplateTerm::Parameter(_) => return false,
13096 }
13097 }
13098 true
13099}
13100
13101fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
13102 let mut work = vec![(left, right)];
13103 while let Some((left, right)) = work.pop() {
13104 match (left, right) {
13105 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
13106 if left != right {
13107 return false;
13108 }
13109 }
13110 (
13111 CppTemplateTerm::Atom {
13112 kind: left_kind,
13113 text: left_text,
13114 },
13115 CppTemplateTerm::Atom {
13116 kind: right_kind,
13117 text: right_text,
13118 },
13119 ) => {
13120 if left_kind != right_kind || left_text != right_text {
13121 return false;
13122 }
13123 }
13124 (
13125 CppTemplateTerm::Node {
13126 kind: left_kind,
13127 children: left_children,
13128 },
13129 CppTemplateTerm::Node {
13130 kind: right_kind,
13131 children: right_children,
13132 },
13133 ) => {
13134 if left_kind != right_kind || left_children.len() != right_children.len() {
13135 return false;
13136 }
13137 work.extend(left_children.iter().zip(right_children).rev());
13138 }
13139 _ => return false,
13140 }
13141 }
13142 true
13143}
13144
13145pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
13146 let mut components = Vec::new();
13147 let mut stack = vec![node];
13148 while let Some(current) = stack.pop() {
13149 match current.kind() {
13150 "identifier"
13151 | "field_identifier"
13152 | "namespace_identifier"
13153 | "type_identifier"
13154 | "operator_name"
13155 | "destructor_name" => components.push(current),
13156 "template_type" | "template_function" => {
13157 stack.push(current.child_by_field_name("name")?);
13158 }
13159 "dependent_name" => stack.push(current.named_child(0)?),
13160 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13161 stack.push(current.child_by_field_name("name")?);
13162 if let Some(scope) = current.child_by_field_name("scope") {
13163 stack.push(scope);
13164 }
13165 }
13166 "nested_namespace_specifier" => {
13167 for index in (0..current.named_child_count()).rev() {
13168 stack.push(current.named_child(index)?);
13169 }
13170 }
13171 _ => return None,
13172 }
13173 }
13174 Some(components)
13175}
13176
13177pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
13178 node.child_by_field_name("scope").is_none()
13179 && node.child(0).is_some_and(|child| child.kind() == "::")
13180}
13181
13182fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
13183 let mut namespaces = Vec::new();
13184 let mut current = node.parent();
13185 while let Some(parent) = current {
13186 if parent.kind() == "namespace_definition"
13187 && let Some(name) = parent.child_by_field_name("name")
13188 {
13189 let mut components = Vec::new();
13190 append_cpp_name_components(name, source, &mut components)?;
13191 namespaces.push(components);
13192 }
13193 current = parent.parent();
13194 }
13195 namespaces.reverse();
13196 Some(namespaces.into_iter().flatten().collect())
13197}
13198
13199fn indexed_namespace_path_is_recoverable(
13210 lexical_scope: &[String],
13211 indexed_owner_scope: &[String],
13212 explicit_owner_component_count: usize,
13213) -> bool {
13214 if lexical_scope.is_empty() {
13215 return explicit_owner_component_count > 1;
13216 }
13217 if lexical_scope.len() >= indexed_owner_scope.len() {
13218 return false;
13219 }
13220 let mut indexed = indexed_owner_scope.iter();
13221 lexical_scope
13222 .iter()
13223 .all(|component| indexed.any(|candidate| candidate == component))
13224}
13225
13226pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
13227 let mut current = node.parent();
13228 while let Some(parent) = current {
13229 if parent.kind() == kind {
13230 return true;
13231 }
13232 current = parent.parent();
13233 }
13234 false
13235}
13236
13237pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
13243 let mut current = Some(node);
13244 while let Some(candidate) = current {
13245 if candidate.kind() == "declaration" {
13246 let Some(type_node) = candidate.child_by_field_name("type") else {
13247 return false;
13248 };
13249 if !(type_node.start_byte() <= node.start_byte()
13250 && node.end_byte() <= type_node.end_byte())
13251 {
13252 return false;
13253 }
13254 let mut cursor = candidate.walk();
13255 return candidate.named_children(&mut cursor).any(|child| {
13256 child.kind() == "init_declarator"
13257 && child
13258 .child_by_field_name("value")
13259 .is_some_and(|value| value.kind() == "cast_expression")
13260 });
13261 }
13262 current = candidate.parent();
13263 }
13264 false
13265}
13266
13267#[derive(Clone, Copy, PartialEq, Eq)]
13268pub(crate) enum QualifiedAliasReferenceKind {
13269 Ordinary,
13270 ConstructorWithExpressionArgument,
13271 ExhaustiveTemplate,
13272}
13273
13274pub(crate) fn qualified_alias_reference_preserves_target(
13281 node: Node<'_>,
13282 target: &CodeUnit,
13283 analyzer: &CppGraphSource<'_>,
13284 visibility: &VisibilityIndex<'_>,
13285 file: &ProjectFile,
13286 source: &str,
13287) -> Option<QualifiedAliasReferenceKind> {
13288 if !matches!(
13289 node.kind(),
13290 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
13291 ) {
13292 return None;
13293 }
13294 let components = cpp_type_name_components(node, source)?;
13295 let name = components.last()?;
13296 analyzer.type_alias_provider().and_then(|provider| {
13297 visibility
13298 .visible_identifier_candidates(file, name)
13299 .find_map(|candidate| {
13300 let proof = provider.is_type_alias(candidate)
13301 && canonical_cpp_scope_components(candidate) == components
13302 && visibility.external_type_candidate_visible_in_context(
13303 analyzer, file, candidate, node,
13304 )
13305 && match cpp_template_reference_arguments(node, source) {
13306 Some(arguments) => visibility.template_alias_arguments_preserve_target(
13307 analyzer, file, candidate, &arguments, target,
13308 ),
13309 None => visibility.structured_alias_primary_preserves_target(
13310 analyzer, file, candidate, target,
13311 ),
13312 };
13313 proof.then(|| {
13314 if cpp_template_reference_arguments(node, source).is_some()
13315 && visibility.is_exhaustive_same_fqn_type_declaration_family(
13316 analyzer, file, candidate,
13317 )
13318 {
13319 QualifiedAliasReferenceKind::ExhaustiveTemplate
13320 } else if qualified_alias_constructor_has_expression_argument(node)
13321 || qualified_alias_local_constructor_declaration(node)
13322 {
13323 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
13324 } else {
13325 QualifiedAliasReferenceKind::Ordinary
13326 }
13327 })
13328 })
13329 })
13330}
13331
13332pub(crate) fn qualified_alias_reference_requires_terminal(
13333 reference: Option<QualifiedAliasReferenceKind>,
13334) -> bool {
13335 matches!(
13336 reference,
13337 Some(
13338 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
13339 | QualifiedAliasReferenceKind::ExhaustiveTemplate
13340 )
13341 )
13342}
13343
13344fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
13345 let Some(declaration) = node.parent().filter(|parent| {
13346 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
13347 }) else {
13348 return false;
13349 };
13350 let mut cursor = declaration.walk();
13351 declaration.named_children(&mut cursor).any(|child| {
13352 child.kind() == "init_declarator"
13353 && child
13354 .child_by_field_name("value")
13355 .filter(|value| value.kind() == "argument_list")
13356 .is_some_and(|arguments| {
13357 let mut cursor = arguments.walk();
13358 arguments.named_children(&mut cursor).any(|argument| {
13359 let is_parameter = matches!(
13360 argument.kind(),
13361 "parameter_declaration" | "optional_parameter_declaration"
13362 );
13363 if is_parameter {
13364 argument
13365 .child_by_field_name("type")
13366 .is_some_and(|type_node| {
13367 type_node.kind() == "type_identifier"
13368 && argument.child_by_field_name("declarator").is_none()
13369 })
13370 } else {
13371 !argument.kind().ends_with("_literal")
13372 && !matches!(argument.kind(), "true" | "false" | "nullptr")
13373 }
13374 })
13375 })
13376 })
13377}
13378
13379fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
13384 let Some(declaration) = node.parent().filter(|parent| {
13385 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
13386 }) else {
13387 return false;
13388 };
13389 if declaration
13390 .parent()
13391 .is_none_or(|parent| parent.kind() != "compound_statement")
13392 {
13393 return false;
13394 }
13395 let mut cursor = declaration.walk();
13396 declaration
13397 .named_children(&mut cursor)
13398 .any(|child| child.kind() == "function_declarator")
13399}
13400
13401pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
13407 loop {
13408 let next = match node.kind() {
13409 "qualified_identifier"
13410 | "scoped_identifier"
13411 | "template_method"
13412 | "template_function"
13413 | "template_type" => node.child_by_field_name("name"),
13414 "field_expression" => node.child_by_field_name("field"),
13415 _ => None,
13416 };
13417 let Some(next) = next else {
13418 return node;
13419 };
13420 node = next;
13421 }
13422}
13423
13424#[derive(Clone, Copy)]
13425pub struct RecoveredRelationalTemplateMemberCall<'tree> {
13426 pub receiver: Node<'tree>,
13427 pub member: Node<'tree>,
13428 pub arity: usize,
13429}
13430
13431pub fn recovered_relational_template_member_call(
13439 field: Node<'_>,
13440) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
13441 if field.kind() != "field_expression" {
13442 return None;
13443 }
13444 let receiver = field
13445 .child_by_field_name("argument")
13446 .or_else(|| field.child_by_field_name("object"))?;
13447 let member = field.child_by_field_name("field")?;
13448 let less = field.parent()?;
13449 if less.kind() != "binary_expression"
13450 || less.child_by_field_name("left") != Some(field)
13451 || less
13452 .child_by_field_name("operator")
13453 .is_none_or(|operator| operator.kind() != "<")
13454 || less.child_by_field_name("right").is_none()
13455 {
13456 return None;
13457 }
13458 let greater = less.parent()?;
13459 if greater.kind() != "binary_expression"
13460 || greater.child_by_field_name("left") != Some(less)
13461 || greater
13462 .child_by_field_name("operator")
13463 .is_none_or(|operator| operator.kind() != ">")
13464 {
13465 return None;
13466 }
13467 let arguments = greater.child_by_field_name("right")?;
13468 if arguments.kind() != "parenthesized_expression" {
13469 return None;
13470 }
13471 let arity = parenthesized_call_argument_arity(arguments)?;
13472 Some(RecoveredRelationalTemplateMemberCall {
13473 receiver,
13474 member,
13475 arity,
13476 })
13477}
13478
13479fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
13480 let expression = arguments.named_child(0)?;
13481 if expression.kind() != "comma_expression" {
13482 return Some(1);
13483 }
13484 let mut arity = 0usize;
13485 let mut stack = vec![expression];
13486 while let Some(node) = stack.pop() {
13487 if node.kind() == "comma_expression" {
13488 stack.push(node.child_by_field_name("right")?);
13489 stack.push(node.child_by_field_name("left")?);
13490 } else {
13491 arity += 1;
13492 }
13493 }
13494 Some(arity)
13495}
13496
13497pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
13500 while let Some(parent) = node.parent() {
13501 match parent.kind() {
13502 "call_expression" => {
13503 return parent
13504 .child_by_field_name("function")
13505 .or_else(|| parent.named_child(0))
13506 == Some(node);
13507 }
13508 "qualified_identifier"
13509 | "scoped_identifier"
13510 | "template_function"
13511 | "template_type"
13512 | "field_expression" => node = parent,
13513 _ => return false,
13514 }
13515 }
13516 false
13517}
13518
13519pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
13520 if is_call_callee_node(node) {
13521 function_terminal_node(node)
13522 } else {
13523 node
13524 }
13525}
13526
13527pub fn normalize_type_text(value: &str) -> String {
13528 strip_tag_type_prefix(
13529 normalize_cpp_whitespace(value)
13530 .trim_start_matches("const ")
13531 .trim_end_matches('*')
13532 .trim_end_matches('&')
13533 .trim(),
13534 )
13535 .to_string()
13536}
13537
13538fn strip_tag_type_prefix(value: &str) -> &str {
13539 let value = value.trim_start_matches("const ");
13540 value
13541 .strip_prefix("struct ")
13542 .or_else(|| value.strip_prefix("class "))
13543 .or_else(|| value.strip_prefix("enum "))
13544 .unwrap_or(value)
13545 .trim()
13546}
13547
13548pub fn normalize_reference_name(value: &str) -> Option<String> {
13549 let normalized = normalize_cpp_reference_text(value);
13550 (!normalized.is_empty()).then_some(normalized)
13551}
13552
13553pub fn normalize_cpp_reference_text(value: &str) -> String {
13554 let mut text = normalize_cpp_whitespace(value)
13555 .trim_start_matches("new ")
13556 .trim()
13557 .to_string();
13558 if let Some(index) = text.find(['(', '{']) {
13559 text.truncate(index);
13560 }
13561 if let Some(index) = text.find('<') {
13562 text.truncate(index);
13563 }
13564 let normalized = text
13565 .trim()
13566 .trim_start_matches("const ")
13567 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
13568 .trim_matches(':')
13569 .trim();
13570 strip_tag_type_prefix(normalized).to_string()
13571}
13572
13573pub fn cpp_name_for(unit: &CodeUnit) -> String {
13574 let short = unit.short_name().replace(['.', '$'], "::");
13575 if unit.package_name().is_empty() {
13576 short
13577 } else {
13578 format!("{}::{}", unit.package_name(), short)
13579 }
13580}
13581
13582fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
13586 let fq = unit.fq();
13587 if fq.is_empty() {
13588 return None;
13589 }
13590 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
13591 Some(
13592 fq.segments()
13593 .iter()
13594 .map(|&segment| interner.resolve(segment).0)
13595 .collect::<Vec<_>>()
13596 .join("::"),
13597 )
13598}
13599
13600fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
13601 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
13602 || unit.fq().is_empty() && cpp_name_for(unit) == expected
13603}
13604
13605pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
13614 let fq = unit.fq();
13615 if !fq.is_empty() {
13616 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
13617 let scope = fq
13618 .segments()
13619 .iter()
13620 .filter_map(|&segment| {
13621 let (text, kind) = interner.resolve(segment);
13622 matches!(
13623 kind,
13624 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
13625 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
13626 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
13627 )
13628 .then(|| text.to_string())
13629 })
13630 .collect();
13631 return scope;
13632 }
13633 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13634 brokk_bifrost_core::analyzer::Language::Cpp,
13635 &cpp_name_for(unit),
13636 )
13637}
13638
13639pub fn terminal_name(value: &str) -> &str {
13650 value
13651 .rsplit("::")
13652 .next()
13653 .unwrap_or(value)
13654 .rsplit(['.', '-', '>'])
13655 .next()
13656 .unwrap_or(value)
13657 .trim()
13658}
13659
13660pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
13661 terminal_name(&normalize_cpp_reference_text(value)) == expected
13662}
13663
13664pub fn name_matches_callable(value: &str, expected: &str) -> bool {
13665 name_matches_terminal(value, expected)
13666 || expected.starts_with("operator")
13667 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
13668}
13669
13670pub fn name_mentions(value: &str, expected: &str) -> bool {
13671 normalize_cpp_reference_text(value)
13672 .split("::")
13673 .any(|part| part == expected)
13674}
13675
13676pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
13677 let cpp_name = cpp_name_for(unit);
13678 if reference.contains("::") {
13679 return reference == cpp_name;
13680 }
13681 reference == cpp_name
13682 || terminal_name(reference) == unit.identifier()
13683 && (unit.package_name().is_empty() || reference == unit.identifier())
13684}
13685
13686pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
13687 match kind {
13688 TargetKind::Type
13689 | TargetKind::Constructor
13690 | TargetKind::Method
13691 | TargetKind::MemberField => true,
13692 TargetKind::FreeFunction => unit.is_function(),
13693 TargetKind::GlobalField => unit.is_field(),
13694 TargetKind::Macro => unit.is_macro(),
13695 }
13696}
13697
13698pub fn is_type_alias(unit: &CodeUnit) -> bool {
13699 unit.kind() == CodeUnitType::Field
13700 && unit.signature().is_some_and(|signature| {
13701 signature.starts_with("typedef ") || signature.starts_with("using ")
13702 })
13703}
13704
13705fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
13706 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
13707 let target_name = cpp_name_for(target);
13708 if normalized.contains("::") {
13709 return normalized == target_name;
13710 }
13711 if let Some(namespace) = alias.namespace.as_deref() {
13712 return namespace_prefixes(namespace)
13713 .into_iter()
13714 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
13715 }
13716 target.package_name().is_empty() && normalized == target.identifier()
13717}
13718
13719pub fn cpp_function_return_type_text(
13722 analyzer: &CppGraphSource<'_>,
13723 function: &CodeUnit,
13724) -> Option<String> {
13725 let metadata = analyzer.signature_metadata(function);
13726 if !metadata.is_empty() {
13727 let first = metadata.first()?.return_type_text()?;
13728 return metadata
13729 .iter()
13730 .all(|metadata| metadata.return_type_text() == Some(first))
13731 .then(|| first.to_string());
13732 }
13733 let signature = cpp_function_signature_text(analyzer, function)?;
13734 cpp_function_return_type_text_from_signature(&signature)
13735}
13736
13737fn cpp_function_signature_text(
13738 analyzer: &CppGraphSource<'_>,
13739 function: &CodeUnit,
13740) -> Option<String> {
13741 function
13742 .signature()
13743 .filter(|signature| signature.contains(function.identifier()))
13744 .map(str::to_string)
13745 .or_else(|| analyzer.signatures(function).first().cloned())
13746 .or_else(|| analyzer.get_source(function, false))
13747}
13748
13749fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
13750 let open = signature.find('(')?;
13751 let name_at = cpp_function_name_start(signature, open)?;
13752 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
13753 return Some(return_type);
13754 }
13755 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
13756 .split_whitespace()
13757 .filter(|token| {
13758 !matches!(
13759 *token,
13760 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
13761 )
13762 })
13763 .collect::<Vec<_>>()
13764 .join(" ");
13765 let type_text = type_text.trim();
13766 (!type_text.is_empty()).then(|| type_text.to_string())
13767}
13768
13769fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
13770 let before_parameters = &signature[..open];
13771 if let Some(operator_at) = before_parameters.rfind("operator") {
13772 let boundary = operator_at == 0
13773 || before_parameters[..operator_at]
13774 .chars()
13775 .next_back()
13776 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
13777 if boundary {
13778 return Some(operator_at);
13779 }
13780 }
13781 before_parameters
13782 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
13783 .map(|index| index + 1)
13784}
13785
13786fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
13787 let open = signature_from_name.find('(')?;
13788 let mut depth = 0i32;
13789 for (offset, ch) in signature_from_name[open..].char_indices() {
13790 match ch {
13791 '(' => depth += 1,
13792 ')' => {
13793 depth -= 1;
13794 if depth == 0 {
13795 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
13796 let arrow = rest.find("->")?;
13797 let return_type = rest[arrow + 2..].trim_start();
13798 let return_type = return_type
13799 .split(['{', ';'])
13800 .next()
13801 .unwrap_or(return_type)
13802 .trim();
13803 return (!return_type.is_empty()).then(|| return_type.to_string());
13804 }
13805 }
13806 _ => {}
13807 }
13808 }
13809 None
13810}
13811
13812fn cpp_strip_leading_template_clause(text: &str) -> &str {
13815 let trimmed = text.trim_start();
13816 let Some(rest) = trimmed.strip_prefix("template") else {
13817 return text;
13818 };
13819 let rest = rest.trim_start();
13820 if !rest.starts_with('<') {
13821 return text;
13822 }
13823 let mut depth = 0i32;
13824 for (offset, ch) in rest.char_indices() {
13825 match ch {
13826 '<' => depth += 1,
13827 '>' => {
13828 depth -= 1;
13829 if depth == 0 {
13830 return rest[offset + ch.len_utf8()..].trim_start();
13831 }
13832 }
13833 _ => {}
13834 }
13835 }
13836 text
13837}
13838
13839pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
13840 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
13850 namespace
13851 .strip_prefix("anonymous_namespace::")
13852 .unwrap_or(namespace)
13853 .to_string()
13854 })
13855}
13856
13857fn namespace_prefixes(namespace: &str) -> Vec<String> {
13858 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13864 brokk_bifrost_core::analyzer::Language::Cpp,
13865 namespace,
13866 );
13867 let mut prefixes = Vec::new();
13868 while !parts.is_empty() {
13869 prefixes.push(parts.join("::"));
13870 parts.pop();
13871 }
13872 prefixes
13873}
13874
13875fn nearest_namespace_candidates(
13876 candidates: Vec<CodeUnit>,
13877 normalized: &str,
13878 lexical_namespace: Option<&str>,
13879) -> Vec<CodeUnit> {
13880 if normalized.contains("::") {
13881 return candidates;
13882 }
13883 if let Some(namespace) = lexical_namespace {
13884 for prefix in namespace_prefixes(namespace) {
13885 let scoped = candidates
13886 .iter()
13887 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
13888 .cloned()
13889 .collect::<Vec<_>>();
13890 if !scoped.is_empty() {
13891 return scoped;
13892 }
13893 }
13894 }
13895 candidates
13896 .into_iter()
13897 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
13898 .collect()
13899}
13900
13901pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
13902 let mut namespaces = Vec::new();
13903 let mut current = node.parent();
13904 while let Some(parent) = current {
13905 if parent.kind() == "namespace_definition"
13906 && let Some(name) = parent.child_by_field_name("name")
13907 {
13908 let namespace = normalize_cpp_reference_text(node_text(name, source));
13909 if !namespace.is_empty() {
13910 namespaces.push(namespace);
13911 }
13912 }
13913 current = parent.parent();
13914 }
13915 if namespaces.is_empty() {
13916 None
13917 } else {
13918 namespaces.reverse();
13919 Some(namespaces.join("::"))
13920 }
13921}
13922
13923pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
13927 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
13928}
13929
13930fn type_owner_resolution(
13931 analyzer: &CppGraphSource<'_>,
13932 code_unit: &CodeUnit,
13933) -> Option<ResolvedTypeOwner> {
13934 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
13935}
13936
13937fn target_type_owner_resolution(
13938 analyzer: &CppGraphSource<'_>,
13939 code_unit: &CodeUnit,
13940) -> Option<ResolvedTypeOwner> {
13941 match type_owner_resolution(analyzer, code_unit) {
13942 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
13943 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
13944 }
13945}
13946
13947fn target_forward_owner_resolution(
13959 analyzer: &CppGraphSource<'_>,
13960 code_unit: &CodeUnit,
13961) -> Option<ResolvedTypeOwner> {
13962 if !code_unit.is_function() {
13963 return None;
13964 }
13965 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
13971 let cpp = analyzer.cpp?;
13972 let mut visible_files = HashSet::default();
13973 collect_include_closure(
13974 analyzer,
13975 cpp.include_target_index(),
13976 code_unit.source(),
13977 &mut visible_files,
13978 None,
13979 );
13980 let candidates = analyzer.workspace_definitions().exact(&owner_name);
13981 let visible_candidates = candidates
13982 .iter()
13983 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
13984 .cloned()
13985 .collect::<Vec<_>>();
13986 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
13987 DirectOwnerResolution::UniqueFull(unit) => {
13988 return Some(ResolvedTypeOwner {
13989 unit,
13990 is_forward_declaration: false,
13991 });
13992 }
13993 DirectOwnerResolution::ForwardsOnly(forwards) => {
13994 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
13995 unit: forwards.into_iter().next().unwrap(),
13996 is_forward_declaration: true,
13997 });
13998 }
13999 DirectOwnerResolution::Ambiguous => return None,
14000 DirectOwnerResolution::None => {}
14001 }
14002
14003 let candidates = candidates
14004 .into_iter()
14005 .filter(|candidate| candidate.is_class())
14006 .collect::<Vec<_>>();
14007 let (unit, is_forward_declaration) =
14008 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
14009 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
14010 DirectOwnerResolution::ForwardsOnly(forwards) => {
14011 (unique_logical_forward_owner(forwards)?, true)
14012 }
14013 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
14014 };
14015 Some(ResolvedTypeOwner {
14016 unit,
14017 is_forward_declaration,
14018 })
14019}
14020
14021pub fn precise_parent_of(
14022 analyzer: &CppGraphSource<'_>,
14023 visibility: &VisibilityIndex<'_>,
14024 code_unit: &CodeUnit,
14025) -> Option<CodeUnit> {
14026 visibility.cached_precise_parent_of(analyzer, code_unit)
14027}
14028
14029fn precise_parent_resolution(
14030 analyzer: &CppGraphSource<'_>,
14031 code_unit: &CodeUnit,
14032) -> Option<ResolvedTypeOwner> {
14033 #[cfg(any(test, feature = "test-support"))]
14034 if let Some(cpp) = analyzer.cpp {
14035 cpp.record_cpp_parent_resolution_for_test();
14036 }
14037 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
14038 return Some(ResolvedTypeOwner {
14039 unit,
14040 is_forward_declaration: false,
14041 });
14042 }
14043 let fallback = analyzer.parent_of(code_unit);
14044 if !code_unit.owner_is_type_scope() {
14045 return fallback.map(|unit| ResolvedTypeOwner {
14046 unit,
14047 is_forward_declaration: false,
14048 });
14049 }
14050 let owner_fq = code_unit
14051 .fq()
14052 .parent()
14053 .expect("a unit with an owner identifier has a structured parent");
14054 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
14055 match same_source_owner(analyzer, code_unit, &owner_candidates) {
14056 DirectOwnerResolution::UniqueFull(owner) => {
14057 return Some(ResolvedTypeOwner {
14058 unit: owner,
14059 is_forward_declaration: false,
14060 });
14061 }
14062 DirectOwnerResolution::Ambiguous => return None,
14063 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
14064 }
14065 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
14066 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
14067 unit: owner,
14068 is_forward_declaration: false,
14069 }),
14070 DirectOwnerResolution::Ambiguous => None,
14071 DirectOwnerResolution::ForwardsOnly(forwards) => {
14072 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
14073 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
14074 unit: owner,
14075 is_forward_declaration: false,
14076 }),
14077 FullOwnerResolution::None => {
14078 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
14079 unit,
14080 is_forward_declaration: true,
14081 })
14082 }
14083 FullOwnerResolution::Ambiguous => None,
14084 }
14085 }
14086 DirectOwnerResolution::None => {
14087 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
14088 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
14089 unit: owner,
14090 is_forward_declaration: false,
14091 }),
14092 FullOwnerResolution::Ambiguous => None,
14093 FullOwnerResolution::None => fallback
14094 .filter(|parent| {
14095 parent.source() == code_unit.source()
14096 && parent.fq() == &owner_fq
14097 && (!parent.is_class()
14098 || cpp_class_declaration_strength(analyzer, parent)
14099 == CppClassDeclarationStrength::Full)
14100 })
14101 .map(|unit| ResolvedTypeOwner {
14102 unit,
14103 is_forward_declaration: false,
14104 }),
14105 }
14106 }
14107 }
14108}
14109
14110fn exact_structural_type_parent(
14111 analyzer: &CppGraphSource<'_>,
14112 code_unit: &CodeUnit,
14113) -> Option<CodeUnit> {
14114 if !code_unit.is_function() && !code_unit.is_field() {
14115 return None;
14116 }
14117 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
14119 let parent = cpp.structural_parent_of(code_unit)?;
14120 (!parent.is_module()
14121 && parent.source() == code_unit.source()
14122 && parent.package_name() == code_unit.package_name()
14123 && parent.short_name() == encoded_owner)
14124 .then_some(parent)
14125}
14126
14127fn same_source_owner(
14128 analyzer: &CppGraphSource<'_>,
14129 code_unit: &CodeUnit,
14130 owner_candidates: &[CodeUnit],
14131) -> DirectOwnerResolution {
14132 let candidates = owner_candidates
14133 .iter()
14134 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
14135 .cloned()
14136 .collect::<Vec<_>>();
14137 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14138 classify_direct_owner_candidates(analyzer, candidates.into_iter())
14139}
14140
14141fn visible_full_cpp_owner(
14142 analyzer: &CppGraphSource<'_>,
14143 code_unit: &CodeUnit,
14144 owner_candidates: &[CodeUnit],
14145) -> FullOwnerResolution {
14146 let Some(cpp) = analyzer.cpp else {
14147 return FullOwnerResolution::None;
14148 };
14149 let mut visible_files = HashSet::default();
14150 collect_include_closure(
14151 analyzer,
14152 cpp.include_target_index(),
14153 code_unit.source(),
14154 &mut visible_files,
14155 None,
14156 );
14157 let candidates = owner_candidates
14158 .iter()
14159 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
14160 .cloned()
14161 .collect::<Vec<_>>();
14162 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14163 let mut full_definition = None;
14164 for candidate in candidates {
14165 match cpp_class_declaration_strength(analyzer, &candidate) {
14166 CppClassDeclarationStrength::Full if full_definition.is_some() => {
14167 return FullOwnerResolution::Ambiguous;
14168 }
14169 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
14170 CppClassDeclarationStrength::Forward => {}
14171 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
14172 }
14173 }
14174 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
14175}
14176
14177pub enum DirectOwnerResolution {
14178 None,
14179 ForwardsOnly(Vec<CodeUnit>),
14180 UniqueFull(CodeUnit),
14181 Ambiguous,
14182}
14183
14184enum FullOwnerResolution {
14185 None,
14186 Unique(CodeUnit),
14187 Ambiguous,
14188}
14189
14190#[derive(Clone, Copy, PartialEq, Eq)]
14191pub enum CppClassDeclarationStrength {
14192 Full,
14193 Forward,
14194 Unknown,
14195}
14196
14197fn directly_included_owner(
14198 analyzer: &CppGraphSource<'_>,
14199 code_unit: &CodeUnit,
14200 owner_candidates: &[CodeUnit],
14201) -> DirectOwnerResolution {
14202 let Some(cpp) = analyzer.cpp else {
14203 return DirectOwnerResolution::None;
14204 };
14205 let imports = analyzer.import_statements(code_unit.source());
14206 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
14207 .into_iter()
14208 .flat_map(|include| {
14209 resolve_include_targets_with_index(
14210 code_unit.source(),
14211 &include,
14212 cpp.include_target_index(),
14213 )
14214 })
14215 .collect();
14216 let candidates = owner_candidates
14217 .iter()
14218 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
14219 .cloned()
14220 .collect::<Vec<_>>();
14221 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14222 classify_direct_owner_candidates(analyzer, candidates.into_iter())
14223}
14224
14225fn prefer_member_declaring_owners(
14226 analyzer: &CppGraphSource<'_>,
14227 member: &CodeUnit,
14228 candidates: Vec<CodeUnit>,
14229) -> Vec<CodeUnit> {
14230 let matching = candidates
14231 .iter()
14232 .filter(|owner| owner_declares_member(analyzer, owner, member))
14233 .cloned()
14234 .collect::<Vec<_>>();
14235 if matching.is_empty() {
14236 candidates
14237 } else {
14238 matching
14239 }
14240}
14241
14242fn owner_declares_member(
14243 analyzer: &CppGraphSource<'_>,
14244 owner: &CodeUnit,
14245 member: &CodeUnit,
14246) -> bool {
14247 analyzer.direct_children(owner).into_iter().any(|child| {
14248 child.kind() == member.kind()
14249 && child.identifier() == member.identifier()
14250 && child.signature() == member.signature()
14251 })
14252}
14253
14254fn classify_direct_owner_candidates(
14255 analyzer: &CppGraphSource<'_>,
14256 candidates: impl Iterator<Item = CodeUnit>,
14257) -> DirectOwnerResolution {
14258 collapse_owner_candidates(candidates.map(|candidate| {
14259 let strength = cpp_class_declaration_strength(analyzer, &candidate);
14260 (candidate, strength)
14261 }))
14262}
14263
14264pub fn collapse_owner_candidates(
14265 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
14266) -> DirectOwnerResolution {
14267 let mut full_definition = None;
14268 let mut forwards = Vec::new();
14269 for (candidate, strength) in candidates {
14270 match strength {
14271 CppClassDeclarationStrength::Full if full_definition.is_some() => {
14272 return DirectOwnerResolution::Ambiguous;
14273 }
14274 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
14275 CppClassDeclarationStrength::Forward => forwards.push(candidate),
14276 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
14277 }
14278 }
14279 if let Some(owner) = full_definition {
14280 DirectOwnerResolution::UniqueFull(owner)
14281 } else if !forwards.is_empty() {
14282 DirectOwnerResolution::ForwardsOnly(forwards)
14283 } else {
14284 DirectOwnerResolution::None
14285 }
14286}
14287
14288#[cfg(any(test, feature = "test-support"))]
14289pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
14290 unique_logical_forward_owner(forwards)
14291}
14292
14293fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
14294 let first = forwards.pop()?;
14295 forwards
14296 .iter()
14297 .all(|forward| same_logical_symbol(forward, &first))
14298 .then_some(first)
14299}
14300
14301pub fn cpp_class_declaration_strength(
14302 analyzer: &CppGraphSource<'_>,
14303 candidate: &CodeUnit,
14304) -> CppClassDeclarationStrength {
14305 if let Some(prepared) = analyzer
14306 .cpp
14307 .and_then(|cpp| cpp.prepared_syntax(analyzer.token, candidate.source()))
14308 {
14309 return cpp_class_declaration_strength_in_tree(
14310 analyzer,
14311 candidate,
14312 prepared.source(),
14313 prepared.tree().root_node(),
14314 );
14315 }
14316 let Some(source) = analyzer.indexed_source(candidate.source()) else {
14317 return CppClassDeclarationStrength::Unknown;
14318 };
14319 #[cfg(any(test, feature = "test-support"))]
14320 if let Some(cpp) = analyzer.cpp {
14321 cpp.record_cpp_class_strength_parse_for_test();
14322 }
14323 let mut parser = Parser::new();
14324 if parser
14325 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14326 .is_err()
14327 {
14328 return CppClassDeclarationStrength::Unknown;
14329 }
14330 let Some(tree) = parser.parse(&source, None) else {
14331 return CppClassDeclarationStrength::Unknown;
14332 };
14333 cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
14334}
14335
14336fn cpp_class_declaration_strength_in_tree(
14337 analyzer: &CppGraphSource<'_>,
14338 candidate: &CodeUnit,
14339 source: &str,
14340 root: Node<'_>,
14341) -> CppClassDeclarationStrength {
14342 let ranges = analyzer.ranges(candidate);
14343 let mut saw_forward = false;
14344 for range in ranges {
14345 let mut stack = vec![root];
14346 while let Some(node) = stack.pop() {
14347 if recovered_function_like_export_class_pair_has_body(
14348 node,
14349 source,
14350 candidate.identifier(),
14351 &range,
14352 ) {
14353 return CppClassDeclarationStrength::Full;
14354 }
14355 if recovered_embedded_function_like_export_class_has_body(
14356 node,
14357 source,
14358 candidate.identifier(),
14359 &range,
14360 ) {
14361 return CppClassDeclarationStrength::Full;
14362 }
14363 if node.start_byte() == range.start_byte
14364 && recovered_fragmented_plain_class_has_body(
14365 node,
14366 source,
14367 candidate.identifier(),
14368 &range,
14369 )
14370 {
14371 return CppClassDeclarationStrength::Full;
14372 }
14373 if node.start_byte() <= range.start_byte
14380 && range.start_byte < node.end_byte()
14381 && let Some(has_body) =
14382 recovered_exported_class_has_body(node, source, candidate.identifier())
14383 {
14384 if has_body {
14385 return CppClassDeclarationStrength::Full;
14386 }
14387 saw_forward = true;
14388 continue;
14389 }
14390 if node.start_byte() > range.start_byte || node.end_byte() < range.start_byte {
14391 continue;
14392 }
14393 if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
14394 if matches!(
14395 node.kind(),
14396 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14397 ) {
14398 if cpp_class_node_has_body(node) {
14399 return CppClassDeclarationStrength::Full;
14400 }
14401 saw_forward = true;
14402 } else if let Some(has_body) =
14403 recovered_exported_class_has_body(node, source, candidate.identifier())
14404 {
14405 if has_body {
14406 return CppClassDeclarationStrength::Full;
14407 }
14408 saw_forward = true;
14409 }
14410 }
14411 let mut cursor = node.walk();
14412 stack.extend(node.named_children(&mut cursor));
14413 }
14414 }
14415 if saw_forward {
14416 CppClassDeclarationStrength::Forward
14417 } else {
14418 CppClassDeclarationStrength::Unknown
14419 }
14420}
14421
14422fn cpp_class_node_has_body(node: Node<'_>) -> bool {
14423 node.child_by_field_name("body").is_some() || {
14424 let mut cursor = node.walk();
14425 node.named_children(&mut cursor).any(|child| {
14426 matches!(
14427 child.kind(),
14428 "declaration_list" | "field_declaration_list" | "enumerator_list"
14429 )
14430 })
14431 }
14432}
14433
14434pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
14435 if !code_unit.owner_is_type_scope() {
14436 return None;
14437 }
14438 let owner_fq = code_unit.fq().parent()?;
14439 ctx.analyzer
14440 .workspace_definitions()
14441 .exact(&owner_fq)
14442 .into_iter()
14443 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
14444}
14445
14446pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14447 left.kind() == right.kind()
14448 && left.fq_name() == right.fq_name()
14449 && left.signature() == right.signature()
14450 && left.source() == right.source()
14451}
14452
14453pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14454 same_symbol(left, right) || same_logical_symbol(left, right)
14455}
14456
14457pub fn same_visible_global_field_symbol(
14458 analyzer: &CppGraphSource<'_>,
14459 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
14460 left: &CodeUnit,
14461 right: &CodeUnit,
14462) -> bool {
14463 if same_symbol(left, right) {
14464 return true;
14465 }
14466 if !same_logical_symbol(left, right) {
14467 return false;
14468 }
14469 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
14470 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
14471 {
14472 left.source() == right.source()
14473 } else {
14474 true
14475 }
14476}
14477
14478fn cpp_global_field_has_internal_linkage_cached(
14479 analyzer: &CppGraphSource<'_>,
14480 cache: &mut HashMap<CodeUnit, bool>,
14481 candidate: &CodeUnit,
14482) -> bool {
14483 if let Some(internal) = cache.get(candidate) {
14484 return *internal;
14485 }
14486 #[cfg(any(test, feature = "test-support"))]
14487 note_cpp_global_field_internal_linkage_classification_for_test();
14488 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
14489 cache.insert(candidate.clone(), internal);
14490 internal
14491}
14492
14493#[cfg(any(test, feature = "test-support"))]
14494thread_local! {
14495 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
14496}
14497
14498#[cfg(any(test, feature = "test-support"))]
14499fn note_cpp_global_field_internal_linkage_classification_for_test() {
14500 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
14501 count.set(count.get() + 1);
14502 });
14503}
14504
14505#[cfg(any(test, feature = "test-support"))]
14506pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
14507 body: impl FnOnce() -> T,
14508) -> (T, usize) {
14509 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
14510 count.set(0);
14511 let result = body();
14512 let observed = count.get();
14513 count.set(0);
14514 (result, observed)
14515 })
14516}
14517
14518pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14519 left.kind() == right.kind()
14520 && left.fq_name() == right.fq_name()
14521 && left.signature() == right.signature()
14522}
14523
14524pub fn cpp_global_field_has_internal_linkage(
14525 analyzer: &CppGraphSource<'_>,
14526 candidate: &CodeUnit,
14527) -> bool {
14528 if !candidate.is_field() || candidate.short_name().contains('.') {
14529 return false;
14530 }
14531 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
14532 return false;
14533 };
14534 match local_linkage {
14535 CppFieldLinkage::Internal => true,
14536 CppFieldLinkage::External => false,
14537 CppFieldLinkage::InternalUnlessExternalPeer => {
14538 !cpp_global_field_linkage_peers(analyzer, candidate)
14539 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
14540 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
14541 }
14542 }
14543}
14544
14545fn cpp_global_field_linkage_peers<'a>(
14546 analyzer: &CppGraphSource<'a>,
14547 candidate: &'a CodeUnit,
14548) -> impl Iterator<Item = CodeUnit> + 'a {
14549 let name = candidate.fq().clone();
14550 analyzer
14551 .workspace_definitions()
14552 .exact(&name)
14553 .into_iter()
14554 .filter(move |peer| {
14555 if peer == candidate {
14556 return false;
14557 }
14558 #[cfg(any(test, feature = "test-support"))]
14559 note_cpp_global_field_linkage_peer_inspection_for_test();
14560 same_logical_symbol(peer, candidate)
14561 })
14562}
14563
14564#[cfg(any(test, feature = "test-support"))]
14565thread_local! {
14566 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
14567}
14568
14569#[cfg(any(test, feature = "test-support"))]
14570fn note_cpp_global_field_linkage_peer_inspection_for_test() {
14571 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
14572 count.set(count.get() + 1);
14573 });
14574}
14575
14576#[cfg(any(test, feature = "test-support"))]
14577pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
14578 body: impl FnOnce() -> T,
14579) -> (T, usize) {
14580 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
14581 count.set(0);
14582 let result = body();
14583 let observed = count.get();
14584 count.set(0);
14585 (result, observed)
14586 })
14587}
14588
14589fn cpp_global_field_declaration_linkage(
14590 analyzer: &CppGraphSource<'_>,
14591 candidate: &CodeUnit,
14592) -> Option<CppFieldLinkage> {
14593 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
14594 return Some(linkage);
14595 }
14596 let cpp = analyzer.cpp?;
14597 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
14598 return cpp_global_field_declaration_linkage_in_tree(
14599 analyzer,
14600 candidate,
14601 prepared.source(),
14602 prepared.tree().root_node(),
14603 );
14604 }
14605 let source = analyzer.indexed_source(candidate.source())?;
14606 let mut parser = Parser::new();
14607 if parser
14608 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14609 .is_err()
14610 {
14611 return None;
14612 }
14613 let tree = parser.parse(&source, None)?;
14614 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
14615}
14616
14617fn cpp_global_field_declaration_linkage_in_tree(
14618 analyzer: &CppGraphSource<'_>,
14619 candidate: &CodeUnit,
14620 source: &str,
14621 root: Node<'_>,
14622) -> Option<CppFieldLinkage> {
14623 analyzer.ranges(candidate).iter().find_map(|range| {
14624 node_for_exact_range(root, range)
14625 .and_then(enclosing_cpp_field_declaration)
14626 .map(|declaration| {
14627 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
14629 })
14630 })
14631}
14632
14633fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
14634 loop {
14635 if matches!(node.kind(), "declaration" | "field_declaration") {
14636 return Some(node);
14637 }
14638 node = node.parent()?;
14639 }
14640}
14641
14642#[cfg(test)]
14643mod tests {
14644 use super::*;
14645
14646 #[test]
14647 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
14648 let source = "int size(void) { return sizeof(((Payload))); }\n";
14649 let mut parser = Parser::new();
14650 parser
14651 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14652 .expect("C++ grammar");
14653 let tree = parser.parse(source, None).expect("fixture tree");
14654 let start = source.find("Payload").expect("sizeof operand");
14655 let node = tree
14656 .root_node()
14657 .named_descendant_for_byte_range(start, start + "Payload".len())
14658 .expect("focused operand");
14659 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
14660 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
14661
14662 assert_eq!(node.kind(), "identifier");
14663 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
14664 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
14665 }
14666
14667 #[test]
14668 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
14669 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
14670 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
14671 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
14672 assert!(indexed_namespace_path_is_recoverable(
14673 &["cache".to_string()],
14674 &indexed,
14675 1,
14676 ));
14677 }
14678
14679 #[test]
14680 fn sort_lookup_units_totally_orders_every_identity_field() {
14681 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
14682 let base = CodeUnit::with_signature(
14683 file.clone(),
14684 CodeUnitType::Function,
14685 "scope",
14686 "value",
14687 Some("()".to_string()),
14688 false,
14689 );
14690 let different_kind = CodeUnit::with_signature(
14691 file.clone(),
14692 CodeUnitType::Field,
14693 "scope",
14694 "value",
14695 Some("()".to_string()),
14696 false,
14697 );
14698 let synthetic = base.with_synthetic(true);
14699
14700 let interner = segment_interner();
14701 let mut member_fq = FqName::new();
14702 member_fq.push(interner.intern("scope", SegmentKind::Package));
14703 member_fq.push(interner.intern("value", SegmentKind::Member));
14704 let different_package_boundary = CodeUnit::from_fq(
14705 file.clone(),
14706 CodeUnitType::Function,
14707 member_fq,
14708 0,
14709 Some("()".to_string()),
14710 false,
14711 );
14712
14713 let mut unknown_fq = FqName::new();
14714 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
14715 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
14716 let different_segment_kind = CodeUnit::from_fq(
14717 file,
14718 CodeUnitType::Function,
14719 unknown_fq,
14720 1,
14721 Some("()".to_string()),
14722 false,
14723 );
14724
14725 let input = vec![
14726 base,
14727 different_kind,
14728 synthetic,
14729 different_package_boundary,
14730 different_segment_kind,
14731 ];
14732 let mut expected = input.clone();
14733 sort_lookup_units(&mut expected);
14734 assert!(expected.windows(2).all(|pair| {
14735 let mut ordered = pair.to_vec();
14736 sort_lookup_units(&mut ordered);
14737 ordered == pair && pair[0] != pair[1]
14738 }));
14739
14740 let mut reversed = input.clone();
14741 reversed.reverse();
14742 sort_lookup_units(&mut reversed);
14743 assert_eq!(reversed, expected);
14744
14745 let mut rotated = input;
14746 rotated.rotate_left(2);
14747 sort_lookup_units(&mut rotated);
14748 assert_eq!(rotated, expected);
14749 }
14750
14751 #[test]
14752 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
14753 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";
14754 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
14755 let parse = |source: &str| {
14756 let mut parser = Parser::new();
14757 parser
14758 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14759 .expect("C++ grammar");
14760 parser.parse(source, None).expect("fixture tree")
14761 };
14762
14763 let tree = parse(damaged);
14764 let root = tree.root_node();
14765 let target = damaged.find("target").expect("target byte");
14766 let declaration = root
14767 .descendant_for_byte_range(target, target + "target".len())
14768 .and_then(|mut node| {
14769 loop {
14770 if node.kind() == "declaration" {
14771 break Some(node);
14772 }
14773 node = node.parent()?;
14774 }
14775 })
14776 .expect("declaration after the displaced terminator");
14777 let conditional = declaration
14778 .parent()
14779 .filter(|node| node.kind() == "preproc_ifdef")
14780 .expect("damaged inner conditional");
14781 let outer = conditional
14782 .parent()
14783 .filter(|node| node.kind() == "preproc_ifdef")
14784 .expect("ordinary outer include guard");
14785 let terminator = cpp_displaced_preprocessor_terminator(conditional)
14786 .expect("structured displaced #endif");
14787 assert_eq!(node_text(terminator, damaged), "#endif");
14788 assert!(terminator.end_byte() <= declaration.start_byte());
14789 assert!(!preprocessor_conditional_contains_descendant(
14790 conditional,
14791 declaration
14792 ));
14793 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
14794 assert!(preprocessor_conditional_contains_descendant(
14795 outer,
14796 declaration
14797 ));
14798
14799 let tree = parse(guarded);
14800 let conditional = tree
14801 .root_node()
14802 .named_child(0)
14803 .filter(|node| node.kind() == "preproc_ifdef")
14804 .expect("ordinary conditional");
14805 let declaration = conditional
14806 .named_children(&mut conditional.walk())
14807 .find(|node| node.kind() == "declaration")
14808 .expect("guarded declaration");
14809 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
14810 assert!(preprocessor_conditional_contains_descendant(
14811 conditional,
14812 declaration
14813 ));
14814
14815 let damaged_alternative = format!(
14816 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
14817 "UNUSED(value)\n".repeat(64)
14818 );
14819 let tree = parse(&damaged_alternative);
14820 let conditional = tree
14821 .root_node()
14822 .named_child(0)
14823 .filter(|node| node.kind() == "preproc_ifdef")
14824 .expect("outer conditional with an alternative");
14825 assert!(conditional.has_error());
14826 assert!(conditional.child_by_field_name("alternative").is_some());
14827 assert!(
14828 conditional
14829 .child(conditional.child_count() - 1)
14830 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
14831 );
14832 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
14833
14834 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";
14835 let tree = parse(split_declaration);
14836 let root = tree.root_node();
14837 let conditional = root
14838 .named_children(&mut root.walk())
14839 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
14840 .expect("split declaration conditional");
14841 let target = split_declaration
14842 .find("static int target")
14843 .expect("target byte");
14844 let boundary =
14845 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
14846 assert!(boundary.end_byte <= target, "{boundary:?}");
14847 assert_eq!(boundary.end_line, 9, "{boundary:?}");
14848 let target_node = root
14849 .descendant_for_byte_range(target, target + "static".len())
14850 .expect("target node");
14851 assert!(!preprocessor_conditional_contains_descendant(
14852 conditional,
14853 target_node
14854 ));
14855 }
14856
14857 #[test]
14858 fn fragmented_reference_guard_is_recovered() {
14859 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";
14860 let mut parser = Parser::new();
14861 parser
14862 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14863 .expect("C++ grammar");
14864 let tree = parser.parse(source, None).expect("fixture tree");
14865 let start = source.rfind("helper").expect("reference byte");
14866 let node = tree
14867 .root_node()
14868 .descendant_for_byte_range(start, start + "helper".len())
14869 .expect("reference node");
14870 let mut expected = HashSet::default();
14871 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
14872 vec![
14873 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
14874 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
14875 ],
14876 )));
14877 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
14878 }
14879
14880 #[test]
14881 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
14882 let source = r#"#ifdef _WIN32
14883#if defined(__cplusplus)
14884extern "C"
14885#endif
14886int platform_api(void);
14887#endif
14888
14889#ifdef _WIN32
14890static int entropy_target(void) { return 0; }
14891#else
14892#ifdef HAVE_COMMON_RANDOM
14893static int other_target(void) { return 0; }
14894#elif defined(HAVE_GETENTROPY)
14895static int entropy_target(void) { return 1; }
14896static int use_entropy(void) { return entropy_target(); }
14897#endif
14898#endif
14899"#;
14900 let mut parser = Parser::new();
14901 parser
14902 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14903 .expect("C++ grammar");
14904 let tree = parser.parse(source, None).expect("fixture tree");
14905 let start = source.rfind("entropy_target()").expect("reference");
14906 let node = tree
14907 .root_node()
14908 .descendant_for_byte_range(start, start + "entropy_target".len())
14909 .expect("reference node");
14910 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
14911 assert!(
14912 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
14913 "{guards:#?}"
14914 );
14915 assert!(
14916 guards.contains(&PreprocessorGuard::Undefined(
14917 "HAVE_COMMON_RANDOM".to_string()
14918 )),
14919 "{guards:#?}"
14920 );
14921 assert!(
14922 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
14923 "{guards:#?}"
14924 );
14925 assert!(
14926 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
14927 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
14928 );
14929 }
14930
14931 #[test]
14932 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
14933 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";
14934 let mut parser = Parser::new();
14935 parser
14936 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14937 .expect("C++ grammar");
14938 let tree = parser.parse(source, None).expect("fixture tree");
14939 let root = tree.root_node();
14940 let node_at = |text: &str, start: usize| {
14941 root.descendant_for_byte_range(start, start + text.len())
14942 .expect("token node")
14943 };
14944
14945 let key_start = source.find("case KEY").expect("case label") + "case ".len();
14946 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
14947 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
14948 assert!(!is_ordinary_macro_reference_node(node_at(
14949 "ENABLE_KEYS",
14950 guard_start,
14951 )));
14952 }
14953
14954 #[test]
14955 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
14956 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";
14957 let mut parser = Parser::new();
14958 parser
14959 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14960 .expect("C++ grammar");
14961 let tree = parser.parse(source, None).expect("fixture tree");
14962 let root = tree.root_node();
14963 let definition_start = source.find("target(void)").expect("definition");
14964 let reference_start = source.rfind("target()").expect("reference");
14965 let definition = root
14966 .descendant_for_byte_range(definition_start, definition_start + "target".len())
14967 .expect("definition node");
14968 let reference = root
14969 .descendant_for_byte_range(reference_start, reference_start + "target".len())
14970 .expect("reference node");
14971 let required =
14972 preprocessor_guard_environment(definition, source).expect("definition guard");
14973 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
14974 assert!(guard_requirements_hold_at_reference(
14975 &required,
14976 Some(&active)
14977 ));
14978 }
14979
14980 #[test]
14981 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
14982 let source = "g_autoptr(FuChunkArray) self = make_array();";
14983 let mut parser = Parser::new();
14984 parser
14985 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14986 .expect("C++ grammar");
14987 let tree = parser.parse(source, None).expect("fixture tree");
14988 let statement = tree.root_node().named_child(0).expect("statement");
14989 let binding =
14990 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
14991 assert_eq!(binding.name, "self");
14992 assert_eq!(binding.type_name, "FuChunkArray");
14993 assert_eq!(binding.pointer_depth, 1);
14994
14995 let near_miss = "holder(FuChunkArray) self = make_array();";
14996 let tree = parser.parse(near_miss, None).expect("near-miss tree");
14997 let statement = tree.root_node().named_child(0).expect("statement");
14998 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
14999 }
15000
15001 #[test]
15002 fn boolean_guard_normalization_proves_equivalence_and_implication() {
15003 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
15004 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
15005 let negated_windows_branch =
15006 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
15007 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
15008 assert_eq!(negated_windows_branch, portable);
15009
15010 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
15011 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
15012 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
15013 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
15014 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
15015 assert!(fallback_branch.implies(&fallback_declaration));
15016 assert!(
15017 BooleanGuardExpression::Truthy("FEATURE".to_string())
15018 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
15019 );
15020 assert!(
15021 BooleanGuardExpression::Undefined("FEATURE".to_string())
15022 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
15023 );
15024 assert!(
15025 !BooleanGuardExpression::Defined("FEATURE".to_string())
15026 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
15027 );
15028 assert!(!fallback_declaration.implies(&fallback_branch));
15029 }
15030
15031 #[test]
15032 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
15033 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";
15034 let mut parser = Parser::new();
15035 parser
15036 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15037 .expect("C++ grammar");
15038 let tree = parser.parse(source, None).expect("fixture tree");
15039 let root = tree.root_node();
15040 let call = |marker: &str| {
15041 let start = source.find(marker).expect("call marker");
15042 let mut node = root
15043 .descendant_for_byte_range(start, start + "helper".len())
15044 .expect("call name node");
15045 loop {
15046 if node.kind() == "call_expression" {
15047 break node;
15048 }
15049 node = node.parent().expect("call expression ancestor");
15050 }
15051 };
15052 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
15053 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
15054 let keyword_call = call("helper(NULL, template); /* bound */");
15055 let keyword_arguments = keyword_call
15056 .child_by_field_name("arguments")
15057 .expect("keyword argument list");
15058 assert_eq!(
15059 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
15060 1
15061 );
15062 assert_eq!(
15063 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
15064 0
15065 );
15066
15067 let unbound_call = call("helper(NULL, template); /* unbound */");
15068 let unbound_arguments = unbound_call
15069 .child_by_field_name("arguments")
15070 .expect("unbound argument list");
15071 assert_eq!(
15072 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
15073 0
15074 );
15075 }
15076
15077 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
15078 let mut parser = Parser::new();
15079 parser
15080 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15081 .expect("C++ grammar");
15082 let tree = parser.parse(source, None).expect("C++ fixture tree");
15083 let mut stack = vec![tree.root_node()];
15084 while let Some(node) = stack.pop() {
15085 if node.kind() == "enum_specifier" {
15086 return flattened_macro_namespace_components(node, source);
15087 }
15088 let mut cursor = node.walk();
15089 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
15090 stack.extend(children.into_iter().rev());
15091 }
15092 None
15093 }
15094
15095 #[test]
15096 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
15097 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
15098namespace detail
15099{
15100enum class value_t { null };
15101}
15102NLOHMANN_JSON_NAMESPACE_END
15103NLOHMANN_JSON_NAMESPACE_BEGIN
15104namespace next
15105{
15106struct next_type {};
15107}
15108NLOHMANN_JSON_NAMESPACE_END
15109"#;
15110 assert_eq!(
15111 first_enum_flattened_namespace(complete),
15112 Some(vec!["detail".to_string()])
15113 );
15114
15115 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
15116 assert_eq!(
15117 first_enum_flattened_namespace(&stale_end),
15118 Some(vec!["detail".to_string()]),
15119 "a stale end marker before the begin marker must not replace the intended namespace"
15120 );
15121
15122 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
15123namespace detail
15124{
15125enum class value_t { null };
15126}
15127struct next_type {};
15128"#;
15129 assert_eq!(first_enum_flattened_namespace(incomplete), None);
15130 }
15131}