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, CppRecoveredExportClassIndex,
9 cpp_callable_identity_suffix, 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_class_body_at, recovered_function_like_field_declarator,
13 recovered_pyobject_head_field,
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::time::{Duration, Instant};
46use tree_sitter::{Node, Parser, Tree};
47
48#[cfg(any(test, feature = "test-support"))]
49thread_local! {
50 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
51}
52
53#[derive(Clone, Copy, PartialEq, Eq)]
54pub enum TargetKind {
55 Type,
56 Constructor,
57 FreeFunction,
58 Method,
59 GlobalField,
60 MemberField,
61 Macro,
62}
63
64pub enum LexicalTypeResolution {
65 Resolved {
66 unit: CodeUnit,
67 components: Vec<String>,
68 candidates: Vec<CodeUnit>,
69 },
70 Ambiguous,
71 Missing,
72}
73
74#[derive(Clone, Copy)]
75enum TypeCandidateResolution<'a> {
76 Canonical,
77 PreserveAlias,
78 PreserveTarget(&'a CodeUnit),
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90enum TypeCandidateFailure {
91 Ambiguous,
92 Unresolvable,
93}
94
95impl TypeCandidateFailure {
96 fn lexical_resolution(self) -> LexicalTypeResolution {
97 match self {
98 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
99 Self::Unresolvable => LexicalTypeResolution::Missing,
100 }
101 }
102}
103
104pub enum LexicalCallableValueResolution {
105 Type(CodeUnit),
106 FreeFunction(CodeUnit),
107 Ambiguous,
108 Missing,
109}
110
111pub enum UsingEnumMemberResolution {
112 Resolved { owner: CodeUnit, member: CodeUnit },
113 Ambiguous,
114 Missing,
115}
116
117pub enum NamespaceValueResolution {
118 Resolved,
119 Ambiguous,
120 Missing,
121}
122
123#[derive(Clone, Debug, PartialEq, Eq)]
124pub enum OrdinaryMacroReferenceResolution {
125 Resolved(CodeUnit),
126 Ambiguous,
127 Missing,
128}
129
130#[derive(Clone, Debug, PartialEq, Eq)]
131pub enum RecoveredCReferenceRanges {
132 Complete(Vec<Range>),
133 LimitExceeded,
134}
135
136pub fn resolve_namespace_value(
137 analyzer: &CppGraphSource<'_>,
138 visibility: &VisibilityIndex<'_>,
139 file: &ProjectFile,
140 namespace: &str,
141 name: &str,
142 before_byte: usize,
143) -> NamespaceValueResolution {
144 let mut matches = Vec::new();
145 for candidate in visibility.visible_identifier_candidates(file, name) {
146 if type_owner_of(analyzer, candidate).is_some()
147 || candidate.package_name() != namespace
148 || (candidate.source() == file
149 && !analyzer
150 .ranges(candidate)
151 .iter()
152 .any(|range| range.start_byte < before_byte))
153 || matches
154 .iter()
155 .any(|existing| same_visible_symbol(existing, candidate))
156 {
157 continue;
158 }
159 matches.push(candidate.clone());
160 if matches.len() > 1 {
161 return NamespaceValueResolution::Ambiguous;
162 }
163 }
164 matches
165 .pop()
166 .map(|_| NamespaceValueResolution::Resolved)
167 .unwrap_or(NamespaceValueResolution::Missing)
168}
169
170pub(crate) struct ScopedUsingEnumOwners {
171 scopes: Vec<Vec<CodeUnit>>,
172}
173
174pub(crate) struct SemanticUsingEnumOwners {
179 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
180 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
181}
182
183pub(crate) enum SemanticUsingEnumMemberResolution {
184 Class(UsingEnumMemberResolution),
185 Namespace(UsingEnumMemberResolution),
186 Missing,
187}
188
189impl SemanticUsingEnumOwners {
190 pub(crate) fn new() -> Self {
191 Self {
192 class_imports: HashMap::default(),
193 namespace_imports: HashMap::default(),
194 }
195 }
196
197 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
198 let imports = self.class_imports.entry(class).or_default();
199 if !imports
200 .iter()
201 .any(|existing| same_visible_symbol(existing, &enum_owner))
202 {
203 imports.push(enum_owner);
204 }
205 }
206
207 pub fn import_namespace(
208 &mut self,
209 namespace: Vec<String>,
210 declaration_byte: usize,
211 enum_owner: CodeUnit,
212 ) {
213 let imports = self.namespace_imports.entry(namespace).or_default();
214 if !imports
215 .iter()
216 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
217 {
218 imports.push((declaration_byte, enum_owner));
219 }
220 }
221
222 pub fn resolve_member(
223 &self,
224 visibility: &VisibilityIndex<'_>,
225 file: &ProjectFile,
226 class: Option<&CodeUnit>,
227 namespace: &[String],
228 before_byte: usize,
229 name: &str,
230 ) -> SemanticUsingEnumMemberResolution {
231 if let Some(class) = class
232 && let Some((_, imports)) = self
233 .class_imports
234 .iter()
235 .find(|(owner, _)| same_visible_symbol(owner, class))
236 {
237 let resolution =
238 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
239 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
240 return SemanticUsingEnumMemberResolution::Class(resolution);
241 }
242 }
243 for prefix_len in (0..=namespace.len()).rev() {
244 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
245 continue;
246 };
247 let owners = imports
248 .iter()
249 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
250 .map(|(_, owner)| owner);
251 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
252 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
253 return SemanticUsingEnumMemberResolution::Namespace(resolution);
254 }
255 }
256 SemanticUsingEnumMemberResolution::Missing
257 }
258}
259
260fn resolve_using_enum_member_for_owners<'a>(
261 visibility: &VisibilityIndex<'_>,
262 file: &ProjectFile,
263 owners: impl IntoIterator<Item = &'a CodeUnit>,
264 name: &str,
265) -> UsingEnumMemberResolution {
266 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
267 for owner in owners {
268 for member in visibility.visible_members_for_owner_name(file, owner, name) {
269 if !member.is_field()
270 || matches.iter().any(|(existing_owner, existing_member)| {
271 same_visible_symbol(existing_owner, owner)
272 && same_visible_symbol(existing_member, member)
273 })
274 {
275 continue;
276 }
277 matches.push((owner.clone(), member.clone()));
278 }
279 }
280 match matches.len() {
281 0 => UsingEnumMemberResolution::Missing,
282 1 => {
283 let (owner, member) = matches.pop().expect("one using-enum match");
284 UsingEnumMemberResolution::Resolved { owner, member }
285 }
286 _ => UsingEnumMemberResolution::Ambiguous,
287 }
288}
289
290impl ScopedUsingEnumOwners {
291 pub(crate) fn new() -> Self {
292 Self {
293 scopes: vec![Vec::new()],
294 }
295 }
296
297 pub fn enter_scope(&mut self) {
298 self.scopes.push(Vec::new());
299 }
300
301 pub fn exit_scope(&mut self) {
302 if self.scopes.len() > 1 {
303 self.scopes.pop();
304 }
305 }
306
307 pub fn import(&mut self, owner: CodeUnit) {
308 let scope = self
309 .scopes
310 .last_mut()
311 .expect("using-enum scope stack is never empty");
312 if !scope
313 .iter()
314 .any(|existing| same_visible_symbol(existing, &owner))
315 {
316 scope.push(owner);
317 }
318 }
319
320 pub fn resolve_member(
321 &self,
322 visibility: &VisibilityIndex<'_>,
323 file: &ProjectFile,
324 name: &str,
325 ) -> UsingEnumMemberResolution {
326 for scope in self.scopes.iter().rev() {
327 let resolution =
328 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
329 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
330 return resolution;
331 }
332 }
333 UsingEnumMemberResolution::Missing
334 }
335}
336
337#[derive(Clone)]
338pub struct TargetSpec {
339 pub target: CodeUnit,
340 pub kind: TargetKind,
341 pub owner: Option<CodeUnit>,
342 pub member_name: String,
343 pub callable_arity: Option<CallableArity>,
344 pub activated_callable_arities: Vec<ActivatedCallableArity>,
345 pub param_types: Option<Vec<String>>,
346 pub enum_owner_kind: EnumOwnerKind,
347 pub owner_is_forward_declaration: bool,
348 pub callable_has_definition_body: bool,
349}
350
351#[derive(Clone, Copy)]
352pub struct ActivatedCallableArity {
353 pub activation_byte: usize,
354 pub arity: CallableArity,
355}
356
357#[derive(Debug, PartialEq, Eq, Hash)]
358pub struct TypeScanKey {
359 target: LogicalSymbolKey,
360 member_name: String,
361}
362
363#[derive(Clone, Debug, PartialEq, Eq, Hash)]
364struct LogicalSymbolKey {
365 kind: CodeUnitType,
366 fq_name: String,
367 signature: Option<String>,
368}
369
370struct ResolvedTypeOwner {
371 unit: CodeUnit,
372 is_forward_declaration: bool,
373}
374
375#[derive(Clone, Copy, PartialEq, Eq)]
376pub enum EnumOwnerKind {
377 Scoped,
378 Unscoped,
379 NonEnum,
380}
381
382impl TargetSpec {
383 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
384 (self.kind == TargetKind::Type).then(|| TypeScanKey {
385 target: logical_symbol_key(&self.target),
386 member_name: self.member_name.clone(),
387 })
388 }
389
390 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
391 if target.is_class() {
392 return Some(Self::new(
393 target.clone(),
394 TargetKind::Type,
395 Some(target.clone()),
396 target.identifier().to_string(),
397 None,
398 None,
399 ));
400 }
401
402 if target.is_field() {
403 let owner = type_owner_of(analyzer, target);
409 let kind = if owner.is_some() {
410 TargetKind::MemberField
411 } else {
412 TargetKind::GlobalField
413 };
414 let enum_owner_kind = owner
415 .as_ref()
416 .map(|owner| classify_enum_owner(analyzer, owner))
417 .unwrap_or(EnumOwnerKind::NonEnum);
418 let mut spec = Self::new(
419 target.clone(),
420 kind,
421 owner,
422 target.identifier().to_string(),
423 None,
424 None,
425 );
426 spec.enum_owner_kind = enum_owner_kind;
427 return Some(spec);
428 }
429
430 if target.is_function() {
431 let owner_resolution = target_type_owner_resolution(analyzer, target);
434 let owner_is_forward_declaration = owner_resolution
435 .as_ref()
436 .is_some_and(|owner| owner.is_forward_declaration);
437 let owner = owner_resolution.map(|owner| owner.unit);
438 let kind = if owner.as_ref().is_some_and(|owner| {
439 target.identifier() == owner.identifier()
440 || analyzer
441 .cpp
442 .and_then(|cpp| cpp.template_metadata(owner))
443 .is_some_and(|metadata| metadata.primary_name == target.identifier())
444 }) {
445 TargetKind::Constructor
446 } else if owner.is_some() {
447 TargetKind::Method
448 } else {
449 TargetKind::FreeFunction
450 };
451 let mut spec = Self::new(
452 target.clone(),
453 kind,
454 owner,
455 target.identifier().to_string(),
456 Some(cpp_callable_arity(analyzer, target)),
457 cpp_callable_parameter_types(analyzer, target),
458 );
459 spec.owner_is_forward_declaration = owner_is_forward_declaration;
460 spec.callable_has_definition_body =
461 callable_target_has_definition_body(analyzer, target);
462 return Some(spec);
463 }
464
465 if target.is_macro() {
466 return Some(Self::new(
467 target.clone(),
468 TargetKind::Macro,
469 None,
470 target.identifier().to_string(),
471 None,
472 None,
473 ));
474 }
475
476 None
477 }
478
479 pub fn with_visible_callable_arities<'a>(
480 &'a self,
481 analyzer: &CppGraphSource<'_>,
482 cpp: &dyn CppSource,
483 visibility: &VisibilityIndex<'_>,
484 file: &ProjectFile,
485 prepared: &PreparedSyntaxTree,
486 ) -> Cow<'a, Self> {
487 let macro_parameter_arity =
488 visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
489 let activated_callable_arities =
490 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
491 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
492 return Cow::Borrowed(self);
493 }
494 let mut effective = self.clone();
495 if let Some(macro_parameter_arity) = macro_parameter_arity {
496 effective.callable_arity = Some(macro_parameter_arity);
497 }
498 effective.activated_callable_arities = activated_callable_arities;
499 Cow::Owned(effective)
500 }
501
502 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
503 let base = self.callable_arity?;
504 Some(
505 self.activated_callable_arities
506 .iter()
507 .filter(|candidate| candidate.activation_byte <= byte)
508 .fold(base, |arity, candidate| {
509 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
510 }),
511 )
512 }
513
514 pub fn new(
515 target: CodeUnit,
516 kind: TargetKind,
517 owner: Option<CodeUnit>,
518 member_name: String,
519 callable_arity: Option<CallableArity>,
520 param_types: Option<Vec<String>>,
521 ) -> Self {
522 Self {
523 target,
524 kind,
525 owner,
526 member_name,
527 callable_arity,
528 activated_callable_arities: Vec::new(),
529 param_types,
530 enum_owner_kind: EnumOwnerKind::NonEnum,
531 owner_is_forward_declaration: false,
532 callable_has_definition_body: false,
533 }
534 }
535}
536
537fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
538 let Some(cpp) = analyzer.cpp else {
539 return false;
540 };
541 let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
542 return false;
543 };
544 analyzer.ranges(target).into_iter().any(|range| {
545 let end = range
546 .start_byte
547 .saturating_add(1)
548 .min(prepared.source().len());
549 let mut current = prepared
550 .tree()
551 .root_node()
552 .descendant_for_byte_range(range.start_byte, end);
553 while let Some(node) = current {
554 match node.kind() {
555 "function_definition" => return true,
556 "declaration" => return false,
557 _ => current = node.parent(),
558 }
559 }
560 false
561 })
562}
563
564fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
565 LogicalSymbolKey {
566 kind: unit.kind(),
567 fq_name: unit.fq_name(),
568 signature: unit.signature().map(str::to_string),
569 }
570}
571
572fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
573 let classify = |source: &str| {
574 let source = source.trim_start();
575 if source.starts_with("enum class ") || source.starts_with("enum struct ") {
576 Some(EnumOwnerKind::Scoped)
577 } else if source.starts_with("enum ") {
578 Some(EnumOwnerKind::Unscoped)
579 } else {
580 None
581 }
582 };
583 owner
584 .signature()
585 .and_then(classify)
586 .or_else(|| {
587 analyzer
588 .get_source(owner, false)
589 .as_deref()
590 .and_then(classify)
591 })
592 .unwrap_or(EnumOwnerKind::NonEnum)
593}
594
595#[derive(Clone, PartialEq, Eq, Hash)]
596pub struct CppScanBinding {
597 pub unit: Option<CodeUnit>,
598 pub type_name: Option<String>,
599 pub indirection: i32,
600}
601
602impl CppScanBinding {
603 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
604 Self {
605 type_name: Some(cpp_name_for(&unit)),
606 unit: Some(unit),
607 indirection,
608 }
609 }
610
611 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
612 Self {
613 type_name: Some(type_name),
614 unit,
615 indirection,
616 }
617 }
618
619 pub fn as_arg_type(&self) -> Option<CppArgType> {
620 let name = self
621 .type_name
622 .clone()
623 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
624 Some(CppArgType {
625 name,
626 unit: self.unit.clone(),
627 indirection: self.indirection,
628 pointee_const: false,
629 })
630 }
631}
632
633type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
634pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
635pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
636type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
637type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
638type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
639type MacroLocalBindingTemplateCache =
640 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
641type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
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
657pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
666
667struct MacroEnvironmentCheckpoint {
669 frontier: usize,
671 environment: Arc<MacroEnvironment>,
672}
673
674struct MacroEnvironmentCheckpoints {
683 checkpoints: Vec<MacroEnvironmentCheckpoint>,
684}
685
686impl MacroEnvironmentCheckpoints {
687 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
689 let index = self
690 .checkpoints
691 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
692 assert!(
693 index > 0,
694 "a checkpoint vector starts at frontier zero, which precedes every request"
695 );
696 &self.checkpoints[index - 1]
697 }
698}
699
700impl MacroEnvironment {
701 fn binding(&self, name: &str) -> Option<&MacroBinding> {
702 self.bindings.get(name)
703 }
704
705 fn may_bind(&self, name: &str) -> bool {
706 self.bindings.contains_key(name) || self.unknown_names
707 }
708
709 fn insert(&mut self, name: String, binding: MacroBinding) {
710 self.known_undefined_names.remove(&name);
711 self.bindings.insert(name, binding);
712 }
713
714 fn remove(&mut self, name: &str) {
715 self.bindings.remove(name);
716 self.known_undefined_names.insert(name.to_string());
717 }
718
719 fn remove_known_undefined(&mut self, name: &str) {
720 self.known_undefined_names.remove(name);
721 }
722
723 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
724 for binding in self.bindings.values_mut() {
725 *binding = MacroBinding::uncertain_from(binding, source, byte);
726 }
727 self.known_undefined_names.clear();
728 self.build_proven_defines.clear();
733 self.unknown_names = true;
734 }
735
736 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
737 guards.iter().all(|guard| self.guard_may_hold(guard))
738 }
739
740 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
741 let Some(expression) = guard.as_boolean_expression() else {
742 return true;
743 };
744 self.boolean_guard_may_hold(&expression)
745 }
746
747 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
748 match expression {
749 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
750 BooleanGuardExpression::Undefined(name) => {
751 self.bindings
752 .get(name)
753 .is_none_or(|binding| !binding.is_exact())
754 && (!self.build_proven_defines.contains(name)
755 || self.known_undefined_names.contains(name))
756 }
757 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
758 BooleanGuardExpression::Opaque(_)
759 | BooleanGuardExpression::NegatedOpaque(_)
760 | BooleanGuardExpression::Constant(true) => true,
761 BooleanGuardExpression::Constant(false) => false,
762 BooleanGuardExpression::All(expressions) => expressions
763 .iter()
764 .all(|expression| self.boolean_guard_may_hold(expression)),
765 BooleanGuardExpression::Any(expressions) => expressions
766 .iter()
767 .any(|expression| self.boolean_guard_may_hold(expression)),
768 }
769 }
770}
771
772#[derive(Clone)]
773pub enum EffectiveUsingTarget {
774 Ordinary {
775 name: String,
776 target_components: Vec<String>,
777 global: bool,
778 },
779 Namespace {
780 namespace_components: Vec<String>,
781 global: bool,
782 },
783}
784
785#[derive(Clone)]
786pub struct OrdinaryTypeImport {
787 pub target: EffectiveUsingTarget,
788 pub source: ProjectFile,
789 pub declaration_byte: usize,
790 pub scope_start: usize,
791 pub scope_end: usize,
792 pub scope_depth: usize,
793 pub block_scope: bool,
794 pub lexical_depth: usize,
795 pub declaration_namespace: Vec<String>,
796 pub namespace_scope: Option<Vec<String>>,
797 pub resolved_target_components: Option<Vec<String>>,
798 pub required_guards: HashSet<PreprocessorGuard>,
799}
800
801#[derive(Clone)]
802pub struct ConditionalIncludeProjection {
803 pub activation_byte: usize,
804 pub required_guards: HashSet<PreprocessorGuard>,
805}
806
807#[derive(Default)]
808pub struct SourceUsingIndex {
809 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
810 pub directives: Vec<OrdinaryTypeImport>,
811}
812
813#[derive(Default)]
814pub struct ProjectUsingIndex {
815 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
816 pub directives: Vec<OrdinaryTypeImport>,
817}
818
819type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
820
821pub struct EffectiveUsingIndex {
822 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
823}
824
825impl EffectiveUsingIndex {
826 fn new(_root: ProjectFile) -> Self {
827 Self {
828 projected_by_name: Mutex::new(HashMap::default()),
829 }
830 }
831
832 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
833 self.projected_by_name
834 .lock()
835 .expect("C++ effective-using projection cache poisoned")
836 .entry(name.to_string())
837 .or_default()
838 .clone()
839 }
840}
841
842pub enum OrdinaryTypeImportResolution {
843 Resolved {
844 target: CodeUnit,
845 target_components: Vec<String>,
846 lexical_depth: usize,
847 is_direct: bool,
848 },
849 Ambiguous {
850 lexical_depth: usize,
851 },
852 Missing,
853}
854
855type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
856type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
857type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
858type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
859type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
860type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
861type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
862
863struct ExtractedComparable {
868 shapes: Vec<CppComparableSlot>,
869 suffix: String,
870}
871
872const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
876
877pub struct VisibilityIndex<'a> {
888 cpp: &'a dyn CppSource,
889 token: QueryToken<'a>,
894 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
895 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
896 global_field_internal_linkage: HashMap<CodeUnit, bool>,
897 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
898 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
899 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
900 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
901 project_using_index: OnceLock<ProjectUsingIndex>,
902 callable_reference_specs:
903 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
904 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
905 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
906 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
907 #[cfg(any(test, feature = "test-support"))]
908 conditional_include_projection_index_build_count: AtomicUsize,
909 #[cfg(any(test, feature = "test-support"))]
910 conditional_include_projection_state_count: AtomicUsize,
911 #[cfg(any(test, feature = "test-support"))]
912 conditional_include_target_state_count: AtomicUsize,
913 #[cfg(any(test, feature = "test-support"))]
914 include_activation_build_count: AtomicUsize,
915 #[cfg(any(test, feature = "test-support"))]
916 using_donor_activation_count: AtomicUsize,
917 #[cfg(any(test, feature = "test-support"))]
918 using_namespace_lookup_count: AtomicUsize,
919 #[cfg(any(test, feature = "test-support"))]
920 using_name_candidate_inspection_count: AtomicUsize,
921 #[cfg(any(test, feature = "test-support"))]
922 callable_reference_spec_build_count: AtomicUsize,
923 #[cfg(any(test, feature = "test-support"))]
924 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
925 #[cfg(any(test, feature = "test-support"))]
926 visible_parser_alias_name_set_build_count: AtomicUsize,
927 parser_alias_fallback_calls: AtomicUsize,
928 parser_alias_fallback_files: AtomicUsize,
929 parser_alias_source_parses: AtomicUsize,
930 parser_alias_fallback_elapsed_micros: AtomicUsize,
931 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
932 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
933 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
934 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
935 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
936 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
937 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
938 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
939 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
940 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
941 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
948 macro_replacements: Mutex<MacroReplacementCache>,
949 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
950 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
951 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
952 #[cfg(any(test, feature = "test-support"))]
953 pub macro_replacement_parse_count: AtomicUsize,
954 #[cfg(any(test, feature = "test-support"))]
955 pub macro_event_application_count: AtomicUsize,
956 #[cfg(any(test, feature = "test-support"))]
959 pub macro_environment_checkpoint_build_count: AtomicUsize,
960 #[cfg(any(test, feature = "test-support"))]
963 pub macro_environment_copy_count: AtomicUsize,
964 #[cfg(any(test, feature = "test-support"))]
965 pub macro_environment_request_count: AtomicUsize,
966 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
967 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
968 #[cfg(any(test, feature = "test-support"))]
969 qualified_candidate_inspections: AtomicUsize,
970 #[cfg(any(test, feature = "test-support"))]
971 target_preserving_type_resolution_count: AtomicUsize,
972}
973
974impl Drop for VisibilityIndex<'_> {
975 fn drop(&mut self) {
976 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
977 return;
978 }
979 #[cfg(any(test, feature = "test-support"))]
980 eprintln!(
981 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
982 self.macro_environment_request_count.load(Ordering::Relaxed),
983 self.macro_environment_copy_count.load(Ordering::Relaxed),
984 self.macro_environment_checkpoint_build_count
985 .load(Ordering::Relaxed),
986 self.macro_event_application_count.load(Ordering::Relaxed),
987 );
988 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
989 if calls == 0 {
990 return;
991 }
992 eprintln!(
993 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
994 calls,
995 self.parser_alias_fallback_files.load(Ordering::Relaxed),
996 self.parser_alias_source_parses.load(Ordering::Relaxed),
997 self.parser_alias_fallback_elapsed_micros
998 .load(Ordering::Relaxed)
999 / 1_000,
1000 );
1001 }
1002}
1003
1004#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1005pub enum PreprocessorGuard {
1006 Defined(String),
1007 Undefined(String),
1008 Boolean(BooleanGuardExpression),
1009 Expression(String),
1010 NegatedExpression(String),
1011 Constant(bool),
1012}
1013
1014#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1015pub enum BooleanGuardExpression {
1016 Defined(String),
1017 Undefined(String),
1018 Truthy(String),
1019 Falsy(String),
1020 Opaque(String),
1021 NegatedOpaque(String),
1022 All(Vec<BooleanGuardExpression>),
1023 Any(Vec<BooleanGuardExpression>),
1024 Constant(bool),
1025}
1026
1027impl BooleanGuardExpression {
1028 fn negated(&self) -> Self {
1029 match self {
1030 Self::Defined(name) => Self::Undefined(name.clone()),
1031 Self::Undefined(name) => Self::Defined(name.clone()),
1032 Self::Truthy(name) => Self::Falsy(name.clone()),
1033 Self::Falsy(name) => Self::Truthy(name.clone()),
1034 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1035 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1036 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1037 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1038 Self::Constant(value) => Self::Constant(!value),
1039 }
1040 }
1041
1042 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1043 Self::normalized(expressions, true)
1044 }
1045
1046 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1047 Self::normalized(expressions, false)
1048 }
1049
1050 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1051 let mut normalized = Vec::new();
1052 for expression in expressions {
1053 match expression {
1054 Self::All(nested) if conjunction => normalized.extend(nested),
1055 Self::Any(nested) if !conjunction => normalized.extend(nested),
1056 Self::Constant(value) if value == conjunction => {}
1057 Self::Constant(value) => return Self::Constant(value),
1058 expression => normalized.push(expression),
1059 }
1060 }
1061 normalized.sort_unstable();
1062 normalized.dedup();
1063 match normalized.len() {
1064 0 => Self::Constant(conjunction),
1065 1 => normalized.pop().expect("one Boolean guard expression"),
1066 _ if conjunction => Self::All(normalized),
1067 _ => Self::Any(normalized),
1068 }
1069 }
1070
1071 fn implies(&self, required: &Self) -> bool {
1072 if self == required
1073 || matches!(self, Self::Constant(false))
1074 || matches!(required, Self::Constant(true))
1075 {
1076 return true;
1077 }
1078 if matches!(
1079 (self, required),
1080 (Self::Truthy(active), Self::Defined(required))
1081 | (Self::Undefined(active), Self::Falsy(required))
1082 if active == required
1083 ) {
1084 return true;
1085 }
1086 match self {
1087 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1088 Self::All(active) => match required {
1089 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1090 _ => active.iter().any(|expression| expression.implies(required)),
1091 },
1092 _ => match required {
1093 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1094 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1095 _ => false,
1096 },
1097 }
1098 }
1099
1100 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1101 match self {
1102 Self::Defined(name)
1103 | Self::Undefined(name)
1104 | Self::Truthy(name)
1105 | Self::Falsy(name) => name == macro_name,
1106 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1109 Self::All(expressions) | Self::Any(expressions) => expressions
1110 .iter()
1111 .any(|expression| expression.may_depend_on_macro(macro_name)),
1112 Self::Constant(_) => false,
1113 }
1114 }
1115
1116 pub fn heap_size(&self) -> usize {
1117 match self {
1118 Self::Defined(value)
1119 | Self::Undefined(value)
1120 | Self::Truthy(value)
1121 | Self::Falsy(value)
1122 | Self::Opaque(value)
1123 | Self::NegatedOpaque(value) => value.len(),
1124 Self::All(expressions) | Self::Any(expressions) => {
1125 expressions
1126 .iter()
1127 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1128 size.saturating_add(std::mem::size_of::<Self>())
1129 .saturating_add(expression.heap_size())
1130 })
1131 }
1132 Self::Constant(_) => 0,
1133 }
1134 }
1135}
1136
1137impl PreprocessorGuard {
1138 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1139 match self {
1140 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1141 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1142 Self::Boolean(expression) => Some(expression.clone()),
1143 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1144 Self::Expression(_) | Self::NegatedExpression(_) => None,
1145 }
1146 }
1147
1148 fn negated(&self) -> Self {
1149 match self {
1150 Self::Defined(name) => Self::Undefined(name.clone()),
1151 Self::Undefined(name) => Self::Defined(name.clone()),
1152 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1153 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1154 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1155 Self::Constant(value) => Self::Constant(!value),
1156 }
1157 }
1158
1159 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1160 match self {
1161 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1162 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1163 Self::Expression(_) | Self::NegatedExpression(_) => true,
1166 Self::Constant(_) => false,
1167 }
1168 }
1169}
1170
1171#[derive(Clone, PartialEq, Eq)]
1172pub enum MacroDefinition {
1173 Object {
1174 replacement: String,
1175 },
1176 Function {
1177 parameters: Vec<String>,
1178 replacement: String,
1179 },
1180 Unsupported,
1181}
1182
1183#[derive(Clone, Debug, PartialEq, Eq)]
1184pub enum MacroIncludeProtection {
1185 MacroGuard(String),
1186 PragmaOnce,
1187 None,
1188}
1189
1190enum ParsedMacroReplacement {
1191 Parsed { source: String, tree: Tree },
1192 Unsupported,
1193}
1194
1195const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1199
1200pub struct ParsedReplacementBody {
1209 pub source: String,
1210 pub tree: Tree,
1211 pub body_offset: usize,
1212 pub parameters: Vec<String>,
1213}
1214
1215impl ParsedReplacementBody {
1216 pub fn statements(&self) -> Option<Node<'_>> {
1218 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1219 .child_by_field_name("body")
1220 }
1221
1222 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1228 debug_assert!(node.start_byte() >= self.body_offset);
1229 let start = replacement_start + (node.start_byte() - self.body_offset);
1230 start..start + (node.end_byte() - node.start_byte())
1231 }
1232
1233 fn expands_variadic_arguments(&self) -> bool {
1240 let mut stack = vec![self.tree.root_node()];
1241 while let Some(node) = stack.pop() {
1242 if matches!(
1243 node.kind(),
1244 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1245 ) && node_text(node, &self.source) == "__VA_ARGS__"
1246 {
1247 return true;
1248 }
1249 for index in (0..node.named_child_count()).rev() {
1250 if let Some(child) = node.named_child(index) {
1251 stack.push(child);
1252 }
1253 }
1254 }
1255 false
1256 }
1257}
1258
1259fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1260 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1261 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1262 if compact.starts_with("0x") || compact.starts_with("0X") {
1263 (16, 2, |ch| ch.is_ascii_hexdigit())
1264 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1265 (2, 2, |ch| matches!(ch, '0' | '1'))
1266 } else if compact.starts_with('0') && compact.len() > 1 {
1267 (8, 0, |ch| matches!(ch, '0'..='7'))
1268 } else {
1269 (10, 0, |ch| ch.is_ascii_digit())
1270 };
1271 let digit_len = compact[digits_start..]
1272 .chars()
1273 .take_while(|ch| digit_matches(*ch))
1274 .map(char::len_utf8)
1275 .sum::<usize>();
1276 if digit_len == 0 {
1277 return None;
1278 }
1279 let digits_end = digits_start + digit_len;
1280 if !compact[digits_end..]
1281 .chars()
1282 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1283 {
1284 return None;
1285 }
1286 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1287}
1288
1289#[derive(Clone)]
1290enum MacroLocalBindingTypeTemplate {
1291 Parameter(usize),
1292 Fixed(String),
1293}
1294
1295#[derive(Clone)]
1296struct MacroLocalBindingTemplate {
1297 name: String,
1298 declared_type: MacroLocalBindingTypeTemplate,
1299 pointer_depth: i32,
1300}
1301
1302pub struct MacroLocalBinding<'tree> {
1310 pub name: String,
1311 pub type_name: String,
1312 pub type_node: Option<Node<'tree>>,
1313 pub pointer_depth: i32,
1314 pub proven_unit: Option<CodeUnit>,
1315}
1316
1317fn macro_replacement_type_parameter(
1318 body: &ParsedReplacementBody,
1319 parameters: &[String],
1320) -> Option<usize> {
1321 let mut found = None;
1322 let mut stack = vec![body.tree.root_node()];
1323 while let Some(node) = stack.pop() {
1324 let type_position = node.kind() == "type_identifier"
1325 || (node.kind() == "identifier"
1326 && node.parent().is_some_and(|parent| {
1327 parent.kind() == "type_descriptor"
1328 && parent.child_by_field_name("type") == Some(node)
1329 }));
1330 let offsetof_type_position = node.kind() == "identifier"
1331 && node
1332 .parent()
1333 .filter(|parent| parent.kind() == "argument_list")
1334 .and_then(|arguments| arguments.parent())
1335 .is_some_and(|call| {
1336 call.kind() == "call_expression"
1337 && call
1338 .child_by_field_name("function")
1339 .is_some_and(|function| {
1340 function.kind() == "identifier"
1341 && node_text(function, &body.source) == "offsetof"
1342 })
1343 && call
1344 .child_by_field_name("arguments")
1345 .is_some_and(|arguments| {
1346 argument_children(arguments).next() == Some(node)
1347 })
1348 });
1349 if (type_position || offsetof_type_position)
1350 && let Some(index) = parameters
1351 .iter()
1352 .position(|parameter| parameter == node_text(node, &body.source))
1353 {
1354 if found.is_some_and(|existing| existing != index) {
1355 return None;
1356 }
1357 found = Some(index);
1358 }
1359 for index in (0..node.named_child_count()).rev() {
1360 if let Some(child) = node.named_child(index) {
1361 stack.push(child);
1362 }
1363 }
1364 }
1365 found
1366}
1367
1368fn macro_type_argument_node<'tree>(node: Node<'tree>, source: &str) -> Option<Node<'tree>> {
1369 match node.kind() {
1370 "type_descriptor" => {
1371 let type_child = node
1372 .child_by_field_name("type")
1373 .or_else(|| first_type_child(node))?;
1374 for index in (0..node.named_child_count()).rev() {
1375 let child = node.named_child(index)?;
1376 if child != type_child
1377 && matches!(
1378 child.kind(),
1379 "identifier"
1380 | "type_identifier"
1381 | "qualified_identifier"
1382 | "scoped_type_identifier"
1383 )
1384 {
1385 return Some(child);
1386 }
1387 }
1388 macro_type_argument_node(type_child, source)
1389 }
1390 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1391 node.child_by_field_name("name")
1392 }
1393 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1394 .next_named_sibling()
1395 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1396 .and_then(|error| error.named_child(0))
1397 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1398 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1399 }
1400}
1401
1402fn recognized_c_macro_declarator_binding<'tree>(
1407 statement: Node<'tree>,
1408 source: &str,
1409) -> Option<MacroLocalBinding<'tree>> {
1410 let assignment = match statement.kind() {
1411 "assignment_expression" => statement,
1412 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1413 _ => return None,
1414 };
1415 if assignment.kind() != "assignment_expression" {
1416 return None;
1417 }
1418 let call = assignment.child_by_field_name("left")?;
1419 if call.kind() != "call_expression" {
1420 return None;
1421 }
1422 let function = call.child_by_field_name("function")?;
1423 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1424 return None;
1425 }
1426 let arguments = call.child_by_field_name("arguments")?;
1427 let mut actuals = argument_children(arguments);
1428 let type_node = actuals.next()?;
1429 if actuals.next().is_some()
1430 || !matches!(
1431 type_node.kind(),
1432 "identifier"
1433 | "type_identifier"
1434 | "qualified_identifier"
1435 | "scoped_type_identifier"
1436 | "template_type"
1437 )
1438 {
1439 return None;
1440 }
1441 let name_node = (0..assignment.named_child_count())
1442 .filter_map(|index| assignment.named_child(index))
1443 .filter(|child| child.kind() == "ERROR")
1444 .filter_map(|error| {
1445 (error.named_child_count() == 1)
1446 .then(|| error.named_child(0))
1447 .flatten()
1448 })
1449 .find(|node| node.kind() == "identifier")?;
1450 let name = node_text(name_node, source).trim();
1451 let type_name = node_text(type_node, source).trim();
1452 if name.is_empty() || type_name.is_empty() {
1453 return None;
1454 }
1455 Some(MacroLocalBinding {
1456 name: name.to_string(),
1457 type_name: type_name.to_string(),
1458 type_node: Some(type_node),
1459 pointer_depth: 1,
1460 proven_unit: None,
1461 })
1462}
1463
1464#[derive(Clone, PartialEq, Eq)]
1465pub struct MacroBinding {
1466 source: ProjectFile,
1467 declaration_byte: usize,
1468 definition: MacroDefinition,
1469 exact: bool,
1470}
1471
1472impl MacroBinding {
1473 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1474 Self {
1475 source: source.clone(),
1476 declaration_byte,
1477 definition: MacroDefinition::Unsupported,
1478 exact: false,
1479 }
1480 }
1481
1482 fn is_exact(&self) -> bool {
1483 self.exact
1484 }
1485
1486 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1487 Self {
1488 source: source.clone(),
1489 declaration_byte,
1490 definition: current.definition.clone(),
1491 exact: false,
1492 }
1493 }
1494}
1495
1496type OwningPreprocessorConditionals = Box<[usize]>;
1508
1509#[derive(Clone)]
1510pub enum MacroEvent {
1511 Define {
1512 name: String,
1513 binding: MacroBinding,
1514 byte: usize,
1515 conditionals: OwningPreprocessorConditionals,
1516 },
1517 Undef {
1518 name: String,
1519 byte: usize,
1520 conditionals: OwningPreprocessorConditionals,
1521 },
1522 Include {
1523 targets: Vec<ProjectFile>,
1524 byte: usize,
1525 conditionals: OwningPreprocessorConditionals,
1526 },
1527 Invalidate {
1528 byte: usize,
1529 },
1530}
1531
1532impl MacroEvent {
1533 pub fn byte(&self) -> usize {
1534 match self {
1535 Self::Define { byte, .. }
1536 | Self::Undef { byte, .. }
1537 | Self::Include { byte, .. }
1538 | Self::Invalidate { byte } => *byte,
1539 }
1540 }
1541}
1542
1543#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1544pub enum CallArityEvidence {
1545 Exact(usize),
1546 Unknown,
1547}
1548
1549impl CallArityEvidence {
1550 pub fn exact(self) -> Option<usize> {
1551 match self {
1552 Self::Exact(arity) => Some(arity),
1553 Self::Unknown => None,
1554 }
1555 }
1556
1557 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1558 self.exact().map(|arity| expected.accepts(arity))
1559 }
1560}
1561
1562#[derive(Clone)]
1563struct DeclaredFieldTypeFact {
1564 type_text: String,
1565 indirection: i32,
1566 template_arguments: Option<Vec<CppTemplateExpression>>,
1567}
1568
1569#[derive(Clone, PartialEq, Eq)]
1570enum StructuredAliasTarget {
1571 Builtin,
1572 Named {
1573 components: Vec<String>,
1574 global: bool,
1575 arguments: Option<Vec<CppTemplateExpression>>,
1576 },
1577}
1578
1579struct CppAlias {
1580 name: String,
1581 target: String,
1582 namespace: Option<String>,
1583}
1584
1585type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1586
1587#[derive(Debug, Clone, PartialEq, Eq)]
1591pub enum CppTemplateResolutionError {
1592 AliasCycle { alias: CodeUnit },
1594 ArgumentBinding,
1596 Substitution,
1598 PrimarySelection,
1601 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1604}
1605
1606fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1609 let mut distinct: Vec<CodeUnit> = Vec::new();
1610 for unit in units {
1611 if !distinct
1612 .iter()
1613 .any(|existing| same_visible_symbol(existing, unit))
1614 {
1615 distinct.push(unit.clone());
1616 }
1617 }
1618 distinct
1619}
1620
1621impl<'a> VisibilityIndex<'a> {
1622 pub fn cpp(&self) -> &'a dyn CppSource {
1623 self.cpp
1624 }
1625
1626 pub fn token(&self) -> QueryToken<'a> {
1628 self.token
1629 }
1630
1631 #[cfg(any(test, feature = "test-support"))]
1639 pub fn from_visible_files_for_test(
1640 cpp: &'a dyn CppSource,
1641 token: QueryToken<'a>,
1642 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1643 ) -> Self {
1644 let visible_source_files_by_root = visible_by_file
1645 .iter()
1646 .map(|(file, visible)| {
1647 (
1648 file.clone(),
1649 visible
1650 .iter()
1651 .map(|unit| unit.source().clone())
1652 .chain(std::iter::once(file.clone()))
1653 .collect(),
1654 )
1655 })
1656 .collect();
1657 let mut global_field_internal_linkage = HashMap::default();
1658 Self {
1659 cpp,
1660 token,
1661 visible_by_identifier: build_visible_identifier_index(
1662 &CppGraphSource::from_source(cpp, token),
1663 &visible_by_file,
1664 &visible_source_files_by_root,
1665 &mut global_field_internal_linkage,
1666 ),
1667 global_field_internal_linkage,
1668 visible_by_file,
1669 visible_source_files_by_root,
1670 alias_cells: Mutex::new(HashMap::default()),
1671 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1672 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1673 project_using_index: OnceLock::new(),
1674 callable_reference_specs: Mutex::new(HashMap::default()),
1675 include_activation_cells: Mutex::new(HashMap::default()),
1676 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1677 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1678 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1679 conditional_include_projection_state_count: AtomicUsize::new(0),
1680 conditional_include_target_state_count: AtomicUsize::new(0),
1681 include_activation_build_count: AtomicUsize::new(0),
1682 using_donor_activation_count: AtomicUsize::new(0),
1683 using_namespace_lookup_count: AtomicUsize::new(0),
1684 using_name_candidate_inspection_count: AtomicUsize::new(0),
1685 callable_reference_spec_build_count: AtomicUsize::new(0),
1686 alias_source_parse_counts: Mutex::new(HashMap::default()),
1687 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1688 parser_alias_fallback_calls: AtomicUsize::new(0),
1689 parser_alias_fallback_files: AtomicUsize::new(0),
1690 parser_alias_source_parses: AtomicUsize::new(0),
1691 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1692 field_type_facts: Mutex::new(HashMap::default()),
1693 structured_alias_targets: Mutex::new(HashMap::default()),
1694 callable_comparables: Mutex::new(HashMap::default()),
1695 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1696 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1697 precise_parent_cache: Mutex::new(HashMap::default()),
1698 c_tag_kind_cache: Mutex::new(HashMap::default()),
1699 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1700 macro_event_cells: Mutex::new(HashMap::default()),
1701 macro_include_protection_cells: Mutex::new(HashMap::default()),
1702 macro_environment_checkpoints: Mutex::new(HashMap::default()),
1703 macro_replacements: Mutex::new(HashMap::default()),
1704 macro_local_binding_templates: Mutex::new(HashMap::default()),
1705 macro_replacement_bodies: Mutex::new(HashMap::default()),
1706 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1707 macro_replacement_parse_count: AtomicUsize::new(0),
1708 macro_event_application_count: AtomicUsize::new(0),
1709 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1710 macro_environment_copy_count: AtomicUsize::new(0),
1711 macro_environment_request_count: AtomicUsize::new(0),
1712 cpp_template_metadata: HashMap::default(),
1713 cpp_template_families: HashMap::default(),
1714 qualified_candidate_inspections: AtomicUsize::new(0),
1715 target_preserving_type_resolution_count: AtomicUsize::new(0),
1716 }
1717 }
1718
1719 fn cpp_source(&self) -> CppGraphSource<'a> {
1726 CppGraphSource::from_source(self.cpp, self.token)
1727 }
1728
1729 pub fn build(
1730 cpp: &'a dyn CppSource,
1731 token: QueryToken<'a>,
1732 analyzer: &CppGraphSource<'_>,
1733 roots: &HashSet<ProjectFile>,
1734 ) -> Self {
1735 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1736 }
1737
1738 pub fn build_with_cancellation(
1739 cpp: &'a dyn CppSource,
1740 token: QueryToken<'a>,
1741 analyzer: &CppGraphSource<'_>,
1742 roots: &HashSet<ProjectFile>,
1743 cancellation: Option<&CancellationToken>,
1744 ) -> Self {
1745 let visibility_started = Instant::now();
1746 let include_targets = cpp.include_target_index();
1747 let includes_started = Instant::now();
1748 let mut include_graph = IncludeGraph::default();
1749 for root in roots {
1750 include_graph.extend_with(root, cancellation, &mut |file| {
1751 cpp_include_paths(&cpp.visibility_import_statements(token, file))
1752 .into_iter()
1753 .flat_map(|include| {
1754 resolve_include_targets_with_index(file, &include, include_targets)
1755 })
1756 .collect()
1757 });
1758 }
1759 let include_elapsed = includes_started.elapsed();
1760 let include_file_count = include_graph.files().count();
1761 let visible_source_files_by_root = roots
1762 .iter()
1763 .map(|root| {
1764 (
1765 root.clone(),
1766 include_graph.reachable_files(root, cancellation),
1767 )
1768 })
1769 .collect::<HashMap<_, _>>();
1770 let mut visibility_stats = BoundedVisibilityStats::default();
1771 let mut visible_by_file = build_bounded_visible_declarations(
1772 cpp,
1773 token,
1774 analyzer,
1775 roots,
1776 &visible_source_files_by_root,
1777 cancellation,
1778 &mut visibility_stats,
1779 );
1780 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
1781 eprintln!(
1782 "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={}",
1783 visibility_started.elapsed().as_millis(),
1784 include_elapsed.as_millis(),
1785 include_file_count,
1786 visibility_stats.rounds,
1787 visibility_stats.root_names,
1788 visibility_stats.identifier_lookups,
1789 visibility_stats.candidate_units,
1790 visibility_stats.candidate_sources,
1791 visibility_stats.declaration_reads,
1792 visibility_stats.declaration_units,
1793 visibility_stats.selected_units,
1794 visibility_stats.dependency_ast_nodes,
1795 visibility_stats.dependency_names,
1796 visibility_stats.lookup_elapsed.as_millis(),
1797 visibility_stats.declaration_elapsed.as_millis(),
1798 visibility_stats.dependency_ast_elapsed.as_millis(),
1799 );
1800 }
1801 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
1802 let finalize_started = Instant::now();
1803 if report_stats {
1804 eprintln!(
1805 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
1806 visible_by_file.len(),
1807 visible_by_file.values().map(HashSet::len).sum::<usize>(),
1808 );
1809 }
1810 let owner_started = Instant::now();
1811 if report_stats {
1812 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
1813 }
1814 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1815 if report_stats {
1816 eprintln!(
1817 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
1818 owner_stats.unseen_owners,
1819 owner_stats.definition_lookups,
1820 owner_stats.admitted,
1821 owner_started.elapsed().as_millis(),
1822 );
1823 }
1824 let mut global_field_internal_linkage = HashMap::default();
1825 let identifier_started = Instant::now();
1826 if report_stats {
1827 eprintln!(
1828 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
1829 );
1830 }
1831 let visible_by_identifier = build_visible_identifier_index(
1832 analyzer,
1833 &visible_by_file,
1834 &visible_source_files_by_root,
1835 &mut global_field_internal_linkage,
1836 );
1837 if report_stats {
1838 eprintln!(
1839 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
1840 visible_by_identifier.len(),
1841 visible_by_identifier
1842 .values()
1843 .map(HashMap::len)
1844 .sum::<usize>(),
1845 visible_by_identifier
1846 .values()
1847 .flat_map(HashMap::values)
1848 .map(Vec::len)
1849 .sum::<usize>(),
1850 identifier_started.elapsed().as_millis(),
1851 );
1852 }
1853 let mut cpp_template_metadata = HashMap::default();
1854 let metadata_started = Instant::now();
1855 let mut template_classes = 0usize;
1856 if report_stats {
1857 eprintln!(
1858 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
1859 );
1860 }
1861 for unit in visible_by_file
1862 .values()
1863 .flatten()
1864 .filter(|unit| unit.is_class())
1865 {
1866 template_classes += 1;
1867 if cpp_template_metadata.contains_key(unit) {
1868 continue;
1869 }
1870 if let Some(metadata) = cpp.template_metadata(unit) {
1871 cpp_template_metadata.insert(unit.clone(), metadata);
1872 }
1873 }
1874 if report_stats {
1875 eprintln!(
1876 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
1877 template_classes,
1878 cpp_template_metadata.len(),
1879 metadata_started.elapsed().as_millis(),
1880 );
1881 }
1882 let families_started = Instant::now();
1883 if report_stats {
1884 eprintln!(
1885 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
1886 );
1887 }
1888 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1889 for (unit, metadata) in &cpp_template_metadata {
1890 cpp_template_families
1891 .entry(metadata.primary_fq_name.clone())
1892 .or_default()
1893 .push(unit.clone());
1894 }
1895 for family in cpp_template_families.values_mut() {
1904 sort_lookup_units(family);
1905 }
1906 if report_stats {
1907 eprintln!(
1908 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
1909 cpp_template_families.len(),
1910 cpp_template_families.values().map(Vec::len).sum::<usize>(),
1911 families_started.elapsed().as_millis(),
1912 );
1913 eprintln!(
1914 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
1915 visible_by_file.len(),
1916 visible_by_file.values().map(HashSet::len).sum::<usize>(),
1917 finalize_started.elapsed().as_millis(),
1918 visibility_started.elapsed().as_millis(),
1919 );
1920 }
1921 Self {
1922 cpp,
1923 token,
1924 visible_by_file,
1925 visible_by_identifier,
1926 global_field_internal_linkage,
1927 visible_source_files_by_root,
1928 alias_cells: Mutex::new(HashMap::default()),
1929 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1930 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1931 project_using_index: OnceLock::new(),
1932 callable_reference_specs: Mutex::new(HashMap::default()),
1933 include_activation_cells: Mutex::new(HashMap::default()),
1934 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1935 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1936 #[cfg(any(test, feature = "test-support"))]
1937 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1938 #[cfg(any(test, feature = "test-support"))]
1939 conditional_include_projection_state_count: AtomicUsize::new(0),
1940 #[cfg(any(test, feature = "test-support"))]
1941 conditional_include_target_state_count: AtomicUsize::new(0),
1942 #[cfg(any(test, feature = "test-support"))]
1943 include_activation_build_count: AtomicUsize::new(0),
1944 #[cfg(any(test, feature = "test-support"))]
1945 using_donor_activation_count: AtomicUsize::new(0),
1946 #[cfg(any(test, feature = "test-support"))]
1947 using_namespace_lookup_count: AtomicUsize::new(0),
1948 #[cfg(any(test, feature = "test-support"))]
1949 using_name_candidate_inspection_count: AtomicUsize::new(0),
1950 #[cfg(any(test, feature = "test-support"))]
1951 callable_reference_spec_build_count: AtomicUsize::new(0),
1952 #[cfg(any(test, feature = "test-support"))]
1953 alias_source_parse_counts: Mutex::new(HashMap::default()),
1954 #[cfg(any(test, feature = "test-support"))]
1955 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1956 parser_alias_fallback_calls: AtomicUsize::new(0),
1957 parser_alias_fallback_files: AtomicUsize::new(0),
1958 parser_alias_source_parses: AtomicUsize::new(0),
1959 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1960 field_type_facts: Mutex::new(HashMap::default()),
1961 structured_alias_targets: Mutex::new(HashMap::default()),
1962 callable_comparables: Mutex::new(HashMap::default()),
1963 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1964 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1965 precise_parent_cache: Mutex::new(HashMap::default()),
1966 c_tag_kind_cache: Mutex::new(HashMap::default()),
1967 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1968 macro_event_cells: Mutex::new(HashMap::default()),
1969 macro_include_protection_cells: Mutex::new(HashMap::default()),
1970 macro_environment_checkpoints: Mutex::new(HashMap::default()),
1971 macro_replacements: Mutex::new(HashMap::default()),
1972 macro_local_binding_templates: Mutex::new(HashMap::default()),
1973 macro_replacement_bodies: Mutex::new(HashMap::default()),
1974 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1975 #[cfg(any(test, feature = "test-support"))]
1976 macro_replacement_parse_count: AtomicUsize::new(0),
1977 #[cfg(any(test, feature = "test-support"))]
1978 macro_event_application_count: AtomicUsize::new(0),
1979 #[cfg(any(test, feature = "test-support"))]
1980 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1981 #[cfg(any(test, feature = "test-support"))]
1982 macro_environment_copy_count: AtomicUsize::new(0),
1983 #[cfg(any(test, feature = "test-support"))]
1984 macro_environment_request_count: AtomicUsize::new(0),
1985 cpp_template_metadata,
1986 cpp_template_families,
1987 #[cfg(any(test, feature = "test-support"))]
1988 qualified_candidate_inspections: AtomicUsize::new(0),
1989 #[cfg(any(test, feature = "test-support"))]
1990 target_preserving_type_resolution_count: AtomicUsize::new(0),
1991 }
1992 }
1993
1994 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1995 if file == target.source() {
1996 return true;
1997 }
1998 if self.global_field_has_internal_linkage(target) {
1999 return self
2000 .visible_source_files_by_root
2001 .get(file)
2002 .is_some_and(|sources| sources.contains(target.source()));
2003 }
2004 self.visible_by_file
2005 .get(file)
2006 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2007 }
2008
2009 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2010 self.global_field_internal_linkage
2011 .get(unit)
2012 .copied()
2013 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2014 }
2015
2016 pub fn call_arity_evidence(
2017 &self,
2018 file: &ProjectFile,
2019 call: Node<'_>,
2020 source: &str,
2021 ) -> CallArityEvidence {
2022 self.call_arity_evidence_at(file, call, source, call.start_byte())
2023 }
2024
2025 pub fn call_arity_evidence_at(
2033 &self,
2034 file: &ProjectFile,
2035 call: Node<'_>,
2036 source: &str,
2037 environment_byte: usize,
2038 ) -> CallArityEvidence {
2039 let Some(arguments) = call
2040 .child_by_field_name("arguments")
2041 .or_else(|| call.child_by_field_name("parameters"))
2042 .or_else(|| call.child_by_field_name("value"))
2043 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2044 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2045 else {
2046 return CallArityEvidence::Exact(0);
2047 };
2048 let recovered_c_keyword_arguments =
2049 recovered_c_keyword_argument_count(file, call, arguments, source);
2050 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2051 let arguments = argument_children(arguments)
2052 .flat_map(|argument| {
2053 recovered_c_new_expression_arguments(argument, c_semantics)
2054 .map(Vec::from)
2055 .unwrap_or_else(|| vec![argument])
2056 })
2057 .collect::<Vec<_>>();
2058 if arguments
2059 .iter()
2060 .all(|argument| !argument_shape_may_change_arity(*argument))
2061 {
2062 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2063 }
2064 let environment = self.macro_environment(file, environment_byte);
2065 let mut stack = Vec::new();
2066 let mut total = recovered_c_keyword_arguments;
2067 for argument in arguments {
2068 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2069 return CallArityEvidence::Unknown;
2070 }
2071 let CallArityEvidence::Exact(spread) =
2072 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2073 else {
2074 return CallArityEvidence::Unknown;
2075 };
2076 total += spread;
2077 }
2078 CallArityEvidence::Exact(total)
2079 }
2080
2081 fn argument_arity_evidence(
2082 &self,
2083 argument: Node<'_>,
2084 source: &str,
2085 environment: &MacroEnvironment,
2086 stack: &mut Vec<(ProjectFile, usize)>,
2087 ) -> CallArityEvidence {
2088 let (name, invocation_arguments, function_like) = match argument.kind() {
2089 "identifier" => (node_text(argument, source), None, false),
2090 "call_expression" => {
2091 let Some(function) = argument.child_by_field_name("function") else {
2092 return CallArityEvidence::Exact(1);
2093 };
2094 if function.kind() != "identifier" {
2095 return CallArityEvidence::Exact(1);
2096 }
2097 let Some(arguments) = argument.child_by_field_name("arguments") else {
2098 return CallArityEvidence::Exact(1);
2099 };
2100 (node_text(function, source), Some(arguments), true)
2101 }
2102 _ => return CallArityEvidence::Exact(1),
2103 };
2104 let Some(binding) = environment.binding(name) else {
2105 return if environment.unknown_names {
2106 CallArityEvidence::Unknown
2107 } else {
2108 CallArityEvidence::Exact(1)
2109 };
2110 };
2111 if !binding.is_exact() {
2112 return CallArityEvidence::Unknown;
2113 }
2114 match (&binding.definition, invocation_arguments, function_like) {
2115 (MacroDefinition::Object { replacement }, None, false) => self
2116 .replacement_arity_evidence(
2117 replacement,
2118 &[],
2119 &[],
2120 source,
2121 environment,
2122 stack,
2123 binding,
2124 ),
2125 (
2126 MacroDefinition::Function {
2127 parameters,
2128 replacement,
2129 },
2130 Some(arguments),
2131 true,
2132 ) => {
2133 let actuals = argument_children(arguments).collect::<Vec<_>>();
2134 if actuals.len() != parameters.len() {
2135 CallArityEvidence::Unknown
2136 } else {
2137 self.replacement_arity_evidence(
2138 replacement,
2139 parameters,
2140 &actuals,
2141 source,
2142 environment,
2143 stack,
2144 binding,
2145 )
2146 }
2147 }
2148 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2149 _ => CallArityEvidence::Unknown,
2150 }
2151 }
2152
2153 #[allow(clippy::too_many_arguments)]
2154 fn replacement_arity_evidence(
2155 &self,
2156 replacement: &str,
2157 parameters: &[String],
2158 actuals: &[Node<'_>],
2159 actual_source: &str,
2160 environment: &MacroEnvironment,
2161 stack: &mut Vec<(ProjectFile, usize)>,
2162 binding: &MacroBinding,
2163 ) -> CallArityEvidence {
2164 let identity = (binding.source.clone(), binding.declaration_byte);
2165 if stack.contains(&identity) || replacement.trim().is_empty() {
2166 return CallArityEvidence::Unknown;
2167 }
2168 stack.push(identity);
2169 let parsed = self.parsed_macro_replacement(binding, replacement);
2170 let evidence = (|| {
2171 let ParsedMacroReplacement::Parsed {
2172 source: sentinel,
2173 tree,
2174 } = parsed.as_ref()
2175 else {
2176 return None;
2177 };
2178 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2179 let arguments = call.child_by_field_name("arguments")?;
2180 let mut total = 0usize;
2181 for argument in argument_children(arguments) {
2182 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2183 return None;
2184 }
2185 if argument.kind() == "identifier"
2186 && let Some(parameter_index) = parameters
2187 .iter()
2188 .position(|parameter| parameter == node_text(argument, sentinel))
2189 {
2190 if !macro_expansion_shape_is_safe(
2191 actuals[parameter_index],
2192 actual_source,
2193 &[],
2194 environment,
2195 ) {
2196 return None;
2197 }
2198 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2199 actuals[parameter_index],
2200 actual_source,
2201 environment,
2202 stack,
2203 ) else {
2204 return None;
2205 };
2206 total += spread;
2207 continue;
2208 }
2209 let CallArityEvidence::Exact(spread) =
2210 self.argument_arity_evidence(argument, sentinel, environment, stack)
2211 else {
2212 return None;
2213 };
2214 total += spread;
2215 }
2216 Some(CallArityEvidence::Exact(total))
2217 })()
2218 .unwrap_or(CallArityEvidence::Unknown);
2219 stack.pop();
2220 evidence
2221 }
2222
2223 fn parsed_macro_replacement(
2224 &self,
2225 binding: &MacroBinding,
2226 replacement: &str,
2227 ) -> Arc<ParsedMacroReplacement> {
2228 let key = (binding.source.clone(), binding.declaration_byte);
2229 let mut cache = self
2230 .macro_replacements
2231 .lock()
2232 .expect("C++ macro replacement cache poisoned");
2233 if let Some(parsed) = cache.get(&key) {
2234 return Arc::clone(parsed);
2235 }
2236 #[cfg(any(test, feature = "test-support"))]
2237 self.macro_replacement_parse_count
2238 .fetch_add(1, Ordering::Relaxed);
2239 let source =
2240 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2241 let mut parser = Parser::new();
2242 let parsed = parser
2243 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2244 .ok()
2245 .and_then(|()| parser.parse(&source, None))
2246 .filter(|tree| !tree.root_node().has_error())
2247 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2248 ParsedMacroReplacement::Parsed { source, tree }
2249 });
2250 let parsed = Arc::new(parsed);
2251 cache.insert(key, Arc::clone(&parsed));
2252 parsed
2253 }
2254
2255 pub fn function_macro_local_binding<'tree>(
2265 &self,
2266 file: &ProjectFile,
2267 statement: Node<'tree>,
2268 source: &str,
2269 ) -> Option<MacroLocalBinding<'tree>> {
2270 if !is_c_source_file(file) {
2271 return None;
2272 }
2273 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2274 return Some(binding);
2275 }
2276 let call = match statement.kind() {
2277 "call_expression" => statement,
2278 "expression_statement" if statement.named_child_count() == 1 => {
2279 statement.named_child(0)?
2280 }
2281 _ => return None,
2282 };
2283 if call.kind() != "call_expression" {
2284 return None;
2285 }
2286 let function = call.child_by_field_name("function")?;
2287 if function.kind() != "identifier" {
2288 return None;
2289 }
2290 let arguments = call.child_by_field_name("arguments")?;
2291 let actuals = argument_children(arguments).collect::<Vec<_>>();
2292 let environment = self.macro_environment(file, call.start_byte());
2293 let function_name = node_text(function, source);
2294 let binding = environment.binding(function_name)?;
2295 let MacroDefinition::Function {
2296 parameters,
2297 replacement,
2298 } = &binding.definition
2299 else {
2300 return None;
2301 };
2302 if actuals.len() != parameters.len() {
2303 return None;
2304 }
2305 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2306 let (type_name, type_node) = match &template.declared_type {
2307 MacroLocalBindingTypeTemplate::Parameter(index) => {
2308 let actual = *actuals.get(*index)?;
2309 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2310 return None;
2311 }
2312 (node_text(actual, source).trim().to_string(), Some(actual))
2313 }
2314 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2315 };
2316 if type_name.is_empty() {
2317 return None;
2318 }
2319 Some(MacroLocalBinding {
2320 name: template.name.clone(),
2321 type_name,
2322 type_node,
2323 pointer_depth: template.pointer_depth,
2324 proven_unit: None,
2325 })
2326 }
2327
2328 pub fn function_macro_container_binding<'tree>(
2337 &self,
2338 analyzer: &CppGraphSource<'_>,
2339 file: &ProjectFile,
2340 assignment: Node<'tree>,
2341 source: &str,
2342 ) -> Option<MacroLocalBinding<'tree>> {
2343 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2344 return None;
2345 }
2346 let name_node = assignment.child_by_field_name("left")?;
2347 if name_node.kind() != "identifier" {
2348 return None;
2349 }
2350 let call = assignment.child_by_field_name("right")?;
2351 if call.kind() != "call_expression" {
2352 return None;
2353 }
2354 let function = call.child_by_field_name("function")?;
2355 if function.kind() != "identifier" {
2356 return None;
2357 }
2358 let arguments = call.child_by_field_name("arguments")?;
2359 let actuals = argument_children(arguments).collect::<Vec<_>>();
2360 let function_name = node_text(function, source);
2361 let environment = self.macro_environment(file, call.start_byte());
2362 let binding = environment.binding(function_name)?;
2363 if !binding.is_exact() {
2364 return None;
2365 }
2366 let MacroDefinition::Function {
2367 parameters,
2368 replacement,
2369 } = &binding.definition
2370 else {
2371 return None;
2372 };
2373 if actuals.len() != parameters.len() {
2374 return None;
2375 }
2376 let body = self.parsed_macro_replacement_body(
2377 &(binding.source.clone(), binding.declaration_byte),
2378 parameters,
2379 replacement,
2380 )?;
2381 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2382 let type_argument = *actuals.get(type_parameter)?;
2383 let type_node = macro_type_argument_node(type_argument, source)?;
2384 let type_name = node_text(type_node, source).trim().to_string();
2385 if type_name.is_empty() {
2386 return None;
2387 }
2388 let explicit_tag = match node_text(type_argument, source) {
2389 "struct" => Some(CppCTagKind::Struct),
2390 "union" => Some(CppCTagKind::Union),
2391 _ => None,
2392 };
2393 let resolved_type = if let Some(tag) = explicit_tag {
2394 let candidates = self
2395 .visible_identifier_candidates(file, &type_name)
2396 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2397 .collect::<Vec<_>>();
2398 self.resolve_type_candidates(
2399 analyzer,
2400 file,
2401 &candidates,
2402 TypeCandidateResolution::Canonical,
2403 )
2404 .ok()
2405 } else {
2406 self.resolve_type_node_result(file, type_node, source)
2407 .ok()
2408 .flatten()
2409 };
2410 let proven_unit = resolved_type?;
2411 Some(MacroLocalBinding {
2412 name: node_text(name_node, source).to_string(),
2413 type_name,
2414 type_node: Some(type_node),
2415 pointer_depth: 1,
2416 proven_unit: Some(proven_unit),
2417 })
2418 }
2419
2420 fn macro_local_binding_template(
2421 &self,
2422 binding: &MacroBinding,
2423 parameters: &[String],
2424 replacement: &str,
2425 ) -> Option<Arc<MacroLocalBindingTemplate>> {
2426 let key = (binding.source.clone(), binding.declaration_byte);
2427 if let Some(template) = self
2428 .macro_local_binding_templates
2429 .lock()
2430 .expect("C++ macro local-binding cache poisoned")
2431 .get(&key)
2432 {
2433 return template.clone();
2434 }
2435 let template = (|| {
2436 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2437 let sentinel = body.source.as_str();
2438 let statements = body.statements()?;
2439 if statements.named_child_count() != 1 {
2440 return None;
2441 }
2442 let declaration = statements.named_child(0)?;
2443 if declaration.kind() != "declaration" {
2444 return None;
2445 }
2446 let type_node = declaration
2447 .child_by_field_name("type")
2448 .or_else(|| first_type_child(declaration))?;
2449 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2450 let mut cursor = declaration.walk();
2451 declaration.named_children(&mut cursor).find_map(|child| {
2452 if child.kind() == "init_declarator" {
2453 child.child_by_field_name("declarator")
2454 } else {
2455 is_declarator_node(child).then_some(child)
2456 }
2457 })
2458 })?;
2459 let name = extract_variable_name(declarator, sentinel)?;
2460 let pointer_depth = declared_name_indirection(declaration, type_node, &name, sentinel)?;
2461 let type_text = node_text(type_node, sentinel).trim();
2462 let declared_type = parameters
2463 .iter()
2464 .position(|parameter| parameter == type_text)
2465 .map(MacroLocalBindingTypeTemplate::Parameter)
2466 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2467 Some(Arc::new(MacroLocalBindingTemplate {
2468 name,
2469 declared_type,
2470 pointer_depth,
2471 }))
2472 })();
2473 self.macro_local_binding_templates
2474 .lock()
2475 .expect("C++ macro local-binding cache poisoned")
2476 .insert(key, template.clone());
2477 template
2478 }
2479
2480 pub fn function_macro_replacement_body(
2487 &self,
2488 file: &ProjectFile,
2489 definition: Node<'_>,
2490 source: &str,
2491 ) -> Option<Arc<ParsedReplacementBody>> {
2492 debug_assert_eq!(definition.kind(), "preproc_function_def");
2493 let MacroDefinition::Function {
2494 parameters,
2495 replacement,
2496 } = Self::decode_macro_definition(definition, source)
2497 else {
2498 return None;
2499 };
2500 self.parsed_macro_replacement_body(
2501 &(file.clone(), definition.start_byte()),
2502 ¶meters,
2503 &replacement,
2504 )
2505 }
2506
2507 fn parsed_macro_replacement_body(
2515 &self,
2516 key: &(ProjectFile, usize),
2517 parameters: &[String],
2518 replacement: &str,
2519 ) -> Option<Arc<ParsedReplacementBody>> {
2520 if let Some(body) = self
2521 .macro_replacement_bodies
2522 .lock()
2523 .expect("C++ macro replacement body cache poisoned")
2524 .get(key)
2525 {
2526 return body.clone();
2527 }
2528 let body = (|| {
2529 if replacement.trim().is_empty() {
2530 return None;
2531 }
2532 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
2533 let mut parser = Parser::new();
2534 parser
2535 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2536 .ok()?;
2537 let tree = parser.parse(&source, None)?;
2538 if tree.root_node().has_error() {
2539 return None;
2540 }
2541 let body = ParsedReplacementBody {
2542 source,
2543 tree,
2544 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2545 parameters: parameters.to_vec(),
2546 };
2547 body.statements()?;
2548 if body.expands_variadic_arguments() {
2549 return None;
2550 }
2551 Some(Arc::new(body))
2552 })();
2553 self.macro_replacement_bodies
2554 .lock()
2555 .expect("C++ macro replacement body cache poisoned")
2556 .insert(key.clone(), body.clone());
2557 body
2558 }
2559
2560 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
2561 let replacement = node
2562 .child_by_field_name("value")
2563 .map(|value| node_text(value, source).to_string())
2564 .unwrap_or_default();
2565 if node.kind() == "preproc_def" {
2566 return MacroDefinition::Object { replacement };
2567 }
2568 let Some(parameters) = node.child_by_field_name("parameters") else {
2569 return MacroDefinition::Unsupported;
2570 };
2571 if (0..parameters.child_count()).any(|index| {
2572 parameters
2573 .child(index)
2574 .is_some_and(|child| child.kind() == "...")
2575 }) {
2576 return MacroDefinition::Unsupported;
2577 }
2578 let parameters = (0..parameters.named_child_count())
2579 .filter_map(|index| parameters.named_child(index))
2580 .map(|parameter| node_text(parameter, source).to_string())
2581 .collect();
2582 MacroDefinition::Function {
2583 parameters,
2584 replacement,
2585 }
2586 }
2587
2588 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
2589 self.macro_event_cells
2590 .lock()
2591 .expect("C++ macro event cache poisoned")
2592 .entry(file.clone())
2593 .or_default()
2594 .clone()
2595 }
2596
2597 fn macro_environment_checkpoint_cell(
2598 &self,
2599 file: &ProjectFile,
2600 ) -> MacroEnvironmentCheckpointCell {
2601 self.macro_environment_checkpoints
2602 .lock()
2603 .expect("C++ macro environment checkpoint cache poisoned")
2604 .entry(file.clone())
2605 .or_default()
2606 .clone()
2607 }
2608
2609 pub fn macro_environment(
2611 &self,
2612 file: &ProjectFile,
2613 before_byte: usize,
2614 ) -> Arc<MacroEnvironment> {
2615 #[cfg(any(test, feature = "test-support"))]
2616 self.macro_environment_request_count
2617 .fetch_add(1, Ordering::Relaxed);
2618 let cell = self.macro_event_cell(file);
2619 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2620 let frontier = events.partition_point(|event| event.byte() < before_byte);
2621 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
2622 let checkpoints =
2623 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
2624 let checkpoint = checkpoints.at_or_before(frontier);
2625 if checkpoint.frontier == frontier {
2626 return Arc::clone(&checkpoint.environment);
2627 }
2628 #[cfg(any(test, feature = "test-support"))]
2629 self.macro_environment_copy_count
2630 .fetch_add(1, Ordering::Relaxed);
2631 let mut environment = checkpoint.environment.as_ref().clone();
2632 let mut include_stack = HashSet::from_iter([file.clone()]);
2633 for event in &events[checkpoint.frontier..frontier] {
2634 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
2635 }
2636 Arc::new(environment)
2637 }
2638
2639 fn build_macro_environment_checkpoints(
2642 &self,
2643 file: &ProjectFile,
2644 events: &[MacroEvent],
2645 ) -> MacroEnvironmentCheckpoints {
2646 #[cfg(any(test, feature = "test-support"))]
2647 self.macro_environment_checkpoint_build_count
2648 .fetch_add(1, Ordering::Relaxed);
2649 let mut environment = MacroEnvironment {
2654 build_proven_defines: self
2655 .compile_proven_guards(file)
2656 .iter()
2657 .filter_map(|guard| match guard {
2658 PreprocessorGuard::Defined(name) => Some(name.clone()),
2659 _ => None,
2660 })
2661 .collect(),
2662 ..MacroEnvironment::default()
2663 };
2664 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
2665 frontier: 0,
2666 environment: Arc::new(environment.clone()),
2667 }];
2668 let checkpoint_stride = events
2675 .len()
2676 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
2677 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
2678 let mut include_stack = HashSet::from_iter([file.clone()]);
2679 for (index, event) in events.iter().enumerate() {
2680 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
2681 let frontier = index + 1;
2682 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
2683 checkpoints.push(MacroEnvironmentCheckpoint {
2684 frontier,
2685 environment: Arc::new(environment.clone()),
2686 });
2687 }
2688 }
2689 MacroEnvironmentCheckpoints { checkpoints }
2690 }
2691
2692 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2701 self.macro_environment(file, before_byte)
2702 .binding(name)
2703 .is_some()
2704 }
2705
2706 pub fn macro_name_may_be_bound_at(
2707 &self,
2708 file: &ProjectFile,
2709 name: &str,
2710 before_byte: usize,
2711 ) -> bool {
2712 self.macro_environment(file, before_byte).may_bind(name)
2713 }
2714
2715 pub fn macro_binding_matches_target_at(
2719 &self,
2720 analyzer: &CppGraphSource<'_>,
2721 file: &ProjectFile,
2722 name: &str,
2723 before_byte: usize,
2724 target: &CodeUnit,
2725 ) -> bool {
2726 let ranges = analyzer.ranges(target);
2727 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
2728 self.macro_binding_matches_target_declaration_at(
2729 file,
2730 name,
2731 before_byte,
2732 target.source(),
2733 &declaration_bytes,
2734 )
2735 }
2736
2737 pub(crate) fn macro_declaration_bytes(
2742 &self,
2743 target: &CodeUnit,
2744 ranges: &[Range],
2745 ) -> Vec<usize> {
2746 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2747 return Vec::new();
2748 };
2749 ranges
2750 .iter()
2751 .filter_map(|range| {
2752 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
2753 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2754 node = node.parent()?;
2755 }
2756 Some(node.start_byte())
2757 })
2758 .collect()
2759 }
2760
2761 pub(crate) fn macro_binding_matches_target_declaration_at(
2762 &self,
2763 file: &ProjectFile,
2764 name: &str,
2765 before_byte: usize,
2766 target_source: &ProjectFile,
2767 target_declaration_bytes: &[usize],
2768 ) -> bool {
2769 let environment = self.macro_environment(file, before_byte);
2770 let Some(binding) = environment.binding(name) else {
2771 return false;
2772 };
2773 if binding.definition == MacroDefinition::Unsupported {
2774 return false;
2775 }
2776 if binding.source != *target_source {
2780 return false;
2781 }
2782 target_declaration_bytes.contains(&binding.declaration_byte)
2783 }
2784
2785 pub fn resolve_ordinary_macro_reference(
2792 &self,
2793 analyzer: &CppGraphSource<'_>,
2794 file: &ProjectFile,
2795 node: Node<'_>,
2796 source: &str,
2797 ) -> OrdinaryMacroReferenceResolution {
2798 if !is_ordinary_macro_reference_node(node) {
2799 return OrdinaryMacroReferenceResolution::Missing;
2800 }
2801 let name = node_text(node, source);
2802 if name.is_empty() {
2803 return OrdinaryMacroReferenceResolution::Missing;
2804 }
2805 let visible = self
2806 .visible_identifier_candidates(file, name)
2807 .filter(|candidate| candidate.is_macro())
2808 .cloned()
2809 .collect::<Vec<_>>();
2810 let mut exact = Vec::new();
2811 for candidate in &visible {
2812 if self.macro_binding_matches_target_at(
2813 analyzer,
2814 file,
2815 name,
2816 node.start_byte(),
2817 candidate,
2818 ) && !exact
2819 .iter()
2820 .any(|existing| same_visible_symbol(existing, candidate))
2821 {
2822 exact.push(candidate.clone());
2823 }
2824 }
2825 match exact.len() {
2826 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2827 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2828 0 if !visible.is_empty()
2829 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2830 {
2831 OrdinaryMacroReferenceResolution::Ambiguous
2832 }
2833 0 => OrdinaryMacroReferenceResolution::Missing,
2834 }
2835 }
2836
2837 pub fn recovered_c_reference_ranges(
2845 &self,
2846 file: &ProjectFile,
2847 root: Node<'_>,
2848 source: &str,
2849 limit: usize,
2850 ) -> RecoveredCReferenceRanges {
2851 if !is_c_source_file(file) {
2852 return RecoveredCReferenceRanges::Complete(Vec::new());
2853 }
2854 let mut ranges = Vec::new();
2855 let mut seen = HashSet::default();
2856 let mut stack = vec![(root, root.is_error())];
2857 while let Some((node, inside_error)) = stack.pop() {
2858 let inside_error = inside_error || node.is_error();
2859 if node.kind() == "preproc_arg" {
2860 let macro_value_kind = node.parent().and_then(|parent| {
2865 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
2866 });
2867 if matches!(
2868 macro_value_kind,
2869 Some("preproc_def" | "preproc_function_def")
2870 ) {
2871 let name = node_text(node, source);
2872 if !name.is_empty()
2873 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
2874 && !push_recovered_c_range(
2875 &mut ranges,
2876 &mut seen,
2877 node.start_byte(),
2878 node.end_byte(),
2879 node,
2880 limit,
2881 )
2882 {
2883 return RecoveredCReferenceRanges::LimitExceeded;
2884 }
2885 }
2886 if macro_value_kind == Some("preproc_def") {
2887 for reference in object_macro_replacement_type_references(node, source) {
2888 for range in reference.component_ranges {
2889 let visible = self
2890 .visible_identifier_candidates(file, &source[range.clone()])
2891 .any(|candidate| {
2892 candidate.is_class()
2893 || candidate.is_module()
2894 || is_type_alias(candidate)
2895 });
2896 if visible
2897 && !push_recovered_c_range(
2898 &mut ranges,
2899 &mut seen,
2900 range.start,
2901 range.end,
2902 node,
2903 limit,
2904 )
2905 {
2906 return RecoveredCReferenceRanges::LimitExceeded;
2907 }
2908 }
2909 }
2910 }
2911 }
2912 if inside_error
2913 && recovered_c_reference_node(self, file, node, source)
2914 && !push_recovered_c_range(
2915 &mut ranges,
2916 &mut seen,
2917 node.start_byte(),
2918 node.end_byte(),
2919 node,
2920 limit,
2921 )
2922 {
2923 return RecoveredCReferenceRanges::LimitExceeded;
2924 }
2925 let mut cursor = node.walk();
2926 for child in node.named_children(&mut cursor) {
2927 stack.push((child, inside_error));
2928 }
2929 }
2930 ranges.sort_unstable();
2931 RecoveredCReferenceRanges::Complete(ranges)
2932 }
2933
2934 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2940 self.visible_identifier_candidates(file, target.identifier())
2941 .filter(|candidate| candidate.is_macro())
2942 .any(|candidate| {
2943 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2944 })
2945 }
2946
2947 pub fn object_macro_replacement_at(
2948 &self,
2949 file: &ProjectFile,
2950 name: &str,
2951 before_byte: usize,
2952 ) -> Option<String> {
2953 let environment = self.macro_environment(file, before_byte);
2954 let binding = environment.binding(name)?;
2955 if !binding.exact {
2956 return None;
2957 }
2958 match &binding.definition {
2959 MacroDefinition::Object { replacement } => Some(replacement.clone()),
2960 MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2961 }
2962 }
2963
2964 fn apply_macro_events(
2965 &self,
2966 file: &ProjectFile,
2967 before_byte: Option<usize>,
2968 environment: &mut MacroEnvironment,
2969 include_stack: &mut HashSet<ProjectFile>,
2970 ) {
2971 if !include_stack.insert(file.clone()) {
2972 return;
2973 }
2974 if self.cpp.prepared_syntax(self.token, file).is_none() {
2975 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2976 include_stack.remove(file);
2977 return;
2978 }
2979 match self.macro_include_protection(file) {
2980 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2981 Some(binding) if binding.is_exact() => {
2982 include_stack.remove(file);
2983 return;
2984 }
2985 Some(_) | None if environment.unknown_names => {
2986 let mut ambiguous_seen = HashSet::default();
2987 self.mark_macro_events_ambiguous(
2988 file,
2989 environment,
2990 &mut ambiguous_seen,
2991 file,
2992 before_byte.unwrap_or_default(),
2993 );
2994 include_stack.remove(file);
2995 return;
2996 }
2997 Some(_) => {
2998 let mut ambiguous_seen = HashSet::default();
2999 self.mark_macro_events_ambiguous(
3000 file,
3001 environment,
3002 &mut ambiguous_seen,
3003 file,
3004 before_byte.unwrap_or_default(),
3005 );
3006 include_stack.remove(file);
3007 return;
3008 }
3009 None => {}
3010 },
3011 MacroIncludeProtection::PragmaOnce => {
3012 if !environment.applied_pragma_once_files.insert(file.clone()) {
3013 include_stack.remove(file);
3014 return;
3015 }
3016 if environment.maybe_applied_pragma_once_files.remove(file) {
3017 let mut ambiguous_seen = HashSet::default();
3022 environment.applied_pragma_once_files.remove(file);
3023 self.mark_macro_events_ambiguous(
3024 file,
3025 environment,
3026 &mut ambiguous_seen,
3027 file,
3028 before_byte.unwrap_or_default(),
3029 );
3030 environment.maybe_applied_pragma_once_files.remove(file);
3031 environment.applied_pragma_once_files.insert(file.clone());
3032 include_stack.remove(file);
3033 return;
3034 }
3035 }
3036 MacroIncludeProtection::None => {}
3037 }
3038 let cell = self.macro_event_cell(file);
3039 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3040 for event in events {
3041 if before_byte.is_some_and(|limit| event.byte() >= limit) {
3042 break;
3043 }
3044 self.apply_macro_event(file, event, environment, include_stack);
3045 }
3046 include_stack.remove(file);
3047 }
3048
3049 fn apply_macro_event(
3050 &self,
3051 file: &ProjectFile,
3052 event: &MacroEvent,
3053 environment: &mut MacroEnvironment,
3054 include_stack: &mut HashSet<ProjectFile>,
3055 ) {
3056 #[cfg(any(test, feature = "test-support"))]
3057 self.macro_event_application_count
3058 .fetch_add(1, Ordering::Relaxed);
3059 match event {
3060 MacroEvent::Define {
3061 name,
3062 binding,
3063 conditionals,
3064 byte,
3065 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3066 Some(true) => environment.insert(name.clone(), binding.clone()),
3067 Some(false) => {}
3068 None => Self::merge_conditional_macro_definition(
3069 environment,
3070 name,
3071 binding,
3072 file,
3073 *byte,
3074 ),
3075 },
3076 MacroEvent::Undef {
3077 name,
3078 conditionals,
3079 byte,
3080 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3081 Some(true) => environment.remove(name),
3082 Some(false) => {}
3083 None => {
3084 if environment.binding(name).is_some() {
3085 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3086 }
3087 }
3088 },
3089 MacroEvent::Include {
3090 targets,
3091 conditionals,
3092 byte,
3093 } => {
3094 let condition =
3095 self.macro_event_condition_value(file, *byte, environment, conditionals);
3096 if condition == Some(false) {
3097 return;
3098 }
3099 if targets.is_empty() {
3100 environment.mark_unknown_names(file, *byte);
3101 return;
3102 }
3103 if condition.is_none() || targets.len() > 1 {
3104 let mut ambiguous_seen = HashSet::default();
3105 for target in targets {
3106 self.mark_macro_events_ambiguous(
3107 target,
3108 environment,
3109 &mut ambiguous_seen,
3110 file,
3111 *byte,
3112 );
3113 }
3114 } else if let Some(target) = targets.first() {
3115 self.apply_macro_events(target, None, environment, include_stack);
3116 }
3117 }
3118 MacroEvent::Invalidate { byte } => {
3119 for binding in environment.bindings.values_mut() {
3120 *binding = MacroBinding::uncertain_from(binding, file, *byte);
3121 }
3122 }
3123 }
3124 }
3125
3126 fn macro_event_condition_value(
3137 &self,
3138 file: &ProjectFile,
3139 event_byte: usize,
3140 environment: &MacroEnvironment,
3141 conditionals: &OwningPreprocessorConditionals,
3142 ) -> Option<bool> {
3143 if conditionals.is_empty() {
3144 return Some(true);
3145 }
3146 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3147 let source = prepared.source();
3148 let root = prepared.tree().root_node();
3149 let descendant = root.descendant_for_byte_range(
3150 event_byte,
3151 event_byte.saturating_add(1).min(source.len()),
3152 )?;
3153 let mut unknown = false;
3154 let mut current = descendant.parent();
3155 while let Some(conditional) = current {
3156 if matches!(
3157 conditional.kind(),
3158 "preproc_if" | "preproc_ifdef" | "preproc_elif"
3159 ) && conditionals.contains(&conditional.start_byte())
3160 {
3161 let mut value = match conditional.kind() {
3162 "preproc_ifdef" => {
3163 let name = conditional.child_by_field_name("name")?;
3164 let defined =
3165 self.macro_name_defined_value(environment, node_text(name, source));
3166 match conditional.child(0)?.kind() {
3167 "#ifdef" => defined,
3168 "#ifndef" => defined.map(|defined| !defined),
3169 _ => None,
3170 }
3171 }
3172 "preproc_if" | "preproc_elif" => conditional
3173 .child_by_field_name("condition")
3174 .and_then(|condition| {
3175 self.preprocessor_integer_value(
3176 condition,
3177 source,
3178 environment,
3179 &mut Vec::new(),
3180 0,
3181 )
3182 })
3183 .map(|value| value != 0),
3184 _ => unreachable!(),
3185 };
3186 if conditional
3187 .child_by_field_name("alternative")
3188 .is_some_and(|alternative| {
3189 alternative.start_byte() <= descendant.start_byte()
3190 && descendant.end_byte() <= alternative.end_byte()
3191 })
3192 {
3193 value = value.map(|value| !value);
3194 }
3195 match value {
3196 Some(true) => {}
3197 Some(false) => return Some(false),
3198 None => unknown = true,
3199 }
3200 }
3201 current = conditional.parent();
3202 }
3203 (!unknown).then_some(true)
3204 }
3205
3206 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3207 if environment.known_undefined_names.contains(name) {
3208 return Some(false);
3209 }
3210 if let Some(binding) = environment.binding(name) {
3211 return binding.is_exact().then_some(true);
3212 }
3213 environment
3214 .build_proven_defines
3215 .contains(name)
3216 .then_some(true)
3217 }
3218
3219 fn preprocessor_integer_value(
3220 &self,
3221 expression: Node<'_>,
3222 source: &str,
3223 environment: &MacroEnvironment,
3224 expansion_stack: &mut Vec<(ProjectFile, usize)>,
3225 depth: usize,
3226 ) -> Option<i128> {
3227 if depth >= 64 {
3230 return None;
3231 }
3232 match expression.kind() {
3233 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3234 "identifier" | "type_identifier" => {
3235 let binding = environment.binding(node_text(expression, source))?;
3236 if !binding.is_exact() {
3237 return None;
3238 }
3239 let MacroDefinition::Object { replacement } = &binding.definition else {
3240 return None;
3241 };
3242 let identity = (binding.source.clone(), binding.declaration_byte);
3243 if expansion_stack.contains(&identity) {
3244 return None;
3245 }
3246 expansion_stack.push(identity);
3247 let parsed = self.parsed_macro_replacement(binding, replacement);
3248 let value = match parsed.as_ref() {
3249 ParsedMacroReplacement::Parsed {
3250 source: replacement_source,
3251 tree,
3252 } => first_descendant_of_kind(tree.root_node(), "call_expression")
3253 .and_then(|call| call.child_by_field_name("arguments"))
3254 .and_then(|arguments| argument_children(arguments).next())
3255 .and_then(|argument| {
3256 self.preprocessor_integer_value(
3257 argument,
3258 replacement_source,
3259 environment,
3260 expansion_stack,
3261 depth + 1,
3262 )
3263 }),
3264 ParsedMacroReplacement::Unsupported => None,
3265 };
3266 expansion_stack.pop();
3267 value
3268 }
3269 "preproc_defined" => {
3270 let mut cursor = expression.walk();
3271 let name = expression
3272 .named_children(&mut cursor)
3273 .find(|child| child.kind() == "identifier")?;
3274 self.macro_name_defined_value(environment, node_text(name, source))
3275 .map(i128::from)
3276 }
3277 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3278 self.preprocessor_integer_value(
3279 child,
3280 source,
3281 environment,
3282 expansion_stack,
3283 depth + 1,
3284 )
3285 }),
3286 "unary_expression" => {
3287 let operator = expression.child_by_field_name("operator")?.kind();
3288 let argument = expression.child_by_field_name("argument")?;
3289 let value = self.preprocessor_integer_value(
3290 argument,
3291 source,
3292 environment,
3293 expansion_stack,
3294 depth + 1,
3295 )?;
3296 match operator {
3297 "+" => Some(value),
3298 "-" => value.checked_neg(),
3299 "!" => Some(i128::from(value == 0)),
3300 "~" => Some(!value),
3301 _ => None,
3302 }
3303 }
3304 "binary_expression" => {
3305 let left = self.preprocessor_integer_value(
3306 expression.child_by_field_name("left")?,
3307 source,
3308 environment,
3309 expansion_stack,
3310 depth + 1,
3311 )?;
3312 let right = self.preprocessor_integer_value(
3313 expression.child_by_field_name("right")?,
3314 source,
3315 environment,
3316 expansion_stack,
3317 depth + 1,
3318 )?;
3319 match expression.child_by_field_name("operator")?.kind() {
3320 "+" => left.checked_add(right),
3321 "-" => left.checked_sub(right),
3322 "*" => left.checked_mul(right),
3323 "/" => left.checked_div(right),
3324 "%" => left.checked_rem(right),
3325 "<<" => u32::try_from(right)
3326 .ok()
3327 .and_then(|shift| left.checked_shl(shift)),
3328 ">>" => u32::try_from(right)
3329 .ok()
3330 .and_then(|shift| left.checked_shr(shift)),
3331 "<" => Some(i128::from(left < right)),
3332 "<=" => Some(i128::from(left <= right)),
3333 ">" => Some(i128::from(left > right)),
3334 ">=" => Some(i128::from(left >= right)),
3335 "==" => Some(i128::from(left == right)),
3336 "!=" => Some(i128::from(left != right)),
3337 "&" => Some(left & right),
3338 "|" => Some(left | right),
3339 "^" => Some(left ^ right),
3340 "&&" => Some(i128::from(left != 0 && right != 0)),
3341 "||" => Some(i128::from(left != 0 || right != 0)),
3342 _ => None,
3343 }
3344 }
3345 _ => None,
3346 }
3347 }
3348
3349 fn mark_macro_events_ambiguous(
3350 &self,
3351 file: &ProjectFile,
3352 environment: &mut MacroEnvironment,
3353 include_stack: &mut HashSet<ProjectFile>,
3354 conditional_file: &ProjectFile,
3355 conditional_byte: usize,
3356 ) {
3357 if !include_stack.insert(file.clone()) {
3358 return;
3359 }
3360 if self.cpp.prepared_syntax(self.token, file).is_none() {
3361 environment.mark_unknown_names(conditional_file, conditional_byte);
3362 return;
3363 }
3364 match self.macro_include_protection(file) {
3365 MacroIncludeProtection::MacroGuard(guard) => {
3366 if environment
3367 .binding(&guard)
3368 .is_some_and(MacroBinding::is_exact)
3369 {
3370 return;
3371 }
3372 }
3373 MacroIncludeProtection::PragmaOnce => {
3374 if environment.applied_pragma_once_files.contains(file) {
3375 return;
3376 }
3377 environment
3378 .maybe_applied_pragma_once_files
3379 .insert(file.clone());
3380 }
3381 MacroIncludeProtection::None => {}
3382 }
3383 let cell = self.macro_event_cell(file);
3384 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3385 for event in events {
3386 #[cfg(any(test, feature = "test-support"))]
3387 self.macro_event_application_count
3388 .fetch_add(1, Ordering::Relaxed);
3389 match event {
3390 MacroEvent::Define { name, binding, .. } => {
3391 Self::merge_conditional_macro_definition(
3392 environment,
3393 name,
3394 binding,
3395 conditional_file,
3396 conditional_byte,
3397 );
3398 }
3399 MacroEvent::Undef { name, .. } => {
3400 if environment.binding(name).is_some() {
3401 environment.insert(
3402 name.clone(),
3403 MacroBinding::ambiguous(conditional_file, conditional_byte),
3404 );
3405 } else {
3406 environment.remove_known_undefined(name);
3407 }
3408 }
3409 MacroEvent::Include { targets, .. } => {
3410 if targets.is_empty() {
3411 environment.mark_unknown_names(conditional_file, conditional_byte);
3412 continue;
3413 }
3414 for target in targets {
3415 self.mark_macro_events_ambiguous(
3416 target,
3417 environment,
3418 include_stack,
3419 conditional_file,
3420 conditional_byte,
3421 );
3422 }
3423 }
3424 MacroEvent::Invalidate { .. } => {
3425 for binding in environment.bindings.values_mut() {
3426 *binding = MacroBinding::uncertain_from(
3427 binding,
3428 conditional_file,
3429 conditional_byte,
3430 );
3431 }
3432 }
3433 }
3434 }
3435 }
3436
3437 fn merge_conditional_macro_definition(
3438 environment: &mut MacroEnvironment,
3439 name: &str,
3440 possible_binding: &MacroBinding,
3441 conditional_file: &ProjectFile,
3442 conditional_byte: usize,
3443 ) {
3444 if environment.binding(name).is_some_and(|current| {
3449 current.definition != MacroDefinition::Unsupported
3450 && current.definition == possible_binding.definition
3451 }) {
3452 return;
3453 }
3454 environment.insert(
3455 name.to_string(),
3456 MacroBinding::ambiguous(conditional_file, conditional_byte),
3457 );
3458 }
3459
3460 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
3461 let cell = self
3462 .macro_include_protection_cells
3463 .lock()
3464 .expect("C++ include protection cache poisoned")
3465 .entry(file.clone())
3466 .or_default()
3467 .clone();
3468 cell.get_or_init(|| {
3469 self.cpp.prepared_syntax(self.token, file).map_or(
3470 MacroIncludeProtection::None,
3471 |prepared| {
3472 top_level_macro_include_protection(
3473 prepared.tree().root_node(),
3474 prepared.source(),
3475 )
3476 },
3477 )
3478 })
3479 .clone()
3480 }
3481
3482 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
3483 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3484 return Vec::new();
3485 };
3486 let source = prepared.source();
3487 let mut events = Vec::new();
3488 let root = prepared.tree().root_node();
3489 let mut stack = vec![root];
3490 while let Some(node) = stack.pop() {
3491 match node.kind() {
3492 "preproc_def" | "preproc_function_def" => {
3493 let Some(name) = node.child_by_field_name("name") else {
3494 continue;
3495 };
3496 let name = node_text(name, source).to_string();
3497 events.push(MacroEvent::Define {
3498 name,
3499 binding: MacroBinding {
3500 source: file.clone(),
3501 declaration_byte: node.start_byte(),
3502 definition: Self::decode_macro_definition(node, source),
3503 exact: true,
3504 },
3505 byte: node.start_byte(),
3506 conditionals: owning_preprocessor_conditionals(root, node, source),
3507 });
3508 continue;
3509 }
3510 "preproc_include" => {
3511 let Some(path) = node.child_by_field_name("path") else {
3512 events.push(MacroEvent::Include {
3513 targets: Vec::new(),
3514 byte: node.start_byte(),
3515 conditionals: owning_preprocessor_conditionals(root, node, source),
3516 });
3517 continue;
3518 };
3519 let targets =
3520 structured_include_path(path, source).map_or_else(Vec::new, |path| {
3521 resolve_include_targets_with_index(
3522 file,
3523 path,
3524 self.cpp.include_target_index(),
3525 )
3526 });
3527 if targets.is_empty() && path.kind() == "system_lib_string" {
3532 continue;
3533 }
3534 events.push(MacroEvent::Include {
3535 targets,
3536 byte: node.start_byte(),
3537 conditionals: owning_preprocessor_conditionals(root, node, source),
3538 });
3539 continue;
3540 }
3541 "preproc_call" => {
3542 let Some(directive) = node.child_by_field_name("directive") else {
3543 continue;
3544 };
3545 if node_text(directive, source) != "#undef" {
3546 continue;
3547 }
3548 let name = node
3549 .child_by_field_name("argument")
3550 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
3551 if let Some(name) = name {
3552 events.push(MacroEvent::Undef {
3553 name,
3554 byte: node.start_byte(),
3555 conditionals: owning_preprocessor_conditionals(root, node, source),
3556 });
3557 } else {
3558 events.push(MacroEvent::Invalidate {
3559 byte: node.start_byte(),
3560 });
3561 }
3562 continue;
3563 }
3564 _ => {}
3565 }
3566 for index in (0..node.named_child_count()).rev() {
3567 if let Some(child) = node.named_child(index) {
3568 stack.push(child);
3569 }
3570 }
3571 }
3572 events.sort_by_key(MacroEvent::byte);
3573 events
3574 }
3575
3576 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
3577 self.ordinary_type_import_cells
3578 .lock()
3579 .expect("C++ ordinary type import cache poisoned")
3580 .entry(file.clone())
3581 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
3582 .clone()
3583 }
3584
3585 pub fn project_using_index(
3586 &self,
3587 build: impl FnOnce() -> ProjectUsingIndex,
3588 ) -> &ProjectUsingIndex {
3589 self.project_using_index.get_or_init(build)
3590 }
3591
3592 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
3593 let mut files = self
3594 .visible_source_files_by_root
3595 .values()
3596 .flatten()
3597 .cloned()
3598 .collect::<HashSet<_>>()
3599 .into_iter()
3600 .collect::<Vec<_>>();
3601 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
3602 files
3603 }
3604
3605 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
3606 self.visible_source_files_by_root
3607 .get(root)
3608 .is_some_and(|files| files.contains(source))
3609 }
3610
3611 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
3612 let cached = self
3613 .visible_parser_alias_name_sets
3614 .read()
3615 .expect("visible parser alias-name cache poisoned")
3616 .get(file)
3617 .cloned();
3618 let cell = if let Some(cached) = cached {
3619 cached
3620 } else {
3621 let mut cells = self
3622 .visible_parser_alias_name_sets
3623 .write()
3624 .expect("visible parser alias-name cache poisoned");
3625 Arc::clone(
3626 cells
3627 .entry(file.clone())
3628 .or_insert_with(|| Arc::new(OnceLock::new())),
3629 )
3630 };
3631 cell.get_or_init(|| {
3632 #[cfg(any(test, feature = "test-support"))]
3633 self.visible_parser_alias_name_set_build_count
3634 .fetch_add(1, Ordering::Relaxed);
3635 let mut names = HashSet::default();
3636 let visible_files = self
3637 .visible_source_files_by_root
3638 .get(file)
3639 .cloned()
3640 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
3641 for visible_file in visible_files {
3642 let aliases = {
3643 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
3644 Arc::clone(
3645 cells
3646 .entry(visible_file.clone())
3647 .or_insert_with(|| Arc::new(OnceLock::new())),
3648 )
3649 };
3650 for alias in aliases
3651 .get_or_init(|| {
3652 self.parser_alias_source_parses
3653 .fetch_add(1, Ordering::Relaxed);
3654 #[cfg(any(test, feature = "test-support"))]
3655 {
3656 *self
3657 .alias_source_parse_counts
3658 .lock()
3659 .expect("alias source parse count lock")
3660 .entry(visible_file.clone())
3661 .or_default() += 1;
3662 }
3663 aliases_from_prepared_source(self.cpp, self.token, &visible_file)
3664 .into_boxed_slice()
3665 })
3666 .iter()
3667 {
3668 names.insert(alias.name.clone());
3669 }
3670 }
3671 names
3672 })
3673 .contains(name)
3674 }
3675
3676 pub fn parser_alias_name_may_resolve_to_target(
3677 &self,
3678 file: &ProjectFile,
3679 alias_name: &str,
3680 target: &CodeUnit,
3681 ) -> bool {
3682 let started = std::time::Instant::now();
3683 self.parser_alias_fallback_calls
3684 .fetch_add(1, Ordering::Relaxed);
3685 let mut files = 0usize;
3686 let matched = match self.visible_source_files_by_root.get(file) {
3687 None => {
3688 files = 1;
3689 self.file_alias_matches(self.cpp, file, alias_name, target)
3690 }
3691 Some(visible_files) => visible_files.iter().any(|visible_file| {
3692 files += 1;
3693 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
3694 }),
3695 };
3696 self.parser_alias_fallback_files
3697 .fetch_add(files, Ordering::Relaxed);
3698 self.parser_alias_fallback_elapsed_micros.fetch_add(
3699 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
3700 Ordering::Relaxed,
3701 );
3702 matched
3703 }
3704
3705 fn callable_arities_for_target(
3706 &self,
3707 analyzer: &CppGraphSource<'_>,
3708 cpp: &dyn CppSource,
3709 file: &ProjectFile,
3710 prepared: &PreparedSyntaxTree,
3711 spec: &TargetSpec,
3712 ) -> Vec<ActivatedCallableArity> {
3713 let Some(signature) = spec.target.signature() else {
3714 return Vec::new();
3715 };
3716 let Some(candidates) = self
3717 .visible_by_identifier
3718 .get(file)
3719 .and_then(|by_name| by_name.get(&spec.member_name))
3720 else {
3721 return Vec::new();
3722 };
3723 let differing_candidates = candidates
3724 .iter()
3725 .filter(|candidate| {
3726 candidate.is_function()
3727 && candidate.fq_name() == spec.target.fq_name()
3728 && candidate.signature() == Some(signature)
3729 })
3730 .filter_map(|candidate| {
3731 analyzer
3732 .signature_metadata(candidate)
3733 .into_iter()
3734 .find_map(|metadata| metadata.callable_arity())
3735 .filter(|arity| Some(*arity) != spec.callable_arity)
3736 .map(|arity| (candidate, arity))
3737 })
3738 .collect::<Vec<_>>();
3739 if differing_candidates.is_empty() {
3740 return Vec::new();
3741 }
3742 let mut arities = Vec::with_capacity(differing_candidates.len());
3743 let reference = CallableReferenceContext {
3746 file,
3747 position: None,
3748 };
3749 for (candidate, candidate_arity) in differing_candidates {
3750 let declaration_activation = if candidate.source() == file {
3751 callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
3752 } else {
3753 cpp.prepared_syntax(self.token, candidate.source())
3754 .and_then(|syntax| {
3755 callable_declaration_activation_in_file(
3756 analyzer,
3757 syntax.as_ref(),
3758 candidate,
3759 &reference,
3760 )
3761 })
3762 };
3763 let Some(declaration_activation) = declaration_activation else {
3764 continue;
3765 };
3766 let activation_byte = if candidate.source() == file {
3767 Some(declaration_activation)
3768 } else {
3769 self.include_activation_for_source(cpp, file, prepared, candidate.source())
3770 };
3771 if let Some(activation_byte) = activation_byte {
3772 arities.push(ActivatedCallableArity {
3773 activation_byte,
3774 arity: candidate_arity,
3775 });
3776 }
3777 }
3778 arities
3779 }
3780
3781 fn callable_parameter_macro_arity(
3782 &self,
3783 target: &CodeUnit,
3784 signature: Option<&str>,
3785 ) -> Option<CallableArity> {
3786 let parameter_types = cpp_signature_param_types(signature?)?;
3787 let [macro_name] = parameter_types.as_slice() else {
3788 return None;
3789 };
3790 if macro_name.is_empty()
3791 || !macro_name
3792 .chars()
3793 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
3794 {
3795 return None;
3796 }
3797 let cache_key = (target.source().clone(), macro_name.clone());
3798 if let Some(cached) = self
3799 .callable_parameter_macro_arities
3800 .lock()
3801 .expect("C++ callable parameter-macro arity cache poisoned")
3802 .get(&cache_key)
3803 .copied()
3804 {
3805 return cached;
3806 }
3807 let mut visible_files = HashSet::default();
3808 collect_include_closure(
3809 &self.cpp_source(),
3810 self.cpp.include_target_index(),
3811 target.source(),
3812 &mut visible_files,
3813 None,
3814 );
3815 let mut arities = Vec::new();
3816 for visible_file in visible_files {
3817 let cell = self.macro_event_cell(&visible_file);
3818 for event in
3819 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
3820 {
3821 let MacroEvent::Define { name, binding, .. } = event else {
3822 continue;
3823 };
3824 if name != macro_name {
3825 continue;
3826 }
3827 let MacroDefinition::Object { replacement } = &binding.definition else {
3828 continue;
3829 };
3830 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
3831 continue;
3832 };
3833 if !arities.contains(&arity) {
3834 arities.push(arity);
3835 }
3836 }
3837 }
3838 let resolved = (|| {
3839 let required = arities
3840 .iter()
3841 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
3842 .min()?;
3843 let total = arities.iter().map(|arity| arity.total()).max()?;
3844 let repeated = arities
3845 .iter()
3846 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
3847 Some(CallableArity::new(required, total, repeated))
3852 })();
3853 self.callable_parameter_macro_arities
3854 .lock()
3855 .expect("C++ callable parameter-macro arity cache poisoned")
3856 .insert(cache_key, resolved);
3857 resolved
3858 }
3859
3860 pub fn include_activation_for_source(
3861 &self,
3862 cpp: &dyn CppSource,
3863 file: &ProjectFile,
3864 prepared: &PreparedSyntaxTree,
3865 donor_source: &ProjectFile,
3866 ) -> Option<usize> {
3867 let key = (file.clone(), donor_source.clone());
3868 if let Some(cached) = self
3869 .include_activation_cells
3870 .lock()
3871 .expect("C++ include activation cache poisoned")
3872 .get(&key)
3873 .copied()
3874 {
3875 return cached;
3876 }
3877 #[cfg(any(test, feature = "test-support"))]
3878 self.include_activation_build_count
3879 .fetch_add(1, Ordering::Relaxed);
3880 let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
3881 let mut cells = self
3882 .include_activation_cells
3883 .lock()
3884 .expect("C++ include activation cache poisoned");
3885 *cells.entry(key).or_insert(activation)
3886 }
3887
3888 pub fn conditional_include_projections_for_source(
3889 &self,
3890 file: &ProjectFile,
3891 prepared: &PreparedSyntaxTree,
3892 donor_source: &ProjectFile,
3893 ) -> Arc<[ConditionalIncludeProjection]> {
3894 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
3895 let cell = self
3896 .conditional_include_projection_cells
3897 .lock()
3898 .expect("C++ conditional include projection cache poisoned")
3899 .entry(file.clone())
3900 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
3901 .clone();
3902 let index = cell.get_or_build_pool_independent(|| {
3903 #[cfg(any(test, feature = "test-support"))]
3904 self.conditional_include_projection_index_build_count
3905 .fetch_add(1, Ordering::Relaxed);
3906 find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
3907 #[cfg(any(test, feature = "test-support"))]
3908 self.conditional_include_projection_state_count
3909 .fetch_add(1, Ordering::Relaxed);
3910 })
3911 });
3912 index
3913 .get(donor_source)
3914 .cloned()
3915 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
3916 }
3917
3918 #[cfg(any(test, feature = "test-support"))]
3919 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
3920 (
3921 self.conditional_include_projection_index_build_count
3922 .load(Ordering::Relaxed),
3923 self.conditional_include_projection_state_count
3924 .load(Ordering::Relaxed),
3925 )
3926 }
3927
3928 #[cfg(any(test, feature = "test-support"))]
3929 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
3930 self.conditional_include_target_state_count
3931 .load(Ordering::Relaxed)
3932 }
3933
3934 #[cfg(any(test, feature = "test-support"))]
3935 pub fn include_activation_build_count_for_test(&self) -> usize {
3936 self.include_activation_build_count.load(Ordering::Relaxed)
3937 }
3938
3939 #[cfg(any(test, feature = "test-support"))]
3940 pub fn note_using_donor_activation_for_test(&self) {
3941 self.using_donor_activation_count
3942 .fetch_add(1, Ordering::Relaxed);
3943 }
3944
3945 #[cfg(not(any(test, feature = "test-support")))]
3946 pub fn note_using_donor_activation_for_test(&self) {}
3947
3948 #[cfg(any(test, feature = "test-support"))]
3949 pub fn note_using_namespace_lookup_for_test(&self) {
3950 self.using_namespace_lookup_count
3951 .fetch_add(1, Ordering::Relaxed);
3952 }
3953
3954 #[cfg(not(any(test, feature = "test-support")))]
3955 pub fn note_using_namespace_lookup_for_test(&self) {}
3956
3957 #[cfg(any(test, feature = "test-support"))]
3958 pub fn note_using_name_candidate_inspection_for_test(&self) {
3959 self.using_name_candidate_inspection_count
3960 .fetch_add(1, Ordering::Relaxed);
3961 }
3962
3963 #[cfg(not(any(test, feature = "test-support")))]
3964 pub fn note_using_name_candidate_inspection_for_test(&self) {}
3965
3966 #[cfg(any(test, feature = "test-support"))]
3967 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
3968 (
3969 self.using_donor_activation_count.load(Ordering::Relaxed),
3970 self.using_namespace_lookup_count.load(Ordering::Relaxed),
3971 self.callable_reference_spec_build_count
3972 .load(Ordering::Relaxed),
3973 self.using_name_candidate_inspection_count
3974 .load(Ordering::Relaxed),
3975 )
3976 }
3977
3978 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3979 file == target.source()
3980 || self
3981 .visible_by_file
3982 .get(file)
3983 .is_some_and(|visible| visible.contains(target))
3984 }
3985
3986 pub fn declaration_visible_at(
3998 &self,
3999 analyzer: &CppGraphSource<'_>,
4000 file: &ProjectFile,
4001 declaration: &CodeUnit,
4002 reference_byte: usize,
4003 ) -> bool {
4004 let reference_guards = OnceCell::new();
4005 self.visible_identifier_candidates(file, declaration.identifier())
4006 .filter(|candidate| {
4007 self.same_logical_callable(analyzer, candidate, declaration)
4008 || flattened_macro_namespace_declaration_matches(
4009 analyzer,
4010 self.cpp,
4011 file,
4012 candidate,
4013 declaration,
4014 reference_byte,
4015 )
4016 })
4017 .any(|candidate| {
4018 self.physical_declaration_visible_at(
4019 analyzer,
4020 file,
4021 candidate,
4022 reference_byte,
4023 &reference_guards,
4024 )
4025 })
4026 }
4027
4028 pub fn declaration_visible_at_reference(
4035 &self,
4036 analyzer: &CppGraphSource<'_>,
4037 file: &ProjectFile,
4038 declaration: &CodeUnit,
4039 reference: Node<'_>,
4040 ) -> bool {
4041 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4042 return false;
4043 };
4044 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4045 let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4046 if !declaration_guards.iter().any(|(_, required)| {
4047 guards_compatible_at_reference(required, reference_guards.as_ref())
4048 }) {
4049 return false;
4050 }
4051 let guards = OnceCell::new();
4052 self.physical_declaration_visible_at(
4053 analyzer,
4054 file,
4055 declaration,
4056 reference.start_byte(),
4057 &guards,
4058 )
4059 }
4060
4061 pub fn declaration_visible_for_c_forward_call(
4067 &self,
4068 analyzer: &CppGraphSource<'_>,
4069 file: &ProjectFile,
4070 declaration: &CodeUnit,
4071 reference_byte: usize,
4072 ) -> bool {
4073 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4074 return true;
4075 }
4076 if declaration.source() != file {
4077 return false;
4078 }
4079 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4080 return false;
4081 };
4082 let reference_guards = prepared
4083 .tree()
4084 .root_node()
4085 .descendant_for_byte_range(reference_byte, reference_byte)
4086 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4087 declaration_guard_requirements(analyzer, self.cpp, declaration)
4088 .into_iter()
4089 .any(|(_, required)| {
4090 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4091 })
4092 }
4093
4094 pub fn callable_arity_at_reference(
4095 &self,
4096 analyzer: &CppGraphSource<'_>,
4097 file: &ProjectFile,
4098 candidate: &CodeUnit,
4099 reference_byte: usize,
4100 ) -> Option<CallableArity> {
4101 let key = (file.clone(), logical_symbol_key(candidate));
4102 let cell = self
4103 .callable_reference_specs
4104 .lock()
4105 .expect("C++ callable reference-spec cache poisoned")
4106 .entry(key)
4107 .or_default()
4108 .clone();
4109 let spec = cell.get_or_init(|| {
4110 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4111 let spec = TargetSpec::from_target(analyzer, candidate)?;
4112 let spec = spec
4113 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4114 .into_owned();
4115 #[cfg(any(test, feature = "test-support"))]
4116 self.callable_reference_spec_build_count
4117 .fetch_add(1, Ordering::Relaxed);
4118 Some(spec)
4119 });
4120 spec.as_ref()?.callable_arity_at(reference_byte)
4121 }
4122
4123 fn physical_declaration_visible_at(
4124 &self,
4125 analyzer: &CppGraphSource<'_>,
4126 file: &ProjectFile,
4127 declaration: &CodeUnit,
4128 reference_byte: usize,
4129 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4130 ) -> bool {
4131 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4132 return false;
4133 };
4134 let reference = CallableReferenceContext {
4135 file,
4136 position: Some(CallableReferencePosition {
4137 prepared: prepared.as_ref(),
4138 byte: reference_byte,
4139 guards: reference_guards,
4140 }),
4141 };
4142 if declaration.source() == file {
4143 return callable_declaration_activation_in_file(
4144 analyzer,
4145 prepared.as_ref(),
4146 declaration,
4147 &reference,
4148 )
4149 .or_else(|| {
4150 self.exhaustive_guard_family_activation(
4151 analyzer,
4152 prepared.as_ref(),
4153 declaration,
4154 &reference,
4155 )
4156 })
4157 .is_some_and(|activation| activation < reference_byte);
4158 }
4159 let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
4160 return false;
4161 };
4162 if callable_declaration_activation_in_file(
4163 analyzer,
4164 donor_syntax.as_ref(),
4165 declaration,
4166 &reference,
4167 )
4168 .or_else(|| {
4169 self.exhaustive_guard_family_activation(
4170 analyzer,
4171 donor_syntax.as_ref(),
4172 declaration,
4173 &reference,
4174 )
4175 })
4176 .is_none()
4177 {
4178 return false;
4179 }
4180 declaration_guard_requirements(analyzer, self.cpp, declaration)
4181 .into_iter()
4182 .any(|(_, declaration_guards)| {
4183 self.foreign_declaration_reachable_at_reference(
4184 file,
4185 prepared.as_ref(),
4186 declaration.source(),
4187 &declaration_guards,
4188 reference.guards(),
4189 reference_byte,
4190 )
4191 })
4192 }
4193
4194 pub fn external_type_candidate_visible_at(
4195 &self,
4196 file: &ProjectFile,
4197 candidate: &CodeUnit,
4198 reference_byte: usize,
4199 ) -> bool {
4200 if candidate.source() == file {
4201 return true;
4202 }
4203 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4204 return false;
4205 };
4206 self.visible_identifier_candidates(file, candidate.identifier())
4207 .filter(|peer| same_logical_symbol(candidate, peer))
4208 .any(|peer| {
4209 peer.source() == file
4210 || self
4211 .include_activation_for_source(
4212 self.cpp,
4213 file,
4214 prepared.as_ref(),
4215 peer.source(),
4216 )
4217 .is_some_and(|activation| activation <= reference_byte)
4218 })
4219 }
4220
4221 pub fn external_type_declaration_visible_at(
4222 &self,
4223 file: &ProjectFile,
4224 candidate: &CodeUnit,
4225 reference_byte: usize,
4226 ) -> bool {
4227 if candidate.source() == file {
4228 return true;
4229 }
4230 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4231 return false;
4232 };
4233 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
4234 .is_some_and(|activation| activation <= reference_byte)
4235 }
4236
4237 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
4256 if let Some(cached) = self
4257 .compile_proven_guard_cells
4258 .lock()
4259 .expect("C++ compile-proven guard cache poisoned")
4260 .get(file)
4261 {
4262 return Arc::clone(cached);
4263 }
4264 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
4265 Some(names) => names,
4266 None => {
4267 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
4268 let seed = translation_units.next().and_then(|translation_unit| {
4269 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
4270 });
4271 match seed {
4272 None => HashSet::default(),
4273 Some(mut names) => {
4274 for translation_unit in translation_units {
4275 let Some(reached) = context_fact_names(
4276 self.cpp.compile_contexts_for(&translation_unit),
4277 ) else {
4278 names.clear();
4279 break;
4280 };
4281 names.retain(|name| reached.contains(name));
4282 if names.is_empty() {
4283 break;
4284 }
4285 }
4286 names
4287 }
4288 }
4289 }
4290 };
4291 let proven = Arc::new(
4292 names
4293 .into_iter()
4294 .map(PreprocessorGuard::Defined)
4295 .collect::<HashSet<_>>(),
4296 );
4297 self.compile_proven_guard_cells
4298 .lock()
4299 .expect("C++ compile-proven guard cache poisoned")
4300 .insert(file.clone(), Arc::clone(&proven));
4301 proven
4302 }
4303
4304 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
4311 if !self.cpp.compile_contexts_for(file).is_empty() {
4312 return false;
4313 }
4314 let translation_units = self.cpp.reaching_translation_units(file);
4315 translation_units.is_empty()
4316 || translation_units
4317 .iter()
4318 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
4319 }
4320
4321 pub fn miss_requires_compile_context(
4333 &self,
4334 file: &ProjectFile,
4335 identifier: &str,
4336 reference: Node<'_>,
4337 ) -> bool {
4338 if !self.compile_context_is_absent(file) {
4339 return false;
4340 }
4341 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4342 return false;
4343 };
4344 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4345 let reference_byte = reference.start_byte();
4346 let mut sources = self
4347 .visible_identifier_candidates(file, identifier)
4348 .map(CodeUnit::source)
4349 .filter(|source| *source != file)
4350 .collect::<Vec<_>>();
4351 sources.sort();
4352 sources.dedup();
4353 sources.into_iter().any(|declaration_source| {
4354 self.conditional_include_projections_for_source(
4355 file,
4356 prepared.as_ref(),
4357 declaration_source,
4358 )
4359 .iter()
4360 .any(|projection| {
4361 projection.activation_byte <= reference_byte
4362 && !guard_requirements_hold_at_reference(
4363 &projection.required_guards,
4364 reference_guards.as_ref(),
4365 )
4366 && guards_compatible_at_reference(
4367 &projection.required_guards,
4368 reference_guards.as_ref(),
4369 )
4370 })
4371 })
4372 }
4373
4374 fn foreign_declaration_reachable_at_reference(
4385 &self,
4386 file: &ProjectFile,
4387 prepared: &PreparedSyntaxTree,
4388 declaration_source: &ProjectFile,
4389 declaration_guards: &HashSet<PreprocessorGuard>,
4390 reference_guards: Option<&HashSet<PreprocessorGuard>>,
4391 reference_byte: usize,
4392 ) -> bool {
4393 let proven = self.compile_proven_guards(file);
4399 let augmented;
4400 let reference_guards = match reference_guards {
4401 Some(active) if !proven.is_empty() => {
4402 augmented = active.union(&proven).cloned().collect();
4403 Some(&augmented)
4404 }
4405 other => other,
4406 };
4407 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4408 eprintln!(
4409 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
4410 declaration_source.rel_path().display(),
4411 );
4412 }
4413 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
4414 return false;
4415 }
4416 if self
4417 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
4418 .is_some_and(|activation| activation <= reference_byte)
4419 {
4420 return true;
4421 }
4422 let projections =
4423 self.conditional_include_projections_for_source(file, prepared, declaration_source);
4424 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4425 eprintln!(
4426 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
4427 declaration_source.rel_path().display(),
4428 declaration_guards.len(),
4429 proven.len(),
4430 projections.len(),
4431 );
4432 }
4433 projections.iter().any(|projection| {
4434 projection.activation_byte <= reference_byte
4435 && guard_requirements_hold_at_reference(
4436 &projection.required_guards,
4437 reference_guards,
4438 )
4439 && self.preprocessor_guards_stable_between(
4440 file,
4441 projection.activation_byte,
4442 reference_byte,
4443 &projection.required_guards,
4444 )
4445 })
4446 }
4447
4448 fn foreign_declaration_may_be_reachable_from_raw_guards(
4449 &self,
4450 file: &ProjectFile,
4451 prepared: &PreparedSyntaxTree,
4452 declaration_source: &ProjectFile,
4453 declaration_guards: &HashSet<PreprocessorGuard>,
4454 reference_guards: Option<&HashSet<PreprocessorGuard>>,
4455 reference_byte: usize,
4456 ) -> bool {
4457 let proven = self.compile_proven_guards(file);
4458 let augmented;
4459 let reference_guards = match reference_guards {
4460 Some(active) if !proven.is_empty() => {
4461 augmented = active.union(&proven).cloned().collect();
4462 Some(&augmented)
4463 }
4464 other => other,
4465 };
4466 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
4467 return false;
4468 }
4469 if self
4470 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
4471 .is_some_and(|activation| activation <= reference_byte)
4472 {
4473 return true;
4474 }
4475 let reachable = find_conditional_include_projection_for_source(
4476 self.cpp,
4477 self.token,
4478 file,
4479 prepared,
4480 declaration_source,
4481 reference_guards,
4482 reference_byte,
4483 &|| {
4484 #[cfg(any(test, feature = "test-support"))]
4485 self.conditional_include_target_state_count
4486 .fetch_add(1, Ordering::Relaxed);
4487 },
4488 );
4489 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4490 eprintln!(
4491 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
4492 declaration_source.rel_path().display(),
4493 declaration_guards.len(),
4494 proven.len(),
4495 reference_guards.map_or(0, HashSet::len),
4496 );
4497 }
4498 reachable
4499 }
4500
4501 fn foreign_declaration_reachable_from_compile_proven_guards(
4502 &self,
4503 file: &ProjectFile,
4504 prepared: &PreparedSyntaxTree,
4505 declaration_source: &ProjectFile,
4506 declaration_guards: &HashSet<PreprocessorGuard>,
4507 reference_byte: usize,
4508 ) -> bool {
4509 let proven = self.compile_proven_guards(file);
4510 if proven.is_empty()
4511 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
4512 {
4513 return false;
4514 }
4515 let projections =
4516 self.conditional_include_projections_for_source(file, prepared, declaration_source);
4517 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4518 eprintln!(
4519 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
4520 declaration_source.rel_path().display(),
4521 declaration_guards.len(),
4522 proven.len(),
4523 projections.len(),
4524 );
4525 }
4526 projections.iter().any(|projection| {
4527 projection.activation_byte <= reference_byte
4528 && guard_requirements_hold_at_reference(
4529 &projection.required_guards,
4530 Some(proven.as_ref()),
4531 )
4532 && self.preprocessor_guards_stable_between(
4537 file,
4538 0,
4539 projection.activation_byte,
4540 &projection.required_guards,
4541 )
4542 })
4543 }
4544
4545 pub fn external_type_candidate_visible_in_context(
4546 &self,
4547 analyzer: &CppGraphSource<'_>,
4548 file: &ProjectFile,
4549 candidate: &CodeUnit,
4550 reference: Node<'_>,
4551 ) -> bool {
4552 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
4553 if report_stats {
4554 eprintln!(
4555 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
4556 candidate.fq_name(),
4557 candidate.source().rel_path().display(),
4558 file.rel_path().display(),
4559 reference.start_byte(),
4560 );
4561 }
4562 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4563 return false;
4564 };
4565 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
4566 let reference_guards = OnceCell::new();
4567 let reference_guards_at_site = || {
4568 reference_guards.get_or_init(|| {
4569 let started = Instant::now();
4570 if report_stats {
4571 eprintln!(
4572 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
4573 file.rel_path().display(),
4574 reference.start_byte(),
4575 raw_reference_guards.as_ref().map_or(0, HashSet::len),
4576 );
4577 }
4578 let macro_environment = self.macro_environment(file, reference.start_byte());
4579 let filtered = raw_reference_guards
4580 .clone()
4581 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
4582 if report_stats {
4583 eprintln!(
4584 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
4585 filtered.is_some(),
4586 started.elapsed().as_millis(),
4587 );
4588 }
4589 filtered
4590 })
4591 };
4592
4593 let peers = self
4594 .visible_identifier_candidates(file, candidate.identifier())
4595 .filter(|peer| same_logical_symbol(candidate, peer))
4596 .collect::<Vec<_>>();
4597 if report_stats {
4598 let peer_sources = peers
4599 .iter()
4600 .map(|peer| peer.source().rel_path().display().to_string())
4601 .collect::<Vec<_>>();
4602 eprintln!(
4603 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
4604 candidate.fq_name(),
4605 );
4606 }
4607 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
4608 declaration_guard_requirements(analyzer, self.cpp, peer)
4609 .into_iter()
4610 .any(|(declaration_byte, declaration_guards)| {
4611 if peer.source() == file {
4612 let visible = declaration_byte < reference.start_byte()
4613 && declaration_guards.is_empty();
4614 if report_stats {
4615 eprintln!(
4616 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
4617 peer.source().rel_path().display(),
4618 declaration_guards.len(),
4619 );
4620 }
4621 return visible;
4622 }
4623 let direct = declaration_guards.is_empty()
4624 && self
4625 .include_activation_for_source(
4626 self.cpp,
4627 file,
4628 prepared.as_ref(),
4629 peer.source(),
4630 )
4631 .is_some_and(|activation| activation <= reference.start_byte());
4632 let compile_proven = !direct
4633 && self.foreign_declaration_reachable_from_compile_proven_guards(
4634 file,
4635 prepared.as_ref(),
4636 peer.source(),
4637 &declaration_guards,
4638 reference.start_byte(),
4639 );
4640 if report_stats {
4641 eprintln!(
4642 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
4643 peer.source().rel_path().display(),
4644 declaration_guards.len(),
4645 );
4646 }
4647 direct || compile_proven
4648 })
4649 });
4650 if directly_visible_without_reference_environment {
4651 if report_stats {
4652 eprintln!(
4653 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
4654 candidate.fq_name(),
4655 );
4656 }
4657 return true;
4658 }
4659 let directly_visible = peers.iter().any(|peer| {
4660 declaration_guard_requirements(analyzer, self.cpp, peer)
4661 .into_iter()
4662 .any(|(declaration_byte, declaration_guards)| {
4663 if peer.source() == file {
4664 if declaration_byte >= reference.start_byte() {
4665 return false;
4666 }
4667 if !guard_requirements_hold_at_reference(
4668 &declaration_guards,
4669 raw_reference_guards.as_ref(),
4670 ) {
4671 return false;
4672 }
4673 return guard_requirements_hold_at_reference(
4674 &declaration_guards,
4675 reference_guards_at_site().as_ref(),
4676 ) && self.preprocessor_guards_stable_between(
4677 file,
4678 declaration_byte,
4679 reference.start_byte(),
4680 &declaration_guards,
4681 );
4682 }
4683 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
4684 file,
4685 prepared.as_ref(),
4686 peer.source(),
4687 &declaration_guards,
4688 raw_reference_guards.as_ref(),
4689 reference.start_byte(),
4690 );
4691 if report_stats {
4692 eprintln!(
4693 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
4694 peer.source().rel_path().display(),
4695 declaration_guards.len(),
4696 );
4697 }
4698 if !raw_feasible {
4699 return false;
4700 }
4701 self.foreign_declaration_reachable_at_reference(
4702 file,
4703 prepared.as_ref(),
4704 peer.source(),
4705 &declaration_guards,
4706 reference_guards_at_site().as_ref(),
4707 reference.start_byte(),
4708 )
4709 })
4710 });
4711 if directly_visible {
4712 if report_stats {
4713 eprintln!(
4714 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
4715 candidate.fq_name(),
4716 );
4717 }
4718 return true;
4719 }
4720 let complementary = self
4721 .visible_identifier_candidates(file, candidate.identifier())
4722 .filter(|peer| {
4723 peer.kind() == candidate.kind()
4724 && peer.fq_name() == candidate.fq_name()
4725 && peer.source() == candidate.source()
4726 })
4727 .collect::<Vec<_>>();
4728 let complementary_family =
4733 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
4734 let raw_candidate_branch_compatible = complementary_family
4735 && raw_reference_guards.as_ref().is_some_and(|active| {
4736 declaration_guard_requirements(analyzer, self.cpp, candidate)
4737 .iter()
4738 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4739 });
4740 if report_stats {
4741 eprintln!(
4742 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
4743 candidate.fq_name(),
4744 complementary.len(),
4745 complementary_family,
4746 raw_candidate_branch_compatible,
4747 );
4748 }
4749 let candidate_branch_compatible = raw_candidate_branch_compatible
4750 && reference_guards_at_site().as_ref().is_some_and(|active| {
4751 declaration_guard_requirements(analyzer, self.cpp, candidate)
4752 .iter()
4753 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4754 });
4755 let complementary_visible = candidate_branch_compatible
4756 && if candidate.source() == file {
4757 declaration_guard_requirements(analyzer, self.cpp, candidate)
4758 .iter()
4759 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
4760 } else {
4761 self.include_activation_for_source(
4762 self.cpp,
4763 file,
4764 prepared.as_ref(),
4765 candidate.source(),
4766 )
4767 .is_some_and(|activation| activation <= reference.start_byte())
4768 };
4769 if report_stats {
4770 eprintln!(
4771 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
4772 if complementary_visible {
4773 "complementary"
4774 } else {
4775 "missing"
4776 },
4777 candidate.fq_name(),
4778 );
4779 }
4780 complementary_visible
4781 }
4782
4783 pub fn is_exhaustive_same_fqn_type_declaration_family(
4784 &self,
4785 analyzer: &CppGraphSource<'_>,
4786 file: &ProjectFile,
4787 candidate: &CodeUnit,
4788 ) -> bool {
4789 let candidates = self
4790 .visible_identifier_candidates(file, candidate.identifier())
4791 .filter(|peer| {
4792 peer.kind() == candidate.kind()
4793 && peer.fq_name() == candidate.fq_name()
4794 && peer.source() == candidate.source()
4795 })
4796 .collect::<Vec<_>>();
4797 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
4798 }
4799
4800 pub fn dependent_member_pointer_alias_visible_in_context(
4815 &self,
4816 analyzer: &CppGraphSource<'_>,
4817 file: &ProjectFile,
4818 candidate: &CodeUnit,
4819 owner_components: &[String],
4820 reference: Node<'_>,
4821 ) -> bool {
4822 if !analyzer
4823 .type_alias_provider()
4824 .is_some_and(|provider| provider.is_type_alias(candidate))
4825 {
4826 return false;
4827 }
4828 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
4829 return false;
4830 };
4831 if terminal != candidate.identifier()
4832 || canonical_cpp_scope_components(candidate) != owner_components
4833 {
4834 return false;
4835 }
4836 let Some(expected_parent_fq_name) =
4837 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
4838 else {
4839 return false;
4840 };
4841 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
4842 return false;
4843 };
4844 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
4845 || parent_anchor.source() != candidate.source()
4846 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
4847 {
4848 return false;
4849 }
4850
4851 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
4857 || candidate.source() == file
4858 && !analyzer
4859 .ranges(candidate)
4860 .iter()
4861 .any(|range| range.start_byte < reference.start_byte())
4862 {
4863 return false;
4864 }
4865
4866 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
4867 if candidate_guards.is_empty() {
4868 return false;
4869 }
4870 let same_guard_sets =
4871 |left: &[(usize, HashSet<PreprocessorGuard>)],
4872 right: &[(usize, HashSet<PreprocessorGuard>)]| {
4873 left.iter().all(|(_, left_guards)| {
4874 right
4875 .iter()
4876 .any(|(_, right_guards)| left_guards == right_guards)
4877 })
4878 };
4879 let parent_candidates = self
4880 .visible_identifier_candidates(file, parent_anchor.identifier())
4881 .filter(|peer| {
4882 peer.kind() == parent_anchor.kind()
4883 && peer.fq_name() == expected_parent_fq_name.as_str()
4884 && peer.source() == parent_anchor.source()
4885 && canonical_cpp_scope_components(peer) == owner_prefix
4886 })
4887 .filter_map(|peer| {
4888 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
4889 (candidate_guards.len() == parent_guards.len()
4890 && same_guard_sets(&candidate_guards, &parent_guards)
4891 && same_guard_sets(&parent_guards, &candidate_guards))
4892 .then(|| (peer.clone(), parent_guards))
4893 })
4894 .collect::<Vec<_>>();
4895 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
4896 return false;
4897 };
4898
4899 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4900 return false;
4901 };
4902 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
4903 else {
4904 return false;
4905 };
4906 if !candidate_guards.iter().any(|(_, target_guards)| {
4911 guards_compatible_at_reference(target_guards, Some(&reference_guards))
4912 && (candidate.source() != file
4913 || self.preprocessor_guards_stable_between(
4914 file,
4915 0,
4916 reference.start_byte(),
4917 target_guards,
4918 ))
4919 }) {
4920 return false;
4921 }
4922
4923 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
4924 }
4925
4926 pub fn external_type_candidate_guard_compatible_in_context(
4936 &self,
4937 analyzer: &CppGraphSource<'_>,
4938 file: &ProjectFile,
4939 candidate: &CodeUnit,
4940 reference: Node<'_>,
4941 ) -> bool {
4942 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4943 return false;
4944 };
4945 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4946
4947 self.visible_identifier_candidates(file, candidate.identifier())
4948 .filter(|peer| same_logical_symbol(candidate, peer))
4949 .any(|peer| {
4950 declaration_guard_requirements(analyzer, self.cpp, peer)
4951 .into_iter()
4952 .any(|(declaration_byte, declaration_guards)| {
4953 if peer.source() == file {
4954 let (start, end) = if declaration_byte <= reference.start_byte() {
4955 (declaration_byte, reference.start_byte())
4956 } else {
4957 (reference.start_byte(), declaration_byte)
4958 };
4959 return guard_requirements_hold_at_reference(
4960 &declaration_guards,
4961 reference_guards.as_ref(),
4962 ) && self.preprocessor_guards_stable_between(
4963 file,
4964 start,
4965 end,
4966 &declaration_guards,
4967 );
4968 }
4969 self.foreign_declaration_reachable_at_reference(
4970 file,
4971 prepared.as_ref(),
4972 peer.source(),
4973 &declaration_guards,
4974 reference_guards.as_ref(),
4975 reference.start_byte(),
4976 )
4977 })
4978 })
4979 }
4980
4981 pub fn same_file_callable_guard_compatible_ignoring_order(
4989 &self,
4990 analyzer: &CppGraphSource<'_>,
4991 file: &ProjectFile,
4992 candidate: &CodeUnit,
4993 reference: Node<'_>,
4994 ) -> bool {
4995 if candidate.source() != file || !candidate.is_callable() {
4996 return false;
4997 }
4998 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4999 return false;
5000 };
5001 let guards = OnceCell::new();
5002 let context = CallableReferenceContext {
5003 file,
5004 position: Some(CallableReferencePosition {
5005 prepared: prepared.as_ref(),
5006 byte: reference.start_byte(),
5007 guards: &guards,
5008 }),
5009 };
5010 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5011 .into_iter()
5012 .any(|declaration| {
5013 callable_preprocessor_context_is_visible_for_reference(
5014 declaration,
5015 prepared.source(),
5016 &context,
5017 )
5018 })
5019 }
5020
5021 pub fn type_candidate_may_be_visible_before_reference(
5022 &self,
5023 analyzer: &CppGraphSource<'_>,
5024 file: &ProjectFile,
5025 candidate: &CodeUnit,
5026 reference_byte: usize,
5027 ) -> bool {
5028 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5029 return false;
5030 };
5031 let root = prepared.tree().root_node();
5032 let end_byte = reference_byte
5033 .saturating_add(1)
5034 .min(prepared.source().len());
5035 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5036 return false;
5037 };
5038 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5039 }
5040
5041 pub fn preprocessor_guards_stable_between(
5042 &self,
5043 file: &ProjectFile,
5044 start_byte: usize,
5045 end_byte: usize,
5046 guards: &HashSet<PreprocessorGuard>,
5047 ) -> bool {
5048 if guards.is_empty() || start_byte >= end_byte {
5049 return true;
5050 }
5051 let cell = self.macro_event_cell(file);
5052 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5053 let mut visited = HashSet::from_iter([file.clone()]);
5054 !events.iter().any(|event| {
5055 event.byte() >= start_byte
5056 && event.byte() < end_byte
5057 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5058 })
5059 }
5060
5061 fn macro_event_may_mutate_guards(
5062 &self,
5063 event: &MacroEvent,
5064 guards: &HashSet<PreprocessorGuard>,
5065 visited: &mut HashSet<ProjectFile>,
5066 ) -> bool {
5067 match event {
5068 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5069 guards.iter().any(|guard| guard.may_depend_on_macro(name))
5070 }
5071 MacroEvent::Include { targets, .. } => {
5072 targets.is_empty()
5073 || targets
5074 .iter()
5075 .any(|target| self.source_may_mutate_guards(target, guards, visited))
5076 }
5077 MacroEvent::Invalidate { .. } => true,
5078 }
5079 }
5080
5081 fn source_may_mutate_guards(
5082 &self,
5083 file: &ProjectFile,
5084 guards: &HashSet<PreprocessorGuard>,
5085 visited: &mut HashSet<ProjectFile>,
5086 ) -> bool {
5087 if !visited.insert(file.clone()) {
5088 return false;
5089 }
5090 let cell = self.macro_event_cell(file);
5091 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5092 events
5093 .iter()
5094 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5095 }
5096
5097 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5098 let normalized = normalize_reference_name(raw_name)?;
5099 self.type_candidates(file, &normalized)
5100 .into_iter()
5101 .next()
5102 .cloned()
5103 }
5104
5105 pub fn unique_visible_parameter_type_fallback(
5114 &self,
5115 analyzer: &CppGraphSource<'_>,
5116 file: &ProjectFile,
5117 node: Node<'_>,
5118 source: &str,
5119 ) -> Option<CodeUnit> {
5120 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5121 return None;
5122 }
5123 let name = node_text(node, source);
5124 let candidates = self
5125 .visible_identifier_candidates(file, name)
5126 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5127 .filter(|candidate| {
5128 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5129 })
5130 .collect::<Vec<_>>();
5131 self.unique_canonical_type_candidate(analyzer, file, &candidates)
5132 }
5133
5134 pub fn resolve_type_node_result(
5135 &self,
5136 file: &ProjectFile,
5137 node: Node<'_>,
5138 source: &str,
5139 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
5140 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
5141 return Ok(None);
5142 };
5143 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
5144 return Ok(Some(primary));
5145 };
5146 self.resolve_template_arguments(file, primary, &arguments)
5147 .map(Some)
5148 }
5149
5150 pub fn resolve_type_node_primary(
5151 &self,
5152 file: &ProjectFile,
5153 node: Node<'_>,
5154 source: &str,
5155 ) -> Option<CodeUnit> {
5156 let components = cpp_type_name_components(node, source)?;
5157 self.resolve_type(file, &components.join("::"))
5158 }
5159
5160 pub fn resolve_template_arguments(
5161 &self,
5162 file: &ProjectFile,
5163 primary: CodeUnit,
5164 arguments: &[CppTemplateExpression],
5165 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5166 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
5167 }
5168
5169 fn resolve_template_arguments_inner(
5170 &self,
5171 file: &ProjectFile,
5172 primary: CodeUnit,
5173 arguments: &[CppTemplateExpression],
5174 seen_aliases: &mut HashSet<CodeUnit>,
5175 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5176 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
5177 && let Some(alias_target) = &metadata.alias_target
5178 {
5179 if !seen_aliases.insert(primary.clone()) {
5180 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
5181 }
5182 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
5183 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
5184 let target_name = alias_target.components.join("::");
5185 let target_primary = if alias_target.global {
5186 unique_logical_type_candidate(self.type_candidates(file, &target_name))
5187 } else {
5188 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
5189 };
5190 let Some(target_primary) = target_primary else {
5191 return Ok(primary);
5195 };
5196 let Some(target_arguments) = &alias_target.arguments else {
5197 return Ok(target_primary);
5198 };
5199 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
5200 .ok_or(CppTemplateResolutionError::Substitution)?;
5201 return self.resolve_template_arguments_inner(
5202 file,
5203 target_primary,
5204 &target_arguments,
5205 seen_aliases,
5206 );
5207 }
5208
5209 let primary_fq_name = self
5210 .cpp_template_metadata
5211 .get(&primary)
5212 .map(|metadata| metadata.primary_fq_name.clone())
5213 .unwrap_or_else(|| primary.fq_name());
5214 let has_specialization_metadata = self
5215 .cpp_template_families
5216 .get(&primary_fq_name)
5217 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
5218 if !has_specialization_metadata {
5219 return Ok(primary);
5220 }
5221 self.select_template_specialization(file, &primary, arguments)
5222 }
5223
5224 fn select_template_specialization(
5225 &self,
5226 file: &ProjectFile,
5227 resolved: &CodeUnit,
5228 explicit_arguments: &[CppTemplateExpression],
5229 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
5230 let primary_fq_name = self
5231 .cpp_template_metadata
5232 .get(resolved)
5233 .map(|metadata| metadata.primary_fq_name.clone())
5234 .unwrap_or_else(|| resolved.fq_name());
5235 let family = self
5236 .cpp_template_families
5237 .get(&primary_fq_name)
5238 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
5239 let primary_candidates = family
5240 .iter()
5241 .filter_map(|unit| {
5242 let metadata = self.cpp_template_metadata.get(unit)?;
5243 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
5244 })
5245 .collect::<Vec<_>>();
5246 let primary_unit = primary_candidates
5247 .iter()
5248 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
5249 .or_else(|| {
5250 primary_candidates
5251 .iter()
5252 .map(|(unit, _)| *unit)
5253 .min_by_key(|unit| {
5254 (
5255 unit.source().to_string(),
5256 unit.signature().unwrap_or_default(),
5257 )
5258 })
5259 })
5260 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
5261 let primary_parameters =
5262 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
5263 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
5264 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
5265 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
5266
5267 let mut applicable = Vec::new();
5268 for unit in family {
5269 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
5270 continue;
5271 };
5272 if metadata.is_primary() || !self.is_visible(file, unit) {
5273 continue;
5274 }
5275 if !cpp_specialization_matches(metadata, &expanded) {
5276 continue;
5277 }
5278 applicable.push((unit, metadata));
5279 }
5280 if applicable.is_empty() {
5281 return Ok(primary_unit.clone());
5282 }
5283
5284 let winners = applicable
5289 .iter()
5290 .filter(|(candidate, candidate_metadata)| {
5291 applicable.iter().all(|(other, other_metadata)| {
5292 same_visible_symbol(candidate, other)
5293 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
5294 })
5295 })
5296 .copied()
5297 .collect::<Vec<_>>();
5298 let Some((selected, _)) = winners.first() else {
5299 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
5302 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
5303 });
5304 };
5305 if winners
5306 .iter()
5307 .any(|(unit, _)| !same_visible_symbol(unit, selected))
5308 {
5309 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
5310 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
5311 });
5312 }
5313 Ok((*selected).clone())
5314 }
5315
5316 pub fn resolve_type_components_lexically(
5317 &self,
5318 analyzer: &CppGraphSource<'_>,
5319 file: &ProjectFile,
5320 components: &[String],
5321 global: bool,
5322 lexical_scope: &[String],
5323 ) -> LexicalTypeResolution {
5324 self.resolve_type_components_lexically_inner(
5325 analyzer,
5326 file,
5327 components,
5328 global,
5329 lexical_scope,
5330 TypeCandidateResolution::Canonical,
5331 )
5332 }
5333
5334 pub fn resolve_type_components_lexically_for_forward(
5335 &self,
5336 analyzer: &CppGraphSource<'_>,
5337 file: &ProjectFile,
5338 components: &[String],
5339 global: bool,
5340 lexical_scope: &[String],
5341 ) -> LexicalTypeResolution {
5342 self.resolve_type_components_lexically_inner(
5343 analyzer,
5344 file,
5345 components,
5346 global,
5347 lexical_scope,
5348 TypeCandidateResolution::PreserveAlias,
5349 )
5350 }
5351
5352 pub fn resolve_type_components_lexically_for_target(
5353 &self,
5354 analyzer: &CppGraphSource<'_>,
5355 file: &ProjectFile,
5356 components: &[String],
5357 global: bool,
5358 lexical_scope: &[String],
5359 target: &CodeUnit,
5360 ) -> LexicalTypeResolution {
5361 #[cfg(any(test, feature = "test-support"))]
5362 self.target_preserving_type_resolution_count
5363 .fetch_add(1, Ordering::Relaxed);
5364 self.resolve_type_components_lexically_inner(
5365 analyzer,
5366 file,
5367 components,
5368 global,
5369 lexical_scope,
5370 TypeCandidateResolution::PreserveTarget(target),
5371 )
5372 }
5373
5374 pub fn coarse_unqualified_type_reference_may_resolve(
5375 &self,
5376 file: &ProjectFile,
5377 name: &str,
5378 ) -> bool {
5379 if name.is_empty() {
5380 return true;
5381 }
5382 self.visible_identifier_candidates(file, name)
5383 .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
5384 || self.visible_parser_alias_name_is_visible(file, name)
5385 }
5386
5387 #[allow(clippy::too_many_arguments)]
5388 pub fn structured_type_reference_may_resolve_to_target(
5389 &self,
5390 analyzer: &CppGraphSource<'_>,
5391 file: &ProjectFile,
5392 components: &[String],
5393 global: bool,
5394 lexical_scope: &[String],
5395 target: &CodeUnit,
5396 ) -> bool {
5397 if components.is_empty() {
5398 return true;
5399 }
5400 let Some(terminal) = components.last() else {
5401 return true;
5402 };
5403 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
5404 .map(|qualified| qualified.join("::"))
5405 .collect::<Vec<_>>();
5406 let target_name = cpp_name_for(target);
5407 if qualified_tiers
5408 .iter()
5409 .any(|qualified| qualified == &target_name)
5410 {
5411 return true;
5412 }
5413
5414 let mut saw_shape_candidate = false;
5415 for candidate in self.visible_identifier_candidates(file, terminal) {
5416 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
5417 {
5418 continue;
5419 }
5420 let candidate_name = cpp_name_for(candidate);
5421 let shape_matches = if global || components.len() > 1 {
5422 qualified_tiers
5423 .iter()
5424 .any(|qualified| qualified == &candidate_name)
5425 } else {
5426 true
5427 };
5428 if !shape_matches {
5429 continue;
5430 }
5431 saw_shape_candidate = true;
5432 if same_visible_symbol(candidate, target)
5433 || self.c_tag_declaration_family_matches_target(
5434 analyzer,
5435 file,
5436 std::slice::from_ref(&candidate),
5437 target,
5438 )
5439 || self.compatible_primary_template_redeclarations(candidate, target)
5440 || (declared_type_alias(analyzer, candidate)
5441 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
5442 {
5443 return true;
5444 }
5445 }
5446
5447 !saw_shape_candidate
5448 }
5449
5450 fn cached_c_tag_kind(
5457 &self,
5458 analyzer: &CppGraphSource<'_>,
5459 candidate: &CodeUnit,
5460 ) -> Option<CppCTagKind> {
5461 if let Some(kind) = self
5462 .c_tag_kind_cache
5463 .lock()
5464 .expect("C tag kind cache poisoned")
5465 .get(candidate)
5466 {
5467 return *kind;
5468 }
5469 let kind = indexed_c_tag_kind(analyzer, candidate);
5470 self.c_tag_kind_cache
5471 .lock()
5472 .expect("C tag kind cache poisoned")
5473 .insert(candidate.clone(), kind);
5474 kind
5475 }
5476
5477 fn cached_unique_c_tag_complete_definition(
5478 &self,
5479 analyzer: &CppGraphSource<'_>,
5480 target: &CodeUnit,
5481 target_tag: CppCTagKind,
5482 ) -> Option<CodeUnit> {
5483 if let Some(definition) = self
5484 .c_tag_complete_definition_cache
5485 .lock()
5486 .expect("C tag complete-definition cache poisoned")
5487 .get(target)
5488 {
5489 return definition.clone();
5490 }
5491 let complete_definitions = analyzer
5492 .definitions(&target.fq_name())
5493 .filter(|candidate| {
5494 candidate.is_class()
5495 && !declared_type_alias(analyzer, candidate)
5496 && is_c_source_file(candidate.source())
5497 && analyzer.parent_of(candidate).is_none()
5498 && cpp_class_declaration_strength(analyzer, candidate)
5499 == CppClassDeclarationStrength::Full
5500 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
5501 })
5502 .collect::<HashSet<_>>();
5503 let definition = (complete_definitions.len() == 1)
5504 .then(|| complete_definitions.into_iter().next())
5505 .flatten()
5506 .filter(|candidate| same_visible_symbol(candidate, target));
5507 self.c_tag_complete_definition_cache
5508 .lock()
5509 .expect("C tag complete-definition cache poisoned")
5510 .insert(target.clone(), definition.clone());
5511 definition
5512 }
5513
5514 pub fn c_tag_declaration_family_matches_target(
5515 &self,
5516 analyzer: &CppGraphSource<'_>,
5517 visible_from: &ProjectFile,
5518 candidates: &[&CodeUnit],
5519 target: &CodeUnit,
5520 ) -> bool {
5521 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5522 let candidate_evidence = candidates
5523 .iter()
5524 .map(|candidate| {
5525 (
5526 candidate.fq_name(),
5527 candidate.source().rel_path().to_path_buf(),
5528 cpp_class_declaration_strength(analyzer, candidate),
5529 indexed_c_tag_kind(analyzer, candidate),
5530 self.is_physically_visible(visible_from, candidate),
5531 )
5532 })
5533 .collect::<Vec<_>>();
5534 eprintln!(
5535 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
5536 visible_from.rel_path().display(),
5537 target.fq_name(),
5538 target.source().rel_path().display(),
5539 cpp_class_declaration_strength(analyzer, target),
5540 indexed_c_tag_kind(analyzer, target),
5541 );
5542 }
5543 if candidates.is_empty()
5544 || !target.is_class()
5545 || declared_type_alias(analyzer, target)
5546 || !is_c_source_file(target.source())
5547 || analyzer.parent_of(target).is_some()
5548 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
5549 {
5550 return false;
5551 }
5552 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
5553 return false;
5554 };
5555 if self
5556 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
5557 .is_none()
5558 {
5559 return false;
5560 }
5561 let mut saw_visible_forward = false;
5562 for candidate in candidates.iter().copied() {
5563 if candidate == target {
5564 continue;
5565 }
5566 if !candidate.is_class()
5567 || declared_type_alias(analyzer, candidate)
5568 || candidate.fq_name() != target.fq_name()
5569 || analyzer.parent_of(candidate).is_some()
5570 || cpp_class_declaration_strength(analyzer, candidate)
5571 != CppClassDeclarationStrength::Forward
5572 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
5573 || !self.is_physically_visible(visible_from, candidate)
5574 {
5575 return false;
5576 }
5577 saw_visible_forward = true;
5578 }
5579 saw_visible_forward
5580 }
5581
5582 fn unique_c_tag_declaration_family(
5587 &self,
5588 analyzer: &CppGraphSource<'_>,
5589 visible_from: &ProjectFile,
5590 candidates: &[&CodeUnit],
5591 ) -> Option<CodeUnit> {
5592 let first = candidates.first()?;
5593 let target_fq_name = first.fq_name();
5594 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
5595 let mut full = None;
5596 let mut saw_forward = false;
5597 for candidate in candidates.iter().copied() {
5598 if !candidate.is_class()
5599 || declared_type_alias(analyzer, candidate)
5600 || candidate.fq_name() != target_fq_name
5601 || analyzer.parent_of(candidate).is_some()
5602 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
5603 {
5604 return None;
5605 }
5606 match cpp_class_declaration_strength(analyzer, candidate) {
5607 CppClassDeclarationStrength::Full
5608 if is_c_source_file(candidate.source())
5609 && full.replace(candidate.clone()).is_none() => {}
5610 CppClassDeclarationStrength::Forward
5611 if self.is_physically_visible(visible_from, candidate) =>
5612 {
5613 saw_forward = true
5614 }
5615 _ => return None,
5616 }
5617 }
5618 if saw_forward { full } else { None }
5619 }
5620
5621 pub fn target_preserving_reference_namespace(
5622 &self,
5623 analyzer: &CppGraphSource<'_>,
5624 file: &ProjectFile,
5625 identifier: &str,
5626 target: &CodeUnit,
5627 ) -> Option<Vec<String>> {
5628 let mut namespace = None;
5629 for candidate in self.visible_identifier_candidates(file, identifier) {
5630 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
5631 {
5632 continue;
5633 }
5634 if !(same_visible_symbol(candidate, target)
5635 || self.compatible_primary_template_redeclarations(candidate, target)
5636 || declared_type_alias(analyzer, candidate)
5637 && self.structured_alias_primary_preserves_target(
5638 analyzer, file, candidate, target,
5639 ))
5640 {
5641 continue;
5642 }
5643 if namespace
5644 .as_ref()
5645 .is_some_and(|existing| existing != candidate.package_name())
5646 {
5647 return None;
5648 }
5649 namespace = Some(candidate.package_name().to_string());
5650 }
5651 let namespace = namespace?;
5652 Some(
5653 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5654 brokk_bifrost_core::analyzer::Language::Cpp,
5655 &namespace,
5656 ),
5657 )
5658 }
5659
5660 pub fn resolve_imported_type_candidate(
5661 &self,
5662 analyzer: &CppGraphSource<'_>,
5663 file: &ProjectFile,
5664 target: &CodeUnit,
5665 target_components: &[String],
5666 direct_target: Option<&CodeUnit>,
5667 preserve_alias: bool,
5668 ) -> LexicalTypeResolution {
5669 let candidates = [target];
5670 let resolution = if preserve_alias {
5671 TypeCandidateResolution::PreserveAlias
5672 } else {
5673 direct_target.map_or(
5674 TypeCandidateResolution::Canonical,
5675 TypeCandidateResolution::PreserveTarget,
5676 )
5677 };
5678 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5682 Ok(unit) => LexicalTypeResolution::Resolved {
5683 unit,
5684 components: target_components.to_vec(),
5685 candidates: vec![target.clone()],
5686 },
5687 Err(failure) => failure.lexical_resolution(),
5688 }
5689 }
5690
5691 fn resolve_type_components_lexically_inner(
5692 &self,
5693 analyzer: &CppGraphSource<'_>,
5694 file: &ProjectFile,
5695 components: &[String],
5696 global: bool,
5697 lexical_scope: &[String],
5698 resolution: TypeCandidateResolution<'_>,
5699 ) -> LexicalTypeResolution {
5700 if components.is_empty() {
5701 return LexicalTypeResolution::Missing;
5702 }
5703 let mut injected = self.resolve_injected_class_name(
5713 analyzer,
5714 file,
5715 components,
5716 global,
5717 lexical_scope,
5718 resolution,
5719 );
5720 for qualified in lexical_component_tiers(components, global, lexical_scope) {
5721 let prefix_len = qualified.len().saturating_sub(components.len());
5722 if injected
5723 .as_ref()
5724 .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
5725 {
5726 return injected
5727 .take()
5728 .expect("injected class resolution was just present")
5729 .1;
5730 }
5731 let qualified_name = qualified.join("::");
5732 let candidates = self
5733 .type_candidates(file, &qualified_name)
5734 .into_iter()
5735 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5736 .collect::<Vec<_>>();
5737 if candidates.is_empty() {
5738 if !global && components.len() == 1 {
5739 match self.resolve_inherited_type_for_lexical_scope(
5740 analyzer,
5741 file,
5742 &qualified[..prefix_len],
5743 &components[0],
5744 resolution,
5745 ) {
5746 LexicalTypeResolution::Missing => {}
5747 inherited => return inherited,
5748 }
5749 }
5750 continue;
5751 }
5752 let candidates =
5753 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
5754 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5755 Ok(unit) => unit,
5756 Err(failure) => return failure.lexical_resolution(),
5757 };
5758 return LexicalTypeResolution::Resolved {
5759 unit,
5760 components: qualified,
5761 candidates: candidates.into_iter().cloned().collect(),
5762 };
5763 }
5764 LexicalTypeResolution::Missing
5765 }
5766
5767 fn resolve_injected_class_name(
5768 &self,
5769 analyzer: &CppGraphSource<'_>,
5770 file: &ProjectFile,
5771 components: &[String],
5772 global: bool,
5773 lexical_scope: &[String],
5774 resolution: TypeCandidateResolution<'_>,
5775 ) -> Option<(usize, LexicalTypeResolution)> {
5776 if global
5777 || components.len() != 1
5778 || file.rel_path().extension().is_some_and(|ext| ext == "c")
5779 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
5780 {
5781 return None;
5782 }
5783 let name = components.first()?;
5784 let mut matches: Vec<&CodeUnit> = Vec::new();
5785 let mut owner_len = 0;
5786 for candidate in self.visible_identifier_candidates(file, name) {
5787 if !candidate.is_class()
5788 || declared_type_alias(analyzer, candidate)
5789 || candidate.identifier() != name
5790 {
5791 continue;
5792 }
5793 let candidate_scope = canonical_cpp_scope_components(candidate);
5794 if candidate_scope.len() > lexical_scope.len()
5795 || !lexical_scope.starts_with(&candidate_scope)
5796 || candidate_scope.last().is_none_or(|last| last != name)
5797 {
5798 continue;
5799 }
5800 if candidate_scope.len() > owner_len {
5801 owner_len = candidate_scope.len();
5802 matches.clear();
5803 }
5804 if candidate_scope.len() == owner_len
5805 && !matches
5806 .iter()
5807 .any(|existing| same_logical_symbol(existing, candidate))
5808 {
5809 matches.push(candidate);
5810 }
5811 }
5812 if matches.is_empty() {
5813 return None;
5814 }
5815 if owner_len >= lexical_scope.len() {
5823 return None;
5824 }
5825 let owner_components = lexical_scope[..owner_len].to_vec();
5826 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
5827 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
5828 Ok(unit) => LexicalTypeResolution::Resolved {
5829 unit,
5830 components: owner_components,
5831 candidates: matches.into_iter().cloned().collect(),
5832 },
5833 Err(failure) => failure.lexical_resolution(),
5834 };
5835 Some((owner_len, resolution))
5836 }
5837
5838 fn resolve_inherited_type_for_lexical_scope(
5839 &self,
5840 analyzer: &CppGraphSource<'_>,
5841 file: &ProjectFile,
5842 lexical_scope: &[String],
5843 name: &str,
5844 resolution: TypeCandidateResolution<'_>,
5845 ) -> LexicalTypeResolution {
5846 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5847 return LexicalTypeResolution::Missing;
5848 };
5849 let lexical_owner_name = lexical_scope.join("::");
5850 if lexical_owner_name.is_empty() {
5851 return LexicalTypeResolution::Missing;
5852 }
5853 let owner_candidates = self
5854 .type_candidates(file, &lexical_owner_name)
5855 .into_iter()
5856 .filter(|candidate| {
5857 canonical_cpp_name_matches(candidate, &lexical_owner_name)
5858 && !declared_type_alias(analyzer, candidate)
5859 })
5860 .collect::<Vec<_>>();
5861 if owner_candidates.is_empty() {
5862 return LexicalTypeResolution::Missing;
5863 }
5864 let physical_owner_candidates = owner_candidates
5869 .iter()
5870 .copied()
5871 .filter(|candidate| candidate.source() == file)
5872 .collect::<Vec<_>>();
5873 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
5874 owner_candidates
5875 } else {
5876 physical_owner_candidates
5877 };
5878 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
5879 return LexicalTypeResolution::Ambiguous;
5880 };
5881
5882 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
5883 let mut visited_owners = HashSet::default();
5884 while !frontier.is_empty() {
5885 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
5886 let mut next_frontier = Vec::new();
5887 for owner in frontier {
5888 if !visited_owners.insert(owner.fq_name()) {
5889 continue;
5890 }
5891 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
5892 let candidates = self
5893 .type_candidates(file, &qualified_name)
5894 .into_iter()
5895 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5896 .collect::<Vec<_>>();
5897 if candidates.is_empty() {
5898 for ancestor in hierarchy.get_direct_ancestors(&owner) {
5899 if !next_frontier
5900 .iter()
5901 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
5902 {
5903 next_frontier.push(ancestor);
5904 }
5905 }
5906 continue;
5907 }
5908 let candidates =
5909 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
5910 let unit =
5911 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5912 Ok(unit) => unit,
5913 Err(failure) => return failure.lexical_resolution(),
5914 };
5915 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
5916 }
5917 if let Some((unit, candidates)) = level_matches.first().cloned() {
5918 let Some(first_declaration) = candidates.first() else {
5919 return LexicalTypeResolution::Ambiguous;
5920 };
5921 if !level_matches.iter().all(|(_, declarations)| {
5922 declarations
5923 .iter()
5924 .all(|declaration| same_logical_symbol(first_declaration, declaration))
5925 }) {
5926 return LexicalTypeResolution::Ambiguous;
5927 }
5928 let mut components = lexical_scope.to_vec();
5929 components.push(name.to_string());
5930 return LexicalTypeResolution::Resolved {
5931 unit,
5932 components,
5933 candidates,
5934 };
5935 }
5936 frontier = next_frontier;
5937 }
5938 LexicalTypeResolution::Missing
5939 }
5940
5941 pub fn inherited_injected_class_owner(
5958 &self,
5959 analyzer: &CppGraphSource<'_>,
5960 file: &ProjectFile,
5961 enclosing_owner: &CodeUnit,
5962 injected_name: &str,
5963 ) -> Option<CodeUnit> {
5964 let hierarchy = analyzer.type_hierarchy_provider()?;
5965 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
5966 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5967 while !frontier.is_empty() {
5968 let mut level_matches = Vec::new();
5969 let mut next_frontier = Vec::new();
5970 for raw_owner in frontier {
5971 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
5972 else {
5973 if raw_owner.identifier() == injected_name {
5974 return None;
5975 }
5976 continue;
5977 };
5978 let propagated = propagated_counts.entry(owner.clone()).or_default();
5979 if *propagated == 2 {
5980 continue;
5981 }
5982 *propagated += 1;
5983 if owner.identifier() == injected_name {
5984 level_matches.push(owner.clone());
5985 }
5986 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
5987 }
5988 match level_matches.as_slice() {
5989 [owner] => return Some(owner.clone()),
5990 [_, ..] => return None,
5991 [] => {}
5992 }
5993 frontier = next_frontier;
5994 }
5995 None
5996 }
5997
5998 fn resolve_type_candidates(
6003 &self,
6004 analyzer: &CppGraphSource<'_>,
6005 file: &ProjectFile,
6006 candidates: &[&CodeUnit],
6007 resolution: TypeCandidateResolution<'_>,
6008 ) -> Result<CodeUnit, TypeCandidateFailure> {
6009 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6010 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6011 {
6012 return Ok(unit);
6013 }
6014 match resolution {
6015 TypeCandidateResolution::Canonical => {
6016 self.canonical_type_candidate_resolution(analyzer, file, candidates)
6017 }
6018 TypeCandidateResolution::PreserveAlias => {
6019 let same_fqn_alias_family = candidates.len() > 1
6026 && candidates.iter().all(|candidate| {
6027 declared_type_alias(analyzer, candidate)
6028 && same_logical_symbol(candidates[0], candidate)
6029 })
6030 && candidates
6031 .iter()
6032 .any(|candidate| candidate.source() != candidates[0].source());
6033 if same_fqn_alias_family {
6034 let physically_visible = candidates
6035 .iter()
6036 .copied()
6037 .filter(|candidate| self.is_physically_visible(file, candidate))
6038 .collect::<Vec<_>>();
6039 let one_structured_target = physically_visible.len() > 1
6049 && physically_visible.iter().skip(1).all(|candidate| {
6050 let target = self.structured_alias_target(analyzer, candidate);
6051 target.is_some()
6052 && target
6053 == self.structured_alias_target(analyzer, physically_visible[0])
6054 });
6055 if physically_visible.len() == 1 || one_structured_target {
6056 return Ok(physically_visible[0].clone());
6057 }
6058 }
6059 unique_type_candidate_preserving_alias(analyzer, candidates)
6060 .ok_or(TypeCandidateFailure::Ambiguous)
6061 }
6062 TypeCandidateResolution::PreserveTarget(target) => self
6063 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6064 .ok_or(TypeCandidateFailure::Ambiguous),
6065 }
6066 }
6067
6068 fn candidates_for_type_resolution<'b>(
6069 &self,
6070 analyzer: &CppGraphSource<'_>,
6071 file: &ProjectFile,
6072 candidates: &[&'b CodeUnit],
6073 resolution: TypeCandidateResolution<'_>,
6074 ) -> Vec<&'b CodeUnit> {
6075 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6076 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6077 if compile_proven.len() == 1 {
6078 return compile_proven;
6079 }
6080 }
6081 candidates.to_vec()
6082 }
6083
6084 fn compile_proven_type_candidates<'b>(
6091 &self,
6092 analyzer: &CppGraphSource<'_>,
6093 file: &ProjectFile,
6094 candidates: &[&'b CodeUnit],
6095 ) -> Vec<&'b CodeUnit> {
6096 let proven = self.compile_proven_guards(file);
6097 if proven.is_empty() {
6098 return Vec::new();
6099 }
6100 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6101 return Vec::new();
6102 };
6103 candidates
6104 .iter()
6105 .copied()
6106 .filter(|candidate| {
6107 let declaration_guards =
6108 declaration_guard_requirements(analyzer, self.cpp, candidate);
6109 if declaration_guards.is_empty() {
6110 return false;
6111 }
6112 if candidate.source() == file {
6113 return declaration_guards.iter().any(|(_, required)| {
6114 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6115 });
6116 }
6117 declaration_guards.iter().any(|(_, required)| {
6118 self.foreign_declaration_reachable_from_compile_proven_guards(
6119 file,
6120 prepared.as_ref(),
6121 candidate.source(),
6122 required,
6123 usize::MAX,
6124 )
6125 })
6126 })
6127 .collect()
6128 }
6129
6130 pub fn resolve_callable_value_components_lexically(
6131 &self,
6132 analyzer: &CppGraphSource<'_>,
6133 file: &ProjectFile,
6134 owner_components: &[String],
6135 member_name: &str,
6136 global: bool,
6137 lexical_scope: &[String],
6138 ) -> LexicalCallableValueResolution {
6139 if owner_components.is_empty() || member_name.is_empty() {
6140 return LexicalCallableValueResolution::Missing;
6141 }
6142 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
6143 let owner_name = qualified_owner.join("::");
6144 let type_candidates = self
6145 .type_candidates(file, &owner_name)
6146 .into_iter()
6147 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
6148 .collect::<Vec<_>>();
6149 let resolved_type = if type_candidates.is_empty() {
6150 None
6151 } else {
6152 let Some(unit) =
6153 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
6154 else {
6155 return LexicalCallableValueResolution::Ambiguous;
6156 };
6157 Some(unit)
6158 };
6159
6160 let mut qualified_callable = qualified_owner;
6161 qualified_callable.push(member_name.to_string());
6162 let callable_name = qualified_callable.join("::");
6163 let free_function = self
6164 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
6165 .into_iter()
6166 .find(|candidate| {
6167 canonical_cpp_name_matches(candidate, &callable_name)
6168 && type_owner_of(analyzer, candidate).is_none()
6169 })
6170 .cloned();
6171
6172 match (resolved_type, free_function) {
6173 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
6174 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
6175 (None, Some(function)) => {
6176 return LexicalCallableValueResolution::FreeFunction(function);
6177 }
6178 (None, None) => {}
6179 }
6180 }
6181 LexicalCallableValueResolution::Missing
6182 }
6183
6184 fn resolve_type_for_declaration(
6185 &self,
6186 visible_from: &ProjectFile,
6187 declaration: &CodeUnit,
6188 raw_name: &str,
6189 ) -> Option<CodeUnit> {
6190 let normalized = normalize_reference_name(raw_name)?;
6191 if !normalized.contains("::")
6192 && let Some(namespace) = cpp_namespace_for(declaration)
6193 {
6194 for prefix in namespace_prefixes(&namespace) {
6195 let qualified = format!("{prefix}::{normalized}");
6196 if let Some(unit) = self
6197 .type_candidates(visible_from, &qualified)
6198 .into_iter()
6199 .next()
6200 {
6201 return Some(unit.clone());
6202 }
6203 }
6204 }
6205 self.resolve_type(visible_from, raw_name)
6206 }
6207
6208 fn resolve_unique_canonical_type_for_declaration(
6209 &self,
6210 analyzer: &CppGraphSource<'_>,
6211 visible_from: &ProjectFile,
6212 declaration: &CodeUnit,
6213 raw_name: &str,
6214 ) -> Option<CodeUnit> {
6215 let mut current =
6216 self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
6217 let mut seen_aliases = HashSet::default();
6218 loop {
6219 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
6220 return current.is_class().then_some(current);
6221 };
6222 if matches!(target, StructuredAliasTarget::Builtin) {
6223 return current.is_class().then_some(current);
6224 }
6225 if !seen_aliases.insert(current.clone()) {
6226 return None;
6227 }
6228 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
6229 }
6230 }
6231
6232 pub fn canonical_type_unit(
6233 &self,
6234 analyzer: &CppGraphSource<'_>,
6235 visible_from: &ProjectFile,
6236 unit: &CodeUnit,
6237 ) -> Option<CodeUnit> {
6238 self.canonical_type_resolution(analyzer, visible_from, unit)
6239 .ok()
6240 }
6241
6242 pub fn canonical_type_unit_in_context(
6250 &self,
6251 analyzer: &CppGraphSource<'_>,
6252 visible_from: &ProjectFile,
6253 reference: Node<'_>,
6254 unit: &CodeUnit,
6255 ) -> Option<CodeUnit> {
6256 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
6257 {
6258 return None;
6259 }
6260 self.canonical_type_resolution(analyzer, visible_from, unit)
6261 .ok()
6262 }
6263
6264 fn canonical_type_resolution(
6272 &self,
6273 analyzer: &CppGraphSource<'_>,
6274 visible_from: &ProjectFile,
6275 unit: &CodeUnit,
6276 ) -> Result<CodeUnit, TypeCandidateFailure> {
6277 let mut current = unit.clone();
6278 let mut seen_aliases = HashSet::default();
6279 loop {
6280 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
6281 return current
6282 .is_class()
6283 .then_some(current)
6284 .ok_or(TypeCandidateFailure::Unresolvable);
6285 };
6286 if matches!(target, StructuredAliasTarget::Builtin) {
6287 return current
6288 .is_class()
6289 .then_some(current)
6290 .ok_or(TypeCandidateFailure::Unresolvable);
6291 }
6292 if !seen_aliases.insert(current.clone()) {
6293 return Err(TypeCandidateFailure::Unresolvable);
6294 }
6295 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
6296 }
6297 }
6298
6299 pub fn canonical_visible_full_type_unit(
6300 &self,
6301 analyzer: &CppGraphSource<'_>,
6302 visible_from: &ProjectFile,
6303 unit: &CodeUnit,
6304 ) -> Option<CodeUnit> {
6305 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
6306 if cpp_class_declaration_strength(analyzer, &canonical)
6307 != CppClassDeclarationStrength::Forward
6308 {
6309 return Some(canonical);
6310 }
6311 let mut full = Vec::new();
6312 for candidate in self
6313 .visible_identifier_candidates(visible_from, canonical.identifier())
6314 .filter(|candidate| {
6315 candidate.is_class()
6316 && candidate.fq_name() == canonical.fq_name()
6317 && cpp_class_declaration_strength(analyzer, candidate)
6318 == CppClassDeclarationStrength::Full
6319 })
6320 {
6321 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
6322 full.push(candidate.clone());
6323 }
6324 }
6325 match full.len() {
6326 0 => Some(canonical),
6327 1 => full.pop(),
6328 _ => None,
6329 }
6330 }
6331
6332 fn resolve_structured_alias_target(
6333 &self,
6334 visible_from: &ProjectFile,
6335 declaration: &CodeUnit,
6336 target: &StructuredAliasTarget,
6337 ) -> Option<CodeUnit> {
6338 self.structured_alias_target_resolution(visible_from, declaration, target)
6339 .ok()
6340 }
6341
6342 fn structured_alias_target_resolution(
6343 &self,
6344 visible_from: &ProjectFile,
6345 declaration: &CodeUnit,
6346 target: &StructuredAliasTarget,
6347 ) -> Result<CodeUnit, TypeCandidateFailure> {
6348 let primary =
6349 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
6350 let StructuredAliasTarget::Named { arguments, .. } = target else {
6351 return Err(TypeCandidateFailure::Unresolvable);
6352 };
6353 match arguments {
6354 Some(arguments) => self
6355 .resolve_template_arguments(visible_from, primary, arguments)
6356 .map_err(|error| match error {
6357 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
6358 TypeCandidateFailure::Ambiguous
6359 }
6360 _ => TypeCandidateFailure::Unresolvable,
6361 }),
6362 None => Ok(primary),
6363 }
6364 }
6365
6366 fn resolve_structured_alias_primary(
6367 &self,
6368 visible_from: &ProjectFile,
6369 declaration: &CodeUnit,
6370 target: &StructuredAliasTarget,
6371 ) -> Option<CodeUnit> {
6372 self.structured_alias_primary_resolution(visible_from, declaration, target)
6373 .ok()
6374 }
6375
6376 fn structured_alias_primary_resolution(
6377 &self,
6378 visible_from: &ProjectFile,
6379 declaration: &CodeUnit,
6380 target: &StructuredAliasTarget,
6381 ) -> Result<CodeUnit, TypeCandidateFailure> {
6382 let StructuredAliasTarget::Named {
6383 components, global, ..
6384 } = target
6385 else {
6386 return Err(TypeCandidateFailure::Unresolvable);
6387 };
6388 let qualified = components.join("::");
6389 let candidates = if *global {
6390 let mut candidates = self.type_candidates(visible_from, &qualified);
6397 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
6398 candidates
6399 } else {
6400 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
6401 };
6402 logical_type_candidate(candidates)
6403 }
6404
6405 pub fn structured_alias_primary_preserves_target(
6406 &self,
6407 analyzer: &CppGraphSource<'_>,
6408 visible_from: &ProjectFile,
6409 candidate: &CodeUnit,
6410 target: &CodeUnit,
6411 ) -> bool {
6412 let mut current = candidate.clone();
6413 let mut seen = HashSet::default();
6414 let mut matched_target = false;
6415 loop {
6416 if same_visible_symbol(¤t, target)
6417 || self.compatible_primary_template_redeclarations(¤t, target)
6418 {
6419 matched_target = true;
6420 }
6421 if !seen.insert(current.clone()) {
6422 return false;
6423 }
6424 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
6425 return matched_target;
6426 };
6427 if matches!(alias_target, StructuredAliasTarget::Builtin) {
6428 return matched_target;
6429 };
6430 let Some(primary) =
6431 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
6432 else {
6433 return matched_target;
6439 };
6440 current = primary;
6441 }
6442 }
6443
6444 pub fn structured_class_alias_resolves_to_target(
6445 &self,
6446 analyzer: &CppGraphSource<'_>,
6447 visible_from: &ProjectFile,
6448 alias: &CodeUnit,
6449 target: &CodeUnit,
6450 ) -> bool {
6451 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
6452 return false;
6453 };
6454 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
6455 return false;
6456 };
6457 let StructuredAliasTarget::Named {
6458 components, global, ..
6459 } = &alias_target
6460 else {
6461 return false;
6462 };
6463 let lexical_scope = canonical_cpp_scope_components(&owner);
6464 match self.resolve_type_components_lexically_for_target(
6465 analyzer,
6466 visible_from,
6467 components,
6468 *global,
6469 &lexical_scope,
6470 target,
6471 ) {
6472 LexicalTypeResolution::Resolved {
6473 unit, candidates, ..
6474 } => {
6475 same_visible_symbol(&unit, target)
6476 || self.same_template_member_identity(analyzer, &unit, target)
6477 || candidates.iter().any(|candidate| {
6478 same_visible_symbol(candidate, target)
6479 || self.same_template_member_identity(analyzer, candidate, target)
6480 })
6481 }
6482 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
6483 self.structured_alias_primary_preserves_target(
6484 analyzer,
6485 visible_from,
6486 alias,
6487 target,
6488 ) || self.flattened_macro_namespace_alias_target_matches(
6489 analyzer,
6490 visible_from,
6491 alias,
6492 &alias_target,
6493 target,
6494 )
6495 }
6496 }
6497 }
6498
6499 pub fn structured_class_alias_path_preserves_target(
6507 &self,
6508 analyzer: &CppGraphSource<'_>,
6509 visible_from: &ProjectFile,
6510 alias: &CodeUnit,
6511 target: &CodeUnit,
6512 ) -> bool {
6513 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
6514 return false;
6515 };
6516 let Some(StructuredAliasTarget::Named {
6517 components, global, ..
6518 }) = self.structured_alias_target(analyzer, alias)
6519 else {
6520 return false;
6521 };
6522 let lexical_scope = canonical_cpp_scope_components(&owner);
6523 (1..components.len()).rev().any(|component_count| {
6524 matches!(
6525 self.resolve_type_components_lexically_for_target(
6526 analyzer,
6527 visible_from,
6528 &components[..component_count],
6529 global,
6530 &lexical_scope,
6531 target,
6532 ),
6533 LexicalTypeResolution::Resolved {
6534 ref unit,
6535 ref candidates,
6536 ..
6537 } if same_visible_symbol(unit, target)
6538 || self.same_template_member_identity(analyzer, unit, target)
6539 || candidates.iter().any(|candidate| {
6540 same_visible_symbol(candidate, target)
6541 || self.same_template_member_identity(analyzer, candidate, target)
6542 })
6543 )
6544 })
6545 }
6546
6547 fn flattened_macro_namespace_alias_target_matches(
6548 &self,
6549 analyzer: &CppGraphSource<'_>,
6550 visible_from: &ProjectFile,
6551 alias: &CodeUnit,
6552 alias_target: &StructuredAliasTarget,
6553 target: &CodeUnit,
6554 ) -> bool {
6555 let StructuredAliasTarget::Named {
6556 components,
6557 global: false,
6558 arguments: None,
6559 } = alias_target
6560 else {
6561 return false;
6562 };
6563 let Some((target_name, namespace_components)) = components.split_last() else {
6564 return false;
6565 };
6566 if namespace_components.is_empty()
6567 || target_name != target.identifier()
6568 || alias.source() != target.source()
6569 || alias.source() != visible_from
6570 || !target.is_class()
6571 || declared_type_alias(analyzer, target)
6572 {
6573 return false;
6574 }
6575 if self
6576 .resolve_structured_alias_target(visible_from, alias, alias_target)
6577 .is_some()
6578 {
6579 return false;
6580 }
6581
6582 let alias_ranges = analyzer.ranges(alias);
6583 let target_ranges = analyzer.ranges(target);
6584 if alias_ranges.is_empty() || target_ranges.is_empty() {
6585 return false;
6586 }
6587 let alias_start = alias_ranges
6588 .iter()
6589 .map(|range| range.start_byte)
6590 .min()
6591 .expect("non-empty alias ranges have a minimum");
6592 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
6593 return false;
6594 };
6595 let root = prepared.tree().root_node();
6596 let has_matching_declaration = target_ranges
6597 .iter()
6598 .filter(|range| range.end_byte <= alias_start)
6599 .filter_map(|range| node_for_exact_range(root, range))
6600 .any(|node| {
6601 flattened_macro_namespace_components(node, prepared.source())
6602 .is_some_and(|recovered| recovered == namespace_components)
6603 });
6604 if !has_matching_declaration {
6605 return false;
6606 }
6607
6608 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
6609 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
6610 guard_requirement_sets_match(&alias_guards, &target_guards)
6611 }
6612
6613 pub fn template_alias_arguments_preserve_target(
6614 &self,
6615 analyzer: &CppGraphSource<'_>,
6616 visible_from: &ProjectFile,
6617 alias: &CodeUnit,
6618 arguments: &[CppTemplateExpression],
6619 target: &CodeUnit,
6620 ) -> bool {
6621 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
6622 return false;
6623 };
6624 if metadata.alias_target.is_none()
6625 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
6626 {
6627 return false;
6628 }
6629 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
6630 }
6631
6632 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
6633 self.cpp_template_metadata
6634 .get(unit)
6635 .is_some_and(CppTemplateMetadata::is_primary)
6636 }
6637
6638 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
6639 self.cpp_template_metadata
6640 .get(unit)
6641 .is_some_and(CppTemplateMetadata::is_specialization)
6642 }
6643
6644 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
6645 same_visible_symbol(left, right)
6646 || self.compatible_primary_template_redeclarations(left, right)
6647 }
6648
6649 pub fn same_template_member_identity(
6650 &self,
6651 analyzer: &CppGraphSource<'_>,
6652 left: &CodeUnit,
6653 right: &CodeUnit,
6654 ) -> bool {
6655 if same_visible_symbol(left, right) {
6656 return true;
6657 }
6658 if left.kind() != right.kind()
6659 || left.identifier() != right.identifier()
6660 || left.signature() != right.signature()
6661 {
6662 return false;
6663 }
6664 let (Some(left_owner), Some(right_owner)) =
6665 (analyzer.parent_of(left), analyzer.parent_of(right))
6666 else {
6667 return false;
6668 };
6669 left_owner.is_class()
6670 && right_owner.is_class()
6671 && self.same_template_owner_identity(&left_owner, &right_owner)
6672 }
6673
6674 fn unique_canonical_type_candidate(
6675 &self,
6676 analyzer: &CppGraphSource<'_>,
6677 visible_from: &ProjectFile,
6678 candidates: &[&CodeUnit],
6679 ) -> Option<CodeUnit> {
6680 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
6681 .ok()
6682 }
6683
6684 fn canonical_type_candidate_resolution(
6685 &self,
6686 analyzer: &CppGraphSource<'_>,
6687 visible_from: &ProjectFile,
6688 candidates: &[&CodeUnit],
6689 ) -> Result<CodeUnit, TypeCandidateFailure> {
6690 let mut canonical = Vec::new();
6691 for candidate in candidates {
6692 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
6693 if canonical
6694 .iter()
6695 .any(|existing| same_visible_symbol(existing, &resolved))
6696 {
6697 continue;
6698 }
6699 if let Some(existing) = canonical.iter_mut().find(|existing| {
6700 self.compatible_primary_template_redeclarations(existing, &resolved)
6701 }) {
6702 if matches!(
6711 (
6712 cpp_class_declaration_strength(analyzer, existing),
6713 cpp_class_declaration_strength(analyzer, &resolved),
6714 ),
6715 (
6716 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
6717 CppClassDeclarationStrength::Full,
6718 ) | (
6719 CppClassDeclarationStrength::Unknown,
6720 CppClassDeclarationStrength::Forward,
6721 )
6722 ) {
6723 *existing = resolved;
6724 }
6725 continue;
6726 }
6727 canonical.push(resolved);
6728 if canonical.len() > 1 {
6729 return Err(TypeCandidateFailure::Ambiguous);
6730 }
6731 }
6732 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
6733 }
6734
6735 pub fn unique_type_candidate_preserving_target(
6736 &self,
6737 analyzer: &CppGraphSource<'_>,
6738 visible_from: &ProjectFile,
6739 candidates: &[&CodeUnit],
6740 target: &CodeUnit,
6741 ) -> Option<CodeUnit> {
6742 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
6753 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
6754 {
6755 return Some(target.clone());
6756 }
6757 let mut resolved_candidates = Vec::new();
6758 for candidate in candidates {
6759 let Some(resolved) =
6765 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
6766 else {
6767 continue;
6768 };
6769 if resolved_candidates
6770 .iter()
6771 .any(|existing| same_visible_symbol(existing, &resolved))
6772 {
6773 continue;
6774 }
6775 resolved_candidates.push(resolved);
6776 }
6777 match resolved_candidates.as_slice() {
6778 [] => None,
6779 [single] => Some(single.clone()),
6780 _ => self
6785 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
6786 .map(|_| target.clone()),
6787 }
6788 }
6789
6790 pub fn same_fqn_type_spelling_for_target<'b>(
6807 &self,
6808 analyzer: &CppGraphSource<'_>,
6809 visible_from: &ProjectFile,
6810 candidates: &[&'b CodeUnit],
6811 target: &CodeUnit,
6812 ) -> Option<&'b CodeUnit> {
6813 let [first, rest @ ..] = candidates else {
6814 return None;
6815 };
6816 if rest.is_empty()
6817 || !rest.iter().all(|candidate| {
6818 candidate.kind() == first.kind()
6819 && candidate.fq_name() == first.fq_name()
6820 && candidate.source() == first.source()
6821 })
6822 {
6823 return None;
6824 }
6825 candidates
6826 .iter()
6827 .copied()
6828 .find(|candidate| same_symbol(candidate, target))
6829 .or_else(|| {
6830 candidates.iter().copied().find(|candidate| {
6831 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
6832 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
6833 })
6834 })
6835 }
6836
6837 pub fn alternate_same_fqn_type_declarations(
6838 &self,
6839 analyzer: &CppGraphSource<'_>,
6840 candidates: &[&CodeUnit],
6841 target: &CodeUnit,
6842 ) -> bool {
6843 let Some(first) = candidates.first() else {
6844 return false;
6845 };
6846 let same_api = first.kind() == target.kind()
6847 && first.fq_name() == target.fq_name()
6848 && first.source() == target.source()
6849 && candidates.iter().all(|candidate| {
6850 candidate.kind() == target.kind()
6851 && candidate.fq_name() == target.fq_name()
6852 && candidate.source() == target.source()
6853 })
6854 && candidates
6855 .iter()
6856 .any(|candidate| same_symbol(candidate, target))
6857 && candidates
6858 .iter()
6859 .any(|candidate| !same_logical_symbol(candidate, target));
6860 if !same_api {
6861 return false;
6862 }
6863
6864 let requirements = candidates
6865 .iter()
6866 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
6867 .collect::<Vec<_>>();
6868 requirements.len() > 1
6869 && requirements
6870 .iter()
6871 .all(|requirement| !requirement.is_empty())
6872 && requirements.iter().enumerate().all(|(index, left)| {
6873 requirements[index + 1..].iter().all(|right| {
6874 left.iter().all(|(_, left_guards)| {
6875 right.iter().all(|(_, right_guards)| {
6876 merge_preprocessor_guards(left_guards, right_guards).is_none()
6877 })
6878 })
6879 })
6880 })
6881 }
6882
6883 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
6884 let mut pending = vec![terms.to_vec()];
6885 while let Some(branch_terms) = pending.pop() {
6886 let mut normalized = Vec::new();
6887 let mut covers_branch = false;
6888 for term in branch_terms {
6889 if term.iter().any(|guard| term.contains(&guard.negated())) {
6890 continue;
6891 }
6892 if term.is_empty() {
6893 covers_branch = true;
6894 break;
6895 }
6896 if !normalized.iter().any(|existing| existing == &term) {
6897 normalized.push(term);
6898 }
6899 }
6900 if covers_branch {
6901 continue;
6902 }
6903 let Some(split_guard) = normalized
6904 .iter()
6905 .flat_map(|term| term.iter())
6906 .next()
6907 .cloned()
6908 else {
6909 return false;
6910 };
6911 let negated_guard = split_guard.negated();
6912 let mut when_defined = Vec::new();
6913 let mut when_undefined = Vec::new();
6914 for term in normalized {
6915 if term.contains(&negated_guard) {
6916 } else if term.contains(&split_guard) {
6918 let mut reduced = term.clone();
6919 reduced.remove(&split_guard);
6920 when_defined.push(reduced);
6921 } else {
6922 when_defined.push(term.clone());
6923 }
6924 if term.contains(&split_guard) {
6925 } else if term.contains(&negated_guard) {
6927 let mut reduced = term;
6928 reduced.remove(&negated_guard);
6929 when_undefined.push(reduced);
6930 } else {
6931 when_undefined.push(term);
6932 }
6933 }
6934 pending.push(when_defined);
6935 pending.push(when_undefined);
6936 }
6937 true
6938 }
6939
6940 fn declarations_share_exhaustive_conditional_family(
6949 &self,
6950 analyzer: &CppGraphSource<'_>,
6951 candidates: &[&CodeUnit],
6952 ) -> Option<(usize, usize)> {
6953 let mut family_range = None;
6954 for candidate in candidates {
6955 let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
6956 let root = prepared.tree().root_node();
6957 let mut candidate_family = None;
6958 for range in analyzer.ranges(candidate) {
6959 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
6960 let family = preprocessor_conditional_family_for_declaration(node)?;
6961 let key = (family.start_byte(), family.end_byte());
6962 if candidate_family.is_some_and(|existing| existing != key) {
6963 return None;
6964 }
6965 candidate_family = Some(key);
6966 }
6967 let candidate_family = candidate_family?;
6968 if family_range.is_some_and(|existing| existing != candidate_family) {
6969 return None;
6970 }
6971 family_range = Some(candidate_family);
6972 }
6973 family_range
6974 }
6975
6976 pub fn complementary_same_fqn_type_declarations(
6977 &self,
6978 analyzer: &CppGraphSource<'_>,
6979 candidates: &[&CodeUnit],
6980 target: &CodeUnit,
6981 ) -> bool {
6982 if candidates.len() < 2
6983 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
6984 || self
6985 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
6986 .is_none()
6987 {
6988 return false;
6989 }
6990 Self::preprocessor_guard_terms_cover_all_paths(
6991 &self.declaration_family_guard_terms(analyzer, candidates),
6992 )
6993 }
6994
6995 fn declaration_family_guard_terms(
6996 &self,
6997 analyzer: &CppGraphSource<'_>,
6998 candidates: &[&CodeUnit],
6999 ) -> Vec<HashSet<PreprocessorGuard>> {
7000 candidates
7001 .iter()
7002 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7003 .map(|(_, guards)| guards)
7004 .collect()
7005 }
7006
7007 fn exhaustive_guard_family_activation(
7023 &self,
7024 analyzer: &CppGraphSource<'_>,
7025 prepared: &PreparedSyntaxTree,
7026 candidate: &CodeUnit,
7027 reference: &CallableReferenceContext<'_>,
7028 ) -> Option<usize> {
7029 if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
7032 return None;
7033 }
7034 let family = self
7035 .visible_identifier_candidates(candidate.source(), candidate.identifier())
7036 .filter(|peer| {
7037 peer.kind() == candidate.kind()
7038 && peer.fq_name() == candidate.fq_name()
7039 && peer.source() == candidate.source()
7040 })
7041 .collect::<Vec<_>>();
7042 let (_, family_end) =
7043 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7044 if !Self::preprocessor_guard_terms_cover_all_paths(
7045 &self.declaration_family_guard_terms(analyzer, &family),
7046 ) {
7047 return None;
7048 }
7049 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7053 .iter()
7054 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7055 {
7056 return None;
7057 }
7058 (first_declaration_byte(analyzer, candidate)?
7059 == family
7060 .iter()
7061 .filter_map(|peer| first_declaration_byte(analyzer, peer))
7062 .min()?)
7063 .then_some(family_end)
7064 }
7065
7066 fn type_candidate_preserving_target(
7067 &self,
7068 analyzer: &CppGraphSource<'_>,
7069 visible_from: &ProjectFile,
7070 candidate: &CodeUnit,
7071 target: &CodeUnit,
7072 ) -> Option<CodeUnit> {
7073 let mut current = candidate.clone();
7074 let mut matched_target = same_visible_symbol(¤t, target)
7075 || self.compatible_primary_template_redeclarations(¤t, target);
7076 let mut seen = HashSet::default();
7077 loop {
7078 if !seen.insert(current.clone()) {
7079 return None;
7080 }
7081 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7082 return matched_target
7083 .then(|| target.clone())
7084 .or_else(|| current.is_class().then_some(current));
7085 };
7086 if self.flattened_macro_namespace_alias_target_matches(
7087 analyzer,
7088 visible_from,
7089 ¤t,
7090 &alias_target,
7091 target,
7092 ) {
7093 return Some(target.clone());
7094 }
7095 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7096 return matched_target
7097 .then(|| target.clone())
7098 .or_else(|| current.is_class().then_some(current));
7099 }
7100 if !self.cpp_template_metadata.contains_key(¤t)
7108 && let Some(primary) =
7109 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7110 && (same_visible_symbol(&primary, target)
7111 || self.compatible_primary_template_redeclarations(&primary, target))
7112 {
7113 return Some(target.clone());
7114 }
7115 if same_visible_symbol(¤t, target) {
7116 return Some(target.clone());
7117 }
7118 if self.cpp_template_metadata.contains_key(¤t) {
7119 return None;
7120 }
7121 let Some(next) =
7122 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
7123 else {
7124 return matched_target.then(|| target.clone());
7125 };
7126 current = next;
7127 matched_target |= same_visible_symbol(¤t, target)
7128 || self.compatible_primary_template_redeclarations(¤t, target);
7129 }
7130 }
7131
7132 fn compatible_primary_template_redeclarations(
7133 &self,
7134 left: &CodeUnit,
7135 right: &CodeUnit,
7136 ) -> bool {
7137 let (Some(left_metadata), Some(right_metadata)) = (
7138 self.cpp_template_metadata.get(left),
7139 self.cpp_template_metadata.get(right),
7140 ) else {
7141 return false;
7142 };
7143 left_metadata.primary_fq_name == right_metadata.primary_fq_name
7144 && left_metadata.is_primary()
7145 && right_metadata.is_primary()
7146 && cpp_reconcile_primary_template_parameters(
7147 &[(left, left_metadata), (right, right_metadata)],
7148 right,
7149 )
7150 .is_some()
7151 }
7152
7153 fn alias_candidate_may_preserve_target(
7154 &self,
7155 analyzer: &CppGraphSource<'_>,
7156 visible_from: &ProjectFile,
7157 candidate: &CodeUnit,
7158 target: &CodeUnit,
7159 ) -> bool {
7160 let mut current = candidate.clone();
7161 let mut seen = HashSet::default();
7162 loop {
7163 if same_visible_symbol(¤t, target)
7164 || self.compatible_primary_template_redeclarations(¤t, target)
7165 {
7166 return true;
7167 }
7168 if self.cpp_template_metadata.contains_key(¤t) {
7169 return true;
7170 }
7171 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7172 return false;
7173 };
7174 let StructuredAliasTarget::Named {
7175 components,
7176 global,
7177 arguments,
7178 } = alias_target
7179 else {
7180 return false;
7181 };
7182 if arguments.is_some() || !seen.insert(current.clone()) {
7183 return true;
7184 }
7185 let qualified = components.join("::");
7186 let next = if global {
7187 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
7188 } else {
7189 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
7190 };
7191 let Some(next) = next else {
7192 return true;
7193 };
7194 current = next;
7195 }
7196 }
7197
7198 fn type_candidates_for_declaration<'b>(
7202 &'b self,
7203 visible_from: &ProjectFile,
7204 declaration: &CodeUnit,
7205 raw_name: &str,
7206 ) -> Vec<&'b CodeUnit> {
7207 let Some(normalized) = normalize_reference_name(raw_name) else {
7208 return Vec::new();
7209 };
7210 if let Some(namespace) = cpp_namespace_for(declaration) {
7211 for prefix in namespace_prefixes(&namespace) {
7212 let qualified = format!("{prefix}::{normalized}");
7213 let candidates = self.type_candidates(visible_from, &qualified);
7214 if !candidates.is_empty() {
7215 return candidates;
7216 }
7217 }
7218 }
7219 self.type_candidates(visible_from, &normalized)
7220 }
7221
7222 fn resolve_unique_type_for_declaration(
7223 &self,
7224 visible_from: &ProjectFile,
7225 declaration: &CodeUnit,
7226 raw_name: &str,
7227 ) -> Option<CodeUnit> {
7228 unique_logical_type_candidate(self.type_candidates_for_declaration(
7229 visible_from,
7230 declaration,
7231 raw_name,
7232 ))
7233 }
7234
7235 pub fn resolves_to_type(
7236 &self,
7237 analyzer: &CppGraphSource<'_>,
7238 file: &ProjectFile,
7239 raw_name: &str,
7240 target: &CodeUnit,
7241 ) -> bool {
7242 let Some(normalized) = normalize_reference_name(raw_name) else {
7243 return false;
7244 };
7245 let candidates = self.type_candidates(file, &normalized);
7246 if candidates.is_empty() {
7247 return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
7248 }
7249 let Some(resolved) =
7250 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
7251 else {
7252 return false;
7253 };
7254 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
7255 }
7256
7257 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
7258 let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
7259 let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
7260 match resolved.kind() {
7261 CodeUnitType::Class => Some(resolved),
7262 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
7263 _ => None,
7264 }
7265 }
7266
7267 pub fn same_logical_callable(
7282 &self,
7283 analyzer: &CppGraphSource<'_>,
7284 left: &CodeUnit,
7285 right: &CodeUnit,
7286 ) -> bool {
7287 if same_logical_symbol(left, right) {
7288 return true;
7289 }
7290 if left.kind() != right.kind()
7291 || !left.is_callable()
7292 || !right.is_callable()
7293 || left.fq_name() != right.fq_name()
7294 {
7295 return false;
7296 }
7297 if self.callable_is_template_declaration(analyzer, left)
7303 || self.callable_is_template_declaration(analyzer, right)
7304 {
7305 return false;
7306 }
7307 let (Some(left_comparable), Some(right_comparable)) = (
7308 self.callable_comparable(analyzer, left),
7309 self.callable_comparable(analyzer, right),
7310 ) else {
7311 return false;
7312 };
7313 if left_comparable.suffix != right_comparable.suffix
7318 || left_comparable.shapes.len() != right_comparable.shapes.len()
7319 {
7320 return false;
7321 }
7322 left_comparable
7323 .shapes
7324 .iter()
7325 .zip(right_comparable.shapes.iter())
7326 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
7327 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
7328 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
7329 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
7330 }
7331 _ => false,
7335 })
7336 }
7337
7338 fn comparable_shapes_agree(
7344 &self,
7345 analyzer: &CppGraphSource<'_>,
7346 left: &CppComparableParameter,
7347 right: &CppComparableParameter,
7348 ) -> bool {
7349 let mut stack = vec![(left.root(), right.root())];
7350 while let Some((left_index, right_index)) = stack.pop() {
7351 match (left.node(left_index), right.node(right_index)) {
7352 (
7353 CppComparableNode::Named {
7354 name: left_name,
7355 primitive: left_primitive,
7356 konst: left_konst,
7357 volatil: left_volatil,
7358 },
7359 CppComparableNode::Named {
7360 name: right_name,
7361 primitive: right_primitive,
7362 konst: right_konst,
7363 volatil: right_volatil,
7364 },
7365 ) => {
7366 if left_konst != right_konst
7367 || left_volatil != right_volatil
7368 || left_primitive != right_primitive
7369 || !self.comparable_names_agree(
7370 analyzer,
7371 left_name,
7372 right_name,
7373 *left_primitive,
7374 )
7375 {
7376 return false;
7377 }
7378 }
7379 (
7380 CppComparableNode::Pointer {
7381 inner: left_inner,
7382 konst: left_konst,
7383 volatil: left_volatil,
7384 },
7385 CppComparableNode::Pointer {
7386 inner: right_inner,
7387 konst: right_konst,
7388 volatil: right_volatil,
7389 },
7390 ) => {
7391 if left_konst != right_konst || left_volatil != right_volatil {
7392 return false;
7393 }
7394 stack.push((*left_inner, *right_inner));
7395 }
7396 (
7397 CppComparableNode::Reference { inner: left_inner },
7398 CppComparableNode::Reference { inner: right_inner },
7399 )
7400 | (
7401 CppComparableNode::Array { inner: left_inner },
7402 CppComparableNode::Array { inner: right_inner },
7403 ) => stack.push((*left_inner, *right_inner)),
7404 (
7405 CppComparableNode::Generic {
7406 base: left_base,
7407 arguments: left_arguments,
7408 },
7409 CppComparableNode::Generic {
7410 base: right_base,
7411 arguments: right_arguments,
7412 },
7413 ) => {
7414 if left_arguments.len() != right_arguments.len() {
7415 return false;
7416 }
7417 stack.push((*left_base, *right_base));
7418 stack.extend(
7419 left_arguments.iter().zip(right_arguments.iter()).map(
7420 |(left_argument, right_argument)| (*left_argument, *right_argument),
7421 ),
7422 );
7423 }
7424 _ => return false,
7425 }
7426 }
7427 true
7428 }
7429
7430 fn comparable_names_agree(
7440 &self,
7441 analyzer: &CppGraphSource<'_>,
7442 left: &StructuredTypeName,
7443 right: &StructuredTypeName,
7444 primitive: bool,
7445 ) -> bool {
7446 if primitive {
7447 return left.path() == right.path();
7448 }
7449 match (
7450 self.comparable_name_terminal(analyzer, left),
7451 self.comparable_name_terminal(analyzer, right),
7452 ) {
7453 (Some(left_terminal), Some(right_terminal)) => {
7454 same_logical_symbol(&left_terminal, &right_terminal)
7455 }
7456 (None, None) => {
7457 left.path() == right.path() && left.is_absolute() == right.is_absolute()
7458 }
7459 _ => false,
7460 }
7461 }
7462
7463 fn comparable_name_terminal(
7474 &self,
7475 analyzer: &CppGraphSource<'_>,
7476 name: &StructuredTypeName,
7477 ) -> Option<CodeUnit> {
7478 let mut current = self.comparable_name_declaration(analyzer, name)?;
7479 let mut visited = HashSet::default();
7480 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
7481 if !declared_type_alias(analyzer, ¤t) {
7488 return current.is_class().then_some(current);
7489 }
7490 if !visited.insert(current.clone()) {
7491 return None;
7492 }
7493 let signature = current.signature()?;
7494 if cpp_alias_declaration_adds_indirection(signature) {
7499 return None;
7500 }
7501 let raw_target = cpp_alias_declaration_target_text(signature)?;
7502 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
7503 }
7504 None
7505 }
7506
7507 fn comparable_alias_target(
7518 &self,
7519 analyzer: &CppGraphSource<'_>,
7520 alias: &CodeUnit,
7521 raw_target: &str,
7522 ) -> Option<CodeUnit> {
7523 let absolute = raw_target.trim_start().starts_with("::");
7528 let normalized = normalize_reference_name(raw_target)?;
7529 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7530 brokk_bifrost_core::analyzer::Language::Cpp,
7531 &normalized,
7532 );
7533 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
7534 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7535 brokk_bifrost_core::analyzer::Language::Cpp,
7536 &namespace,
7537 )
7538 });
7539 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
7540 self.comparable_name_declaration(analyzer, &name)
7541 }
7542
7543 fn comparable_name_declaration(
7551 &self,
7552 analyzer: &CppGraphSource<'_>,
7553 name: &StructuredTypeName,
7554 ) -> Option<CodeUnit> {
7555 let definitions = analyzer.workspace_definitions();
7556 let interner = segment_interner();
7557 let first_depth = if name.is_absolute() {
7558 0
7559 } else {
7560 name.lexical_scope().len()
7561 };
7562 for depth in (0..=first_depth).rev() {
7563 let mut structured = FqName::new();
7564 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
7565 structured.push(interner.intern(component, SegmentKind::Unknown));
7566 }
7567 let mut candidates = definitions
7568 .identifier(&structured)
7569 .into_iter()
7570 .filter(|unit| unit.fq().same_segment_texts(&structured))
7571 .filter(|unit| {
7572 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
7573 });
7574 let Some(first) = candidates.next() else {
7575 continue;
7576 };
7577 return candidates
7578 .all(|unit| same_logical_symbol(&unit, &first))
7579 .then_some(first);
7580 }
7581 None
7582 }
7583
7584 fn callable_comparable(
7590 &self,
7591 analyzer: &CppGraphSource<'_>,
7592 unit: &CodeUnit,
7593 ) -> Option<Arc<ExtractedComparable>> {
7594 if let Some(cached) = self
7595 .callable_comparables
7596 .lock()
7597 .expect("C++ callable comparable cache poisoned")
7598 .get(unit)
7599 .cloned()
7600 {
7601 return cached;
7602 }
7603 let extracted = self
7604 .extract_callable_comparable(analyzer, unit)
7605 .map(Arc::new);
7606 self.callable_comparables
7607 .lock()
7608 .expect("C++ callable comparable cache poisoned")
7609 .insert(unit.clone(), extracted.clone());
7610 extracted
7611 }
7612
7613 fn extract_callable_comparable(
7614 &self,
7615 analyzer: &CppGraphSource<'_>,
7616 unit: &CodeUnit,
7617 ) -> Option<ExtractedComparable> {
7618 let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
7619 let root = prepared.tree().root_node();
7620 let declarator = analyzer
7621 .ranges(unit)
7622 .into_iter()
7623 .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
7624 Some(ExtractedComparable {
7625 shapes: cpp_comparable_parameter_shapes(
7628 declarator,
7629 prepared.source(),
7630 &ParentIndex::unindexed(),
7631 ),
7632 suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
7633 })
7634 }
7635
7636 pub fn canonical_type_for_reference(
7637 &self,
7638 file: &ProjectFile,
7639 raw_name: &str,
7640 ) -> Option<CodeUnit> {
7641 let resolved = self.resolve_type(file, raw_name)?;
7642 self.alias_target(&resolved).or(Some(resolved))
7643 }
7644
7645 pub fn parser_alias_resolves_to_type(
7646 &self,
7647 analyzer: &CppGraphSource<'_>,
7648 file: &ProjectFile,
7649 raw_name: &str,
7650 target: &CodeUnit,
7651 ) -> bool {
7652 let Some(alias_name) = normalize_reference_name(raw_name) else {
7653 return false;
7654 };
7655 let Some(cpp) = analyzer.cpp else {
7656 return false;
7657 };
7658 let matches_file = |source_file: &ProjectFile| {
7659 self.file_alias_matches(cpp, source_file, &alias_name, target)
7660 };
7661 self.visible_source_files_by_root.get(file).map_or_else(
7662 || matches_file(file),
7663 |files| files.iter().any(matches_file),
7664 )
7665 }
7666
7667 fn file_alias_matches(
7668 &self,
7669 cpp: &dyn CppSource,
7670 file: &ProjectFile,
7671 alias_name: &str,
7672 target: &CodeUnit,
7673 ) -> bool {
7674 let cell = {
7675 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
7676 Arc::clone(
7677 cells
7678 .entry(file.clone())
7679 .or_insert_with(|| Arc::new(OnceLock::new())),
7680 )
7681 };
7682 cell.get_or_init(|| {
7683 self.parser_alias_source_parses
7684 .fetch_add(1, Ordering::Relaxed);
7685 #[cfg(any(test, feature = "test-support"))]
7686 {
7687 *self
7688 .alias_source_parse_counts
7689 .lock()
7690 .expect("alias source parse count lock")
7691 .entry(file.clone())
7692 .or_default() += 1;
7693 }
7694 aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
7695 })
7696 .iter()
7697 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
7698 }
7699
7700 #[cfg(any(test, feature = "test-support"))]
7701 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
7702 self.visible_source_files_by_root
7703 .get(file)
7704 .cloned()
7705 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
7706 }
7707
7708 #[cfg(any(test, feature = "test-support"))]
7709 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
7710 self.alias_source_parse_counts
7711 .lock()
7712 .expect("alias source parse count lock")
7713 .get(file)
7714 .copied()
7715 .unwrap_or(0)
7716 }
7717
7718 pub fn resolve_named(
7719 &self,
7720 file: &ProjectFile,
7721 raw_name: &str,
7722 kind: TargetKind,
7723 ) -> Option<CodeUnit> {
7724 let normalized = normalize_reference_name(raw_name)?;
7725 self.named_candidates_for_normalized(file, &normalized, kind)
7726 .into_iter()
7727 .next()
7728 .cloned()
7729 }
7730
7731 pub fn contains_named_symbol(
7732 &self,
7733 file: &ProjectFile,
7734 raw_name: &str,
7735 kind: TargetKind,
7736 target: &CodeUnit,
7737 ) -> bool {
7738 let Some(normalized) = normalize_reference_name(raw_name) else {
7739 return false;
7740 };
7741 self.named_candidates_for_normalized(file, &normalized, kind)
7742 .into_iter()
7743 .any(|unit| {
7744 matches_kind_for_lookup(unit, kind)
7745 && reference_matches_unit(&normalized, unit)
7746 && same_visible_symbol(unit, target)
7747 })
7748 }
7749
7750 pub fn named_candidates(
7751 &self,
7752 file: &ProjectFile,
7753 raw_name: &str,
7754 kind: TargetKind,
7755 ) -> Vec<CodeUnit> {
7756 let Some(normalized) = normalize_reference_name(raw_name) else {
7757 return Vec::new();
7758 };
7759 self.named_candidates_for_normalized(file, &normalized, kind)
7760 .into_iter()
7761 .cloned()
7762 .collect()
7763 }
7764
7765 pub fn resolve_known_non_target(
7766 &self,
7767 file: &ProjectFile,
7768 raw_name: &str,
7769 kind: TargetKind,
7770 target: &CodeUnit,
7771 ) -> bool {
7772 let Some(normalized) = normalize_reference_name(raw_name) else {
7773 return false;
7774 };
7775 normalized.contains("::")
7776 && self
7777 .named_candidates_for_normalized(file, &normalized, kind)
7778 .into_iter()
7779 .any(|unit| {
7780 matches_kind_for_lookup(unit, kind)
7781 && reference_matches_unit(&normalized, unit)
7782 && !same_visible_symbol(unit, target)
7783 })
7784 }
7785
7786 pub fn resolve_call_return_binding(
7787 &self,
7788 analyzer: &CppGraphSource<'_>,
7789 file: &ProjectFile,
7790 raw_name: &str,
7791 arity: usize,
7792 lexical_namespace: Option<&str>,
7793 direct_type: Option<&CodeUnit>,
7794 ) -> Option<CppScanBinding> {
7795 let normalized = normalize_reference_name(raw_name)?;
7796 let mut candidates = Vec::new();
7797 for function in
7798 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
7799 {
7800 if cpp_callable_arity(analyzer, function).accepts(arity)
7801 && !direct_type.is_some_and(|direct_type| {
7802 self.callable_is_constructor_declaration(analyzer, function)
7803 && type_owner_of(analyzer, function)
7804 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
7805 })
7806 {
7807 candidates.push(function.clone());
7808 }
7809 }
7810 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
7811 unanimous_return_binding(analyzer, self, file, &candidates)
7812 }
7813
7814 pub fn resolve_call_return_binding_without_arity(
7815 &self,
7816 analyzer: &CppGraphSource<'_>,
7817 file: &ProjectFile,
7818 raw_name: &str,
7819 lexical_namespace: Option<&str>,
7820 direct_type: Option<&CodeUnit>,
7821 ) -> (bool, Option<CppScanBinding>) {
7822 let Some(normalized) = normalize_reference_name(raw_name) else {
7823 return (false, None);
7824 };
7825 let mut candidates = self
7826 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
7827 .into_iter()
7828 .filter(|function| {
7829 function.is_function()
7830 && !direct_type.is_some_and(|direct_type| {
7831 self.callable_is_constructor_declaration(analyzer, function)
7832 && type_owner_of(analyzer, function)
7833 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
7834 })
7835 })
7836 .cloned()
7837 .collect::<Vec<_>>();
7838 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
7839 let has_candidates = !candidates.is_empty();
7840 (
7841 has_candidates,
7842 unanimous_return_binding(analyzer, self, file, &candidates),
7843 )
7844 }
7845
7846 pub fn visible_identifier_candidates<'b>(
7847 &'b self,
7848 file: &ProjectFile,
7849 identifier: &str,
7850 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
7851 self.visible_by_identifier
7852 .get(file)
7853 .and_then(|by_name| by_name.get(identifier))
7854 .into_iter()
7855 .flatten()
7856 }
7857
7858 pub fn visible_type_reference_component_names_for_target(
7866 &self,
7867 analyzer: &CppGraphSource<'_>,
7868 file: &ProjectFile,
7869 target: &CodeUnit,
7870 ) -> HashSet<String> {
7871 let mut names = HashSet::from_iter([target.identifier().to_string()]);
7872 if let Some(metadata) = self.cpp_template_metadata.get(target) {
7873 names.insert(metadata.primary_name.clone());
7874 }
7875
7876 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
7877 for (identifier, candidates) in by_identifier {
7878 if candidates.iter().any(|candidate| {
7879 (candidate.is_class()
7880 && (same_visible_symbol(candidate, target)
7881 || self.compatible_primary_template_redeclarations(candidate, target)))
7882 || (declared_type_alias(analyzer, candidate)
7883 && self.alias_candidate_may_preserve_target(
7884 analyzer, file, candidate, target,
7885 ))
7886 }) {
7887 names.insert(identifier.clone());
7888 }
7889 }
7890 }
7891
7892 names
7893 }
7894
7895 pub fn indexed_structural_class_scope(
7896 &self,
7897 file: &ProjectFile,
7898 class: Node<'_>,
7899 source: &str,
7900 ) -> Option<Vec<String>> {
7901 let key = (file.clone(), class.start_byte(), class.end_byte());
7902 if let Some(cached) = self
7903 .indexed_structural_class_scopes
7904 .lock()
7905 .expect("C++ indexed structural-class scope cache poisoned")
7906 .get(&key)
7907 .cloned()
7908 {
7909 return cached;
7910 }
7911 let resolved = (|| {
7912 let name = class.child_by_field_name("name")?;
7913 let identifier = if name.kind() == "template_type" {
7914 node_text(name.child_by_field_name("name")?, source).to_string()
7915 } else {
7916 let mut components = Vec::new();
7917 append_cpp_name_components(name, source, &mut components)?;
7918 components.last()?.clone()
7919 };
7920 let visible = self
7921 .visible_identifier_candidates(file, &identifier)
7922 .cloned()
7923 .collect::<Vec<_>>();
7924 let mut visible = visible;
7925 for candidate in
7926 self.visible_by_file
7927 .get(file)
7928 .into_iter()
7929 .flatten()
7930 .filter(|candidate| {
7931 self.cpp_template_metadata
7932 .get(candidate)
7933 .is_some_and(|metadata| metadata.primary_name == identifier)
7934 })
7935 {
7936 if !visible
7937 .iter()
7938 .any(|existing| same_logical_symbol(existing, candidate))
7939 {
7940 visible.push(candidate.clone());
7941 }
7942 }
7943 let cpp_source = self.cpp_source();
7946 let candidates = visible
7947 .iter()
7948 .filter(|candidate| {
7949 candidate.source() == file
7950 && candidate.is_class()
7951 && !declared_type_alias(&cpp_source, candidate)
7952 && self.cpp.ranges(candidate).iter().any(|range| {
7953 range.start_byte <= class.start_byte()
7954 && class.end_byte() <= range.end_byte
7955 })
7956 })
7957 .collect::<Vec<_>>();
7958 let owner = if name.kind() == "template_type" {
7959 let expected = normalize_cpp_whitespace(node_text(name, source));
7960 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
7961 let exact = candidates
7962 .iter()
7963 .copied()
7964 .filter(|candidate| {
7965 candidate
7966 .fq()
7967 .segments()
7968 .iter()
7969 .rev()
7970 .find_map(|&segment| {
7971 let (text, kind) = interner.resolve(segment);
7972 matches!(
7973 kind,
7974 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
7975 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
7976 )
7977 .then_some(text)
7978 })
7979 .is_some_and(|text| text == expected)
7980 })
7981 .collect::<Vec<_>>();
7982 unique_logical_type_candidate(exact)
7983 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
7984 } else {
7985 unique_logical_type_candidate(candidates)?
7986 };
7987 Some(canonical_cpp_scope_components(&owner))
7988 })();
7989 self.indexed_structural_class_scopes
7990 .lock()
7991 .expect("C++ indexed structural-class scope cache poisoned")
7992 .insert(key, resolved.clone());
7993 resolved
7994 }
7995
7996 pub fn indexed_enclosing_owner_scope(
7997 &self,
7998 analyzer: &CppGraphSource<'_>,
7999 file: &ProjectFile,
8000 node: Node<'_>,
8001 ) -> Option<Vec<String>> {
8002 let anchor = std::iter::successors(Some(node), |current| current.parent())
8003 .find(|current| {
8004 matches!(
8005 current.kind(),
8006 "function_definition"
8007 | "class_specifier"
8008 | "struct_specifier"
8009 | "union_specifier"
8010 )
8011 })
8012 .unwrap_or(node);
8013 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
8014 if let Some(cached) = self
8015 .indexed_enclosing_owner_scopes
8016 .lock()
8017 .expect("C++ indexed enclosing-owner scope cache poisoned")
8018 .get(&key)
8019 .cloned()
8020 {
8021 return cached;
8022 }
8023 let resolved = (|| {
8024 let range = Range {
8025 start_byte: node.start_byte(),
8026 end_byte: node.end_byte(),
8027 start_line: node.start_position().row,
8028 end_line: node.end_position().row,
8029 };
8030 let start = analyzer.enclosing_code_unit(file, &range)?;
8031 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
8032 start,
8033 |unit| self.cached_precise_parent_of(analyzer, unit),
8034 )
8035 .find(|unit| {
8036 unit.is_class()
8037 && !analyzer
8038 .type_alias_provider()
8039 .is_some_and(|provider| provider.is_type_alias(unit))
8040 })?;
8041 Some(canonical_cpp_scope_components(&owner))
8042 })();
8043 self.indexed_enclosing_owner_scopes
8044 .lock()
8045 .expect("C++ indexed enclosing-owner scope cache poisoned")
8046 .insert(key, resolved.clone());
8047 resolved
8048 }
8049
8050 fn cached_precise_parent_of(
8051 &self,
8052 analyzer: &CppGraphSource<'_>,
8053 code_unit: &CodeUnit,
8054 ) -> Option<CodeUnit> {
8055 if let Some(cached) = self
8056 .precise_parent_cache
8057 .lock()
8058 .expect("C++ precise-parent cache poisoned")
8059 .get(code_unit)
8060 .cloned()
8061 {
8062 return cached;
8063 }
8064 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
8065 self.precise_parent_cache
8066 .lock()
8067 .expect("C++ precise-parent cache poisoned")
8068 .insert(code_unit.clone(), resolved.clone());
8069 resolved
8070 }
8071
8072 pub fn callable_is_constructor_declaration(
8073 &self,
8074 analyzer: &CppGraphSource<'_>,
8075 candidate: &CodeUnit,
8076 ) -> bool {
8077 if !candidate.is_function() {
8078 return false;
8079 }
8080 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
8081 return false;
8082 };
8083 let root = prepared.tree().root_node();
8084 let candidate_ranges = analyzer.ranges(candidate);
8085 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
8086 let mut current = root
8087 .descendant_for_byte_range(range.start_byte, range.end_byte)
8088 .and_then(|node| node.parent());
8089 while let Some(node) = current {
8090 if matches!(
8091 node.kind(),
8092 "class_specifier" | "struct_specifier" | "union_specifier"
8093 ) {
8094 return node
8095 .child_by_field_name("name")
8096 .map(|name| terminal_name(node_text(name, prepared.source())))
8097 .is_some_and(|name| name == candidate.identifier());
8098 }
8099 current = node.parent();
8100 }
8101 false
8102 });
8103 if enclosed_by_matching_type {
8104 return true;
8105 }
8106 let indexed_containment = analyzer
8107 .declarations(candidate.source())
8108 .into_iter()
8109 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
8110 .any(|owner| {
8111 analyzer.ranges(&owner).iter().any(|owner_range| {
8112 candidate_ranges.iter().any(|candidate_range| {
8113 owner_range.start_byte <= candidate_range.start_byte
8114 && candidate_range.end_byte <= owner_range.end_byte
8115 })
8116 })
8117 });
8118 if indexed_containment {
8119 return true;
8120 }
8121 let metadata = analyzer.signature_metadata(candidate);
8122 !metadata.is_empty()
8123 && metadata
8124 .iter()
8125 .all(|signature| signature.return_type_text().is_none())
8126 }
8127
8128 pub fn callable_is_deduction_guide_declaration(
8136 &self,
8137 analyzer: &CppGraphSource<'_>,
8138 candidate: &CodeUnit,
8139 ) -> bool {
8140 if !candidate.is_function() {
8141 return false;
8142 }
8143 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
8144 return false;
8145 };
8146 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
8147 .into_iter()
8148 .any(|declaration| {
8149 if declaration.kind() != "declaration"
8150 || declaration.child_by_field_name("type").is_some()
8151 {
8152 return false;
8153 }
8154 let Some(declarator) = declaration.child_by_field_name("declarator") else {
8155 return false;
8156 };
8157 if declarator.kind() != "function_declarator" {
8158 return false;
8159 }
8160 let mut cursor = declarator.walk();
8161 let has_trailing_return = declarator
8162 .named_children(&mut cursor)
8163 .any(|child| child.kind() == "trailing_return_type");
8164 has_trailing_return
8165 && declarator_name_node(declarator).is_some_and(|name| {
8166 node_text(name, prepared.source()) == candidate.identifier()
8167 })
8168 })
8169 }
8170
8171 pub fn callable_is_template_declaration(
8175 &self,
8176 analyzer: &CppGraphSource<'_>,
8177 candidate: &CodeUnit,
8178 ) -> bool {
8179 if !candidate.is_function() {
8180 return false;
8181 }
8182 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
8183 return false;
8184 };
8185 let root = prepared.tree().root_node();
8186 analyzer.ranges(candidate).iter().any(|range| {
8187 let Some(node) = node_for_exact_range(root, range)
8188 .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
8189 else {
8190 return false;
8191 };
8192 node.parent().is_some_and(|parent| {
8193 parent.kind() == "template_declaration"
8194 && parent
8195 .named_child(parent.named_child_count().saturating_sub(1))
8196 .is_some_and(|declaration| same_node(declaration, node))
8197 })
8198 })
8199 }
8200
8201 pub fn type_name_candidates<'b>(
8202 &'b self,
8203 file: &ProjectFile,
8204 normalized: &str,
8205 ) -> Vec<&'b CodeUnit> {
8206 self.candidate_units(file, normalized, TargetKind::Type)
8207 }
8208
8209 pub fn visible_members_for_owner_name<'b>(
8210 &'b self,
8211 file: &ProjectFile,
8212 owner: &CodeUnit,
8213 name: &str,
8214 ) -> Vec<&'b CodeUnit> {
8215 self.visible_identifier_candidates(file, name)
8216 .filter(|unit| {
8217 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
8221 .is_some_and(|parent| parent == owner.fq_name())
8222 })
8223 .collect()
8224 }
8225
8226 pub fn visible_member_for_owner_name(
8227 &self,
8228 file: &ProjectFile,
8229 owner: &CodeUnit,
8230 name: &str,
8231 ) -> VisibleMemberResolution {
8232 let candidates = self.visible_members_for_owner_name(file, owner, name);
8233 let mut callables = Vec::new();
8234 let mut non_callable = None;
8235 for candidate in candidates {
8236 if candidate.is_function() {
8237 callables.push(candidate.clone());
8238 } else if non_callable.is_none() {
8239 non_callable = Some(candidate.clone());
8240 }
8241 }
8242 match (callables.is_empty(), non_callable) {
8243 (false, None) => VisibleMemberResolution::Callable(callables),
8244 (true, Some(_)) => VisibleMemberResolution::NonCallable,
8245 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
8246 (true, None) => VisibleMemberResolution::Missing,
8247 }
8248 }
8249
8250 fn field_declared_type_fact(
8251 &self,
8252 analyzer: &CppGraphSource<'_>,
8253 field: &CodeUnit,
8254 ) -> Option<DeclaredFieldTypeFact> {
8255 if let Some(cached) = self
8256 .field_type_facts
8257 .lock()
8258 .expect("C++ field type fact cache poisoned")
8259 .get(field)
8260 .cloned()
8261 {
8262 return cached;
8263 }
8264 let decoded = decode_field_declared_type_fact(analyzer, field);
8265 self.field_type_facts
8266 .lock()
8267 .expect("C++ field type fact cache poisoned")
8268 .insert(field.clone(), decoded.clone());
8269 decoded
8270 }
8271
8272 fn structured_alias_target(
8273 &self,
8274 analyzer: &CppGraphSource<'_>,
8275 unit: &CodeUnit,
8276 ) -> Option<StructuredAliasTarget> {
8277 if let Some(cached) = self
8278 .structured_alias_targets
8279 .lock()
8280 .expect("C++ structured alias target cache poisoned")
8281 .get(unit)
8282 .cloned()
8283 {
8284 return cached;
8285 }
8286 let decoded = decode_structured_alias_target(analyzer, unit);
8287 self.structured_alias_targets
8288 .lock()
8289 .expect("C++ structured alias target cache poisoned")
8290 .insert(unit.clone(), decoded.clone());
8291 decoded
8292 }
8293
8294 pub fn type_candidates<'b>(
8295 &'b self,
8296 file: &ProjectFile,
8297 normalized: &str,
8298 ) -> Vec<&'b CodeUnit> {
8299 let mut candidates = self
8300 .candidate_units(file, normalized, TargetKind::Type)
8301 .into_iter()
8302 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
8303 .collect::<Vec<_>>();
8304 dedup_unit_refs(&mut candidates);
8305 candidates
8306 }
8307
8308 pub fn named_candidates_for_normalized<'b>(
8309 &'b self,
8310 file: &ProjectFile,
8311 normalized: &str,
8312 kind: TargetKind,
8313 ) -> Vec<&'b CodeUnit> {
8314 let mut candidates = self
8315 .candidate_units(file, normalized, kind)
8316 .into_iter()
8317 .filter(|unit| {
8318 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
8319 })
8320 .collect::<Vec<_>>();
8321 dedup_unit_refs(&mut candidates);
8322 candidates
8323 }
8324
8325 pub fn candidate_units<'b>(
8326 &'b self,
8327 file: &ProjectFile,
8328 normalized: &str,
8329 kind: TargetKind,
8330 ) -> Vec<&'b CodeUnit> {
8331 if normalized.contains("::") {
8332 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8341 brokk_bifrost_core::analyzer::Language::Cpp,
8342 normalized,
8343 )
8344 .pop() else {
8345 return Vec::new();
8346 };
8347 let fqns = cpp_reference_fqn_candidates(normalized, kind);
8348 return self
8349 .visible_identifier_candidates(file, &identifier)
8350 .filter(|unit| {
8351 #[cfg(any(test, feature = "test-support"))]
8352 self.qualified_candidate_inspections
8353 .fetch_add(1, Ordering::Relaxed);
8354 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
8355 || canonical_cpp_name_matches(unit, normalized)
8356 })
8357 .collect();
8358 }
8359 self.visible_identifier_candidates(file, normalized)
8360 .collect()
8361 }
8362
8363 #[cfg(any(test, feature = "test-support"))]
8364 pub fn reset_qualified_candidate_inspections(&self) {
8365 self.qualified_candidate_inspections
8366 .store(0, Ordering::Relaxed);
8367 }
8368
8369 #[cfg(any(test, feature = "test-support"))]
8370 pub fn qualified_candidate_inspections(&self) -> usize {
8371 self.qualified_candidate_inspections.load(Ordering::Relaxed)
8372 }
8373
8374 #[cfg(any(test, feature = "test-support"))]
8375 pub fn reset_target_preserving_type_resolution_count(&self) {
8376 self.target_preserving_type_resolution_count
8377 .store(0, Ordering::Relaxed);
8378 }
8379
8380 #[cfg(any(test, feature = "test-support"))]
8381 pub fn target_preserving_type_resolution_count(&self) -> usize {
8382 self.target_preserving_type_resolution_count
8383 .load(Ordering::Relaxed)
8384 }
8385
8386 #[cfg(any(test, feature = "test-support"))]
8387 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
8388 self.visible_parser_alias_name_set_build_count
8389 .load(Ordering::Relaxed)
8390 }
8391}
8392
8393#[derive(Default)]
8394struct IncludeGraph {
8395 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
8396}
8397
8398impl IncludeGraph {
8399 fn extend_with<F>(
8400 &mut self,
8401 root: &ProjectFile,
8402 cancellation: Option<&CancellationToken>,
8403 targets_for: &mut F,
8404 ) where
8405 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
8406 {
8407 let mut stack = vec![root.clone()];
8408 while let Some(file) = stack.pop() {
8409 if cancellation.is_some_and(CancellationToken::is_cancelled) {
8410 break;
8411 }
8412 if self.targets_by_file.contains_key(&file) {
8413 continue;
8414 }
8415 let targets = targets_for(&file);
8416 stack.extend(targets.iter().cloned());
8417 self.targets_by_file.insert(file, targets);
8418 }
8419 }
8420
8421 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
8422 self.targets_by_file.keys()
8423 }
8424
8425 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
8426 self.targets_by_file
8427 .get(file)
8428 .map(Vec::as_slice)
8429 .unwrap_or_default()
8430 }
8431
8432 fn reachable_files(
8433 &self,
8434 root: &ProjectFile,
8435 cancellation: Option<&CancellationToken>,
8436 ) -> HashSet<ProjectFile> {
8437 let mut pending = vec![root.clone()];
8438 let mut visited = HashSet::default();
8439 while let Some(file) = pending.pop() {
8440 if cancellation.is_some_and(CancellationToken::is_cancelled) {
8441 break;
8442 }
8443 if visited.insert(file.clone()) {
8444 pending.extend(self.targets(&file).iter().cloned());
8445 }
8446 }
8447 visited
8448 }
8449}
8450
8451fn build_bounded_visible_declarations(
8452 cpp: &dyn CppSource,
8453 token: QueryToken<'_>,
8454 analyzer: &CppGraphSource<'_>,
8455 roots: &HashSet<ProjectFile>,
8456 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
8457 cancellation: Option<&CancellationToken>,
8458 stats: &mut BoundedVisibilityStats,
8459) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
8460 roots
8461 .iter()
8462 .map(|root| {
8463 let reading_is_c = analyzer.reference_uses_c_semantics(root);
8464 let declarations_started = Instant::now();
8465 let root_declarations =
8466 bounded_visibility_declarations_in_reading(analyzer, root, reading_is_c);
8467 stats.declaration_elapsed += declarations_started.elapsed();
8468 stats.declaration_reads += 1;
8469 stats.declaration_units += root_declarations.len();
8470 let mut visible = root_declarations.into_iter().collect::<HashSet<_>>();
8471 let mut pending_names = HashSet::default();
8472 if let Some(prepared) = cpp.prepared_syntax(token, root) {
8473 let mut pending_nodes = vec![prepared.tree().root_node()];
8474 while let Some(node) = pending_nodes.pop() {
8475 if matches!(
8476 node.kind(),
8477 "identifier"
8478 | "type_identifier"
8479 | "field_identifier"
8480 | "namespace_identifier"
8481 ) {
8482 pending_names.insert(node_text(node, prepared.source()).to_string());
8483 }
8484 if node.kind() == "preproc_arg" {
8485 for reference in
8486 object_macro_replacement_type_references(node, prepared.source())
8487 {
8488 pending_names.extend(reference.components);
8489 }
8490 }
8491 for index in 0..node.named_child_count() {
8492 if let Some(child) = node.named_child(index) {
8493 pending_nodes.push(child);
8494 }
8495 }
8496 }
8497 }
8498 stats.root_names += pending_names.len();
8499 let mut completed_names = HashSet::default();
8500 while !pending_names.is_empty() {
8501 stats.rounds += 1;
8502 let round_names = std::mem::take(&mut pending_names);
8503 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
8504 HashMap::default();
8505 for identifier in round_names {
8506 if !completed_names.insert(identifier.clone())
8507 || cancellation.is_some_and(CancellationToken::is_cancelled)
8508 {
8509 continue;
8510 }
8511 let lookup_started = Instant::now();
8512 let candidates = cpp.visibility_identifier_candidates(&identifier);
8513 stats.lookup_elapsed += lookup_started.elapsed();
8514 stats.identifier_lookups += 1;
8515 stats.candidate_units += candidates.len();
8516 for source in candidates
8517 .into_iter()
8518 .map(|unit| unit.source().clone())
8519 .collect::<HashSet<_>>()
8520 {
8521 if source != *root
8522 && visible_sources
8523 .get(root)
8524 .is_some_and(|files| files.contains(&source))
8525 {
8526 requested_names_by_source
8527 .entry(source)
8528 .or_default()
8529 .insert(identifier.clone());
8530 }
8531 }
8532 }
8533 stats.candidate_sources += requested_names_by_source.len();
8534 for (source, requested_names) in requested_names_by_source {
8535 let declarations_started = Instant::now();
8536 let declarations =
8537 bounded_visibility_declarations_in_reading(analyzer, &source, reading_is_c);
8538 stats.declaration_elapsed += declarations_started.elapsed();
8539 stats.declaration_reads += 1;
8540 stats.declaration_units += declarations.len();
8541 for unit in declarations {
8542 let template_metadata = unit
8543 .is_class()
8544 .then(|| cpp.template_metadata(&unit))
8545 .flatten();
8546 if !requested_names.contains(unit.identifier())
8547 && !template_metadata.as_ref().is_some_and(|metadata| {
8548 requested_names.contains(&metadata.primary_name)
8549 })
8550 {
8551 continue;
8552 }
8553 stats.selected_units += 1;
8554 if let Some(prepared) = cpp.prepared_syntax(token, &source) {
8555 let ast_started = Instant::now();
8556 for range in analyzer.ranges(&unit) {
8557 let Some(declaration) =
8558 node_for_exact_range(prepared.tree().root_node(), &range)
8559 else {
8560 continue;
8561 };
8562 let mut pending_nodes = vec![declaration];
8563 while let Some(node) = pending_nodes.pop() {
8564 stats.dependency_ast_nodes += 1;
8565 if matches!(
8566 node.kind(),
8567 "type_identifier" | "namespace_identifier"
8568 ) {
8569 let name = node_text(node, prepared.source());
8570 if !completed_names.contains(name)
8571 && pending_names.insert(name.to_string())
8572 {
8573 stats.dependency_names += 1;
8574 }
8575 }
8576 for index in 0..node.named_child_count() {
8577 if let Some(child) = node.named_child(index) {
8578 pending_nodes.push(child);
8579 }
8580 }
8581 }
8582 }
8583 stats.dependency_ast_elapsed += ast_started.elapsed();
8584 }
8585 if let Some(metadata) = template_metadata
8586 && !completed_names.contains(&metadata.primary_name)
8587 {
8588 pending_names.insert(metadata.primary_name);
8589 }
8590 visible.insert(unit);
8591 }
8592 }
8593 }
8594 (root.clone(), visible)
8595 })
8596 .collect()
8597}
8598
8599#[derive(Default)]
8600struct BoundedVisibilityStats {
8601 rounds: usize,
8602 root_names: usize,
8603 identifier_lookups: usize,
8604 candidate_units: usize,
8605 candidate_sources: usize,
8606 declaration_reads: usize,
8607 declaration_units: usize,
8608 selected_units: usize,
8609 dependency_ast_nodes: usize,
8610 dependency_names: usize,
8611 lookup_elapsed: Duration,
8612 declaration_elapsed: Duration,
8613 dependency_ast_elapsed: Duration,
8614}
8615
8616fn bounded_visibility_declarations_in_reading(
8617 analyzer: &CppGraphSource<'_>,
8618 file: &ProjectFile,
8619 c_semantics: bool,
8620) -> BTreeSet<CodeUnit> {
8621 #[cfg(any(test, feature = "test-support"))]
8622 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
8623 analyzer.declarations_in_reading(file, c_semantics)
8624}
8625
8626#[cfg(any(test, feature = "test-support"))]
8627pub fn reset_bounded_visibility_declaration_read_count_for_test() {
8628 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
8629}
8630
8631#[cfg(any(test, feature = "test-support"))]
8632pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
8633 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
8634}
8635
8636pub struct VisibilityData {
8637 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
8638 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
8639}
8640
8641pub fn build_visibility_data<F, R, D>(
8651 roots: &HashSet<ProjectFile>,
8652 cancellation: Option<&CancellationToken>,
8653 mut targets_for: F,
8654 mut reading_is_c_for: R,
8655 mut declarations_for: D,
8656) -> VisibilityData
8657where
8658 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
8659 R: FnMut(&ProjectFile) -> bool,
8660 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
8661{
8662 let mut include_graph = IncludeGraph::default();
8663 for file in roots {
8664 if cancellation.is_some_and(CancellationToken::is_cancelled) {
8665 break;
8666 }
8667 include_graph.extend_with(file, cancellation, &mut targets_for);
8668 }
8669 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
8670 .files()
8671 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
8672 .map(|file| (file.clone(), declarations_for(file, false)))
8673 .collect();
8674 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
8675 let mut visible_by_file = HashMap::default();
8676 let mut visible_source_files_by_root = HashMap::default();
8677 for file in roots {
8678 if cancellation.is_some_and(CancellationToken::is_cancelled) {
8679 break;
8680 }
8681 let mut visited = HashSet::default();
8682 let mut visible = HashSet::default();
8683 let declarations_by_file = if reading_is_c_for(file) {
8684 for reached in cpp_declarations_by_file.keys() {
8685 if !c_declarations_by_file.contains_key(reached) {
8686 let declarations = declarations_for(reached, true);
8687 c_declarations_by_file.insert(reached.clone(), declarations);
8688 }
8689 }
8690 &c_declarations_by_file
8691 } else {
8692 &cpp_declarations_by_file
8693 };
8694 collect_visible_declarations(
8695 &include_graph,
8696 declarations_by_file,
8697 file,
8698 &mut visited,
8699 &mut visible,
8700 cancellation,
8701 );
8702 visible_by_file.insert(file.clone(), visible);
8703 visible_source_files_by_root.insert(file.clone(), visited);
8704 }
8705 VisibilityData {
8706 visible_by_file,
8707 visible_source_files_by_root,
8708 }
8709}
8710
8711#[derive(Default)]
8730struct OutOfLineOwnerBindingStats {
8731 unseen_owners: usize,
8732 definition_lookups: usize,
8733 admitted: usize,
8734}
8735
8736fn extend_with_out_of_line_owner_bindings(
8737 cpp: &dyn CppSource,
8738 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
8739) -> OutOfLineOwnerBindingStats {
8740 let mut stats = OutOfLineOwnerBindingStats::default();
8741 for (file, visible) in visible_by_file.iter_mut() {
8742 let mut unseen_owners: HashSet<String> = visible
8746 .iter()
8747 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
8748 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
8749 .collect();
8750 if unseen_owners.is_empty() {
8751 continue;
8752 }
8753 for unit in visible.iter().filter(|unit| unit.is_class()) {
8754 unseen_owners.remove(&unit.fq_name());
8755 }
8756 stats.unseen_owners += unseen_owners.len();
8757 stats.definition_lookups += unseen_owners.len();
8758 let admitted = unseen_owners
8759 .iter()
8760 .flat_map(|owner| cpp.definitions(owner))
8761 .filter(CodeUnit::is_class)
8762 .collect::<Vec<_>>();
8763 stats.admitted += admitted.len();
8764 visible.extend(admitted);
8765 }
8766 stats
8767}
8768
8769pub enum VisibleMemberResolution {
8770 Callable(Vec<CodeUnit>),
8771 NonCallable,
8772 AmbiguousKind,
8773 Missing,
8774}
8775
8776#[derive(Clone)]
8777pub enum EnclosingMemberOwnerResolution {
8778 Owner(CodeUnit),
8779 Ambiguous,
8780 Missing,
8781}
8782
8783pub fn resolve_declaring_member_owner(
8784 analyzer: &CppGraphSource<'_>,
8785 visibility: &VisibilityIndex<'_>,
8786 file: &ProjectFile,
8787 receiver_owner: &CodeUnit,
8788 member_name: &str,
8789) -> EnclosingMemberOwnerResolution {
8790 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8791 return EnclosingMemberOwnerResolution::Missing;
8792 };
8793 let Some(receiver_owner) =
8794 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
8795 else {
8796 return EnclosingMemberOwnerResolution::Ambiguous;
8797 };
8798 let resolve_level = |frontier: &[CodeUnit]| {
8799 let mut member_owners = Vec::new();
8800 for raw_owner in frontier {
8801 let Some(owner) =
8802 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
8803 else {
8804 return EnclosingMemberOwnerResolution::Ambiguous;
8805 };
8806 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
8807 if !member.is_field() && !member.is_function() {
8812 continue;
8813 }
8814 let Some(member_owner) = type_owner_of(analyzer, member) else {
8815 return EnclosingMemberOwnerResolution::Ambiguous;
8816 };
8817 if !member_owners
8818 .iter()
8819 .any(|existing| same_visible_symbol(existing, &member_owner))
8820 {
8821 member_owners.push(member_owner);
8822 }
8823 }
8824 }
8825 match member_owners.len() {
8826 0 => EnclosingMemberOwnerResolution::Missing,
8827 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
8828 _ => EnclosingMemberOwnerResolution::Ambiguous,
8829 }
8830 };
8831 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
8835 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
8836 return direct;
8837 }
8838 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
8839 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
8840 let mut path_matches = Vec::new();
8841 while let Some(raw_owner) = stack.pop() {
8842 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
8843 else {
8844 return EnclosingMemberOwnerResolution::Ambiguous;
8845 };
8846 let propagated = propagated_counts.entry(owner.clone()).or_default();
8850 if *propagated == 2 {
8851 continue;
8852 }
8853 *propagated += 1;
8854 match resolve_level(std::slice::from_ref(&owner)) {
8855 EnclosingMemberOwnerResolution::Owner(owner) => {
8856 path_matches.push(owner);
8857 if path_matches.len() == 2 {
8858 return EnclosingMemberOwnerResolution::Ambiguous;
8859 }
8860 }
8861 EnclosingMemberOwnerResolution::Ambiguous => {
8862 return EnclosingMemberOwnerResolution::Ambiguous;
8863 }
8864 EnclosingMemberOwnerResolution::Missing => {
8865 stack.extend(hierarchy.get_direct_ancestors(&owner));
8866 }
8867 }
8868 }
8869 match path_matches.len() {
8870 0 => EnclosingMemberOwnerResolution::Missing,
8871 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
8872 _ => unreachable!("base-path matches are capped at one before returning"),
8873 }
8874}
8875
8876pub fn resolve_declaring_callable_owner(
8891 analyzer: &CppGraphSource<'_>,
8892 visibility: &VisibilityIndex<'_>,
8893 file: &ProjectFile,
8894 ordinary: EnclosingMemberOwnerResolution,
8895 member_name: &str,
8896 call_arity: usize,
8897) -> EnclosingMemberOwnerResolution {
8898 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
8899 return ordinary;
8900 };
8901 if visibility
8902 .visible_members_for_owner_name(file, ordinary_owner, member_name)
8903 .into_iter()
8904 .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
8905 {
8906 return ordinary;
8907 }
8908
8909 let mut pending = match member_using_declaration_bases(
8910 analyzer,
8911 visibility,
8912 file,
8913 ordinary_owner,
8914 member_name,
8915 ) {
8916 Ok(bases) => bases,
8917 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8918 };
8919 let mut visited = HashSet::default();
8920 let mut introduced_owners = Vec::new();
8921 while let Some(owner) = pending.pop() {
8922 if !visited.insert(owner.clone()) {
8923 continue;
8924 }
8925 let accepts_arity = visibility
8926 .visible_members_for_owner_name(file, &owner, member_name)
8927 .into_iter()
8928 .any(|unit| {
8929 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
8930 });
8931 if accepts_arity {
8932 if !introduced_owners
8933 .iter()
8934 .any(|existing| same_visible_symbol(existing, &owner))
8935 {
8936 introduced_owners.push(owner);
8937 }
8938 continue;
8939 }
8940 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
8941 Ok(bases) => pending.extend(bases),
8942 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8943 }
8944 }
8945 match introduced_owners.as_slice() {
8946 [] => ordinary,
8947 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
8948 _ => EnclosingMemberOwnerResolution::Ambiguous,
8949 }
8950}
8951
8952fn member_using_declaration_bases(
8953 analyzer: &CppGraphSource<'_>,
8954 visibility: &VisibilityIndex<'_>,
8955 file: &ProjectFile,
8956 owner: &CodeUnit,
8957 member_name: &str,
8958) -> Result<Vec<CodeUnit>, ()> {
8959 let Some(source) = analyzer.get_source(owner, false) else {
8960 return Ok(Vec::new());
8961 };
8962 let scopes = cpp_member_using_declaration_scopes(&source, member_name);
8963 if scopes.is_empty() {
8964 return Ok(Vec::new());
8965 }
8966 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8967 return Ok(Vec::new());
8968 };
8969 let mut bases = Vec::new();
8970 for raw_ancestor in hierarchy.get_ancestors(owner) {
8971 let Some(ancestor) =
8972 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
8973 else {
8974 return Err(());
8975 };
8976 let qualified = cpp_name_for(&ancestor);
8977 if scopes
8978 .iter()
8979 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
8980 && !bases
8981 .iter()
8982 .any(|existing| same_visible_symbol(existing, &ancestor))
8983 {
8984 bases.push(ancestor);
8985 }
8986 }
8987 Ok(bases)
8988}
8989
8990pub fn lexical_component_tiers<'a>(
8991 components: &'a [String],
8992 global: bool,
8993 lexical_scope: &'a [String],
8994) -> impl Iterator<Item = Vec<String>> + 'a {
8995 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
8996 (0..=first_prefix_len).rev().map(move |prefix_len| {
8997 let mut qualified = Vec::with_capacity(prefix_len + components.len());
8998 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
8999 qualified.extend_from_slice(components);
9000 qualified
9001 })
9002}
9003
9004pub fn build_visible_identifier_index(
9005 analyzer: &CppGraphSource<'_>,
9006 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
9007 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9008 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
9009) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
9010 let mut out = HashMap::default();
9011 for (file, visible) in visible_by_file {
9012 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9013 for unit in visible {
9014 if unit.is_field()
9015 && !visible_source_files_by_root
9016 .get(file)
9017 .is_some_and(|sources| sources.contains(unit.source()))
9018 && cpp_global_field_has_internal_linkage_cached(
9019 analyzer,
9020 global_field_internal_linkage,
9021 unit,
9022 )
9023 {
9024 continue;
9025 }
9026 by_identifier
9027 .entry(unit.identifier().to_string())
9028 .or_default()
9029 .push(unit.clone());
9030 }
9031 for units in by_identifier.values_mut() {
9032 sort_lookup_units(units);
9033 units.dedup();
9034 }
9035 out.insert(file.clone(), by_identifier);
9036 }
9037 out
9038}
9039
9040fn sort_lookup_units(units: &mut [CodeUnit]) {
9041 units.sort_by(|left, right| {
9042 left.fq_name()
9043 .cmp(&right.fq_name())
9044 .then_with(|| left.signature().cmp(&right.signature()))
9045 .then_with(|| left.source().cmp(right.source()))
9046 .then_with(|| left.kind().cmp(&right.kind()))
9047 .then_with(|| {
9048 left.package_segment_count()
9049 .cmp(&right.package_segment_count())
9050 })
9051 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
9052 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
9053 });
9054}
9055
9056fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
9057 let interner = segment_interner();
9058 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
9059 let (left_text, left_kind) = interner.resolve(left_id);
9060 let (right_text, right_kind) = interner.resolve(right_id);
9061 let order = left_text
9062 .cmp(right_text)
9063 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
9064 if order != CmpOrdering::Equal {
9065 return order;
9066 }
9067 }
9068 left.len().cmp(&right.len())
9069}
9070
9071const fn segment_kind_order(kind: SegmentKind) -> u8 {
9072 match kind {
9073 SegmentKind::Path => 0,
9074 SegmentKind::Package => 1,
9075 SegmentKind::Type => 2,
9076 SegmentKind::Companion => 3,
9077 SegmentKind::Nested => 4,
9078 SegmentKind::Member => 5,
9079 SegmentKind::Unknown => 6,
9080 }
9081}
9082
9083fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
9084 let mut deduped = Vec::with_capacity(units.len());
9085 for unit in units.drain(..) {
9086 if !deduped.contains(&unit) {
9087 deduped.push(unit);
9088 }
9089 }
9090 *units = deduped;
9091}
9092
9093pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
9094 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9098 brokk_bifrost_core::analyzer::Language::Cpp,
9099 reference,
9100 );
9101 if parts.is_empty() {
9102 return Vec::new();
9103 }
9104
9105 let mut candidates = Vec::new();
9106 for package_len in 0..parts.len() {
9107 let package = parts[..package_len].join("::");
9108 let rest = &parts[package_len..];
9109 if rest.is_empty() {
9110 continue;
9111 }
9112 match kind {
9113 TargetKind::Type | TargetKind::Constructor => {
9114 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
9115 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
9116 }
9117 TargetKind::FreeFunction
9118 | TargetKind::Method
9119 | TargetKind::GlobalField
9120 | TargetKind::MemberField
9121 | TargetKind::Macro => {
9122 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
9123 if rest.len() > 1 {
9124 let owner = rest[..rest.len() - 1].join("$");
9125 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
9126 push_cpp_fqn_candidate(&mut candidates, &package, &short);
9127 }
9128 }
9129 }
9130 }
9131 candidates
9132}
9133
9134fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
9135 let fqn = if package.is_empty() {
9136 short.to_string()
9137 } else {
9138 format!("{package}.{short}")
9139 };
9140 if !out.contains(&fqn) {
9141 out.push(fqn);
9142 }
9143}
9144
9145pub fn infer_cpp_initializer_type(
9146 analyzer: &CppGraphSource<'_>,
9147 visibility: &VisibilityIndex<'_>,
9148 file: &ProjectFile,
9149 source: &str,
9150 node: Node<'_>,
9151) -> Option<CodeUnit> {
9152 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
9153 .and_then(|binding| binding.unit)
9154}
9155
9156pub fn infer_cpp_initializer_binding(
9157 analyzer: &CppGraphSource<'_>,
9158 visibility: &VisibilityIndex<'_>,
9159 file: &ProjectFile,
9160 source: &str,
9161 node: Node<'_>,
9162 receiver_resolver: Option<&ReceiverResolver<'_>>,
9163) -> Option<CppScanBinding> {
9164 match node.kind() {
9165 "new_expression" => {
9166 let text = normalize_cpp_whitespace(node_text(node, source));
9167 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
9168 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
9169 let name = normalize_cpp_type_name(type_text);
9170 Some(CppScanBinding::from_type_name(
9171 name.clone(),
9172 visibility.resolve_type(file, &name),
9173 1,
9174 ))
9175 }
9176 "call_expression" => node.child_by_field_name("function").and_then(|function| {
9177 if function.kind() == "field_expression" {
9185 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
9186 return resolve_field_method_call_return_binding(
9187 analyzer,
9188 visibility,
9189 file,
9190 source,
9191 function,
9192 arity,
9193 receiver_resolver,
9194 );
9195 }
9196 let function_text = node_text(function, source);
9197 let direct_type_binding = visibility
9198 .resolve_type(file, function_text)
9199 .map(|unit| CppScanBinding::from_unit(unit, 0));
9200 if function.kind() == "template_function" && direct_type_binding.is_some() {
9201 let lexical_namespace = enclosing_namespace_context(node, source);
9202 let arity = visibility.call_arity_evidence(file, node, source).exact();
9203 if let Some(arity) = arity
9204 && let Some(binding) = visibility.resolve_call_return_binding(
9205 analyzer,
9206 file,
9207 function_text,
9208 arity,
9209 lexical_namespace.as_deref(),
9210 direct_type_binding
9211 .as_ref()
9212 .and_then(|binding| binding.unit.as_ref()),
9213 )
9214 {
9215 return Some(binding);
9216 }
9217 let (has_callable, callable_binding) = visibility
9218 .resolve_call_return_binding_without_arity(
9219 analyzer,
9220 file,
9221 function_text,
9222 lexical_namespace.as_deref(),
9223 direct_type_binding
9224 .as_ref()
9225 .and_then(|binding| binding.unit.as_ref()),
9226 );
9227 if let Some(binding) = callable_binding {
9228 return Some(binding);
9229 }
9230 if has_callable {
9231 return None;
9232 }
9233 return direct_type_binding;
9234 }
9235 let arity = visibility.call_arity_evidence(file, node, source).exact();
9240 if let Some(arity) = arity {
9241 let direct_type_binding_for_call = direct_type_binding.clone();
9242 if let Some(binding) = resolve_static_method_call_return_binding(
9243 analyzer, visibility, file, source, function, arity,
9244 )
9245 .or_else(|| {
9246 visibility.resolve_call_return_binding(
9251 analyzer,
9252 file,
9253 function_text,
9254 arity,
9255 enclosing_namespace_context(node, source).as_deref(),
9256 direct_type_binding_for_call
9257 .as_ref()
9258 .and_then(|binding| binding.unit.as_ref()),
9259 )
9260 }) {
9261 return Some(binding);
9262 }
9263 }
9264 direct_type_binding
9265 }),
9266 _ => None,
9267 }
9268}
9269
9270fn resolve_static_method_call_return_binding(
9271 analyzer: &CppGraphSource<'_>,
9272 visibility: &VisibilityIndex<'_>,
9273 file: &ProjectFile,
9274 source: &str,
9275 function: Node<'_>,
9276 arity: usize,
9277) -> Option<CppScanBinding> {
9278 if function.kind() != "qualified_identifier" {
9279 return None;
9280 }
9281 let qualified = normalize_cpp_reference_text(node_text(function, source));
9282 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9289 brokk_bifrost_core::analyzer::Language::Cpp,
9290 &qualified,
9291 );
9292 let (owner_text, member_name) = match parts.split_last() {
9293 Some((member, owner_parts)) if !owner_parts.is_empty() => {
9294 (owner_parts.join("::"), member.clone())
9295 }
9296 _ => {
9297 let scope = function.child_by_field_name("scope")?;
9298 let name = function.child_by_field_name("name")?;
9299 (
9300 node_text(scope, source).to_string(),
9301 node_text(name, source).to_string(),
9302 )
9303 }
9304 };
9305 let owner = visibility.resolve_type(file, &owner_text)?;
9306 let candidates = visibility
9307 .visible_members_for_owner_name(file, &owner, &member_name)
9308 .into_iter()
9309 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
9310 .cloned()
9311 .collect::<Vec<_>>();
9312 unanimous_return_binding(analyzer, visibility, file, &candidates)
9313}
9314
9315fn resolve_field_method_call_return_binding(
9316 analyzer: &CppGraphSource<'_>,
9317 visibility: &VisibilityIndex<'_>,
9318 file: &ProjectFile,
9319 source: &str,
9320 function: Node<'_>,
9321 arity: usize,
9322 receiver_resolver: Option<&ReceiverResolver<'_>>,
9323) -> Option<CppScanBinding> {
9324 debug_assert_eq!(
9325 function.kind(),
9326 "field_expression",
9327 "the member-call return binding answers only for a field-expression callee"
9328 );
9329 let receiver_resolver = receiver_resolver?;
9330 let field = function.child_by_field_name("field")?;
9331 let member_name = node_text(function_terminal_node(field), source);
9332 let receiver = function
9333 .child_by_field_name("argument")
9334 .or_else(|| function.named_child(0))?;
9335 let owners = receiver_resolver(receiver, source);
9336 let mut candidates = Vec::new();
9337 for owner in owners {
9338 let declaring_owner =
9339 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
9340 EnclosingMemberOwnerResolution::Owner(owner) => owner,
9341 EnclosingMemberOwnerResolution::Missing => continue,
9342 EnclosingMemberOwnerResolution::Ambiguous => return None,
9343 };
9344 candidates.extend(
9345 visibility
9346 .visible_members_for_owner_name(file, &declaring_owner, member_name)
9347 .into_iter()
9348 .filter(|unit| {
9349 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
9350 })
9351 .cloned(),
9352 );
9353 }
9354 unanimous_return_binding(analyzer, visibility, file, &candidates)
9355}
9356
9357fn unanimous_return_binding(
9358 analyzer: &CppGraphSource<'_>,
9359 visibility: &VisibilityIndex<'_>,
9360 file: &ProjectFile,
9361 candidates: &[CodeUnit],
9362) -> Option<CppScanBinding> {
9363 let mut resolved_return: Option<CppScanBinding> = None;
9364 for function in candidates {
9365 let metadata = analyzer.signature_metadata(function);
9366 let return_types = if metadata.is_empty() {
9367 vec![cpp_function_return_type_text(analyzer, function)?]
9368 } else {
9369 metadata
9370 .iter()
9371 .map(|metadata| metadata.return_type_text().map(str::to_string))
9372 .collect::<Option<Vec<_>>>()?
9373 };
9374 for return_text in return_types {
9375 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
9376 let name = normalize_cpp_type_name(&return_text);
9377 let binding = CppScanBinding::from_type_name(
9378 name.clone(),
9379 visibility
9380 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
9381 indirection,
9382 );
9383 if let Some(existing) = resolved_return.as_ref()
9384 && (existing.indirection != binding.indirection
9385 || match (&existing.unit, &binding.unit) {
9386 (Some(left), Some(right)) => !same_visible_symbol(left, right),
9387 (None, None) => existing.type_name != binding.type_name,
9388 (Some(_), None) | (None, Some(_)) => true,
9389 })
9390 {
9391 return None;
9392 }
9393 resolved_return = Some(binding);
9394 }
9395 }
9396 resolved_return
9397}
9398
9399fn aliases_from_prepared_source(
9400 cpp: &dyn CppSource,
9401 token: QueryToken<'_>,
9402 file: &ProjectFile,
9403) -> Vec<CppAlias> {
9404 let Some(prepared) = cpp.prepared_syntax(token, file) else {
9405 return Vec::new();
9406 };
9407 let mut aliases = Vec::new();
9408 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
9409 aliases
9410}
9411
9412fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
9413 let mut stack = vec![root];
9414 while let Some(node) = stack.pop() {
9415 match node.kind() {
9416 "alias_declaration" if alias_has_visible_file_scope(node) => {
9417 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
9418 out.push(alias);
9419 }
9420 }
9421 "type_definition" if alias_has_visible_file_scope(node) => {
9422 collect_typedef_aliases(node, source, out)
9423 }
9424 _ => {}
9425 }
9426
9427 for index in (0..node.named_child_count()).rev() {
9428 if let Some(child) = node.named_child(index) {
9429 stack.push(child);
9430 }
9431 }
9432 }
9433}
9434
9435fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
9436 let mut current = node.parent();
9437 while let Some(parent) = current {
9438 match parent.kind() {
9439 "translation_unit"
9440 | "namespace_definition"
9441 | "declaration_list"
9442 | "linkage_specification" => current = parent.parent(),
9443 "template_declaration" => current = parent.parent(),
9444 _ => return false,
9445 }
9446 }
9447 true
9448}
9449
9450fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
9451 let name = node
9452 .child_by_field_name("name")
9453 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
9454 let target = node
9455 .child_by_field_name("type")
9456 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
9457 Some(CppAlias {
9458 name,
9459 target,
9460 namespace: enclosing_namespace_context(node, source),
9461 })
9462}
9463
9464fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
9465 let Some(type_node) = node.child_by_field_name("type") else {
9466 return;
9467 };
9468 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
9469 return;
9470 };
9471
9472 let mut cursor = node.walk();
9473 for child in node.named_children(&mut cursor) {
9474 if same_node(child, type_node) {
9475 continue;
9476 }
9477 if let Some(name) = extract_typedef_declarator_name(child, source) {
9478 out.push(CppAlias {
9479 name,
9480 target: target.clone(),
9481 namespace: enclosing_namespace_context(node, source),
9482 });
9483 }
9484 }
9485}
9486
9487fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
9488 match node.kind() {
9489 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
9490 normalize_reference_name(node_text(node, source))
9491 }
9492 _ => node
9493 .child_by_field_name("declarator")
9494 .or_else(|| node.child_by_field_name("name"))
9495 .or_else(|| last_named_child(node))
9496 .and_then(|child| extract_typedef_declarator_name(child, source)),
9497 }
9498}
9499
9500fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
9501 let count = node.named_child_count();
9502 if count == 0 {
9503 None
9504 } else {
9505 node.named_child(count - 1)
9506 }
9507}
9508
9509pub fn collect_include_closure(
9510 analyzer: &CppGraphSource<'_>,
9511 include_targets: &IncludeTargetIndex,
9512 file: &ProjectFile,
9513 out: &mut HashSet<ProjectFile>,
9514 cancellation: Option<&CancellationToken>,
9515) {
9516 let mut stack = vec![file.clone()];
9517 while let Some(file) = stack.pop() {
9518 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9519 break;
9520 }
9521 if !out.insert(file.clone()) {
9522 continue;
9523 }
9524 let imports = analyzer.import_statements(&file);
9525 for include in cpp_include_paths(&imports) {
9526 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
9527 stack.push(target);
9528 }
9529 }
9530 }
9531}
9532
9533fn collect_visible_declarations(
9534 include_graph: &IncludeGraph,
9535 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
9536 file: &ProjectFile,
9537 visited: &mut HashSet<ProjectFile>,
9538 out: &mut HashSet<CodeUnit>,
9539 cancellation: Option<&CancellationToken>,
9540) {
9541 let mut stack = vec![file.clone()];
9542 while let Some(file) = stack.pop() {
9543 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9544 break;
9545 }
9546 if !visited.insert(file.clone()) {
9547 continue;
9548 }
9549 if let Some(declarations) = declarations_by_file.get(&file) {
9550 out.extend(declarations.iter().cloned());
9551 }
9552 stack.extend(include_graph.targets(&file).iter().cloned());
9553 }
9554}
9555
9556pub fn signature_arity(signature: Option<&str>) -> usize {
9557 let Some(signature) = signature else {
9558 return 0;
9559 };
9560 let inner = signature
9561 .find('(')
9562 .and_then(|open| {
9563 signature[open + 1..]
9564 .find(')')
9565 .map(|close| &signature[open + 1..open + 1 + close])
9566 })
9567 .unwrap_or(signature)
9568 .trim();
9569 if inner.is_empty() || inner == "void" {
9570 return 0;
9571 }
9572 cpp_split_top_level_commas(inner).count()
9573}
9574
9575fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
9576 let source = format!("void __bifrost_macro_parameters({replacement});");
9577 let mut parser = Parser::new();
9578 parser
9579 .set_language(&tree_sitter_cpp::LANGUAGE.into())
9580 .ok()?;
9581 let tree = parser.parse(&source, None)?;
9582 let root = tree.root_node();
9583 if root.has_error() {
9584 return None;
9585 }
9586 let declaration = root.named_child(0)?;
9587 let declarator = declaration.child_by_field_name("declarator")?;
9588 let parameters = declarator.child_by_field_name("parameters")?;
9589 let mut required = 0;
9590 let mut total = 0;
9591 let mut repeated = false;
9592 let mut cursor = parameters.walk();
9593 for parameter in parameters.children(&mut cursor) {
9594 match parameter.kind() {
9595 "parameter_declaration" => {
9596 if parameter.child_by_field_name("declarator").is_none()
9597 && parameter
9598 .child_by_field_name("type")
9599 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
9600 {
9601 continue;
9602 }
9603 required += 1;
9604 total += 1;
9605 }
9606 "optional_parameter_declaration" => total += 1,
9607 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
9608 repeated = true;
9609 }
9610 _ => {}
9611 }
9612 }
9613 Some(CallableArity::new(required, total, repeated))
9614}
9615
9616pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
9617 analyzer
9618 .signature_metadata(unit)
9619 .into_iter()
9620 .find_map(|metadata| metadata.callable_arity())
9621 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
9622}
9623
9624pub fn cpp_callable_parameter_types(
9625 analyzer: &CppGraphSource<'_>,
9626 unit: &CodeUnit,
9627) -> Option<Vec<String>> {
9628 analyzer
9629 .signature_metadata(unit)
9630 .into_iter()
9631 .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
9632 .or_else(|| unit.signature().and_then(cpp_signature_param_types))
9633}
9634
9635fn merge_compatible_callable_arities(
9636 left: CallableArity,
9637 right: CallableArity,
9638) -> Option<CallableArity> {
9639 let total = left.total();
9640 let left_repeated = left.accepts(total.saturating_add(1));
9641 let right_repeated = right.accepts(right.total().saturating_add(1));
9642 if total != right.total() || left_repeated != right_repeated {
9643 return None;
9644 }
9645 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
9646 Some(CallableArity::new(required, total, left_repeated))
9647}
9648
9649fn find_include_activation(
9650 cpp: &dyn CppSource,
9651 token: QueryToken<'_>,
9652 file: &ProjectFile,
9653 prepared: &PreparedSyntaxTree,
9654 donor_source: &ProjectFile,
9655) -> Option<usize> {
9656 let include_targets = cpp.include_target_index();
9657 let mut direct_includes = Vec::new();
9658 let mut nodes = vec![prepared.tree().root_node()];
9659 let reference = CallableReferenceContext {
9662 file,
9663 position: None,
9664 };
9665 while let Some(node) = nodes.pop() {
9666 if node.kind() == "preproc_include" {
9667 if callable_preprocessor_context_is_visible_for_reference(
9668 node,
9669 prepared.source(),
9670 &reference,
9671 ) {
9672 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9673 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9674 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
9675 file,
9676 &include,
9677 include_targets,
9678 )) {
9679 direct_includes.push((node.end_byte(), target));
9680 }
9681 }
9682 }
9683 continue;
9684 }
9685 for index in (0..node.named_child_count()).rev() {
9686 if let Some(child) = node.named_child(index) {
9687 nodes.push(child);
9688 }
9689 }
9690 }
9691 direct_includes.sort_by_key(|(activation, _)| *activation);
9692 let mut known_missing = HashSet::default();
9693 direct_includes
9694 .into_iter()
9695 .find(|(_, direct)| {
9696 unconditional_include_reaches(
9697 cpp,
9698 token,
9699 include_targets,
9700 direct,
9701 donor_source,
9702 file,
9703 &mut known_missing,
9704 )
9705 })
9706 .map(|(activation, _)| activation)
9707}
9708
9709fn find_conditional_include_projection_index(
9710 cpp: &dyn CppSource,
9711 token: QueryToken<'_>,
9712 file: &ProjectFile,
9713 prepared: &PreparedSyntaxTree,
9714 on_state: &dyn Fn(),
9715) -> ConditionalIncludeProjectionIndex {
9716 let include_targets = cpp.include_target_index();
9717 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
9718 HashMap::default();
9719 let mut pending = Vec::new();
9720 let mut nodes = vec![prepared.tree().root_node()];
9721 while let Some(node) = nodes.pop() {
9722 if node.kind() == "preproc_include" {
9723 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
9724 else {
9725 continue;
9726 };
9727 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9728 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9729 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9730 file,
9731 &include,
9732 include_targets,
9733 )) else {
9734 continue;
9735 };
9736 pending.push((target, node.end_byte(), required_guards.clone()));
9737 }
9738 continue;
9739 }
9740 for index in (0..node.named_child_count()).rev() {
9741 if let Some(child) = node.named_child(index) {
9742 nodes.push(child);
9743 }
9744 }
9745 }
9746
9747 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
9759 HashMap::default();
9760 while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
9761 let guard_sets = expanded
9762 .entry((current_file.clone(), activation_byte))
9763 .or_default();
9764 if guard_sets
9765 .iter()
9766 .any(|existing| existing.is_subset(&required_guards))
9767 {
9768 continue;
9769 }
9770 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
9771 .drain(..)
9772 .partition(|existing| required_guards.is_subset(existing));
9773 *guard_sets = kept;
9774 guard_sets.push(required_guards.clone());
9775 if !evicted.is_empty()
9776 && let Some(projections) = projections_by_source.get_mut(¤t_file)
9777 {
9778 projections.retain(|projection| {
9779 projection.activation_byte != activation_byte
9780 || !evicted.contains(&projection.required_guards)
9781 });
9782 }
9783 on_state();
9784
9785 projections_by_source
9788 .entry(current_file.clone())
9789 .or_default()
9790 .push(ConditionalIncludeProjection {
9791 activation_byte,
9792 required_guards: required_guards.clone(),
9793 });
9794
9795 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
9796 continue;
9797 };
9798 let mut nodes = vec![current_prepared.tree().root_node()];
9799 while let Some(node) = nodes.pop() {
9800 if node.kind() == "preproc_include" {
9801 let Some(include_guards) =
9802 preprocessor_guard_environment(node, current_prepared.source())
9803 else {
9804 continue;
9805 };
9806 let Some(path_guards) =
9807 merge_preprocessor_guards(&required_guards, &include_guards)
9808 else {
9809 continue;
9810 };
9811 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9812 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9813 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9814 ¤t_file,
9815 &include,
9816 include_targets,
9817 )) else {
9818 continue;
9819 };
9820 pending.push((target, activation_byte, path_guards.clone()));
9821 }
9822 continue;
9823 }
9824 for index in (0..node.named_child_count()).rev() {
9825 if let Some(child) = node.named_child(index) {
9826 nodes.push(child);
9827 }
9828 }
9829 }
9830 }
9831
9832 projections_by_source
9833 .into_iter()
9834 .map(|(source, mut projections)| {
9835 projections.sort_by_key(|projection| projection.activation_byte);
9836 (source, Arc::from(projections))
9837 })
9838 .collect()
9839}
9840
9841#[allow(clippy::too_many_arguments)]
9846fn find_conditional_include_projection_for_source(
9847 cpp: &dyn CppSource,
9848 token: QueryToken<'_>,
9849 file: &ProjectFile,
9850 prepared: &PreparedSyntaxTree,
9851 donor_source: &ProjectFile,
9852 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9853 reference_byte: usize,
9854 on_state: &dyn Fn(),
9855) -> bool {
9856 let Some(reference_guards) = reference_guards else {
9857 return false;
9858 };
9859 let include_targets = cpp.include_target_index();
9860 let mut pending = Vec::new();
9861 let mut nodes = vec![prepared.tree().root_node()];
9862 while let Some(node) = nodes.pop() {
9863 if node.kind() == "preproc_include" {
9864 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
9865 else {
9866 continue;
9867 };
9868 if node.end_byte() > reference_byte
9869 || !guard_requirements_hold_at_reference(&required_guards, Some(reference_guards))
9870 {
9871 continue;
9872 }
9873 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9874 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9875 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9876 file,
9877 &include,
9878 include_targets,
9879 )) else {
9880 continue;
9881 };
9882 if &target == donor_source {
9883 return true;
9884 }
9885 pending.push((target, required_guards.clone()));
9886 }
9887 continue;
9888 }
9889 for index in (0..node.named_child_count()).rev() {
9890 if let Some(child) = node.named_child(index) {
9891 nodes.push(child);
9892 }
9893 }
9894 }
9895
9896 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
9897 while let Some((current_file, required_guards)) = pending.pop() {
9898 let guard_sets = expanded.entry(current_file.clone()).or_default();
9899 if guard_sets.contains(&required_guards) {
9900 continue;
9901 }
9902 guard_sets.push(required_guards.clone());
9903 on_state();
9904
9905 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
9906 continue;
9907 };
9908 let mut nodes = vec![current_prepared.tree().root_node()];
9909 while let Some(node) = nodes.pop() {
9910 if node.kind() == "preproc_include" {
9911 let Some(include_guards) =
9912 preprocessor_guard_environment(node, current_prepared.source())
9913 else {
9914 continue;
9915 };
9916 let Some(path_guards) =
9917 merge_preprocessor_guards(&required_guards, &include_guards)
9918 else {
9919 continue;
9920 };
9921 if !guard_requirements_hold_at_reference(&path_guards, Some(reference_guards)) {
9922 continue;
9923 }
9924 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9925 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9926 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9927 ¤t_file,
9928 &include,
9929 include_targets,
9930 )) else {
9931 continue;
9932 };
9933 if &target == donor_source {
9934 return true;
9935 }
9936 pending.push((target, path_guards.clone()));
9937 }
9938 continue;
9939 }
9940 for index in (0..node.named_child_count()).rev() {
9941 if let Some(child) = node.named_child(index) {
9942 nodes.push(child);
9943 }
9944 }
9945 }
9946 }
9947 false
9948}
9949
9950pub fn cpp_include_closure_reaches(
9963 cpp: &dyn CppSource,
9964 token: QueryToken<'_>,
9965 translation_unit: &ProjectFile,
9966 header: &ProjectFile,
9967) -> bool {
9968 unconditional_include_reaches(
9969 cpp,
9970 token,
9971 cpp.include_target_index(),
9972 translation_unit,
9973 header,
9974 translation_unit,
9975 &mut HashSet::default(),
9976 )
9977}
9978
9979fn unconditional_include_reaches(
9980 cpp: &dyn CppSource,
9981 token: QueryToken<'_>,
9982 include_targets: &IncludeTargetIndex,
9983 first: &ProjectFile,
9984 donor_source: &ProjectFile,
9985 reference_file: &ProjectFile,
9986 known_missing: &mut HashSet<ProjectFile>,
9987) -> bool {
9988 if first == donor_source {
9989 return true;
9990 }
9991 if known_missing.contains(first) {
9992 return false;
9993 }
9994 let reference_is_c = reference_file
9995 .rel_path()
9996 .extension()
9997 .and_then(|extension| extension.to_str())
9998 == Some("c");
9999 if let Some(reaches) =
10000 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
10001 {
10002 return reaches;
10003 }
10004 let mut visited = HashSet::default();
10005 let mut files = vec![first.clone()];
10006 let reference = CallableReferenceContext {
10009 file: reference_file,
10010 position: None,
10011 };
10012 while let Some(file) = files.pop() {
10013 if file == *donor_source {
10014 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
10015 return true;
10016 }
10017 if known_missing.contains(&file) || !visited.insert(file.clone()) {
10018 continue;
10019 }
10020 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
10021 continue;
10022 };
10023 let mut nodes = vec![prepared.tree().root_node()];
10024 while let Some(node) = nodes.pop() {
10025 if node.kind() == "preproc_include" {
10026 if callable_preprocessor_context_is_visible_for_reference(
10027 node,
10028 prepared.source(),
10029 &reference,
10030 ) {
10031 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10032 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10033 if let Some(target) = unique_include_target(
10034 resolve_include_targets_with_index(&file, &include, include_targets),
10035 ) {
10036 files.push(target);
10037 }
10038 }
10039 }
10040 continue;
10041 }
10042 for index in (0..node.named_child_count()).rev() {
10043 if let Some(child) = node.named_child(index) {
10044 nodes.push(child);
10045 }
10046 }
10047 }
10048 }
10049 known_missing.extend(visited);
10050 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
10051 false
10052}
10053
10054fn declaration_guard_requirements(
10055 analyzer: &CppGraphSource<'_>,
10056 cpp: &dyn CppSource,
10057 candidate: &CodeUnit,
10058) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
10059 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
10060 return Vec::new();
10061 };
10062 let root = prepared.tree().root_node();
10063 analyzer
10064 .ranges(candidate)
10065 .into_iter()
10066 .filter_map(|range| {
10067 root.descendant_for_byte_range(range.start_byte, range.end_byte)
10068 .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
10069 .map(|required| (range.start_byte, required))
10073 })
10074 .collect()
10075}
10076
10077fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
10078 analyzer
10079 .ranges(candidate)
10080 .into_iter()
10081 .map(|range| range.start_byte)
10082 .min()
10083}
10084
10085fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
10091 let (first, rest) = contexts.split_first()?;
10092 Some(
10093 first
10094 .defined_macros
10095 .iter()
10096 .filter(|name| {
10097 rest.iter()
10098 .all(|context| context.defined_macros.contains(*name))
10099 })
10100 .cloned()
10101 .collect(),
10102 )
10103}
10104
10105pub fn guard_requirements_hold_at_reference(
10106 required: &HashSet<PreprocessorGuard>,
10107 reference: Option<&HashSet<PreprocessorGuard>>,
10108) -> bool {
10109 reference.is_some_and(|active| {
10110 required
10111 .iter()
10112 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
10113 })
10114}
10115
10116fn preprocessor_guard_holds_at_reference(
10117 required: &PreprocessorGuard,
10118 active: &HashSet<PreprocessorGuard>,
10119) -> bool {
10120 if active.contains(required) {
10121 return true;
10122 }
10123 let active_expression = BooleanGuardExpression::all(
10124 active
10125 .iter()
10126 .filter_map(PreprocessorGuard::as_boolean_expression),
10127 );
10128 required
10129 .as_boolean_expression()
10130 .is_some_and(|required| active_expression.implies(&required))
10131}
10132
10133fn guards_compatible_at_reference(
10138 declaration: &HashSet<PreprocessorGuard>,
10139 reference: Option<&HashSet<PreprocessorGuard>>,
10140) -> bool {
10141 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
10142}
10143
10144pub fn preprocessor_conditional_family_range(
10153 root: Node<'_>,
10154 start_byte: usize,
10155 end_byte: usize,
10156) -> Option<(usize, usize)> {
10157 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
10158 let mut ancestor = Some(node);
10159 while let Some(current) = ancestor {
10160 if is_preprocessor_conditional(current)
10161 && preprocessor_conditional_contains_descendant(current, node)
10162 {
10163 let family = preprocessor_conditional_family_root(current);
10164 return Some((family.start_byte(), family.end_byte()));
10165 }
10166 ancestor = current.parent();
10167 }
10168 None
10169}
10170
10171fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
10172 let mut ancestor = node.parent();
10173 while let Some(current) = ancestor {
10174 if is_preprocessor_conditional(current)
10175 && preprocessor_conditional_contains_descendant(current, node)
10176 {
10177 let family = preprocessor_conditional_family_root(current);
10178 if preprocessor_conditional_family_has_terminal_else(family) {
10179 return Some(family);
10180 }
10181 }
10182 ancestor = current.parent();
10183 }
10184 None
10185}
10186
10187fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
10188 while let Some(parent) = conditional.parent() {
10189 let is_alternative = parent
10190 .child_by_field_name("alternative")
10191 .is_some_and(|alternative| {
10192 alternative.start_byte() == conditional.start_byte()
10193 && alternative.end_byte() == conditional.end_byte()
10194 });
10195 if !is_alternative {
10196 break;
10197 }
10198 conditional = parent;
10199 }
10200 conditional
10201}
10202
10203fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
10204 loop {
10205 let Some(alternative) = conditional.child_by_field_name("alternative") else {
10206 return false;
10207 };
10208 match alternative.kind() {
10209 "preproc_else" => return true,
10210 "preproc_elif" => conditional = alternative,
10211 _ => return false,
10212 }
10213 }
10214}
10215
10216pub fn preprocessor_guard_environment(
10217 node: Node<'_>,
10218 source: &str,
10219) -> Option<HashSet<PreprocessorGuard>> {
10220 let mut guards = HashSet::default();
10221 let mut ancestor = node.parent();
10222 while let Some(conditional) = ancestor {
10223 if matches!(
10224 conditional.kind(),
10225 "preproc_if" | "preproc_ifdef" | "preproc_elif"
10226 ) && !is_file_covering_include_guard(conditional, source)
10227 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
10228 && preprocessor_conditional_contains_descendant(conditional, node)
10229 {
10230 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
10231 match guard {
10232 PreprocessorGuard::Constant(true) => {
10233 ancestor = conditional.parent();
10234 continue;
10235 }
10236 PreprocessorGuard::Constant(false) => return None,
10237 _ => {}
10238 }
10239 if guards.contains(&guard.negated()) {
10240 return None;
10241 }
10242 guards.insert(guard);
10243 }
10244 ancestor = conditional.parent();
10245 }
10246 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
10247 match guard {
10248 PreprocessorGuard::Constant(true) => {}
10249 PreprocessorGuard::Constant(false) => return None,
10250 _ => {
10251 if guards.contains(&guard.negated()) {
10252 return None;
10253 }
10254 guards.insert(guard);
10255 }
10256 }
10257 }
10258 Some(guards)
10259}
10260
10261fn fragmented_statement_preprocessor_guard(
10262 descendant: Node<'_>,
10263 source: &str,
10264) -> Option<PreprocessorGuard> {
10265 let mut ancestor = descendant.parent();
10271 while let Some(statement) = ancestor {
10272 if statement.kind() == "if_statement"
10273 && let (Some(consequence), Some(alternative)) = (
10274 statement.child_by_field_name("consequence"),
10275 statement.child_by_field_name("alternative"),
10276 )
10277 && alternative.start_byte() <= descendant.start_byte()
10278 && descendant.end_byte() <= alternative.end_byte()
10279 {
10280 let mut cursor = consequence.walk();
10281 let openers = consequence
10282 .named_children(&mut cursor)
10283 .filter(|child| {
10284 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
10285 && child
10286 .child(child.child_count().saturating_sub(1))
10287 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
10288 })
10289 .collect::<Vec<_>>();
10290 if openers.len() != 1 {
10291 ancestor = statement.parent();
10292 continue;
10293 }
10294
10295 let mut terminators = Vec::new();
10296 let mut stack = vec![alternative];
10297 while let Some(node) = stack.pop() {
10298 if node.kind() == "preproc_call"
10299 && node.start_byte() >= descendant.end_byte()
10300 && node
10301 .child_by_field_name("directive")
10302 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
10303 {
10304 terminators.push(node);
10305 continue;
10306 }
10307 for index in (0..node.named_child_count()).rev() {
10308 if let Some(child) = node.named_child(index) {
10309 stack.push(child);
10310 }
10311 }
10312 }
10313 if terminators.len() == 1 {
10314 return simple_preprocessor_guard(openers[0], source);
10315 }
10316 }
10317 ancestor = statement.parent();
10318 }
10319 None
10320}
10321
10322fn preprocessor_guard_for_descendant(
10323 conditional: Node<'_>,
10324 descendant: Node<'_>,
10325 source: &str,
10326) -> Option<PreprocessorGuard> {
10327 let mut guard = simple_preprocessor_guard(conditional, source)?;
10328 if conditional
10329 .child_by_field_name("alternative")
10330 .is_some_and(|alternative| {
10331 alternative.start_byte() <= descendant.start_byte()
10332 && descendant.end_byte() <= alternative.end_byte()
10333 })
10334 {
10335 let alternative = conditional.child_by_field_name("alternative")?;
10336 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
10340 return None;
10341 }
10342 guard = guard.negated();
10343 }
10344 Some(guard)
10345}
10346
10347fn preprocessor_conditional_contains_descendant(
10348 conditional: Node<'_>,
10349 descendant: Node<'_>,
10350) -> bool {
10351 cpp_displaced_preprocessor_boundary(conditional)
10352 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
10353}
10354
10355pub fn merge_preprocessor_guards(
10356 left: &HashSet<PreprocessorGuard>,
10357 right: &HashSet<PreprocessorGuard>,
10358) -> Option<HashSet<PreprocessorGuard>> {
10359 let mut merged = left.clone();
10360 for guard in right {
10361 let boolean_negation = guard
10362 .as_boolean_expression()
10363 .map(|expression| expression.negated());
10364 if merged.contains(&guard.negated())
10365 || boolean_negation.is_some_and(|negated| {
10366 merged
10367 .iter()
10368 .filter_map(PreprocessorGuard::as_boolean_expression)
10369 .any(|existing| existing == negated)
10370 })
10371 {
10372 return None;
10373 }
10374 merged.insert(guard.clone());
10375 }
10376 Some(merged)
10377}
10378
10379fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
10380 if conditional.kind() == "preproc_ifdef" {
10381 let name = conditional.child_by_field_name("name")?;
10382 let name = node_text(name, source).to_string();
10383 return match conditional.child(0)?.kind() {
10384 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
10385 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
10386 _ => None,
10387 };
10388 }
10389 let condition = conditional.child_by_field_name("condition")?;
10390 simple_preprocessor_expression_guard(condition, source).or_else(|| {
10391 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
10392 node_text(condition, source),
10393 )))
10394 })
10395}
10396
10397fn simple_preprocessor_expression_guard(
10398 expression: Node<'_>,
10399 source: &str,
10400) -> Option<PreprocessorGuard> {
10401 match expression.kind() {
10402 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
10403 node_text(expression, source).to_string(),
10404 ))),
10405 "number_literal" => match node_text(expression, source).trim() {
10406 "0" => Some(PreprocessorGuard::Constant(false)),
10407 "1" => Some(PreprocessorGuard::Constant(true)),
10408 _ => None,
10409 },
10410 "preproc_defined" => {
10411 let identifier = (0..expression.named_child_count())
10412 .filter_map(|index| expression.named_child(index))
10413 .find(|child| child.kind() == "identifier")?;
10414 Some(PreprocessorGuard::Defined(
10415 node_text(identifier, source).to_string(),
10416 ))
10417 }
10418 "unary_expression"
10419 if expression
10420 .child_by_field_name("operator")
10421 .is_some_and(|operator| operator.kind() == "!") =>
10422 {
10423 simple_preprocessor_expression_guard(
10424 expression.child_by_field_name("argument")?,
10425 source,
10426 )
10427 .map(|guard| guard.negated())
10428 }
10429 "parenthesized_expression" => (0..expression.named_child_count())
10430 .filter_map(|index| expression.named_child(index))
10431 .next()
10432 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
10433 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
10434 expression, source,
10435 ))),
10436 _ => None,
10437 }
10438}
10439
10440fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
10441 match expression.kind() {
10442 "number_literal" => match node_text(expression, source).trim() {
10443 "0" => BooleanGuardExpression::Constant(false),
10444 "1" => BooleanGuardExpression::Constant(true),
10445 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10446 expression, source,
10447 ))),
10448 },
10449 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
10450 "preproc_defined" => {
10451 let identifier = (0..expression.named_child_count())
10452 .filter_map(|index| expression.named_child(index))
10453 .find(|child| child.kind() == "identifier");
10454 identifier.map_or_else(
10455 || {
10456 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10457 expression, source,
10458 )))
10459 },
10460 |identifier| {
10461 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
10462 },
10463 )
10464 }
10465 "unary_expression"
10466 if expression
10467 .child_by_field_name("operator")
10468 .is_some_and(|operator| operator.kind() == "!") =>
10469 {
10470 expression.child_by_field_name("argument").map_or_else(
10471 || {
10472 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10473 expression, source,
10474 )))
10475 },
10476 |argument| boolean_preprocessor_expression(argument, source).negated(),
10477 )
10478 }
10479 "parenthesized_expression" => (0..expression.named_child_count())
10480 .filter_map(|index| expression.named_child(index))
10481 .next()
10482 .map_or_else(
10483 || {
10484 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10485 expression, source,
10486 )))
10487 },
10488 |child| boolean_preprocessor_expression(child, source),
10489 ),
10490 "binary_expression" => {
10491 let operands = || {
10492 Some((
10493 boolean_preprocessor_expression(
10494 expression.child_by_field_name("left")?,
10495 source,
10496 ),
10497 boolean_preprocessor_expression(
10498 expression.child_by_field_name("right")?,
10499 source,
10500 ),
10501 ))
10502 };
10503 match expression
10504 .child_by_field_name("operator")
10505 .map(|operator| operator.kind())
10506 {
10507 Some("&&") => operands().map_or_else(
10508 || {
10509 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10510 expression, source,
10511 )))
10512 },
10513 |(left, right)| BooleanGuardExpression::all([left, right]),
10514 ),
10515 Some("||") => operands().map_or_else(
10516 || {
10517 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10518 expression, source,
10519 )))
10520 },
10521 |(left, right)| BooleanGuardExpression::any([left, right]),
10522 ),
10523 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
10524 expression, source,
10525 ))),
10526 }
10527 }
10528 _ => {
10529 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
10530 }
10531 }
10532}
10533
10534fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
10535 if targets.len() == 1 {
10536 targets.pop()
10537 } else {
10538 None
10539 }
10540}
10541
10542fn nameable_callable_declaration_nodes<'tree>(
10551 analyzer: &CppGraphSource<'_>,
10552 prepared: &'tree PreparedSyntaxTree,
10553 candidate: &CodeUnit,
10554) -> Vec<Node<'tree>> {
10555 let root = prepared.tree().root_node();
10556 analyzer
10557 .ranges(candidate)
10558 .into_iter()
10559 .filter_map(|range| {
10560 let mut declaration =
10561 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
10562 while !matches!(
10566 declaration.kind(),
10567 "declaration" | "field_declaration" | "function_definition"
10568 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
10569 declaration,
10570 prepared.source(),
10571 ) {
10572 declaration = declaration.parent()?;
10573 }
10574 let mut ancestor = declaration.parent();
10575 while let Some(node) = ancestor {
10576 if node.kind() == "function_definition"
10577 && is_recovered_declaration_scope_container(node, prepared.source())
10578 {
10579 ancestor = node.parent();
10580 continue;
10581 }
10582 if node.kind() == "compound_statement"
10583 && node.parent().is_some_and(|parent| {
10584 is_recovered_declaration_scope_container(parent, prepared.source())
10585 })
10586 {
10587 ancestor = node.parent().and_then(|parent| parent.parent());
10588 continue;
10589 }
10590 if matches!(
10591 node.kind(),
10592 "compound_statement" | "function_definition" | "lambda_expression"
10593 ) {
10594 return None;
10595 }
10596 ancestor = node.parent();
10597 }
10598 Some(declaration)
10599 })
10600 .collect()
10601}
10602
10603fn callable_declaration_activation_in_file(
10604 analyzer: &CppGraphSource<'_>,
10605 prepared: &PreparedSyntaxTree,
10606 candidate: &CodeUnit,
10607 reference: &CallableReferenceContext<'_>,
10608) -> Option<usize> {
10609 nameable_callable_declaration_nodes(analyzer, prepared, candidate)
10610 .into_iter()
10611 .filter(|declaration| {
10612 callable_preprocessor_context_is_visible_for_reference(
10613 *declaration,
10614 prepared.source(),
10615 reference,
10616 )
10617 })
10618 .map(callable_declaration_activation_byte)
10619 .min()
10620}
10621
10622fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
10627 if declaration.kind() != "function_definition" {
10628 return declaration.end_byte();
10629 }
10630 declaration
10631 .child_by_field_name("declarator")
10632 .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
10633}
10634
10635struct CallableReferenceContext<'a> {
10641 file: &'a ProjectFile,
10642 position: Option<CallableReferencePosition<'a>>,
10643}
10644
10645struct CallableReferencePosition<'a> {
10649 prepared: &'a PreparedSyntaxTree,
10650 byte: usize,
10651 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
10652}
10653
10654impl CallableReferenceContext<'_> {
10655 fn is_c(&self) -> bool {
10656 self.file
10657 .rel_path()
10658 .extension()
10659 .and_then(|extension| extension.to_str())
10660 == Some("c")
10661 }
10662
10663 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
10664 let position = self.position.as_ref()?;
10665 position
10666 .guards
10667 .get_or_init(|| {
10668 position
10669 .prepared
10670 .tree()
10671 .root_node()
10672 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
10673 .and_then(|node| {
10674 preprocessor_guard_environment(node, position.prepared.source())
10675 })
10676 })
10677 .as_ref()
10678 }
10679}
10680
10681fn callable_preprocessor_context_is_visible_for_reference(
10682 node: Node<'_>,
10683 source: &str,
10684 reference: &CallableReferenceContext<'_>,
10685) -> bool {
10686 let reference_is_c = reference.is_c();
10687 let mut ancestor = node.parent();
10688 while let Some(conditional) = ancestor {
10689 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
10690 && !is_file_covering_include_guard(conditional, source)
10691 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
10692 && preprocessor_conditional_contains_descendant(conditional, node)
10693 {
10694 let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
10695 return false;
10696 };
10697 match guard {
10698 PreprocessorGuard::Constant(true) => {}
10699 PreprocessorGuard::Constant(false) => return false,
10700 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
10701 if reference_is_c {
10702 return false;
10703 }
10704 }
10705 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
10706 if !reference_is_c {
10707 return false;
10708 }
10709 }
10710 guard => {
10716 if !reference
10717 .guards()
10718 .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
10719 {
10720 return false;
10721 }
10722 }
10723 }
10724 }
10725 ancestor = conditional.parent();
10726 }
10727 true
10728}
10729
10730fn flattened_macro_namespace_declaration_matches(
10731 analyzer: &CppGraphSource<'_>,
10732 cpp: &dyn CppSource,
10733 reference_file: &ProjectFile,
10734 visible_declaration: &CodeUnit,
10735 qualified_candidate: &CodeUnit,
10736 reference_byte: usize,
10737) -> bool {
10738 if visible_declaration.kind() != qualified_candidate.kind()
10744 || visible_declaration.identifier() != qualified_candidate.identifier()
10745 || visible_declaration.signature() != qualified_candidate.signature()
10746 || !visible_declaration.package_name().is_empty()
10747 || qualified_candidate.package_name().is_empty()
10748 {
10749 return false;
10750 }
10751
10752 let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
10753 return false;
10754 };
10755 let root = prepared.tree().root_node();
10756 let closing_brace_limit = if visible_declaration.source() == reference_file {
10757 reference_byte
10758 } else {
10759 usize::MAX
10760 };
10761
10762 analyzer
10763 .ranges(visible_declaration)
10764 .into_iter()
10765 .any(|range| {
10766 let Some(mut declaration) =
10767 root.descendant_for_byte_range(range.start_byte, range.end_byte)
10768 else {
10769 return false;
10770 };
10771 while !matches!(
10772 declaration.kind(),
10773 "declaration" | "field_declaration" | "function_definition"
10774 ) {
10775 let Some(parent) = declaration.parent() else {
10776 return false;
10777 };
10778 declaration = parent;
10779 }
10780 if declaration
10781 .parent()
10782 .is_none_or(|parent| parent.kind() != "translation_unit")
10783 || !macro_displaced_cpp_return_type(declaration, prepared.source())
10784 {
10785 return false;
10786 }
10787
10788 let mut cursor = root.walk();
10789 root.named_children(&mut cursor).any(|sibling| {
10790 sibling.start_byte() >= declaration.end_byte()
10791 && sibling.start_byte() < closing_brace_limit
10792 && direct_unmatched_closing_brace(sibling)
10793 })
10794 })
10795}
10796
10797fn flattened_macro_namespace_components(
10798 declaration: Node<'_>,
10799 source: &str,
10800) -> Option<Vec<String>> {
10801 flattened_macro_function_namespace_components(declaration, source)
10802 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
10803}
10804
10805fn flattened_macro_function_namespace_components(
10806 declaration: Node<'_>,
10807 source: &str,
10808) -> Option<Vec<String>> {
10809 let body = declaration
10810 .parent()
10811 .filter(|parent| parent.kind() == "compound_statement")?;
10812 let function = body.parent()?;
10813 if function.child_by_field_name("body") != Some(body) {
10814 return None;
10815 }
10816 let namespace_name = recovered_macro_namespace_name(function, source)?;
10817 let mut components = enclosing_namespace_components(declaration, source)?;
10818 components.push(namespace_name);
10819 Some(components)
10820}
10821
10822fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
10833 if function.kind() != "function_definition" || !function.has_error() {
10834 return None;
10835 }
10836 let body = function
10837 .child_by_field_name("body")
10838 .filter(|body| body.kind() == "compound_statement")?;
10839 let mut cursor = function.walk();
10840 let prefix = function
10841 .named_children(&mut cursor)
10842 .take_while(|child| child.start_byte() < body.start_byte())
10843 .filter(|child| child.kind() != "comment")
10844 .collect::<Vec<_>>();
10845 let begin_index = prefix.iter().rposition(|child| {
10846 flattened_macro_sentinel_name(*child, source)
10847 .is_some_and(|name| is_namespace_begin_sentinel(&name))
10848 })?;
10849 let mut identifiers = Vec::new();
10850 let mut stack = prefix[begin_index + 1..]
10851 .iter()
10852 .rev()
10853 .copied()
10854 .collect::<Vec<_>>();
10855 while let Some(current) = stack.pop() {
10856 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
10857 identifiers.push(identifier);
10858 continue;
10859 }
10860 let mut cursor = current.walk();
10861 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
10862 stack.extend(children.into_iter().rev());
10863 }
10864 let [keyword, namespace_name] = identifiers.as_slice() else {
10865 return None;
10866 };
10867 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
10868 {
10869 return None;
10870 }
10871 let mut next = function.next_named_sibling();
10872 let next = loop {
10873 let candidate = next?;
10874 next = candidate.next_named_sibling();
10875 if candidate.kind() != "comment" {
10876 break candidate;
10877 }
10878 };
10879 flattened_macro_sentinel_name(next, source)
10880 .is_some_and(|name| is_namespace_end_sentinel(&name))
10881 .then(|| namespace_name.clone())
10882}
10883
10884fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
10889 crate::declarations::is_recovered_exported_class_container(node, source)
10890 || recovered_macro_namespace_name(node, source).is_some()
10891}
10892
10893fn flattened_macro_error_namespace_components(
10894 declaration: Node<'_>,
10895 source: &str,
10896) -> Option<Vec<String>> {
10897 let parent = declaration
10898 .parent()
10899 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
10900 let mut cursor = parent.walk();
10901 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
10902 let declaration_index = siblings
10903 .iter()
10904 .position(|candidate| same_node(*candidate, declaration))?;
10905 let begin_index = (0..declaration_index).rev().find(|index| {
10906 flattened_macro_sentinel_name(siblings[*index], source)
10907 .is_some_and(|name| is_namespace_begin_sentinel(&name))
10908 })?;
10909
10910 let significant = siblings[begin_index + 1..declaration_index]
10911 .iter()
10912 .copied()
10913 .filter(|node| node.kind() != "comment")
10914 .collect::<Vec<_>>();
10915 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
10916 return None;
10917 };
10918 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
10919 return None;
10920 }
10921 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
10922 if significant[2..].iter().any(|node| {
10923 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
10924 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
10925 })
10926 }) {
10927 return None;
10928 }
10929
10930 let mut saw_namespace_close = false;
10931 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
10932 if sibling.kind() == "comment" {
10933 continue;
10934 }
10935 if !saw_namespace_close {
10936 if direct_unmatched_closing_brace(sibling) {
10937 saw_namespace_close = true;
10938 continue;
10939 }
10940 if flattened_macro_sentinel_name(sibling, source).is_some() {
10941 return None;
10942 }
10943 continue;
10944 }
10945 if !flattened_macro_sentinel_name(sibling, source)
10946 .is_some_and(|name| is_namespace_end_sentinel(&name))
10947 {
10948 return None;
10949 }
10950 let mut components = enclosing_namespace_components(declaration, source)?;
10951 components.push(namespace_name);
10952 return Some(components);
10953 }
10954 None
10955}
10956
10957fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
10958 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
10962 node.named_child(0)?
10963 } else {
10964 node
10965 };
10966 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
10967 node.child_by_field_name("type")
10968 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
10969 })?;
10970 (cpp_export_macro_token(&candidate)
10971 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
10972 .then_some(candidate)
10973}
10974
10975fn is_namespace_begin_sentinel(name: &str) -> bool {
10978 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
10979}
10980
10981fn is_namespace_end_sentinel(name: &str) -> bool {
10982 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
10983}
10984
10985fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
10986 if node.kind() != "ERROR" || node.named_child_count() != 1 {
10987 return None;
10988 }
10989 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
10990 (!cpp_export_macro_token(&name)).then_some(name)
10991}
10992
10993fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
10994 if !matches!(
10995 node.kind(),
10996 "identifier" | "namespace_identifier" | "type_identifier"
10997 ) {
10998 return None;
10999 }
11000 let name = normalize_cpp_whitespace(node_text(node, source));
11001 (!name.is_empty()).then_some(name)
11002}
11003
11004fn guard_requirement_sets_match(
11005 left: &[(usize, HashSet<PreprocessorGuard>)],
11006 right: &[(usize, HashSet<PreprocessorGuard>)],
11007) -> bool {
11008 left.len() == right.len()
11009 && left.iter().all(|(_, left_guards)| {
11010 right
11011 .iter()
11012 .any(|(_, right_guards)| left_guards == right_guards)
11013 })
11014 && right.iter().all(|(_, right_guards)| {
11015 left.iter()
11016 .any(|(_, left_guards)| right_guards == left_guards)
11017 })
11018}
11019
11020fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
11021 let Some(type_node) = declaration.child_by_field_name("type") else {
11022 return false;
11023 };
11024 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
11025 !type_name.is_empty()
11026 && type_name
11027 .chars()
11028 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
11029 && (0..declaration.named_child_count()).any(|index| {
11030 declaration
11031 .named_child(index)
11032 .is_some_and(|child| child.kind() == "ERROR")
11033 })
11034}
11035
11036fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
11037 node.kind() == "ERROR"
11038 && (0..node.child_count())
11039 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
11040}
11041
11042pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
11043 let mut ancestor = node.parent();
11044 while let Some(parent) = ancestor {
11045 if is_preprocessor_conditional(parent)
11046 && !is_file_covering_include_guard(parent, source)
11047 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
11048 {
11049 return false;
11050 }
11051 ancestor = parent.parent();
11052 }
11053 true
11054}
11055
11056fn is_split_cpp_language_linkage_wrapper(
11057 conditional: Node<'_>,
11058 descendant: Node<'_>,
11059 source: &str,
11060) -> bool {
11061 if conditional.child_by_field_name("alternative").is_some()
11062 || !matches!(
11063 simple_preprocessor_guard(conditional, source),
11064 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
11065 )
11066 {
11067 return false;
11068 }
11069 let mut current = descendant.parent();
11070 let linkage = loop {
11071 let Some(node) = current else {
11072 return false;
11073 };
11074 if node == conditional {
11075 return false;
11076 }
11077 if node.kind() == "linkage_specification" {
11078 break node;
11079 }
11080 current = node.parent();
11081 };
11082 if linkage
11083 .child_by_field_name("value")
11084 .is_none_or(|value| node_text(value, source) != "\"C\"")
11085 {
11086 return false;
11087 }
11088 let Some(body) = linkage.child_by_field_name("body") else {
11089 return false;
11090 };
11091 let closes_opening_branch = (0..body.named_child_count())
11092 .filter_map(|index| body.named_child(index))
11093 .take_while(|child| child.end_byte() <= descendant.start_byte())
11094 .any(|child| {
11095 child.kind() == "preproc_call"
11096 && child
11097 .child_by_field_name("directive")
11098 .is_some_and(|directive| node_text(directive, source) == "#endif")
11099 });
11100 let reopens_for_closing_brace = (0..body.named_child_count())
11101 .filter_map(|index| body.named_child(index))
11102 .skip_while(|child| child.start_byte() < descendant.end_byte())
11103 .any(|child| {
11104 matches!(
11105 simple_preprocessor_guard(child, source),
11106 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
11107 ) && (0..child.child_count()).any(|index| {
11108 child
11109 .child(index)
11110 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
11111 })
11112 });
11113 closes_opening_branch && reopens_for_closing_brace
11114}
11115
11116pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
11120 node.child_by_field_name("arguments")
11121 .or_else(|| node.child_by_field_name("parameters"))
11122 .or_else(|| node.child_by_field_name("value"))
11123 .or_else(|| first_named_child_of_kind(node, "argument_list"))
11124 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
11125}
11126
11127pub fn call_arity(node: Node<'_>) -> usize {
11128 call_arguments_node(node)
11129 .map(|args| argument_children(args).count())
11130 .unwrap_or(0)
11131}
11132
11133pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
11134 let recovered_block_arguments = recovered_block_literal_arguments(node);
11135 (0..node.child_count())
11136 .filter_map(move |index| node.child(index))
11137 .filter(|child| child.is_named() && !child.is_extra())
11138 .flat_map(move |child| {
11139 if let Some((raw, left, right)) = recovered_block_arguments
11140 && child == raw
11141 {
11142 [Some(left), Some(right)]
11143 } else {
11144 [Some(child), None]
11145 }
11146 })
11147 .flatten()
11148}
11149
11150pub fn recovered_c_new_expression_arguments(
11159 node: Node<'_>,
11160 uses_c_semantics: bool,
11161) -> Option<[Node<'_>; 2]> {
11162 if !uses_c_semantics || node.kind() != "new_expression" {
11163 return None;
11164 }
11165 let parent = node.parent()?;
11166 if parent.kind() != "argument_list" {
11167 return None;
11168 }
11169 let keyword = node.child(0)?;
11170 let error = node.child(1)?;
11171 let trailing = node.child(2)?;
11172 if node.child(3).is_some()
11173 || keyword.kind() != "new"
11174 || keyword.is_named()
11175 || keyword.child_count() != 0
11176 || error.kind() != "ERROR"
11177 || !error.is_extra()
11178 || error.child_count() != 1
11179 || error.child(0).is_none_or(|comma| comma.kind() != ",")
11180 || node.child_by_field_name("type") != Some(trailing)
11181 || trailing.kind() != "type_identifier"
11182 {
11183 return None;
11184 }
11185 Some([keyword, trailing])
11186}
11187
11188pub fn recovered_c_new_expression_argument_at(
11191 mut node: Node<'_>,
11192 start_byte: usize,
11193 end_byte: usize,
11194 uses_c_semantics: bool,
11195) -> Option<Node<'_>> {
11196 loop {
11197 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
11198 return arguments.into_iter().find(|argument| {
11199 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
11200 });
11201 }
11202 node = node.parent()?;
11203 }
11204}
11205
11206fn recovered_c_keyword_argument_count(
11207 file: &ProjectFile,
11208 call: Node<'_>,
11209 arguments: Node<'_>,
11210 source: &str,
11211) -> usize {
11212 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
11217 return 0;
11218 }
11219 let mut ancestor = Some(call);
11220 let function = loop {
11221 let Some(current) = ancestor else {
11222 return 0;
11223 };
11224 if current.kind() == "function_definition" {
11225 break current;
11226 }
11227 ancestor = current.parent();
11228 };
11229 let Some(parameters) = function
11230 .child_by_field_name("declarator")
11231 .and_then(|declarator| declarator.child_by_field_name("parameters"))
11232 else {
11233 return 0;
11234 };
11235 let displaced_parameter_keywords = (0..parameters.child_count())
11236 .filter_map(|index| parameters.child(index))
11237 .filter(|error| error.kind() == "ERROR")
11238 .filter_map(|error| {
11239 let parameter = error.prev_named_sibling()?;
11240 if parameter.kind() != "parameter_declaration"
11241 || parameter.end_byte() != error.start_byte()
11242 || extract_variable_name(parameter, source).is_some()
11243 {
11244 return None;
11245 }
11246 let mut children = (0..error.child_count())
11247 .filter_map(|index| error.child(index))
11248 .filter(|child| !child.is_extra() && !child.is_missing());
11249 let keyword = children.next()?;
11250 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
11251 .then_some(keyword)
11252 })
11253 .collect::<Vec<_>>();
11254 if displaced_parameter_keywords.is_empty() {
11255 return 0;
11256 }
11257
11258 (0..arguments.child_count())
11259 .filter_map(|index| arguments.child(index))
11260 .filter(|error| error.kind() == "ERROR" && error.is_extra())
11261 .filter(|error| {
11262 let mut children = (0..error.child_count())
11263 .filter_map(|index| error.child(index))
11264 .filter(|child| !child.is_extra() && !child.is_missing());
11265 let Some(comma) = children.next() else {
11266 return false;
11267 };
11268 let Some(keyword) = children.next() else {
11269 return false;
11270 };
11271 children.next().is_none()
11272 && comma.kind() == ","
11273 && !keyword.is_named()
11274 && keyword.child_count() == 0
11275 && displaced_parameter_keywords
11276 .iter()
11277 .any(|parameter| parameter.kind_id() == keyword.kind_id())
11278 })
11279 .count()
11280}
11281
11282fn recovered_block_literal_arguments<'tree>(
11283 arguments: Node<'tree>,
11284) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
11285 if arguments.kind() != "argument_list" {
11286 return None;
11287 }
11288 let mut raw_arguments = (0..arguments.child_count())
11289 .filter_map(|index| arguments.child(index))
11290 .filter(|child| child.is_named() && !child.is_extra());
11291 let raw = raw_arguments.next()?;
11292 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
11293 return None;
11294 }
11295
11296 let left = raw.child_by_field_name("left")?;
11297 if left.is_missing() || left.start_byte() == left.end_byte() {
11298 return None;
11299 }
11300 let right = raw.child_by_field_name("right")?;
11301 if right.kind() != "compound_literal_expression"
11302 || right.is_missing()
11303 || right
11304 .child_by_field_name("type")
11305 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
11306 || right
11307 .child_by_field_name("value")
11308 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
11309 {
11310 return None;
11311 }
11312 let has_intervening_error = (0..raw.child_count())
11313 .filter_map(|index| raw.child(index))
11314 .any(|child| {
11315 child.kind() == "ERROR"
11316 && !child.is_missing()
11317 && child.start_byte() >= left.end_byte()
11318 && child.end_byte() <= right.start_byte()
11319 });
11320 has_intervening_error.then_some((raw, left, right))
11321}
11322
11323pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
11324 match node.kind() {
11325 "new_expression" => node
11326 .child_by_field_name("type")
11327 .or_else(|| node.named_child(0)),
11328 "compound_literal_expression" => node.child_by_field_name("type"),
11329 "call_expression" => node.child_by_field_name("function"),
11330 _ => None,
11331 }
11332}
11333
11334pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
11340 if node.kind() != "cast_expression" {
11341 return None;
11342 }
11343 let descriptor = node.child_by_field_name("type")?;
11344 if descriptor.kind() == "type_descriptor" {
11345 descriptor.child_by_field_name("type")
11346 } else {
11347 Some(descriptor)
11348 }
11349}
11350
11351pub fn field_initializer_constructs_target(
11352 node: Node<'_>,
11353 ctx: &ScanCtx<'_>,
11354 owner: &CodeUnit,
11355) -> bool {
11356 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
11365 return qualified_base_initializer_constructs_target(node, ctx, owner);
11366 }
11367 let Some(name) = node
11368 .child_by_field_name("name")
11369 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
11370 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
11371 else {
11372 return false;
11373 };
11374 let field_name = node_text(name, ctx.source);
11375 ctx.visibility
11376 .visible_identifier_candidates(ctx.file, field_name)
11377 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
11378 .any(|unit| field_declares_type(unit, ctx, owner))
11379}
11380
11381fn qualified_base_initializer_constructs_target(
11382 node: Node<'_>,
11383 ctx: &ScanCtx<'_>,
11384 owner: &CodeUnit,
11385) -> bool {
11386 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
11387 return false;
11388 };
11389 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
11390 return false;
11391 };
11392 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
11393 return false;
11394 };
11395 let resolves_target = |components: &[String]| {
11396 matches!(
11397 ctx.visibility.resolve_type_components_lexically_for_target(
11398 &ctx.analyzer,
11399 ctx.file,
11400 components,
11401 is_globally_qualified_cpp_name(qualified),
11402 &lexical_scope,
11403 owner,
11404 ),
11405 LexicalTypeResolution::Resolved { unit, .. }
11406 if same_visible_symbol(&unit, owner)
11407 )
11408 };
11409 if resolves_target(&components) {
11410 return true;
11411 }
11412
11413 components
11419 .last()
11420 .is_some_and(|terminal| terminal == owner.identifier())
11421 && resolves_target(&components[..components.len() - 1])
11422}
11423
11424fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
11425 unit.signature()
11426 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
11427 || ctx
11428 .analyzer
11429 .get_source(unit, false)
11430 .is_some_and(|declaration| {
11431 field_declaration_type_matches(&declaration, unit, ctx, owner)
11432 })
11433}
11434
11435pub fn field_declared_binding(
11436 analyzer: &CppGraphSource<'_>,
11437 visibility: &VisibilityIndex<'_>,
11438 visible_from: &ProjectFile,
11439 field: &CodeUnit,
11440) -> Option<CppScanBinding> {
11441 let fact = visibility.field_declared_type_fact(analyzer, field)?;
11442 let normalized = normalize_field_type_text(&fact.type_text);
11443 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
11444 analyzer,
11445 visible_from,
11446 field,
11447 &normalized,
11448 );
11449 let resolved = match (resolved, fact.template_arguments.as_deref()) {
11450 (Some(primary), Some(arguments)) => visibility
11451 .resolve_template_arguments(visible_from, primary, arguments)
11452 .ok(),
11453 (resolved, None) => resolved,
11454 (None, Some(_)) => None,
11455 }
11456 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
11457 Some(CppScanBinding::from_type_name(
11458 normalized,
11459 resolved,
11460 fact.indirection,
11461 ))
11462}
11463
11464fn anonymous_aggregate_field_owner(
11471 analyzer: &CppGraphSource<'_>,
11472 visibility: &VisibilityIndex<'_>,
11473 visible_from: &ProjectFile,
11474 field: &CodeUnit,
11475) -> Option<CodeUnit> {
11476 let owner = type_owner_of(analyzer, field)?;
11477 if !owner.is_class() {
11478 return None;
11479 }
11480 let declaration = analyzer.get_source(field, false)?;
11481 let mut parser = Parser::new();
11482 parser
11483 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11484 .ok()?;
11485 let tree = parser.parse(&declaration, None)?;
11486 let mut stack = vec![tree.root_node()];
11487 while let Some(node) = stack.pop() {
11488 if matches!(node.kind(), "declaration" | "field_declaration")
11489 && let Some(type_node) = node
11490 .child_by_field_name("type")
11491 .or_else(|| first_type_child(node))
11492 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
11493 && type_node.child_by_field_name("name").is_none()
11494 && declared_name_indirection(node, type_node, field.identifier(), &declaration)
11495 .is_some()
11496 {
11497 let matches = visibility
11498 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
11499 .into_iter()
11500 .filter(|child| child.is_class() && child.identifier() == field.identifier())
11501 .collect::<Vec<_>>();
11502 return match matches.as_slice() {
11503 [child] => Some((*child).clone()),
11504 _ => None,
11505 };
11506 }
11507 let mut cursor = node.walk();
11508 stack.extend(node.named_children(&mut cursor));
11509 }
11510 None
11511}
11512
11513pub fn anonymous_aggregate_owner(
11520 analyzer: &CppGraphSource<'_>,
11521 file: &ProjectFile,
11522 node: Node<'_>,
11523) -> Option<CodeUnit> {
11524 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
11525 || node.child_by_field_name("name").is_some()
11526 {
11527 return None;
11528 }
11529 let mut candidates = analyzer
11530 .declarations(file)
11531 .into_iter()
11532 .filter(|candidate| {
11533 candidate.is_class()
11534 && analyzer.ranges(candidate).into_iter().any(|range| {
11535 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
11536 })
11537 })
11538 .collect::<Vec<_>>();
11539 candidates.sort_by_key(|candidate| candidate.fq_name());
11540 candidates.dedup();
11541 match candidates.as_slice() {
11542 [candidate] => Some(candidate.clone()),
11543 _ => None,
11544 }
11545}
11546
11547fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
11549 let Some(first) = candidates.first() else {
11550 return Err(TypeCandidateFailure::Unresolvable);
11551 };
11552 if candidates
11553 .iter()
11554 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
11555 {
11556 Ok((*first).clone())
11557 } else {
11558 Err(TypeCandidateFailure::Ambiguous)
11559 }
11560}
11561
11562fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
11563 logical_type_candidate(candidates).ok()
11564}
11565
11566fn unique_type_candidate_preserving_alias(
11567 analyzer: &CppGraphSource<'_>,
11568 candidates: &[&CodeUnit],
11569) -> Option<CodeUnit> {
11570 let first = *candidates.first()?;
11571 if declared_type_alias(analyzer, first) {
11572 return candidates
11573 .iter()
11574 .all(|candidate| {
11575 declared_type_alias(analyzer, candidate)
11576 && candidate.kind() == first.kind()
11577 && candidate.fq_name() == first.fq_name()
11578 && candidate.source() == first.source()
11579 })
11580 .then(|| first.clone());
11581 }
11582 if first.is_class() && indexed_c_tag_kind(analyzer, first).is_some() {
11583 let mut full_source = None;
11584 let mut tag_kind = None;
11585 for candidate in candidates.iter().copied() {
11586 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
11587 if tag_kind
11588 .replace(candidate_tag_kind)
11589 .is_some_and(|existing| existing != candidate_tag_kind)
11590 {
11591 return None;
11592 }
11593 if cpp_class_declaration_strength(analyzer, candidate)
11594 == CppClassDeclarationStrength::Full
11595 && full_source
11596 .replace(candidate.source())
11597 .is_some_and(|existing| existing != candidate.source())
11598 {
11599 return None;
11600 }
11601 }
11602 }
11603 candidates
11604 .iter()
11605 .all(|candidate| {
11606 !declared_type_alias(analyzer, candidate)
11607 && candidate.kind() == first.kind()
11608 && candidate.fq_name() == first.fq_name()
11609 })
11610 .then(|| first.clone())
11611}
11612
11613fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
11614 is_type_alias(unit)
11615 || analyzer
11616 .type_alias_provider()
11617 .is_some_and(|provider| provider.is_type_alias(unit))
11618}
11619
11620pub fn field_declared_type_binding(
11621 analyzer: &CppGraphSource<'_>,
11622 visibility: &VisibilityIndex<'_>,
11623 visible_from: &ProjectFile,
11624 field: &CodeUnit,
11625) -> Option<(String, Option<CodeUnit>, i32)> {
11626 let fact = visibility.field_declared_type_fact(analyzer, field)?;
11627 let normalized = normalize_field_type_text(&fact.type_text);
11628 let primary = visibility.resolve_unique_canonical_type_for_declaration(
11629 analyzer,
11630 visible_from,
11631 field,
11632 &normalized,
11633 );
11634 let resolved = match (primary, fact.template_arguments.as_deref()) {
11635 (Some(primary), Some(arguments)) => visibility
11636 .resolve_template_arguments(visible_from, primary, arguments)
11637 .ok(),
11638 (resolved, None) => resolved,
11639 (None, Some(_)) => None,
11640 };
11641 Some((normalized, resolved, fact.indirection))
11642}
11643
11644fn decode_field_declared_type_fact(
11645 analyzer: &CppGraphSource<'_>,
11646 field: &CodeUnit,
11647) -> Option<DeclaredFieldTypeFact> {
11648 let declaration = analyzer.get_source(field, false)?;
11649 let mut parser = Parser::new();
11650 parser
11651 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11652 .ok()?;
11653 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
11657 let contextual_tree = parser.parse(&contextual_declaration, None)?;
11658 let mut stack = vec![contextual_tree.root_node()];
11659 while let Some(node) = stack.pop() {
11660 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
11661 && node_text(recovered.declarator, &contextual_declaration) == field.identifier()
11662 {
11663 return Some(DeclaredFieldTypeFact {
11664 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
11665 indirection: 0,
11666 template_arguments: None,
11667 });
11668 }
11669 if let Some(recovered) =
11670 recovered_function_like_field_declarator(node, &contextual_declaration)
11671 && node_text(recovered.name, &contextual_declaration) == field.identifier()
11672 {
11673 let type_node = node
11674 .child_by_field_name("type")
11675 .or_else(|| first_type_child(node))?;
11676 return Some(DeclaredFieldTypeFact {
11677 type_text: node_text(type_node, &contextual_declaration).to_string(),
11678 indirection: recovered.pointer_depth(),
11679 template_arguments: cpp_template_reference_arguments(
11680 type_node,
11681 &contextual_declaration,
11682 ),
11683 });
11684 }
11685 if let Some(fact) =
11686 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
11687 {
11688 return Some(fact);
11689 }
11690 let mut cursor = node.walk();
11691 stack.extend(node.named_children(&mut cursor));
11692 }
11693 let tree = parser.parse(&declaration, None)?;
11694 let mut stack = vec![tree.root_node()];
11695 while let Some(node) = stack.pop() {
11696 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
11697 {
11698 return Some(fact);
11699 }
11700 let mut cursor = node.walk();
11701 stack.extend(node.named_children(&mut cursor));
11702 }
11703 None
11704}
11705
11706fn decode_declared_field_type_node(
11707 node: Node<'_>,
11708 field_name: &str,
11709 source: &str,
11710) -> Option<DeclaredFieldTypeFact> {
11711 if !matches!(node.kind(), "declaration" | "field_declaration") {
11712 return None;
11713 }
11714 let type_node = node
11715 .child_by_field_name("type")
11716 .or_else(|| first_type_child(node))?;
11717 let indirection = declared_name_indirection(node, type_node, field_name, source)?;
11718 let declared_type = if matches!(
11719 type_node.kind(),
11720 "class_specifier" | "struct_specifier" | "union_specifier"
11721 ) {
11722 type_node.child_by_field_name("name")
11723 } else {
11724 Some(type_node)
11725 };
11726 Some(DeclaredFieldTypeFact {
11727 type_text: declared_type.map_or_else(
11728 || field_name.to_string(),
11729 |declared_type| node_text(declared_type, source).to_string(),
11730 ),
11731 indirection,
11732 template_arguments: declared_type
11733 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
11734 })
11735}
11736
11737pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
11751 let mut parser = Parser::new();
11752 parser
11753 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11754 .ok()?;
11755 let tree = parser.parse(declaration, None)?;
11756 let mut stack = vec![tree.root_node()];
11757 while let Some(node) = stack.pop() {
11758 let type_node = match node.kind() {
11759 "type_definition" => {
11760 let mut cursor = node.walk();
11761 if node
11762 .children_by_field_name("declarator", &mut cursor)
11763 .any(declarator_names_function_type)
11764 {
11765 return None;
11766 }
11767 node.child_by_field_name("type")?
11768 }
11769 "alias_declaration" => {
11770 let type_node = node.child_by_field_name("type")?;
11771 if type_node
11772 .child_by_field_name("declarator")
11773 .is_some_and(declarator_names_function_type)
11774 {
11775 return None;
11776 }
11777 type_node
11778 }
11779 _ => {
11780 let mut cursor = node.walk();
11781 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
11782 stack.extend(children.into_iter().rev());
11783 continue;
11784 }
11785 };
11786 return Some(node_text(type_node, declaration).to_string());
11787 }
11788 None
11789}
11790
11791fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
11800 let mut parser = Parser::new();
11801 if parser
11802 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11803 .is_err()
11804 {
11805 return true;
11806 }
11807 let Some(tree) = parser.parse(declaration, None) else {
11808 return true;
11809 };
11810 let mut stack = vec![tree.root_node()];
11811 while let Some(node) = stack.pop() {
11812 let declarators = match node.kind() {
11813 "type_definition" => {
11814 let mut cursor = node.walk();
11815 node.children_by_field_name("declarator", &mut cursor)
11816 .collect::<Vec<_>>()
11817 }
11818 "alias_declaration" => node
11819 .child_by_field_name("type")
11820 .and_then(|type_node| type_node.child_by_field_name("declarator"))
11821 .into_iter()
11822 .collect::<Vec<_>>(),
11823 _ => {
11824 let mut cursor = node.walk();
11825 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
11826 stack.extend(children.into_iter().rev());
11827 continue;
11828 }
11829 };
11830 return declarators.into_iter().any(cpp_declarator_adds_indirection);
11831 }
11832 true
11833}
11834
11835fn declarator_names_function_type(declarator: Node<'_>) -> bool {
11841 let mut current = Some(declarator);
11842 while let Some(node) = current {
11843 match node.kind() {
11844 "function_declarator" | "abstract_function_declarator" => return true,
11845 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
11846 current = node.named_child(0);
11847 }
11848 _ => current = node.child_by_field_name("declarator"),
11849 }
11850 }
11851 false
11852}
11853
11854pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
11858 let mut parser = Parser::new();
11859 if parser
11860 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11861 .is_err()
11862 {
11863 return false;
11864 }
11865 let Some(tree) = parser.parse(declaration, None) else {
11866 return false;
11867 };
11868 let mut stack = vec![tree.root_node()];
11869 while let Some(node) = stack.pop() {
11870 if matches!(node.kind(), "declaration" | "field_declaration") {
11871 let mut cursor = node.walk();
11872 if node
11873 .children_by_field_name("declarator", &mut cursor)
11874 .any(|declarator| {
11875 declarator_name_node(declarator).is_some_and(|name| {
11876 node_text(name, declaration) == field_name
11877 && declarator_names_function_type(declarator)
11878 })
11879 })
11880 {
11881 return true;
11882 }
11883 }
11884 let mut cursor = node.walk();
11885 stack.extend(node.named_children(&mut cursor));
11886 }
11887 false
11888}
11889
11890pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
11894 let mut parser = Parser::new();
11895 if parser
11896 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11897 .is_err()
11898 {
11899 return false;
11900 }
11901 let Some(tree) = parser.parse(declaration, None) else {
11902 return false;
11903 };
11904 let mut stack = vec![tree.root_node()];
11905 while let Some(node) = stack.pop() {
11906 match node.kind() {
11907 "type_definition" => {
11908 let mut cursor = node.walk();
11909 if node
11910 .children_by_field_name("declarator", &mut cursor)
11911 .any(|declarator| {
11912 extract_typedef_declarator_name(declarator, declaration)
11913 .is_some_and(|name| name == alias_name)
11914 && declarator_names_function_type(declarator)
11915 })
11916 {
11917 return true;
11918 }
11919 }
11920 "alias_declaration" => {
11921 let names_alias = node
11922 .child_by_field_name("name")
11923 .is_some_and(|name| node_text(name, declaration) == alias_name);
11924 if names_alias
11925 && node
11926 .child_by_field_name("type")
11927 .and_then(|type_node| type_node.child_by_field_name("declarator"))
11928 .is_some_and(declarator_names_function_type)
11929 {
11930 return true;
11931 }
11932 }
11933 _ => {}
11934 }
11935 let mut cursor = node.walk();
11936 stack.extend(node.named_children(&mut cursor));
11937 }
11938 false
11939}
11940
11941fn decode_structured_alias_target(
11942 analyzer: &CppGraphSource<'_>,
11943 unit: &CodeUnit,
11944) -> Option<StructuredAliasTarget> {
11945 analyzer
11946 .get_source(unit, false)
11947 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
11948 .or_else(|| {
11949 let signature = unit.signature()?;
11950 decode_structured_alias_target_source(unit, signature, false)
11951 })
11952}
11953
11954fn decode_structured_alias_target_source(
11955 unit: &CodeUnit,
11956 declaration: &str,
11957 require_top_level: bool,
11958) -> Option<StructuredAliasTarget> {
11959 let mut parser = Parser::new();
11960 parser
11961 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11962 .ok()?;
11963 let tree = parser.parse(declaration, None)?;
11964 let mut stack = vec![tree.root_node()];
11965 while let Some(node) = stack.pop() {
11966 let type_node = match node.kind() {
11967 "type_definition" => {
11968 if require_top_level
11969 && node
11970 .parent()
11971 .is_none_or(|parent| parent.kind() != "translation_unit")
11972 {
11973 let mut cursor = node.walk();
11974 stack.extend(node.named_children(&mut cursor));
11975 continue;
11976 }
11977 let mut declarator_cursor = node.walk();
11978 let declarator = node
11979 .children_by_field_name("declarator", &mut declarator_cursor)
11980 .find(|declarator| {
11981 extract_typedef_declarator_name(*declarator, declaration)
11982 .is_some_and(|name| name == unit.identifier())
11983 })?;
11984 if declarator_names_function_type(declarator) {
11985 return None;
11986 }
11987 node.child_by_field_name("type")?
11988 }
11989 "alias_declaration" => {
11990 if require_top_level
11991 && node
11992 .parent()
11993 .is_none_or(|parent| parent.kind() != "translation_unit")
11994 {
11995 let mut cursor = node.walk();
11996 stack.extend(node.named_children(&mut cursor));
11997 continue;
11998 }
11999 let name = node.child_by_field_name("name")?;
12000 if node_text(name, declaration) != unit.identifier() {
12001 return None;
12002 }
12003 let type_node = node.child_by_field_name("type")?;
12004 if type_node
12005 .child_by_field_name("declarator")
12006 .is_some_and(declarator_names_function_type)
12007 {
12008 return None;
12009 }
12010 type_node
12011 }
12012 _ => {
12013 let mut cursor = node.walk();
12014 stack.extend(node.named_children(&mut cursor));
12015 continue;
12016 }
12017 };
12018 return structured_alias_type_target(type_node, declaration);
12019 }
12020 None
12021}
12022
12023fn structured_alias_type_target(
12024 mut type_node: Node<'_>,
12025 source: &str,
12026) -> Option<StructuredAliasTarget> {
12027 while type_node.kind() == "type_descriptor" {
12028 type_node = type_node.child_by_field_name("type")?;
12029 }
12030 if type_node.kind() == "primitive_type" {
12031 return Some(StructuredAliasTarget::Builtin);
12032 }
12033 if matches!(
12034 type_node.kind(),
12035 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
12036 ) {
12037 type_node = type_node.child_by_field_name("name")?;
12038 }
12039 let global = type_node.child_by_field_name("scope").is_none()
12040 && type_node.child(0).is_some_and(|child| child.kind() == "::");
12041 let mut components = Vec::new();
12042 append_structured_type_components(type_node, source, &mut components)?;
12043 let arguments = cpp_template_reference_arguments(type_node, source);
12044 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
12045 components,
12046 global,
12047 arguments,
12048 })
12049}
12050
12051fn append_structured_type_components(
12052 node: Node<'_>,
12053 source: &str,
12054 out: &mut Vec<String>,
12055) -> Option<()> {
12056 match node.kind() {
12057 "identifier" | "namespace_identifier" | "type_identifier" => {
12058 out.push(node_text(node, source).to_string());
12059 Some(())
12060 }
12061 "template_type" => {
12062 append_structured_type_components(node.child_by_field_name("name")?, source, out)
12063 }
12064 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
12065 if let Some(scope) = node.child_by_field_name("scope") {
12066 append_structured_type_components(scope, source, out)?;
12067 }
12068 append_structured_type_components(node.child_by_field_name("name")?, source, out)
12069 }
12070 _ => None,
12071 }
12072}
12073
12074fn declared_name_indirection(
12075 declaration: Node<'_>,
12076 type_node: Node<'_>,
12077 field_name: &str,
12078 source: &str,
12079) -> Option<i32> {
12080 let mut stack = Vec::new();
12081 let mut cursor = declaration.walk();
12082 stack.extend(
12083 declaration
12084 .named_children(&mut cursor)
12085 .filter(|child| !same_node(*child, type_node)),
12086 );
12087 while let Some(node) = stack.pop() {
12088 if matches!(node.kind(), "identifier" | "field_identifier")
12089 && node_text(node, source) == field_name
12090 {
12091 let mut indirection = 0;
12092 let mut current = node.parent();
12093 while let Some(parent) = current {
12094 if same_node(parent, declaration) {
12095 return Some(indirection);
12096 }
12097 if parent.kind() == "pointer_declarator" {
12098 indirection += 1;
12099 }
12100 current = parent.parent();
12101 }
12102 return None;
12103 }
12104 let mut cursor = node.walk();
12105 stack.extend(node.named_children(&mut cursor));
12106 }
12107 None
12108}
12109
12110fn field_declaration_type_matches(
12111 declaration: &str,
12112 unit: &CodeUnit,
12113 ctx: &ScanCtx<'_>,
12114 owner: &CodeUnit,
12115) -> bool {
12116 ctx.visibility
12117 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
12118 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
12119 let normalized = normalize_field_type_text(type_text);
12120 ctx.visibility
12121 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
12122 || ctx.visibility.resolves_to_type(
12123 &ctx.analyzer,
12124 ctx.file,
12125 normalized.as_str(),
12126 owner,
12127 )
12128 })
12129}
12130
12131fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
12132 let declaration = declaration
12133 .split(['=', ';'])
12134 .next()
12135 .unwrap_or(declaration)
12136 .trim();
12137 let index = declaration.rfind(field_name)?;
12138 let before = &declaration[..index];
12139 let after = &declaration[index + field_name.len()..];
12140 if before.chars().next_back().is_some_and(is_identifier_char)
12141 || after.chars().next().is_some_and(is_identifier_char)
12142 {
12143 return None;
12144 }
12145 Some(before.trim())
12146}
12147
12148fn normalize_field_type_text(type_text: &str) -> String {
12149 const FIELD_SPECIFIERS: [&str; 8] = [
12150 "extern ",
12151 "static ",
12152 "mutable ",
12153 "constexpr ",
12154 "constinit ",
12155 "inline ",
12156 "volatile ",
12157 "const ",
12158 ];
12159
12160 let mut normalized = normalize_type_text(type_text);
12161 loop {
12162 let Some(stripped) = FIELD_SPECIFIERS
12163 .iter()
12164 .find_map(|specifier| normalized.strip_prefix(specifier))
12165 else {
12166 return normalized;
12167 };
12168 normalized = normalize_type_text(stripped);
12169 }
12170}
12171
12172fn is_identifier_char(ch: char) -> bool {
12173 ch == '_' || ch.is_ascii_alphanumeric()
12174}
12175
12176pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12177 let Some(type_node) = node.child_by_field_name("type") else {
12178 return false;
12179 };
12180 ctx.visibility.resolves_to_type(
12181 &ctx.analyzer,
12182 ctx.file,
12183 node_text(type_node, ctx.source),
12184 owner,
12185 )
12186}
12187
12188pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12189 !ctx.analyzer
12190 .declarations(ctx.file)
12191 .into_iter()
12192 .filter(|unit| unit.is_function())
12193 .any(|unit| {
12194 ctx.analyzer.ranges(&unit).iter().any(|range| {
12195 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
12196 })
12197 })
12198}
12199
12200pub enum DeclarationConstructorInitializer<'tree> {
12202 Arguments(Node<'tree>),
12205 Expression(Node<'tree>),
12208 Empty,
12210}
12211
12212pub fn declaration_constructor_initializer(
12213 node: Node<'_>,
12214) -> DeclarationConstructorInitializer<'_> {
12215 let mut cursor = node.walk();
12216 for child in node.named_children(&mut cursor) {
12217 if child.kind() == "init_declarator" {
12218 let Some(value) = child
12219 .child_by_field_name("value")
12220 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
12221 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
12222 else {
12223 return DeclarationConstructorInitializer::Empty;
12224 };
12225 return match value.kind() {
12226 "argument_list" | "initializer_list" => {
12227 DeclarationConstructorInitializer::Arguments(value)
12228 }
12229 "compound_literal_expression" => call_arguments_node(value)
12230 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
12231 DeclarationConstructorInitializer::Arguments(arguments)
12232 }),
12233 _ => DeclarationConstructorInitializer::Expression(value),
12234 };
12235 }
12236 if is_declarator_node(child) {
12237 return declarator_parameters(child)
12238 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
12239 DeclarationConstructorInitializer::Arguments(parameters)
12240 });
12241 }
12242 }
12243 DeclarationConstructorInitializer::Empty
12244}
12245
12246pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
12247 match declaration_constructor_initializer(node) {
12248 DeclarationConstructorInitializer::Arguments(arguments) => {
12249 argument_children(arguments).count()
12250 }
12251 DeclarationConstructorInitializer::Expression(_) => 1,
12252 DeclarationConstructorInitializer::Empty => 0,
12253 }
12254}
12255
12256fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
12260 let mut current = node;
12261 loop {
12262 if let Some(parameters) = current.child_by_field_name("parameters") {
12263 return Some(parameters);
12264 }
12265 current = current.child_by_field_name("declarator")?;
12266 }
12267}
12268
12269pub(super) fn first_named_child_of_kind<'tree>(
12270 node: Node<'tree>,
12271 kind: &str,
12272) -> Option<Node<'tree>> {
12273 let mut cursor = node.walk();
12274 node.named_children(&mut cursor)
12275 .find(|child| child.kind() == kind)
12276}
12277
12278fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
12279 let mut stack = vec![root];
12280 while let Some(node) = stack.pop() {
12281 if node.kind() == kind {
12282 return Some(node);
12283 }
12284 for index in (0..node.named_child_count()).rev() {
12285 if let Some(child) = node.named_child(index) {
12286 stack.push(child);
12287 }
12288 }
12289 }
12290 None
12291}
12292
12293fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
12294 if node.kind() == "identifier" {
12295 return true;
12296 }
12297 if node.kind() == "parenthesized_expression" {
12298 return false;
12299 }
12300 if node.kind() == "call_expression" {
12301 return node
12302 .child_by_field_name("function")
12303 .is_some_and(|function| function.kind() == "identifier");
12304 }
12305 let mut stack = vec![node];
12306 while let Some(descendant) = stack.pop() {
12307 if descendant != node && descendant.kind() == "parenthesized_expression" {
12308 continue;
12309 }
12310 if descendant.kind() == "identifier" {
12311 return true;
12312 }
12313 if descendant.kind() == "call_expression" {
12314 if descendant
12315 .child_by_field_name("function")
12316 .is_some_and(|function| function.kind() == "identifier")
12317 {
12318 return true;
12319 }
12320 continue;
12321 }
12322 for index in (0..descendant.named_child_count()).rev() {
12323 if let Some(child) = descendant.named_child(index) {
12324 stack.push(child);
12325 }
12326 }
12327 }
12328 false
12329}
12330
12331fn macro_expansion_shape_is_safe(
12332 node: Node<'_>,
12333 source: &str,
12334 parameters: &[String],
12335 environment: &MacroEnvironment,
12336) -> bool {
12337 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
12338 return true;
12339 }
12340 if node.kind() == "call_expression" {
12341 let Some(function) = node.child_by_field_name("function") else {
12342 return true;
12343 };
12344 if function.kind() != "identifier" {
12345 return true;
12346 }
12347 let function_name = node_text(function, source);
12348 if parameters
12349 .iter()
12350 .any(|parameter| parameter == function_name)
12351 {
12352 return false;
12353 }
12354 if !environment.may_bind(function_name) {
12355 return true;
12356 }
12357 let Some(arguments) = node.child_by_field_name("arguments") else {
12358 return false;
12359 };
12360 return argument_children(arguments).all(|argument| {
12361 if argument.kind() == "identifier"
12362 && parameters
12363 .iter()
12364 .any(|parameter| parameter == node_text(argument, source))
12365 {
12366 return false;
12367 }
12368 macro_expansion_shape_is_safe(argument, source, parameters, environment)
12369 });
12370 }
12371 let mut stack = vec![node];
12372 while let Some(descendant) = stack.pop() {
12373 if descendant != node {
12374 if descendant.kind() == "parenthesized_expression" {
12375 continue;
12376 }
12377 if descendant.kind() == "call_expression" {
12378 let expands = descendant
12379 .child_by_field_name("function")
12380 .filter(|function| function.kind() == "identifier")
12381 .is_some_and(|function| environment.may_bind(node_text(function, source)));
12382 if expands {
12383 return false;
12384 }
12385 continue;
12386 }
12387 }
12388 if descendant.kind() == "identifier" {
12389 let identifier = node_text(descendant, source);
12390 if parameters.iter().any(|parameter| parameter == identifier)
12391 || environment.may_bind(identifier)
12392 {
12393 return false;
12394 }
12395 }
12396 for index in (0..descendant.named_child_count()).rev() {
12397 if let Some(child) = descendant.named_child(index) {
12398 stack.push(child);
12399 }
12400 }
12401 }
12402 true
12403}
12404
12405fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
12406 let text = node_text(path, source);
12407 match path.kind() {
12408 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
12409 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
12410 _ => None,
12411 }
12412}
12413
12414fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
12415 let descendant = node;
12416 while let Some(parent) = node.parent() {
12417 if is_preprocessor_conditional(parent)
12418 && !is_file_covering_include_guard(parent, source)
12419 && preprocessor_conditional_contains_descendant(parent, descendant)
12420 {
12421 return true;
12422 }
12423 node = parent;
12424 }
12425 false
12426}
12427
12428fn owning_preprocessor_conditionals(
12440 root: Node<'_>,
12441 event: Node<'_>,
12442 source: &str,
12443) -> OwningPreprocessorConditionals {
12444 if !has_preprocessor_conditional_ancestor(event, source) {
12445 return OwningPreprocessorConditionals::default();
12446 }
12447 let start = event.start_byte();
12448 let descendant = root
12449 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
12450 .expect("a byte inside the parsed tree names a descendant");
12451 let mut owners = Vec::new();
12452 let mut current = descendant.parent();
12453 while let Some(conditional) = current {
12454 if is_preprocessor_conditional(conditional)
12455 && !is_file_covering_include_guard(conditional, source)
12456 && preprocessor_conditional_contains_descendant(conditional, descendant)
12457 {
12458 owners.push(conditional.start_byte());
12459 }
12460 current = conditional.parent();
12461 }
12462 owners.into_boxed_slice()
12463}
12464
12465fn is_preprocessor_conditional(node: Node<'_>) -> bool {
12466 matches!(
12467 node.kind(),
12468 "preproc_if"
12469 | "preproc_ifdef"
12470 | "preproc_ifndef"
12471 | "preproc_elif"
12472 | "preproc_elifdef"
12473 | "preproc_else"
12474 )
12475}
12476
12477fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
12478 node.parent()
12479 .filter(|parent| parent.kind() == "translation_unit")
12480 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
12481 && is_canonical_include_guard(node, source)
12482}
12483
12484fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
12485 if node.kind() != "preproc_ifdef"
12486 || node
12487 .child(0)
12488 .is_none_or(|directive| directive.kind() != "#ifndef")
12489 || node.child_by_field_name("alternative").is_some()
12490 {
12491 return false;
12492 }
12493 let Some(guard_name) = node.child_by_field_name("name") else {
12494 return false;
12495 };
12496 let mut cursor = node.walk();
12497 node.named_children(&mut cursor)
12498 .find(|child| *child != guard_name && child.kind() != "comment")
12499 .filter(|child| child.kind() == "preproc_def")
12500 .and_then(|definition| definition.child_by_field_name("name"))
12501 .is_some_and(|defined_name| {
12502 node_text(defined_name, source) == node_text(guard_name, source)
12503 })
12504}
12505
12506fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
12507 let mut guard = None;
12508 for index in 0..root.named_child_count() {
12509 let Some(child) = root.named_child(index) else {
12510 continue;
12511 };
12512 if child.kind() == "comment" || is_pragma_once(child, source) {
12513 continue;
12514 }
12515 if guard.is_none() && is_canonical_include_guard(child, source) {
12516 guard = Some(child);
12517 } else {
12518 return None;
12519 }
12520 }
12521 guard
12522 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
12523 .map(|name| node_text(name, source).to_string())
12524}
12525
12526fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
12527 if (0..root.named_child_count())
12528 .filter_map(|index| root.named_child(index))
12529 .any(|child| is_pragma_once(child, source))
12530 {
12531 return MacroIncludeProtection::PragmaOnce;
12532 }
12533 top_level_canonical_include_guard_name(root, source)
12534 .map(MacroIncludeProtection::MacroGuard)
12535 .unwrap_or(MacroIncludeProtection::None)
12536}
12537
12538fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
12539 node.kind() == "preproc_call"
12540 && node
12541 .child_by_field_name("directive")
12542 .is_some_and(|directive| node_text(directive, source) == "#pragma")
12543 && node
12544 .child_by_field_name("argument")
12545 .is_some_and(|argument| node_text(argument, source).trim() == "once")
12546}
12547
12548fn parse_preproc_identifier(argument: &str) -> Option<String> {
12549 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
12550 let mut parser = Parser::new();
12551 parser
12552 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12553 .ok()?;
12554 let tree = parser.parse(&sentinel, None)?;
12555 if tree.root_node().has_error() {
12556 return None;
12557 }
12558 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
12559 let identifier = statement.named_child(0)?;
12560 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
12561 .then(|| node_text(identifier, &sentinel).to_string())
12562}
12563
12564pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
12565 match node.kind() {
12566 "identifier" | "field_identifier" => {
12567 let name = node_text(node, source).trim();
12568 (!name.is_empty()).then(|| name.to_string())
12569 }
12570 "abstract_array_declarator"
12571 | "abstract_function_declarator"
12572 | "abstract_parenthesized_declarator"
12573 | "abstract_pointer_declarator"
12574 | "abstract_reference_declarator" => None,
12575 "function_declarator" => node
12576 .child_by_field_name("declarator")
12577 .or_else(|| node.child_by_field_name("name"))
12578 .and_then(|child| extract_variable_name(child, source)),
12579 _ => node
12580 .child_by_field_name("declarator")
12581 .or_else(|| node.child_by_field_name("name"))
12582 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
12583 .and_then(|child| extract_variable_name(child, source)),
12584 }
12585}
12586
12587pub fn is_c_source_file(file: &ProjectFile) -> bool {
12598 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
12599}
12600
12601pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
12608 if !is_c_source_file(file) || node.kind() != "identifier" {
12609 return false;
12610 }
12611 let mut operand = node;
12612 while let Some(parent) = operand.parent().filter(|parent| {
12613 parent.kind() == "parenthesized_expression"
12614 && parent.named_child_count() == 1
12615 && parent.named_child(0) == Some(operand)
12616 }) {
12617 operand = parent;
12618 }
12619 operand.parent().is_some_and(|parent| {
12620 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
12621 })
12622}
12623
12624pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
12633 if node.kind() != "field_identifier" {
12634 return None;
12635 }
12636 let expression = node.parent().filter(|parent| {
12637 parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
12638 })?;
12639 if expression.has_error() {
12640 return None;
12641 }
12642 let closing = expression.child(expression.child_count().saturating_sub(1))?;
12643 if closing.kind() != ")" || closing.is_missing() {
12644 return None;
12645 }
12646 let type_descriptor = expression.child_by_field_name("type")?;
12647 if type_descriptor.kind() != "type_descriptor"
12648 || type_descriptor.is_missing()
12649 || type_descriptor.has_error()
12650 {
12651 return None;
12652 }
12653 let type_specifier = type_descriptor.child_by_field_name("type")?;
12654 if type_specifier.is_missing() || type_specifier.has_error() {
12655 return None;
12656 }
12657 let type_reference = match type_specifier.kind() {
12658 "class_specifier" | "struct_specifier" | "union_specifier" => {
12659 type_specifier.child_by_field_name("name")?
12660 }
12661 _ => type_specifier,
12662 };
12663 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
12664}
12665
12666pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
12671 node.kind() == "field_identifier"
12672 && node.parent().is_some_and(|parent| {
12673 parent.kind() == "offsetof_expression"
12674 && parent.child_by_field_name("member") == Some(node)
12675 })
12676}
12677
12678pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
12694 if node.kind() != "type_identifier" {
12695 return false;
12696 }
12697 let Some(descriptor) = node
12698 .parent()
12699 .filter(|parent| parent.kind() == "type_descriptor")
12700 else {
12701 return false;
12702 };
12703 if descriptor.child_by_field_name("type") != Some(node) {
12704 return false;
12705 }
12706 let Some(arguments) = descriptor
12707 .parent()
12708 .filter(|parent| parent.kind() == "template_argument_list")
12709 else {
12710 return false;
12711 };
12712 arguments.parent().is_some_and(|owner| {
12713 matches!(
12714 owner.kind(),
12715 "template_type" | "template_function" | "template_method"
12716 ) && owner.child_by_field_name("arguments") == Some(arguments)
12717 })
12718}
12719
12720pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
12733 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
12734}
12735
12736pub fn is_declarator_node(node: Node<'_>) -> bool {
12737 matches!(
12738 node.kind(),
12739 "identifier"
12740 | "field_identifier"
12741 | "pointer_declarator"
12742 | "reference_declarator"
12743 | "array_declarator"
12744 | "parenthesized_declarator"
12745 | "function_declarator"
12746 )
12747}
12748
12749#[derive(Clone, Debug, PartialEq, Eq)]
12752pub struct RecoveredNamespaceRegion {
12753 pub start: usize,
12755 pub end: usize,
12757 pub components: Vec<String>,
12761}
12762
12763#[derive(Clone, Debug, Default)]
12786pub struct OrphanedNamespaceScopeIndex {
12787 regions: Vec<RecoveredNamespaceRegion>,
12788}
12789
12790impl OrphanedNamespaceScopeIndex {
12791 pub fn build(root: Node<'_>, source: &str) -> Self {
12792 if !root.has_error() {
12793 return Self::default();
12794 }
12795 struct Frame<'tree> {
12796 node: Node<'tree>,
12797 children: std::vec::IntoIter<Node<'tree>>,
12798 scope: Vec<String>,
12802 own_scope: Vec<String>,
12805 open: Vec<Vec<String>>,
12809 own_open: bool,
12811 owed: usize,
12813 run: Option<RecoveredNamespaceRegion>,
12815 }
12816 fn frame<'tree>(
12817 node: Node<'tree>,
12818 scope: Vec<String>,
12819 own_scope: Vec<String>,
12820 ) -> Frame<'tree> {
12821 let mut cursor = node.walk();
12822 let children = node.children(&mut cursor).collect::<Vec<_>>().into_iter();
12823 Frame {
12824 node,
12825 children,
12826 scope,
12827 own_scope,
12828 open: Vec::new(),
12829 own_open: false,
12830 owed: 0,
12831 run: None,
12832 }
12833 }
12834 let mut regions = Vec::new();
12835 let mut frames = vec![frame(root, Vec::new(), Vec::new())];
12836 while let Some(current) = frames.last_mut() {
12837 let Some(child) = current.children.next() else {
12838 let done = frames.pop().expect("the frame just borrowed");
12839 regions.extend(done.run);
12840 let Some(parent) = frames.last_mut() else {
12841 break;
12842 };
12843 for _ in 0..done.owed {
12844 if parent.open.pop().is_none() {
12845 parent.owed += 1;
12846 }
12847 }
12848 parent.own_open &= !parent.open.is_empty();
12849 parent.open.extend(done.open);
12850 continue;
12851 };
12852 match child.kind() {
12853 "{" if !child.is_missing() => {
12854 let own = current.open.is_empty();
12855 current.open.push(if own {
12856 current.own_scope.clone()
12857 } else {
12858 Vec::new()
12859 });
12860 current.own_open |= own;
12861 continue;
12862 }
12863 "}" if !child.is_missing() => {
12864 if current.open.pop().is_none() {
12865 current.owed += 1;
12866 }
12867 current.own_open &= !current.open.is_empty();
12871 continue;
12872 }
12873 _ => {}
12874 }
12875 let lost = ¤t.open[usize::from(current.own_open)..];
12876 let child_scope = current
12877 .scope
12878 .iter()
12879 .chain(current.open.iter().flatten())
12880 .cloned()
12881 .collect::<Vec<_>>();
12882 if lost.iter().any(|scope| !scope.is_empty()) {
12883 match &mut current.run {
12884 Some(run) if run.components == child_scope => run.end = child.end_byte(),
12885 run => {
12886 regions.extend(run.take());
12887 *run = Some(RecoveredNamespaceRegion {
12888 start: child.start_byte(),
12889 end: child.end_byte(),
12890 components: child_scope.clone(),
12891 });
12892 }
12893 }
12894 } else {
12895 regions.extend(current.run.take());
12896 }
12897 if child.has_error() {
12898 let own_scope = namespace_body_name_components(current.node, child, source);
12899 frames.push(frame(child, child_scope, own_scope));
12900 }
12901 }
12902 Self { regions }
12903 }
12904
12905 pub fn is_empty(&self) -> bool {
12906 self.regions.is_empty()
12907 }
12908
12909 pub fn approximate_size(&self) -> usize {
12911 self.regions.iter().fold(0usize, |total, region| {
12912 total
12913 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
12914 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
12915 })
12916 }
12917
12918 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
12920 self.regions
12921 .iter()
12922 .filter(|region| region.start <= byte && byte < region.end)
12923 .min_by_key(|region| region.end - region.start)
12924 }
12925
12926 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
12930 let mut parsed = Vec::new();
12931 let mut current = node.parent();
12932 while let Some(parent) = current {
12933 if parent.kind() == "namespace_definition"
12934 && let Some(name) = parent.child_by_field_name("name")
12935 {
12936 let mut components = Vec::new();
12937 if append_cpp_name_components(name, source, &mut components).is_some() {
12938 parsed.push((parent.start_byte(), components));
12939 }
12940 }
12941 current = parent.parent();
12942 }
12943 parsed.reverse();
12944 self.restore_enclosing_namespaces(parsed, node.start_byte())
12945 }
12946
12947 pub fn restore_enclosing_namespaces(
12953 &self,
12954 parsed: Vec<(usize, Vec<String>)>,
12955 node_start: usize,
12956 ) -> Vec<String> {
12957 let Some(region) = self.region_at(node_start) else {
12958 return parsed
12959 .into_iter()
12960 .flat_map(|(_, components)| components)
12961 .collect();
12962 };
12963 region
12964 .components
12965 .iter()
12966 .cloned()
12967 .chain(
12968 parsed
12969 .into_iter()
12970 .filter(|(start, _)| *start >= region.start)
12971 .flat_map(|(_, components)| components),
12972 )
12973 .collect()
12974 }
12975}
12976
12977fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
12980 let mut components = Vec::new();
12981 if body.kind() == "declaration_list"
12982 && parent.kind() == "namespace_definition"
12983 && parent.child_by_field_name("body") == Some(body)
12984 && let Some(name) = parent.child_by_field_name("name")
12985 && append_cpp_name_components(name, source, &mut components).is_none()
12986 {
12987 components.clear();
12988 }
12989 components
12990}
12991
12992#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12993pub enum RecoveredDeclaratorTypeContext {
12994 Declaration,
12995 FunctionDefinition,
12996 Parameter,
12997}
12998
12999pub fn recovered_macro_decorated_declarator_type(
13014 node: Node<'_>,
13015) -> Option<RecoveredDeclaratorTypeContext> {
13016 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
13017}
13018
13019pub fn recovered_macro_decorated_type_node(
13024 node: Node<'_>,
13025) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
13026 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
13027 return None;
13028 }
13029 let qualified = node.parent()?;
13030 if qualified.kind() != "qualified_identifier"
13031 || qualified.child_by_field_name("scope") != Some(node)
13032 || !(0..qualified.child_count())
13033 .filter_map(|index| qualified.child(index))
13034 .any(|child| child.kind() == "::" && child.is_missing())
13035 {
13036 return None;
13037 }
13038 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
13039 return None;
13040 }
13041
13042 let (declaration, context) = recovered_declarator_container(qualified)?;
13043 let type_node = declaration
13044 .child_by_field_name("type")
13045 .filter(|type_node| {
13046 *type_node != qualified
13047 && !type_node.is_missing()
13048 && type_node.start_byte() != type_node.end_byte()
13049 })?;
13050 Some((type_node, context))
13051}
13052
13053fn recovered_declarator_container(
13054 mut declarator: Node<'_>,
13055) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
13056 loop {
13057 let parent = declarator.parent()?;
13058 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
13059 return Some((
13060 parent
13061 .parent()
13062 .filter(|declaration| declaration.kind() == "declaration")?,
13063 RecoveredDeclaratorTypeContext::Declaration,
13064 ));
13065 }
13066 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
13067 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
13068 }
13069 if parent.kind() == "function_definition"
13070 && has_field_child(parent, "declarator", declarator)
13071 {
13072 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
13073 }
13074 if matches!(
13080 parent.kind(),
13081 "parameter_declaration" | "optional_parameter_declaration"
13082 ) && has_field_child(parent, "declarator", declarator)
13083 {
13084 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
13085 }
13086 if !matches!(
13087 parent.kind(),
13088 "array_declarator"
13089 | "function_declarator"
13090 | "parenthesized_declarator"
13091 | "pointer_declarator"
13092 | "pointer_type_declarator"
13093 | "reference_declarator"
13094 ) || !has_field_child(parent, "declarator", declarator)
13095 {
13096 return None;
13097 }
13098 declarator = parent;
13099 }
13100}
13101
13102fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
13103 let mut cursor = parent.walk();
13104 parent
13105 .children_by_field_name(field, &mut cursor)
13106 .any(|child| child == target)
13107}
13108
13109fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
13110 loop {
13111 if node.is_missing() || node.start_byte() == node.end_byte() {
13112 return false;
13113 }
13114 match node.kind() {
13115 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
13116 return true;
13117 }
13118 "array_declarator"
13119 | "function_declarator"
13120 | "parenthesized_declarator"
13121 | "pointer_declarator"
13122 | "pointer_type_declarator"
13123 | "reference_declarator" => {
13124 let Some(declarator) = node.child_by_field_name("declarator") else {
13125 return false;
13126 };
13127 node = declarator;
13128 }
13129 _ => return false,
13130 }
13131 }
13132}
13133
13134pub enum DesignatedInitializerOwner {
13136 Resolved(CodeUnit),
13137 Unresolved,
13138}
13139
13140pub fn designated_initializer_owner(
13151 visibility: &VisibilityIndex<'_>,
13152 file: &ProjectFile,
13153 source: &str,
13154 node: Node<'_>,
13155) -> Option<DesignatedInitializerOwner> {
13156 if let Some(designator) = node
13157 .parent()
13158 .filter(|parent| parent.kind() == "field_designator")
13159 {
13160 let pair = designator.parent()?;
13161 if pair.kind() != "initializer_pair"
13162 || pair.child_by_field_name("designator") != Some(designator)
13163 {
13164 return None;
13165 }
13166 let initializer = pair.parent()?;
13167 if initializer.kind() != "initializer_list" {
13168 return None;
13169 }
13170 return Some(classified_designated_owner(initializer_list_owner(
13171 visibility,
13172 file,
13173 source,
13174 initializer,
13175 )));
13176 }
13177
13178 let init_declarator = node.parent()?;
13179 if init_declarator.child_by_field_name("declarator") != Some(node)
13180 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
13181 {
13182 return None;
13183 }
13184 Some(classified_designated_owner(declaration_owner(
13185 visibility,
13186 file,
13187 source,
13188 init_declarator.parent()?,
13189 )))
13190}
13191
13192fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
13193 owner.map_or(
13194 DesignatedInitializerOwner::Unresolved,
13195 DesignatedInitializerOwner::Resolved,
13196 )
13197}
13198
13199fn initializer_list_owner(
13200 visibility: &VisibilityIndex<'_>,
13201 file: &ProjectFile,
13202 source: &str,
13203 initializer: Node<'_>,
13204) -> Option<CodeUnit> {
13205 let mut current = initializer;
13206 let mut outer_initializer_lists = 0usize;
13207 loop {
13208 let parent = current.parent()?;
13209 match parent.kind() {
13210 "initializer_pair" => return None,
13211 "initializer_list" => {
13212 outer_initializer_lists += 1;
13213 if outer_initializer_lists > 1 {
13214 return None;
13215 }
13216 current = parent;
13217 }
13218 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
13219 let declaration = parent.parent()?;
13220 if outer_initializer_lists == 1
13221 && !parent
13222 .child_by_field_name("declarator")
13223 .is_some_and(contains_array_declarator)
13224 {
13225 return None;
13226 }
13227 return declaration_owner(visibility, file, source, declaration);
13228 }
13229 "compound_literal_expression"
13230 if parent.child_by_field_name("value") == Some(current)
13231 && outer_initializer_lists == 0 =>
13232 {
13233 let type_node = parent.child_by_field_name("type")?;
13234 return resolve_designated_owner_type(visibility, file, source, type_node);
13235 }
13236 "ERROR" => current = parent,
13237 _ => return None,
13238 }
13239 }
13240}
13241
13242fn declaration_owner(
13243 visibility: &VisibilityIndex<'_>,
13244 file: &ProjectFile,
13245 source: &str,
13246 declaration: Node<'_>,
13247) -> Option<CodeUnit> {
13248 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
13249 return None;
13250 }
13251 let type_node = declaration
13252 .child_by_field_name("type")
13253 .or_else(|| first_type_child(declaration))?;
13254 resolve_designated_owner_type(visibility, file, source, type_node)
13255}
13256
13257fn resolve_designated_owner_type(
13258 visibility: &VisibilityIndex<'_>,
13259 file: &ProjectFile,
13260 source: &str,
13261 type_node: Node<'_>,
13262) -> Option<CodeUnit> {
13263 let type_name = normalize_type_text(node_text(type_node, source));
13264 visibility
13265 .resolve_type(file, &type_name)
13266 .filter(CodeUnit::is_class)
13267}
13268
13269fn contains_array_declarator(declarator: Node<'_>) -> bool {
13270 let mut stack = vec![declarator];
13271 while let Some(node) = stack.pop() {
13272 if node.kind() == "array_declarator" {
13273 return true;
13274 }
13275 if matches!(node.kind(), "initializer_list" | "compound_statement") {
13276 continue;
13277 }
13278 let mut cursor = node.walk();
13279 stack.extend(node.named_children(&mut cursor));
13280 }
13281 false
13282}
13283
13284pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
13285 let mut cursor = node.walk();
13286 node.named_children(&mut cursor).find(|child| {
13287 matches!(
13288 child.kind(),
13289 "type_identifier"
13290 | "primitive_type"
13291 | "qualified_identifier"
13292 | "scoped_type_identifier"
13293 | "struct_specifier"
13294 | "union_specifier"
13295 | "enum_specifier"
13296 )
13297 })
13298}
13299
13300pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
13301 visibility: &VisibilityIndex<'_>,
13302 file: &ProjectFile,
13303 source: &str,
13304 declarator: Node<'_>,
13305 type_text: Option<&str>,
13306 bindings: &LocalInferenceEngine<T>,
13307) -> bool {
13308 if !has_ancestor_kind(declarator, "compound_statement") {
13309 return false;
13310 }
13311 if declarator
13312 .child_by_field_name("declarator")
13313 .is_none_or(|declarator| declarator.kind() != "identifier")
13314 {
13315 return false;
13316 }
13317 if !type_text
13318 .and_then(|text| visibility.resolve_type(file, text))
13319 .is_some_and(|unit| unit.is_class())
13320 {
13321 return false;
13322 }
13323 declarator
13324 .child_by_field_name("parameters")
13325 .is_some_and(|parameters| {
13326 constructor_parameters_look_like_expressions(parameters, source, bindings)
13327 })
13328}
13329
13330fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
13331 parameters: Node<'_>,
13332 source: &str,
13333 bindings: &LocalInferenceEngine<T>,
13334) -> bool {
13335 let mut cursor = parameters.walk();
13336 parameters.named_children(&mut cursor).any(|parameter| {
13337 !matches!(
13338 parameter.kind(),
13339 "parameter_declaration" | "optional_parameter_declaration"
13340 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
13341 })
13342}
13343
13344fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
13345 parameter: Node<'_>,
13346 source: &str,
13347 bindings: &LocalInferenceEngine<T>,
13348) -> bool {
13349 let text = node_text(parameter, source).trim();
13350 if text
13351 .chars()
13352 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
13353 && bindings.is_shadowed(text)
13354 {
13355 return true;
13356 }
13357
13358 let Some(base) = parameter
13359 .child_by_field_name("type")
13360 .filter(|base| base.kind() == "type_identifier")
13361 else {
13362 return false;
13363 };
13364 let Some(subscript) = parameter
13365 .child_by_field_name("declarator")
13366 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
13367 else {
13368 return false;
13369 };
13370 subscript.child_by_field_name("size").is_some()
13371 && bindings.is_shadowed(node_text(base, source).trim())
13372}
13373
13374pub fn is_declaration_name(node: Node<'_>) -> bool {
13375 let Some(parent) = node.parent() else {
13376 return false;
13377 };
13378 if parent
13379 .child_by_field_name("name")
13380 .is_some_and(|name| same_node(name, node))
13381 {
13382 if matches!(
13383 parent.kind(),
13384 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13385 ) {
13386 return cpp_tag_specifier_declares_name(parent);
13387 }
13388 if matches!(
13389 parent.kind(),
13390 "namespace_definition"
13391 | "namespace_alias_definition"
13392 | "alias_declaration"
13393 | "enumerator"
13394 ) {
13395 return true;
13396 }
13397 }
13398
13399 let mut current = Some(parent);
13400 while let Some(ancestor) = current {
13401 let type_definition = ancestor.kind() == "type_definition";
13402 let mut declarator_cursor = ancestor.walk();
13403 if ancestor
13404 .children_by_field_name("declarator", &mut declarator_cursor)
13405 .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
13406 {
13407 return true;
13408 }
13409 if matches!(
13410 ancestor.kind(),
13411 "declaration"
13412 | "field_declaration"
13413 | "parameter_declaration"
13414 | "optional_parameter_declaration"
13415 | "function_definition"
13416 | "type_definition"
13417 | "alias_declaration"
13418 | "class_specifier"
13419 | "struct_specifier"
13420 | "union_specifier"
13421 | "enum_specifier"
13422 ) {
13423 return false;
13424 }
13425 current = ancestor.parent();
13426 }
13427 false
13428}
13429
13430pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
13439 if !matches!(
13440 node.kind(),
13441 "qualified_identifier" | "scoped_type_identifier"
13442 ) {
13443 return false;
13444 }
13445 let Some(declaration) = node
13446 .parent()
13447 .filter(|parent| parent.kind() == "declaration")
13448 else {
13449 return false;
13450 };
13451 if declaration.child_by_field_name("declarator") != Some(node)
13452 || !declaration
13453 .child_by_field_name("type")
13454 .is_some_and(|friend| {
13455 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
13456 })
13457 {
13458 return false;
13459 }
13460 let mut cursor = declaration.walk();
13461 let mut errors = declaration
13462 .named_children(&mut cursor)
13463 .filter(|child| child.kind() == "ERROR");
13464 let Some(error) = errors.next() else {
13465 return false;
13466 };
13467 errors.next().is_none()
13468 && error.named_child_count() == 1
13469 && error.named_child(0).is_some_and(|class| {
13470 class.kind() == "identifier" && node_text(class, source) == "class"
13471 })
13472}
13473
13474pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
13475 if !matches!(node.kind(), "identifier" | "field_identifier") {
13476 return false;
13477 }
13478 if let Some(parent) = node.parent() {
13479 if parent.kind() == "call_expression"
13480 && parent.child_by_field_name("function") == Some(node)
13481 {
13482 return false;
13483 }
13484 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
13485 && parent.child_by_field_name("label") == Some(node)
13486 {
13487 return false;
13488 }
13489 }
13490 if is_declaration_name(node) {
13491 return false;
13492 }
13493 let mut current = node.parent();
13494 while let Some(ancestor) = current {
13495 match ancestor.kind() {
13496 "preproc_ifdef" | "preproc_ifndef" => {
13497 if ancestor
13498 .child_by_field_name("name")
13499 .is_some_and(|name| node_range_contains(name, node))
13500 {
13501 return false;
13502 }
13503 }
13504 "preproc_if" | "preproc_elif" => {
13505 if ancestor
13506 .child_by_field_name("condition")
13507 .is_some_and(|condition| node_range_contains(condition, node))
13508 {
13509 return false;
13510 }
13511 }
13512 "preproc_else" => {}
13513 kind if kind.starts_with("preproc_") => return false,
13514 _ => {}
13515 }
13516 if matches!(
13517 ancestor.kind(),
13518 "translation_unit" | "function_definition" | "compound_statement"
13519 ) {
13520 break;
13521 }
13522 current = ancestor.parent();
13523 }
13524 true
13525}
13526
13527fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
13528 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
13529}
13530
13531fn recovered_c_reference_node(
13532 visibility: &VisibilityIndex<'_>,
13533 file: &ProjectFile,
13534 node: Node<'_>,
13535 source: &str,
13536) -> bool {
13537 if node.start_byte() >= node.end_byte()
13538 || node.is_error()
13539 || node.is_missing()
13540 || !matches!(
13541 node.kind(),
13542 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
13543 )
13544 || recovered_c_macro_binding_role(node)
13545 || recovered_c_label_role(node)
13546 {
13547 return false;
13548 }
13549 let name = node_text(node, source);
13557 let recovered_function_call =
13558 recovered_c_function_declarator_call(visibility, file, node, name);
13559 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
13560 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
13561 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
13562 if is_declaration_name(node)
13563 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
13564 && !recovered_function_call
13565 && !recovered_macro_call
13566 && !recovered_parenthesized_reference
13567 {
13568 return false;
13569 }
13570
13571 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
13572 return true;
13573 }
13574 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
13575 return true;
13576 }
13577 if recovered_parenthesized_reference {
13578 return true;
13579 }
13580 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
13581 if recovered_function_call {
13582 return true;
13583 }
13584 return visibility
13585 .visible_identifier_candidates(file, name)
13586 .any(|candidate| {
13587 candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
13588 });
13589 }
13590 let visible = visibility
13591 .visible_identifier_candidates(file, name)
13592 .next()
13593 .is_some();
13594 visible
13595 && (recovered_c_reference_anchor(node)
13596 || recovered_c_error_expression_leaf(node)
13597 || recovered_function_call)
13598}
13599
13600fn push_recovered_c_range(
13601 ranges: &mut Vec<Range>,
13602 seen: &mut HashSet<(usize, usize)>,
13603 start_byte: usize,
13604 end_byte: usize,
13605 node: Node<'_>,
13606 limit: usize,
13607) -> bool {
13608 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
13609 return true;
13610 }
13611 if ranges.len() >= limit {
13612 return false;
13613 }
13614 ranges.push(Range {
13615 start_byte,
13616 end_byte,
13617 start_line: node.start_position().row,
13618 end_line: node.end_position().row,
13619 });
13620 true
13621}
13622
13623fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
13628 let mut current = node.parent();
13629 while let Some(parent) = current {
13630 if parent.is_error() {
13631 let Some(anchor) = parent.parent() else {
13632 return false;
13633 };
13634 return anchor.kind().ends_with("_expression")
13635 || matches!(
13636 anchor.kind(),
13637 "argument_list"
13638 | "return_statement"
13639 | "expression_statement"
13640 | "case_statement"
13641 | "initializer_list"
13642 | "field_designator"
13643 | "enumerator"
13644 );
13645 }
13646 if matches!(
13647 parent.kind(),
13648 "translation_unit" | "function_definition" | "compound_statement"
13649 ) {
13650 return false;
13651 }
13652 current = parent.parent();
13653 }
13654 false
13655}
13656
13657fn recovered_c_function_declarator_call(
13663 visibility: &VisibilityIndex<'_>,
13664 file: &ProjectFile,
13665 node: Node<'_>,
13666 name: &str,
13667) -> bool {
13668 if !matches!(
13669 node.kind(),
13670 "identifier" | "field_identifier" | "type_identifier"
13671 ) {
13672 return false;
13673 }
13674 recovered_c_function_declarator_invocation(node)
13675 && visibility
13676 .visible_identifier_candidates(file, name)
13677 .any(CodeUnit::is_function)
13678}
13679
13680fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
13690 let function_declarator = if node.parent().is_some_and(|parent| {
13691 parent.kind() == "function_declarator"
13692 && parent.child_by_field_name("declarator") == Some(node)
13693 }) {
13694 node.parent().expect("checked function declarator parent")
13695 } else {
13696 let Some(parameter) = node.parent().filter(|parent| {
13697 parent.kind() == "parameter_declaration"
13698 && parent.child_by_field_name("type") == Some(node)
13699 }) else {
13700 return false;
13701 };
13702 if !parameter
13703 .child_by_field_name("declarator")
13704 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
13705 {
13706 return false;
13707 }
13708 let Some(parameters) = parameter
13709 .parent()
13710 .filter(|parent| parent.kind() == "parameter_list")
13711 else {
13712 return false;
13713 };
13714 let Some(function_declarator) = parameters
13715 .parent()
13716 .filter(|parent| parent.kind() == "function_declarator")
13717 else {
13718 return false;
13719 };
13720 function_declarator
13721 };
13722
13723 let Some(mut current) = function_declarator
13724 .parent()
13725 .filter(|parent| parent.is_error())
13726 else {
13727 return false;
13728 };
13729 loop {
13730 let Some(parent) = current.parent() else {
13731 return false;
13732 };
13733 if matches!(
13734 parent.kind(),
13735 "translation_unit"
13736 | "compound_statement"
13737 | "preproc_if"
13738 | "preproc_ifdef"
13739 | "preproc_ifndef"
13740 | "preproc_else"
13741 | "preproc_elif"
13742 ) {
13743 return true;
13744 }
13745 if parent.is_error()
13746 || matches!(
13747 parent.kind(),
13748 "parameter_declaration"
13749 | "parameter_list"
13750 | "function_declarator"
13751 | "abstract_function_declarator"
13752 | "parenthesized_declarator"
13753 )
13754 {
13755 current = parent;
13756 continue;
13757 }
13758 return false;
13759 }
13760}
13761
13762fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
13768 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
13769 return false;
13770 };
13771 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
13772 return false;
13773 }
13774 let Some(declarator) = error
13775 .parent()
13776 .filter(|parent| parent.kind() == "parenthesized_declarator")
13777 else {
13778 return false;
13779 };
13780 let Some(declaration) = declarator
13781 .parent()
13782 .filter(|parent| parent.kind() == "declaration")
13783 else {
13784 return false;
13785 };
13786 if declaration.child_by_field_name("declarator") != Some(declarator) {
13787 return false;
13788 }
13789 let Some(type_node) = declaration.child_by_field_name("type") else {
13790 return false;
13791 };
13792 type_node.kind() == "dependent_type"
13793 && type_node
13794 .child(0)
13795 .is_some_and(|keyword| keyword.kind() == "typename")
13796}
13797
13798fn recovered_c_explicit_assignment_callee(
13799 visibility: &VisibilityIndex<'_>,
13800 file: &ProjectFile,
13801 node: Node<'_>,
13802 name: &str,
13803) -> bool {
13804 let mut current = node;
13805 let error = loop {
13806 let Some(parent) = current.parent() else {
13807 return false;
13808 };
13809 if parent.is_error() {
13810 break parent;
13811 }
13812 current = parent;
13813 };
13814 let mut cursor = error.walk();
13815 let explicit_recovery_precedes_callee = error
13816 .named_children(&mut cursor)
13817 .take_while(|child| child.start_byte() < node.start_byte())
13818 .any(|child| child.kind() == "explicit_function_specifier");
13819 if !explicit_recovery_precedes_callee {
13820 return false;
13821 }
13822 visibility
13823 .visible_identifier_candidates(file, name)
13824 .any(CodeUnit::is_function)
13825}
13826
13827fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
13828 while let Some(parent) = node.parent() {
13829 if matches!(
13830 parent.kind(),
13831 "preproc_def" | "preproc_function_def" | "preproc_params"
13832 ) {
13833 return true;
13834 }
13835 if parent.is_error()
13836 || matches!(
13837 parent.kind(),
13838 "translation_unit" | "function_definition" | "compound_statement"
13839 )
13840 {
13841 return false;
13842 }
13843 node = parent;
13844 }
13845 false
13846}
13847
13848fn recovered_c_label_role(node: Node<'_>) -> bool {
13849 node.parent().is_some_and(|parent| {
13850 matches!(parent.kind(), "labeled_statement" | "goto_statement")
13851 && parent.child_by_field_name("label") == Some(node)
13852 })
13853}
13854
13855fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
13856 while let Some(parent) = node.parent() {
13857 if parent.is_error() {
13858 return false;
13859 }
13860 if parent.kind() == "optional_parameter_declaration"
13865 && parent
13866 .child_by_field_name("default_value")
13867 .is_some_and(|value| node_range_contains(value, node))
13868 {
13869 return true;
13870 }
13871 if parent.kind().ends_with("_expression")
13872 || matches!(
13873 parent.kind(),
13874 "argument_list"
13875 | "return_statement"
13876 | "expression_statement"
13877 | "case_statement"
13878 | "initializer_list"
13879 | "init_declarator"
13880 | "array_declarator"
13881 | "field_designator"
13882 | "enumerator"
13883 )
13884 {
13885 return true;
13886 }
13887 if matches!(
13888 parent.kind(),
13889 "translation_unit"
13890 | "function_definition"
13891 | "compound_statement"
13892 | "declaration"
13893 | "field_declaration"
13894 | "parameter_declaration"
13895 ) {
13896 return false;
13897 }
13898 node = parent;
13899 }
13900 false
13901}
13902
13903pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
13911 let mut current = parameter.parent();
13912 while let Some(ancestor) = current {
13913 if ancestor.kind() == "lambda_expression" {
13914 return ancestor
13915 .child_by_field_name("declarator")
13916 .is_some_and(|declarator| {
13917 declarator.start_byte() <= parameter.start_byte()
13918 && parameter.end_byte() <= declarator.end_byte()
13919 });
13920 }
13921 if ancestor.kind() == "function_definition" {
13922 return ancestor
13923 .child_by_field_name("declarator")
13924 .is_some_and(|declarator| {
13925 declarator.start_byte() <= parameter.start_byte()
13926 && parameter.end_byte() <= declarator.end_byte()
13927 });
13928 }
13929 current = ancestor.parent();
13930 }
13931 false
13932}
13933
13934pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
13935 let mut current = node.parent();
13936 while let Some(ancestor) = current {
13937 if matches!(
13938 ancestor.kind(),
13939 "parameter_declaration" | "optional_parameter_declaration"
13940 ) {
13941 return ancestor
13942 .child_by_field_name("type")
13943 .is_some_and(|type_node| {
13944 type_node.start_byte() <= node.start_byte()
13945 && node.end_byte() <= type_node.end_byte()
13946 });
13947 }
13948 if matches!(
13949 ancestor.kind(),
13950 "function_definition" | "lambda_expression" | "compound_statement"
13951 ) {
13952 return false;
13953 }
13954 current = ancestor.parent();
13955 }
13956 false
13957}
13958
13959fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
13960 if specifier.child_by_field_name("body").is_some() {
13961 return true;
13962 }
13963 let mut current = specifier.parent();
13964 while let Some(ancestor) = current {
13965 match ancestor.kind() {
13966 "type_descriptor"
13967 | "parameter_declaration"
13968 | "optional_parameter_declaration"
13969 | "template_argument_list"
13970 | "cast_expression" => return false,
13971 "declaration" | "field_declaration" => {
13972 let mut cursor = ancestor.walk();
13973 return ancestor
13974 .children_by_field_name("declarator", &mut cursor)
13975 .next()
13976 .is_none();
13977 }
13978 "translation_unit" => return true,
13979 _ => current = ancestor.parent(),
13980 }
13981 }
13982 false
13983}
13984
13985pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
13986 match node.kind() {
13987 "identifier"
13988 | "field_identifier"
13989 | "qualified_identifier"
13990 | "scoped_identifier"
13991 | "operator_name"
13992 | "destructor_name"
13993 | "literal_operator_name" => Some(node),
13994 "reference_declarator" | "parenthesized_declarator" => {
13995 node.named_child(0).and_then(declarator_name_node)
13996 }
13997 _ => node
13998 .child_by_field_name("declarator")
13999 .or_else(|| node.child_by_field_name("name"))
14000 .or_else(|| node.child_by_field_name("field"))
14001 .and_then(declarator_name_node),
14002 }
14003}
14004
14005fn declarator_name_path_contains(
14006 declarator: Node<'_>,
14007 candidate: Node<'_>,
14008 allow_type_identifier: bool,
14009) -> bool {
14010 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
14011 return false;
14012 };
14013 let mut current = Some(declarator);
14014 while let Some(node) = current {
14015 if same_node(node, candidate) {
14016 return true;
14017 }
14018 if same_node(node, name) {
14019 return false;
14020 }
14021 current = node
14022 .child_by_field_name("declarator")
14023 .or_else(|| node.child_by_field_name("name"))
14024 .or_else(|| node.child_by_field_name("field"));
14025 }
14026 false
14027}
14028
14029fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
14030 match node.kind() {
14031 "identifier"
14032 | "field_identifier"
14033 | "operator_name"
14034 | "destructor_name"
14035 | "literal_operator_name" => Some(node),
14036 "type_identifier" if allow_type_identifier => Some(node),
14037 _ => node
14038 .child_by_field_name("declarator")
14039 .or_else(|| node.child_by_field_name("name"))
14040 .or_else(|| node.child_by_field_name("field"))
14041 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
14042 }
14043}
14044
14045pub fn is_nested_type_node(node: Node<'_>) -> bool {
14048 node.parent().is_some_and(|parent| {
14049 matches!(
14050 parent.kind(),
14051 "qualified_identifier" | "scoped_type_identifier" | "template_type"
14052 )
14053 })
14054}
14055
14056pub struct OutOfLineMemberDefinitionOwners<'tree> {
14057 pub owners: Vec<(Node<'tree>, CodeUnit)>,
14058 innermost: Option<(Node<'tree>, CodeUnit)>,
14059}
14060
14061impl OutOfLineMemberDefinitionOwners<'_> {
14062 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
14063 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
14064 }
14065}
14066
14067pub struct QualifiedOwnerComponents<'tree> {
14068 pub nodes: Vec<Node<'tree>>,
14069 pub names: Vec<String>,
14070 pub global: bool,
14071}
14072
14073pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
14078 let mut stack = vec![node];
14079 let mut found_separator = false;
14080 while let Some(current) = stack.pop() {
14081 if !matches!(
14082 current.kind(),
14083 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
14084 ) {
14085 continue;
14086 }
14087 let mut current_has_separator = false;
14088 for index in 0..current.child_count() {
14089 let Some(child) = current.child(index) else {
14090 continue;
14091 };
14092 if child.kind() == "::" {
14093 if child.is_missing() {
14094 return false;
14095 }
14096 current_has_separator = true;
14097 found_separator = true;
14098 }
14099 }
14100 if !current_has_separator {
14101 return false;
14102 }
14103 for field in ["scope", "name"] {
14104 if let Some(child) = current.child_by_field_name(field)
14105 && matches!(
14106 child.kind(),
14107 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
14108 )
14109 {
14110 stack.push(child);
14111 }
14112 }
14113 }
14114 found_separator
14115}
14116
14117pub fn qualified_owner_components<'tree>(
14118 node: Node<'tree>,
14119 source: &str,
14120) -> Option<QualifiedOwnerComponents<'tree>> {
14121 if !qualified_name_has_concrete_scope_separators(node) {
14122 return None;
14123 }
14124 let mut nodes = cpp_name_component_nodes(node)?;
14125 nodes.pop()?;
14126 if nodes.is_empty() {
14127 return None;
14128 }
14129 let names = nodes
14130 .iter()
14131 .map(|component| node_text(*component, source).to_string())
14132 .collect();
14133 Some(QualifiedOwnerComponents {
14134 nodes,
14135 names,
14136 global: is_globally_qualified_cpp_name(node),
14137 })
14138}
14139
14140pub fn out_of_line_member_definition_owner<'tree>(
14141 analyzer: &CppGraphSource<'_>,
14142 visibility: &VisibilityIndex<'_>,
14143 file: &ProjectFile,
14144 source: &str,
14145 node: Node<'tree>,
14146) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
14147 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
14148 || !has_ancestor_kind(node, "function_definition")
14149 || !is_function_declarator_name_root(node)
14150 {
14151 return None;
14152 }
14153 let qualified = qualified_owner_components(node, source)?;
14154 let lexical_scope = enclosing_namespace_components(node, source)?;
14155 let mut owners = Vec::new();
14156 let mut innermost = None;
14157
14158 for component_count in 1..=qualified.names.len() {
14159 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
14160 .resolve_type_components_lexically(
14161 analyzer,
14162 file,
14163 &qualified.names[..component_count],
14164 qualified.global,
14165 &lexical_scope,
14166 )
14167 && !owners
14168 .iter()
14169 .any(|(_, existing)| same_visible_symbol(existing, &unit))
14170 {
14171 if component_count == qualified.names.len() {
14172 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
14173 }
14174 owners.push((qualified.nodes[component_count - 1], unit));
14175 }
14176 }
14177
14178 if innermost.is_none() {
14188 let indexed_owner_components = visibility
14189 .indexed_enclosing_owner_scope(analyzer, file, node)
14190 .or_else(|| {
14191 if qualified.names.len() <= 1 {
14196 return None;
14197 }
14198 let range = Range {
14199 start_byte: node.start_byte(),
14200 end_byte: node.end_byte(),
14201 start_line: node.start_position().row,
14202 end_line: node.end_position().row,
14203 };
14204 let start = analyzer.enclosing_code_unit(file, &range)?;
14205 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
14206 brokk_bifrost_core::analyzer::Language::Cpp,
14207 &cpp_name_for(&start),
14208 );
14209 components.pop();
14210 Some(components)
14211 });
14212 if let Some(indexed_owner_components) = indexed_owner_components
14213 && indexed_owner_components.len() > qualified.names.len()
14214 && indexed_owner_components.ends_with(&qualified.names)
14215 && indexed_namespace_path_is_recoverable(
14216 &lexical_scope,
14217 &indexed_owner_components,
14218 qualified.names.len(),
14219 )
14220 && (qualified.names.len() > 1 || !qualified.global)
14225 {
14226 let namespace_count = indexed_owner_components.len() - qualified.names.len();
14227 for component_count in 1..=qualified.names.len() {
14228 let expected = &indexed_owner_components[..namespace_count + component_count];
14229 let owner_node = qualified.nodes[component_count - 1];
14230 for owner in visibility
14231 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
14232 .filter(|candidate| candidate.is_class())
14233 .filter(|candidate| {
14234 canonical_cpp_scope_components(candidate) == expected
14235 && visibility.external_type_candidate_visible_in_context(
14236 analyzer, file, candidate, node,
14237 )
14238 })
14239 {
14240 if component_count == qualified.names.len() && innermost.is_none() {
14241 innermost = Some((owner_node, owner.clone()));
14242 }
14243 if !owners
14244 .iter()
14245 .any(|(_, existing)| same_symbol(existing, owner))
14246 {
14247 owners.push((owner_node, owner.clone()));
14248 }
14249 }
14250 }
14251 }
14252 }
14253 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
14254}
14255
14256fn is_function_declarator_name_root(node: Node<'_>) -> bool {
14257 let mut current = node;
14258 while let Some(parent) = current.parent() {
14259 if parent.kind() == "function_declarator" {
14260 return parent.child_by_field_name("declarator") == Some(current);
14261 }
14262 if matches!(
14263 parent.kind(),
14264 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
14265 ) && parent.child_by_field_name("declarator") == Some(current)
14266 {
14267 current = parent;
14268 continue;
14269 }
14270 return false;
14271 }
14272 false
14273}
14274
14275pub fn append_cpp_name_components(
14276 node: Node<'_>,
14277 source: &str,
14278 out: &mut Vec<String>,
14279) -> Option<()> {
14280 out.extend(
14281 cpp_name_component_nodes(node)?
14282 .into_iter()
14283 .map(|component| node_text(component, source).to_string()),
14284 );
14285 Some(())
14286}
14287
14288pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
14289 let mut components = Vec::new();
14290 append_cpp_name_components(node, source, &mut components)?;
14291 Some(components)
14292}
14293
14294pub fn unique_macro_replacement_type_candidate(
14303 analyzer: &CppGraphSource<'_>,
14304 visibility: &VisibilityIndex<'_>,
14305 file: &ProjectFile,
14306 components: &[String],
14307) -> Option<CodeUnit> {
14308 let terminal = components.last()?;
14309 let mut candidates = Vec::new();
14310 for candidate in visibility
14311 .visible_identifier_candidates(file, terminal)
14312 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
14313 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
14314 {
14315 if !candidates
14316 .iter()
14317 .any(|existing| same_logical_symbol(existing, candidate))
14318 {
14319 candidates.push(candidate.clone());
14320 }
14321 }
14322 (candidates.len() == 1).then(|| candidates.remove(0))
14323}
14324
14325pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
14333 let mut parser = Parser::new();
14334 if parser
14335 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14336 .is_err()
14337 {
14338 return Vec::new();
14339 }
14340 let Some(tree) = parser.parse(source, None) else {
14341 return Vec::new();
14342 };
14343 let mut scopes = Vec::new();
14344 let mut pending = vec![tree.root_node()];
14345 while let Some(node) = pending.pop() {
14346 if node.kind() == "using_declaration" {
14347 let Some(imported) = node.named_child(0) else {
14348 continue;
14349 };
14350 let Some(mut components) = cpp_type_name_components(imported, source) else {
14351 continue;
14352 };
14353 if components.pop().as_deref() == Some(member) && !components.is_empty() {
14354 scopes.push(components.join("::"));
14355 }
14356 continue;
14357 }
14358 for index in (0..node.named_child_count()).rev() {
14359 if let Some(child) = node.named_child(index) {
14360 pending.push(child);
14361 }
14362 }
14363 }
14364 scopes
14365}
14366
14367pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
14371 qualified == scope
14372 || qualified
14373 .strip_suffix(scope)
14374 .is_some_and(|prefix| prefix.ends_with("::"))
14375}
14376
14377pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
14382 let Some(type_descriptor) = node.parent() else {
14383 return false;
14384 };
14385 if type_descriptor.kind() != "type_descriptor"
14386 || type_descriptor.child_by_field_name("type") != Some(node)
14387 {
14388 return false;
14389 }
14390 let Some(arguments) = type_descriptor.parent() else {
14391 return false;
14392 };
14393 if arguments.kind() != "template_argument_list" {
14394 return false;
14395 }
14396 arguments.parent().is_some_and(|parent| {
14397 matches!(parent.kind(), "template_type" | "template_function")
14398 && parent.child_by_field_name("arguments") == Some(arguments)
14399 })
14400}
14401
14402pub fn cpp_template_reference_arguments(
14403 mut node: Node<'_>,
14404 source: &str,
14405) -> Option<Vec<CppTemplateExpression>> {
14406 loop {
14407 match node.kind() {
14408 "template_type" | "template_function" => {
14409 let arguments = node.child_by_field_name("arguments")?;
14410 let mut cursor = arguments.walk();
14411 return Some(
14412 arguments
14413 .named_children(&mut cursor)
14414 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
14415 .map(|argument| CppTemplateExpression {
14416 text: normalize_cpp_whitespace(node_text(argument, source)),
14417 term: cpp_template_term(
14419 argument,
14420 source,
14421 &[],
14422 &ParentIndex::unindexed(),
14423 ),
14424 })
14425 .collect(),
14426 );
14427 }
14428 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
14429 node = node
14430 .child_by_field_name("name")
14431 .or_else(|| node.child_by_field_name("type"))?;
14432 }
14433 _ => return None,
14434 }
14435 }
14436}
14437
14438fn cpp_reconcile_primary_template_parameters(
14439 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
14440 preferred: &CodeUnit,
14441) -> Option<Vec<CppTemplateParameterMetadata>> {
14442 let canonical = candidates
14443 .iter()
14444 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
14445 let mut merged = canonical
14446 .parameters
14447 .iter()
14448 .map(|parameter| CppTemplateParameterMetadata {
14449 name: parameter.name.clone(),
14450 kind: parameter.kind,
14451 variadic: parameter.variadic,
14452 default: None,
14453 })
14454 .collect::<Vec<_>>();
14455
14456 for (_, metadata) in candidates {
14457 if metadata.parameters.len() != merged.len() {
14458 return None;
14459 }
14460 let rename_bindings = metadata
14461 .parameters
14462 .iter()
14463 .zip(&merged)
14464 .map(|(parameter, canonical)| {
14465 (
14466 parameter.name.clone(),
14467 CppTemplateTerm::Parameter(canonical.name.clone()),
14468 )
14469 })
14470 .collect::<HashMap<_, _>>();
14471 for ((parameter, canonical), merged_parameter) in metadata
14472 .parameters
14473 .iter()
14474 .zip(&canonical.parameters)
14475 .zip(&mut merged)
14476 {
14477 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
14478 return None;
14479 }
14480 let Some(default) = ¶meter.default else {
14481 continue;
14482 };
14483 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
14484 if let Some(existing) = &merged_parameter.default {
14485 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
14486 return None;
14487 }
14488 } else {
14489 merged_parameter.default = Some(CppTemplateExpression {
14490 text: default.text.clone(),
14491 term: normalized_term,
14492 });
14493 }
14494 }
14495 }
14496 Some(merged)
14497}
14498
14499pub fn cpp_bind_template_arguments(
14500 parameters: &[CppTemplateParameterMetadata],
14501 explicit_arguments: &[CppTemplateExpression],
14502) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
14503 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
14504 if variadic_index.is_some_and(|index| {
14505 index + 1 != parameters.len()
14506 || parameters[index + 1..]
14507 .iter()
14508 .any(|parameter| parameter.variadic)
14509 }) {
14510 return None;
14511 }
14512 let fixed_count = variadic_index.unwrap_or(parameters.len());
14513 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
14514 return None;
14515 }
14516 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
14517 let mut expanded = explicit_arguments[..explicit_fixed_count]
14518 .iter()
14519 .map(cpp_clone_template_expression_iterative)
14520 .collect::<Vec<_>>();
14521 let mut bindings = HashMap::default();
14522 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
14523 bindings.insert(
14524 parameter.name.clone(),
14525 cpp_clone_template_term_iterative(&argument.term),
14526 );
14527 }
14528 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
14529 let default = parameter.default.as_ref()?;
14530 let term = cpp_substitute_template_term(&default.term, &bindings)?;
14531 bindings.insert(parameter.name.clone(), term.clone());
14532 expanded.push(CppTemplateExpression {
14533 text: default.text.clone(),
14534 term,
14535 });
14536 }
14537 if let Some(index) = variadic_index {
14538 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
14539 expanded.extend(
14540 packed_arguments
14541 .iter()
14542 .map(cpp_clone_template_expression_iterative),
14543 );
14544 bindings.insert(
14545 parameters[index].name.clone(),
14546 CppTemplateTerm::Node {
14547 kind: "parameter_pack".to_string(),
14548 children: packed_arguments
14549 .iter()
14550 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
14551 .collect(),
14552 },
14553 );
14554 }
14555 Some((expanded, bindings))
14556}
14557
14558fn cpp_specialization_matches(
14559 metadata: &CppTemplateMetadata,
14560 arguments: &[CppTemplateExpression],
14561) -> bool {
14562 if metadata.specialization_arguments.len() != arguments.len() {
14563 return false;
14564 }
14565 let parameter_names = metadata
14566 .parameters
14567 .iter()
14568 .map(|parameter| parameter.name.as_str())
14569 .collect::<HashSet<_>>();
14570 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
14571 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
14572 if !cpp_unify_template_term(
14573 &pattern.term,
14574 &argument.term,
14575 ¶meter_names,
14576 &mut bindings,
14577 ) {
14578 return false;
14579 }
14580 }
14581 true
14582}
14583
14584fn cpp_specialization_more_specialized(
14585 candidate: &CppTemplateMetadata,
14586 other: &CppTemplateMetadata,
14587) -> bool {
14588 cpp_specialization_pattern_accepts(other, candidate)
14589 && !cpp_specialization_pattern_accepts(candidate, other)
14590}
14591
14592fn cpp_specialization_pattern_accepts(
14593 broader: &CppTemplateMetadata,
14594 narrower: &CppTemplateMetadata,
14595) -> bool {
14596 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
14597 return false;
14598 }
14599 let parameter_names = broader
14600 .parameters
14601 .iter()
14602 .map(|parameter| parameter.name.as_str())
14603 .collect::<HashSet<_>>();
14604 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
14605 broader
14606 .specialization_arguments
14607 .iter()
14608 .zip(&narrower.specialization_arguments)
14609 .all(|(pattern, argument)| {
14610 cpp_unify_template_term(
14611 &pattern.term,
14612 &argument.term,
14613 ¶meter_names,
14614 &mut bindings,
14615 )
14616 })
14617}
14618
14619pub fn cpp_substitute_template_term(
14620 term: &CppTemplateTerm,
14621 bindings: &HashMap<String, CppTemplateTerm>,
14622) -> Option<CppTemplateTerm> {
14623 enum Work<'a> {
14624 Visit(&'a CppTemplateTerm),
14625 Build { kind: String, child_count: usize },
14626 }
14627
14628 let mut work = vec![Work::Visit(term)];
14629 let mut substituted = Vec::new();
14630 while let Some(next) = work.pop() {
14631 match next {
14632 Work::Visit(CppTemplateTerm::Parameter(name)) => {
14633 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
14634 }
14635 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
14636 substituted.push(CppTemplateTerm::Atom {
14637 kind: kind.clone(),
14638 text: text.clone(),
14639 });
14640 }
14641 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
14642 work.push(Work::Build {
14643 kind: kind.clone(),
14644 child_count: children.len(),
14645 });
14646 work.extend(children.iter().rev().map(Work::Visit));
14647 }
14648 Work::Build { kind, child_count } => {
14649 let children = substituted.split_off(substituted.len() - child_count);
14650 substituted.push(CppTemplateTerm::Node { kind, children });
14651 }
14652 }
14653 }
14654 substituted.pop()
14655}
14656
14657pub fn cpp_substitute_template_arguments(
14658 arguments: &[CppTemplateExpression],
14659 bindings: &HashMap<String, CppTemplateTerm>,
14660) -> Option<Vec<CppTemplateExpression>> {
14661 let mut substituted = Vec::new();
14662 for argument in arguments {
14663 let CppTemplateTerm::Node { kind, children } = &argument.term else {
14664 substituted.push(CppTemplateExpression {
14665 text: argument.text.clone(),
14666 term: cpp_substitute_template_term(&argument.term, bindings)?,
14667 });
14668 continue;
14669 };
14670 if kind != "parameter_pack_expansion" {
14671 substituted.push(CppTemplateExpression {
14672 text: argument.text.clone(),
14673 term: cpp_substitute_template_term(&argument.term, bindings)?,
14674 });
14675 continue;
14676 }
14677 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
14678 return None;
14679 };
14680 if ellipsis != "..." {
14681 return None;
14682 }
14683
14684 let mut pack_names = Vec::new();
14685 let mut work = vec![pattern];
14686 while let Some(term) = work.pop() {
14687 match term {
14688 CppTemplateTerm::Parameter(name)
14689 if matches!(
14690 bindings.get(name),
14691 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
14692 ) =>
14693 {
14694 if !pack_names.contains(name) {
14695 pack_names.push(name.clone());
14696 }
14697 }
14698 CppTemplateTerm::Node { children, .. } => work.extend(children),
14699 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
14700 }
14701 }
14702 let first_pack = pack_names.first()?;
14703 let CppTemplateTerm::Node {
14704 children: first_elements,
14705 ..
14706 } = bindings.get(first_pack)?
14707 else {
14708 return None;
14709 };
14710 let pack_len = first_elements.len();
14711 for pack_name in &pack_names {
14712 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
14713 return None;
14714 };
14715 if children.len() != pack_len {
14716 return None;
14717 }
14718 }
14719 for index in 0..pack_len {
14720 let mut element_bindings = bindings.clone();
14721 for pack_name in &pack_names {
14722 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
14723 return None;
14724 };
14725 element_bindings.insert(
14726 pack_name.clone(),
14727 cpp_clone_template_term_iterative(&children[index]),
14728 );
14729 }
14730 substituted.push(CppTemplateExpression {
14731 text: argument.text.clone(),
14732 term: cpp_substitute_template_term(pattern, &element_bindings)?,
14733 });
14734 }
14735 }
14736 Some(substituted)
14737}
14738
14739fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
14740 enum Work<'a> {
14741 Visit(&'a CppTemplateTerm),
14742 Build { kind: String, child_count: usize },
14743 }
14744
14745 let mut work = vec![Work::Visit(term)];
14746 let mut cloned = Vec::new();
14747 while let Some(next) = work.pop() {
14748 match next {
14749 Work::Visit(CppTemplateTerm::Parameter(name)) => {
14750 cloned.push(CppTemplateTerm::Parameter(name.clone()));
14751 }
14752 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
14753 cloned.push(CppTemplateTerm::Atom {
14754 kind: kind.clone(),
14755 text: text.clone(),
14756 });
14757 }
14758 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
14759 work.push(Work::Build {
14760 kind: kind.clone(),
14761 child_count: children.len(),
14762 });
14763 work.extend(children.iter().rev().map(Work::Visit));
14764 }
14765 Work::Build { kind, child_count } => {
14766 let children = cloned.split_off(cloned.len() - child_count);
14767 cloned.push(CppTemplateTerm::Node { kind, children });
14768 }
14769 }
14770 }
14771 cloned
14772 .pop()
14773 .expect("template term traversal emits one root")
14774}
14775
14776fn cpp_clone_template_expression_iterative(
14777 expression: &CppTemplateExpression,
14778) -> CppTemplateExpression {
14779 CppTemplateExpression {
14780 text: expression.text.clone(),
14781 term: cpp_clone_template_term_iterative(&expression.term),
14782 }
14783}
14784
14785pub fn cpp_unify_template_term(
14786 pattern: &CppTemplateTerm,
14787 argument: &CppTemplateTerm,
14788 parameters: &HashSet<&str>,
14789 bindings: &mut HashMap<String, CppTemplateTerm>,
14790) -> bool {
14791 let mut work = vec![(pattern, argument)];
14792 while let Some((pattern, argument)) = work.pop() {
14793 match pattern {
14794 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
14795 if let Some(bound) = bindings.get(name) {
14796 if !cpp_template_terms_equal(bound, argument) {
14797 return false;
14798 }
14799 } else {
14800 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
14801 }
14802 }
14803 CppTemplateTerm::Atom {
14804 kind: pattern_kind,
14805 text: pattern_text,
14806 } => {
14807 if !matches!(
14808 argument,
14809 CppTemplateTerm::Atom { kind, text }
14810 if kind == pattern_kind && text == pattern_text
14811 ) {
14812 return false;
14813 }
14814 }
14815 CppTemplateTerm::Node {
14816 kind: pattern_kind,
14817 children: pattern_children,
14818 } => {
14819 let CppTemplateTerm::Node { kind, children } = argument else {
14820 return false;
14821 };
14822 if kind != pattern_kind || children.len() != pattern_children.len() {
14823 return false;
14824 }
14825 work.extend(pattern_children.iter().zip(children).rev());
14826 }
14827 CppTemplateTerm::Parameter(_) => return false,
14828 }
14829 }
14830 true
14831}
14832
14833fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
14834 let mut work = vec![(left, right)];
14835 while let Some((left, right)) = work.pop() {
14836 match (left, right) {
14837 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
14838 if left != right {
14839 return false;
14840 }
14841 }
14842 (
14843 CppTemplateTerm::Atom {
14844 kind: left_kind,
14845 text: left_text,
14846 },
14847 CppTemplateTerm::Atom {
14848 kind: right_kind,
14849 text: right_text,
14850 },
14851 ) => {
14852 if left_kind != right_kind || left_text != right_text {
14853 return false;
14854 }
14855 }
14856 (
14857 CppTemplateTerm::Node {
14858 kind: left_kind,
14859 children: left_children,
14860 },
14861 CppTemplateTerm::Node {
14862 kind: right_kind,
14863 children: right_children,
14864 },
14865 ) => {
14866 if left_kind != right_kind || left_children.len() != right_children.len() {
14867 return false;
14868 }
14869 work.extend(left_children.iter().zip(right_children).rev());
14870 }
14871 _ => return false,
14872 }
14873 }
14874 true
14875}
14876
14877pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
14878 let mut components = Vec::new();
14879 let mut stack = vec![node];
14880 while let Some(current) = stack.pop() {
14881 match current.kind() {
14882 "identifier"
14883 | "field_identifier"
14884 | "namespace_identifier"
14885 | "type_identifier"
14886 | "operator_name"
14887 | "destructor_name" => components.push(current),
14888 "template_type" | "template_function" => {
14889 stack.push(current.child_by_field_name("name")?);
14890 }
14891 "dependent_name" => stack.push(current.named_child(0)?),
14892 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
14893 stack.push(current.child_by_field_name("name")?);
14894 if let Some(scope) = current.child_by_field_name("scope") {
14895 stack.push(scope);
14896 }
14897 }
14898 "nested_namespace_specifier" => {
14899 for index in (0..current.named_child_count()).rev() {
14900 stack.push(current.named_child(index)?);
14901 }
14902 }
14903 _ => return None,
14904 }
14905 }
14906 Some(components)
14907}
14908
14909pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
14910 node.child_by_field_name("scope").is_none()
14911 && node.child(0).is_some_and(|child| child.kind() == "::")
14912}
14913
14914fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
14915 let mut namespaces = Vec::new();
14916 let mut current = node.parent();
14917 while let Some(parent) = current {
14918 if parent.kind() == "namespace_definition"
14919 && let Some(name) = parent.child_by_field_name("name")
14920 {
14921 let mut components = Vec::new();
14922 append_cpp_name_components(name, source, &mut components)?;
14923 namespaces.push(components);
14924 }
14925 current = parent.parent();
14926 }
14927 namespaces.reverse();
14928 Some(namespaces.into_iter().flatten().collect())
14929}
14930
14931fn indexed_namespace_path_is_recoverable(
14942 lexical_scope: &[String],
14943 indexed_owner_scope: &[String],
14944 explicit_owner_component_count: usize,
14945) -> bool {
14946 if lexical_scope.is_empty() {
14947 return explicit_owner_component_count > 1;
14948 }
14949 if lexical_scope.len() >= indexed_owner_scope.len() {
14950 return false;
14951 }
14952 let mut indexed = indexed_owner_scope.iter();
14953 lexical_scope
14954 .iter()
14955 .all(|component| indexed.any(|candidate| candidate == component))
14956}
14957
14958pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
14959 let mut current = node.parent();
14960 while let Some(parent) = current {
14961 if parent.kind() == kind {
14962 return true;
14963 }
14964 current = parent.parent();
14965 }
14966 false
14967}
14968
14969pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
14975 let mut current = Some(node);
14976 while let Some(candidate) = current {
14977 if candidate.kind() == "declaration" {
14978 let Some(type_node) = candidate.child_by_field_name("type") else {
14979 return false;
14980 };
14981 if !(type_node.start_byte() <= node.start_byte()
14982 && node.end_byte() <= type_node.end_byte())
14983 {
14984 return false;
14985 }
14986 let mut cursor = candidate.walk();
14987 return candidate.named_children(&mut cursor).any(|child| {
14988 child.kind() == "init_declarator"
14989 && child
14990 .child_by_field_name("value")
14991 .is_some_and(|value| value.kind() == "cast_expression")
14992 });
14993 }
14994 current = candidate.parent();
14995 }
14996 false
14997}
14998
14999#[derive(Clone, Copy, PartialEq, Eq)]
15000pub(crate) enum QualifiedAliasReferenceKind {
15001 Ordinary,
15002 ConstructorWithExpressionArgument,
15003 ExhaustiveTemplate,
15004}
15005
15006pub(crate) fn qualified_alias_reference_preserves_target(
15013 node: Node<'_>,
15014 target: &CodeUnit,
15015 analyzer: &CppGraphSource<'_>,
15016 visibility: &VisibilityIndex<'_>,
15017 file: &ProjectFile,
15018 source: &str,
15019) -> Option<QualifiedAliasReferenceKind> {
15020 if !matches!(
15021 node.kind(),
15022 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15023 ) {
15024 return None;
15025 }
15026 let components = cpp_type_name_components(node, source)?;
15027 let name = components.last()?;
15028 analyzer.type_alias_provider().and_then(|provider| {
15029 visibility
15030 .visible_identifier_candidates(file, name)
15031 .find_map(|candidate| {
15032 let proof = provider.is_type_alias(candidate)
15033 && canonical_cpp_scope_components(candidate) == components
15034 && visibility.external_type_candidate_visible_in_context(
15035 analyzer, file, candidate, node,
15036 )
15037 && match cpp_template_reference_arguments(node, source) {
15038 Some(arguments) => visibility.template_alias_arguments_preserve_target(
15039 analyzer, file, candidate, &arguments, target,
15040 ),
15041 None => visibility.structured_alias_primary_preserves_target(
15042 analyzer, file, candidate, target,
15043 ),
15044 };
15045 proof.then(|| {
15046 if cpp_template_reference_arguments(node, source).is_some()
15047 && visibility.is_exhaustive_same_fqn_type_declaration_family(
15048 analyzer, file, candidate,
15049 )
15050 {
15051 QualifiedAliasReferenceKind::ExhaustiveTemplate
15052 } else if qualified_alias_constructor_has_expression_argument(node)
15053 || qualified_alias_local_constructor_declaration(node)
15054 {
15055 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
15056 } else {
15057 QualifiedAliasReferenceKind::Ordinary
15058 }
15059 })
15060 })
15061 })
15062}
15063
15064pub(crate) fn qualified_alias_reference_requires_terminal(
15065 reference: Option<QualifiedAliasReferenceKind>,
15066) -> bool {
15067 matches!(
15068 reference,
15069 Some(
15070 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
15071 | QualifiedAliasReferenceKind::ExhaustiveTemplate
15072 )
15073 )
15074}
15075
15076fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
15077 let Some(declaration) = node.parent().filter(|parent| {
15078 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
15079 }) else {
15080 return false;
15081 };
15082 let mut cursor = declaration.walk();
15083 declaration.named_children(&mut cursor).any(|child| {
15084 child.kind() == "init_declarator"
15085 && child
15086 .child_by_field_name("value")
15087 .filter(|value| value.kind() == "argument_list")
15088 .is_some_and(|arguments| {
15089 let mut cursor = arguments.walk();
15090 arguments.named_children(&mut cursor).any(|argument| {
15091 let is_parameter = matches!(
15092 argument.kind(),
15093 "parameter_declaration" | "optional_parameter_declaration"
15094 );
15095 if is_parameter {
15096 argument
15097 .child_by_field_name("type")
15098 .is_some_and(|type_node| {
15099 type_node.kind() == "type_identifier"
15100 && argument.child_by_field_name("declarator").is_none()
15101 })
15102 } else {
15103 !argument.kind().ends_with("_literal")
15104 && !matches!(argument.kind(), "true" | "false" | "nullptr")
15105 }
15106 })
15107 })
15108 })
15109}
15110
15111fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
15116 let Some(declaration) = node.parent().filter(|parent| {
15117 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
15118 }) else {
15119 return false;
15120 };
15121 if declaration
15122 .parent()
15123 .is_none_or(|parent| parent.kind() != "compound_statement")
15124 {
15125 return false;
15126 }
15127 let mut cursor = declaration.walk();
15128 declaration
15129 .named_children(&mut cursor)
15130 .any(|child| child.kind() == "function_declarator")
15131}
15132
15133pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
15139 loop {
15140 let next = match node.kind() {
15141 "qualified_identifier"
15142 | "scoped_identifier"
15143 | "template_method"
15144 | "template_function"
15145 | "template_type" => node.child_by_field_name("name"),
15146 "field_expression" => node.child_by_field_name("field"),
15147 _ => None,
15148 };
15149 let Some(next) = next else {
15150 return node;
15151 };
15152 node = next;
15153 }
15154}
15155
15156#[derive(Clone, Copy)]
15157pub struct RecoveredRelationalTemplateMemberCall<'tree> {
15158 pub receiver: Node<'tree>,
15159 pub member: Node<'tree>,
15160 pub arity: usize,
15161}
15162
15163pub fn recovered_relational_template_member_call(
15171 field: Node<'_>,
15172) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
15173 if field.kind() != "field_expression" {
15174 return None;
15175 }
15176 let receiver = field
15177 .child_by_field_name("argument")
15178 .or_else(|| field.child_by_field_name("object"))?;
15179 let member = field.child_by_field_name("field")?;
15180 let less = field.parent()?;
15181 if less.kind() != "binary_expression"
15182 || less.child_by_field_name("left") != Some(field)
15183 || less
15184 .child_by_field_name("operator")
15185 .is_none_or(|operator| operator.kind() != "<")
15186 || less.child_by_field_name("right").is_none()
15187 {
15188 return None;
15189 }
15190 let greater = less.parent()?;
15191 if greater.kind() != "binary_expression"
15192 || greater.child_by_field_name("left") != Some(less)
15193 || greater
15194 .child_by_field_name("operator")
15195 .is_none_or(|operator| operator.kind() != ">")
15196 {
15197 return None;
15198 }
15199 let arguments = greater.child_by_field_name("right")?;
15200 if arguments.kind() != "parenthesized_expression" {
15201 return None;
15202 }
15203 let arity = parenthesized_call_argument_arity(arguments)?;
15204 Some(RecoveredRelationalTemplateMemberCall {
15205 receiver,
15206 member,
15207 arity,
15208 })
15209}
15210
15211fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
15212 let expression = arguments.named_child(0)?;
15213 if expression.kind() != "comma_expression" {
15214 return Some(1);
15215 }
15216 let mut arity = 0usize;
15217 let mut stack = vec![expression];
15218 while let Some(node) = stack.pop() {
15219 if node.kind() == "comma_expression" {
15220 stack.push(node.child_by_field_name("right")?);
15221 stack.push(node.child_by_field_name("left")?);
15222 } else {
15223 arity += 1;
15224 }
15225 }
15226 Some(arity)
15227}
15228
15229pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
15232 while let Some(parent) = node.parent() {
15233 match parent.kind() {
15234 "call_expression" => {
15235 return parent
15236 .child_by_field_name("function")
15237 .or_else(|| parent.named_child(0))
15238 == Some(node);
15239 }
15240 "qualified_identifier"
15241 | "scoped_identifier"
15242 | "template_function"
15243 | "template_type"
15244 | "field_expression" => node = parent,
15245 _ => return false,
15246 }
15247 }
15248 false
15249}
15250
15251pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
15252 if is_call_callee_node(node) {
15253 function_terminal_node(node)
15254 } else {
15255 node
15256 }
15257}
15258
15259pub fn normalize_type_text(value: &str) -> String {
15260 strip_tag_type_prefix(
15261 normalize_cpp_whitespace(value)
15262 .trim_start_matches("const ")
15263 .trim_end_matches('*')
15264 .trim_end_matches('&')
15265 .trim(),
15266 )
15267 .to_string()
15268}
15269
15270fn strip_tag_type_prefix(value: &str) -> &str {
15271 let value = value.trim_start_matches("const ");
15272 value
15273 .strip_prefix("struct ")
15274 .or_else(|| value.strip_prefix("class "))
15275 .or_else(|| value.strip_prefix("enum "))
15276 .unwrap_or(value)
15277 .trim()
15278}
15279
15280pub fn normalize_reference_name(value: &str) -> Option<String> {
15281 let normalized = normalize_cpp_reference_text(value);
15282 (!normalized.is_empty()).then_some(normalized)
15283}
15284
15285pub fn normalize_cpp_reference_text(value: &str) -> String {
15286 let mut text = normalize_cpp_whitespace(value)
15287 .trim_start_matches("new ")
15288 .trim()
15289 .to_string();
15290 if let Some(index) = text.find(['(', '{']) {
15291 text.truncate(index);
15292 }
15293 if let Some(index) = text.find('<') {
15294 text.truncate(index);
15295 }
15296 let normalized = text
15297 .trim()
15298 .trim_start_matches("const ")
15299 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
15300 .trim_matches(':')
15301 .trim();
15302 strip_tag_type_prefix(normalized).to_string()
15303}
15304
15305pub fn cpp_name_for(unit: &CodeUnit) -> String {
15306 let short = unit.short_name().replace(['.', '$'], "::");
15307 if unit.package_name().is_empty() {
15308 short
15309 } else {
15310 format!("{}::{}", unit.package_name(), short)
15311 }
15312}
15313
15314fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
15318 let fq = unit.fq();
15319 if fq.is_empty() {
15320 return None;
15321 }
15322 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
15323 Some(
15324 fq.segments()
15325 .iter()
15326 .map(|&segment| interner.resolve(segment).0)
15327 .collect::<Vec<_>>()
15328 .join("::"),
15329 )
15330}
15331
15332fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
15333 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
15334 || unit.fq().is_empty() && cpp_name_for(unit) == expected
15335}
15336
15337pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
15346 let fq = unit.fq();
15347 if !fq.is_empty() {
15348 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
15349 let scope = fq
15350 .segments()
15351 .iter()
15352 .filter_map(|&segment| {
15353 let (text, kind) = interner.resolve(segment);
15354 matches!(
15355 kind,
15356 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
15357 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
15358 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
15359 )
15360 .then(|| text.to_string())
15361 })
15362 .collect();
15363 return scope;
15364 }
15365 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15366 brokk_bifrost_core::analyzer::Language::Cpp,
15367 &cpp_name_for(unit),
15368 )
15369}
15370
15371pub fn terminal_name(value: &str) -> &str {
15382 value
15383 .rsplit("::")
15384 .next()
15385 .unwrap_or(value)
15386 .rsplit(['.', '-', '>'])
15387 .next()
15388 .unwrap_or(value)
15389 .trim()
15390}
15391
15392pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
15393 terminal_name(&normalize_cpp_reference_text(value)) == expected
15394}
15395
15396pub fn name_matches_callable(value: &str, expected: &str) -> bool {
15397 name_matches_terminal(value, expected)
15398 || expected.starts_with("operator")
15399 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
15400}
15401
15402pub fn name_mentions(value: &str, expected: &str) -> bool {
15403 normalize_cpp_reference_text(value)
15404 .split("::")
15405 .any(|part| part == expected)
15406}
15407
15408pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
15409 let cpp_name = cpp_name_for(unit);
15410 if reference.contains("::") {
15411 return reference == cpp_name;
15412 }
15413 reference == cpp_name
15414 || terminal_name(reference) == unit.identifier()
15415 && (unit.package_name().is_empty() || reference == unit.identifier())
15416}
15417
15418pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
15419 match kind {
15420 TargetKind::Type
15421 | TargetKind::Constructor
15422 | TargetKind::Method
15423 | TargetKind::MemberField => true,
15424 TargetKind::FreeFunction => unit.is_function(),
15425 TargetKind::GlobalField => unit.is_field(),
15426 TargetKind::Macro => unit.is_macro(),
15427 }
15428}
15429
15430pub fn is_type_alias(unit: &CodeUnit) -> bool {
15431 unit.kind() == CodeUnitType::Field
15432 && unit.signature().is_some_and(|signature| {
15433 signature.starts_with("typedef ") || signature.starts_with("using ")
15434 })
15435}
15436
15437fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
15438 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
15439 let target_name = cpp_name_for(target);
15440 if normalized.contains("::") {
15441 return normalized == target_name;
15442 }
15443 if let Some(namespace) = alias.namespace.as_deref() {
15444 return namespace_prefixes(namespace)
15445 .into_iter()
15446 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
15447 }
15448 target.package_name().is_empty() && normalized == target.identifier()
15449}
15450
15451pub fn cpp_function_return_type_text(
15454 analyzer: &CppGraphSource<'_>,
15455 function: &CodeUnit,
15456) -> Option<String> {
15457 let metadata = analyzer.signature_metadata(function);
15458 if !metadata.is_empty() {
15459 let first = metadata.first()?.return_type_text()?;
15460 return metadata
15461 .iter()
15462 .all(|metadata| metadata.return_type_text() == Some(first))
15463 .then(|| first.to_string());
15464 }
15465 let signature = cpp_function_signature_text(analyzer, function)?;
15466 cpp_function_return_type_text_from_signature(&signature)
15467}
15468
15469fn cpp_function_signature_text(
15470 analyzer: &CppGraphSource<'_>,
15471 function: &CodeUnit,
15472) -> Option<String> {
15473 function
15474 .signature()
15475 .filter(|signature| signature.contains(function.identifier()))
15476 .map(str::to_string)
15477 .or_else(|| analyzer.signatures(function).first().cloned())
15478 .or_else(|| analyzer.get_source(function, false))
15479}
15480
15481fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
15482 let open = signature.find('(')?;
15483 let name_at = cpp_function_name_start(signature, open)?;
15484 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
15485 return Some(return_type);
15486 }
15487 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
15488 .split_whitespace()
15489 .filter(|token| {
15490 !matches!(
15491 *token,
15492 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
15493 )
15494 })
15495 .collect::<Vec<_>>()
15496 .join(" ");
15497 let type_text = type_text.trim();
15498 (!type_text.is_empty()).then(|| type_text.to_string())
15499}
15500
15501fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
15502 let before_parameters = &signature[..open];
15503 if let Some(operator_at) = before_parameters.rfind("operator") {
15504 let boundary = operator_at == 0
15505 || before_parameters[..operator_at]
15506 .chars()
15507 .next_back()
15508 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
15509 if boundary {
15510 return Some(operator_at);
15511 }
15512 }
15513 before_parameters
15514 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
15515 .map(|index| index + 1)
15516}
15517
15518fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
15519 let open = signature_from_name.find('(')?;
15520 let mut depth = 0i32;
15521 for (offset, ch) in signature_from_name[open..].char_indices() {
15522 match ch {
15523 '(' => depth += 1,
15524 ')' => {
15525 depth -= 1;
15526 if depth == 0 {
15527 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
15528 let arrow = rest.find("->")?;
15529 let return_type = rest[arrow + 2..].trim_start();
15530 let return_type = return_type
15531 .split(['{', ';'])
15532 .next()
15533 .unwrap_or(return_type)
15534 .trim();
15535 return (!return_type.is_empty()).then(|| return_type.to_string());
15536 }
15537 }
15538 _ => {}
15539 }
15540 }
15541 None
15542}
15543
15544fn cpp_strip_leading_template_clause(text: &str) -> &str {
15547 let trimmed = text.trim_start();
15548 let Some(rest) = trimmed.strip_prefix("template") else {
15549 return text;
15550 };
15551 let rest = rest.trim_start();
15552 if !rest.starts_with('<') {
15553 return text;
15554 }
15555 let mut depth = 0i32;
15556 for (offset, ch) in rest.char_indices() {
15557 match ch {
15558 '<' => depth += 1,
15559 '>' => {
15560 depth -= 1;
15561 if depth == 0 {
15562 return rest[offset + ch.len_utf8()..].trim_start();
15563 }
15564 }
15565 _ => {}
15566 }
15567 }
15568 text
15569}
15570
15571pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
15572 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
15582 namespace
15583 .strip_prefix("anonymous_namespace::")
15584 .unwrap_or(namespace)
15585 .to_string()
15586 })
15587}
15588
15589fn namespace_prefixes(namespace: &str) -> Vec<String> {
15590 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15596 brokk_bifrost_core::analyzer::Language::Cpp,
15597 namespace,
15598 );
15599 let mut prefixes = Vec::new();
15600 while !parts.is_empty() {
15601 prefixes.push(parts.join("::"));
15602 parts.pop();
15603 }
15604 prefixes
15605}
15606
15607fn nearest_namespace_candidates(
15608 candidates: Vec<CodeUnit>,
15609 normalized: &str,
15610 lexical_namespace: Option<&str>,
15611) -> Vec<CodeUnit> {
15612 if normalized.contains("::") {
15613 return candidates;
15614 }
15615 if let Some(namespace) = lexical_namespace {
15616 for prefix in namespace_prefixes(namespace) {
15617 let scoped = candidates
15618 .iter()
15619 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
15620 .cloned()
15621 .collect::<Vec<_>>();
15622 if !scoped.is_empty() {
15623 return scoped;
15624 }
15625 }
15626 }
15627 candidates
15628 .into_iter()
15629 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
15630 .collect()
15631}
15632
15633pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
15634 let mut namespaces = Vec::new();
15635 let mut current = node.parent();
15636 while let Some(parent) = current {
15637 if parent.kind() == "namespace_definition"
15638 && let Some(name) = parent.child_by_field_name("name")
15639 {
15640 let namespace = normalize_cpp_reference_text(node_text(name, source));
15641 if !namespace.is_empty() {
15642 namespaces.push(namespace);
15643 }
15644 }
15645 current = parent.parent();
15646 }
15647 if namespaces.is_empty() {
15648 None
15649 } else {
15650 namespaces.reverse();
15651 Some(namespaces.join("::"))
15652 }
15653}
15654
15655pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
15659 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
15660}
15661
15662fn type_owner_resolution(
15663 analyzer: &CppGraphSource<'_>,
15664 code_unit: &CodeUnit,
15665) -> Option<ResolvedTypeOwner> {
15666 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
15667}
15668
15669fn target_type_owner_resolution(
15670 analyzer: &CppGraphSource<'_>,
15671 code_unit: &CodeUnit,
15672) -> Option<ResolvedTypeOwner> {
15673 match type_owner_resolution(analyzer, code_unit) {
15674 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
15675 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
15676 }
15677}
15678
15679fn target_forward_owner_resolution(
15691 analyzer: &CppGraphSource<'_>,
15692 code_unit: &CodeUnit,
15693) -> Option<ResolvedTypeOwner> {
15694 if !code_unit.is_function() {
15695 return None;
15696 }
15697 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
15703 let cpp = analyzer.cpp?;
15704 let mut visible_files = HashSet::default();
15705 collect_include_closure(
15706 analyzer,
15707 cpp.include_target_index(),
15708 code_unit.source(),
15709 &mut visible_files,
15710 None,
15711 );
15712 let candidates = analyzer.workspace_definitions().exact(&owner_name);
15713 let visible_candidates = candidates
15714 .iter()
15715 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
15716 .cloned()
15717 .collect::<Vec<_>>();
15718 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
15719 DirectOwnerResolution::UniqueFull(unit) => {
15720 return Some(ResolvedTypeOwner {
15721 unit,
15722 is_forward_declaration: false,
15723 });
15724 }
15725 DirectOwnerResolution::ForwardsOnly(forwards) => {
15726 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
15727 unit: forwards.into_iter().next().unwrap(),
15728 is_forward_declaration: true,
15729 });
15730 }
15731 DirectOwnerResolution::Ambiguous => return None,
15732 DirectOwnerResolution::None => {}
15733 }
15734
15735 let candidates = candidates
15736 .into_iter()
15737 .filter(|candidate| candidate.is_class())
15738 .collect::<Vec<_>>();
15739 let (unit, is_forward_declaration) =
15740 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
15741 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
15742 DirectOwnerResolution::ForwardsOnly(forwards) => {
15743 (unique_logical_forward_owner(forwards)?, true)
15744 }
15745 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
15746 };
15747 Some(ResolvedTypeOwner {
15748 unit,
15749 is_forward_declaration,
15750 })
15751}
15752
15753pub fn precise_parent_of(
15754 analyzer: &CppGraphSource<'_>,
15755 visibility: &VisibilityIndex<'_>,
15756 code_unit: &CodeUnit,
15757) -> Option<CodeUnit> {
15758 visibility.cached_precise_parent_of(analyzer, code_unit)
15759}
15760
15761fn precise_parent_resolution(
15762 analyzer: &CppGraphSource<'_>,
15763 code_unit: &CodeUnit,
15764) -> Option<ResolvedTypeOwner> {
15765 #[cfg(any(test, feature = "test-support"))]
15766 if let Some(cpp) = analyzer.cpp {
15767 cpp.record_cpp_parent_resolution_for_test();
15768 }
15769 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
15770 return Some(ResolvedTypeOwner {
15771 unit,
15772 is_forward_declaration: false,
15773 });
15774 }
15775 let fallback = analyzer.parent_of(code_unit);
15776 if !code_unit.owner_is_type_scope() {
15777 return fallback.map(|unit| ResolvedTypeOwner {
15778 unit,
15779 is_forward_declaration: false,
15780 });
15781 }
15782 let owner_fq = code_unit
15783 .fq()
15784 .parent()
15785 .expect("a unit with an owner identifier has a structured parent");
15786 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
15787 match same_source_owner(analyzer, code_unit, &owner_candidates) {
15788 DirectOwnerResolution::UniqueFull(owner) => {
15789 return Some(ResolvedTypeOwner {
15790 unit: owner,
15791 is_forward_declaration: false,
15792 });
15793 }
15794 DirectOwnerResolution::Ambiguous => return None,
15795 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
15796 }
15797 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
15798 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
15799 unit: owner,
15800 is_forward_declaration: false,
15801 }),
15802 DirectOwnerResolution::Ambiguous => None,
15803 DirectOwnerResolution::ForwardsOnly(forwards) => {
15804 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
15805 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
15806 unit: owner,
15807 is_forward_declaration: false,
15808 }),
15809 FullOwnerResolution::None => {
15810 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
15811 unit,
15812 is_forward_declaration: true,
15813 })
15814 }
15815 FullOwnerResolution::Ambiguous => None,
15816 }
15817 }
15818 DirectOwnerResolution::None => {
15819 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
15820 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
15821 unit: owner,
15822 is_forward_declaration: false,
15823 }),
15824 FullOwnerResolution::Ambiguous => None,
15825 FullOwnerResolution::None => fallback
15826 .filter(|parent| {
15827 parent.source() == code_unit.source()
15828 && parent.fq() == &owner_fq
15829 && (!parent.is_class()
15830 || cpp_class_declaration_strength(analyzer, parent)
15831 == CppClassDeclarationStrength::Full)
15832 })
15833 .map(|unit| ResolvedTypeOwner {
15834 unit,
15835 is_forward_declaration: false,
15836 }),
15837 }
15838 }
15839 }
15840}
15841
15842fn exact_structural_type_parent(
15843 analyzer: &CppGraphSource<'_>,
15844 code_unit: &CodeUnit,
15845) -> Option<CodeUnit> {
15846 if !code_unit.is_function() && !code_unit.is_field() {
15847 return None;
15848 }
15849 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
15851 let parent = cpp.structural_parent_of(code_unit)?;
15852 (!parent.is_module()
15853 && parent.source() == code_unit.source()
15854 && parent.package_name() == code_unit.package_name()
15855 && parent.short_name() == encoded_owner)
15856 .then_some(parent)
15857}
15858
15859fn same_source_owner(
15860 analyzer: &CppGraphSource<'_>,
15861 code_unit: &CodeUnit,
15862 owner_candidates: &[CodeUnit],
15863) -> DirectOwnerResolution {
15864 let candidates = owner_candidates
15865 .iter()
15866 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
15867 .cloned()
15868 .collect::<Vec<_>>();
15869 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
15870 classify_direct_owner_candidates(analyzer, candidates.into_iter())
15871}
15872
15873fn visible_full_cpp_owner(
15874 analyzer: &CppGraphSource<'_>,
15875 code_unit: &CodeUnit,
15876 owner_candidates: &[CodeUnit],
15877) -> FullOwnerResolution {
15878 let Some(cpp) = analyzer.cpp else {
15879 return FullOwnerResolution::None;
15880 };
15881 let mut visible_files = HashSet::default();
15882 collect_include_closure(
15883 analyzer,
15884 cpp.include_target_index(),
15885 code_unit.source(),
15886 &mut visible_files,
15887 None,
15888 );
15889 let candidates = owner_candidates
15890 .iter()
15891 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
15892 .cloned()
15893 .collect::<Vec<_>>();
15894 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
15895 let mut full_definition = None;
15896 for candidate in candidates {
15897 match cpp_class_declaration_strength(analyzer, &candidate) {
15898 CppClassDeclarationStrength::Full if full_definition.is_some() => {
15899 return FullOwnerResolution::Ambiguous;
15900 }
15901 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
15902 CppClassDeclarationStrength::Forward => {}
15903 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
15904 }
15905 }
15906 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
15907}
15908
15909pub enum DirectOwnerResolution {
15910 None,
15911 ForwardsOnly(Vec<CodeUnit>),
15912 UniqueFull(CodeUnit),
15913 Ambiguous,
15914}
15915
15916enum FullOwnerResolution {
15917 None,
15918 Unique(CodeUnit),
15919 Ambiguous,
15920}
15921
15922#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15923pub enum CppClassDeclarationStrength {
15924 Full,
15925 Forward,
15926 Unknown,
15927}
15928
15929fn directly_included_owner(
15930 analyzer: &CppGraphSource<'_>,
15931 code_unit: &CodeUnit,
15932 owner_candidates: &[CodeUnit],
15933) -> DirectOwnerResolution {
15934 let Some(cpp) = analyzer.cpp else {
15935 return DirectOwnerResolution::None;
15936 };
15937 let imports = analyzer.import_statements(code_unit.source());
15938 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
15939 .into_iter()
15940 .flat_map(|include| {
15941 resolve_include_targets_with_index(
15942 code_unit.source(),
15943 &include,
15944 cpp.include_target_index(),
15945 )
15946 })
15947 .collect();
15948 let candidates = owner_candidates
15949 .iter()
15950 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
15951 .cloned()
15952 .collect::<Vec<_>>();
15953 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
15954 classify_direct_owner_candidates(analyzer, candidates.into_iter())
15955}
15956
15957fn prefer_member_declaring_owners(
15958 analyzer: &CppGraphSource<'_>,
15959 member: &CodeUnit,
15960 candidates: Vec<CodeUnit>,
15961) -> Vec<CodeUnit> {
15962 let matching = candidates
15963 .iter()
15964 .filter(|owner| owner_declares_member(analyzer, owner, member))
15965 .cloned()
15966 .collect::<Vec<_>>();
15967 if matching.is_empty() {
15968 candidates
15969 } else {
15970 matching
15971 }
15972}
15973
15974fn owner_declares_member(
15975 analyzer: &CppGraphSource<'_>,
15976 owner: &CodeUnit,
15977 member: &CodeUnit,
15978) -> bool {
15979 analyzer.direct_children(owner).into_iter().any(|child| {
15980 child.kind() == member.kind()
15981 && child.identifier() == member.identifier()
15982 && child.signature() == member.signature()
15983 })
15984}
15985
15986fn classify_direct_owner_candidates(
15987 analyzer: &CppGraphSource<'_>,
15988 candidates: impl Iterator<Item = CodeUnit>,
15989) -> DirectOwnerResolution {
15990 collapse_owner_candidates(candidates.map(|candidate| {
15991 let strength = cpp_class_declaration_strength(analyzer, &candidate);
15992 (candidate, strength)
15993 }))
15994}
15995
15996pub fn collapse_owner_candidates(
15997 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
15998) -> DirectOwnerResolution {
15999 let mut full_definition = None;
16000 let mut forwards = Vec::new();
16001 for (candidate, strength) in candidates {
16002 match strength {
16003 CppClassDeclarationStrength::Full if full_definition.is_some() => {
16004 return DirectOwnerResolution::Ambiguous;
16005 }
16006 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
16007 CppClassDeclarationStrength::Forward => forwards.push(candidate),
16008 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
16009 }
16010 }
16011 if let Some(owner) = full_definition {
16012 DirectOwnerResolution::UniqueFull(owner)
16013 } else if !forwards.is_empty() {
16014 DirectOwnerResolution::ForwardsOnly(forwards)
16015 } else {
16016 DirectOwnerResolution::None
16017 }
16018}
16019
16020#[cfg(any(test, feature = "test-support"))]
16021pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
16022 unique_logical_forward_owner(forwards)
16023}
16024
16025fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
16026 let first = forwards.pop()?;
16027 forwards
16028 .iter()
16029 .all(|forward| same_logical_symbol(forward, &first))
16030 .then_some(first)
16031}
16032
16033pub fn cpp_class_declaration_strength(
16034 analyzer: &CppGraphSource<'_>,
16035 candidate: &CodeUnit,
16036) -> CppClassDeclarationStrength {
16037 let Some(cpp) = analyzer.cpp else {
16045 return uncached_cpp_class_declaration_strength(analyzer, candidate);
16046 };
16047 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
16048 return strength;
16049 }
16050 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
16051 cpp.cache_class_declaration_strength(candidate, strength);
16052 strength
16053}
16054
16055fn uncached_cpp_class_declaration_strength(
16056 analyzer: &CppGraphSource<'_>,
16057 candidate: &CodeUnit,
16058) -> CppClassDeclarationStrength {
16059 if let Some(cpp) = analyzer.cpp
16060 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
16061 {
16062 return cpp_class_declaration_strength_in_tree(
16063 analyzer,
16064 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
16065 candidate,
16066 prepared.source(),
16067 prepared.tree().root_node(),
16068 );
16069 }
16070 let Some(source) = analyzer.indexed_source(candidate.source()) else {
16071 return CppClassDeclarationStrength::Unknown;
16072 };
16073 #[cfg(any(test, feature = "test-support"))]
16074 if let Some(cpp) = analyzer.cpp {
16075 cpp.record_cpp_class_strength_parse_for_test();
16076 }
16077 let mut parser = Parser::new();
16078 if parser
16079 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16080 .is_err()
16081 {
16082 return CppClassDeclarationStrength::Unknown;
16083 }
16084 let Some(tree) = parser.parse(&source, None) else {
16085 return CppClassDeclarationStrength::Unknown;
16086 };
16087 let recovered_export_classes =
16090 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
16091 cpp_class_declaration_strength_in_tree(
16092 analyzer,
16093 &recovered_export_classes,
16094 candidate,
16095 &source,
16096 tree.root_node(),
16097 )
16098}
16099
16100fn cpp_class_declaration_strength_in_tree(
16101 analyzer: &CppGraphSource<'_>,
16102 recovered_export_classes: &CppRecoveredExportClassIndex,
16103 candidate: &CodeUnit,
16104 source: &str,
16105 root: Node<'_>,
16106) -> CppClassDeclarationStrength {
16107 let ranges = analyzer.ranges(candidate);
16108 let mut saw_forward = false;
16109 for range in ranges {
16110 match recovered_class_body_at(
16113 recovered_export_classes,
16114 root,
16115 source,
16116 candidate.identifier(),
16117 &range,
16118 ) {
16119 Some(true) => return CppClassDeclarationStrength::Full,
16120 Some(false) => {
16121 saw_forward = true;
16122 continue;
16123 }
16124 None => {}
16125 }
16126 let covers_range_start = |node: &Node<'_>| {
16133 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
16134 };
16135 let mut stack = Vec::new();
16136 if covers_range_start(&root) {
16137 stack.push(root);
16138 }
16139 while let Some(node) = stack.pop() {
16140 if node.start_byte() == range.start_byte
16141 && node.end_byte() == range.end_byte
16142 && matches!(
16143 node.kind(),
16144 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
16145 )
16146 {
16147 if cpp_class_node_has_body(node) {
16148 return CppClassDeclarationStrength::Full;
16149 }
16150 saw_forward = true;
16151 }
16152 let mut cursor = node.walk();
16153 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
16154 }
16155 }
16156 if saw_forward {
16157 CppClassDeclarationStrength::Forward
16158 } else {
16159 CppClassDeclarationStrength::Unknown
16160 }
16161}
16162
16163fn cpp_class_node_has_body(node: Node<'_>) -> bool {
16164 node.child_by_field_name("body").is_some() || {
16165 let mut cursor = node.walk();
16166 node.named_children(&mut cursor).any(|child| {
16167 matches!(
16168 child.kind(),
16169 "declaration_list" | "field_declaration_list" | "enumerator_list"
16170 )
16171 })
16172 }
16173}
16174
16175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16176enum CppCTagKind {
16177 Struct,
16178 Union,
16179}
16180
16181fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
16182 let declaration = analyzer.get_source(code_unit, false)?;
16183 let mut parser = Parser::new();
16184 parser
16185 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16186 .ok()?;
16187 let tree = parser.parse(&declaration, None)?;
16188 let mut stack = vec![tree.root_node()];
16189 while let Some(node) = stack.pop() {
16190 let kind = match node.kind() {
16191 "struct_specifier" => CppCTagKind::Struct,
16192 "union_specifier" => CppCTagKind::Union,
16193 _ => {
16194 let mut cursor = node.walk();
16195 stack.extend(node.named_children(&mut cursor));
16196 continue;
16197 }
16198 };
16199 if node
16200 .child_by_field_name("name")
16201 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
16202 {
16203 return Some(kind);
16204 }
16205 let mut cursor = node.walk();
16206 stack.extend(node.named_children(&mut cursor));
16207 }
16208 None
16209}
16210
16211pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
16212 if !code_unit.owner_is_type_scope() {
16213 return None;
16214 }
16215 let owner_fq = code_unit.fq().parent()?;
16216 ctx.analyzer
16217 .workspace_definitions()
16218 .exact(&owner_fq)
16219 .into_iter()
16220 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
16221}
16222
16223pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
16224 left.kind() == right.kind()
16225 && left.fq_name() == right.fq_name()
16226 && left.signature() == right.signature()
16227 && left.source() == right.source()
16228}
16229
16230pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
16231 same_symbol(left, right) || same_logical_symbol(left, right)
16232}
16233
16234pub fn same_visible_global_field_symbol(
16235 analyzer: &CppGraphSource<'_>,
16236 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
16237 left: &CodeUnit,
16238 right: &CodeUnit,
16239) -> bool {
16240 if same_symbol(left, right) {
16241 return true;
16242 }
16243 if !same_logical_symbol(left, right) {
16244 return false;
16245 }
16246 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
16247 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
16248 {
16249 left.source() == right.source()
16250 } else {
16251 true
16252 }
16253}
16254
16255fn cpp_global_field_has_internal_linkage_cached(
16256 analyzer: &CppGraphSource<'_>,
16257 cache: &mut HashMap<CodeUnit, bool>,
16258 candidate: &CodeUnit,
16259) -> bool {
16260 if let Some(internal) = cache.get(candidate) {
16261 return *internal;
16262 }
16263 #[cfg(any(test, feature = "test-support"))]
16264 note_cpp_global_field_internal_linkage_classification_for_test();
16265 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
16266 cache.insert(candidate.clone(), internal);
16267 internal
16268}
16269
16270#[cfg(any(test, feature = "test-support"))]
16271thread_local! {
16272 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
16273}
16274
16275#[cfg(any(test, feature = "test-support"))]
16276fn note_cpp_global_field_internal_linkage_classification_for_test() {
16277 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
16278 count.set(count.get() + 1);
16279 });
16280}
16281
16282#[cfg(any(test, feature = "test-support"))]
16283pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
16284 body: impl FnOnce() -> T,
16285) -> (T, usize) {
16286 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
16287 count.set(0);
16288 let result = body();
16289 let observed = count.get();
16290 count.set(0);
16291 (result, observed)
16292 })
16293}
16294
16295pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
16296 left.kind() == right.kind()
16297 && left.fq_name() == right.fq_name()
16298 && left.signature() == right.signature()
16299}
16300
16301pub fn cpp_global_field_has_internal_linkage(
16302 analyzer: &CppGraphSource<'_>,
16303 candidate: &CodeUnit,
16304) -> bool {
16305 if !candidate.is_field() || candidate.short_name().contains('.') {
16306 return false;
16307 }
16308 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
16309 return false;
16310 };
16311 match local_linkage {
16312 CppFieldLinkage::Internal => true,
16313 CppFieldLinkage::External => false,
16314 CppFieldLinkage::InternalUnlessExternalPeer => {
16315 !cpp_global_field_linkage_peers(analyzer, candidate)
16316 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
16317 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
16318 }
16319 }
16320}
16321
16322fn cpp_global_field_linkage_peers<'a>(
16323 analyzer: &CppGraphSource<'a>,
16324 candidate: &'a CodeUnit,
16325) -> impl Iterator<Item = CodeUnit> + 'a {
16326 let name = candidate.fq().clone();
16327 analyzer
16328 .workspace_definitions()
16329 .exact(&name)
16330 .into_iter()
16331 .filter(move |peer| {
16332 if peer == candidate {
16333 return false;
16334 }
16335 #[cfg(any(test, feature = "test-support"))]
16336 note_cpp_global_field_linkage_peer_inspection_for_test();
16337 same_logical_symbol(peer, candidate)
16338 })
16339}
16340
16341#[cfg(any(test, feature = "test-support"))]
16342thread_local! {
16343 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
16344}
16345
16346#[cfg(any(test, feature = "test-support"))]
16347fn note_cpp_global_field_linkage_peer_inspection_for_test() {
16348 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
16349 count.set(count.get() + 1);
16350 });
16351}
16352
16353#[cfg(any(test, feature = "test-support"))]
16354pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
16355 body: impl FnOnce() -> T,
16356) -> (T, usize) {
16357 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
16358 count.set(0);
16359 let result = body();
16360 let observed = count.get();
16361 count.set(0);
16362 (result, observed)
16363 })
16364}
16365
16366fn cpp_global_field_declaration_linkage(
16367 analyzer: &CppGraphSource<'_>,
16368 candidate: &CodeUnit,
16369) -> Option<CppFieldLinkage> {
16370 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
16371 return Some(linkage);
16372 }
16373 let cpp = analyzer.cpp?;
16374 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
16375 return cpp_global_field_declaration_linkage_in_tree(
16376 analyzer,
16377 candidate,
16378 prepared.source(),
16379 prepared.tree().root_node(),
16380 );
16381 }
16382 let source = analyzer.indexed_source(candidate.source())?;
16383 let mut parser = Parser::new();
16384 if parser
16385 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16386 .is_err()
16387 {
16388 return None;
16389 }
16390 let tree = parser.parse(&source, None)?;
16391 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
16392}
16393
16394fn cpp_global_field_declaration_linkage_in_tree(
16395 analyzer: &CppGraphSource<'_>,
16396 candidate: &CodeUnit,
16397 source: &str,
16398 root: Node<'_>,
16399) -> Option<CppFieldLinkage> {
16400 analyzer.ranges(candidate).iter().find_map(|range| {
16401 node_for_exact_range(root, range)
16402 .and_then(enclosing_cpp_field_declaration)
16403 .map(|declaration| {
16404 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
16406 })
16407 })
16408}
16409
16410fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
16411 loop {
16412 if matches!(node.kind(), "declaration" | "field_declaration") {
16413 return Some(node);
16414 }
16415 node = node.parent()?;
16416 }
16417}
16418
16419#[cfg(test)]
16420mod tests {
16421 use super::*;
16422
16423 #[test]
16424 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
16425 let source = "int size(void) { return sizeof(((Payload))); }\n";
16426 let mut parser = Parser::new();
16427 parser
16428 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16429 .expect("C++ grammar");
16430 let tree = parser.parse(source, None).expect("fixture tree");
16431 let start = source.find("Payload").expect("sizeof operand");
16432 let node = tree
16433 .root_node()
16434 .named_descendant_for_byte_range(start, start + "Payload".len())
16435 .expect("focused operand");
16436 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
16437 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
16438
16439 assert_eq!(node.kind(), "identifier");
16440 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
16441 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
16442 }
16443
16444 fn parse_cpp(source: &str) -> Tree {
16445 let mut parser = Parser::new();
16446 parser
16447 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16448 .expect("C++ grammar");
16449 parser.parse(source, None).expect("fixture tree")
16450 }
16451
16452 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
16453 let start = source.find(needle).expect("fixture needle");
16454 tree.root_node()
16455 .named_descendant_for_byte_range(start, start + needle.len())
16456 .expect("node at needle")
16457 }
16458
16459 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
16464namespace matchers {
16465 namespace detail {
16466 class API [[nodiscard]] First {
16467 public:
16468 int value() const { return count_ + 1; }
16469 private:
16470 int count_;
16471 };
16472 class API [[nodiscard]] Second {
16473 public:
16474 int value() const { return count_ + 2; }
16475 private:
16476 int count_;
16477 };
16478 } // namespace detail
16479
16480 template <typename T>
16481 void tail_function(MatcherBase<T> const& value);
16482
16483 class TailClass {};
16484} // namespace matchers
16485} // namespace app
16486
16487struct AfterAll {};
16488"#;
16489
16490 #[test]
16491 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
16492 let source = STOLEN_BRACE_CASCADE;
16493 let tree = parse_cpp(source);
16494 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
16495
16496 let tail_class = named_node_at(&tree, source, "TailClass");
16497 assert!(
16498 !has_ancestor_kind(tail_class, "namespace_definition"),
16499 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
16500 );
16501 let displaced = named_node_at(&tree, source, "Second");
16502 assert_eq!(
16503 enclosing_namespace_components(displaced, source),
16504 Some(vec!["app".to_string(), "matchers".to_string()]),
16505 "the fixture must displace the second class out of detail"
16506 );
16507
16508 let components = |needle: &str| {
16509 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
16510 };
16511 assert_eq!(components("First"), ["app", "matchers", "detail"]);
16512 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
16513 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
16514 assert_eq!(components("tail_function"), ["app", "matchers"]);
16515 assert_eq!(components("TailClass"), ["app", "matchers"]);
16516 assert!(components("AfterAll").is_empty());
16517 }
16518
16519 #[test]
16520 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
16521 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
16522 let tree = parse_cpp(clean);
16523 assert!(!tree.root_node().has_error());
16524 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
16525
16526 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
16529 let tree = parse_cpp(damaged);
16530 assert!(tree.root_node().has_error());
16531 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
16532 assert_eq!(
16533 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
16534 ["a"]
16535 );
16536 }
16537
16538 #[test]
16539 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
16540 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
16541 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
16542 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
16543 assert!(indexed_namespace_path_is_recoverable(
16544 &["cache".to_string()],
16545 &indexed,
16546 1,
16547 ));
16548 }
16549
16550 #[test]
16551 fn sort_lookup_units_totally_orders_every_identity_field() {
16552 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
16553 let base = CodeUnit::with_signature(
16554 file.clone(),
16555 CodeUnitType::Function,
16556 "scope",
16557 "value",
16558 Some("()".to_string()),
16559 false,
16560 );
16561 let different_kind = CodeUnit::with_signature(
16562 file.clone(),
16563 CodeUnitType::Field,
16564 "scope",
16565 "value",
16566 Some("()".to_string()),
16567 false,
16568 );
16569 let synthetic = base.with_synthetic(true);
16570
16571 let interner = segment_interner();
16572 let mut member_fq = FqName::new();
16573 member_fq.push(interner.intern("scope", SegmentKind::Package));
16574 member_fq.push(interner.intern("value", SegmentKind::Member));
16575 let different_package_boundary = CodeUnit::from_fq(
16576 file.clone(),
16577 CodeUnitType::Function,
16578 member_fq,
16579 0,
16580 Some("()".to_string()),
16581 false,
16582 );
16583
16584 let mut unknown_fq = FqName::new();
16585 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
16586 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
16587 let different_segment_kind = CodeUnit::from_fq(
16588 file,
16589 CodeUnitType::Function,
16590 unknown_fq,
16591 1,
16592 Some("()".to_string()),
16593 false,
16594 );
16595
16596 let input = vec![
16597 base,
16598 different_kind,
16599 synthetic,
16600 different_package_boundary,
16601 different_segment_kind,
16602 ];
16603 let mut expected = input.clone();
16604 sort_lookup_units(&mut expected);
16605 assert!(expected.windows(2).all(|pair| {
16606 let mut ordered = pair.to_vec();
16607 sort_lookup_units(&mut ordered);
16608 ordered == pair && pair[0] != pair[1]
16609 }));
16610
16611 let mut reversed = input.clone();
16612 reversed.reverse();
16613 sort_lookup_units(&mut reversed);
16614 assert_eq!(reversed, expected);
16615
16616 let mut rotated = input;
16617 rotated.rotate_left(2);
16618 sort_lookup_units(&mut rotated);
16619 assert_eq!(rotated, expected);
16620 }
16621
16622 #[test]
16623 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
16624 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";
16625 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
16626 let parse = |source: &str| {
16627 let mut parser = Parser::new();
16628 parser
16629 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16630 .expect("C++ grammar");
16631 parser.parse(source, None).expect("fixture tree")
16632 };
16633
16634 let tree = parse(damaged);
16635 let root = tree.root_node();
16636 let target = damaged.find("target").expect("target byte");
16637 let declaration = root
16638 .descendant_for_byte_range(target, target + "target".len())
16639 .and_then(|mut node| {
16640 loop {
16641 if node.kind() == "declaration" {
16642 break Some(node);
16643 }
16644 node = node.parent()?;
16645 }
16646 })
16647 .expect("declaration after the displaced terminator");
16648 let conditional = declaration
16649 .parent()
16650 .filter(|node| node.kind() == "preproc_ifdef")
16651 .expect("damaged inner conditional");
16652 let outer = conditional
16653 .parent()
16654 .filter(|node| node.kind() == "preproc_ifdef")
16655 .expect("ordinary outer include guard");
16656 let terminator = cpp_displaced_preprocessor_terminator(conditional)
16657 .expect("structured displaced #endif");
16658 assert_eq!(node_text(terminator, damaged), "#endif");
16659 assert!(terminator.end_byte() <= declaration.start_byte());
16660 assert!(!preprocessor_conditional_contains_descendant(
16661 conditional,
16662 declaration
16663 ));
16664 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
16665 assert!(preprocessor_conditional_contains_descendant(
16666 outer,
16667 declaration
16668 ));
16669
16670 let tree = parse(guarded);
16671 let conditional = tree
16672 .root_node()
16673 .named_child(0)
16674 .filter(|node| node.kind() == "preproc_ifdef")
16675 .expect("ordinary conditional");
16676 let declaration = conditional
16677 .named_children(&mut conditional.walk())
16678 .find(|node| node.kind() == "declaration")
16679 .expect("guarded declaration");
16680 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
16681 assert!(preprocessor_conditional_contains_descendant(
16682 conditional,
16683 declaration
16684 ));
16685
16686 let damaged_alternative = format!(
16687 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
16688 "UNUSED(value)\n".repeat(64)
16689 );
16690 let tree = parse(&damaged_alternative);
16691 let conditional = tree
16692 .root_node()
16693 .named_child(0)
16694 .filter(|node| node.kind() == "preproc_ifdef")
16695 .expect("outer conditional with an alternative");
16696 assert!(conditional.has_error());
16697 assert!(conditional.child_by_field_name("alternative").is_some());
16698 assert!(
16699 conditional
16700 .child(conditional.child_count() - 1)
16701 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
16702 );
16703 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
16704
16705 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";
16706 let tree = parse(split_declaration);
16707 let root = tree.root_node();
16708 let conditional = root
16709 .named_children(&mut root.walk())
16710 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
16711 .expect("split declaration conditional");
16712 let target = split_declaration
16713 .find("static int target")
16714 .expect("target byte");
16715 let boundary =
16716 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
16717 assert!(boundary.end_byte <= target, "{boundary:?}");
16718 assert_eq!(boundary.end_line, 9, "{boundary:?}");
16719 let target_node = root
16720 .descendant_for_byte_range(target, target + "static".len())
16721 .expect("target node");
16722 assert!(!preprocessor_conditional_contains_descendant(
16723 conditional,
16724 target_node
16725 ));
16726 }
16727
16728 #[test]
16729 fn fragmented_reference_guard_is_recovered() {
16730 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";
16731 let mut parser = Parser::new();
16732 parser
16733 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16734 .expect("C++ grammar");
16735 let tree = parser.parse(source, None).expect("fixture tree");
16736 let start = source.rfind("helper").expect("reference byte");
16737 let node = tree
16738 .root_node()
16739 .descendant_for_byte_range(start, start + "helper".len())
16740 .expect("reference node");
16741 let mut expected = HashSet::default();
16742 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
16743 vec![
16744 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
16745 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
16746 ],
16747 )));
16748 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
16749 }
16750
16751 #[test]
16752 fn expression_defined_and_ifndef_guards_are_incompatible() {
16753 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
16754 let mut parser = Parser::new();
16755 parser
16756 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16757 .expect("C++ grammar");
16758 let tree = parser.parse(source, None).expect("fixture tree");
16759 let root = tree.root_node();
16760 let selected_start = source.find("selected").expect("selected declaration");
16761 let rejected_start = source.find("rejected").expect("rejected declaration");
16762 let selected = root
16763 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
16764 .expect("selected node");
16765 let rejected = root
16766 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
16767 .expect("rejected node");
16768 let selected_guards =
16769 preprocessor_guard_environment(selected, source).expect("selected guards");
16770 let rejected_guards =
16771 preprocessor_guard_environment(rejected, source).expect("rejected guards");
16772
16773 assert!(
16774 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
16775 "opposite spellings of one macro guard must contradict"
16776 );
16777 }
16778
16779 #[test]
16780 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
16781 let source = r#"#ifdef _WIN32
16782#if defined(__cplusplus)
16783extern "C"
16784#endif
16785int platform_api(void);
16786#endif
16787
16788#ifdef _WIN32
16789static int entropy_target(void) { return 0; }
16790#else
16791#ifdef HAVE_COMMON_RANDOM
16792static int other_target(void) { return 0; }
16793#elif defined(HAVE_GETENTROPY)
16794static int entropy_target(void) { return 1; }
16795static int use_entropy(void) { return entropy_target(); }
16796#endif
16797#endif
16798"#;
16799 let mut parser = Parser::new();
16800 parser
16801 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16802 .expect("C++ grammar");
16803 let tree = parser.parse(source, None).expect("fixture tree");
16804 let start = source.rfind("entropy_target()").expect("reference");
16805 let node = tree
16806 .root_node()
16807 .descendant_for_byte_range(start, start + "entropy_target".len())
16808 .expect("reference node");
16809 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
16810 assert!(
16811 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
16812 "{guards:#?}"
16813 );
16814 assert!(
16815 guards.contains(&PreprocessorGuard::Undefined(
16816 "HAVE_COMMON_RANDOM".to_string()
16817 )),
16818 "{guards:#?}"
16819 );
16820 assert!(
16821 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
16822 "{guards:#?}"
16823 );
16824 assert!(
16825 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
16826 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
16827 );
16828 }
16829
16830 #[test]
16831 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
16832 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";
16833 let mut parser = Parser::new();
16834 parser
16835 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16836 .expect("C++ grammar");
16837 let tree = parser.parse(source, None).expect("fixture tree");
16838 let root = tree.root_node();
16839 let node_at = |text: &str, start: usize| {
16840 root.descendant_for_byte_range(start, start + text.len())
16841 .expect("token node")
16842 };
16843
16844 let key_start = source.find("case KEY").expect("case label") + "case ".len();
16845 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
16846 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
16847 assert!(!is_ordinary_macro_reference_node(node_at(
16848 "ENABLE_KEYS",
16849 guard_start,
16850 )));
16851 }
16852
16853 #[test]
16854 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
16855 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";
16856 let mut parser = Parser::new();
16857 parser
16858 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16859 .expect("C++ grammar");
16860 let tree = parser.parse(source, None).expect("fixture tree");
16861 let root = tree.root_node();
16862 let definition_start = source.find("target(void)").expect("definition");
16863 let reference_start = source.rfind("target()").expect("reference");
16864 let definition = root
16865 .descendant_for_byte_range(definition_start, definition_start + "target".len())
16866 .expect("definition node");
16867 let reference = root
16868 .descendant_for_byte_range(reference_start, reference_start + "target".len())
16869 .expect("reference node");
16870 let required =
16871 preprocessor_guard_environment(definition, source).expect("definition guard");
16872 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
16873 assert!(guard_requirements_hold_at_reference(
16874 &required,
16875 Some(&active)
16876 ));
16877 }
16878
16879 #[test]
16880 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
16881 let source = "g_autoptr(FuChunkArray) self = make_array();";
16882 let mut parser = Parser::new();
16883 parser
16884 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16885 .expect("C++ grammar");
16886 let tree = parser.parse(source, None).expect("fixture tree");
16887 let statement = tree.root_node().named_child(0).expect("statement");
16888 let binding =
16889 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
16890 assert_eq!(binding.name, "self");
16891 assert_eq!(binding.type_name, "FuChunkArray");
16892 assert_eq!(binding.pointer_depth, 1);
16893
16894 let near_miss = "holder(FuChunkArray) self = make_array();";
16895 let tree = parser.parse(near_miss, None).expect("near-miss tree");
16896 let statement = tree.root_node().named_child(0).expect("statement");
16897 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
16898 }
16899
16900 #[test]
16901 fn boolean_guard_normalization_proves_equivalence_and_implication() {
16902 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
16903 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
16904 let negated_windows_branch =
16905 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
16906 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
16907 assert_eq!(negated_windows_branch, portable);
16908
16909 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
16910 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
16911 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
16912 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
16913 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
16914 assert!(fallback_branch.implies(&fallback_declaration));
16915 assert!(
16916 BooleanGuardExpression::Truthy("FEATURE".to_string())
16917 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
16918 );
16919 assert!(
16920 BooleanGuardExpression::Undefined("FEATURE".to_string())
16921 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
16922 );
16923 assert!(
16924 !BooleanGuardExpression::Defined("FEATURE".to_string())
16925 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
16926 );
16927 assert!(!fallback_declaration.implies(&fallback_branch));
16928 }
16929
16930 #[test]
16931 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
16932 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";
16933 let mut parser = Parser::new();
16934 parser
16935 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16936 .expect("C++ grammar");
16937 let tree = parser.parse(source, None).expect("fixture tree");
16938 let root = tree.root_node();
16939 let call = |marker: &str| {
16940 let start = source.find(marker).expect("call marker");
16941 let mut node = root
16942 .descendant_for_byte_range(start, start + "helper".len())
16943 .expect("call name node");
16944 loop {
16945 if node.kind() == "call_expression" {
16946 break node;
16947 }
16948 node = node.parent().expect("call expression ancestor");
16949 }
16950 };
16951 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
16952 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
16953 let keyword_call = call("helper(NULL, template); /* bound */");
16954 let keyword_arguments = keyword_call
16955 .child_by_field_name("arguments")
16956 .expect("keyword argument list");
16957 assert_eq!(
16958 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
16959 1
16960 );
16961 assert_eq!(
16962 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
16963 0
16964 );
16965
16966 let unbound_call = call("helper(NULL, template); /* unbound */");
16967 let unbound_arguments = unbound_call
16968 .child_by_field_name("arguments")
16969 .expect("unbound argument list");
16970 assert_eq!(
16971 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
16972 0
16973 );
16974 }
16975
16976 #[test]
16977 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
16978 let source = r#"#define MAKE(type) type *value
16979MAKE(int *);
16980typedef struct Item Item;
16981struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
16982void gen_op_movl(void *s, int first, int second) { }
16983const char *strZ(const char *value) { return value; }
16984int body(void *s) {
16985 MAKE(int *);
16986 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
16987 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
16988 execvp(strZ(value), UNCONSTIFY(char **, args));
16989}
16990STATIC EFI_STATUS Encode () { return 0; }
16991"#;
16992 let tree = parse_cpp(source);
16993 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
16994 let top_macro = tree
16995 .root_node()
16996 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
16997 .expect("top macro node");
16998 let body_macro_start = source
16999 .match_indices("MAKE(int *);")
17000 .nth(1)
17001 .expect("body macro")
17002 .0;
17003 let body_macro = tree
17004 .root_node()
17005 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
17006 .expect("body macro node");
17007 let function_call_start = source
17008 .find("gen_op_movl(s, offsetof(CPUX86State")
17009 .expect("function call");
17010 let function_call = tree
17011 .root_node()
17012 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
17013 .expect("function call node");
17014 let strz_start = source.find("strZ(value)").expect("nested function call");
17015 let strz = tree
17016 .root_node()
17017 .named_descendant_for_byte_range(strz_start, strz_start + 4)
17018 .expect("nested function call node");
17019 let binder_start = source.find("Encode").expect("binder");
17020 let binder = tree
17021 .root_node()
17022 .named_descendant_for_byte_range(binder_start, binder_start + 6)
17023 .expect("binder node");
17024
17025 assert!(recovered_c_function_declarator_invocation(top_macro));
17026 assert!(recovered_c_function_declarator_invocation(body_macro));
17027 assert!(recovered_c_function_declarator_invocation(function_call));
17028 assert!(recovered_c_function_declarator_invocation(strz));
17029 assert!(!recovered_c_function_declarator_invocation(binder));
17030 }
17031
17032 #[test]
17033 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
17034 let source = r#"typedef int krb5_context;
17035int helper(int first, int second) { return first + second; }
17036static krb5_context ctx;
17037int main(int argc, char **argv) {
17038 int ccinitial;
17039 const char *collection_name, *typename;
17040 typename = helper(ctx, ccinitial);
17041 return 0;
17042}
17043"#;
17044 let tree = parse_cpp(source);
17045 let ctx = tree
17046 .root_node()
17047 .descendant_for_byte_range(
17048 source.find("ctx, ccinitial").expect("ctx argument"),
17049 source.find("ctx, ccinitial").expect("ctx argument") + 3,
17050 )
17051 .expect("ctx node");
17052 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
17053 let ccinitial = tree
17054 .root_node()
17055 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
17056 .expect("sibling node");
17057 let typename = named_node_at(&tree, source, "typename = helper");
17058 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
17059
17060 assert_eq!(ctx.kind(), "identifier");
17061 assert!(recovered_c_parenthesized_declarator_reference(ctx));
17062 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
17063 assert!(!recovered_c_parenthesized_declarator_reference(typename));
17064 assert!(!recovered_c_parenthesized_declarator_reference(helper));
17065 }
17066
17067 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
17068 let mut parser = Parser::new();
17069 parser
17070 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17071 .expect("C++ grammar");
17072 let tree = parser.parse(source, None).expect("C++ fixture tree");
17073 let mut stack = vec![tree.root_node()];
17074 while let Some(node) = stack.pop() {
17075 if node.kind() == "enum_specifier" {
17076 return flattened_macro_namespace_components(node, source);
17077 }
17078 let mut cursor = node.walk();
17079 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
17080 stack.extend(children.into_iter().rev());
17081 }
17082 None
17083 }
17084
17085 #[test]
17086 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
17087 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
17088namespace detail
17089{
17090enum class value_t { null };
17091}
17092NLOHMANN_JSON_NAMESPACE_END
17093NLOHMANN_JSON_NAMESPACE_BEGIN
17094namespace next
17095{
17096struct next_type {};
17097}
17098NLOHMANN_JSON_NAMESPACE_END
17099"#;
17100 assert_eq!(
17101 first_enum_flattened_namespace(complete),
17102 Some(vec!["detail".to_string()])
17103 );
17104
17105 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
17106 assert_eq!(
17107 first_enum_flattened_namespace(&stale_end),
17108 Some(vec!["detail".to_string()]),
17109 "a stale end marker before the begin marker must not replace the intended namespace"
17110 );
17111
17112 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
17113namespace detail
17114{
17115enum class value_t { null };
17116}
17117struct next_type {};
17118"#;
17119 assert_eq!(first_enum_flattened_namespace(incomplete), None);
17120 }
17121}
17122
17123#[cfg(test)]
17139mod lookup_order_properties {
17140 use super::*;
17141 use proptest::prelude::*;
17142
17143 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
17147 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
17148 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
17152 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
17153 const KINDS: [CodeUnitType; 6] = [
17154 CodeUnitType::Class,
17155 CodeUnitType::Function,
17156 CodeUnitType::Field,
17157 CodeUnitType::Module,
17158 CodeUnitType::Macro,
17159 CodeUnitType::FileScope,
17160 ];
17161
17162 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
17165 enum ProbedOrder {
17166 Before,
17167 Tied,
17168 After,
17169 Contradictory,
17172 }
17173
17174 impl ProbedOrder {
17175 fn mirror(self) -> Self {
17176 match self {
17177 ProbedOrder::Before => ProbedOrder::After,
17178 ProbedOrder::After => ProbedOrder::Before,
17179 other => other,
17180 }
17181 }
17182
17183 fn signum(self) -> i8 {
17185 match self {
17186 ProbedOrder::Before => -1,
17187 ProbedOrder::Tied => 0,
17188 ProbedOrder::After => 1,
17189 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
17190 }
17191 }
17192 }
17193
17194 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
17202 if left == right {
17203 return ProbedOrder::Tied;
17206 }
17207 let mut forward = vec![left.clone(), right.clone()];
17208 sort_lookup_units(&mut forward);
17209 let mut backward = vec![right.clone(), left.clone()];
17210 sort_lookup_units(&mut backward);
17211 let left_first = backward[0] == *left;
17212 let right_first = forward[0] == *right;
17213 match (left_first, right_first) {
17214 (true, true) => ProbedOrder::Contradictory,
17215 (true, false) => ProbedOrder::Before,
17216 (false, true) => ProbedOrder::After,
17217 (false, false) => ProbedOrder::Tied,
17218 }
17219 }
17220
17221 fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
17224 let interner = segment_interner();
17225 unit.fq()
17226 .segments()
17227 .iter()
17228 .map(|&id| {
17229 let (text, kind) = interner.resolve(id);
17230 (kind.name(), text)
17231 })
17232 .collect()
17233 }
17234
17235 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
17236 (
17237 0..ROOT_NAMES.len(),
17238 0..REL_PATHS.len(),
17239 0..KINDS.len(),
17240 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
17241 0..3usize,
17242 0..SIGNATURES.len(),
17243 any::<bool>(),
17244 )
17245 .prop_map(
17246 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
17247 let source = ProjectFile::new(
17248 std::env::temp_dir().join(ROOT_NAMES[root]),
17249 REL_PATHS[rel_path],
17250 );
17251 let interner = segment_interner();
17252 let mut fq = FqName::new();
17253 for (atom, segment_kind) in &segments {
17254 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
17255 }
17256 let package_segment_count = package_prefix % fq.len();
17258 CodeUnit::from_fq(
17259 source,
17260 KINDS[kind],
17261 fq,
17262 package_segment_count,
17263 SIGNATURES[signature].map(str::to_string),
17264 synthetic,
17265 )
17266 },
17267 )
17268 }
17269
17270 proptest! {
17271 #![proptest_config(ProptestConfig::with_cases(256))]
17272
17273 #[test]
17276 fn lookup_order_is_reflexive_and_dual(
17277 left in code_unit_strategy(),
17278 right in code_unit_strategy(),
17279 ) {
17280 prop_assert_eq!(
17281 probe_order(&left, &left),
17282 ProbedOrder::Tied,
17283 "a unit must tie with itself: {:?}",
17284 left
17285 );
17286 let forward = probe_order(&left, &right);
17287 prop_assert_ne!(
17288 forward,
17289 ProbedOrder::Contradictory,
17290 "comparator put each of these strictly first: left={:?} right={:?}",
17291 left,
17292 right
17293 );
17294 prop_assert_eq!(
17295 probe_order(&right, &left),
17296 forward.mirror(),
17297 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
17298 left,
17299 right
17300 );
17301 }
17302
17303 #[test]
17305 fn lookup_order_is_transitive(
17306 a in code_unit_strategy(),
17307 b in code_unit_strategy(),
17308 c in code_unit_strategy(),
17309 ) {
17310 let ab = probe_order(&a, &b);
17311 let bc = probe_order(&b, &c);
17312 let ac = probe_order(&a, &c);
17313 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
17314 prop_assert_ne!(
17315 probed,
17316 ProbedOrder::Contradictory,
17317 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
17318 pair,
17319 a,
17320 b,
17321 c
17322 );
17323 }
17324 if ab.signum() <= 0 && bc.signum() <= 0 {
17325 prop_assert!(
17326 ac.signum() <= 0,
17327 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
17328 a={:?} b={:?} c={:?}",
17329 ab,
17330 bc,
17331 ac,
17332 a,
17333 b,
17334 c
17335 );
17336 }
17337 }
17338
17339 #[test]
17342 fn lookup_order_separates_distinct_identities(
17343 left in code_unit_strategy(),
17344 right in code_unit_strategy(),
17345 ) {
17346 if probe_order(&left, &right) == ProbedOrder::Tied {
17347 prop_assert_eq!(
17348 &left,
17349 &right,
17350 "distinct identities tied, so their order is whatever order they \
17351 arrived in: left_segments={:?} right_segments={:?}",
17352 fq_segments(&left),
17353 fq_segments(&right)
17354 );
17355 }
17356 }
17357
17358 #[test]
17361 fn lookup_sort_is_permutation_invariant(
17362 units in prop::collection::vec(code_unit_strategy(), 1..=8),
17363 ) {
17364 let mut sorted = units.clone();
17365 sort_lookup_units(&mut sorted);
17366 for rotation in 0..units.len() {
17367 for reversed in [false, true] {
17368 let mut permuted = units.clone();
17369 permuted.rotate_left(rotation);
17370 if reversed {
17371 permuted.reverse();
17372 }
17373 sort_lookup_units(&mut permuted);
17374 prop_assert_eq!(
17375 &permuted,
17376 &sorted,
17377 "sorting a permutation gave a different list \
17378 (rotation={}, reversed={}): input={:?}",
17379 rotation,
17380 reversed,
17381 units
17382 );
17383 }
17384 }
17385 }
17386 }
17387}