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,
13};
14use crate::graph::CppGraphSource;
15use crate::graph::extractor::ScanCtx;
16use crate::graph::syntax::object_macro_replacement_type_references;
17use crate::graph_support::CppSource;
18use crate::imports::{
19 IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
20};
21use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
22use brokk_bifrost_core::analyzer::model::{
23 CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
24 CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
25};
26use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
27use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
28use brokk_bifrost_core::analyzer::query_token::QueryToken;
29use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, node_for_exact_range};
30use brokk_bifrost_core::analyzer::usages::common::same_node;
31use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
32use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
33use brokk_bifrost_core::cancellation::CancellationToken;
34use brokk_bifrost_core::hash::{HashMap, HashSet};
35use std::borrow::Cow;
36#[cfg(any(test, feature = "test-support"))]
37use std::cell::Cell;
38use std::cell::OnceCell;
39use std::cmp::Ordering as CmpOrdering;
40use std::collections::BTreeSet;
41use std::hash::Hash;
42use std::sync::atomic::{AtomicUsize, Ordering};
43use std::sync::{Arc, Mutex, OnceLock, RwLock};
44use std::thread::ThreadId;
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>>;
637pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
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
657#[derive(Default)]
658pub struct MacroEnvironmentCursor {
659 frontier: usize,
660 environment: Arc<MacroEnvironment>,
661}
662
663impl MacroEnvironment {
664 fn binding(&self, name: &str) -> Option<&MacroBinding> {
665 self.bindings.get(name)
666 }
667
668 fn may_bind(&self, name: &str) -> bool {
669 self.bindings.contains_key(name) || self.unknown_names
670 }
671
672 fn insert(&mut self, name: String, binding: MacroBinding) {
673 self.known_undefined_names.remove(&name);
674 self.bindings.insert(name, binding);
675 }
676
677 fn remove(&mut self, name: &str) {
678 self.bindings.remove(name);
679 self.known_undefined_names.insert(name.to_string());
680 }
681
682 fn remove_known_undefined(&mut self, name: &str) {
683 self.known_undefined_names.remove(name);
684 }
685
686 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
687 for binding in self.bindings.values_mut() {
688 *binding = MacroBinding::uncertain_from(binding, source, byte);
689 }
690 self.known_undefined_names.clear();
691 self.build_proven_defines.clear();
696 self.unknown_names = true;
697 }
698
699 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
700 guards.iter().all(|guard| self.guard_may_hold(guard))
701 }
702
703 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
704 let Some(expression) = guard.as_boolean_expression() else {
705 return true;
706 };
707 self.boolean_guard_may_hold(&expression)
708 }
709
710 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
711 match expression {
712 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
713 BooleanGuardExpression::Undefined(name) => {
714 self.bindings
715 .get(name)
716 .is_none_or(|binding| !binding.is_exact())
717 && (!self.build_proven_defines.contains(name)
718 || self.known_undefined_names.contains(name))
719 }
720 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
721 BooleanGuardExpression::Opaque(_)
722 | BooleanGuardExpression::NegatedOpaque(_)
723 | BooleanGuardExpression::Constant(true) => true,
724 BooleanGuardExpression::Constant(false) => false,
725 BooleanGuardExpression::All(expressions) => expressions
726 .iter()
727 .all(|expression| self.boolean_guard_may_hold(expression)),
728 BooleanGuardExpression::Any(expressions) => expressions
729 .iter()
730 .any(|expression| self.boolean_guard_may_hold(expression)),
731 }
732 }
733}
734
735#[derive(Clone)]
736pub enum EffectiveUsingTarget {
737 Ordinary {
738 name: String,
739 target_components: Vec<String>,
740 global: bool,
741 },
742 Namespace {
743 namespace_components: Vec<String>,
744 global: bool,
745 },
746}
747
748#[derive(Clone)]
749pub struct OrdinaryTypeImport {
750 pub target: EffectiveUsingTarget,
751 pub source: ProjectFile,
752 pub declaration_byte: usize,
753 pub scope_start: usize,
754 pub scope_end: usize,
755 pub scope_depth: usize,
756 pub block_scope: bool,
757 pub lexical_depth: usize,
758 pub declaration_namespace: Vec<String>,
759 pub namespace_scope: Option<Vec<String>>,
760 pub resolved_target_components: Option<Vec<String>>,
761 pub required_guards: HashSet<PreprocessorGuard>,
762}
763
764#[derive(Clone)]
765pub struct ConditionalIncludeProjection {
766 pub activation_byte: usize,
767 pub required_guards: HashSet<PreprocessorGuard>,
768}
769
770#[derive(Default)]
771pub struct SourceUsingIndex {
772 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
773 pub directives: Vec<OrdinaryTypeImport>,
774}
775
776#[derive(Default)]
777pub struct ProjectUsingIndex {
778 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
779 pub directives: Vec<OrdinaryTypeImport>,
780}
781
782type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
783
784pub struct EffectiveUsingIndex {
785 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
786}
787
788impl EffectiveUsingIndex {
789 fn new(_root: ProjectFile) -> Self {
790 Self {
791 projected_by_name: Mutex::new(HashMap::default()),
792 }
793 }
794
795 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
796 self.projected_by_name
797 .lock()
798 .expect("C++ effective-using projection cache poisoned")
799 .entry(name.to_string())
800 .or_default()
801 .clone()
802 }
803}
804
805pub enum OrdinaryTypeImportResolution {
806 Resolved {
807 target: CodeUnit,
808 target_components: Vec<String>,
809 lexical_depth: usize,
810 is_direct: bool,
811 },
812 Ambiguous {
813 lexical_depth: usize,
814 },
815 Missing,
816}
817
818type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
819type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
820type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
821type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
822type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
823type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
824type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
825
826struct ExtractedComparable {
831 shapes: Vec<CppComparableSlot>,
832 suffix: String,
833}
834
835const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
839
840pub struct VisibilityIndex<'a> {
851 cpp: &'a dyn CppSource,
852 token: QueryToken<'a>,
857 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
858 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
859 global_field_internal_linkage: HashMap<CodeUnit, bool>,
860 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
861 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
862 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
863 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
864 project_using_index: OnceLock<ProjectUsingIndex>,
865 callable_reference_specs:
866 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
867 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
868 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
869 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
870 #[cfg(any(test, feature = "test-support"))]
871 conditional_include_projection_index_build_count: AtomicUsize,
872 #[cfg(any(test, feature = "test-support"))]
873 conditional_include_projection_state_count: AtomicUsize,
874 #[cfg(any(test, feature = "test-support"))]
875 conditional_include_target_state_count: AtomicUsize,
876 #[cfg(any(test, feature = "test-support"))]
877 include_activation_build_count: AtomicUsize,
878 #[cfg(any(test, feature = "test-support"))]
879 using_donor_activation_count: AtomicUsize,
880 #[cfg(any(test, feature = "test-support"))]
881 using_namespace_lookup_count: AtomicUsize,
882 #[cfg(any(test, feature = "test-support"))]
883 using_name_candidate_inspection_count: AtomicUsize,
884 #[cfg(any(test, feature = "test-support"))]
885 callable_reference_spec_build_count: AtomicUsize,
886 #[cfg(any(test, feature = "test-support"))]
887 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
888 #[cfg(any(test, feature = "test-support"))]
889 visible_parser_alias_name_set_build_count: AtomicUsize,
890 parser_alias_fallback_calls: AtomicUsize,
891 parser_alias_fallback_files: AtomicUsize,
892 parser_alias_source_parses: AtomicUsize,
893 parser_alias_fallback_elapsed_micros: AtomicUsize,
894 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
895 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
896 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
897 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
898 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
899 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
900 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
901 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
902 pub macro_environment_cursors:
908 Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
909 macro_replacements: Mutex<MacroReplacementCache>,
910 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
911 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
912 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
913 #[cfg(any(test, feature = "test-support"))]
914 pub macro_replacement_parse_count: AtomicUsize,
915 #[cfg(any(test, feature = "test-support"))]
916 pub macro_event_application_count: AtomicUsize,
917 #[cfg(any(test, feature = "test-support"))]
918 pub macro_environment_copy_count: AtomicUsize,
919 #[cfg(any(test, feature = "test-support"))]
920 pub macro_environment_request_count: AtomicUsize,
921 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
922 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
923 #[cfg(any(test, feature = "test-support"))]
924 qualified_candidate_inspections: AtomicUsize,
925 #[cfg(any(test, feature = "test-support"))]
926 target_preserving_type_resolution_count: AtomicUsize,
927}
928
929impl Drop for VisibilityIndex<'_> {
930 fn drop(&mut self) {
931 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
932 return;
933 }
934 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
935 if calls == 0 {
936 return;
937 }
938 eprintln!(
939 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
940 calls,
941 self.parser_alias_fallback_files.load(Ordering::Relaxed),
942 self.parser_alias_source_parses.load(Ordering::Relaxed),
943 self.parser_alias_fallback_elapsed_micros
944 .load(Ordering::Relaxed)
945 / 1_000,
946 );
947 }
948}
949
950#[derive(Clone, Debug, PartialEq, Eq, Hash)]
951pub enum PreprocessorGuard {
952 Defined(String),
953 Undefined(String),
954 Boolean(BooleanGuardExpression),
955 Expression(String),
956 NegatedExpression(String),
957 Constant(bool),
958}
959
960#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
961pub enum BooleanGuardExpression {
962 Defined(String),
963 Undefined(String),
964 Truthy(String),
965 Falsy(String),
966 Opaque(String),
967 NegatedOpaque(String),
968 All(Vec<BooleanGuardExpression>),
969 Any(Vec<BooleanGuardExpression>),
970 Constant(bool),
971}
972
973impl BooleanGuardExpression {
974 fn negated(&self) -> Self {
975 match self {
976 Self::Defined(name) => Self::Undefined(name.clone()),
977 Self::Undefined(name) => Self::Defined(name.clone()),
978 Self::Truthy(name) => Self::Falsy(name.clone()),
979 Self::Falsy(name) => Self::Truthy(name.clone()),
980 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
981 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
982 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
983 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
984 Self::Constant(value) => Self::Constant(!value),
985 }
986 }
987
988 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
989 Self::normalized(expressions, true)
990 }
991
992 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
993 Self::normalized(expressions, false)
994 }
995
996 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
997 let mut normalized = Vec::new();
998 for expression in expressions {
999 match expression {
1000 Self::All(nested) if conjunction => normalized.extend(nested),
1001 Self::Any(nested) if !conjunction => normalized.extend(nested),
1002 Self::Constant(value) if value == conjunction => {}
1003 Self::Constant(value) => return Self::Constant(value),
1004 expression => normalized.push(expression),
1005 }
1006 }
1007 normalized.sort_unstable();
1008 normalized.dedup();
1009 match normalized.len() {
1010 0 => Self::Constant(conjunction),
1011 1 => normalized.pop().expect("one Boolean guard expression"),
1012 _ if conjunction => Self::All(normalized),
1013 _ => Self::Any(normalized),
1014 }
1015 }
1016
1017 fn implies(&self, required: &Self) -> bool {
1018 if self == required
1019 || matches!(self, Self::Constant(false))
1020 || matches!(required, Self::Constant(true))
1021 {
1022 return true;
1023 }
1024 if matches!(
1025 (self, required),
1026 (Self::Truthy(active), Self::Defined(required))
1027 | (Self::Undefined(active), Self::Falsy(required))
1028 if active == required
1029 ) {
1030 return true;
1031 }
1032 match self {
1033 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1034 Self::All(active) => match required {
1035 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1036 _ => active.iter().any(|expression| expression.implies(required)),
1037 },
1038 _ => match required {
1039 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1040 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1041 _ => false,
1042 },
1043 }
1044 }
1045
1046 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1047 match self {
1048 Self::Defined(name)
1049 | Self::Undefined(name)
1050 | Self::Truthy(name)
1051 | Self::Falsy(name) => name == macro_name,
1052 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1055 Self::All(expressions) | Self::Any(expressions) => expressions
1056 .iter()
1057 .any(|expression| expression.may_depend_on_macro(macro_name)),
1058 Self::Constant(_) => false,
1059 }
1060 }
1061
1062 pub fn heap_size(&self) -> usize {
1063 match self {
1064 Self::Defined(value)
1065 | Self::Undefined(value)
1066 | Self::Truthy(value)
1067 | Self::Falsy(value)
1068 | Self::Opaque(value)
1069 | Self::NegatedOpaque(value) => value.len(),
1070 Self::All(expressions) | Self::Any(expressions) => {
1071 expressions
1072 .iter()
1073 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1074 size.saturating_add(std::mem::size_of::<Self>())
1075 .saturating_add(expression.heap_size())
1076 })
1077 }
1078 Self::Constant(_) => 0,
1079 }
1080 }
1081}
1082
1083impl PreprocessorGuard {
1084 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1085 match self {
1086 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1087 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1088 Self::Boolean(expression) => Some(expression.clone()),
1089 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1090 Self::Expression(_) | Self::NegatedExpression(_) => None,
1091 }
1092 }
1093
1094 fn negated(&self) -> Self {
1095 match self {
1096 Self::Defined(name) => Self::Undefined(name.clone()),
1097 Self::Undefined(name) => Self::Defined(name.clone()),
1098 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1099 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1100 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1101 Self::Constant(value) => Self::Constant(!value),
1102 }
1103 }
1104
1105 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1106 match self {
1107 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1108 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1109 Self::Expression(_) | Self::NegatedExpression(_) => true,
1112 Self::Constant(_) => false,
1113 }
1114 }
1115}
1116
1117#[derive(Clone, PartialEq, Eq)]
1118pub enum MacroDefinition {
1119 Object {
1120 replacement: String,
1121 },
1122 Function {
1123 parameters: Vec<String>,
1124 replacement: String,
1125 },
1126 Unsupported,
1127}
1128
1129#[derive(Clone, Debug, PartialEq, Eq)]
1130pub enum MacroIncludeProtection {
1131 MacroGuard(String),
1132 PragmaOnce,
1133 None,
1134}
1135
1136enum ParsedMacroReplacement {
1137 Parsed { source: String, tree: Tree },
1138 Unsupported,
1139}
1140
1141const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1145
1146pub struct ParsedReplacementBody {
1155 pub source: String,
1156 pub tree: Tree,
1157 pub body_offset: usize,
1158 pub parameters: Vec<String>,
1159}
1160
1161impl ParsedReplacementBody {
1162 pub fn statements(&self) -> Option<Node<'_>> {
1164 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1165 .child_by_field_name("body")
1166 }
1167
1168 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1174 debug_assert!(node.start_byte() >= self.body_offset);
1175 let start = replacement_start + (node.start_byte() - self.body_offset);
1176 start..start + (node.end_byte() - node.start_byte())
1177 }
1178
1179 fn expands_variadic_arguments(&self) -> bool {
1186 let mut stack = vec![self.tree.root_node()];
1187 while let Some(node) = stack.pop() {
1188 if matches!(
1189 node.kind(),
1190 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1191 ) && node_text(node, &self.source) == "__VA_ARGS__"
1192 {
1193 return true;
1194 }
1195 for index in (0..node.named_child_count()).rev() {
1196 if let Some(child) = node.named_child(index) {
1197 stack.push(child);
1198 }
1199 }
1200 }
1201 false
1202 }
1203}
1204
1205fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1206 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1207 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1208 if compact.starts_with("0x") || compact.starts_with("0X") {
1209 (16, 2, |ch| ch.is_ascii_hexdigit())
1210 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1211 (2, 2, |ch| matches!(ch, '0' | '1'))
1212 } else if compact.starts_with('0') && compact.len() > 1 {
1213 (8, 0, |ch| matches!(ch, '0'..='7'))
1214 } else {
1215 (10, 0, |ch| ch.is_ascii_digit())
1216 };
1217 let digit_len = compact[digits_start..]
1218 .chars()
1219 .take_while(|ch| digit_matches(*ch))
1220 .map(char::len_utf8)
1221 .sum::<usize>();
1222 if digit_len == 0 {
1223 return None;
1224 }
1225 let digits_end = digits_start + digit_len;
1226 if !compact[digits_end..]
1227 .chars()
1228 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1229 {
1230 return None;
1231 }
1232 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1233}
1234
1235#[derive(Clone)]
1236enum MacroLocalBindingTypeTemplate {
1237 Parameter(usize),
1238 Fixed(String),
1239}
1240
1241#[derive(Clone)]
1242struct MacroLocalBindingTemplate {
1243 name: String,
1244 declared_type: MacroLocalBindingTypeTemplate,
1245 pointer_depth: i32,
1246}
1247
1248pub struct MacroLocalBinding<'tree> {
1254 pub name: String,
1255 pub type_name: String,
1256 pub type_node: Option<Node<'tree>>,
1257 pub pointer_depth: i32,
1258}
1259
1260fn recognized_c_macro_declarator_binding<'tree>(
1265 statement: Node<'tree>,
1266 source: &str,
1267) -> Option<MacroLocalBinding<'tree>> {
1268 let assignment = match statement.kind() {
1269 "assignment_expression" => statement,
1270 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1271 _ => return None,
1272 };
1273 if assignment.kind() != "assignment_expression" {
1274 return None;
1275 }
1276 let call = assignment.child_by_field_name("left")?;
1277 if call.kind() != "call_expression" {
1278 return None;
1279 }
1280 let function = call.child_by_field_name("function")?;
1281 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1282 return None;
1283 }
1284 let arguments = call.child_by_field_name("arguments")?;
1285 let mut actuals = argument_children(arguments);
1286 let type_node = actuals.next()?;
1287 if actuals.next().is_some()
1288 || !matches!(
1289 type_node.kind(),
1290 "identifier"
1291 | "type_identifier"
1292 | "qualified_identifier"
1293 | "scoped_type_identifier"
1294 | "template_type"
1295 )
1296 {
1297 return None;
1298 }
1299 let name_node = (0..assignment.named_child_count())
1300 .filter_map(|index| assignment.named_child(index))
1301 .filter(|child| child.kind() == "ERROR")
1302 .filter_map(|error| {
1303 (error.named_child_count() == 1)
1304 .then(|| error.named_child(0))
1305 .flatten()
1306 })
1307 .find(|node| node.kind() == "identifier")?;
1308 let name = node_text(name_node, source).trim();
1309 let type_name = node_text(type_node, source).trim();
1310 if name.is_empty() || type_name.is_empty() {
1311 return None;
1312 }
1313 Some(MacroLocalBinding {
1314 name: name.to_string(),
1315 type_name: type_name.to_string(),
1316 type_node: Some(type_node),
1317 pointer_depth: 1,
1318 })
1319}
1320
1321#[derive(Clone, PartialEq, Eq)]
1322pub struct MacroBinding {
1323 source: ProjectFile,
1324 declaration_byte: usize,
1325 definition: MacroDefinition,
1326 exact: bool,
1327}
1328
1329impl MacroBinding {
1330 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1331 Self {
1332 source: source.clone(),
1333 declaration_byte,
1334 definition: MacroDefinition::Unsupported,
1335 exact: false,
1336 }
1337 }
1338
1339 fn is_exact(&self) -> bool {
1340 self.exact
1341 }
1342
1343 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1344 Self {
1345 source: source.clone(),
1346 declaration_byte,
1347 definition: current.definition.clone(),
1348 exact: false,
1349 }
1350 }
1351}
1352
1353#[derive(Clone)]
1354pub enum MacroEvent {
1355 Define {
1356 name: String,
1357 binding: MacroBinding,
1358 byte: usize,
1359 conditional: bool,
1360 },
1361 Undef {
1362 name: String,
1363 byte: usize,
1364 conditional: bool,
1365 },
1366 Include {
1367 targets: Vec<ProjectFile>,
1368 byte: usize,
1369 conditional: bool,
1370 },
1371 Invalidate {
1372 byte: usize,
1373 },
1374}
1375
1376impl MacroEvent {
1377 pub fn byte(&self) -> usize {
1378 match self {
1379 Self::Define { byte, .. }
1380 | Self::Undef { byte, .. }
1381 | Self::Include { byte, .. }
1382 | Self::Invalidate { byte } => *byte,
1383 }
1384 }
1385}
1386
1387#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1388pub enum CallArityEvidence {
1389 Exact(usize),
1390 Unknown,
1391}
1392
1393impl CallArityEvidence {
1394 pub fn exact(self) -> Option<usize> {
1395 match self {
1396 Self::Exact(arity) => Some(arity),
1397 Self::Unknown => None,
1398 }
1399 }
1400
1401 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1402 self.exact().map(|arity| expected.accepts(arity))
1403 }
1404}
1405
1406#[derive(Clone)]
1407struct DeclaredFieldTypeFact {
1408 type_text: String,
1409 indirection: i32,
1410 template_arguments: Option<Vec<CppTemplateExpression>>,
1411}
1412
1413#[derive(Clone, PartialEq, Eq)]
1414enum StructuredAliasTarget {
1415 Builtin,
1416 Named {
1417 components: Vec<String>,
1418 global: bool,
1419 arguments: Option<Vec<CppTemplateExpression>>,
1420 },
1421}
1422
1423struct CppAlias {
1424 name: String,
1425 target: String,
1426 namespace: Option<String>,
1427}
1428
1429type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1430
1431#[derive(Debug, Clone, PartialEq, Eq)]
1435pub enum CppTemplateResolutionError {
1436 AliasCycle { alias: CodeUnit },
1438 ArgumentBinding,
1440 Substitution,
1442 PrimarySelection,
1445 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1448}
1449
1450fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1453 let mut distinct: Vec<CodeUnit> = Vec::new();
1454 for unit in units {
1455 if !distinct
1456 .iter()
1457 .any(|existing| same_visible_symbol(existing, unit))
1458 {
1459 distinct.push(unit.clone());
1460 }
1461 }
1462 distinct
1463}
1464
1465impl<'a> VisibilityIndex<'a> {
1466 pub fn cpp(&self) -> &'a dyn CppSource {
1467 self.cpp
1468 }
1469
1470 pub fn token(&self) -> QueryToken<'a> {
1472 self.token
1473 }
1474
1475 #[cfg(any(test, feature = "test-support"))]
1483 pub fn from_visible_files_for_test(
1484 cpp: &'a dyn CppSource,
1485 token: QueryToken<'a>,
1486 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1487 ) -> Self {
1488 let visible_source_files_by_root = visible_by_file
1489 .iter()
1490 .map(|(file, visible)| {
1491 (
1492 file.clone(),
1493 visible
1494 .iter()
1495 .map(|unit| unit.source().clone())
1496 .chain(std::iter::once(file.clone()))
1497 .collect(),
1498 )
1499 })
1500 .collect();
1501 let mut global_field_internal_linkage = HashMap::default();
1502 Self {
1503 cpp,
1504 token,
1505 visible_by_identifier: build_visible_identifier_index(
1506 &CppGraphSource::from_source(cpp, token),
1507 &visible_by_file,
1508 &visible_source_files_by_root,
1509 &mut global_field_internal_linkage,
1510 ),
1511 global_field_internal_linkage,
1512 visible_by_file,
1513 visible_source_files_by_root,
1514 alias_cells: Mutex::new(HashMap::default()),
1515 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1516 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1517 project_using_index: OnceLock::new(),
1518 callable_reference_specs: Mutex::new(HashMap::default()),
1519 include_activation_cells: Mutex::new(HashMap::default()),
1520 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1521 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1522 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1523 conditional_include_projection_state_count: AtomicUsize::new(0),
1524 conditional_include_target_state_count: AtomicUsize::new(0),
1525 include_activation_build_count: AtomicUsize::new(0),
1526 using_donor_activation_count: AtomicUsize::new(0),
1527 using_namespace_lookup_count: AtomicUsize::new(0),
1528 using_name_candidate_inspection_count: AtomicUsize::new(0),
1529 callable_reference_spec_build_count: AtomicUsize::new(0),
1530 alias_source_parse_counts: Mutex::new(HashMap::default()),
1531 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1532 parser_alias_fallback_calls: AtomicUsize::new(0),
1533 parser_alias_fallback_files: AtomicUsize::new(0),
1534 parser_alias_source_parses: AtomicUsize::new(0),
1535 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1536 field_type_facts: Mutex::new(HashMap::default()),
1537 structured_alias_targets: Mutex::new(HashMap::default()),
1538 callable_comparables: Mutex::new(HashMap::default()),
1539 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1540 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1541 precise_parent_cache: Mutex::new(HashMap::default()),
1542 macro_event_cells: Mutex::new(HashMap::default()),
1543 macro_include_protection_cells: Mutex::new(HashMap::default()),
1544 macro_environment_cursors: Mutex::new(HashMap::default()),
1545 macro_replacements: Mutex::new(HashMap::default()),
1546 macro_local_binding_templates: Mutex::new(HashMap::default()),
1547 macro_replacement_bodies: Mutex::new(HashMap::default()),
1548 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1549 macro_replacement_parse_count: AtomicUsize::new(0),
1550 macro_event_application_count: AtomicUsize::new(0),
1551 macro_environment_copy_count: AtomicUsize::new(0),
1552 macro_environment_request_count: AtomicUsize::new(0),
1553 cpp_template_metadata: HashMap::default(),
1554 cpp_template_families: HashMap::default(),
1555 qualified_candidate_inspections: AtomicUsize::new(0),
1556 target_preserving_type_resolution_count: AtomicUsize::new(0),
1557 }
1558 }
1559
1560 fn cpp_source(&self) -> CppGraphSource<'a> {
1567 CppGraphSource::from_source(self.cpp, self.token)
1568 }
1569
1570 pub fn build(
1571 cpp: &'a dyn CppSource,
1572 token: QueryToken<'a>,
1573 analyzer: &CppGraphSource<'_>,
1574 roots: &HashSet<ProjectFile>,
1575 ) -> Self {
1576 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1577 }
1578
1579 pub fn build_with_cancellation(
1580 cpp: &'a dyn CppSource,
1581 token: QueryToken<'a>,
1582 analyzer: &CppGraphSource<'_>,
1583 roots: &HashSet<ProjectFile>,
1584 cancellation: Option<&CancellationToken>,
1585 ) -> Self {
1586 let visibility_started = Instant::now();
1587 let include_targets = cpp.include_target_index();
1588 let includes_started = Instant::now();
1589 let mut include_graph = IncludeGraph::default();
1590 for root in roots {
1591 include_graph.extend_with(root, cancellation, &mut |file| {
1592 cpp_include_paths(&cpp.visibility_import_statements(token, file))
1593 .into_iter()
1594 .flat_map(|include| {
1595 resolve_include_targets_with_index(file, &include, include_targets)
1596 })
1597 .collect()
1598 });
1599 }
1600 let include_elapsed = includes_started.elapsed();
1601 let include_file_count = include_graph.files().count();
1602 let visible_source_files_by_root = roots
1603 .iter()
1604 .map(|root| {
1605 (
1606 root.clone(),
1607 include_graph.reachable_files(root, cancellation),
1608 )
1609 })
1610 .collect::<HashMap<_, _>>();
1611 let mut visibility_stats = BoundedVisibilityStats::default();
1612 let mut visible_by_file = build_bounded_visible_declarations(
1613 cpp,
1614 token,
1615 analyzer,
1616 roots,
1617 &visible_source_files_by_root,
1618 cancellation,
1619 &mut visibility_stats,
1620 );
1621 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
1622 eprintln!(
1623 "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={}",
1624 visibility_started.elapsed().as_millis(),
1625 include_elapsed.as_millis(),
1626 include_file_count,
1627 visibility_stats.rounds,
1628 visibility_stats.root_names,
1629 visibility_stats.identifier_lookups,
1630 visibility_stats.candidate_units,
1631 visibility_stats.candidate_sources,
1632 visibility_stats.declaration_reads,
1633 visibility_stats.declaration_units,
1634 visibility_stats.selected_units,
1635 visibility_stats.dependency_ast_nodes,
1636 visibility_stats.dependency_names,
1637 visibility_stats.lookup_elapsed.as_millis(),
1638 visibility_stats.declaration_elapsed.as_millis(),
1639 visibility_stats.dependency_ast_elapsed.as_millis(),
1640 );
1641 }
1642 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
1643 let finalize_started = Instant::now();
1644 if report_stats {
1645 eprintln!(
1646 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
1647 visible_by_file.len(),
1648 visible_by_file.values().map(HashSet::len).sum::<usize>(),
1649 );
1650 }
1651 let owner_started = Instant::now();
1652 if report_stats {
1653 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
1654 }
1655 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1656 if report_stats {
1657 eprintln!(
1658 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
1659 owner_stats.unseen_owners,
1660 owner_stats.definition_lookups,
1661 owner_stats.admitted,
1662 owner_started.elapsed().as_millis(),
1663 );
1664 }
1665 let mut global_field_internal_linkage = HashMap::default();
1666 let identifier_started = Instant::now();
1667 if report_stats {
1668 eprintln!(
1669 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
1670 );
1671 }
1672 let visible_by_identifier = build_visible_identifier_index(
1673 analyzer,
1674 &visible_by_file,
1675 &visible_source_files_by_root,
1676 &mut global_field_internal_linkage,
1677 );
1678 if report_stats {
1679 eprintln!(
1680 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
1681 visible_by_identifier.len(),
1682 visible_by_identifier
1683 .values()
1684 .map(HashMap::len)
1685 .sum::<usize>(),
1686 visible_by_identifier
1687 .values()
1688 .flat_map(HashMap::values)
1689 .map(Vec::len)
1690 .sum::<usize>(),
1691 identifier_started.elapsed().as_millis(),
1692 );
1693 }
1694 let mut cpp_template_metadata = HashMap::default();
1695 let metadata_started = Instant::now();
1696 let mut template_classes = 0usize;
1697 if report_stats {
1698 eprintln!(
1699 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
1700 );
1701 }
1702 for unit in visible_by_file
1703 .values()
1704 .flatten()
1705 .filter(|unit| unit.is_class())
1706 {
1707 template_classes += 1;
1708 if cpp_template_metadata.contains_key(unit) {
1709 continue;
1710 }
1711 if let Some(metadata) = cpp.template_metadata(unit) {
1712 cpp_template_metadata.insert(unit.clone(), metadata);
1713 }
1714 }
1715 if report_stats {
1716 eprintln!(
1717 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
1718 template_classes,
1719 cpp_template_metadata.len(),
1720 metadata_started.elapsed().as_millis(),
1721 );
1722 }
1723 let families_started = Instant::now();
1724 if report_stats {
1725 eprintln!(
1726 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
1727 );
1728 }
1729 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1730 for (unit, metadata) in &cpp_template_metadata {
1731 cpp_template_families
1732 .entry(metadata.primary_fq_name.clone())
1733 .or_default()
1734 .push(unit.clone());
1735 }
1736 for family in cpp_template_families.values_mut() {
1745 sort_lookup_units(family);
1746 }
1747 if report_stats {
1748 eprintln!(
1749 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
1750 cpp_template_families.len(),
1751 cpp_template_families.values().map(Vec::len).sum::<usize>(),
1752 families_started.elapsed().as_millis(),
1753 );
1754 eprintln!(
1755 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
1756 visible_by_file.len(),
1757 visible_by_file.values().map(HashSet::len).sum::<usize>(),
1758 finalize_started.elapsed().as_millis(),
1759 visibility_started.elapsed().as_millis(),
1760 );
1761 }
1762 Self {
1763 cpp,
1764 token,
1765 visible_by_file,
1766 visible_by_identifier,
1767 global_field_internal_linkage,
1768 visible_source_files_by_root,
1769 alias_cells: Mutex::new(HashMap::default()),
1770 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1771 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1772 project_using_index: OnceLock::new(),
1773 callable_reference_specs: Mutex::new(HashMap::default()),
1774 include_activation_cells: Mutex::new(HashMap::default()),
1775 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1776 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1777 #[cfg(any(test, feature = "test-support"))]
1778 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1779 #[cfg(any(test, feature = "test-support"))]
1780 conditional_include_projection_state_count: AtomicUsize::new(0),
1781 #[cfg(any(test, feature = "test-support"))]
1782 conditional_include_target_state_count: AtomicUsize::new(0),
1783 #[cfg(any(test, feature = "test-support"))]
1784 include_activation_build_count: AtomicUsize::new(0),
1785 #[cfg(any(test, feature = "test-support"))]
1786 using_donor_activation_count: AtomicUsize::new(0),
1787 #[cfg(any(test, feature = "test-support"))]
1788 using_namespace_lookup_count: AtomicUsize::new(0),
1789 #[cfg(any(test, feature = "test-support"))]
1790 using_name_candidate_inspection_count: AtomicUsize::new(0),
1791 #[cfg(any(test, feature = "test-support"))]
1792 callable_reference_spec_build_count: AtomicUsize::new(0),
1793 #[cfg(any(test, feature = "test-support"))]
1794 alias_source_parse_counts: Mutex::new(HashMap::default()),
1795 #[cfg(any(test, feature = "test-support"))]
1796 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1797 parser_alias_fallback_calls: AtomicUsize::new(0),
1798 parser_alias_fallback_files: AtomicUsize::new(0),
1799 parser_alias_source_parses: AtomicUsize::new(0),
1800 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1801 field_type_facts: Mutex::new(HashMap::default()),
1802 structured_alias_targets: Mutex::new(HashMap::default()),
1803 callable_comparables: Mutex::new(HashMap::default()),
1804 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1805 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1806 precise_parent_cache: Mutex::new(HashMap::default()),
1807 macro_event_cells: Mutex::new(HashMap::default()),
1808 macro_include_protection_cells: Mutex::new(HashMap::default()),
1809 macro_environment_cursors: Mutex::new(HashMap::default()),
1810 macro_replacements: Mutex::new(HashMap::default()),
1811 macro_local_binding_templates: Mutex::new(HashMap::default()),
1812 macro_replacement_bodies: Mutex::new(HashMap::default()),
1813 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1814 #[cfg(any(test, feature = "test-support"))]
1815 macro_replacement_parse_count: AtomicUsize::new(0),
1816 #[cfg(any(test, feature = "test-support"))]
1817 macro_event_application_count: AtomicUsize::new(0),
1818 #[cfg(any(test, feature = "test-support"))]
1819 macro_environment_copy_count: AtomicUsize::new(0),
1820 #[cfg(any(test, feature = "test-support"))]
1821 macro_environment_request_count: AtomicUsize::new(0),
1822 cpp_template_metadata,
1823 cpp_template_families,
1824 #[cfg(any(test, feature = "test-support"))]
1825 qualified_candidate_inspections: AtomicUsize::new(0),
1826 #[cfg(any(test, feature = "test-support"))]
1827 target_preserving_type_resolution_count: AtomicUsize::new(0),
1828 }
1829 }
1830
1831 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1832 if file == target.source() {
1833 return true;
1834 }
1835 if self.global_field_has_internal_linkage(target) {
1836 return self
1837 .visible_source_files_by_root
1838 .get(file)
1839 .is_some_and(|sources| sources.contains(target.source()));
1840 }
1841 self.visible_by_file
1842 .get(file)
1843 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1844 }
1845
1846 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1847 self.global_field_internal_linkage
1848 .get(unit)
1849 .copied()
1850 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1851 }
1852
1853 pub fn call_arity_evidence(
1854 &self,
1855 file: &ProjectFile,
1856 call: Node<'_>,
1857 source: &str,
1858 ) -> CallArityEvidence {
1859 self.call_arity_evidence_at(file, call, source, call.start_byte())
1860 }
1861
1862 pub fn call_arity_evidence_at(
1870 &self,
1871 file: &ProjectFile,
1872 call: Node<'_>,
1873 source: &str,
1874 environment_byte: usize,
1875 ) -> CallArityEvidence {
1876 let Some(arguments) = call
1877 .child_by_field_name("arguments")
1878 .or_else(|| call.child_by_field_name("parameters"))
1879 .or_else(|| call.child_by_field_name("value"))
1880 .or_else(|| first_named_child_of_kind(call, "argument_list"))
1881 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1882 else {
1883 return CallArityEvidence::Exact(0);
1884 };
1885 let recovered_c_keyword_arguments =
1886 recovered_c_keyword_argument_count(file, call, arguments, source);
1887 let arguments = argument_children(arguments).collect::<Vec<_>>();
1888 if arguments
1889 .iter()
1890 .all(|argument| !argument_shape_may_change_arity(*argument))
1891 {
1892 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1893 }
1894 let environment = self.macro_environment(file, environment_byte);
1895 let mut stack = Vec::new();
1896 let mut total = recovered_c_keyword_arguments;
1897 for argument in arguments {
1898 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1899 return CallArityEvidence::Unknown;
1900 }
1901 let CallArityEvidence::Exact(spread) =
1902 self.argument_arity_evidence(argument, source, &environment, &mut stack)
1903 else {
1904 return CallArityEvidence::Unknown;
1905 };
1906 total += spread;
1907 }
1908 CallArityEvidence::Exact(total)
1909 }
1910
1911 fn argument_arity_evidence(
1912 &self,
1913 argument: Node<'_>,
1914 source: &str,
1915 environment: &MacroEnvironment,
1916 stack: &mut Vec<(ProjectFile, usize)>,
1917 ) -> CallArityEvidence {
1918 let (name, invocation_arguments, function_like) = match argument.kind() {
1919 "identifier" => (node_text(argument, source), None, false),
1920 "call_expression" => {
1921 let Some(function) = argument.child_by_field_name("function") else {
1922 return CallArityEvidence::Exact(1);
1923 };
1924 if function.kind() != "identifier" {
1925 return CallArityEvidence::Exact(1);
1926 }
1927 let Some(arguments) = argument.child_by_field_name("arguments") else {
1928 return CallArityEvidence::Exact(1);
1929 };
1930 (node_text(function, source), Some(arguments), true)
1931 }
1932 _ => return CallArityEvidence::Exact(1),
1933 };
1934 let Some(binding) = environment.binding(name) else {
1935 return if environment.unknown_names {
1936 CallArityEvidence::Unknown
1937 } else {
1938 CallArityEvidence::Exact(1)
1939 };
1940 };
1941 if !binding.is_exact() {
1942 return CallArityEvidence::Unknown;
1943 }
1944 match (&binding.definition, invocation_arguments, function_like) {
1945 (MacroDefinition::Object { replacement }, None, false) => self
1946 .replacement_arity_evidence(
1947 replacement,
1948 &[],
1949 &[],
1950 source,
1951 environment,
1952 stack,
1953 binding,
1954 ),
1955 (
1956 MacroDefinition::Function {
1957 parameters,
1958 replacement,
1959 },
1960 Some(arguments),
1961 true,
1962 ) => {
1963 let actuals = argument_children(arguments).collect::<Vec<_>>();
1964 if actuals.len() != parameters.len() {
1965 CallArityEvidence::Unknown
1966 } else {
1967 self.replacement_arity_evidence(
1968 replacement,
1969 parameters,
1970 &actuals,
1971 source,
1972 environment,
1973 stack,
1974 binding,
1975 )
1976 }
1977 }
1978 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1979 _ => CallArityEvidence::Unknown,
1980 }
1981 }
1982
1983 #[allow(clippy::too_many_arguments)]
1984 fn replacement_arity_evidence(
1985 &self,
1986 replacement: &str,
1987 parameters: &[String],
1988 actuals: &[Node<'_>],
1989 actual_source: &str,
1990 environment: &MacroEnvironment,
1991 stack: &mut Vec<(ProjectFile, usize)>,
1992 binding: &MacroBinding,
1993 ) -> CallArityEvidence {
1994 let identity = (binding.source.clone(), binding.declaration_byte);
1995 if stack.contains(&identity) || replacement.trim().is_empty() {
1996 return CallArityEvidence::Unknown;
1997 }
1998 stack.push(identity);
1999 let parsed = self.parsed_macro_replacement(binding, replacement);
2000 let evidence = (|| {
2001 let ParsedMacroReplacement::Parsed {
2002 source: sentinel,
2003 tree,
2004 } = parsed.as_ref()
2005 else {
2006 return None;
2007 };
2008 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2009 let arguments = call.child_by_field_name("arguments")?;
2010 let mut total = 0usize;
2011 for argument in argument_children(arguments) {
2012 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2013 return None;
2014 }
2015 if argument.kind() == "identifier"
2016 && let Some(parameter_index) = parameters
2017 .iter()
2018 .position(|parameter| parameter == node_text(argument, sentinel))
2019 {
2020 if !macro_expansion_shape_is_safe(
2021 actuals[parameter_index],
2022 actual_source,
2023 &[],
2024 environment,
2025 ) {
2026 return None;
2027 }
2028 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2029 actuals[parameter_index],
2030 actual_source,
2031 environment,
2032 stack,
2033 ) else {
2034 return None;
2035 };
2036 total += spread;
2037 continue;
2038 }
2039 let CallArityEvidence::Exact(spread) =
2040 self.argument_arity_evidence(argument, sentinel, environment, stack)
2041 else {
2042 return None;
2043 };
2044 total += spread;
2045 }
2046 Some(CallArityEvidence::Exact(total))
2047 })()
2048 .unwrap_or(CallArityEvidence::Unknown);
2049 stack.pop();
2050 evidence
2051 }
2052
2053 fn parsed_macro_replacement(
2054 &self,
2055 binding: &MacroBinding,
2056 replacement: &str,
2057 ) -> Arc<ParsedMacroReplacement> {
2058 let key = (binding.source.clone(), binding.declaration_byte);
2059 let mut cache = self
2060 .macro_replacements
2061 .lock()
2062 .expect("C++ macro replacement cache poisoned");
2063 if let Some(parsed) = cache.get(&key) {
2064 return Arc::clone(parsed);
2065 }
2066 #[cfg(any(test, feature = "test-support"))]
2067 self.macro_replacement_parse_count
2068 .fetch_add(1, Ordering::Relaxed);
2069 let source =
2070 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2071 let mut parser = Parser::new();
2072 let parsed = parser
2073 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2074 .ok()
2075 .and_then(|()| parser.parse(&source, None))
2076 .filter(|tree| !tree.root_node().has_error())
2077 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2078 ParsedMacroReplacement::Parsed { source, tree }
2079 });
2080 let parsed = Arc::new(parsed);
2081 cache.insert(key, Arc::clone(&parsed));
2082 parsed
2083 }
2084
2085 pub fn function_macro_local_binding<'tree>(
2095 &self,
2096 file: &ProjectFile,
2097 statement: Node<'tree>,
2098 source: &str,
2099 ) -> Option<MacroLocalBinding<'tree>> {
2100 if !is_c_source_file(file) {
2101 return None;
2102 }
2103 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2104 return Some(binding);
2105 }
2106 let call = match statement.kind() {
2107 "call_expression" => statement,
2108 "expression_statement" if statement.named_child_count() == 1 => {
2109 statement.named_child(0)?
2110 }
2111 _ => return None,
2112 };
2113 if call.kind() != "call_expression" {
2114 return None;
2115 }
2116 let function = call.child_by_field_name("function")?;
2117 if function.kind() != "identifier" {
2118 return None;
2119 }
2120 let arguments = call.child_by_field_name("arguments")?;
2121 let actuals = argument_children(arguments).collect::<Vec<_>>();
2122 let environment = self.macro_environment(file, call.start_byte());
2123 let function_name = node_text(function, source);
2124 let binding = environment.binding(function_name)?;
2125 let MacroDefinition::Function {
2126 parameters,
2127 replacement,
2128 } = &binding.definition
2129 else {
2130 return None;
2131 };
2132 if actuals.len() != parameters.len() {
2133 return None;
2134 }
2135 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2136 let (type_name, type_node) = match &template.declared_type {
2137 MacroLocalBindingTypeTemplate::Parameter(index) => {
2138 let actual = *actuals.get(*index)?;
2139 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2140 return None;
2141 }
2142 (node_text(actual, source).trim().to_string(), Some(actual))
2143 }
2144 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2145 };
2146 if type_name.is_empty() {
2147 return None;
2148 }
2149 Some(MacroLocalBinding {
2150 name: template.name.clone(),
2151 type_name,
2152 type_node,
2153 pointer_depth: template.pointer_depth,
2154 })
2155 }
2156
2157 fn macro_local_binding_template(
2158 &self,
2159 binding: &MacroBinding,
2160 parameters: &[String],
2161 replacement: &str,
2162 ) -> Option<Arc<MacroLocalBindingTemplate>> {
2163 let key = (binding.source.clone(), binding.declaration_byte);
2164 if let Some(template) = self
2165 .macro_local_binding_templates
2166 .lock()
2167 .expect("C++ macro local-binding cache poisoned")
2168 .get(&key)
2169 {
2170 return template.clone();
2171 }
2172 let template = (|| {
2173 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2174 let sentinel = body.source.as_str();
2175 let statements = body.statements()?;
2176 if statements.named_child_count() != 1 {
2177 return None;
2178 }
2179 let declaration = statements.named_child(0)?;
2180 if declaration.kind() != "declaration" {
2181 return None;
2182 }
2183 let type_node = declaration
2184 .child_by_field_name("type")
2185 .or_else(|| first_type_child(declaration))?;
2186 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2187 let mut cursor = declaration.walk();
2188 declaration.named_children(&mut cursor).find_map(|child| {
2189 if child.kind() == "init_declarator" {
2190 child.child_by_field_name("declarator")
2191 } else {
2192 is_declarator_node(child).then_some(child)
2193 }
2194 })
2195 })?;
2196 let name = extract_variable_name(declarator, sentinel)?;
2197 let pointer_depth = declared_name_indirection(declaration, type_node, &name, sentinel)?;
2198 let type_text = node_text(type_node, sentinel).trim();
2199 let declared_type = parameters
2200 .iter()
2201 .position(|parameter| parameter == type_text)
2202 .map(MacroLocalBindingTypeTemplate::Parameter)
2203 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2204 Some(Arc::new(MacroLocalBindingTemplate {
2205 name,
2206 declared_type,
2207 pointer_depth,
2208 }))
2209 })();
2210 self.macro_local_binding_templates
2211 .lock()
2212 .expect("C++ macro local-binding cache poisoned")
2213 .insert(key, template.clone());
2214 template
2215 }
2216
2217 pub fn function_macro_replacement_body(
2224 &self,
2225 file: &ProjectFile,
2226 definition: Node<'_>,
2227 source: &str,
2228 ) -> Option<Arc<ParsedReplacementBody>> {
2229 debug_assert_eq!(definition.kind(), "preproc_function_def");
2230 let MacroDefinition::Function {
2231 parameters,
2232 replacement,
2233 } = Self::decode_macro_definition(definition, source)
2234 else {
2235 return None;
2236 };
2237 self.parsed_macro_replacement_body(
2238 &(file.clone(), definition.start_byte()),
2239 ¶meters,
2240 &replacement,
2241 )
2242 }
2243
2244 fn parsed_macro_replacement_body(
2252 &self,
2253 key: &(ProjectFile, usize),
2254 parameters: &[String],
2255 replacement: &str,
2256 ) -> Option<Arc<ParsedReplacementBody>> {
2257 if let Some(body) = self
2258 .macro_replacement_bodies
2259 .lock()
2260 .expect("C++ macro replacement body cache poisoned")
2261 .get(key)
2262 {
2263 return body.clone();
2264 }
2265 let body = (|| {
2266 if replacement.trim().is_empty() {
2267 return None;
2268 }
2269 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
2270 let mut parser = Parser::new();
2271 parser
2272 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2273 .ok()?;
2274 let tree = parser.parse(&source, None)?;
2275 if tree.root_node().has_error() {
2276 return None;
2277 }
2278 let body = ParsedReplacementBody {
2279 source,
2280 tree,
2281 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2282 parameters: parameters.to_vec(),
2283 };
2284 body.statements()?;
2285 if body.expands_variadic_arguments() {
2286 return None;
2287 }
2288 Some(Arc::new(body))
2289 })();
2290 self.macro_replacement_bodies
2291 .lock()
2292 .expect("C++ macro replacement body cache poisoned")
2293 .insert(key.clone(), body.clone());
2294 body
2295 }
2296
2297 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
2298 let replacement = node
2299 .child_by_field_name("value")
2300 .map(|value| node_text(value, source).to_string())
2301 .unwrap_or_default();
2302 if node.kind() == "preproc_def" {
2303 return MacroDefinition::Object { replacement };
2304 }
2305 let Some(parameters) = node.child_by_field_name("parameters") else {
2306 return MacroDefinition::Unsupported;
2307 };
2308 if (0..parameters.child_count()).any(|index| {
2309 parameters
2310 .child(index)
2311 .is_some_and(|child| child.kind() == "...")
2312 }) {
2313 return MacroDefinition::Unsupported;
2314 }
2315 let parameters = (0..parameters.named_child_count())
2316 .filter_map(|index| parameters.named_child(index))
2317 .map(|parameter| node_text(parameter, source).to_string())
2318 .collect();
2319 MacroDefinition::Function {
2320 parameters,
2321 replacement,
2322 }
2323 }
2324
2325 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
2326 self.macro_event_cells
2327 .lock()
2328 .expect("C++ macro event cache poisoned")
2329 .entry(file.clone())
2330 .or_default()
2331 .clone()
2332 }
2333
2334 pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
2335 let key = (file.clone(), std::thread::current().id());
2336 self.macro_environment_cursors
2337 .lock()
2338 .expect("C++ macro environment cursor cache poisoned")
2339 .entry(key)
2340 .or_default()
2341 .clone()
2342 }
2343
2344 pub fn macro_environment(
2345 &self,
2346 file: &ProjectFile,
2347 before_byte: usize,
2348 ) -> Arc<MacroEnvironment> {
2349 #[cfg(any(test, feature = "test-support"))]
2350 self.macro_environment_request_count
2351 .fetch_add(1, Ordering::Relaxed);
2352 let cell = self.macro_event_cell(file);
2353 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2354 let frontier = events.partition_point(|event| event.byte() < before_byte);
2355 let cursor_cell = self.macro_environment_cursor_cell(file);
2356 let mut cursor = cursor_cell
2357 .lock()
2358 .expect("C++ macro environment cursor poisoned");
2359 if frontier < cursor.frontier {
2360 *cursor = MacroEnvironmentCursor::default();
2361 }
2362 if cursor.frontier == 0 {
2367 let proven = self.compile_proven_guards(file);
2368 if !proven.is_empty() && cursor.environment.build_proven_defines.len() != proven.len() {
2369 Arc::make_mut(&mut cursor.environment).build_proven_defines = proven
2370 .iter()
2371 .filter_map(|guard| match guard {
2372 PreprocessorGuard::Defined(name) => Some(name.clone()),
2373 _ => None,
2374 })
2375 .collect();
2376 }
2377 }
2378 if frontier > cursor.frontier {
2379 #[cfg(any(test, feature = "test-support"))]
2380 if Arc::strong_count(&cursor.environment) > 1 {
2381 self.macro_environment_copy_count
2382 .fetch_add(1, Ordering::Relaxed);
2383 }
2384 let start = cursor.frontier;
2385 let environment = Arc::make_mut(&mut cursor.environment);
2386 let mut include_stack = HashSet::from_iter([file.clone()]);
2387 for event in &events[start..frontier] {
2388 self.apply_macro_event(file, event, environment, &mut include_stack);
2389 }
2390 cursor.frontier = frontier;
2391 }
2392 Arc::clone(&cursor.environment)
2393 }
2394
2395 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2404 self.macro_environment(file, before_byte)
2405 .binding(name)
2406 .is_some()
2407 }
2408
2409 pub fn macro_name_may_be_bound_at(
2410 &self,
2411 file: &ProjectFile,
2412 name: &str,
2413 before_byte: usize,
2414 ) -> bool {
2415 self.macro_environment(file, before_byte).may_bind(name)
2416 }
2417
2418 pub fn macro_binding_matches_target_at(
2422 &self,
2423 analyzer: &CppGraphSource<'_>,
2424 file: &ProjectFile,
2425 name: &str,
2426 before_byte: usize,
2427 target: &CodeUnit,
2428 ) -> bool {
2429 let environment = self.macro_environment(file, before_byte);
2430 let Some(binding) = environment.binding(name) else {
2431 return false;
2432 };
2433 if binding.definition == MacroDefinition::Unsupported {
2434 return false;
2435 }
2436 if binding.source != *target.source() {
2440 return false;
2441 }
2442 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2443 return false;
2444 };
2445 analyzer.ranges(target).iter().any(|range| {
2446 let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
2447 return false;
2448 };
2449 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2450 let Some(parent) = node.parent() else {
2451 return false;
2452 };
2453 node = parent;
2454 }
2455 node.start_byte() == binding.declaration_byte
2456 })
2457 }
2458
2459 pub fn resolve_ordinary_macro_reference(
2466 &self,
2467 analyzer: &CppGraphSource<'_>,
2468 file: &ProjectFile,
2469 node: Node<'_>,
2470 source: &str,
2471 ) -> OrdinaryMacroReferenceResolution {
2472 if !is_ordinary_macro_reference_node(node) {
2473 return OrdinaryMacroReferenceResolution::Missing;
2474 }
2475 let name = node_text(node, source);
2476 if name.is_empty() {
2477 return OrdinaryMacroReferenceResolution::Missing;
2478 }
2479 let visible = self
2480 .visible_identifier_candidates(file, name)
2481 .filter(|candidate| candidate.is_macro())
2482 .cloned()
2483 .collect::<Vec<_>>();
2484 let mut exact = Vec::new();
2485 for candidate in &visible {
2486 if self.macro_binding_matches_target_at(
2487 analyzer,
2488 file,
2489 name,
2490 node.start_byte(),
2491 candidate,
2492 ) && !exact
2493 .iter()
2494 .any(|existing| same_visible_symbol(existing, candidate))
2495 {
2496 exact.push(candidate.clone());
2497 }
2498 }
2499 match exact.len() {
2500 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2501 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2502 0 if !visible.is_empty()
2503 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2504 {
2505 OrdinaryMacroReferenceResolution::Ambiguous
2506 }
2507 0 => OrdinaryMacroReferenceResolution::Missing,
2508 }
2509 }
2510
2511 pub fn recovered_c_reference_ranges(
2519 &self,
2520 file: &ProjectFile,
2521 root: Node<'_>,
2522 source: &str,
2523 limit: usize,
2524 ) -> RecoveredCReferenceRanges {
2525 if !is_c_source_file(file) {
2526 return RecoveredCReferenceRanges::Complete(Vec::new());
2527 }
2528 let mut ranges = Vec::new();
2529 let mut seen = HashSet::default();
2530 let mut stack = vec![(root, root.is_error())];
2531 while let Some((node, inside_error)) = stack.pop() {
2532 let inside_error = inside_error || node.is_error();
2533 if inside_error
2534 && recovered_c_reference_node(self, file, node, source)
2535 && seen.insert((node.start_byte(), node.end_byte()))
2536 {
2537 if ranges.len() == limit {
2538 return RecoveredCReferenceRanges::LimitExceeded;
2539 }
2540 ranges.push(Range {
2541 start_byte: node.start_byte(),
2542 end_byte: node.end_byte(),
2543 start_line: node.start_position().row,
2544 end_line: node.end_position().row,
2545 });
2546 }
2547 let mut cursor = node.walk();
2548 for child in node.named_children(&mut cursor) {
2549 stack.push((child, inside_error));
2550 }
2551 }
2552 ranges.sort_unstable();
2553 RecoveredCReferenceRanges::Complete(ranges)
2554 }
2555
2556 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2562 self.visible_identifier_candidates(file, target.identifier())
2563 .filter(|candidate| candidate.is_macro())
2564 .any(|candidate| {
2565 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2566 })
2567 }
2568
2569 pub fn object_macro_replacement_at(
2570 &self,
2571 file: &ProjectFile,
2572 name: &str,
2573 before_byte: usize,
2574 ) -> Option<String> {
2575 let environment = self.macro_environment(file, before_byte);
2576 let binding = environment.binding(name)?;
2577 if !binding.exact {
2578 return None;
2579 }
2580 match &binding.definition {
2581 MacroDefinition::Object { replacement } => Some(replacement.clone()),
2582 MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2583 }
2584 }
2585
2586 fn apply_macro_events(
2587 &self,
2588 file: &ProjectFile,
2589 before_byte: Option<usize>,
2590 environment: &mut MacroEnvironment,
2591 include_stack: &mut HashSet<ProjectFile>,
2592 ) {
2593 if !include_stack.insert(file.clone()) {
2594 return;
2595 }
2596 if self.cpp.prepared_syntax(self.token, file).is_none() {
2597 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2598 include_stack.remove(file);
2599 return;
2600 }
2601 match self.macro_include_protection(file) {
2602 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2603 Some(binding) if binding.is_exact() => {
2604 include_stack.remove(file);
2605 return;
2606 }
2607 Some(_) | None if environment.unknown_names => {
2608 let mut ambiguous_seen = HashSet::default();
2609 self.mark_macro_events_ambiguous(
2610 file,
2611 environment,
2612 &mut ambiguous_seen,
2613 file,
2614 before_byte.unwrap_or_default(),
2615 );
2616 include_stack.remove(file);
2617 return;
2618 }
2619 Some(_) => {
2620 let mut ambiguous_seen = HashSet::default();
2621 self.mark_macro_events_ambiguous(
2622 file,
2623 environment,
2624 &mut ambiguous_seen,
2625 file,
2626 before_byte.unwrap_or_default(),
2627 );
2628 include_stack.remove(file);
2629 return;
2630 }
2631 None => {}
2632 },
2633 MacroIncludeProtection::PragmaOnce => {
2634 if !environment.applied_pragma_once_files.insert(file.clone()) {
2635 include_stack.remove(file);
2636 return;
2637 }
2638 if environment.maybe_applied_pragma_once_files.remove(file) {
2639 let mut ambiguous_seen = HashSet::default();
2644 environment.applied_pragma_once_files.remove(file);
2645 self.mark_macro_events_ambiguous(
2646 file,
2647 environment,
2648 &mut ambiguous_seen,
2649 file,
2650 before_byte.unwrap_or_default(),
2651 );
2652 environment.maybe_applied_pragma_once_files.remove(file);
2653 environment.applied_pragma_once_files.insert(file.clone());
2654 include_stack.remove(file);
2655 return;
2656 }
2657 }
2658 MacroIncludeProtection::None => {}
2659 }
2660 let cell = self.macro_event_cell(file);
2661 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2662 for event in events {
2663 if before_byte.is_some_and(|limit| event.byte() >= limit) {
2664 break;
2665 }
2666 self.apply_macro_event(file, event, environment, include_stack);
2667 }
2668 include_stack.remove(file);
2669 }
2670
2671 fn apply_macro_event(
2672 &self,
2673 file: &ProjectFile,
2674 event: &MacroEvent,
2675 environment: &mut MacroEnvironment,
2676 include_stack: &mut HashSet<ProjectFile>,
2677 ) {
2678 #[cfg(any(test, feature = "test-support"))]
2679 self.macro_event_application_count
2680 .fetch_add(1, Ordering::Relaxed);
2681 match event {
2682 MacroEvent::Define {
2683 name,
2684 binding,
2685 conditional,
2686 byte,
2687 } => {
2688 match conditional
2689 .then(|| self.macro_event_condition_value(file, *byte, environment))
2690 .unwrap_or(Some(true))
2691 {
2692 Some(true) => environment.insert(name.clone(), binding.clone()),
2693 Some(false) => {}
2694 None => Self::merge_conditional_macro_definition(
2695 environment,
2696 name,
2697 binding,
2698 file,
2699 *byte,
2700 ),
2701 }
2702 }
2703 MacroEvent::Undef {
2704 name,
2705 conditional,
2706 byte,
2707 } => {
2708 match conditional
2709 .then(|| self.macro_event_condition_value(file, *byte, environment))
2710 .unwrap_or(Some(true))
2711 {
2712 Some(true) => environment.remove(name),
2713 Some(false) => {}
2714 None => {
2715 if environment.binding(name).is_some() {
2716 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2717 }
2718 }
2719 }
2720 }
2721 MacroEvent::Include {
2722 targets,
2723 conditional,
2724 byte,
2725 } => {
2726 let condition = conditional
2727 .then(|| self.macro_event_condition_value(file, *byte, environment))
2728 .unwrap_or(Some(true));
2729 if condition == Some(false) {
2730 return;
2731 }
2732 if targets.is_empty() {
2733 environment.mark_unknown_names(file, *byte);
2734 return;
2735 }
2736 if condition.is_none() || targets.len() > 1 {
2737 let mut ambiguous_seen = HashSet::default();
2738 for target in targets {
2739 self.mark_macro_events_ambiguous(
2740 target,
2741 environment,
2742 &mut ambiguous_seen,
2743 file,
2744 *byte,
2745 );
2746 }
2747 } else if let Some(target) = targets.first() {
2748 self.apply_macro_events(target, None, environment, include_stack);
2749 }
2750 }
2751 MacroEvent::Invalidate { byte } => {
2752 for binding in environment.bindings.values_mut() {
2753 *binding = MacroBinding::uncertain_from(binding, file, *byte);
2754 }
2755 }
2756 }
2757 }
2758
2759 fn macro_event_condition_value(
2765 &self,
2766 file: &ProjectFile,
2767 event_byte: usize,
2768 environment: &MacroEnvironment,
2769 ) -> Option<bool> {
2770 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2771 let source = prepared.source();
2772 let root = prepared.tree().root_node();
2773 let descendant = root.descendant_for_byte_range(
2774 event_byte,
2775 event_byte.saturating_add(1).min(source.len()),
2776 )?;
2777 let mut unknown = false;
2778 let mut current = descendant.parent();
2779 while let Some(conditional) = current {
2780 if matches!(
2781 conditional.kind(),
2782 "preproc_if" | "preproc_ifdef" | "preproc_elif"
2783 ) && !is_file_covering_include_guard(conditional, source)
2784 && preprocessor_conditional_contains_descendant(conditional, descendant)
2785 {
2786 let mut value = match conditional.kind() {
2787 "preproc_ifdef" => {
2788 let name = conditional.child_by_field_name("name")?;
2789 let defined =
2790 self.macro_name_defined_value(environment, node_text(name, source));
2791 match conditional.child(0)?.kind() {
2792 "#ifdef" => defined,
2793 "#ifndef" => defined.map(|defined| !defined),
2794 _ => None,
2795 }
2796 }
2797 "preproc_if" | "preproc_elif" => conditional
2798 .child_by_field_name("condition")
2799 .and_then(|condition| {
2800 self.preprocessor_integer_value(
2801 condition,
2802 source,
2803 environment,
2804 &mut Vec::new(),
2805 0,
2806 )
2807 })
2808 .map(|value| value != 0),
2809 _ => unreachable!(),
2810 };
2811 if conditional
2812 .child_by_field_name("alternative")
2813 .is_some_and(|alternative| {
2814 alternative.start_byte() <= descendant.start_byte()
2815 && descendant.end_byte() <= alternative.end_byte()
2816 })
2817 {
2818 value = value.map(|value| !value);
2819 }
2820 match value {
2821 Some(true) => {}
2822 Some(false) => return Some(false),
2823 None => unknown = true,
2824 }
2825 }
2826 current = conditional.parent();
2827 }
2828 (!unknown).then_some(true)
2829 }
2830
2831 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
2832 if environment.known_undefined_names.contains(name) {
2833 return Some(false);
2834 }
2835 if let Some(binding) = environment.binding(name) {
2836 return binding.is_exact().then_some(true);
2837 }
2838 environment
2839 .build_proven_defines
2840 .contains(name)
2841 .then_some(true)
2842 }
2843
2844 fn preprocessor_integer_value(
2845 &self,
2846 expression: Node<'_>,
2847 source: &str,
2848 environment: &MacroEnvironment,
2849 expansion_stack: &mut Vec<(ProjectFile, usize)>,
2850 depth: usize,
2851 ) -> Option<i128> {
2852 if depth >= 64 {
2855 return None;
2856 }
2857 match expression.kind() {
2858 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
2859 "identifier" | "type_identifier" => {
2860 let binding = environment.binding(node_text(expression, source))?;
2861 if !binding.is_exact() {
2862 return None;
2863 }
2864 let MacroDefinition::Object { replacement } = &binding.definition else {
2865 return None;
2866 };
2867 let identity = (binding.source.clone(), binding.declaration_byte);
2868 if expansion_stack.contains(&identity) {
2869 return None;
2870 }
2871 expansion_stack.push(identity);
2872 let parsed = self.parsed_macro_replacement(binding, replacement);
2873 let value = match parsed.as_ref() {
2874 ParsedMacroReplacement::Parsed {
2875 source: replacement_source,
2876 tree,
2877 } => first_descendant_of_kind(tree.root_node(), "call_expression")
2878 .and_then(|call| call.child_by_field_name("arguments"))
2879 .and_then(|arguments| argument_children(arguments).next())
2880 .and_then(|argument| {
2881 self.preprocessor_integer_value(
2882 argument,
2883 replacement_source,
2884 environment,
2885 expansion_stack,
2886 depth + 1,
2887 )
2888 }),
2889 ParsedMacroReplacement::Unsupported => None,
2890 };
2891 expansion_stack.pop();
2892 value
2893 }
2894 "preproc_defined" => {
2895 let mut cursor = expression.walk();
2896 let name = expression
2897 .named_children(&mut cursor)
2898 .find(|child| child.kind() == "identifier")?;
2899 self.macro_name_defined_value(environment, node_text(name, source))
2900 .map(i128::from)
2901 }
2902 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
2903 self.preprocessor_integer_value(
2904 child,
2905 source,
2906 environment,
2907 expansion_stack,
2908 depth + 1,
2909 )
2910 }),
2911 "unary_expression" => {
2912 let operator = expression.child_by_field_name("operator")?.kind();
2913 let argument = expression.child_by_field_name("argument")?;
2914 let value = self.preprocessor_integer_value(
2915 argument,
2916 source,
2917 environment,
2918 expansion_stack,
2919 depth + 1,
2920 )?;
2921 match operator {
2922 "+" => Some(value),
2923 "-" => value.checked_neg(),
2924 "!" => Some(i128::from(value == 0)),
2925 "~" => Some(!value),
2926 _ => None,
2927 }
2928 }
2929 "binary_expression" => {
2930 let left = self.preprocessor_integer_value(
2931 expression.child_by_field_name("left")?,
2932 source,
2933 environment,
2934 expansion_stack,
2935 depth + 1,
2936 )?;
2937 let right = self.preprocessor_integer_value(
2938 expression.child_by_field_name("right")?,
2939 source,
2940 environment,
2941 expansion_stack,
2942 depth + 1,
2943 )?;
2944 match expression.child_by_field_name("operator")?.kind() {
2945 "+" => left.checked_add(right),
2946 "-" => left.checked_sub(right),
2947 "*" => left.checked_mul(right),
2948 "/" => left.checked_div(right),
2949 "%" => left.checked_rem(right),
2950 "<<" => u32::try_from(right)
2951 .ok()
2952 .and_then(|shift| left.checked_shl(shift)),
2953 ">>" => u32::try_from(right)
2954 .ok()
2955 .and_then(|shift| left.checked_shr(shift)),
2956 "<" => Some(i128::from(left < right)),
2957 "<=" => Some(i128::from(left <= right)),
2958 ">" => Some(i128::from(left > right)),
2959 ">=" => Some(i128::from(left >= right)),
2960 "==" => Some(i128::from(left == right)),
2961 "!=" => Some(i128::from(left != right)),
2962 "&" => Some(left & right),
2963 "|" => Some(left | right),
2964 "^" => Some(left ^ right),
2965 "&&" => Some(i128::from(left != 0 && right != 0)),
2966 "||" => Some(i128::from(left != 0 || right != 0)),
2967 _ => None,
2968 }
2969 }
2970 _ => None,
2971 }
2972 }
2973
2974 fn mark_macro_events_ambiguous(
2975 &self,
2976 file: &ProjectFile,
2977 environment: &mut MacroEnvironment,
2978 include_stack: &mut HashSet<ProjectFile>,
2979 conditional_file: &ProjectFile,
2980 conditional_byte: usize,
2981 ) {
2982 if !include_stack.insert(file.clone()) {
2983 return;
2984 }
2985 if self.cpp.prepared_syntax(self.token, file).is_none() {
2986 environment.mark_unknown_names(conditional_file, conditional_byte);
2987 return;
2988 }
2989 match self.macro_include_protection(file) {
2990 MacroIncludeProtection::MacroGuard(guard) => {
2991 if environment
2992 .binding(&guard)
2993 .is_some_and(MacroBinding::is_exact)
2994 {
2995 return;
2996 }
2997 }
2998 MacroIncludeProtection::PragmaOnce => {
2999 if environment.applied_pragma_once_files.contains(file) {
3000 return;
3001 }
3002 environment
3003 .maybe_applied_pragma_once_files
3004 .insert(file.clone());
3005 }
3006 MacroIncludeProtection::None => {}
3007 }
3008 let cell = self.macro_event_cell(file);
3009 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3010 for event in events {
3011 #[cfg(any(test, feature = "test-support"))]
3012 self.macro_event_application_count
3013 .fetch_add(1, Ordering::Relaxed);
3014 match event {
3015 MacroEvent::Define { name, binding, .. } => {
3016 Self::merge_conditional_macro_definition(
3017 environment,
3018 name,
3019 binding,
3020 conditional_file,
3021 conditional_byte,
3022 );
3023 }
3024 MacroEvent::Undef { name, .. } => {
3025 if environment.binding(name).is_some() {
3026 environment.insert(
3027 name.clone(),
3028 MacroBinding::ambiguous(conditional_file, conditional_byte),
3029 );
3030 } else {
3031 environment.remove_known_undefined(name);
3032 }
3033 }
3034 MacroEvent::Include { targets, .. } => {
3035 if targets.is_empty() {
3036 environment.mark_unknown_names(conditional_file, conditional_byte);
3037 continue;
3038 }
3039 for target in targets {
3040 self.mark_macro_events_ambiguous(
3041 target,
3042 environment,
3043 include_stack,
3044 conditional_file,
3045 conditional_byte,
3046 );
3047 }
3048 }
3049 MacroEvent::Invalidate { .. } => {
3050 for binding in environment.bindings.values_mut() {
3051 *binding = MacroBinding::uncertain_from(
3052 binding,
3053 conditional_file,
3054 conditional_byte,
3055 );
3056 }
3057 }
3058 }
3059 }
3060 }
3061
3062 fn merge_conditional_macro_definition(
3063 environment: &mut MacroEnvironment,
3064 name: &str,
3065 possible_binding: &MacroBinding,
3066 conditional_file: &ProjectFile,
3067 conditional_byte: usize,
3068 ) {
3069 if environment.binding(name).is_some_and(|current| {
3074 current.definition != MacroDefinition::Unsupported
3075 && current.definition == possible_binding.definition
3076 }) {
3077 return;
3078 }
3079 environment.insert(
3080 name.to_string(),
3081 MacroBinding::ambiguous(conditional_file, conditional_byte),
3082 );
3083 }
3084
3085 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
3086 let cell = self
3087 .macro_include_protection_cells
3088 .lock()
3089 .expect("C++ include protection cache poisoned")
3090 .entry(file.clone())
3091 .or_default()
3092 .clone();
3093 cell.get_or_init(|| {
3094 self.cpp.prepared_syntax(self.token, file).map_or(
3095 MacroIncludeProtection::None,
3096 |prepared| {
3097 top_level_macro_include_protection(
3098 prepared.tree().root_node(),
3099 prepared.source(),
3100 )
3101 },
3102 )
3103 })
3104 .clone()
3105 }
3106
3107 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
3108 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3109 return Vec::new();
3110 };
3111 let source = prepared.source();
3112 let mut events = Vec::new();
3113 let mut stack = vec![prepared.tree().root_node()];
3114 while let Some(node) = stack.pop() {
3115 let conditional = has_preprocessor_conditional_ancestor(node, source);
3116 match node.kind() {
3117 "preproc_def" | "preproc_function_def" => {
3118 let Some(name) = node.child_by_field_name("name") else {
3119 continue;
3120 };
3121 let name = node_text(name, source).to_string();
3122 events.push(MacroEvent::Define {
3123 name,
3124 binding: MacroBinding {
3125 source: file.clone(),
3126 declaration_byte: node.start_byte(),
3127 definition: Self::decode_macro_definition(node, source),
3128 exact: true,
3129 },
3130 byte: node.start_byte(),
3131 conditional,
3132 });
3133 continue;
3134 }
3135 "preproc_include" => {
3136 let Some(path) = node.child_by_field_name("path") else {
3137 events.push(MacroEvent::Include {
3138 targets: Vec::new(),
3139 byte: node.start_byte(),
3140 conditional,
3141 });
3142 continue;
3143 };
3144 let targets =
3145 structured_include_path(path, source).map_or_else(Vec::new, |path| {
3146 resolve_include_targets_with_index(
3147 file,
3148 path,
3149 self.cpp.include_target_index(),
3150 )
3151 });
3152 if targets.is_empty() && path.kind() == "system_lib_string" {
3157 continue;
3158 }
3159 events.push(MacroEvent::Include {
3160 targets,
3161 byte: node.start_byte(),
3162 conditional,
3163 });
3164 continue;
3165 }
3166 "preproc_call" => {
3167 let Some(directive) = node.child_by_field_name("directive") else {
3168 continue;
3169 };
3170 if node_text(directive, source) != "#undef" {
3171 continue;
3172 }
3173 let name = node
3174 .child_by_field_name("argument")
3175 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
3176 if let Some(name) = name {
3177 events.push(MacroEvent::Undef {
3178 name,
3179 byte: node.start_byte(),
3180 conditional,
3181 });
3182 } else {
3183 events.push(MacroEvent::Invalidate {
3184 byte: node.start_byte(),
3185 });
3186 }
3187 continue;
3188 }
3189 _ => {}
3190 }
3191 for index in (0..node.named_child_count()).rev() {
3192 if let Some(child) = node.named_child(index) {
3193 stack.push(child);
3194 }
3195 }
3196 }
3197 events.sort_by_key(MacroEvent::byte);
3198 events
3199 }
3200
3201 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
3202 self.ordinary_type_import_cells
3203 .lock()
3204 .expect("C++ ordinary type import cache poisoned")
3205 .entry(file.clone())
3206 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
3207 .clone()
3208 }
3209
3210 pub fn project_using_index(
3211 &self,
3212 build: impl FnOnce() -> ProjectUsingIndex,
3213 ) -> &ProjectUsingIndex {
3214 self.project_using_index.get_or_init(build)
3215 }
3216
3217 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
3218 let mut files = self
3219 .visible_source_files_by_root
3220 .values()
3221 .flatten()
3222 .cloned()
3223 .collect::<HashSet<_>>()
3224 .into_iter()
3225 .collect::<Vec<_>>();
3226 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
3227 files
3228 }
3229
3230 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
3231 self.visible_source_files_by_root
3232 .get(root)
3233 .is_some_and(|files| files.contains(source))
3234 }
3235
3236 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
3237 let cached = self
3238 .visible_parser_alias_name_sets
3239 .read()
3240 .expect("visible parser alias-name cache poisoned")
3241 .get(file)
3242 .cloned();
3243 let cell = if let Some(cached) = cached {
3244 cached
3245 } else {
3246 let mut cells = self
3247 .visible_parser_alias_name_sets
3248 .write()
3249 .expect("visible parser alias-name cache poisoned");
3250 Arc::clone(
3251 cells
3252 .entry(file.clone())
3253 .or_insert_with(|| Arc::new(OnceLock::new())),
3254 )
3255 };
3256 cell.get_or_init(|| {
3257 #[cfg(any(test, feature = "test-support"))]
3258 self.visible_parser_alias_name_set_build_count
3259 .fetch_add(1, Ordering::Relaxed);
3260 let mut names = HashSet::default();
3261 let visible_files = self
3262 .visible_source_files_by_root
3263 .get(file)
3264 .cloned()
3265 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
3266 for visible_file in visible_files {
3267 let aliases = {
3268 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
3269 Arc::clone(
3270 cells
3271 .entry(visible_file.clone())
3272 .or_insert_with(|| Arc::new(OnceLock::new())),
3273 )
3274 };
3275 for alias in aliases
3276 .get_or_init(|| {
3277 self.parser_alias_source_parses
3278 .fetch_add(1, Ordering::Relaxed);
3279 #[cfg(any(test, feature = "test-support"))]
3280 {
3281 *self
3282 .alias_source_parse_counts
3283 .lock()
3284 .expect("alias source parse count lock")
3285 .entry(visible_file.clone())
3286 .or_default() += 1;
3287 }
3288 aliases_from_prepared_source(self.cpp, self.token, &visible_file)
3289 .into_boxed_slice()
3290 })
3291 .iter()
3292 {
3293 names.insert(alias.name.clone());
3294 }
3295 }
3296 names
3297 })
3298 .contains(name)
3299 }
3300
3301 pub fn parser_alias_name_may_resolve_to_target(
3302 &self,
3303 file: &ProjectFile,
3304 alias_name: &str,
3305 target: &CodeUnit,
3306 ) -> bool {
3307 let started = std::time::Instant::now();
3308 self.parser_alias_fallback_calls
3309 .fetch_add(1, Ordering::Relaxed);
3310 let mut files = 0usize;
3311 let matched = match self.visible_source_files_by_root.get(file) {
3312 None => {
3313 files = 1;
3314 self.file_alias_matches(self.cpp, file, alias_name, target)
3315 }
3316 Some(visible_files) => visible_files.iter().any(|visible_file| {
3317 files += 1;
3318 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
3319 }),
3320 };
3321 self.parser_alias_fallback_files
3322 .fetch_add(files, Ordering::Relaxed);
3323 self.parser_alias_fallback_elapsed_micros.fetch_add(
3324 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
3325 Ordering::Relaxed,
3326 );
3327 matched
3328 }
3329
3330 fn callable_arities_for_target(
3331 &self,
3332 analyzer: &CppGraphSource<'_>,
3333 cpp: &dyn CppSource,
3334 file: &ProjectFile,
3335 prepared: &PreparedSyntaxTree,
3336 spec: &TargetSpec,
3337 ) -> Vec<ActivatedCallableArity> {
3338 let Some(signature) = spec.target.signature() else {
3339 return Vec::new();
3340 };
3341 let Some(candidates) = self
3342 .visible_by_identifier
3343 .get(file)
3344 .and_then(|by_name| by_name.get(&spec.member_name))
3345 else {
3346 return Vec::new();
3347 };
3348 let differing_candidates = candidates
3349 .iter()
3350 .filter(|candidate| {
3351 candidate.is_function()
3352 && candidate.fq_name() == spec.target.fq_name()
3353 && candidate.signature() == Some(signature)
3354 })
3355 .filter_map(|candidate| {
3356 analyzer
3357 .signature_metadata(candidate)
3358 .into_iter()
3359 .find_map(|metadata| metadata.callable_arity())
3360 .filter(|arity| Some(*arity) != spec.callable_arity)
3361 .map(|arity| (candidate, arity))
3362 })
3363 .collect::<Vec<_>>();
3364 if differing_candidates.is_empty() {
3365 return Vec::new();
3366 }
3367 let mut arities = Vec::with_capacity(differing_candidates.len());
3368 let reference = CallableReferenceContext {
3371 file,
3372 position: None,
3373 };
3374 for (candidate, candidate_arity) in differing_candidates {
3375 let declaration_activation = if candidate.source() == file {
3376 callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
3377 } else {
3378 cpp.prepared_syntax(self.token, candidate.source())
3379 .and_then(|syntax| {
3380 callable_declaration_activation_in_file(
3381 analyzer,
3382 syntax.as_ref(),
3383 candidate,
3384 &reference,
3385 )
3386 })
3387 };
3388 let Some(declaration_activation) = declaration_activation else {
3389 continue;
3390 };
3391 let activation_byte = if candidate.source() == file {
3392 Some(declaration_activation)
3393 } else {
3394 self.include_activation_for_source(cpp, file, prepared, candidate.source())
3395 };
3396 if let Some(activation_byte) = activation_byte {
3397 arities.push(ActivatedCallableArity {
3398 activation_byte,
3399 arity: candidate_arity,
3400 });
3401 }
3402 }
3403 arities
3404 }
3405
3406 fn callable_parameter_macro_arity(
3407 &self,
3408 target: &CodeUnit,
3409 signature: Option<&str>,
3410 ) -> Option<CallableArity> {
3411 let parameter_types = cpp_signature_param_types(signature?)?;
3412 let [macro_name] = parameter_types.as_slice() else {
3413 return None;
3414 };
3415 if macro_name.is_empty()
3416 || !macro_name
3417 .chars()
3418 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
3419 {
3420 return None;
3421 }
3422 let cache_key = (target.source().clone(), macro_name.clone());
3423 if let Some(cached) = self
3424 .callable_parameter_macro_arities
3425 .lock()
3426 .expect("C++ callable parameter-macro arity cache poisoned")
3427 .get(&cache_key)
3428 .copied()
3429 {
3430 return cached;
3431 }
3432 let mut visible_files = HashSet::default();
3433 collect_include_closure(
3434 &self.cpp_source(),
3435 self.cpp.include_target_index(),
3436 target.source(),
3437 &mut visible_files,
3438 None,
3439 );
3440 let mut arities = Vec::new();
3441 for visible_file in visible_files {
3442 let cell = self.macro_event_cell(&visible_file);
3443 for event in
3444 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
3445 {
3446 let MacroEvent::Define { name, binding, .. } = event else {
3447 continue;
3448 };
3449 if name != macro_name {
3450 continue;
3451 }
3452 let MacroDefinition::Object { replacement } = &binding.definition else {
3453 continue;
3454 };
3455 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
3456 continue;
3457 };
3458 if !arities.contains(&arity) {
3459 arities.push(arity);
3460 }
3461 }
3462 }
3463 let resolved = (|| {
3464 let required = arities
3465 .iter()
3466 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
3467 .min()?;
3468 let total = arities.iter().map(|arity| arity.total()).max()?;
3469 let repeated = arities
3470 .iter()
3471 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
3472 Some(CallableArity::new(required, total, repeated))
3477 })();
3478 self.callable_parameter_macro_arities
3479 .lock()
3480 .expect("C++ callable parameter-macro arity cache poisoned")
3481 .insert(cache_key, resolved);
3482 resolved
3483 }
3484
3485 pub fn include_activation_for_source(
3486 &self,
3487 cpp: &dyn CppSource,
3488 file: &ProjectFile,
3489 prepared: &PreparedSyntaxTree,
3490 donor_source: &ProjectFile,
3491 ) -> Option<usize> {
3492 let key = (file.clone(), donor_source.clone());
3493 if let Some(cached) = self
3494 .include_activation_cells
3495 .lock()
3496 .expect("C++ include activation cache poisoned")
3497 .get(&key)
3498 .copied()
3499 {
3500 return cached;
3501 }
3502 #[cfg(any(test, feature = "test-support"))]
3503 self.include_activation_build_count
3504 .fetch_add(1, Ordering::Relaxed);
3505 let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
3506 let mut cells = self
3507 .include_activation_cells
3508 .lock()
3509 .expect("C++ include activation cache poisoned");
3510 *cells.entry(key).or_insert(activation)
3511 }
3512
3513 pub fn conditional_include_projections_for_source(
3514 &self,
3515 file: &ProjectFile,
3516 prepared: &PreparedSyntaxTree,
3517 donor_source: &ProjectFile,
3518 ) -> Arc<[ConditionalIncludeProjection]> {
3519 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
3520 let cell = self
3521 .conditional_include_projection_cells
3522 .lock()
3523 .expect("C++ conditional include projection cache poisoned")
3524 .entry(file.clone())
3525 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
3526 .clone();
3527 let index = cell.get_or_build_pool_independent(|| {
3528 #[cfg(any(test, feature = "test-support"))]
3529 self.conditional_include_projection_index_build_count
3530 .fetch_add(1, Ordering::Relaxed);
3531 find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
3532 #[cfg(any(test, feature = "test-support"))]
3533 self.conditional_include_projection_state_count
3534 .fetch_add(1, Ordering::Relaxed);
3535 })
3536 });
3537 index
3538 .get(donor_source)
3539 .cloned()
3540 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
3541 }
3542
3543 #[cfg(any(test, feature = "test-support"))]
3544 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
3545 (
3546 self.conditional_include_projection_index_build_count
3547 .load(Ordering::Relaxed),
3548 self.conditional_include_projection_state_count
3549 .load(Ordering::Relaxed),
3550 )
3551 }
3552
3553 #[cfg(any(test, feature = "test-support"))]
3554 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
3555 self.conditional_include_target_state_count
3556 .load(Ordering::Relaxed)
3557 }
3558
3559 #[cfg(any(test, feature = "test-support"))]
3560 pub fn include_activation_build_count_for_test(&self) -> usize {
3561 self.include_activation_build_count.load(Ordering::Relaxed)
3562 }
3563
3564 #[cfg(any(test, feature = "test-support"))]
3565 pub fn note_using_donor_activation_for_test(&self) {
3566 self.using_donor_activation_count
3567 .fetch_add(1, Ordering::Relaxed);
3568 }
3569
3570 #[cfg(not(any(test, feature = "test-support")))]
3571 pub fn note_using_donor_activation_for_test(&self) {}
3572
3573 #[cfg(any(test, feature = "test-support"))]
3574 pub fn note_using_namespace_lookup_for_test(&self) {
3575 self.using_namespace_lookup_count
3576 .fetch_add(1, Ordering::Relaxed);
3577 }
3578
3579 #[cfg(not(any(test, feature = "test-support")))]
3580 pub fn note_using_namespace_lookup_for_test(&self) {}
3581
3582 #[cfg(any(test, feature = "test-support"))]
3583 pub fn note_using_name_candidate_inspection_for_test(&self) {
3584 self.using_name_candidate_inspection_count
3585 .fetch_add(1, Ordering::Relaxed);
3586 }
3587
3588 #[cfg(not(any(test, feature = "test-support")))]
3589 pub fn note_using_name_candidate_inspection_for_test(&self) {}
3590
3591 #[cfg(any(test, feature = "test-support"))]
3592 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
3593 (
3594 self.using_donor_activation_count.load(Ordering::Relaxed),
3595 self.using_namespace_lookup_count.load(Ordering::Relaxed),
3596 self.callable_reference_spec_build_count
3597 .load(Ordering::Relaxed),
3598 self.using_name_candidate_inspection_count
3599 .load(Ordering::Relaxed),
3600 )
3601 }
3602
3603 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3604 file == target.source()
3605 || self
3606 .visible_by_file
3607 .get(file)
3608 .is_some_and(|visible| visible.contains(target))
3609 }
3610
3611 pub fn declaration_visible_at(
3623 &self,
3624 analyzer: &CppGraphSource<'_>,
3625 file: &ProjectFile,
3626 declaration: &CodeUnit,
3627 reference_byte: usize,
3628 ) -> bool {
3629 let reference_guards = OnceCell::new();
3630 self.visible_identifier_candidates(file, declaration.identifier())
3631 .filter(|candidate| {
3632 self.same_logical_callable(analyzer, candidate, declaration)
3633 || flattened_macro_namespace_declaration_matches(
3634 analyzer,
3635 self.cpp,
3636 file,
3637 candidate,
3638 declaration,
3639 reference_byte,
3640 )
3641 })
3642 .any(|candidate| {
3643 self.physical_declaration_visible_at(
3644 analyzer,
3645 file,
3646 candidate,
3647 reference_byte,
3648 &reference_guards,
3649 )
3650 })
3651 }
3652
3653 pub fn declaration_visible_for_c_forward_call(
3659 &self,
3660 analyzer: &CppGraphSource<'_>,
3661 file: &ProjectFile,
3662 declaration: &CodeUnit,
3663 reference_byte: usize,
3664 ) -> bool {
3665 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
3666 return true;
3667 }
3668 if declaration.source() != file {
3669 return false;
3670 }
3671 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3672 return false;
3673 };
3674 let reference_guards = prepared
3675 .tree()
3676 .root_node()
3677 .descendant_for_byte_range(reference_byte, reference_byte)
3678 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
3679 declaration_guard_requirements(analyzer, self.cpp, declaration)
3680 .into_iter()
3681 .any(|(_, required)| {
3682 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
3683 })
3684 }
3685
3686 pub fn callable_arity_at_reference(
3687 &self,
3688 analyzer: &CppGraphSource<'_>,
3689 file: &ProjectFile,
3690 candidate: &CodeUnit,
3691 reference_byte: usize,
3692 ) -> Option<CallableArity> {
3693 let key = (file.clone(), logical_symbol_key(candidate));
3694 let cell = self
3695 .callable_reference_specs
3696 .lock()
3697 .expect("C++ callable reference-spec cache poisoned")
3698 .entry(key)
3699 .or_default()
3700 .clone();
3701 let spec = cell.get_or_init(|| {
3702 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3703 let spec = TargetSpec::from_target(analyzer, candidate)?;
3704 let spec = spec
3705 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
3706 .into_owned();
3707 #[cfg(any(test, feature = "test-support"))]
3708 self.callable_reference_spec_build_count
3709 .fetch_add(1, Ordering::Relaxed);
3710 Some(spec)
3711 });
3712 spec.as_ref()?.callable_arity_at(reference_byte)
3713 }
3714
3715 fn physical_declaration_visible_at(
3716 &self,
3717 analyzer: &CppGraphSource<'_>,
3718 file: &ProjectFile,
3719 declaration: &CodeUnit,
3720 reference_byte: usize,
3721 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
3722 ) -> bool {
3723 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3724 return false;
3725 };
3726 let reference = CallableReferenceContext {
3727 file,
3728 position: Some(CallableReferencePosition {
3729 prepared: prepared.as_ref(),
3730 byte: reference_byte,
3731 guards: reference_guards,
3732 }),
3733 };
3734 if declaration.source() == file {
3735 return callable_declaration_activation_in_file(
3736 analyzer,
3737 prepared.as_ref(),
3738 declaration,
3739 &reference,
3740 )
3741 .or_else(|| {
3742 self.exhaustive_guard_family_activation(
3743 analyzer,
3744 prepared.as_ref(),
3745 declaration,
3746 &reference,
3747 )
3748 })
3749 .is_some_and(|activation| activation < reference_byte);
3750 }
3751 let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
3752 return false;
3753 };
3754 if callable_declaration_activation_in_file(
3755 analyzer,
3756 donor_syntax.as_ref(),
3757 declaration,
3758 &reference,
3759 )
3760 .or_else(|| {
3761 self.exhaustive_guard_family_activation(
3762 analyzer,
3763 donor_syntax.as_ref(),
3764 declaration,
3765 &reference,
3766 )
3767 })
3768 .is_none()
3769 {
3770 return false;
3771 }
3772 declaration_guard_requirements(analyzer, self.cpp, declaration)
3773 .into_iter()
3774 .any(|(_, declaration_guards)| {
3775 self.foreign_declaration_reachable_at_reference(
3776 file,
3777 prepared.as_ref(),
3778 declaration.source(),
3779 &declaration_guards,
3780 reference.guards(),
3781 reference_byte,
3782 )
3783 })
3784 }
3785
3786 pub fn external_type_candidate_visible_at(
3787 &self,
3788 file: &ProjectFile,
3789 candidate: &CodeUnit,
3790 reference_byte: usize,
3791 ) -> bool {
3792 if candidate.source() == file {
3793 return true;
3794 }
3795 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3796 return false;
3797 };
3798 self.visible_identifier_candidates(file, candidate.identifier())
3799 .filter(|peer| same_logical_symbol(candidate, peer))
3800 .any(|peer| {
3801 peer.source() == file
3802 || self
3803 .include_activation_for_source(
3804 self.cpp,
3805 file,
3806 prepared.as_ref(),
3807 peer.source(),
3808 )
3809 .is_some_and(|activation| activation <= reference_byte)
3810 })
3811 }
3812
3813 pub fn external_type_declaration_visible_at(
3814 &self,
3815 file: &ProjectFile,
3816 candidate: &CodeUnit,
3817 reference_byte: usize,
3818 ) -> bool {
3819 if candidate.source() == file {
3820 return true;
3821 }
3822 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3823 return false;
3824 };
3825 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3826 .is_some_and(|activation| activation <= reference_byte)
3827 }
3828
3829 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
3848 if let Some(cached) = self
3849 .compile_proven_guard_cells
3850 .lock()
3851 .expect("C++ compile-proven guard cache poisoned")
3852 .get(file)
3853 {
3854 return Arc::clone(cached);
3855 }
3856 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
3857 Some(names) => names,
3858 None => {
3859 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
3860 let seed = translation_units.next().and_then(|translation_unit| {
3861 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
3862 });
3863 match seed {
3864 None => HashSet::default(),
3865 Some(mut names) => {
3866 for translation_unit in translation_units {
3867 let Some(reached) = context_fact_names(
3868 self.cpp.compile_contexts_for(&translation_unit),
3869 ) else {
3870 names.clear();
3871 break;
3872 };
3873 names.retain(|name| reached.contains(name));
3874 if names.is_empty() {
3875 break;
3876 }
3877 }
3878 names
3879 }
3880 }
3881 }
3882 };
3883 let proven = Arc::new(
3884 names
3885 .into_iter()
3886 .map(PreprocessorGuard::Defined)
3887 .collect::<HashSet<_>>(),
3888 );
3889 self.compile_proven_guard_cells
3890 .lock()
3891 .expect("C++ compile-proven guard cache poisoned")
3892 .insert(file.clone(), Arc::clone(&proven));
3893 proven
3894 }
3895
3896 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
3903 if !self.cpp.compile_contexts_for(file).is_empty() {
3904 return false;
3905 }
3906 let translation_units = self.cpp.reaching_translation_units(file);
3907 translation_units.is_empty()
3908 || translation_units
3909 .iter()
3910 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
3911 }
3912
3913 pub fn miss_requires_compile_context(
3925 &self,
3926 file: &ProjectFile,
3927 identifier: &str,
3928 reference: Node<'_>,
3929 ) -> bool {
3930 if !self.compile_context_is_absent(file) {
3931 return false;
3932 }
3933 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3934 return false;
3935 };
3936 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3937 let reference_byte = reference.start_byte();
3938 let mut sources = self
3939 .visible_identifier_candidates(file, identifier)
3940 .map(CodeUnit::source)
3941 .filter(|source| *source != file)
3942 .collect::<Vec<_>>();
3943 sources.sort();
3944 sources.dedup();
3945 sources.into_iter().any(|declaration_source| {
3946 self.conditional_include_projections_for_source(
3947 file,
3948 prepared.as_ref(),
3949 declaration_source,
3950 )
3951 .iter()
3952 .any(|projection| {
3953 projection.activation_byte <= reference_byte
3954 && !guard_requirements_hold_at_reference(
3955 &projection.required_guards,
3956 reference_guards.as_ref(),
3957 )
3958 && guards_compatible_at_reference(
3959 &projection.required_guards,
3960 reference_guards.as_ref(),
3961 )
3962 })
3963 })
3964 }
3965
3966 fn foreign_declaration_reachable_at_reference(
3977 &self,
3978 file: &ProjectFile,
3979 prepared: &PreparedSyntaxTree,
3980 declaration_source: &ProjectFile,
3981 declaration_guards: &HashSet<PreprocessorGuard>,
3982 reference_guards: Option<&HashSet<PreprocessorGuard>>,
3983 reference_byte: usize,
3984 ) -> bool {
3985 let proven = self.compile_proven_guards(file);
3991 let augmented;
3992 let reference_guards = match reference_guards {
3993 Some(active) if !proven.is_empty() => {
3994 augmented = active.union(&proven).cloned().collect();
3995 Some(&augmented)
3996 }
3997 other => other,
3998 };
3999 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
4000 return false;
4001 }
4002 if self
4003 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
4004 .is_some_and(|activation| activation <= reference_byte)
4005 {
4006 return true;
4007 }
4008 let projections =
4009 self.conditional_include_projections_for_source(file, prepared, declaration_source);
4010 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4011 eprintln!(
4012 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
4013 declaration_source.rel_path().display(),
4014 declaration_guards.len(),
4015 proven.len(),
4016 projections.len(),
4017 );
4018 }
4019 projections.iter().any(|projection| {
4020 projection.activation_byte <= reference_byte
4021 && guard_requirements_hold_at_reference(
4022 &projection.required_guards,
4023 reference_guards,
4024 )
4025 && self.preprocessor_guards_stable_between(
4026 file,
4027 projection.activation_byte,
4028 reference_byte,
4029 &projection.required_guards,
4030 )
4031 })
4032 }
4033
4034 fn foreign_declaration_may_be_reachable_from_raw_guards(
4035 &self,
4036 file: &ProjectFile,
4037 prepared: &PreparedSyntaxTree,
4038 declaration_source: &ProjectFile,
4039 declaration_guards: &HashSet<PreprocessorGuard>,
4040 reference_guards: Option<&HashSet<PreprocessorGuard>>,
4041 reference_byte: usize,
4042 ) -> bool {
4043 let proven = self.compile_proven_guards(file);
4044 let augmented;
4045 let reference_guards = match reference_guards {
4046 Some(active) if !proven.is_empty() => {
4047 augmented = active.union(&proven).cloned().collect();
4048 Some(&augmented)
4049 }
4050 other => other,
4051 };
4052 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
4053 return false;
4054 }
4055 if self
4056 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
4057 .is_some_and(|activation| activation <= reference_byte)
4058 {
4059 return true;
4060 }
4061 let reachable = find_conditional_include_projection_for_source(
4062 self.cpp,
4063 self.token,
4064 file,
4065 prepared,
4066 declaration_source,
4067 reference_guards,
4068 reference_byte,
4069 &|| {
4070 #[cfg(any(test, feature = "test-support"))]
4071 self.conditional_include_target_state_count
4072 .fetch_add(1, Ordering::Relaxed);
4073 },
4074 );
4075 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4076 eprintln!(
4077 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
4078 declaration_source.rel_path().display(),
4079 declaration_guards.len(),
4080 proven.len(),
4081 reference_guards.map_or(0, HashSet::len),
4082 );
4083 }
4084 reachable
4085 }
4086
4087 fn foreign_declaration_reachable_from_compile_proven_guards(
4088 &self,
4089 file: &ProjectFile,
4090 prepared: &PreparedSyntaxTree,
4091 declaration_source: &ProjectFile,
4092 declaration_guards: &HashSet<PreprocessorGuard>,
4093 reference_byte: usize,
4094 ) -> bool {
4095 let proven = self.compile_proven_guards(file);
4096 if proven.is_empty()
4097 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
4098 {
4099 return false;
4100 }
4101 let projections =
4102 self.conditional_include_projections_for_source(file, prepared, declaration_source);
4103 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4104 eprintln!(
4105 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
4106 declaration_source.rel_path().display(),
4107 declaration_guards.len(),
4108 proven.len(),
4109 projections.len(),
4110 );
4111 }
4112 projections.iter().any(|projection| {
4113 projection.activation_byte <= reference_byte
4114 && guard_requirements_hold_at_reference(
4115 &projection.required_guards,
4116 Some(proven.as_ref()),
4117 )
4118 && self.preprocessor_guards_stable_between(
4123 file,
4124 0,
4125 projection.activation_byte,
4126 &projection.required_guards,
4127 )
4128 })
4129 }
4130
4131 pub fn external_type_candidate_visible_in_context(
4132 &self,
4133 analyzer: &CppGraphSource<'_>,
4134 file: &ProjectFile,
4135 candidate: &CodeUnit,
4136 reference: Node<'_>,
4137 ) -> bool {
4138 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
4139 if report_stats {
4140 eprintln!(
4141 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
4142 candidate.fq_name(),
4143 candidate.source().rel_path().display(),
4144 file.rel_path().display(),
4145 reference.start_byte(),
4146 );
4147 }
4148 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4149 return false;
4150 };
4151 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
4152 let reference_guards = OnceCell::new();
4153 let reference_guards_at_site = || {
4154 reference_guards.get_or_init(|| {
4155 let started = Instant::now();
4156 if report_stats {
4157 eprintln!(
4158 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
4159 file.rel_path().display(),
4160 reference.start_byte(),
4161 raw_reference_guards.as_ref().map_or(0, HashSet::len),
4162 );
4163 }
4164 let macro_environment = self.macro_environment(file, reference.start_byte());
4165 let filtered = raw_reference_guards
4166 .clone()
4167 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
4168 if report_stats {
4169 eprintln!(
4170 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
4171 filtered.is_some(),
4172 started.elapsed().as_millis(),
4173 );
4174 }
4175 filtered
4176 })
4177 };
4178
4179 let peers = self
4180 .visible_identifier_candidates(file, candidate.identifier())
4181 .filter(|peer| same_logical_symbol(candidate, peer))
4182 .collect::<Vec<_>>();
4183 if report_stats {
4184 let peer_sources = peers
4185 .iter()
4186 .map(|peer| peer.source().rel_path().display().to_string())
4187 .collect::<Vec<_>>();
4188 eprintln!(
4189 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
4190 candidate.fq_name(),
4191 );
4192 }
4193 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
4194 declaration_guard_requirements(analyzer, self.cpp, peer)
4195 .into_iter()
4196 .any(|(declaration_byte, declaration_guards)| {
4197 if peer.source() == file {
4198 let visible = declaration_byte < reference.start_byte()
4199 && declaration_guards.is_empty();
4200 if report_stats {
4201 eprintln!(
4202 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
4203 peer.source().rel_path().display(),
4204 declaration_guards.len(),
4205 );
4206 }
4207 return visible;
4208 }
4209 let direct = declaration_guards.is_empty()
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.start_byte());
4218 let compile_proven = !direct
4219 && self.foreign_declaration_reachable_from_compile_proven_guards(
4220 file,
4221 prepared.as_ref(),
4222 peer.source(),
4223 &declaration_guards,
4224 reference.start_byte(),
4225 );
4226 if report_stats {
4227 eprintln!(
4228 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
4229 peer.source().rel_path().display(),
4230 declaration_guards.len(),
4231 );
4232 }
4233 direct || compile_proven
4234 })
4235 });
4236 if directly_visible_without_reference_environment {
4237 if report_stats {
4238 eprintln!(
4239 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
4240 candidate.fq_name(),
4241 );
4242 }
4243 return true;
4244 }
4245 let directly_visible = peers.iter().any(|peer| {
4246 declaration_guard_requirements(analyzer, self.cpp, peer)
4247 .into_iter()
4248 .any(|(declaration_byte, declaration_guards)| {
4249 if peer.source() == file {
4250 if declaration_byte >= reference.start_byte() {
4251 return false;
4252 }
4253 if !guard_requirements_hold_at_reference(
4254 &declaration_guards,
4255 raw_reference_guards.as_ref(),
4256 ) {
4257 return false;
4258 }
4259 return guard_requirements_hold_at_reference(
4260 &declaration_guards,
4261 reference_guards_at_site().as_ref(),
4262 ) && self.preprocessor_guards_stable_between(
4263 file,
4264 declaration_byte,
4265 reference.start_byte(),
4266 &declaration_guards,
4267 );
4268 }
4269 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
4270 file,
4271 prepared.as_ref(),
4272 peer.source(),
4273 &declaration_guards,
4274 raw_reference_guards.as_ref(),
4275 reference.start_byte(),
4276 );
4277 if report_stats {
4278 eprintln!(
4279 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
4280 peer.source().rel_path().display(),
4281 declaration_guards.len(),
4282 );
4283 }
4284 if !raw_feasible {
4285 return false;
4286 }
4287 self.foreign_declaration_reachable_at_reference(
4288 file,
4289 prepared.as_ref(),
4290 peer.source(),
4291 &declaration_guards,
4292 reference_guards_at_site().as_ref(),
4293 reference.start_byte(),
4294 )
4295 })
4296 });
4297 if directly_visible {
4298 if report_stats {
4299 eprintln!(
4300 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
4301 candidate.fq_name(),
4302 );
4303 }
4304 return true;
4305 }
4306 let complementary = self
4307 .visible_identifier_candidates(file, candidate.identifier())
4308 .filter(|peer| {
4309 peer.kind() == candidate.kind()
4310 && peer.fq_name() == candidate.fq_name()
4311 && peer.source() == candidate.source()
4312 })
4313 .collect::<Vec<_>>();
4314 let complementary_family =
4319 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
4320 let raw_candidate_branch_compatible = complementary_family
4321 && raw_reference_guards.as_ref().is_some_and(|active| {
4322 declaration_guard_requirements(analyzer, self.cpp, candidate)
4323 .iter()
4324 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4325 });
4326 if report_stats {
4327 eprintln!(
4328 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
4329 candidate.fq_name(),
4330 complementary.len(),
4331 complementary_family,
4332 raw_candidate_branch_compatible,
4333 );
4334 }
4335 let candidate_branch_compatible = raw_candidate_branch_compatible
4336 && reference_guards_at_site().as_ref().is_some_and(|active| {
4337 declaration_guard_requirements(analyzer, self.cpp, candidate)
4338 .iter()
4339 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4340 });
4341 let complementary_visible = candidate_branch_compatible
4342 && if candidate.source() == file {
4343 declaration_guard_requirements(analyzer, self.cpp, candidate)
4344 .iter()
4345 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
4346 } else {
4347 self.include_activation_for_source(
4348 self.cpp,
4349 file,
4350 prepared.as_ref(),
4351 candidate.source(),
4352 )
4353 .is_some_and(|activation| activation <= reference.start_byte())
4354 };
4355 if report_stats {
4356 eprintln!(
4357 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
4358 if complementary_visible {
4359 "complementary"
4360 } else {
4361 "missing"
4362 },
4363 candidate.fq_name(),
4364 );
4365 }
4366 complementary_visible
4367 }
4368
4369 pub fn is_exhaustive_same_fqn_type_declaration_family(
4370 &self,
4371 analyzer: &CppGraphSource<'_>,
4372 file: &ProjectFile,
4373 candidate: &CodeUnit,
4374 ) -> bool {
4375 let candidates = self
4376 .visible_identifier_candidates(file, candidate.identifier())
4377 .filter(|peer| {
4378 peer.kind() == candidate.kind()
4379 && peer.fq_name() == candidate.fq_name()
4380 && peer.source() == candidate.source()
4381 })
4382 .collect::<Vec<_>>();
4383 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
4384 }
4385
4386 pub fn dependent_member_pointer_alias_visible_in_context(
4401 &self,
4402 analyzer: &CppGraphSource<'_>,
4403 file: &ProjectFile,
4404 candidate: &CodeUnit,
4405 owner_components: &[String],
4406 reference: Node<'_>,
4407 ) -> bool {
4408 if !analyzer
4409 .type_alias_provider()
4410 .is_some_and(|provider| provider.is_type_alias(candidate))
4411 {
4412 return false;
4413 }
4414 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
4415 return false;
4416 };
4417 if terminal != candidate.identifier()
4418 || canonical_cpp_scope_components(candidate) != owner_components
4419 {
4420 return false;
4421 }
4422 let Some(expected_parent_fq_name) =
4423 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
4424 else {
4425 return false;
4426 };
4427 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
4428 return false;
4429 };
4430 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
4431 || parent_anchor.source() != candidate.source()
4432 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
4433 {
4434 return false;
4435 }
4436
4437 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
4443 || candidate.source() == file
4444 && !analyzer
4445 .ranges(candidate)
4446 .iter()
4447 .any(|range| range.start_byte < reference.start_byte())
4448 {
4449 return false;
4450 }
4451
4452 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
4453 if candidate_guards.is_empty() {
4454 return false;
4455 }
4456 let same_guard_sets =
4457 |left: &[(usize, HashSet<PreprocessorGuard>)],
4458 right: &[(usize, HashSet<PreprocessorGuard>)]| {
4459 left.iter().all(|(_, left_guards)| {
4460 right
4461 .iter()
4462 .any(|(_, right_guards)| left_guards == right_guards)
4463 })
4464 };
4465 let parent_candidates = self
4466 .visible_identifier_candidates(file, parent_anchor.identifier())
4467 .filter(|peer| {
4468 peer.kind() == parent_anchor.kind()
4469 && peer.fq_name() == expected_parent_fq_name.as_str()
4470 && peer.source() == parent_anchor.source()
4471 && canonical_cpp_scope_components(peer) == owner_prefix
4472 })
4473 .filter_map(|peer| {
4474 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
4475 (candidate_guards.len() == parent_guards.len()
4476 && same_guard_sets(&candidate_guards, &parent_guards)
4477 && same_guard_sets(&parent_guards, &candidate_guards))
4478 .then(|| (peer.clone(), parent_guards))
4479 })
4480 .collect::<Vec<_>>();
4481 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
4482 return false;
4483 };
4484
4485 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4486 return false;
4487 };
4488 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
4489 else {
4490 return false;
4491 };
4492 if !candidate_guards.iter().any(|(_, target_guards)| {
4497 guards_compatible_at_reference(target_guards, Some(&reference_guards))
4498 && (candidate.source() != file
4499 || self.preprocessor_guards_stable_between(
4500 file,
4501 0,
4502 reference.start_byte(),
4503 target_guards,
4504 ))
4505 }) {
4506 return false;
4507 }
4508
4509 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
4510 }
4511
4512 pub fn external_type_candidate_guard_compatible_in_context(
4522 &self,
4523 analyzer: &CppGraphSource<'_>,
4524 file: &ProjectFile,
4525 candidate: &CodeUnit,
4526 reference: Node<'_>,
4527 ) -> bool {
4528 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4529 return false;
4530 };
4531 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4532
4533 self.visible_identifier_candidates(file, candidate.identifier())
4534 .filter(|peer| same_logical_symbol(candidate, peer))
4535 .any(|peer| {
4536 declaration_guard_requirements(analyzer, self.cpp, peer)
4537 .into_iter()
4538 .any(|(declaration_byte, declaration_guards)| {
4539 if peer.source() == file {
4540 let (start, end) = if declaration_byte <= reference.start_byte() {
4541 (declaration_byte, reference.start_byte())
4542 } else {
4543 (reference.start_byte(), declaration_byte)
4544 };
4545 return guard_requirements_hold_at_reference(
4546 &declaration_guards,
4547 reference_guards.as_ref(),
4548 ) && self.preprocessor_guards_stable_between(
4549 file,
4550 start,
4551 end,
4552 &declaration_guards,
4553 );
4554 }
4555 self.foreign_declaration_reachable_at_reference(
4556 file,
4557 prepared.as_ref(),
4558 peer.source(),
4559 &declaration_guards,
4560 reference_guards.as_ref(),
4561 reference.start_byte(),
4562 )
4563 })
4564 })
4565 }
4566
4567 pub fn same_file_callable_guard_compatible_ignoring_order(
4575 &self,
4576 analyzer: &CppGraphSource<'_>,
4577 file: &ProjectFile,
4578 candidate: &CodeUnit,
4579 reference: Node<'_>,
4580 ) -> bool {
4581 if candidate.source() != file || !candidate.is_callable() {
4582 return false;
4583 }
4584 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4585 return false;
4586 };
4587 let guards = OnceCell::new();
4588 let context = CallableReferenceContext {
4589 file,
4590 position: Some(CallableReferencePosition {
4591 prepared: prepared.as_ref(),
4592 byte: reference.start_byte(),
4593 guards: &guards,
4594 }),
4595 };
4596 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
4597 .into_iter()
4598 .any(|declaration| {
4599 callable_preprocessor_context_is_visible_for_reference(
4600 declaration,
4601 prepared.source(),
4602 &context,
4603 )
4604 })
4605 }
4606
4607 pub fn type_candidate_may_be_visible_before_reference(
4608 &self,
4609 analyzer: &CppGraphSource<'_>,
4610 file: &ProjectFile,
4611 candidate: &CodeUnit,
4612 reference_byte: usize,
4613 ) -> bool {
4614 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4615 return false;
4616 };
4617 let root = prepared.tree().root_node();
4618 let end_byte = reference_byte
4619 .saturating_add(1)
4620 .min(prepared.source().len());
4621 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
4622 return false;
4623 };
4624 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
4625 }
4626
4627 pub fn preprocessor_guards_stable_between(
4628 &self,
4629 file: &ProjectFile,
4630 start_byte: usize,
4631 end_byte: usize,
4632 guards: &HashSet<PreprocessorGuard>,
4633 ) -> bool {
4634 if guards.is_empty() || start_byte >= end_byte {
4635 return true;
4636 }
4637 let cell = self.macro_event_cell(file);
4638 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4639 let mut visited = HashSet::from_iter([file.clone()]);
4640 !events.iter().any(|event| {
4641 event.byte() >= start_byte
4642 && event.byte() < end_byte
4643 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
4644 })
4645 }
4646
4647 fn macro_event_may_mutate_guards(
4648 &self,
4649 event: &MacroEvent,
4650 guards: &HashSet<PreprocessorGuard>,
4651 visited: &mut HashSet<ProjectFile>,
4652 ) -> bool {
4653 match event {
4654 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
4655 guards.iter().any(|guard| guard.may_depend_on_macro(name))
4656 }
4657 MacroEvent::Include { targets, .. } => {
4658 targets.is_empty()
4659 || targets
4660 .iter()
4661 .any(|target| self.source_may_mutate_guards(target, guards, visited))
4662 }
4663 MacroEvent::Invalidate { .. } => true,
4664 }
4665 }
4666
4667 fn source_may_mutate_guards(
4668 &self,
4669 file: &ProjectFile,
4670 guards: &HashSet<PreprocessorGuard>,
4671 visited: &mut HashSet<ProjectFile>,
4672 ) -> bool {
4673 if !visited.insert(file.clone()) {
4674 return false;
4675 }
4676 let cell = self.macro_event_cell(file);
4677 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4678 events
4679 .iter()
4680 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
4681 }
4682
4683 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
4684 let normalized = normalize_reference_name(raw_name)?;
4685 self.type_candidates(file, &normalized)
4686 .into_iter()
4687 .next()
4688 .cloned()
4689 }
4690
4691 pub fn unique_visible_parameter_type_fallback(
4700 &self,
4701 analyzer: &CppGraphSource<'_>,
4702 file: &ProjectFile,
4703 node: Node<'_>,
4704 source: &str,
4705 ) -> Option<CodeUnit> {
4706 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
4707 return None;
4708 }
4709 let name = node_text(node, source);
4710 let candidates = self
4711 .visible_identifier_candidates(file, name)
4712 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
4713 .filter(|candidate| {
4714 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
4715 })
4716 .collect::<Vec<_>>();
4717 self.unique_canonical_type_candidate(analyzer, file, &candidates)
4718 }
4719
4720 pub fn resolve_type_node_result(
4721 &self,
4722 file: &ProjectFile,
4723 node: Node<'_>,
4724 source: &str,
4725 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
4726 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
4727 return Ok(None);
4728 };
4729 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4730 return Ok(Some(primary));
4731 };
4732 self.resolve_template_arguments(file, primary, &arguments)
4733 .map(Some)
4734 }
4735
4736 pub fn resolve_type_node_primary(
4737 &self,
4738 file: &ProjectFile,
4739 node: Node<'_>,
4740 source: &str,
4741 ) -> Option<CodeUnit> {
4742 let components = cpp_type_name_components(node, source)?;
4743 self.resolve_type(file, &components.join("::"))
4744 }
4745
4746 pub fn resolve_template_arguments(
4747 &self,
4748 file: &ProjectFile,
4749 primary: CodeUnit,
4750 arguments: &[CppTemplateExpression],
4751 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4752 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
4753 }
4754
4755 fn resolve_template_arguments_inner(
4756 &self,
4757 file: &ProjectFile,
4758 primary: CodeUnit,
4759 arguments: &[CppTemplateExpression],
4760 seen_aliases: &mut HashSet<CodeUnit>,
4761 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4762 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
4763 && let Some(alias_target) = &metadata.alias_target
4764 {
4765 if !seen_aliases.insert(primary.clone()) {
4766 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
4767 }
4768 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
4769 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
4770 let target_name = alias_target.components.join("::");
4771 let target_primary = if alias_target.global {
4772 unique_logical_type_candidate(self.type_candidates(file, &target_name))
4773 } else {
4774 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
4775 };
4776 let Some(target_primary) = target_primary else {
4777 return Ok(primary);
4781 };
4782 let Some(target_arguments) = &alias_target.arguments else {
4783 return Ok(target_primary);
4784 };
4785 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
4786 .ok_or(CppTemplateResolutionError::Substitution)?;
4787 return self.resolve_template_arguments_inner(
4788 file,
4789 target_primary,
4790 &target_arguments,
4791 seen_aliases,
4792 );
4793 }
4794
4795 let primary_fq_name = self
4796 .cpp_template_metadata
4797 .get(&primary)
4798 .map(|metadata| metadata.primary_fq_name.clone())
4799 .unwrap_or_else(|| primary.fq_name());
4800 let has_specialization_metadata = self
4801 .cpp_template_families
4802 .get(&primary_fq_name)
4803 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
4804 if !has_specialization_metadata {
4805 return Ok(primary);
4806 }
4807 self.select_template_specialization(file, &primary, arguments)
4808 }
4809
4810 fn select_template_specialization(
4811 &self,
4812 file: &ProjectFile,
4813 resolved: &CodeUnit,
4814 explicit_arguments: &[CppTemplateExpression],
4815 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4816 let primary_fq_name = self
4817 .cpp_template_metadata
4818 .get(resolved)
4819 .map(|metadata| metadata.primary_fq_name.clone())
4820 .unwrap_or_else(|| resolved.fq_name());
4821 let family = self
4822 .cpp_template_families
4823 .get(&primary_fq_name)
4824 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4825 let primary_candidates = family
4826 .iter()
4827 .filter_map(|unit| {
4828 let metadata = self.cpp_template_metadata.get(unit)?;
4829 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
4830 })
4831 .collect::<Vec<_>>();
4832 let primary_unit = primary_candidates
4833 .iter()
4834 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
4835 .or_else(|| {
4836 primary_candidates
4837 .iter()
4838 .map(|(unit, _)| *unit)
4839 .min_by_key(|unit| {
4840 (
4841 unit.source().to_string(),
4842 unit.signature().unwrap_or_default(),
4843 )
4844 })
4845 })
4846 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4847 let primary_parameters =
4848 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
4849 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4850 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
4851 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
4852
4853 let mut applicable = Vec::new();
4854 for unit in family {
4855 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
4856 continue;
4857 };
4858 if metadata.is_primary() || !self.is_visible(file, unit) {
4859 continue;
4860 }
4861 if !cpp_specialization_matches(metadata, &expanded) {
4862 continue;
4863 }
4864 applicable.push((unit, metadata));
4865 }
4866 if applicable.is_empty() {
4867 return Ok(primary_unit.clone());
4868 }
4869
4870 let winners = applicable
4875 .iter()
4876 .filter(|(candidate, candidate_metadata)| {
4877 applicable.iter().all(|(other, other_metadata)| {
4878 same_visible_symbol(candidate, other)
4879 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
4880 })
4881 })
4882 .copied()
4883 .collect::<Vec<_>>();
4884 let Some((selected, _)) = winners.first() else {
4885 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4888 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
4889 });
4890 };
4891 if winners
4892 .iter()
4893 .any(|(unit, _)| !same_visible_symbol(unit, selected))
4894 {
4895 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4896 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
4897 });
4898 }
4899 Ok((*selected).clone())
4900 }
4901
4902 pub fn resolve_type_components_lexically(
4903 &self,
4904 analyzer: &CppGraphSource<'_>,
4905 file: &ProjectFile,
4906 components: &[String],
4907 global: bool,
4908 lexical_scope: &[String],
4909 ) -> LexicalTypeResolution {
4910 self.resolve_type_components_lexically_inner(
4911 analyzer,
4912 file,
4913 components,
4914 global,
4915 lexical_scope,
4916 TypeCandidateResolution::Canonical,
4917 )
4918 }
4919
4920 pub fn resolve_type_components_lexically_for_forward(
4921 &self,
4922 analyzer: &CppGraphSource<'_>,
4923 file: &ProjectFile,
4924 components: &[String],
4925 global: bool,
4926 lexical_scope: &[String],
4927 ) -> LexicalTypeResolution {
4928 self.resolve_type_components_lexically_inner(
4929 analyzer,
4930 file,
4931 components,
4932 global,
4933 lexical_scope,
4934 TypeCandidateResolution::PreserveAlias,
4935 )
4936 }
4937
4938 pub fn resolve_type_components_lexically_for_target(
4939 &self,
4940 analyzer: &CppGraphSource<'_>,
4941 file: &ProjectFile,
4942 components: &[String],
4943 global: bool,
4944 lexical_scope: &[String],
4945 target: &CodeUnit,
4946 ) -> LexicalTypeResolution {
4947 #[cfg(any(test, feature = "test-support"))]
4948 self.target_preserving_type_resolution_count
4949 .fetch_add(1, Ordering::Relaxed);
4950 self.resolve_type_components_lexically_inner(
4951 analyzer,
4952 file,
4953 components,
4954 global,
4955 lexical_scope,
4956 TypeCandidateResolution::PreserveTarget(target),
4957 )
4958 }
4959
4960 pub fn coarse_unqualified_type_reference_may_resolve(
4961 &self,
4962 file: &ProjectFile,
4963 name: &str,
4964 ) -> bool {
4965 if name.is_empty() {
4966 return true;
4967 }
4968 self.visible_identifier_candidates(file, name)
4969 .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
4970 || self.visible_parser_alias_name_is_visible(file, name)
4971 }
4972
4973 #[allow(clippy::too_many_arguments)]
4974 pub fn structured_type_reference_may_resolve_to_target(
4975 &self,
4976 analyzer: &CppGraphSource<'_>,
4977 file: &ProjectFile,
4978 components: &[String],
4979 global: bool,
4980 lexical_scope: &[String],
4981 target: &CodeUnit,
4982 ) -> bool {
4983 if components.is_empty() {
4984 return true;
4985 }
4986 let Some(terminal) = components.last() else {
4987 return true;
4988 };
4989 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
4990 .map(|qualified| qualified.join("::"))
4991 .collect::<Vec<_>>();
4992 let target_name = cpp_name_for(target);
4993 if qualified_tiers
4994 .iter()
4995 .any(|qualified| qualified == &target_name)
4996 {
4997 return true;
4998 }
4999
5000 let mut saw_shape_candidate = false;
5001 for candidate in self.visible_identifier_candidates(file, terminal) {
5002 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
5003 {
5004 continue;
5005 }
5006 let candidate_name = cpp_name_for(candidate);
5007 let shape_matches = if global || components.len() > 1 {
5008 qualified_tiers
5009 .iter()
5010 .any(|qualified| qualified == &candidate_name)
5011 } else {
5012 true
5013 };
5014 if !shape_matches {
5015 continue;
5016 }
5017 saw_shape_candidate = true;
5018 if same_visible_symbol(candidate, target)
5019 || self.compatible_primary_template_redeclarations(candidate, target)
5020 || (declared_type_alias(analyzer, candidate)
5021 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
5022 {
5023 return true;
5024 }
5025 }
5026
5027 !saw_shape_candidate
5028 }
5029
5030 pub fn target_preserving_reference_namespace(
5031 &self,
5032 analyzer: &CppGraphSource<'_>,
5033 file: &ProjectFile,
5034 identifier: &str,
5035 target: &CodeUnit,
5036 ) -> Option<Vec<String>> {
5037 let mut namespace = None;
5038 for candidate in self.visible_identifier_candidates(file, identifier) {
5039 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
5040 {
5041 continue;
5042 }
5043 if !(same_visible_symbol(candidate, target)
5044 || self.compatible_primary_template_redeclarations(candidate, target)
5045 || declared_type_alias(analyzer, candidate)
5046 && self.structured_alias_primary_preserves_target(
5047 analyzer, file, candidate, target,
5048 ))
5049 {
5050 continue;
5051 }
5052 if namespace
5053 .as_ref()
5054 .is_some_and(|existing| existing != candidate.package_name())
5055 {
5056 return None;
5057 }
5058 namespace = Some(candidate.package_name().to_string());
5059 }
5060 let namespace = namespace?;
5061 Some(
5062 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5063 brokk_bifrost_core::analyzer::Language::Cpp,
5064 &namespace,
5065 ),
5066 )
5067 }
5068
5069 pub fn resolve_imported_type_candidate(
5070 &self,
5071 analyzer: &CppGraphSource<'_>,
5072 file: &ProjectFile,
5073 target: &CodeUnit,
5074 target_components: &[String],
5075 direct_target: Option<&CodeUnit>,
5076 preserve_alias: bool,
5077 ) -> LexicalTypeResolution {
5078 let candidates = [target];
5079 let resolution = if preserve_alias {
5080 TypeCandidateResolution::PreserveAlias
5081 } else {
5082 direct_target.map_or(
5083 TypeCandidateResolution::Canonical,
5084 TypeCandidateResolution::PreserveTarget,
5085 )
5086 };
5087 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5091 Ok(unit) => LexicalTypeResolution::Resolved {
5092 unit,
5093 components: target_components.to_vec(),
5094 candidates: vec![target.clone()],
5095 },
5096 Err(failure) => failure.lexical_resolution(),
5097 }
5098 }
5099
5100 fn resolve_type_components_lexically_inner(
5101 &self,
5102 analyzer: &CppGraphSource<'_>,
5103 file: &ProjectFile,
5104 components: &[String],
5105 global: bool,
5106 lexical_scope: &[String],
5107 resolution: TypeCandidateResolution<'_>,
5108 ) -> LexicalTypeResolution {
5109 if components.is_empty() {
5110 return LexicalTypeResolution::Missing;
5111 }
5112 let mut injected = self.resolve_injected_class_name(
5122 analyzer,
5123 file,
5124 components,
5125 global,
5126 lexical_scope,
5127 resolution,
5128 );
5129 for qualified in lexical_component_tiers(components, global, lexical_scope) {
5130 let prefix_len = qualified.len().saturating_sub(components.len());
5131 if injected
5132 .as_ref()
5133 .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
5134 {
5135 return injected
5136 .take()
5137 .expect("injected class resolution was just present")
5138 .1;
5139 }
5140 let qualified_name = qualified.join("::");
5141 let candidates = self
5142 .type_candidates(file, &qualified_name)
5143 .into_iter()
5144 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5145 .collect::<Vec<_>>();
5146 if candidates.is_empty() {
5147 if !global && components.len() == 1 {
5148 match self.resolve_inherited_type_for_lexical_scope(
5149 analyzer,
5150 file,
5151 &qualified[..prefix_len],
5152 &components[0],
5153 resolution,
5154 ) {
5155 LexicalTypeResolution::Missing => {}
5156 inherited => return inherited,
5157 }
5158 }
5159 continue;
5160 }
5161 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5162 Ok(unit) => unit,
5163 Err(failure) => return failure.lexical_resolution(),
5164 };
5165 return LexicalTypeResolution::Resolved {
5166 unit,
5167 components: qualified,
5168 candidates: candidates.into_iter().cloned().collect(),
5169 };
5170 }
5171 LexicalTypeResolution::Missing
5172 }
5173
5174 fn resolve_injected_class_name(
5175 &self,
5176 analyzer: &CppGraphSource<'_>,
5177 file: &ProjectFile,
5178 components: &[String],
5179 global: bool,
5180 lexical_scope: &[String],
5181 resolution: TypeCandidateResolution<'_>,
5182 ) -> Option<(usize, LexicalTypeResolution)> {
5183 if global
5184 || components.len() != 1
5185 || file.rel_path().extension().is_some_and(|ext| ext == "c")
5186 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
5187 {
5188 return None;
5189 }
5190 let name = components.first()?;
5191 let mut matches: Vec<&CodeUnit> = Vec::new();
5192 let mut owner_len = 0;
5193 for candidate in self.visible_identifier_candidates(file, name) {
5194 if !candidate.is_class()
5195 || declared_type_alias(analyzer, candidate)
5196 || candidate.identifier() != name
5197 {
5198 continue;
5199 }
5200 let candidate_scope = canonical_cpp_scope_components(candidate);
5201 if candidate_scope.len() > lexical_scope.len()
5202 || !lexical_scope.starts_with(&candidate_scope)
5203 || candidate_scope.last().is_none_or(|last| last != name)
5204 {
5205 continue;
5206 }
5207 if candidate_scope.len() > owner_len {
5208 owner_len = candidate_scope.len();
5209 matches.clear();
5210 }
5211 if candidate_scope.len() == owner_len
5212 && !matches
5213 .iter()
5214 .any(|existing| same_logical_symbol(existing, candidate))
5215 {
5216 matches.push(candidate);
5217 }
5218 }
5219 if matches.is_empty() {
5220 return None;
5221 }
5222 if owner_len >= lexical_scope.len() {
5230 return None;
5231 }
5232 let owner_components = lexical_scope[..owner_len].to_vec();
5233 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
5234 Ok(unit) => LexicalTypeResolution::Resolved {
5235 unit,
5236 components: owner_components,
5237 candidates: matches.into_iter().cloned().collect(),
5238 },
5239 Err(failure) => failure.lexical_resolution(),
5240 };
5241 Some((owner_len, resolution))
5242 }
5243
5244 fn resolve_inherited_type_for_lexical_scope(
5245 &self,
5246 analyzer: &CppGraphSource<'_>,
5247 file: &ProjectFile,
5248 lexical_scope: &[String],
5249 name: &str,
5250 resolution: TypeCandidateResolution<'_>,
5251 ) -> LexicalTypeResolution {
5252 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5253 return LexicalTypeResolution::Missing;
5254 };
5255 let lexical_owner_name = lexical_scope.join("::");
5256 if lexical_owner_name.is_empty() {
5257 return LexicalTypeResolution::Missing;
5258 }
5259 let owner_candidates = self
5260 .type_candidates(file, &lexical_owner_name)
5261 .into_iter()
5262 .filter(|candidate| {
5263 canonical_cpp_name_matches(candidate, &lexical_owner_name)
5264 && !declared_type_alias(analyzer, candidate)
5265 })
5266 .collect::<Vec<_>>();
5267 if owner_candidates.is_empty() {
5268 return LexicalTypeResolution::Missing;
5269 }
5270 let physical_owner_candidates = owner_candidates
5275 .iter()
5276 .copied()
5277 .filter(|candidate| candidate.source() == file)
5278 .collect::<Vec<_>>();
5279 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
5280 owner_candidates
5281 } else {
5282 physical_owner_candidates
5283 };
5284 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
5285 return LexicalTypeResolution::Ambiguous;
5286 };
5287
5288 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
5289 let mut visited_owners = HashSet::default();
5290 while !frontier.is_empty() {
5291 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
5292 let mut next_frontier = Vec::new();
5293 for owner in frontier {
5294 if !visited_owners.insert(owner.fq_name()) {
5295 continue;
5296 }
5297 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
5298 let candidates = self
5299 .type_candidates(file, &qualified_name)
5300 .into_iter()
5301 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5302 .collect::<Vec<_>>();
5303 if candidates.is_empty() {
5304 for ancestor in hierarchy.get_direct_ancestors(&owner) {
5305 if !next_frontier
5306 .iter()
5307 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
5308 {
5309 next_frontier.push(ancestor);
5310 }
5311 }
5312 continue;
5313 }
5314 let unit =
5315 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5316 Ok(unit) => unit,
5317 Err(failure) => return failure.lexical_resolution(),
5318 };
5319 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
5320 }
5321 if let Some((unit, candidates)) = level_matches.first().cloned() {
5322 let Some(first_declaration) = candidates.first() else {
5323 return LexicalTypeResolution::Ambiguous;
5324 };
5325 if !level_matches.iter().all(|(_, declarations)| {
5326 declarations
5327 .iter()
5328 .all(|declaration| same_logical_symbol(first_declaration, declaration))
5329 }) {
5330 return LexicalTypeResolution::Ambiguous;
5331 }
5332 let mut components = lexical_scope.to_vec();
5333 components.push(name.to_string());
5334 return LexicalTypeResolution::Resolved {
5335 unit,
5336 components,
5337 candidates,
5338 };
5339 }
5340 frontier = next_frontier;
5341 }
5342 LexicalTypeResolution::Missing
5343 }
5344
5345 pub fn inherited_injected_class_owner(
5362 &self,
5363 analyzer: &CppGraphSource<'_>,
5364 file: &ProjectFile,
5365 enclosing_owner: &CodeUnit,
5366 injected_name: &str,
5367 ) -> Option<CodeUnit> {
5368 let hierarchy = analyzer.type_hierarchy_provider()?;
5369 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
5370 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5371 while !frontier.is_empty() {
5372 let mut level_matches = Vec::new();
5373 let mut next_frontier = Vec::new();
5374 for raw_owner in frontier {
5375 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
5376 else {
5377 if raw_owner.identifier() == injected_name {
5378 return None;
5379 }
5380 continue;
5381 };
5382 let propagated = propagated_counts.entry(owner.clone()).or_default();
5383 if *propagated == 2 {
5384 continue;
5385 }
5386 *propagated += 1;
5387 if owner.identifier() == injected_name {
5388 level_matches.push(owner.clone());
5389 }
5390 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
5391 }
5392 match level_matches.as_slice() {
5393 [owner] => return Some(owner.clone()),
5394 [_, ..] => return None,
5395 [] => {}
5396 }
5397 frontier = next_frontier;
5398 }
5399 None
5400 }
5401
5402 fn resolve_type_candidates(
5407 &self,
5408 analyzer: &CppGraphSource<'_>,
5409 file: &ProjectFile,
5410 candidates: &[&CodeUnit],
5411 resolution: TypeCandidateResolution<'_>,
5412 ) -> Result<CodeUnit, TypeCandidateFailure> {
5413 match resolution {
5414 TypeCandidateResolution::Canonical => {
5415 self.canonical_type_candidate_resolution(analyzer, file, candidates)
5416 }
5417 TypeCandidateResolution::PreserveAlias => {
5418 let same_fqn_alias_family = candidates.len() > 1
5425 && candidates.iter().all(|candidate| {
5426 declared_type_alias(analyzer, candidate)
5427 && same_logical_symbol(candidates[0], candidate)
5428 })
5429 && candidates
5430 .iter()
5431 .any(|candidate| candidate.source() != candidates[0].source());
5432 if same_fqn_alias_family {
5433 let physically_visible = candidates
5434 .iter()
5435 .copied()
5436 .filter(|candidate| self.is_physically_visible(file, candidate))
5437 .collect::<Vec<_>>();
5438 let one_structured_target = physically_visible.len() > 1
5448 && physically_visible.iter().skip(1).all(|candidate| {
5449 let target = self.structured_alias_target(analyzer, candidate);
5450 target.is_some()
5451 && target
5452 == self.structured_alias_target(analyzer, physically_visible[0])
5453 });
5454 if physically_visible.len() == 1 || one_structured_target {
5455 return Ok(physically_visible[0].clone());
5456 }
5457 }
5458 unique_type_candidate_preserving_alias(analyzer, candidates)
5459 .ok_or(TypeCandidateFailure::Ambiguous)
5460 }
5461 TypeCandidateResolution::PreserveTarget(target) => self
5462 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
5463 .ok_or(TypeCandidateFailure::Ambiguous),
5464 }
5465 }
5466
5467 pub fn resolve_callable_value_components_lexically(
5468 &self,
5469 analyzer: &CppGraphSource<'_>,
5470 file: &ProjectFile,
5471 owner_components: &[String],
5472 member_name: &str,
5473 global: bool,
5474 lexical_scope: &[String],
5475 ) -> LexicalCallableValueResolution {
5476 if owner_components.is_empty() || member_name.is_empty() {
5477 return LexicalCallableValueResolution::Missing;
5478 }
5479 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
5480 let owner_name = qualified_owner.join("::");
5481 let type_candidates = self
5482 .type_candidates(file, &owner_name)
5483 .into_iter()
5484 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
5485 .collect::<Vec<_>>();
5486 let resolved_type = if type_candidates.is_empty() {
5487 None
5488 } else {
5489 let Some(unit) =
5490 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
5491 else {
5492 return LexicalCallableValueResolution::Ambiguous;
5493 };
5494 Some(unit)
5495 };
5496
5497 let mut qualified_callable = qualified_owner;
5498 qualified_callable.push(member_name.to_string());
5499 let callable_name = qualified_callable.join("::");
5500 let free_function = self
5501 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
5502 .into_iter()
5503 .find(|candidate| {
5504 canonical_cpp_name_matches(candidate, &callable_name)
5505 && type_owner_of(analyzer, candidate).is_none()
5506 })
5507 .cloned();
5508
5509 match (resolved_type, free_function) {
5510 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
5511 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
5512 (None, Some(function)) => {
5513 return LexicalCallableValueResolution::FreeFunction(function);
5514 }
5515 (None, None) => {}
5516 }
5517 }
5518 LexicalCallableValueResolution::Missing
5519 }
5520
5521 fn resolve_type_for_declaration(
5522 &self,
5523 visible_from: &ProjectFile,
5524 declaration: &CodeUnit,
5525 raw_name: &str,
5526 ) -> Option<CodeUnit> {
5527 let normalized = normalize_reference_name(raw_name)?;
5528 if !normalized.contains("::")
5529 && let Some(namespace) = cpp_namespace_for(declaration)
5530 {
5531 for prefix in namespace_prefixes(&namespace) {
5532 let qualified = format!("{prefix}::{normalized}");
5533 if let Some(unit) = self
5534 .type_candidates(visible_from, &qualified)
5535 .into_iter()
5536 .next()
5537 {
5538 return Some(unit.clone());
5539 }
5540 }
5541 }
5542 self.resolve_type(visible_from, raw_name)
5543 }
5544
5545 fn resolve_unique_canonical_type_for_declaration(
5546 &self,
5547 analyzer: &CppGraphSource<'_>,
5548 visible_from: &ProjectFile,
5549 declaration: &CodeUnit,
5550 raw_name: &str,
5551 ) -> Option<CodeUnit> {
5552 let mut current =
5553 self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
5554 let mut seen_aliases = HashSet::default();
5555 loop {
5556 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
5557 return current.is_class().then_some(current);
5558 };
5559 if matches!(target, StructuredAliasTarget::Builtin) {
5560 return current.is_class().then_some(current);
5561 }
5562 if !seen_aliases.insert(current.clone()) {
5563 return None;
5564 }
5565 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
5566 }
5567 }
5568
5569 pub fn canonical_type_unit(
5570 &self,
5571 analyzer: &CppGraphSource<'_>,
5572 visible_from: &ProjectFile,
5573 unit: &CodeUnit,
5574 ) -> Option<CodeUnit> {
5575 self.canonical_type_resolution(analyzer, visible_from, unit)
5576 .ok()
5577 }
5578
5579 fn canonical_type_resolution(
5587 &self,
5588 analyzer: &CppGraphSource<'_>,
5589 visible_from: &ProjectFile,
5590 unit: &CodeUnit,
5591 ) -> Result<CodeUnit, TypeCandidateFailure> {
5592 let mut current = unit.clone();
5593 let mut seen_aliases = HashSet::default();
5594 loop {
5595 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
5596 return current
5597 .is_class()
5598 .then_some(current)
5599 .ok_or(TypeCandidateFailure::Unresolvable);
5600 };
5601 if matches!(target, StructuredAliasTarget::Builtin) {
5602 return current
5603 .is_class()
5604 .then_some(current)
5605 .ok_or(TypeCandidateFailure::Unresolvable);
5606 }
5607 if !seen_aliases.insert(current.clone()) {
5608 return Err(TypeCandidateFailure::Unresolvable);
5609 }
5610 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
5611 }
5612 }
5613
5614 pub fn canonical_visible_full_type_unit(
5615 &self,
5616 analyzer: &CppGraphSource<'_>,
5617 visible_from: &ProjectFile,
5618 unit: &CodeUnit,
5619 ) -> Option<CodeUnit> {
5620 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
5621 if cpp_class_declaration_strength(analyzer, &canonical)
5622 != CppClassDeclarationStrength::Forward
5623 {
5624 return Some(canonical);
5625 }
5626 let mut full = Vec::new();
5627 for candidate in self
5628 .visible_identifier_candidates(visible_from, canonical.identifier())
5629 .filter(|candidate| {
5630 candidate.is_class()
5631 && candidate.fq_name() == canonical.fq_name()
5632 && cpp_class_declaration_strength(analyzer, candidate)
5633 == CppClassDeclarationStrength::Full
5634 })
5635 {
5636 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
5637 full.push(candidate.clone());
5638 }
5639 }
5640 match full.len() {
5641 0 => Some(canonical),
5642 1 => full.pop(),
5643 _ => None,
5644 }
5645 }
5646
5647 fn resolve_structured_alias_target(
5648 &self,
5649 visible_from: &ProjectFile,
5650 declaration: &CodeUnit,
5651 target: &StructuredAliasTarget,
5652 ) -> Option<CodeUnit> {
5653 self.structured_alias_target_resolution(visible_from, declaration, target)
5654 .ok()
5655 }
5656
5657 fn structured_alias_target_resolution(
5658 &self,
5659 visible_from: &ProjectFile,
5660 declaration: &CodeUnit,
5661 target: &StructuredAliasTarget,
5662 ) -> Result<CodeUnit, TypeCandidateFailure> {
5663 let primary =
5664 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
5665 let StructuredAliasTarget::Named { arguments, .. } = target else {
5666 return Err(TypeCandidateFailure::Unresolvable);
5667 };
5668 match arguments {
5669 Some(arguments) => self
5670 .resolve_template_arguments(visible_from, primary, arguments)
5671 .map_err(|error| match error {
5672 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
5673 TypeCandidateFailure::Ambiguous
5674 }
5675 _ => TypeCandidateFailure::Unresolvable,
5676 }),
5677 None => Ok(primary),
5678 }
5679 }
5680
5681 fn resolve_structured_alias_primary(
5682 &self,
5683 visible_from: &ProjectFile,
5684 declaration: &CodeUnit,
5685 target: &StructuredAliasTarget,
5686 ) -> Option<CodeUnit> {
5687 self.structured_alias_primary_resolution(visible_from, declaration, target)
5688 .ok()
5689 }
5690
5691 fn structured_alias_primary_resolution(
5692 &self,
5693 visible_from: &ProjectFile,
5694 declaration: &CodeUnit,
5695 target: &StructuredAliasTarget,
5696 ) -> Result<CodeUnit, TypeCandidateFailure> {
5697 let StructuredAliasTarget::Named {
5698 components, global, ..
5699 } = target
5700 else {
5701 return Err(TypeCandidateFailure::Unresolvable);
5702 };
5703 let qualified = components.join("::");
5704 let candidates = if *global {
5705 let mut candidates = self.type_candidates(visible_from, &qualified);
5712 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
5713 candidates
5714 } else {
5715 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
5716 };
5717 logical_type_candidate(candidates)
5718 }
5719
5720 pub fn structured_alias_primary_preserves_target(
5721 &self,
5722 analyzer: &CppGraphSource<'_>,
5723 visible_from: &ProjectFile,
5724 candidate: &CodeUnit,
5725 target: &CodeUnit,
5726 ) -> bool {
5727 let mut current = candidate.clone();
5728 let mut seen = HashSet::default();
5729 let mut matched_target = false;
5730 loop {
5731 if same_visible_symbol(¤t, target)
5732 || self.compatible_primary_template_redeclarations(¤t, target)
5733 {
5734 matched_target = true;
5735 }
5736 if !seen.insert(current.clone()) {
5737 return false;
5738 }
5739 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
5740 return matched_target;
5741 };
5742 if matches!(alias_target, StructuredAliasTarget::Builtin) {
5743 return matched_target;
5744 };
5745 let Some(primary) =
5746 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
5747 else {
5748 return matched_target;
5754 };
5755 current = primary;
5756 }
5757 }
5758
5759 pub fn structured_class_alias_resolves_to_target(
5760 &self,
5761 analyzer: &CppGraphSource<'_>,
5762 visible_from: &ProjectFile,
5763 alias: &CodeUnit,
5764 target: &CodeUnit,
5765 ) -> bool {
5766 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
5767 return false;
5768 };
5769 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
5770 return false;
5771 };
5772 let StructuredAliasTarget::Named {
5773 components, global, ..
5774 } = &alias_target
5775 else {
5776 return false;
5777 };
5778 let lexical_scope = canonical_cpp_scope_components(&owner);
5779 match self.resolve_type_components_lexically_for_target(
5780 analyzer,
5781 visible_from,
5782 components,
5783 *global,
5784 &lexical_scope,
5785 target,
5786 ) {
5787 LexicalTypeResolution::Resolved {
5788 unit, candidates, ..
5789 } => {
5790 same_visible_symbol(&unit, target)
5791 || self.same_template_member_identity(analyzer, &unit, target)
5792 || candidates.iter().any(|candidate| {
5793 same_visible_symbol(candidate, target)
5794 || self.same_template_member_identity(analyzer, candidate, target)
5795 })
5796 }
5797 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
5798 self.structured_alias_primary_preserves_target(
5799 analyzer,
5800 visible_from,
5801 alias,
5802 target,
5803 ) || self.flattened_macro_namespace_alias_target_matches(
5804 analyzer,
5805 visible_from,
5806 alias,
5807 &alias_target,
5808 target,
5809 )
5810 }
5811 }
5812 }
5813
5814 pub fn structured_class_alias_path_preserves_target(
5822 &self,
5823 analyzer: &CppGraphSource<'_>,
5824 visible_from: &ProjectFile,
5825 alias: &CodeUnit,
5826 target: &CodeUnit,
5827 ) -> bool {
5828 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
5829 return false;
5830 };
5831 let Some(StructuredAliasTarget::Named {
5832 components, global, ..
5833 }) = self.structured_alias_target(analyzer, alias)
5834 else {
5835 return false;
5836 };
5837 let lexical_scope = canonical_cpp_scope_components(&owner);
5838 (1..components.len()).rev().any(|component_count| {
5839 matches!(
5840 self.resolve_type_components_lexically_for_target(
5841 analyzer,
5842 visible_from,
5843 &components[..component_count],
5844 global,
5845 &lexical_scope,
5846 target,
5847 ),
5848 LexicalTypeResolution::Resolved {
5849 ref unit,
5850 ref candidates,
5851 ..
5852 } if same_visible_symbol(unit, target)
5853 || self.same_template_member_identity(analyzer, unit, target)
5854 || candidates.iter().any(|candidate| {
5855 same_visible_symbol(candidate, target)
5856 || self.same_template_member_identity(analyzer, candidate, target)
5857 })
5858 )
5859 })
5860 }
5861
5862 fn flattened_macro_namespace_alias_target_matches(
5863 &self,
5864 analyzer: &CppGraphSource<'_>,
5865 visible_from: &ProjectFile,
5866 alias: &CodeUnit,
5867 alias_target: &StructuredAliasTarget,
5868 target: &CodeUnit,
5869 ) -> bool {
5870 let StructuredAliasTarget::Named {
5871 components,
5872 global: false,
5873 arguments: None,
5874 } = alias_target
5875 else {
5876 return false;
5877 };
5878 let Some((target_name, namespace_components)) = components.split_last() else {
5879 return false;
5880 };
5881 if namespace_components.is_empty()
5882 || target_name != target.identifier()
5883 || alias.source() != target.source()
5884 || alias.source() != visible_from
5885 || !target.is_class()
5886 || declared_type_alias(analyzer, target)
5887 {
5888 return false;
5889 }
5890 if self
5891 .resolve_structured_alias_target(visible_from, alias, alias_target)
5892 .is_some()
5893 {
5894 return false;
5895 }
5896
5897 let alias_ranges = analyzer.ranges(alias);
5898 let target_ranges = analyzer.ranges(target);
5899 if alias_ranges.is_empty() || target_ranges.is_empty() {
5900 return false;
5901 }
5902 let alias_start = alias_ranges
5903 .iter()
5904 .map(|range| range.start_byte)
5905 .min()
5906 .expect("non-empty alias ranges have a minimum");
5907 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
5908 return false;
5909 };
5910 let root = prepared.tree().root_node();
5911 let has_matching_declaration = target_ranges
5912 .iter()
5913 .filter(|range| range.end_byte <= alias_start)
5914 .filter_map(|range| node_for_exact_range(root, range))
5915 .any(|node| {
5916 flattened_macro_namespace_components(node, prepared.source())
5917 .is_some_and(|recovered| recovered == namespace_components)
5918 });
5919 if !has_matching_declaration {
5920 return false;
5921 }
5922
5923 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
5924 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
5925 guard_requirement_sets_match(&alias_guards, &target_guards)
5926 }
5927
5928 pub fn template_alias_arguments_preserve_target(
5929 &self,
5930 analyzer: &CppGraphSource<'_>,
5931 visible_from: &ProjectFile,
5932 alias: &CodeUnit,
5933 arguments: &[CppTemplateExpression],
5934 target: &CodeUnit,
5935 ) -> bool {
5936 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
5937 return false;
5938 };
5939 if metadata.alias_target.is_none()
5940 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
5941 {
5942 return false;
5943 }
5944 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
5945 }
5946
5947 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
5948 self.cpp_template_metadata
5949 .get(unit)
5950 .is_some_and(CppTemplateMetadata::is_primary)
5951 }
5952
5953 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
5954 self.cpp_template_metadata
5955 .get(unit)
5956 .is_some_and(CppTemplateMetadata::is_specialization)
5957 }
5958
5959 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
5960 same_visible_symbol(left, right)
5961 || self.compatible_primary_template_redeclarations(left, right)
5962 }
5963
5964 pub fn same_template_member_identity(
5965 &self,
5966 analyzer: &CppGraphSource<'_>,
5967 left: &CodeUnit,
5968 right: &CodeUnit,
5969 ) -> bool {
5970 if same_visible_symbol(left, right) {
5971 return true;
5972 }
5973 if left.kind() != right.kind()
5974 || left.identifier() != right.identifier()
5975 || left.signature() != right.signature()
5976 {
5977 return false;
5978 }
5979 let (Some(left_owner), Some(right_owner)) =
5980 (analyzer.parent_of(left), analyzer.parent_of(right))
5981 else {
5982 return false;
5983 };
5984 left_owner.is_class()
5985 && right_owner.is_class()
5986 && self.same_template_owner_identity(&left_owner, &right_owner)
5987 }
5988
5989 fn unique_canonical_type_candidate(
5990 &self,
5991 analyzer: &CppGraphSource<'_>,
5992 visible_from: &ProjectFile,
5993 candidates: &[&CodeUnit],
5994 ) -> Option<CodeUnit> {
5995 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
5996 .ok()
5997 }
5998
5999 fn canonical_type_candidate_resolution(
6000 &self,
6001 analyzer: &CppGraphSource<'_>,
6002 visible_from: &ProjectFile,
6003 candidates: &[&CodeUnit],
6004 ) -> Result<CodeUnit, TypeCandidateFailure> {
6005 let mut canonical = Vec::new();
6006 for candidate in candidates {
6007 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
6008 if canonical
6009 .iter()
6010 .any(|existing| same_visible_symbol(existing, &resolved))
6011 {
6012 continue;
6013 }
6014 if let Some(existing) = canonical.iter_mut().find(|existing| {
6015 self.compatible_primary_template_redeclarations(existing, &resolved)
6016 }) {
6017 if matches!(
6026 (
6027 cpp_class_declaration_strength(analyzer, existing),
6028 cpp_class_declaration_strength(analyzer, &resolved),
6029 ),
6030 (
6031 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
6032 CppClassDeclarationStrength::Full,
6033 ) | (
6034 CppClassDeclarationStrength::Unknown,
6035 CppClassDeclarationStrength::Forward,
6036 )
6037 ) {
6038 *existing = resolved;
6039 }
6040 continue;
6041 }
6042 canonical.push(resolved);
6043 if canonical.len() > 1 {
6044 return Err(TypeCandidateFailure::Ambiguous);
6045 }
6046 }
6047 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
6048 }
6049
6050 pub fn unique_type_candidate_preserving_target(
6051 &self,
6052 analyzer: &CppGraphSource<'_>,
6053 visible_from: &ProjectFile,
6054 candidates: &[&CodeUnit],
6055 target: &CodeUnit,
6056 ) -> Option<CodeUnit> {
6057 if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
6068 return Some(target.clone());
6069 }
6070 let mut resolved_candidates = Vec::new();
6071 for candidate in candidates {
6072 let Some(resolved) =
6078 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
6079 else {
6080 continue;
6081 };
6082 if resolved_candidates
6083 .iter()
6084 .any(|existing| same_visible_symbol(existing, &resolved))
6085 {
6086 continue;
6087 }
6088 resolved_candidates.push(resolved);
6089 }
6090 match resolved_candidates.as_slice() {
6091 [] => None,
6092 [single] => Some(single.clone()),
6093 _ => self
6098 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
6099 .map(|_| target.clone()),
6100 }
6101 }
6102
6103 pub fn same_fqn_type_spelling_for_target<'b>(
6120 &self,
6121 analyzer: &CppGraphSource<'_>,
6122 visible_from: &ProjectFile,
6123 candidates: &[&'b CodeUnit],
6124 target: &CodeUnit,
6125 ) -> Option<&'b CodeUnit> {
6126 let [first, rest @ ..] = candidates else {
6127 return None;
6128 };
6129 if rest.is_empty()
6130 || !rest.iter().all(|candidate| {
6131 candidate.kind() == first.kind()
6132 && candidate.fq_name() == first.fq_name()
6133 && candidate.source() == first.source()
6134 })
6135 {
6136 return None;
6137 }
6138 candidates
6139 .iter()
6140 .copied()
6141 .find(|candidate| same_symbol(candidate, target))
6142 .or_else(|| {
6143 candidates.iter().copied().find(|candidate| {
6144 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
6145 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
6146 })
6147 })
6148 }
6149
6150 pub fn alternate_same_fqn_type_declarations(
6151 &self,
6152 analyzer: &CppGraphSource<'_>,
6153 candidates: &[&CodeUnit],
6154 target: &CodeUnit,
6155 ) -> bool {
6156 let Some(first) = candidates.first() else {
6157 return false;
6158 };
6159 let same_api = first.kind() == target.kind()
6160 && first.fq_name() == target.fq_name()
6161 && first.source() == target.source()
6162 && candidates.iter().all(|candidate| {
6163 candidate.kind() == target.kind()
6164 && candidate.fq_name() == target.fq_name()
6165 && candidate.source() == target.source()
6166 })
6167 && candidates
6168 .iter()
6169 .any(|candidate| same_symbol(candidate, target))
6170 && candidates
6171 .iter()
6172 .any(|candidate| !same_logical_symbol(candidate, target));
6173 if !same_api {
6174 return false;
6175 }
6176
6177 let requirements = candidates
6178 .iter()
6179 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
6180 .collect::<Vec<_>>();
6181 requirements.len() > 1
6182 && requirements
6183 .iter()
6184 .all(|requirement| !requirement.is_empty())
6185 && requirements.iter().enumerate().all(|(index, left)| {
6186 requirements[index + 1..].iter().all(|right| {
6187 left.iter().all(|(_, left_guards)| {
6188 right.iter().all(|(_, right_guards)| {
6189 merge_preprocessor_guards(left_guards, right_guards).is_none()
6190 })
6191 })
6192 })
6193 })
6194 }
6195
6196 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
6197 let mut pending = vec![terms.to_vec()];
6198 while let Some(branch_terms) = pending.pop() {
6199 let mut normalized = Vec::new();
6200 let mut covers_branch = false;
6201 for term in branch_terms {
6202 if term.iter().any(|guard| term.contains(&guard.negated())) {
6203 continue;
6204 }
6205 if term.is_empty() {
6206 covers_branch = true;
6207 break;
6208 }
6209 if !normalized.iter().any(|existing| existing == &term) {
6210 normalized.push(term);
6211 }
6212 }
6213 if covers_branch {
6214 continue;
6215 }
6216 let Some(split_guard) = normalized
6217 .iter()
6218 .flat_map(|term| term.iter())
6219 .next()
6220 .cloned()
6221 else {
6222 return false;
6223 };
6224 let negated_guard = split_guard.negated();
6225 let mut when_defined = Vec::new();
6226 let mut when_undefined = Vec::new();
6227 for term in normalized {
6228 if term.contains(&negated_guard) {
6229 } else if term.contains(&split_guard) {
6231 let mut reduced = term.clone();
6232 reduced.remove(&split_guard);
6233 when_defined.push(reduced);
6234 } else {
6235 when_defined.push(term.clone());
6236 }
6237 if term.contains(&split_guard) {
6238 } else if term.contains(&negated_guard) {
6240 let mut reduced = term;
6241 reduced.remove(&negated_guard);
6242 when_undefined.push(reduced);
6243 } else {
6244 when_undefined.push(term);
6245 }
6246 }
6247 pending.push(when_defined);
6248 pending.push(when_undefined);
6249 }
6250 true
6251 }
6252
6253 fn declarations_share_exhaustive_conditional_family(
6262 &self,
6263 analyzer: &CppGraphSource<'_>,
6264 candidates: &[&CodeUnit],
6265 ) -> Option<(usize, usize)> {
6266 let mut family_range = None;
6267 for candidate in candidates {
6268 let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
6269 let root = prepared.tree().root_node();
6270 let mut candidate_family = None;
6271 for range in analyzer.ranges(candidate) {
6272 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
6273 let family = preprocessor_conditional_family_for_declaration(node)?;
6274 let key = (family.start_byte(), family.end_byte());
6275 if candidate_family.is_some_and(|existing| existing != key) {
6276 return None;
6277 }
6278 candidate_family = Some(key);
6279 }
6280 let candidate_family = candidate_family?;
6281 if family_range.is_some_and(|existing| existing != candidate_family) {
6282 return None;
6283 }
6284 family_range = Some(candidate_family);
6285 }
6286 family_range
6287 }
6288
6289 pub fn complementary_same_fqn_type_declarations(
6290 &self,
6291 analyzer: &CppGraphSource<'_>,
6292 candidates: &[&CodeUnit],
6293 target: &CodeUnit,
6294 ) -> bool {
6295 if candidates.len() < 2
6296 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
6297 || self
6298 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
6299 .is_none()
6300 {
6301 return false;
6302 }
6303 Self::preprocessor_guard_terms_cover_all_paths(
6304 &self.declaration_family_guard_terms(analyzer, candidates),
6305 )
6306 }
6307
6308 fn declaration_family_guard_terms(
6309 &self,
6310 analyzer: &CppGraphSource<'_>,
6311 candidates: &[&CodeUnit],
6312 ) -> Vec<HashSet<PreprocessorGuard>> {
6313 candidates
6314 .iter()
6315 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
6316 .map(|(_, guards)| guards)
6317 .collect()
6318 }
6319
6320 fn exhaustive_guard_family_activation(
6336 &self,
6337 analyzer: &CppGraphSource<'_>,
6338 prepared: &PreparedSyntaxTree,
6339 candidate: &CodeUnit,
6340 reference: &CallableReferenceContext<'_>,
6341 ) -> Option<usize> {
6342 if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
6345 return None;
6346 }
6347 let family = self
6348 .visible_identifier_candidates(candidate.source(), candidate.identifier())
6349 .filter(|peer| {
6350 peer.kind() == candidate.kind()
6351 && peer.fq_name() == candidate.fq_name()
6352 && peer.source() == candidate.source()
6353 })
6354 .collect::<Vec<_>>();
6355 let (_, family_end) =
6356 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
6357 if !Self::preprocessor_guard_terms_cover_all_paths(
6358 &self.declaration_family_guard_terms(analyzer, &family),
6359 ) {
6360 return None;
6361 }
6362 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
6366 .iter()
6367 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
6368 {
6369 return None;
6370 }
6371 (first_declaration_byte(analyzer, candidate)?
6372 == family
6373 .iter()
6374 .filter_map(|peer| first_declaration_byte(analyzer, peer))
6375 .min()?)
6376 .then_some(family_end)
6377 }
6378
6379 fn type_candidate_preserving_target(
6380 &self,
6381 analyzer: &CppGraphSource<'_>,
6382 visible_from: &ProjectFile,
6383 candidate: &CodeUnit,
6384 target: &CodeUnit,
6385 ) -> Option<CodeUnit> {
6386 let mut current = candidate.clone();
6387 let mut matched_target = same_visible_symbol(¤t, target)
6388 || self.compatible_primary_template_redeclarations(¤t, target);
6389 let mut seen = HashSet::default();
6390 loop {
6391 if !seen.insert(current.clone()) {
6392 return None;
6393 }
6394 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
6395 return matched_target
6396 .then(|| target.clone())
6397 .or_else(|| current.is_class().then_some(current));
6398 };
6399 if self.flattened_macro_namespace_alias_target_matches(
6400 analyzer,
6401 visible_from,
6402 ¤t,
6403 &alias_target,
6404 target,
6405 ) {
6406 return Some(target.clone());
6407 }
6408 if matches!(alias_target, StructuredAliasTarget::Builtin) {
6409 return matched_target
6410 .then(|| target.clone())
6411 .or_else(|| current.is_class().then_some(current));
6412 }
6413 if !self.cpp_template_metadata.contains_key(¤t)
6421 && let Some(primary) =
6422 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
6423 && (same_visible_symbol(&primary, target)
6424 || self.compatible_primary_template_redeclarations(&primary, target))
6425 {
6426 return Some(target.clone());
6427 }
6428 if same_visible_symbol(¤t, target) {
6429 return Some(target.clone());
6430 }
6431 if self.cpp_template_metadata.contains_key(¤t) {
6432 return None;
6433 }
6434 let Some(next) =
6435 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
6436 else {
6437 return matched_target.then(|| target.clone());
6438 };
6439 current = next;
6440 matched_target |= same_visible_symbol(¤t, target)
6441 || self.compatible_primary_template_redeclarations(¤t, target);
6442 }
6443 }
6444
6445 fn compatible_primary_template_redeclarations(
6446 &self,
6447 left: &CodeUnit,
6448 right: &CodeUnit,
6449 ) -> bool {
6450 let (Some(left_metadata), Some(right_metadata)) = (
6451 self.cpp_template_metadata.get(left),
6452 self.cpp_template_metadata.get(right),
6453 ) else {
6454 return false;
6455 };
6456 left_metadata.primary_fq_name == right_metadata.primary_fq_name
6457 && left_metadata.is_primary()
6458 && right_metadata.is_primary()
6459 && cpp_reconcile_primary_template_parameters(
6460 &[(left, left_metadata), (right, right_metadata)],
6461 right,
6462 )
6463 .is_some()
6464 }
6465
6466 fn alias_candidate_may_preserve_target(
6467 &self,
6468 analyzer: &CppGraphSource<'_>,
6469 visible_from: &ProjectFile,
6470 candidate: &CodeUnit,
6471 target: &CodeUnit,
6472 ) -> bool {
6473 let mut current = candidate.clone();
6474 let mut seen = HashSet::default();
6475 loop {
6476 if same_visible_symbol(¤t, target)
6477 || self.compatible_primary_template_redeclarations(¤t, target)
6478 {
6479 return true;
6480 }
6481 if self.cpp_template_metadata.contains_key(¤t) {
6482 return true;
6483 }
6484 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
6485 return false;
6486 };
6487 let StructuredAliasTarget::Named {
6488 components,
6489 global,
6490 arguments,
6491 } = alias_target
6492 else {
6493 return false;
6494 };
6495 if arguments.is_some() || !seen.insert(current.clone()) {
6496 return true;
6497 }
6498 let qualified = components.join("::");
6499 let next = if global {
6500 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
6501 } else {
6502 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
6503 };
6504 let Some(next) = next else {
6505 return true;
6506 };
6507 current = next;
6508 }
6509 }
6510
6511 fn type_candidates_for_declaration<'b>(
6515 &'b self,
6516 visible_from: &ProjectFile,
6517 declaration: &CodeUnit,
6518 raw_name: &str,
6519 ) -> Vec<&'b CodeUnit> {
6520 let Some(normalized) = normalize_reference_name(raw_name) else {
6521 return Vec::new();
6522 };
6523 if let Some(namespace) = cpp_namespace_for(declaration) {
6524 for prefix in namespace_prefixes(&namespace) {
6525 let qualified = format!("{prefix}::{normalized}");
6526 let candidates = self.type_candidates(visible_from, &qualified);
6527 if !candidates.is_empty() {
6528 return candidates;
6529 }
6530 }
6531 }
6532 self.type_candidates(visible_from, &normalized)
6533 }
6534
6535 fn resolve_unique_type_for_declaration(
6536 &self,
6537 visible_from: &ProjectFile,
6538 declaration: &CodeUnit,
6539 raw_name: &str,
6540 ) -> Option<CodeUnit> {
6541 unique_logical_type_candidate(self.type_candidates_for_declaration(
6542 visible_from,
6543 declaration,
6544 raw_name,
6545 ))
6546 }
6547
6548 pub fn resolves_to_type(
6549 &self,
6550 analyzer: &CppGraphSource<'_>,
6551 file: &ProjectFile,
6552 raw_name: &str,
6553 target: &CodeUnit,
6554 ) -> bool {
6555 let Some(normalized) = normalize_reference_name(raw_name) else {
6556 return false;
6557 };
6558 let candidates = self.type_candidates(file, &normalized);
6559 if candidates.is_empty() {
6560 return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
6561 }
6562 let Some(resolved) =
6563 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
6564 else {
6565 return false;
6566 };
6567 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
6568 }
6569
6570 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
6571 let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
6572 let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
6573 match resolved.kind() {
6574 CodeUnitType::Class => Some(resolved),
6575 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
6576 _ => None,
6577 }
6578 }
6579
6580 pub fn same_logical_callable(
6595 &self,
6596 analyzer: &CppGraphSource<'_>,
6597 left: &CodeUnit,
6598 right: &CodeUnit,
6599 ) -> bool {
6600 if same_logical_symbol(left, right) {
6601 return true;
6602 }
6603 if left.kind() != right.kind()
6604 || !left.is_callable()
6605 || !right.is_callable()
6606 || left.fq_name() != right.fq_name()
6607 {
6608 return false;
6609 }
6610 if self.callable_is_template_declaration(analyzer, left)
6616 || self.callable_is_template_declaration(analyzer, right)
6617 {
6618 return false;
6619 }
6620 let (Some(left_comparable), Some(right_comparable)) = (
6621 self.callable_comparable(analyzer, left),
6622 self.callable_comparable(analyzer, right),
6623 ) else {
6624 return false;
6625 };
6626 if left_comparable.suffix != right_comparable.suffix
6631 || left_comparable.shapes.len() != right_comparable.shapes.len()
6632 {
6633 return false;
6634 }
6635 left_comparable
6636 .shapes
6637 .iter()
6638 .zip(right_comparable.shapes.iter())
6639 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
6640 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
6641 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
6642 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
6643 }
6644 _ => false,
6648 })
6649 }
6650
6651 fn comparable_shapes_agree(
6657 &self,
6658 analyzer: &CppGraphSource<'_>,
6659 left: &CppComparableParameter,
6660 right: &CppComparableParameter,
6661 ) -> bool {
6662 let mut stack = vec![(left.root(), right.root())];
6663 while let Some((left_index, right_index)) = stack.pop() {
6664 match (left.node(left_index), right.node(right_index)) {
6665 (
6666 CppComparableNode::Named {
6667 name: left_name,
6668 primitive: left_primitive,
6669 konst: left_konst,
6670 volatil: left_volatil,
6671 },
6672 CppComparableNode::Named {
6673 name: right_name,
6674 primitive: right_primitive,
6675 konst: right_konst,
6676 volatil: right_volatil,
6677 },
6678 ) => {
6679 if left_konst != right_konst
6680 || left_volatil != right_volatil
6681 || left_primitive != right_primitive
6682 || !self.comparable_names_agree(
6683 analyzer,
6684 left_name,
6685 right_name,
6686 *left_primitive,
6687 )
6688 {
6689 return false;
6690 }
6691 }
6692 (
6693 CppComparableNode::Pointer {
6694 inner: left_inner,
6695 konst: left_konst,
6696 volatil: left_volatil,
6697 },
6698 CppComparableNode::Pointer {
6699 inner: right_inner,
6700 konst: right_konst,
6701 volatil: right_volatil,
6702 },
6703 ) => {
6704 if left_konst != right_konst || left_volatil != right_volatil {
6705 return false;
6706 }
6707 stack.push((*left_inner, *right_inner));
6708 }
6709 (
6710 CppComparableNode::Reference { inner: left_inner },
6711 CppComparableNode::Reference { inner: right_inner },
6712 )
6713 | (
6714 CppComparableNode::Array { inner: left_inner },
6715 CppComparableNode::Array { inner: right_inner },
6716 ) => stack.push((*left_inner, *right_inner)),
6717 (
6718 CppComparableNode::Generic {
6719 base: left_base,
6720 arguments: left_arguments,
6721 },
6722 CppComparableNode::Generic {
6723 base: right_base,
6724 arguments: right_arguments,
6725 },
6726 ) => {
6727 if left_arguments.len() != right_arguments.len() {
6728 return false;
6729 }
6730 stack.push((*left_base, *right_base));
6731 stack.extend(
6732 left_arguments.iter().zip(right_arguments.iter()).map(
6733 |(left_argument, right_argument)| (*left_argument, *right_argument),
6734 ),
6735 );
6736 }
6737 _ => return false,
6738 }
6739 }
6740 true
6741 }
6742
6743 fn comparable_names_agree(
6753 &self,
6754 analyzer: &CppGraphSource<'_>,
6755 left: &StructuredTypeName,
6756 right: &StructuredTypeName,
6757 primitive: bool,
6758 ) -> bool {
6759 if primitive {
6760 return left.path() == right.path();
6761 }
6762 match (
6763 self.comparable_name_terminal(analyzer, left),
6764 self.comparable_name_terminal(analyzer, right),
6765 ) {
6766 (Some(left_terminal), Some(right_terminal)) => {
6767 same_logical_symbol(&left_terminal, &right_terminal)
6768 }
6769 (None, None) => {
6770 left.path() == right.path() && left.is_absolute() == right.is_absolute()
6771 }
6772 _ => false,
6773 }
6774 }
6775
6776 fn comparable_name_terminal(
6787 &self,
6788 analyzer: &CppGraphSource<'_>,
6789 name: &StructuredTypeName,
6790 ) -> Option<CodeUnit> {
6791 let mut current = self.comparable_name_declaration(analyzer, name)?;
6792 let mut visited = HashSet::default();
6793 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
6794 if !declared_type_alias(analyzer, ¤t) {
6801 return current.is_class().then_some(current);
6802 }
6803 if !visited.insert(current.clone()) {
6804 return None;
6805 }
6806 let signature = current.signature()?;
6807 if cpp_alias_declaration_adds_indirection(signature) {
6812 return None;
6813 }
6814 let raw_target = cpp_alias_declaration_target_text(signature)?;
6815 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
6816 }
6817 None
6818 }
6819
6820 fn comparable_alias_target(
6831 &self,
6832 analyzer: &CppGraphSource<'_>,
6833 alias: &CodeUnit,
6834 raw_target: &str,
6835 ) -> Option<CodeUnit> {
6836 let absolute = raw_target.trim_start().starts_with("::");
6841 let normalized = normalize_reference_name(raw_target)?;
6842 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6843 brokk_bifrost_core::analyzer::Language::Cpp,
6844 &normalized,
6845 );
6846 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
6847 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6848 brokk_bifrost_core::analyzer::Language::Cpp,
6849 &namespace,
6850 )
6851 });
6852 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
6853 self.comparable_name_declaration(analyzer, &name)
6854 }
6855
6856 fn comparable_name_declaration(
6864 &self,
6865 analyzer: &CppGraphSource<'_>,
6866 name: &StructuredTypeName,
6867 ) -> Option<CodeUnit> {
6868 let definitions = analyzer.workspace_definitions();
6869 let interner = segment_interner();
6870 let first_depth = if name.is_absolute() {
6871 0
6872 } else {
6873 name.lexical_scope().len()
6874 };
6875 for depth in (0..=first_depth).rev() {
6876 let mut structured = FqName::new();
6877 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
6878 structured.push(interner.intern(component, SegmentKind::Unknown));
6879 }
6880 let mut candidates = definitions
6881 .identifier(&structured)
6882 .into_iter()
6883 .filter(|unit| unit.fq().same_segment_texts(&structured))
6884 .filter(|unit| {
6885 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
6886 });
6887 let Some(first) = candidates.next() else {
6888 continue;
6889 };
6890 return candidates
6891 .all(|unit| same_logical_symbol(&unit, &first))
6892 .then_some(first);
6893 }
6894 None
6895 }
6896
6897 fn callable_comparable(
6903 &self,
6904 analyzer: &CppGraphSource<'_>,
6905 unit: &CodeUnit,
6906 ) -> Option<Arc<ExtractedComparable>> {
6907 if let Some(cached) = self
6908 .callable_comparables
6909 .lock()
6910 .expect("C++ callable comparable cache poisoned")
6911 .get(unit)
6912 .cloned()
6913 {
6914 return cached;
6915 }
6916 let extracted = self
6917 .extract_callable_comparable(analyzer, unit)
6918 .map(Arc::new);
6919 self.callable_comparables
6920 .lock()
6921 .expect("C++ callable comparable cache poisoned")
6922 .insert(unit.clone(), extracted.clone());
6923 extracted
6924 }
6925
6926 fn extract_callable_comparable(
6927 &self,
6928 analyzer: &CppGraphSource<'_>,
6929 unit: &CodeUnit,
6930 ) -> Option<ExtractedComparable> {
6931 let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
6932 let root = prepared.tree().root_node();
6933 let declarator = analyzer
6934 .ranges(unit)
6935 .into_iter()
6936 .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
6937 Some(ExtractedComparable {
6938 shapes: cpp_comparable_parameter_shapes(
6941 declarator,
6942 prepared.source(),
6943 &ParentIndex::unindexed(),
6944 ),
6945 suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
6946 })
6947 }
6948
6949 pub fn canonical_type_for_reference(
6950 &self,
6951 file: &ProjectFile,
6952 raw_name: &str,
6953 ) -> Option<CodeUnit> {
6954 let resolved = self.resolve_type(file, raw_name)?;
6955 self.alias_target(&resolved).or(Some(resolved))
6956 }
6957
6958 pub fn parser_alias_resolves_to_type(
6959 &self,
6960 analyzer: &CppGraphSource<'_>,
6961 file: &ProjectFile,
6962 raw_name: &str,
6963 target: &CodeUnit,
6964 ) -> bool {
6965 let Some(alias_name) = normalize_reference_name(raw_name) else {
6966 return false;
6967 };
6968 let Some(cpp) = analyzer.cpp else {
6969 return false;
6970 };
6971 let matches_file = |source_file: &ProjectFile| {
6972 self.file_alias_matches(cpp, source_file, &alias_name, target)
6973 };
6974 self.visible_source_files_by_root.get(file).map_or_else(
6975 || matches_file(file),
6976 |files| files.iter().any(matches_file),
6977 )
6978 }
6979
6980 fn file_alias_matches(
6981 &self,
6982 cpp: &dyn CppSource,
6983 file: &ProjectFile,
6984 alias_name: &str,
6985 target: &CodeUnit,
6986 ) -> bool {
6987 let cell = {
6988 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
6989 Arc::clone(
6990 cells
6991 .entry(file.clone())
6992 .or_insert_with(|| Arc::new(OnceLock::new())),
6993 )
6994 };
6995 cell.get_or_init(|| {
6996 self.parser_alias_source_parses
6997 .fetch_add(1, Ordering::Relaxed);
6998 #[cfg(any(test, feature = "test-support"))]
6999 {
7000 *self
7001 .alias_source_parse_counts
7002 .lock()
7003 .expect("alias source parse count lock")
7004 .entry(file.clone())
7005 .or_default() += 1;
7006 }
7007 aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
7008 })
7009 .iter()
7010 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
7011 }
7012
7013 #[cfg(any(test, feature = "test-support"))]
7014 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
7015 self.visible_source_files_by_root
7016 .get(file)
7017 .cloned()
7018 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
7019 }
7020
7021 #[cfg(any(test, feature = "test-support"))]
7022 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
7023 self.alias_source_parse_counts
7024 .lock()
7025 .expect("alias source parse count lock")
7026 .get(file)
7027 .copied()
7028 .unwrap_or(0)
7029 }
7030
7031 pub fn resolve_named(
7032 &self,
7033 file: &ProjectFile,
7034 raw_name: &str,
7035 kind: TargetKind,
7036 ) -> Option<CodeUnit> {
7037 let normalized = normalize_reference_name(raw_name)?;
7038 self.named_candidates_for_normalized(file, &normalized, kind)
7039 .into_iter()
7040 .next()
7041 .cloned()
7042 }
7043
7044 pub fn contains_named_symbol(
7045 &self,
7046 file: &ProjectFile,
7047 raw_name: &str,
7048 kind: TargetKind,
7049 target: &CodeUnit,
7050 ) -> bool {
7051 let Some(normalized) = normalize_reference_name(raw_name) else {
7052 return false;
7053 };
7054 self.named_candidates_for_normalized(file, &normalized, kind)
7055 .into_iter()
7056 .any(|unit| {
7057 matches_kind_for_lookup(unit, kind)
7058 && reference_matches_unit(&normalized, unit)
7059 && same_visible_symbol(unit, target)
7060 })
7061 }
7062
7063 pub fn named_candidates(
7064 &self,
7065 file: &ProjectFile,
7066 raw_name: &str,
7067 kind: TargetKind,
7068 ) -> Vec<CodeUnit> {
7069 let Some(normalized) = normalize_reference_name(raw_name) else {
7070 return Vec::new();
7071 };
7072 self.named_candidates_for_normalized(file, &normalized, kind)
7073 .into_iter()
7074 .cloned()
7075 .collect()
7076 }
7077
7078 pub fn resolve_known_non_target(
7079 &self,
7080 file: &ProjectFile,
7081 raw_name: &str,
7082 kind: TargetKind,
7083 target: &CodeUnit,
7084 ) -> bool {
7085 let Some(normalized) = normalize_reference_name(raw_name) else {
7086 return false;
7087 };
7088 normalized.contains("::")
7089 && self
7090 .named_candidates_for_normalized(file, &normalized, kind)
7091 .into_iter()
7092 .any(|unit| {
7093 matches_kind_for_lookup(unit, kind)
7094 && reference_matches_unit(&normalized, unit)
7095 && !same_visible_symbol(unit, target)
7096 })
7097 }
7098
7099 pub fn resolve_call_return_binding(
7100 &self,
7101 analyzer: &CppGraphSource<'_>,
7102 file: &ProjectFile,
7103 raw_name: &str,
7104 arity: usize,
7105 lexical_namespace: Option<&str>,
7106 direct_type: Option<&CodeUnit>,
7107 ) -> Option<CppScanBinding> {
7108 let normalized = normalize_reference_name(raw_name)?;
7109 let mut candidates = Vec::new();
7110 for function in
7111 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
7112 {
7113 if cpp_callable_arity(analyzer, function).accepts(arity)
7114 && !direct_type.is_some_and(|direct_type| {
7115 self.callable_is_constructor_declaration(analyzer, function)
7116 && type_owner_of(analyzer, function)
7117 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
7118 })
7119 {
7120 candidates.push(function.clone());
7121 }
7122 }
7123 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
7124 unanimous_return_binding(analyzer, self, file, &candidates)
7125 }
7126
7127 pub fn resolve_call_return_binding_without_arity(
7128 &self,
7129 analyzer: &CppGraphSource<'_>,
7130 file: &ProjectFile,
7131 raw_name: &str,
7132 lexical_namespace: Option<&str>,
7133 direct_type: Option<&CodeUnit>,
7134 ) -> (bool, Option<CppScanBinding>) {
7135 let Some(normalized) = normalize_reference_name(raw_name) else {
7136 return (false, None);
7137 };
7138 let mut candidates = self
7139 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
7140 .into_iter()
7141 .filter(|function| {
7142 function.is_function()
7143 && !direct_type.is_some_and(|direct_type| {
7144 self.callable_is_constructor_declaration(analyzer, function)
7145 && type_owner_of(analyzer, function)
7146 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
7147 })
7148 })
7149 .cloned()
7150 .collect::<Vec<_>>();
7151 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
7152 let has_candidates = !candidates.is_empty();
7153 (
7154 has_candidates,
7155 unanimous_return_binding(analyzer, self, file, &candidates),
7156 )
7157 }
7158
7159 pub fn visible_identifier_candidates<'b>(
7160 &'b self,
7161 file: &ProjectFile,
7162 identifier: &str,
7163 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
7164 self.visible_by_identifier
7165 .get(file)
7166 .and_then(|by_name| by_name.get(identifier))
7167 .into_iter()
7168 .flatten()
7169 }
7170
7171 pub fn visible_type_reference_component_names_for_target(
7179 &self,
7180 analyzer: &CppGraphSource<'_>,
7181 file: &ProjectFile,
7182 target: &CodeUnit,
7183 ) -> HashSet<String> {
7184 let mut names = HashSet::from_iter([target.identifier().to_string()]);
7185 if let Some(metadata) = self.cpp_template_metadata.get(target) {
7186 names.insert(metadata.primary_name.clone());
7187 }
7188
7189 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
7190 for (identifier, candidates) in by_identifier {
7191 if candidates.iter().any(|candidate| {
7192 (candidate.is_class()
7193 && (same_visible_symbol(candidate, target)
7194 || self.compatible_primary_template_redeclarations(candidate, target)))
7195 || (declared_type_alias(analyzer, candidate)
7196 && self.alias_candidate_may_preserve_target(
7197 analyzer, file, candidate, target,
7198 ))
7199 }) {
7200 names.insert(identifier.clone());
7201 }
7202 }
7203 }
7204
7205 names
7206 }
7207
7208 pub fn indexed_structural_class_scope(
7209 &self,
7210 file: &ProjectFile,
7211 class: Node<'_>,
7212 source: &str,
7213 ) -> Option<Vec<String>> {
7214 let key = (file.clone(), class.start_byte(), class.end_byte());
7215 if let Some(cached) = self
7216 .indexed_structural_class_scopes
7217 .lock()
7218 .expect("C++ indexed structural-class scope cache poisoned")
7219 .get(&key)
7220 .cloned()
7221 {
7222 return cached;
7223 }
7224 let resolved = (|| {
7225 let name = class.child_by_field_name("name")?;
7226 let identifier = if name.kind() == "template_type" {
7227 node_text(name.child_by_field_name("name")?, source).to_string()
7228 } else {
7229 let mut components = Vec::new();
7230 append_cpp_name_components(name, source, &mut components)?;
7231 components.last()?.clone()
7232 };
7233 let visible = self
7234 .visible_identifier_candidates(file, &identifier)
7235 .cloned()
7236 .collect::<Vec<_>>();
7237 let mut visible = visible;
7238 for candidate in
7239 self.visible_by_file
7240 .get(file)
7241 .into_iter()
7242 .flatten()
7243 .filter(|candidate| {
7244 self.cpp_template_metadata
7245 .get(candidate)
7246 .is_some_and(|metadata| metadata.primary_name == identifier)
7247 })
7248 {
7249 if !visible
7250 .iter()
7251 .any(|existing| same_logical_symbol(existing, candidate))
7252 {
7253 visible.push(candidate.clone());
7254 }
7255 }
7256 let cpp_source = self.cpp_source();
7259 let candidates = visible
7260 .iter()
7261 .filter(|candidate| {
7262 candidate.source() == file
7263 && candidate.is_class()
7264 && !declared_type_alias(&cpp_source, candidate)
7265 && self.cpp.ranges(candidate).iter().any(|range| {
7266 range.start_byte <= class.start_byte()
7267 && class.end_byte() <= range.end_byte
7268 })
7269 })
7270 .collect::<Vec<_>>();
7271 let owner = if name.kind() == "template_type" {
7272 let expected = normalize_cpp_whitespace(node_text(name, source));
7273 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
7274 let exact = candidates
7275 .iter()
7276 .copied()
7277 .filter(|candidate| {
7278 candidate
7279 .fq()
7280 .segments()
7281 .iter()
7282 .rev()
7283 .find_map(|&segment| {
7284 let (text, kind) = interner.resolve(segment);
7285 matches!(
7286 kind,
7287 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
7288 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
7289 )
7290 .then_some(text)
7291 })
7292 .is_some_and(|text| text == expected)
7293 })
7294 .collect::<Vec<_>>();
7295 unique_logical_type_candidate(exact)
7296 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
7297 } else {
7298 unique_logical_type_candidate(candidates)?
7299 };
7300 Some(canonical_cpp_scope_components(&owner))
7301 })();
7302 self.indexed_structural_class_scopes
7303 .lock()
7304 .expect("C++ indexed structural-class scope cache poisoned")
7305 .insert(key, resolved.clone());
7306 resolved
7307 }
7308
7309 pub fn indexed_enclosing_owner_scope(
7310 &self,
7311 analyzer: &CppGraphSource<'_>,
7312 file: &ProjectFile,
7313 node: Node<'_>,
7314 ) -> Option<Vec<String>> {
7315 let anchor = std::iter::successors(Some(node), |current| current.parent())
7316 .find(|current| {
7317 matches!(
7318 current.kind(),
7319 "function_definition"
7320 | "class_specifier"
7321 | "struct_specifier"
7322 | "union_specifier"
7323 )
7324 })
7325 .unwrap_or(node);
7326 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
7327 if let Some(cached) = self
7328 .indexed_enclosing_owner_scopes
7329 .lock()
7330 .expect("C++ indexed enclosing-owner scope cache poisoned")
7331 .get(&key)
7332 .cloned()
7333 {
7334 return cached;
7335 }
7336 let resolved = (|| {
7337 let range = Range {
7338 start_byte: node.start_byte(),
7339 end_byte: node.end_byte(),
7340 start_line: node.start_position().row,
7341 end_line: node.end_position().row,
7342 };
7343 let start = analyzer.enclosing_code_unit(file, &range)?;
7344 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
7345 start,
7346 |unit| self.cached_precise_parent_of(analyzer, unit),
7347 )
7348 .find(|unit| {
7349 unit.is_class()
7350 && !analyzer
7351 .type_alias_provider()
7352 .is_some_and(|provider| provider.is_type_alias(unit))
7353 })?;
7354 Some(canonical_cpp_scope_components(&owner))
7355 })();
7356 self.indexed_enclosing_owner_scopes
7357 .lock()
7358 .expect("C++ indexed enclosing-owner scope cache poisoned")
7359 .insert(key, resolved.clone());
7360 resolved
7361 }
7362
7363 fn cached_precise_parent_of(
7364 &self,
7365 analyzer: &CppGraphSource<'_>,
7366 code_unit: &CodeUnit,
7367 ) -> Option<CodeUnit> {
7368 if let Some(cached) = self
7369 .precise_parent_cache
7370 .lock()
7371 .expect("C++ precise-parent cache poisoned")
7372 .get(code_unit)
7373 .cloned()
7374 {
7375 return cached;
7376 }
7377 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
7378 self.precise_parent_cache
7379 .lock()
7380 .expect("C++ precise-parent cache poisoned")
7381 .insert(code_unit.clone(), resolved.clone());
7382 resolved
7383 }
7384
7385 pub fn callable_is_constructor_declaration(
7386 &self,
7387 analyzer: &CppGraphSource<'_>,
7388 candidate: &CodeUnit,
7389 ) -> bool {
7390 if !candidate.is_function() {
7391 return false;
7392 }
7393 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7394 return false;
7395 };
7396 let root = prepared.tree().root_node();
7397 let candidate_ranges = analyzer.ranges(candidate);
7398 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
7399 let mut current = root
7400 .descendant_for_byte_range(range.start_byte, range.end_byte)
7401 .and_then(|node| node.parent());
7402 while let Some(node) = current {
7403 if matches!(
7404 node.kind(),
7405 "class_specifier" | "struct_specifier" | "union_specifier"
7406 ) {
7407 return node
7408 .child_by_field_name("name")
7409 .map(|name| terminal_name(node_text(name, prepared.source())))
7410 .is_some_and(|name| name == candidate.identifier());
7411 }
7412 current = node.parent();
7413 }
7414 false
7415 });
7416 if enclosed_by_matching_type {
7417 return true;
7418 }
7419 let indexed_containment = analyzer
7420 .declarations(candidate.source())
7421 .into_iter()
7422 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
7423 .any(|owner| {
7424 analyzer.ranges(&owner).iter().any(|owner_range| {
7425 candidate_ranges.iter().any(|candidate_range| {
7426 owner_range.start_byte <= candidate_range.start_byte
7427 && candidate_range.end_byte <= owner_range.end_byte
7428 })
7429 })
7430 });
7431 if indexed_containment {
7432 return true;
7433 }
7434 let metadata = analyzer.signature_metadata(candidate);
7435 !metadata.is_empty()
7436 && metadata
7437 .iter()
7438 .all(|signature| signature.return_type_text().is_none())
7439 }
7440
7441 pub fn callable_is_deduction_guide_declaration(
7449 &self,
7450 analyzer: &CppGraphSource<'_>,
7451 candidate: &CodeUnit,
7452 ) -> bool {
7453 if !candidate.is_function() {
7454 return false;
7455 }
7456 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7457 return false;
7458 };
7459 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
7460 .into_iter()
7461 .any(|declaration| {
7462 if declaration.kind() != "declaration"
7463 || declaration.child_by_field_name("type").is_some()
7464 {
7465 return false;
7466 }
7467 let Some(declarator) = declaration.child_by_field_name("declarator") else {
7468 return false;
7469 };
7470 if declarator.kind() != "function_declarator" {
7471 return false;
7472 }
7473 let mut cursor = declarator.walk();
7474 let has_trailing_return = declarator
7475 .named_children(&mut cursor)
7476 .any(|child| child.kind() == "trailing_return_type");
7477 has_trailing_return
7478 && declarator_name_node(declarator).is_some_and(|name| {
7479 node_text(name, prepared.source()) == candidate.identifier()
7480 })
7481 })
7482 }
7483
7484 pub fn callable_is_template_declaration(
7488 &self,
7489 analyzer: &CppGraphSource<'_>,
7490 candidate: &CodeUnit,
7491 ) -> bool {
7492 if !candidate.is_function() {
7493 return false;
7494 }
7495 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7496 return false;
7497 };
7498 let root = prepared.tree().root_node();
7499 analyzer.ranges(candidate).iter().any(|range| {
7500 let Some(node) = node_for_exact_range(root, range)
7501 .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
7502 else {
7503 return false;
7504 };
7505 node.parent().is_some_and(|parent| {
7506 parent.kind() == "template_declaration"
7507 && parent
7508 .named_child(parent.named_child_count().saturating_sub(1))
7509 .is_some_and(|declaration| same_node(declaration, node))
7510 })
7511 })
7512 }
7513
7514 pub fn type_name_candidates<'b>(
7515 &'b self,
7516 file: &ProjectFile,
7517 normalized: &str,
7518 ) -> Vec<&'b CodeUnit> {
7519 self.candidate_units(file, normalized, TargetKind::Type)
7520 }
7521
7522 pub fn visible_members_for_owner_name<'b>(
7523 &'b self,
7524 file: &ProjectFile,
7525 owner: &CodeUnit,
7526 name: &str,
7527 ) -> Vec<&'b CodeUnit> {
7528 self.visible_identifier_candidates(file, name)
7529 .filter(|unit| {
7530 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
7534 .is_some_and(|parent| parent == owner.fq_name())
7535 })
7536 .collect()
7537 }
7538
7539 pub fn visible_member_for_owner_name(
7540 &self,
7541 file: &ProjectFile,
7542 owner: &CodeUnit,
7543 name: &str,
7544 ) -> VisibleMemberResolution {
7545 let candidates = self.visible_members_for_owner_name(file, owner, name);
7546 let mut callables = Vec::new();
7547 let mut non_callable = None;
7548 for candidate in candidates {
7549 if candidate.is_function() {
7550 callables.push(candidate.clone());
7551 } else if non_callable.is_none() {
7552 non_callable = Some(candidate.clone());
7553 }
7554 }
7555 match (callables.is_empty(), non_callable) {
7556 (false, None) => VisibleMemberResolution::Callable(callables),
7557 (true, Some(_)) => VisibleMemberResolution::NonCallable,
7558 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
7559 (true, None) => VisibleMemberResolution::Missing,
7560 }
7561 }
7562
7563 fn field_declared_type_fact(
7564 &self,
7565 analyzer: &CppGraphSource<'_>,
7566 field: &CodeUnit,
7567 ) -> Option<DeclaredFieldTypeFact> {
7568 if let Some(cached) = self
7569 .field_type_facts
7570 .lock()
7571 .expect("C++ field type fact cache poisoned")
7572 .get(field)
7573 .cloned()
7574 {
7575 return cached;
7576 }
7577 let decoded = decode_field_declared_type_fact(analyzer, field);
7578 self.field_type_facts
7579 .lock()
7580 .expect("C++ field type fact cache poisoned")
7581 .insert(field.clone(), decoded.clone());
7582 decoded
7583 }
7584
7585 fn structured_alias_target(
7586 &self,
7587 analyzer: &CppGraphSource<'_>,
7588 unit: &CodeUnit,
7589 ) -> Option<StructuredAliasTarget> {
7590 if let Some(cached) = self
7591 .structured_alias_targets
7592 .lock()
7593 .expect("C++ structured alias target cache poisoned")
7594 .get(unit)
7595 .cloned()
7596 {
7597 return cached;
7598 }
7599 let decoded = decode_structured_alias_target(analyzer, unit);
7600 self.structured_alias_targets
7601 .lock()
7602 .expect("C++ structured alias target cache poisoned")
7603 .insert(unit.clone(), decoded.clone());
7604 decoded
7605 }
7606
7607 pub fn type_candidates<'b>(
7608 &'b self,
7609 file: &ProjectFile,
7610 normalized: &str,
7611 ) -> Vec<&'b CodeUnit> {
7612 let mut candidates = self
7613 .candidate_units(file, normalized, TargetKind::Type)
7614 .into_iter()
7615 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
7616 .collect::<Vec<_>>();
7617 dedup_unit_refs(&mut candidates);
7618 candidates
7619 }
7620
7621 pub fn named_candidates_for_normalized<'b>(
7622 &'b self,
7623 file: &ProjectFile,
7624 normalized: &str,
7625 kind: TargetKind,
7626 ) -> Vec<&'b CodeUnit> {
7627 let mut candidates = self
7628 .candidate_units(file, normalized, kind)
7629 .into_iter()
7630 .filter(|unit| {
7631 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
7632 })
7633 .collect::<Vec<_>>();
7634 dedup_unit_refs(&mut candidates);
7635 candidates
7636 }
7637
7638 pub fn candidate_units<'b>(
7639 &'b self,
7640 file: &ProjectFile,
7641 normalized: &str,
7642 kind: TargetKind,
7643 ) -> Vec<&'b CodeUnit> {
7644 if normalized.contains("::") {
7645 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7654 brokk_bifrost_core::analyzer::Language::Cpp,
7655 normalized,
7656 )
7657 .pop() else {
7658 return Vec::new();
7659 };
7660 let fqns = cpp_reference_fqn_candidates(normalized, kind);
7661 return self
7662 .visible_identifier_candidates(file, &identifier)
7663 .filter(|unit| {
7664 #[cfg(any(test, feature = "test-support"))]
7665 self.qualified_candidate_inspections
7666 .fetch_add(1, Ordering::Relaxed);
7667 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
7668 || canonical_cpp_name_matches(unit, normalized)
7669 })
7670 .collect();
7671 }
7672 self.visible_identifier_candidates(file, normalized)
7673 .collect()
7674 }
7675
7676 #[cfg(any(test, feature = "test-support"))]
7677 pub fn reset_qualified_candidate_inspections(&self) {
7678 self.qualified_candidate_inspections
7679 .store(0, Ordering::Relaxed);
7680 }
7681
7682 #[cfg(any(test, feature = "test-support"))]
7683 pub fn qualified_candidate_inspections(&self) -> usize {
7684 self.qualified_candidate_inspections.load(Ordering::Relaxed)
7685 }
7686
7687 #[cfg(any(test, feature = "test-support"))]
7688 pub fn reset_target_preserving_type_resolution_count(&self) {
7689 self.target_preserving_type_resolution_count
7690 .store(0, Ordering::Relaxed);
7691 }
7692
7693 #[cfg(any(test, feature = "test-support"))]
7694 pub fn target_preserving_type_resolution_count(&self) -> usize {
7695 self.target_preserving_type_resolution_count
7696 .load(Ordering::Relaxed)
7697 }
7698
7699 #[cfg(any(test, feature = "test-support"))]
7700 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
7701 self.visible_parser_alias_name_set_build_count
7702 .load(Ordering::Relaxed)
7703 }
7704}
7705
7706#[derive(Default)]
7707struct IncludeGraph {
7708 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
7709}
7710
7711impl IncludeGraph {
7712 fn extend_with<F>(
7713 &mut self,
7714 root: &ProjectFile,
7715 cancellation: Option<&CancellationToken>,
7716 targets_for: &mut F,
7717 ) where
7718 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
7719 {
7720 let mut stack = vec![root.clone()];
7721 while let Some(file) = stack.pop() {
7722 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7723 break;
7724 }
7725 if self.targets_by_file.contains_key(&file) {
7726 continue;
7727 }
7728 let targets = targets_for(&file);
7729 stack.extend(targets.iter().cloned());
7730 self.targets_by_file.insert(file, targets);
7731 }
7732 }
7733
7734 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
7735 self.targets_by_file.keys()
7736 }
7737
7738 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
7739 self.targets_by_file
7740 .get(file)
7741 .map(Vec::as_slice)
7742 .unwrap_or_default()
7743 }
7744
7745 fn reachable_files(
7746 &self,
7747 root: &ProjectFile,
7748 cancellation: Option<&CancellationToken>,
7749 ) -> HashSet<ProjectFile> {
7750 let mut pending = vec![root.clone()];
7751 let mut visited = HashSet::default();
7752 while let Some(file) = pending.pop() {
7753 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7754 break;
7755 }
7756 if visited.insert(file.clone()) {
7757 pending.extend(self.targets(&file).iter().cloned());
7758 }
7759 }
7760 visited
7761 }
7762}
7763
7764fn build_bounded_visible_declarations(
7765 cpp: &dyn CppSource,
7766 token: QueryToken<'_>,
7767 analyzer: &CppGraphSource<'_>,
7768 roots: &HashSet<ProjectFile>,
7769 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
7770 cancellation: Option<&CancellationToken>,
7771 stats: &mut BoundedVisibilityStats,
7772) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
7773 roots
7774 .iter()
7775 .map(|root| {
7776 let reading_is_c = analyzer.reference_uses_c_semantics(root);
7777 let declarations_started = Instant::now();
7778 let root_declarations =
7779 bounded_visibility_declarations_in_reading(analyzer, root, reading_is_c);
7780 stats.declaration_elapsed += declarations_started.elapsed();
7781 stats.declaration_reads += 1;
7782 stats.declaration_units += root_declarations.len();
7783 let mut visible = root_declarations.into_iter().collect::<HashSet<_>>();
7784 let mut pending_names = HashSet::default();
7785 if let Some(prepared) = cpp.prepared_syntax(token, root) {
7786 let mut pending_nodes = vec![prepared.tree().root_node()];
7787 while let Some(node) = pending_nodes.pop() {
7788 if matches!(
7789 node.kind(),
7790 "identifier"
7791 | "type_identifier"
7792 | "field_identifier"
7793 | "namespace_identifier"
7794 ) {
7795 pending_names.insert(node_text(node, prepared.source()).to_string());
7796 }
7797 if node.kind() == "preproc_arg" {
7798 for reference in
7799 object_macro_replacement_type_references(node, prepared.source())
7800 {
7801 pending_names.extend(reference.components);
7802 }
7803 }
7804 for index in 0..node.named_child_count() {
7805 if let Some(child) = node.named_child(index) {
7806 pending_nodes.push(child);
7807 }
7808 }
7809 }
7810 }
7811 stats.root_names += pending_names.len();
7812 let mut completed_names = HashSet::default();
7813 while !pending_names.is_empty() {
7814 stats.rounds += 1;
7815 let round_names = std::mem::take(&mut pending_names);
7816 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
7817 HashMap::default();
7818 for identifier in round_names {
7819 if !completed_names.insert(identifier.clone())
7820 || cancellation.is_some_and(CancellationToken::is_cancelled)
7821 {
7822 continue;
7823 }
7824 let lookup_started = Instant::now();
7825 let candidates = cpp.visibility_identifier_candidates(&identifier);
7826 stats.lookup_elapsed += lookup_started.elapsed();
7827 stats.identifier_lookups += 1;
7828 stats.candidate_units += candidates.len();
7829 for source in candidates
7830 .into_iter()
7831 .map(|unit| unit.source().clone())
7832 .collect::<HashSet<_>>()
7833 {
7834 if source != *root
7835 && visible_sources
7836 .get(root)
7837 .is_some_and(|files| files.contains(&source))
7838 {
7839 requested_names_by_source
7840 .entry(source)
7841 .or_default()
7842 .insert(identifier.clone());
7843 }
7844 }
7845 }
7846 stats.candidate_sources += requested_names_by_source.len();
7847 for (source, requested_names) in requested_names_by_source {
7848 let declarations_started = Instant::now();
7849 let declarations =
7850 bounded_visibility_declarations_in_reading(analyzer, &source, reading_is_c);
7851 stats.declaration_elapsed += declarations_started.elapsed();
7852 stats.declaration_reads += 1;
7853 stats.declaration_units += declarations.len();
7854 for unit in declarations {
7855 let template_metadata = unit
7856 .is_class()
7857 .then(|| cpp.template_metadata(&unit))
7858 .flatten();
7859 if !requested_names.contains(unit.identifier())
7860 && !template_metadata.as_ref().is_some_and(|metadata| {
7861 requested_names.contains(&metadata.primary_name)
7862 })
7863 {
7864 continue;
7865 }
7866 stats.selected_units += 1;
7867 if let Some(prepared) = cpp.prepared_syntax(token, &source) {
7868 let ast_started = Instant::now();
7869 for range in analyzer.ranges(&unit) {
7870 let Some(declaration) =
7871 node_for_exact_range(prepared.tree().root_node(), &range)
7872 else {
7873 continue;
7874 };
7875 let mut pending_nodes = vec![declaration];
7876 while let Some(node) = pending_nodes.pop() {
7877 stats.dependency_ast_nodes += 1;
7878 if matches!(
7879 node.kind(),
7880 "type_identifier" | "namespace_identifier"
7881 ) {
7882 let name = node_text(node, prepared.source());
7883 if !completed_names.contains(name)
7884 && pending_names.insert(name.to_string())
7885 {
7886 stats.dependency_names += 1;
7887 }
7888 }
7889 for index in 0..node.named_child_count() {
7890 if let Some(child) = node.named_child(index) {
7891 pending_nodes.push(child);
7892 }
7893 }
7894 }
7895 }
7896 stats.dependency_ast_elapsed += ast_started.elapsed();
7897 }
7898 if let Some(metadata) = template_metadata
7899 && !completed_names.contains(&metadata.primary_name)
7900 {
7901 pending_names.insert(metadata.primary_name);
7902 }
7903 visible.insert(unit);
7904 }
7905 }
7906 }
7907 (root.clone(), visible)
7908 })
7909 .collect()
7910}
7911
7912#[derive(Default)]
7913struct BoundedVisibilityStats {
7914 rounds: usize,
7915 root_names: usize,
7916 identifier_lookups: usize,
7917 candidate_units: usize,
7918 candidate_sources: usize,
7919 declaration_reads: usize,
7920 declaration_units: usize,
7921 selected_units: usize,
7922 dependency_ast_nodes: usize,
7923 dependency_names: usize,
7924 lookup_elapsed: Duration,
7925 declaration_elapsed: Duration,
7926 dependency_ast_elapsed: Duration,
7927}
7928
7929fn bounded_visibility_declarations_in_reading(
7930 analyzer: &CppGraphSource<'_>,
7931 file: &ProjectFile,
7932 c_semantics: bool,
7933) -> BTreeSet<CodeUnit> {
7934 #[cfg(any(test, feature = "test-support"))]
7935 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
7936 analyzer.declarations_in_reading(file, c_semantics)
7937}
7938
7939#[cfg(any(test, feature = "test-support"))]
7940pub fn reset_bounded_visibility_declaration_read_count_for_test() {
7941 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
7942}
7943
7944#[cfg(any(test, feature = "test-support"))]
7945pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
7946 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
7947}
7948
7949pub struct VisibilityData {
7950 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
7951 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
7952}
7953
7954pub fn build_visibility_data<F, R, D>(
7964 roots: &HashSet<ProjectFile>,
7965 cancellation: Option<&CancellationToken>,
7966 mut targets_for: F,
7967 mut reading_is_c_for: R,
7968 mut declarations_for: D,
7969) -> VisibilityData
7970where
7971 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
7972 R: FnMut(&ProjectFile) -> bool,
7973 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
7974{
7975 let mut include_graph = IncludeGraph::default();
7976 for file in roots {
7977 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7978 break;
7979 }
7980 include_graph.extend_with(file, cancellation, &mut targets_for);
7981 }
7982 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
7983 .files()
7984 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
7985 .map(|file| (file.clone(), declarations_for(file, false)))
7986 .collect();
7987 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
7988 let mut visible_by_file = HashMap::default();
7989 let mut visible_source_files_by_root = HashMap::default();
7990 for file in roots {
7991 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7992 break;
7993 }
7994 let mut visited = HashSet::default();
7995 let mut visible = HashSet::default();
7996 let declarations_by_file = if reading_is_c_for(file) {
7997 for reached in cpp_declarations_by_file.keys() {
7998 if !c_declarations_by_file.contains_key(reached) {
7999 let declarations = declarations_for(reached, true);
8000 c_declarations_by_file.insert(reached.clone(), declarations);
8001 }
8002 }
8003 &c_declarations_by_file
8004 } else {
8005 &cpp_declarations_by_file
8006 };
8007 collect_visible_declarations(
8008 &include_graph,
8009 declarations_by_file,
8010 file,
8011 &mut visited,
8012 &mut visible,
8013 cancellation,
8014 );
8015 visible_by_file.insert(file.clone(), visible);
8016 visible_source_files_by_root.insert(file.clone(), visited);
8017 }
8018 VisibilityData {
8019 visible_by_file,
8020 visible_source_files_by_root,
8021 }
8022}
8023
8024#[derive(Default)]
8043struct OutOfLineOwnerBindingStats {
8044 unseen_owners: usize,
8045 definition_lookups: usize,
8046 admitted: usize,
8047}
8048
8049fn extend_with_out_of_line_owner_bindings(
8050 cpp: &dyn CppSource,
8051 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
8052) -> OutOfLineOwnerBindingStats {
8053 let mut stats = OutOfLineOwnerBindingStats::default();
8054 for (file, visible) in visible_by_file.iter_mut() {
8055 let mut unseen_owners: HashSet<String> = visible
8059 .iter()
8060 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
8061 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
8062 .collect();
8063 if unseen_owners.is_empty() {
8064 continue;
8065 }
8066 for unit in visible.iter().filter(|unit| unit.is_class()) {
8067 unseen_owners.remove(&unit.fq_name());
8068 }
8069 stats.unseen_owners += unseen_owners.len();
8070 stats.definition_lookups += unseen_owners.len();
8071 let admitted = unseen_owners
8072 .iter()
8073 .flat_map(|owner| cpp.definitions(owner))
8074 .filter(CodeUnit::is_class)
8075 .collect::<Vec<_>>();
8076 stats.admitted += admitted.len();
8077 visible.extend(admitted);
8078 }
8079 stats
8080}
8081
8082pub enum VisibleMemberResolution {
8083 Callable(Vec<CodeUnit>),
8084 NonCallable,
8085 AmbiguousKind,
8086 Missing,
8087}
8088
8089#[derive(Clone)]
8090pub enum EnclosingMemberOwnerResolution {
8091 Owner(CodeUnit),
8092 Ambiguous,
8093 Missing,
8094}
8095
8096pub fn resolve_declaring_member_owner(
8097 analyzer: &CppGraphSource<'_>,
8098 visibility: &VisibilityIndex<'_>,
8099 file: &ProjectFile,
8100 receiver_owner: &CodeUnit,
8101 member_name: &str,
8102) -> EnclosingMemberOwnerResolution {
8103 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8104 return EnclosingMemberOwnerResolution::Missing;
8105 };
8106 let Some(receiver_owner) =
8107 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
8108 else {
8109 return EnclosingMemberOwnerResolution::Ambiguous;
8110 };
8111 let resolve_level = |frontier: &[CodeUnit]| {
8112 let mut member_owners = Vec::new();
8113 for raw_owner in frontier {
8114 let Some(owner) =
8115 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
8116 else {
8117 return EnclosingMemberOwnerResolution::Ambiguous;
8118 };
8119 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
8120 let Some(member_owner) = type_owner_of(analyzer, member) else {
8121 return EnclosingMemberOwnerResolution::Ambiguous;
8122 };
8123 if !member_owners
8124 .iter()
8125 .any(|existing| same_visible_symbol(existing, &member_owner))
8126 {
8127 member_owners.push(member_owner);
8128 }
8129 }
8130 }
8131 match member_owners.len() {
8132 0 => EnclosingMemberOwnerResolution::Missing,
8133 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
8134 _ => EnclosingMemberOwnerResolution::Ambiguous,
8135 }
8136 };
8137 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
8141 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
8142 return direct;
8143 }
8144 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
8145 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
8146 let mut path_matches = Vec::new();
8147 while let Some(raw_owner) = stack.pop() {
8148 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
8149 else {
8150 return EnclosingMemberOwnerResolution::Ambiguous;
8151 };
8152 let propagated = propagated_counts.entry(owner.clone()).or_default();
8156 if *propagated == 2 {
8157 continue;
8158 }
8159 *propagated += 1;
8160 match resolve_level(std::slice::from_ref(&owner)) {
8161 EnclosingMemberOwnerResolution::Owner(owner) => {
8162 path_matches.push(owner);
8163 if path_matches.len() == 2 {
8164 return EnclosingMemberOwnerResolution::Ambiguous;
8165 }
8166 }
8167 EnclosingMemberOwnerResolution::Ambiguous => {
8168 return EnclosingMemberOwnerResolution::Ambiguous;
8169 }
8170 EnclosingMemberOwnerResolution::Missing => {
8171 stack.extend(hierarchy.get_direct_ancestors(&owner));
8172 }
8173 }
8174 }
8175 match path_matches.len() {
8176 0 => EnclosingMemberOwnerResolution::Missing,
8177 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
8178 _ => unreachable!("base-path matches are capped at one before returning"),
8179 }
8180}
8181
8182pub fn resolve_declaring_callable_owner(
8197 analyzer: &CppGraphSource<'_>,
8198 visibility: &VisibilityIndex<'_>,
8199 file: &ProjectFile,
8200 ordinary: EnclosingMemberOwnerResolution,
8201 member_name: &str,
8202 call_arity: usize,
8203) -> EnclosingMemberOwnerResolution {
8204 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
8205 return ordinary;
8206 };
8207 if visibility
8208 .visible_members_for_owner_name(file, ordinary_owner, member_name)
8209 .into_iter()
8210 .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
8211 {
8212 return ordinary;
8213 }
8214
8215 let mut pending = match member_using_declaration_bases(
8216 analyzer,
8217 visibility,
8218 file,
8219 ordinary_owner,
8220 member_name,
8221 ) {
8222 Ok(bases) => bases,
8223 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8224 };
8225 let mut visited = HashSet::default();
8226 let mut introduced_owners = Vec::new();
8227 while let Some(owner) = pending.pop() {
8228 if !visited.insert(owner.clone()) {
8229 continue;
8230 }
8231 let accepts_arity = visibility
8232 .visible_members_for_owner_name(file, &owner, member_name)
8233 .into_iter()
8234 .any(|unit| {
8235 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
8236 });
8237 if accepts_arity {
8238 if !introduced_owners
8239 .iter()
8240 .any(|existing| same_visible_symbol(existing, &owner))
8241 {
8242 introduced_owners.push(owner);
8243 }
8244 continue;
8245 }
8246 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
8247 Ok(bases) => pending.extend(bases),
8248 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8249 }
8250 }
8251 match introduced_owners.as_slice() {
8252 [] => ordinary,
8253 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
8254 _ => EnclosingMemberOwnerResolution::Ambiguous,
8255 }
8256}
8257
8258fn member_using_declaration_bases(
8259 analyzer: &CppGraphSource<'_>,
8260 visibility: &VisibilityIndex<'_>,
8261 file: &ProjectFile,
8262 owner: &CodeUnit,
8263 member_name: &str,
8264) -> Result<Vec<CodeUnit>, ()> {
8265 let Some(source) = analyzer.get_source(owner, false) else {
8266 return Ok(Vec::new());
8267 };
8268 let scopes = cpp_member_using_declaration_scopes(&source, member_name);
8269 if scopes.is_empty() {
8270 return Ok(Vec::new());
8271 }
8272 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8273 return Ok(Vec::new());
8274 };
8275 let mut bases = Vec::new();
8276 for raw_ancestor in hierarchy.get_ancestors(owner) {
8277 let Some(ancestor) =
8278 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
8279 else {
8280 return Err(());
8281 };
8282 let qualified = cpp_name_for(&ancestor);
8283 if scopes
8284 .iter()
8285 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
8286 && !bases
8287 .iter()
8288 .any(|existing| same_visible_symbol(existing, &ancestor))
8289 {
8290 bases.push(ancestor);
8291 }
8292 }
8293 Ok(bases)
8294}
8295
8296pub fn lexical_component_tiers<'a>(
8297 components: &'a [String],
8298 global: bool,
8299 lexical_scope: &'a [String],
8300) -> impl Iterator<Item = Vec<String>> + 'a {
8301 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
8302 (0..=first_prefix_len).rev().map(move |prefix_len| {
8303 let mut qualified = Vec::with_capacity(prefix_len + components.len());
8304 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
8305 qualified.extend_from_slice(components);
8306 qualified
8307 })
8308}
8309
8310pub fn build_visible_identifier_index(
8311 analyzer: &CppGraphSource<'_>,
8312 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
8313 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
8314 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
8315) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
8316 let mut out = HashMap::default();
8317 for (file, visible) in visible_by_file {
8318 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
8319 for unit in visible {
8320 if unit.is_field()
8321 && !visible_source_files_by_root
8322 .get(file)
8323 .is_some_and(|sources| sources.contains(unit.source()))
8324 && cpp_global_field_has_internal_linkage_cached(
8325 analyzer,
8326 global_field_internal_linkage,
8327 unit,
8328 )
8329 {
8330 continue;
8331 }
8332 by_identifier
8333 .entry(unit.identifier().to_string())
8334 .or_default()
8335 .push(unit.clone());
8336 }
8337 for units in by_identifier.values_mut() {
8338 sort_lookup_units(units);
8339 units.dedup();
8340 }
8341 out.insert(file.clone(), by_identifier);
8342 }
8343 out
8344}
8345
8346fn sort_lookup_units(units: &mut [CodeUnit]) {
8347 units.sort_by(|left, right| {
8348 left.fq_name()
8349 .cmp(&right.fq_name())
8350 .then_with(|| left.signature().cmp(&right.signature()))
8351 .then_with(|| left.source().cmp(right.source()))
8352 .then_with(|| left.kind().cmp(&right.kind()))
8353 .then_with(|| {
8354 left.package_segment_count()
8355 .cmp(&right.package_segment_count())
8356 })
8357 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
8358 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
8359 });
8360}
8361
8362fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
8363 let interner = segment_interner();
8364 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
8365 let (left_text, left_kind) = interner.resolve(left_id);
8366 let (right_text, right_kind) = interner.resolve(right_id);
8367 let order = left_text
8368 .cmp(right_text)
8369 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
8370 if order != CmpOrdering::Equal {
8371 return order;
8372 }
8373 }
8374 left.len().cmp(&right.len())
8375}
8376
8377const fn segment_kind_order(kind: SegmentKind) -> u8 {
8378 match kind {
8379 SegmentKind::Path => 0,
8380 SegmentKind::Package => 1,
8381 SegmentKind::Type => 2,
8382 SegmentKind::Companion => 3,
8383 SegmentKind::Nested => 4,
8384 SegmentKind::Member => 5,
8385 SegmentKind::Unknown => 6,
8386 }
8387}
8388
8389fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
8390 let mut deduped = Vec::with_capacity(units.len());
8391 for unit in units.drain(..) {
8392 if !deduped.contains(&unit) {
8393 deduped.push(unit);
8394 }
8395 }
8396 *units = deduped;
8397}
8398
8399pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
8400 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8404 brokk_bifrost_core::analyzer::Language::Cpp,
8405 reference,
8406 );
8407 if parts.is_empty() {
8408 return Vec::new();
8409 }
8410
8411 let mut candidates = Vec::new();
8412 for package_len in 0..parts.len() {
8413 let package = parts[..package_len].join("::");
8414 let rest = &parts[package_len..];
8415 if rest.is_empty() {
8416 continue;
8417 }
8418 match kind {
8419 TargetKind::Type | TargetKind::Constructor => {
8420 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
8421 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
8422 }
8423 TargetKind::FreeFunction
8424 | TargetKind::Method
8425 | TargetKind::GlobalField
8426 | TargetKind::MemberField
8427 | TargetKind::Macro => {
8428 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
8429 if rest.len() > 1 {
8430 let owner = rest[..rest.len() - 1].join("$");
8431 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
8432 push_cpp_fqn_candidate(&mut candidates, &package, &short);
8433 }
8434 }
8435 }
8436 }
8437 candidates
8438}
8439
8440fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
8441 let fqn = if package.is_empty() {
8442 short.to_string()
8443 } else {
8444 format!("{package}.{short}")
8445 };
8446 if !out.contains(&fqn) {
8447 out.push(fqn);
8448 }
8449}
8450
8451pub fn infer_cpp_initializer_type(
8452 analyzer: &CppGraphSource<'_>,
8453 visibility: &VisibilityIndex<'_>,
8454 file: &ProjectFile,
8455 source: &str,
8456 node: Node<'_>,
8457) -> Option<CodeUnit> {
8458 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
8459 .and_then(|binding| binding.unit)
8460}
8461
8462pub fn infer_cpp_initializer_binding(
8463 analyzer: &CppGraphSource<'_>,
8464 visibility: &VisibilityIndex<'_>,
8465 file: &ProjectFile,
8466 source: &str,
8467 node: Node<'_>,
8468 receiver_resolver: Option<&ReceiverResolver<'_>>,
8469) -> Option<CppScanBinding> {
8470 match node.kind() {
8471 "new_expression" => {
8472 let text = normalize_cpp_whitespace(node_text(node, source));
8473 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
8474 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
8475 let name = normalize_cpp_type_name(type_text);
8476 Some(CppScanBinding::from_type_name(
8477 name.clone(),
8478 visibility.resolve_type(file, &name),
8479 1,
8480 ))
8481 }
8482 "call_expression" => node.child_by_field_name("function").and_then(|function| {
8483 if function.kind() == "field_expression" {
8491 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
8492 return resolve_field_method_call_return_binding(
8493 analyzer,
8494 visibility,
8495 file,
8496 source,
8497 function,
8498 arity,
8499 receiver_resolver,
8500 );
8501 }
8502 let function_text = node_text(function, source);
8503 let direct_type_binding = visibility
8504 .resolve_type(file, function_text)
8505 .map(|unit| CppScanBinding::from_unit(unit, 0));
8506 if function.kind() == "template_function" && direct_type_binding.is_some() {
8507 let lexical_namespace = enclosing_namespace_context(node, source);
8508 let arity = visibility.call_arity_evidence(file, node, source).exact();
8509 if let Some(arity) = arity
8510 && let Some(binding) = visibility.resolve_call_return_binding(
8511 analyzer,
8512 file,
8513 function_text,
8514 arity,
8515 lexical_namespace.as_deref(),
8516 direct_type_binding
8517 .as_ref()
8518 .and_then(|binding| binding.unit.as_ref()),
8519 )
8520 {
8521 return Some(binding);
8522 }
8523 let (has_callable, callable_binding) = visibility
8524 .resolve_call_return_binding_without_arity(
8525 analyzer,
8526 file,
8527 function_text,
8528 lexical_namespace.as_deref(),
8529 direct_type_binding
8530 .as_ref()
8531 .and_then(|binding| binding.unit.as_ref()),
8532 );
8533 if let Some(binding) = callable_binding {
8534 return Some(binding);
8535 }
8536 if has_callable {
8537 return None;
8538 }
8539 return direct_type_binding;
8540 }
8541 let arity = visibility.call_arity_evidence(file, node, source).exact();
8546 if let Some(arity) = arity {
8547 let direct_type_binding_for_call = direct_type_binding.clone();
8548 if let Some(binding) = resolve_static_method_call_return_binding(
8549 analyzer, visibility, file, source, function, arity,
8550 )
8551 .or_else(|| {
8552 visibility.resolve_call_return_binding(
8557 analyzer,
8558 file,
8559 function_text,
8560 arity,
8561 enclosing_namespace_context(node, source).as_deref(),
8562 direct_type_binding_for_call
8563 .as_ref()
8564 .and_then(|binding| binding.unit.as_ref()),
8565 )
8566 }) {
8567 return Some(binding);
8568 }
8569 }
8570 direct_type_binding
8571 }),
8572 _ => None,
8573 }
8574}
8575
8576fn resolve_static_method_call_return_binding(
8577 analyzer: &CppGraphSource<'_>,
8578 visibility: &VisibilityIndex<'_>,
8579 file: &ProjectFile,
8580 source: &str,
8581 function: Node<'_>,
8582 arity: usize,
8583) -> Option<CppScanBinding> {
8584 if function.kind() != "qualified_identifier" {
8585 return None;
8586 }
8587 let qualified = normalize_cpp_reference_text(node_text(function, source));
8588 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8595 brokk_bifrost_core::analyzer::Language::Cpp,
8596 &qualified,
8597 );
8598 let (owner_text, member_name) = match parts.split_last() {
8599 Some((member, owner_parts)) if !owner_parts.is_empty() => {
8600 (owner_parts.join("::"), member.clone())
8601 }
8602 _ => {
8603 let scope = function.child_by_field_name("scope")?;
8604 let name = function.child_by_field_name("name")?;
8605 (
8606 node_text(scope, source).to_string(),
8607 node_text(name, source).to_string(),
8608 )
8609 }
8610 };
8611 let owner = visibility.resolve_type(file, &owner_text)?;
8612 let candidates = visibility
8613 .visible_members_for_owner_name(file, &owner, &member_name)
8614 .into_iter()
8615 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
8616 .cloned()
8617 .collect::<Vec<_>>();
8618 unanimous_return_binding(analyzer, visibility, file, &candidates)
8619}
8620
8621fn resolve_field_method_call_return_binding(
8622 analyzer: &CppGraphSource<'_>,
8623 visibility: &VisibilityIndex<'_>,
8624 file: &ProjectFile,
8625 source: &str,
8626 function: Node<'_>,
8627 arity: usize,
8628 receiver_resolver: Option<&ReceiverResolver<'_>>,
8629) -> Option<CppScanBinding> {
8630 debug_assert_eq!(
8631 function.kind(),
8632 "field_expression",
8633 "the member-call return binding answers only for a field-expression callee"
8634 );
8635 let receiver_resolver = receiver_resolver?;
8636 let field = function.child_by_field_name("field")?;
8637 let member_name = node_text(function_terminal_node(field), source);
8638 let receiver = function
8639 .child_by_field_name("argument")
8640 .or_else(|| function.named_child(0))?;
8641 let owners = receiver_resolver(receiver, source);
8642 let mut candidates = Vec::new();
8643 for owner in owners {
8644 let declaring_owner =
8645 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
8646 EnclosingMemberOwnerResolution::Owner(owner) => owner,
8647 EnclosingMemberOwnerResolution::Missing => continue,
8648 EnclosingMemberOwnerResolution::Ambiguous => return None,
8649 };
8650 candidates.extend(
8651 visibility
8652 .visible_members_for_owner_name(file, &declaring_owner, member_name)
8653 .into_iter()
8654 .filter(|unit| {
8655 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
8656 })
8657 .cloned(),
8658 );
8659 }
8660 unanimous_return_binding(analyzer, visibility, file, &candidates)
8661}
8662
8663fn unanimous_return_binding(
8664 analyzer: &CppGraphSource<'_>,
8665 visibility: &VisibilityIndex<'_>,
8666 file: &ProjectFile,
8667 candidates: &[CodeUnit],
8668) -> Option<CppScanBinding> {
8669 let mut resolved_return: Option<CppScanBinding> = None;
8670 for function in candidates {
8671 let metadata = analyzer.signature_metadata(function);
8672 let return_types = if metadata.is_empty() {
8673 vec![cpp_function_return_type_text(analyzer, function)?]
8674 } else {
8675 metadata
8676 .iter()
8677 .map(|metadata| metadata.return_type_text().map(str::to_string))
8678 .collect::<Option<Vec<_>>>()?
8679 };
8680 for return_text in return_types {
8681 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
8682 let name = normalize_cpp_type_name(&return_text);
8683 let binding = CppScanBinding::from_type_name(
8684 name.clone(),
8685 visibility
8686 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
8687 indirection,
8688 );
8689 if let Some(existing) = resolved_return.as_ref()
8690 && (existing.indirection != binding.indirection
8691 || match (&existing.unit, &binding.unit) {
8692 (Some(left), Some(right)) => !same_visible_symbol(left, right),
8693 (None, None) => existing.type_name != binding.type_name,
8694 (Some(_), None) | (None, Some(_)) => true,
8695 })
8696 {
8697 return None;
8698 }
8699 resolved_return = Some(binding);
8700 }
8701 }
8702 resolved_return
8703}
8704
8705fn aliases_from_prepared_source(
8706 cpp: &dyn CppSource,
8707 token: QueryToken<'_>,
8708 file: &ProjectFile,
8709) -> Vec<CppAlias> {
8710 let Some(prepared) = cpp.prepared_syntax(token, file) else {
8711 return Vec::new();
8712 };
8713 let mut aliases = Vec::new();
8714 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
8715 aliases
8716}
8717
8718fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
8719 let mut stack = vec![root];
8720 while let Some(node) = stack.pop() {
8721 match node.kind() {
8722 "alias_declaration" if alias_has_visible_file_scope(node) => {
8723 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
8724 out.push(alias);
8725 }
8726 }
8727 "type_definition" if alias_has_visible_file_scope(node) => {
8728 collect_typedef_aliases(node, source, out)
8729 }
8730 _ => {}
8731 }
8732
8733 for index in (0..node.named_child_count()).rev() {
8734 if let Some(child) = node.named_child(index) {
8735 stack.push(child);
8736 }
8737 }
8738 }
8739}
8740
8741fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
8742 let mut current = node.parent();
8743 while let Some(parent) = current {
8744 match parent.kind() {
8745 "translation_unit"
8746 | "namespace_definition"
8747 | "declaration_list"
8748 | "linkage_specification" => current = parent.parent(),
8749 "template_declaration" => current = parent.parent(),
8750 _ => return false,
8751 }
8752 }
8753 true
8754}
8755
8756fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
8757 let name = node
8758 .child_by_field_name("name")
8759 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
8760 let target = node
8761 .child_by_field_name("type")
8762 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
8763 Some(CppAlias {
8764 name,
8765 target,
8766 namespace: enclosing_namespace_context(node, source),
8767 })
8768}
8769
8770fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
8771 let Some(type_node) = node.child_by_field_name("type") else {
8772 return;
8773 };
8774 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
8775 return;
8776 };
8777
8778 let mut cursor = node.walk();
8779 for child in node.named_children(&mut cursor) {
8780 if same_node(child, type_node) {
8781 continue;
8782 }
8783 if let Some(name) = extract_typedef_declarator_name(child, source) {
8784 out.push(CppAlias {
8785 name,
8786 target: target.clone(),
8787 namespace: enclosing_namespace_context(node, source),
8788 });
8789 }
8790 }
8791}
8792
8793fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
8794 match node.kind() {
8795 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
8796 normalize_reference_name(node_text(node, source))
8797 }
8798 _ => node
8799 .child_by_field_name("declarator")
8800 .or_else(|| node.child_by_field_name("name"))
8801 .or_else(|| last_named_child(node))
8802 .and_then(|child| extract_typedef_declarator_name(child, source)),
8803 }
8804}
8805
8806fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
8807 let count = node.named_child_count();
8808 if count == 0 {
8809 None
8810 } else {
8811 node.named_child(count - 1)
8812 }
8813}
8814
8815pub fn collect_include_closure(
8816 analyzer: &CppGraphSource<'_>,
8817 include_targets: &IncludeTargetIndex,
8818 file: &ProjectFile,
8819 out: &mut HashSet<ProjectFile>,
8820 cancellation: Option<&CancellationToken>,
8821) {
8822 let mut stack = vec![file.clone()];
8823 while let Some(file) = stack.pop() {
8824 if cancellation.is_some_and(CancellationToken::is_cancelled) {
8825 break;
8826 }
8827 if !out.insert(file.clone()) {
8828 continue;
8829 }
8830 let imports = analyzer.import_statements(&file);
8831 for include in cpp_include_paths(&imports) {
8832 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
8833 stack.push(target);
8834 }
8835 }
8836 }
8837}
8838
8839fn collect_visible_declarations(
8840 include_graph: &IncludeGraph,
8841 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
8842 file: &ProjectFile,
8843 visited: &mut HashSet<ProjectFile>,
8844 out: &mut HashSet<CodeUnit>,
8845 cancellation: Option<&CancellationToken>,
8846) {
8847 let mut stack = vec![file.clone()];
8848 while let Some(file) = stack.pop() {
8849 if cancellation.is_some_and(CancellationToken::is_cancelled) {
8850 break;
8851 }
8852 if !visited.insert(file.clone()) {
8853 continue;
8854 }
8855 if let Some(declarations) = declarations_by_file.get(&file) {
8856 out.extend(declarations.iter().cloned());
8857 }
8858 stack.extend(include_graph.targets(&file).iter().cloned());
8859 }
8860}
8861
8862pub fn signature_arity(signature: Option<&str>) -> usize {
8863 let Some(signature) = signature else {
8864 return 0;
8865 };
8866 let inner = signature
8867 .find('(')
8868 .and_then(|open| {
8869 signature[open + 1..]
8870 .find(')')
8871 .map(|close| &signature[open + 1..open + 1 + close])
8872 })
8873 .unwrap_or(signature)
8874 .trim();
8875 if inner.is_empty() || inner == "void" {
8876 return 0;
8877 }
8878 cpp_split_top_level_commas(inner).count()
8879}
8880
8881fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
8882 let source = format!("void __bifrost_macro_parameters({replacement});");
8883 let mut parser = Parser::new();
8884 parser
8885 .set_language(&tree_sitter_cpp::LANGUAGE.into())
8886 .ok()?;
8887 let tree = parser.parse(&source, None)?;
8888 let root = tree.root_node();
8889 if root.has_error() {
8890 return None;
8891 }
8892 let declaration = root.named_child(0)?;
8893 let declarator = declaration.child_by_field_name("declarator")?;
8894 let parameters = declarator.child_by_field_name("parameters")?;
8895 let mut required = 0;
8896 let mut total = 0;
8897 let mut repeated = false;
8898 let mut cursor = parameters.walk();
8899 for parameter in parameters.children(&mut cursor) {
8900 match parameter.kind() {
8901 "parameter_declaration" => {
8902 if parameter.child_by_field_name("declarator").is_none()
8903 && parameter
8904 .child_by_field_name("type")
8905 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
8906 {
8907 continue;
8908 }
8909 required += 1;
8910 total += 1;
8911 }
8912 "optional_parameter_declaration" => total += 1,
8913 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
8914 repeated = true;
8915 }
8916 _ => {}
8917 }
8918 }
8919 Some(CallableArity::new(required, total, repeated))
8920}
8921
8922pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
8923 analyzer
8924 .signature_metadata(unit)
8925 .into_iter()
8926 .find_map(|metadata| metadata.callable_arity())
8927 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
8928}
8929
8930pub fn cpp_callable_parameter_types(
8931 analyzer: &CppGraphSource<'_>,
8932 unit: &CodeUnit,
8933) -> Option<Vec<String>> {
8934 analyzer
8935 .signature_metadata(unit)
8936 .into_iter()
8937 .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
8938 .or_else(|| unit.signature().and_then(cpp_signature_param_types))
8939}
8940
8941fn merge_compatible_callable_arities(
8942 left: CallableArity,
8943 right: CallableArity,
8944) -> Option<CallableArity> {
8945 let total = left.total();
8946 let left_repeated = left.accepts(total.saturating_add(1));
8947 let right_repeated = right.accepts(right.total().saturating_add(1));
8948 if total != right.total() || left_repeated != right_repeated {
8949 return None;
8950 }
8951 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
8952 Some(CallableArity::new(required, total, left_repeated))
8953}
8954
8955fn find_include_activation(
8956 cpp: &dyn CppSource,
8957 token: QueryToken<'_>,
8958 file: &ProjectFile,
8959 prepared: &PreparedSyntaxTree,
8960 donor_source: &ProjectFile,
8961) -> Option<usize> {
8962 let include_targets = cpp.include_target_index();
8963 let mut direct_includes = Vec::new();
8964 let mut nodes = vec![prepared.tree().root_node()];
8965 let reference = CallableReferenceContext {
8968 file,
8969 position: None,
8970 };
8971 while let Some(node) = nodes.pop() {
8972 if node.kind() == "preproc_include" {
8973 if callable_preprocessor_context_is_visible_for_reference(
8974 node,
8975 prepared.source(),
8976 &reference,
8977 ) {
8978 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8979 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8980 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
8981 file,
8982 &include,
8983 include_targets,
8984 )) {
8985 direct_includes.push((node.end_byte(), target));
8986 }
8987 }
8988 }
8989 continue;
8990 }
8991 for index in (0..node.named_child_count()).rev() {
8992 if let Some(child) = node.named_child(index) {
8993 nodes.push(child);
8994 }
8995 }
8996 }
8997 direct_includes.sort_by_key(|(activation, _)| *activation);
8998 let mut known_missing = HashSet::default();
8999 direct_includes
9000 .into_iter()
9001 .find(|(_, direct)| {
9002 unconditional_include_reaches(
9003 cpp,
9004 token,
9005 include_targets,
9006 direct,
9007 donor_source,
9008 file,
9009 &mut known_missing,
9010 )
9011 })
9012 .map(|(activation, _)| activation)
9013}
9014
9015fn find_conditional_include_projection_index(
9016 cpp: &dyn CppSource,
9017 token: QueryToken<'_>,
9018 file: &ProjectFile,
9019 prepared: &PreparedSyntaxTree,
9020 on_state: &dyn Fn(),
9021) -> ConditionalIncludeProjectionIndex {
9022 let include_targets = cpp.include_target_index();
9023 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
9024 HashMap::default();
9025 let mut pending = Vec::new();
9026 let mut nodes = vec![prepared.tree().root_node()];
9027 while let Some(node) = nodes.pop() {
9028 if node.kind() == "preproc_include" {
9029 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
9030 else {
9031 continue;
9032 };
9033 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9034 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9035 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9036 file,
9037 &include,
9038 include_targets,
9039 )) else {
9040 continue;
9041 };
9042 pending.push((target, node.end_byte(), required_guards.clone()));
9043 }
9044 continue;
9045 }
9046 for index in (0..node.named_child_count()).rev() {
9047 if let Some(child) = node.named_child(index) {
9048 nodes.push(child);
9049 }
9050 }
9051 }
9052
9053 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
9065 HashMap::default();
9066 while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
9067 let guard_sets = expanded
9068 .entry((current_file.clone(), activation_byte))
9069 .or_default();
9070 if guard_sets
9071 .iter()
9072 .any(|existing| existing.is_subset(&required_guards))
9073 {
9074 continue;
9075 }
9076 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
9077 .drain(..)
9078 .partition(|existing| required_guards.is_subset(existing));
9079 *guard_sets = kept;
9080 guard_sets.push(required_guards.clone());
9081 if !evicted.is_empty()
9082 && let Some(projections) = projections_by_source.get_mut(¤t_file)
9083 {
9084 projections.retain(|projection| {
9085 projection.activation_byte != activation_byte
9086 || !evicted.contains(&projection.required_guards)
9087 });
9088 }
9089 on_state();
9090
9091 projections_by_source
9094 .entry(current_file.clone())
9095 .or_default()
9096 .push(ConditionalIncludeProjection {
9097 activation_byte,
9098 required_guards: required_guards.clone(),
9099 });
9100
9101 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
9102 continue;
9103 };
9104 let mut nodes = vec![current_prepared.tree().root_node()];
9105 while let Some(node) = nodes.pop() {
9106 if node.kind() == "preproc_include" {
9107 let Some(include_guards) =
9108 preprocessor_guard_environment(node, current_prepared.source())
9109 else {
9110 continue;
9111 };
9112 let Some(path_guards) =
9113 merge_preprocessor_guards(&required_guards, &include_guards)
9114 else {
9115 continue;
9116 };
9117 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9118 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9119 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9120 ¤t_file,
9121 &include,
9122 include_targets,
9123 )) else {
9124 continue;
9125 };
9126 pending.push((target, activation_byte, path_guards.clone()));
9127 }
9128 continue;
9129 }
9130 for index in (0..node.named_child_count()).rev() {
9131 if let Some(child) = node.named_child(index) {
9132 nodes.push(child);
9133 }
9134 }
9135 }
9136 }
9137
9138 projections_by_source
9139 .into_iter()
9140 .map(|(source, mut projections)| {
9141 projections.sort_by_key(|projection| projection.activation_byte);
9142 (source, Arc::from(projections))
9143 })
9144 .collect()
9145}
9146
9147#[allow(clippy::too_many_arguments)]
9152fn find_conditional_include_projection_for_source(
9153 cpp: &dyn CppSource,
9154 token: QueryToken<'_>,
9155 file: &ProjectFile,
9156 prepared: &PreparedSyntaxTree,
9157 donor_source: &ProjectFile,
9158 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9159 reference_byte: usize,
9160 on_state: &dyn Fn(),
9161) -> bool {
9162 let Some(reference_guards) = reference_guards else {
9163 return false;
9164 };
9165 let include_targets = cpp.include_target_index();
9166 let mut pending = Vec::new();
9167 let mut nodes = vec![prepared.tree().root_node()];
9168 while let Some(node) = nodes.pop() {
9169 if node.kind() == "preproc_include" {
9170 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
9171 else {
9172 continue;
9173 };
9174 if node.end_byte() > reference_byte
9175 || !guard_requirements_hold_at_reference(&required_guards, Some(reference_guards))
9176 {
9177 continue;
9178 }
9179 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9180 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9181 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9182 file,
9183 &include,
9184 include_targets,
9185 )) else {
9186 continue;
9187 };
9188 if &target == donor_source {
9189 return true;
9190 }
9191 pending.push((target, required_guards.clone()));
9192 }
9193 continue;
9194 }
9195 for index in (0..node.named_child_count()).rev() {
9196 if let Some(child) = node.named_child(index) {
9197 nodes.push(child);
9198 }
9199 }
9200 }
9201
9202 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
9203 while let Some((current_file, required_guards)) = pending.pop() {
9204 let guard_sets = expanded.entry(current_file.clone()).or_default();
9205 if guard_sets.contains(&required_guards) {
9206 continue;
9207 }
9208 guard_sets.push(required_guards.clone());
9209 on_state();
9210
9211 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
9212 continue;
9213 };
9214 let mut nodes = vec![current_prepared.tree().root_node()];
9215 while let Some(node) = nodes.pop() {
9216 if node.kind() == "preproc_include" {
9217 let Some(include_guards) =
9218 preprocessor_guard_environment(node, current_prepared.source())
9219 else {
9220 continue;
9221 };
9222 let Some(path_guards) =
9223 merge_preprocessor_guards(&required_guards, &include_guards)
9224 else {
9225 continue;
9226 };
9227 if !guard_requirements_hold_at_reference(&path_guards, Some(reference_guards)) {
9228 continue;
9229 }
9230 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9231 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9232 let Some(target) = unique_include_target(resolve_include_targets_with_index(
9233 ¤t_file,
9234 &include,
9235 include_targets,
9236 )) else {
9237 continue;
9238 };
9239 if &target == donor_source {
9240 return true;
9241 }
9242 pending.push((target, path_guards.clone()));
9243 }
9244 continue;
9245 }
9246 for index in (0..node.named_child_count()).rev() {
9247 if let Some(child) = node.named_child(index) {
9248 nodes.push(child);
9249 }
9250 }
9251 }
9252 }
9253 false
9254}
9255
9256pub fn cpp_include_closure_reaches(
9269 cpp: &dyn CppSource,
9270 token: QueryToken<'_>,
9271 translation_unit: &ProjectFile,
9272 header: &ProjectFile,
9273) -> bool {
9274 unconditional_include_reaches(
9275 cpp,
9276 token,
9277 cpp.include_target_index(),
9278 translation_unit,
9279 header,
9280 translation_unit,
9281 &mut HashSet::default(),
9282 )
9283}
9284
9285fn unconditional_include_reaches(
9286 cpp: &dyn CppSource,
9287 token: QueryToken<'_>,
9288 include_targets: &IncludeTargetIndex,
9289 first: &ProjectFile,
9290 donor_source: &ProjectFile,
9291 reference_file: &ProjectFile,
9292 known_missing: &mut HashSet<ProjectFile>,
9293) -> bool {
9294 if first == donor_source {
9295 return true;
9296 }
9297 if known_missing.contains(first) {
9298 return false;
9299 }
9300 let reference_is_c = reference_file
9301 .rel_path()
9302 .extension()
9303 .and_then(|extension| extension.to_str())
9304 == Some("c");
9305 if let Some(reaches) =
9306 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
9307 {
9308 return reaches;
9309 }
9310 let mut visited = HashSet::default();
9311 let mut files = vec![first.clone()];
9312 let reference = CallableReferenceContext {
9315 file: reference_file,
9316 position: None,
9317 };
9318 while let Some(file) = files.pop() {
9319 if file == *donor_source {
9320 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
9321 return true;
9322 }
9323 if known_missing.contains(&file) || !visited.insert(file.clone()) {
9324 continue;
9325 }
9326 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
9327 continue;
9328 };
9329 let mut nodes = vec![prepared.tree().root_node()];
9330 while let Some(node) = nodes.pop() {
9331 if node.kind() == "preproc_include" {
9332 if callable_preprocessor_context_is_visible_for_reference(
9333 node,
9334 prepared.source(),
9335 &reference,
9336 ) {
9337 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9338 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9339 if let Some(target) = unique_include_target(
9340 resolve_include_targets_with_index(&file, &include, include_targets),
9341 ) {
9342 files.push(target);
9343 }
9344 }
9345 }
9346 continue;
9347 }
9348 for index in (0..node.named_child_count()).rev() {
9349 if let Some(child) = node.named_child(index) {
9350 nodes.push(child);
9351 }
9352 }
9353 }
9354 }
9355 known_missing.extend(visited);
9356 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
9357 false
9358}
9359
9360fn declaration_guard_requirements(
9361 analyzer: &CppGraphSource<'_>,
9362 cpp: &dyn CppSource,
9363 candidate: &CodeUnit,
9364) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
9365 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
9366 return Vec::new();
9367 };
9368 let root = prepared.tree().root_node();
9369 analyzer
9370 .ranges(candidate)
9371 .into_iter()
9372 .filter_map(|range| {
9373 root.descendant_for_byte_range(range.start_byte, range.end_byte)
9374 .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
9375 .map(|required| (range.start_byte, required))
9379 })
9380 .collect()
9381}
9382
9383fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
9384 analyzer
9385 .ranges(candidate)
9386 .into_iter()
9387 .map(|range| range.start_byte)
9388 .min()
9389}
9390
9391fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
9397 let (first, rest) = contexts.split_first()?;
9398 Some(
9399 first
9400 .defined_macros
9401 .iter()
9402 .filter(|name| {
9403 rest.iter()
9404 .all(|context| context.defined_macros.contains(*name))
9405 })
9406 .cloned()
9407 .collect(),
9408 )
9409}
9410
9411fn guard_requirements_hold_at_reference(
9412 required: &HashSet<PreprocessorGuard>,
9413 reference: Option<&HashSet<PreprocessorGuard>>,
9414) -> bool {
9415 reference.is_some_and(|active| {
9416 required
9417 .iter()
9418 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
9419 })
9420}
9421
9422fn preprocessor_guard_holds_at_reference(
9423 required: &PreprocessorGuard,
9424 active: &HashSet<PreprocessorGuard>,
9425) -> bool {
9426 if active.contains(required) {
9427 return true;
9428 }
9429 let active_expression = BooleanGuardExpression::all(
9430 active
9431 .iter()
9432 .filter_map(PreprocessorGuard::as_boolean_expression),
9433 );
9434 required
9435 .as_boolean_expression()
9436 .is_some_and(|required| active_expression.implies(&required))
9437}
9438
9439fn guards_compatible_at_reference(
9444 declaration: &HashSet<PreprocessorGuard>,
9445 reference: Option<&HashSet<PreprocessorGuard>>,
9446) -> bool {
9447 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
9448}
9449
9450pub fn preprocessor_conditional_family_range(
9459 root: Node<'_>,
9460 start_byte: usize,
9461 end_byte: usize,
9462) -> Option<(usize, usize)> {
9463 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
9464 let mut ancestor = Some(node);
9465 while let Some(current) = ancestor {
9466 if is_preprocessor_conditional(current)
9467 && preprocessor_conditional_contains_descendant(current, node)
9468 {
9469 let family = preprocessor_conditional_family_root(current);
9470 return Some((family.start_byte(), family.end_byte()));
9471 }
9472 ancestor = current.parent();
9473 }
9474 None
9475}
9476
9477fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
9478 let mut ancestor = node.parent();
9479 while let Some(current) = ancestor {
9480 if is_preprocessor_conditional(current)
9481 && preprocessor_conditional_contains_descendant(current, node)
9482 {
9483 let family = preprocessor_conditional_family_root(current);
9484 if preprocessor_conditional_family_has_terminal_else(family) {
9485 return Some(family);
9486 }
9487 }
9488 ancestor = current.parent();
9489 }
9490 None
9491}
9492
9493fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
9494 while let Some(parent) = conditional.parent() {
9495 let is_alternative = parent
9496 .child_by_field_name("alternative")
9497 .is_some_and(|alternative| {
9498 alternative.start_byte() == conditional.start_byte()
9499 && alternative.end_byte() == conditional.end_byte()
9500 });
9501 if !is_alternative {
9502 break;
9503 }
9504 conditional = parent;
9505 }
9506 conditional
9507}
9508
9509fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
9510 loop {
9511 let Some(alternative) = conditional.child_by_field_name("alternative") else {
9512 return false;
9513 };
9514 match alternative.kind() {
9515 "preproc_else" => return true,
9516 "preproc_elif" => conditional = alternative,
9517 _ => return false,
9518 }
9519 }
9520}
9521
9522pub fn preprocessor_guard_environment(
9523 node: Node<'_>,
9524 source: &str,
9525) -> Option<HashSet<PreprocessorGuard>> {
9526 let mut guards = HashSet::default();
9527 let mut ancestor = node.parent();
9528 while let Some(conditional) = ancestor {
9529 if matches!(
9530 conditional.kind(),
9531 "preproc_if" | "preproc_ifdef" | "preproc_elif"
9532 ) && !is_file_covering_include_guard(conditional, source)
9533 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
9534 && preprocessor_conditional_contains_descendant(conditional, node)
9535 {
9536 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
9537 match guard {
9538 PreprocessorGuard::Constant(true) => {
9539 ancestor = conditional.parent();
9540 continue;
9541 }
9542 PreprocessorGuard::Constant(false) => return None,
9543 _ => {}
9544 }
9545 if guards.contains(&guard.negated()) {
9546 return None;
9547 }
9548 guards.insert(guard);
9549 }
9550 ancestor = conditional.parent();
9551 }
9552 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
9553 match guard {
9554 PreprocessorGuard::Constant(true) => {}
9555 PreprocessorGuard::Constant(false) => return None,
9556 _ => {
9557 if guards.contains(&guard.negated()) {
9558 return None;
9559 }
9560 guards.insert(guard);
9561 }
9562 }
9563 }
9564 Some(guards)
9565}
9566
9567fn fragmented_statement_preprocessor_guard(
9568 descendant: Node<'_>,
9569 source: &str,
9570) -> Option<PreprocessorGuard> {
9571 let mut ancestor = descendant.parent();
9577 while let Some(statement) = ancestor {
9578 if statement.kind() == "if_statement"
9579 && let (Some(consequence), Some(alternative)) = (
9580 statement.child_by_field_name("consequence"),
9581 statement.child_by_field_name("alternative"),
9582 )
9583 && alternative.start_byte() <= descendant.start_byte()
9584 && descendant.end_byte() <= alternative.end_byte()
9585 {
9586 let mut cursor = consequence.walk();
9587 let openers = consequence
9588 .named_children(&mut cursor)
9589 .filter(|child| {
9590 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
9591 && child
9592 .child(child.child_count().saturating_sub(1))
9593 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
9594 })
9595 .collect::<Vec<_>>();
9596 if openers.len() != 1 {
9597 ancestor = statement.parent();
9598 continue;
9599 }
9600
9601 let mut terminators = Vec::new();
9602 let mut stack = vec![alternative];
9603 while let Some(node) = stack.pop() {
9604 if node.kind() == "preproc_call"
9605 && node.start_byte() >= descendant.end_byte()
9606 && node
9607 .child_by_field_name("directive")
9608 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
9609 {
9610 terminators.push(node);
9611 continue;
9612 }
9613 for index in (0..node.named_child_count()).rev() {
9614 if let Some(child) = node.named_child(index) {
9615 stack.push(child);
9616 }
9617 }
9618 }
9619 if terminators.len() == 1 {
9620 return simple_preprocessor_guard(openers[0], source);
9621 }
9622 }
9623 ancestor = statement.parent();
9624 }
9625 None
9626}
9627
9628fn preprocessor_guard_for_descendant(
9629 conditional: Node<'_>,
9630 descendant: Node<'_>,
9631 source: &str,
9632) -> Option<PreprocessorGuard> {
9633 let mut guard = simple_preprocessor_guard(conditional, source)?;
9634 if conditional
9635 .child_by_field_name("alternative")
9636 .is_some_and(|alternative| {
9637 alternative.start_byte() <= descendant.start_byte()
9638 && descendant.end_byte() <= alternative.end_byte()
9639 })
9640 {
9641 let alternative = conditional.child_by_field_name("alternative")?;
9642 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
9646 return None;
9647 }
9648 guard = guard.negated();
9649 }
9650 Some(guard)
9651}
9652
9653fn preprocessor_conditional_contains_descendant(
9654 conditional: Node<'_>,
9655 descendant: Node<'_>,
9656) -> bool {
9657 cpp_displaced_preprocessor_boundary(conditional)
9658 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
9659}
9660
9661pub fn merge_preprocessor_guards(
9662 left: &HashSet<PreprocessorGuard>,
9663 right: &HashSet<PreprocessorGuard>,
9664) -> Option<HashSet<PreprocessorGuard>> {
9665 let mut merged = left.clone();
9666 for guard in right {
9667 if merged.contains(&guard.negated()) {
9668 return None;
9669 }
9670 merged.insert(guard.clone());
9671 }
9672 Some(merged)
9673}
9674
9675fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
9676 if conditional.kind() == "preproc_ifdef" {
9677 let name = conditional.child_by_field_name("name")?;
9678 let name = node_text(name, source).to_string();
9679 return match conditional.child(0)?.kind() {
9680 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
9681 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
9682 _ => None,
9683 };
9684 }
9685 let condition = conditional.child_by_field_name("condition")?;
9686 simple_preprocessor_expression_guard(condition, source).or_else(|| {
9687 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
9688 node_text(condition, source),
9689 )))
9690 })
9691}
9692
9693fn simple_preprocessor_expression_guard(
9694 expression: Node<'_>,
9695 source: &str,
9696) -> Option<PreprocessorGuard> {
9697 match expression.kind() {
9698 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
9699 node_text(expression, source).to_string(),
9700 ))),
9701 "number_literal" => match node_text(expression, source).trim() {
9702 "0" => Some(PreprocessorGuard::Constant(false)),
9703 "1" => Some(PreprocessorGuard::Constant(true)),
9704 _ => None,
9705 },
9706 "preproc_defined" => {
9707 let identifier = (0..expression.named_child_count())
9708 .filter_map(|index| expression.named_child(index))
9709 .find(|child| child.kind() == "identifier")?;
9710 Some(PreprocessorGuard::Defined(
9711 node_text(identifier, source).to_string(),
9712 ))
9713 }
9714 "unary_expression"
9715 if expression
9716 .child_by_field_name("operator")
9717 .is_some_and(|operator| operator.kind() == "!") =>
9718 {
9719 simple_preprocessor_expression_guard(
9720 expression.child_by_field_name("argument")?,
9721 source,
9722 )
9723 .map(|guard| guard.negated())
9724 }
9725 "parenthesized_expression" => (0..expression.named_child_count())
9726 .filter_map(|index| expression.named_child(index))
9727 .next()
9728 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
9729 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
9730 expression, source,
9731 ))),
9732 _ => None,
9733 }
9734}
9735
9736fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
9737 match expression.kind() {
9738 "number_literal" => match node_text(expression, source).trim() {
9739 "0" => BooleanGuardExpression::Constant(false),
9740 "1" => BooleanGuardExpression::Constant(true),
9741 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9742 expression, source,
9743 ))),
9744 },
9745 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
9746 "preproc_defined" => {
9747 let identifier = (0..expression.named_child_count())
9748 .filter_map(|index| expression.named_child(index))
9749 .find(|child| child.kind() == "identifier");
9750 identifier.map_or_else(
9751 || {
9752 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9753 expression, source,
9754 )))
9755 },
9756 |identifier| {
9757 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
9758 },
9759 )
9760 }
9761 "unary_expression"
9762 if expression
9763 .child_by_field_name("operator")
9764 .is_some_and(|operator| operator.kind() == "!") =>
9765 {
9766 expression.child_by_field_name("argument").map_or_else(
9767 || {
9768 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9769 expression, source,
9770 )))
9771 },
9772 |argument| boolean_preprocessor_expression(argument, source).negated(),
9773 )
9774 }
9775 "parenthesized_expression" => (0..expression.named_child_count())
9776 .filter_map(|index| expression.named_child(index))
9777 .next()
9778 .map_or_else(
9779 || {
9780 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9781 expression, source,
9782 )))
9783 },
9784 |child| boolean_preprocessor_expression(child, source),
9785 ),
9786 "binary_expression" => {
9787 let operands = || {
9788 Some((
9789 boolean_preprocessor_expression(
9790 expression.child_by_field_name("left")?,
9791 source,
9792 ),
9793 boolean_preprocessor_expression(
9794 expression.child_by_field_name("right")?,
9795 source,
9796 ),
9797 ))
9798 };
9799 match expression
9800 .child_by_field_name("operator")
9801 .map(|operator| operator.kind())
9802 {
9803 Some("&&") => operands().map_or_else(
9804 || {
9805 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9806 expression, source,
9807 )))
9808 },
9809 |(left, right)| BooleanGuardExpression::all([left, right]),
9810 ),
9811 Some("||") => operands().map_or_else(
9812 || {
9813 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9814 expression, source,
9815 )))
9816 },
9817 |(left, right)| BooleanGuardExpression::any([left, right]),
9818 ),
9819 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9820 expression, source,
9821 ))),
9822 }
9823 }
9824 _ => {
9825 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
9826 }
9827 }
9828}
9829
9830fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
9831 if targets.len() == 1 {
9832 targets.pop()
9833 } else {
9834 None
9835 }
9836}
9837
9838fn nameable_callable_declaration_nodes<'tree>(
9847 analyzer: &CppGraphSource<'_>,
9848 prepared: &'tree PreparedSyntaxTree,
9849 candidate: &CodeUnit,
9850) -> Vec<Node<'tree>> {
9851 let root = prepared.tree().root_node();
9852 analyzer
9853 .ranges(candidate)
9854 .into_iter()
9855 .filter_map(|range| {
9856 let mut declaration =
9857 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
9858 while !matches!(
9862 declaration.kind(),
9863 "declaration" | "field_declaration" | "function_definition"
9864 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
9865 declaration,
9866 prepared.source(),
9867 ) {
9868 declaration = declaration.parent()?;
9869 }
9870 let mut ancestor = declaration.parent();
9871 while let Some(node) = ancestor {
9872 if node.kind() == "function_definition"
9873 && is_recovered_declaration_scope_container(node, prepared.source())
9874 {
9875 ancestor = node.parent();
9876 continue;
9877 }
9878 if node.kind() == "compound_statement"
9879 && node.parent().is_some_and(|parent| {
9880 is_recovered_declaration_scope_container(parent, prepared.source())
9881 })
9882 {
9883 ancestor = node.parent().and_then(|parent| parent.parent());
9884 continue;
9885 }
9886 if matches!(
9887 node.kind(),
9888 "compound_statement" | "function_definition" | "lambda_expression"
9889 ) {
9890 return None;
9891 }
9892 ancestor = node.parent();
9893 }
9894 Some(declaration)
9895 })
9896 .collect()
9897}
9898
9899fn callable_declaration_activation_in_file(
9900 analyzer: &CppGraphSource<'_>,
9901 prepared: &PreparedSyntaxTree,
9902 candidate: &CodeUnit,
9903 reference: &CallableReferenceContext<'_>,
9904) -> Option<usize> {
9905 nameable_callable_declaration_nodes(analyzer, prepared, candidate)
9906 .into_iter()
9907 .filter(|declaration| {
9908 callable_preprocessor_context_is_visible_for_reference(
9909 *declaration,
9910 prepared.source(),
9911 reference,
9912 )
9913 })
9914 .map(callable_declaration_activation_byte)
9915 .min()
9916}
9917
9918fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
9923 if declaration.kind() != "function_definition" {
9924 return declaration.end_byte();
9925 }
9926 declaration
9927 .child_by_field_name("declarator")
9928 .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
9929}
9930
9931struct CallableReferenceContext<'a> {
9937 file: &'a ProjectFile,
9938 position: Option<CallableReferencePosition<'a>>,
9939}
9940
9941struct CallableReferencePosition<'a> {
9945 prepared: &'a PreparedSyntaxTree,
9946 byte: usize,
9947 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
9948}
9949
9950impl CallableReferenceContext<'_> {
9951 fn is_c(&self) -> bool {
9952 self.file
9953 .rel_path()
9954 .extension()
9955 .and_then(|extension| extension.to_str())
9956 == Some("c")
9957 }
9958
9959 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
9960 let position = self.position.as_ref()?;
9961 position
9962 .guards
9963 .get_or_init(|| {
9964 position
9965 .prepared
9966 .tree()
9967 .root_node()
9968 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
9969 .and_then(|node| {
9970 preprocessor_guard_environment(node, position.prepared.source())
9971 })
9972 })
9973 .as_ref()
9974 }
9975}
9976
9977fn callable_preprocessor_context_is_visible_for_reference(
9978 node: Node<'_>,
9979 source: &str,
9980 reference: &CallableReferenceContext<'_>,
9981) -> bool {
9982 let reference_is_c = reference.is_c();
9983 let mut ancestor = node.parent();
9984 while let Some(conditional) = ancestor {
9985 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
9986 && !is_file_covering_include_guard(conditional, source)
9987 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
9988 && preprocessor_conditional_contains_descendant(conditional, node)
9989 {
9990 let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
9991 return false;
9992 };
9993 match guard {
9994 PreprocessorGuard::Constant(true) => {}
9995 PreprocessorGuard::Constant(false) => return false,
9996 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
9997 if reference_is_c {
9998 return false;
9999 }
10000 }
10001 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
10002 if !reference_is_c {
10003 return false;
10004 }
10005 }
10006 guard => {
10012 if !reference
10013 .guards()
10014 .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
10015 {
10016 return false;
10017 }
10018 }
10019 }
10020 }
10021 ancestor = conditional.parent();
10022 }
10023 true
10024}
10025
10026fn flattened_macro_namespace_declaration_matches(
10027 analyzer: &CppGraphSource<'_>,
10028 cpp: &dyn CppSource,
10029 reference_file: &ProjectFile,
10030 visible_declaration: &CodeUnit,
10031 qualified_candidate: &CodeUnit,
10032 reference_byte: usize,
10033) -> bool {
10034 if visible_declaration.kind() != qualified_candidate.kind()
10040 || visible_declaration.identifier() != qualified_candidate.identifier()
10041 || visible_declaration.signature() != qualified_candidate.signature()
10042 || !visible_declaration.package_name().is_empty()
10043 || qualified_candidate.package_name().is_empty()
10044 {
10045 return false;
10046 }
10047
10048 let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
10049 return false;
10050 };
10051 let root = prepared.tree().root_node();
10052 let closing_brace_limit = if visible_declaration.source() == reference_file {
10053 reference_byte
10054 } else {
10055 usize::MAX
10056 };
10057
10058 analyzer
10059 .ranges(visible_declaration)
10060 .into_iter()
10061 .any(|range| {
10062 let Some(mut declaration) =
10063 root.descendant_for_byte_range(range.start_byte, range.end_byte)
10064 else {
10065 return false;
10066 };
10067 while !matches!(
10068 declaration.kind(),
10069 "declaration" | "field_declaration" | "function_definition"
10070 ) {
10071 let Some(parent) = declaration.parent() else {
10072 return false;
10073 };
10074 declaration = parent;
10075 }
10076 if declaration
10077 .parent()
10078 .is_none_or(|parent| parent.kind() != "translation_unit")
10079 || !macro_displaced_cpp_return_type(declaration, prepared.source())
10080 {
10081 return false;
10082 }
10083
10084 let mut cursor = root.walk();
10085 root.named_children(&mut cursor).any(|sibling| {
10086 sibling.start_byte() >= declaration.end_byte()
10087 && sibling.start_byte() < closing_brace_limit
10088 && direct_unmatched_closing_brace(sibling)
10089 })
10090 })
10091}
10092
10093fn flattened_macro_namespace_components(
10094 declaration: Node<'_>,
10095 source: &str,
10096) -> Option<Vec<String>> {
10097 flattened_macro_function_namespace_components(declaration, source)
10098 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
10099}
10100
10101fn flattened_macro_function_namespace_components(
10102 declaration: Node<'_>,
10103 source: &str,
10104) -> Option<Vec<String>> {
10105 let body = declaration
10106 .parent()
10107 .filter(|parent| parent.kind() == "compound_statement")?;
10108 let function = body.parent()?;
10109 if function.child_by_field_name("body") != Some(body) {
10110 return None;
10111 }
10112 let namespace_name = recovered_macro_namespace_name(function, source)?;
10113 let mut components = enclosing_namespace_components(declaration, source)?;
10114 components.push(namespace_name);
10115 Some(components)
10116}
10117
10118fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
10129 if function.kind() != "function_definition" || !function.has_error() {
10130 return None;
10131 }
10132 let body = function
10133 .child_by_field_name("body")
10134 .filter(|body| body.kind() == "compound_statement")?;
10135 let mut cursor = function.walk();
10136 let prefix = function
10137 .named_children(&mut cursor)
10138 .take_while(|child| child.start_byte() < body.start_byte())
10139 .filter(|child| child.kind() != "comment")
10140 .collect::<Vec<_>>();
10141 let begin_index = prefix.iter().rposition(|child| {
10142 flattened_macro_sentinel_name(*child, source)
10143 .is_some_and(|name| is_namespace_begin_sentinel(&name))
10144 })?;
10145 let mut identifiers = Vec::new();
10146 let mut stack = prefix[begin_index + 1..]
10147 .iter()
10148 .rev()
10149 .copied()
10150 .collect::<Vec<_>>();
10151 while let Some(current) = stack.pop() {
10152 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
10153 identifiers.push(identifier);
10154 continue;
10155 }
10156 let mut cursor = current.walk();
10157 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
10158 stack.extend(children.into_iter().rev());
10159 }
10160 let [keyword, namespace_name] = identifiers.as_slice() else {
10161 return None;
10162 };
10163 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
10164 {
10165 return None;
10166 }
10167 let mut next = function.next_named_sibling();
10168 let next = loop {
10169 let candidate = next?;
10170 next = candidate.next_named_sibling();
10171 if candidate.kind() != "comment" {
10172 break candidate;
10173 }
10174 };
10175 flattened_macro_sentinel_name(next, source)
10176 .is_some_and(|name| is_namespace_end_sentinel(&name))
10177 .then(|| namespace_name.clone())
10178}
10179
10180fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
10185 crate::declarations::is_recovered_exported_class_container(node, source)
10186 || recovered_macro_namespace_name(node, source).is_some()
10187}
10188
10189fn flattened_macro_error_namespace_components(
10190 declaration: Node<'_>,
10191 source: &str,
10192) -> Option<Vec<String>> {
10193 let parent = declaration
10194 .parent()
10195 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
10196 let mut cursor = parent.walk();
10197 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
10198 let declaration_index = siblings
10199 .iter()
10200 .position(|candidate| same_node(*candidate, declaration))?;
10201 let begin_index = (0..declaration_index).rev().find(|index| {
10202 flattened_macro_sentinel_name(siblings[*index], source)
10203 .is_some_and(|name| is_namespace_begin_sentinel(&name))
10204 })?;
10205
10206 let significant = siblings[begin_index + 1..declaration_index]
10207 .iter()
10208 .copied()
10209 .filter(|node| node.kind() != "comment")
10210 .collect::<Vec<_>>();
10211 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
10212 return None;
10213 };
10214 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
10215 return None;
10216 }
10217 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
10218 if significant[2..].iter().any(|node| {
10219 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
10220 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
10221 })
10222 }) {
10223 return None;
10224 }
10225
10226 let mut saw_namespace_close = false;
10227 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
10228 if sibling.kind() == "comment" {
10229 continue;
10230 }
10231 if !saw_namespace_close {
10232 if direct_unmatched_closing_brace(sibling) {
10233 saw_namespace_close = true;
10234 continue;
10235 }
10236 if flattened_macro_sentinel_name(sibling, source).is_some() {
10237 return None;
10238 }
10239 continue;
10240 }
10241 if !flattened_macro_sentinel_name(sibling, source)
10242 .is_some_and(|name| is_namespace_end_sentinel(&name))
10243 {
10244 return None;
10245 }
10246 let mut components = enclosing_namespace_components(declaration, source)?;
10247 components.push(namespace_name);
10248 return Some(components);
10249 }
10250 None
10251}
10252
10253fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
10254 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
10258 node.named_child(0)?
10259 } else {
10260 node
10261 };
10262 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
10263 node.child_by_field_name("type")
10264 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
10265 })?;
10266 (cpp_export_macro_token(&candidate)
10267 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
10268 .then_some(candidate)
10269}
10270
10271fn is_namespace_begin_sentinel(name: &str) -> bool {
10274 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
10275}
10276
10277fn is_namespace_end_sentinel(name: &str) -> bool {
10278 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
10279}
10280
10281fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
10282 if node.kind() != "ERROR" || node.named_child_count() != 1 {
10283 return None;
10284 }
10285 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
10286 (!cpp_export_macro_token(&name)).then_some(name)
10287}
10288
10289fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
10290 if !matches!(
10291 node.kind(),
10292 "identifier" | "namespace_identifier" | "type_identifier"
10293 ) {
10294 return None;
10295 }
10296 let name = normalize_cpp_whitespace(node_text(node, source));
10297 (!name.is_empty()).then_some(name)
10298}
10299
10300fn guard_requirement_sets_match(
10301 left: &[(usize, HashSet<PreprocessorGuard>)],
10302 right: &[(usize, HashSet<PreprocessorGuard>)],
10303) -> bool {
10304 left.len() == right.len()
10305 && left.iter().all(|(_, left_guards)| {
10306 right
10307 .iter()
10308 .any(|(_, right_guards)| left_guards == right_guards)
10309 })
10310 && right.iter().all(|(_, right_guards)| {
10311 left.iter()
10312 .any(|(_, left_guards)| right_guards == left_guards)
10313 })
10314}
10315
10316fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
10317 let Some(type_node) = declaration.child_by_field_name("type") else {
10318 return false;
10319 };
10320 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
10321 !type_name.is_empty()
10322 && type_name
10323 .chars()
10324 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
10325 && (0..declaration.named_child_count()).any(|index| {
10326 declaration
10327 .named_child(index)
10328 .is_some_and(|child| child.kind() == "ERROR")
10329 })
10330}
10331
10332fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
10333 node.kind() == "ERROR"
10334 && (0..node.child_count())
10335 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
10336}
10337
10338fn unmatched_closing_brace_is_followed_by_semicolon(node: Node<'_>) -> bool {
10339 let mut following = node.next_named_sibling();
10340 let following = loop {
10341 match following {
10342 Some(candidate) if candidate.kind() == "comment" => {
10343 following = candidate.next_named_sibling();
10344 continue;
10345 }
10346 candidate => break candidate,
10347 }
10348 };
10349 following.is_some_and(|candidate| {
10350 candidate.kind() == "expression_statement"
10351 && candidate.named_child_count() == 0
10352 && (0..candidate.child_count()).any(|index| {
10353 candidate
10354 .child(index)
10355 .is_some_and(|child| child.kind() == ";")
10356 })
10357 })
10358}
10359
10360pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
10361 let mut ancestor = node.parent();
10362 while let Some(parent) = ancestor {
10363 if is_preprocessor_conditional(parent)
10364 && !is_file_covering_include_guard(parent, source)
10365 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
10366 {
10367 return false;
10368 }
10369 ancestor = parent.parent();
10370 }
10371 true
10372}
10373
10374fn is_split_cpp_language_linkage_wrapper(
10375 conditional: Node<'_>,
10376 descendant: Node<'_>,
10377 source: &str,
10378) -> bool {
10379 if conditional.child_by_field_name("alternative").is_some()
10380 || !matches!(
10381 simple_preprocessor_guard(conditional, source),
10382 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
10383 )
10384 {
10385 return false;
10386 }
10387 let mut current = descendant.parent();
10388 let linkage = loop {
10389 let Some(node) = current else {
10390 return false;
10391 };
10392 if node == conditional {
10393 return false;
10394 }
10395 if node.kind() == "linkage_specification" {
10396 break node;
10397 }
10398 current = node.parent();
10399 };
10400 if linkage
10401 .child_by_field_name("value")
10402 .is_none_or(|value| node_text(value, source) != "\"C\"")
10403 {
10404 return false;
10405 }
10406 let Some(body) = linkage.child_by_field_name("body") else {
10407 return false;
10408 };
10409 let closes_opening_branch = (0..body.named_child_count())
10410 .filter_map(|index| body.named_child(index))
10411 .take_while(|child| child.end_byte() <= descendant.start_byte())
10412 .any(|child| {
10413 child.kind() == "preproc_call"
10414 && child
10415 .child_by_field_name("directive")
10416 .is_some_and(|directive| node_text(directive, source) == "#endif")
10417 });
10418 let reopens_for_closing_brace = (0..body.named_child_count())
10419 .filter_map(|index| body.named_child(index))
10420 .skip_while(|child| child.start_byte() < descendant.end_byte())
10421 .any(|child| {
10422 matches!(
10423 simple_preprocessor_guard(child, source),
10424 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
10425 ) && (0..child.child_count()).any(|index| {
10426 child
10427 .child(index)
10428 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
10429 })
10430 });
10431 closes_opening_branch && reopens_for_closing_brace
10432}
10433
10434pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
10438 node.child_by_field_name("arguments")
10439 .or_else(|| node.child_by_field_name("parameters"))
10440 .or_else(|| node.child_by_field_name("value"))
10441 .or_else(|| first_named_child_of_kind(node, "argument_list"))
10442 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
10443}
10444
10445pub fn call_arity(node: Node<'_>) -> usize {
10446 call_arguments_node(node)
10447 .map(|args| argument_children(args).count())
10448 .unwrap_or(0)
10449}
10450
10451pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
10452 let recovered_block_arguments = recovered_block_literal_arguments(node);
10453 (0..node.child_count())
10454 .filter_map(move |index| node.child(index))
10455 .filter(|child| child.is_named() && !child.is_extra())
10456 .flat_map(move |child| {
10457 if let Some((raw, left, right)) = recovered_block_arguments
10458 && child == raw
10459 {
10460 [Some(left), Some(right)]
10461 } else {
10462 [Some(child), None]
10463 }
10464 })
10465 .flatten()
10466}
10467
10468fn recovered_c_keyword_argument_count(
10469 file: &ProjectFile,
10470 call: Node<'_>,
10471 arguments: Node<'_>,
10472 source: &str,
10473) -> usize {
10474 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
10479 return 0;
10480 }
10481 let mut ancestor = Some(call);
10482 let function = loop {
10483 let Some(current) = ancestor else {
10484 return 0;
10485 };
10486 if current.kind() == "function_definition" {
10487 break current;
10488 }
10489 ancestor = current.parent();
10490 };
10491 let Some(parameters) = function
10492 .child_by_field_name("declarator")
10493 .and_then(|declarator| declarator.child_by_field_name("parameters"))
10494 else {
10495 return 0;
10496 };
10497 let displaced_parameter_keywords = (0..parameters.child_count())
10498 .filter_map(|index| parameters.child(index))
10499 .filter(|error| error.kind() == "ERROR")
10500 .filter_map(|error| {
10501 let parameter = error.prev_named_sibling()?;
10502 if parameter.kind() != "parameter_declaration"
10503 || parameter.end_byte() != error.start_byte()
10504 || extract_variable_name(parameter, source).is_some()
10505 {
10506 return None;
10507 }
10508 let mut children = (0..error.child_count())
10509 .filter_map(|index| error.child(index))
10510 .filter(|child| !child.is_extra() && !child.is_missing());
10511 let keyword = children.next()?;
10512 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
10513 .then_some(keyword)
10514 })
10515 .collect::<Vec<_>>();
10516 if displaced_parameter_keywords.is_empty() {
10517 return 0;
10518 }
10519
10520 (0..arguments.child_count())
10521 .filter_map(|index| arguments.child(index))
10522 .filter(|error| error.kind() == "ERROR" && error.is_extra())
10523 .filter(|error| {
10524 let mut children = (0..error.child_count())
10525 .filter_map(|index| error.child(index))
10526 .filter(|child| !child.is_extra() && !child.is_missing());
10527 let Some(comma) = children.next() else {
10528 return false;
10529 };
10530 let Some(keyword) = children.next() else {
10531 return false;
10532 };
10533 children.next().is_none()
10534 && comma.kind() == ","
10535 && !keyword.is_named()
10536 && keyword.child_count() == 0
10537 && displaced_parameter_keywords
10538 .iter()
10539 .any(|parameter| parameter.kind_id() == keyword.kind_id())
10540 })
10541 .count()
10542}
10543
10544fn recovered_block_literal_arguments<'tree>(
10545 arguments: Node<'tree>,
10546) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
10547 if arguments.kind() != "argument_list" {
10548 return None;
10549 }
10550 let mut raw_arguments = (0..arguments.child_count())
10551 .filter_map(|index| arguments.child(index))
10552 .filter(|child| child.is_named() && !child.is_extra());
10553 let raw = raw_arguments.next()?;
10554 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
10555 return None;
10556 }
10557
10558 let left = raw.child_by_field_name("left")?;
10559 if left.is_missing() || left.start_byte() == left.end_byte() {
10560 return None;
10561 }
10562 let right = raw.child_by_field_name("right")?;
10563 if right.kind() != "compound_literal_expression"
10564 || right.is_missing()
10565 || right
10566 .child_by_field_name("type")
10567 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
10568 || right
10569 .child_by_field_name("value")
10570 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
10571 {
10572 return None;
10573 }
10574 let has_intervening_error = (0..raw.child_count())
10575 .filter_map(|index| raw.child(index))
10576 .any(|child| {
10577 child.kind() == "ERROR"
10578 && !child.is_missing()
10579 && child.start_byte() >= left.end_byte()
10580 && child.end_byte() <= right.start_byte()
10581 });
10582 has_intervening_error.then_some((raw, left, right))
10583}
10584
10585pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
10586 match node.kind() {
10587 "new_expression" => node
10588 .child_by_field_name("type")
10589 .or_else(|| node.named_child(0)),
10590 "compound_literal_expression" => node.child_by_field_name("type"),
10591 "call_expression" => node.child_by_field_name("function"),
10592 _ => None,
10593 }
10594}
10595
10596pub fn field_initializer_constructs_target(
10597 node: Node<'_>,
10598 ctx: &ScanCtx<'_>,
10599 owner: &CodeUnit,
10600) -> bool {
10601 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
10610 return qualified_base_initializer_constructs_target(node, ctx, owner);
10611 }
10612 let Some(name) = node
10613 .child_by_field_name("name")
10614 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
10615 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
10616 else {
10617 return false;
10618 };
10619 let field_name = node_text(name, ctx.source);
10620 ctx.visibility
10621 .visible_identifier_candidates(ctx.file, field_name)
10622 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
10623 .any(|unit| field_declares_type(unit, ctx, owner))
10624}
10625
10626fn qualified_base_initializer_constructs_target(
10627 node: Node<'_>,
10628 ctx: &ScanCtx<'_>,
10629 owner: &CodeUnit,
10630) -> bool {
10631 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
10632 return false;
10633 };
10634 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
10635 return false;
10636 };
10637 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
10638 return false;
10639 };
10640 let resolves_target = |components: &[String]| {
10641 matches!(
10642 ctx.visibility.resolve_type_components_lexically_for_target(
10643 &ctx.analyzer,
10644 ctx.file,
10645 components,
10646 is_globally_qualified_cpp_name(qualified),
10647 &lexical_scope,
10648 owner,
10649 ),
10650 LexicalTypeResolution::Resolved { unit, .. }
10651 if same_visible_symbol(&unit, owner)
10652 )
10653 };
10654 if resolves_target(&components) {
10655 return true;
10656 }
10657
10658 components
10664 .last()
10665 .is_some_and(|terminal| terminal == owner.identifier())
10666 && resolves_target(&components[..components.len() - 1])
10667}
10668
10669fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
10670 unit.signature()
10671 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
10672 || ctx
10673 .analyzer
10674 .get_source(unit, false)
10675 .is_some_and(|declaration| {
10676 field_declaration_type_matches(&declaration, unit, ctx, owner)
10677 })
10678}
10679
10680pub fn field_declared_binding(
10681 analyzer: &CppGraphSource<'_>,
10682 visibility: &VisibilityIndex<'_>,
10683 visible_from: &ProjectFile,
10684 field: &CodeUnit,
10685) -> Option<CppScanBinding> {
10686 let fact = visibility.field_declared_type_fact(analyzer, field)?;
10687 let normalized = normalize_field_type_text(&fact.type_text);
10688 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
10689 analyzer,
10690 visible_from,
10691 field,
10692 &normalized,
10693 );
10694 let resolved = match (resolved, fact.template_arguments.as_deref()) {
10695 (Some(primary), Some(arguments)) => visibility
10696 .resolve_template_arguments(visible_from, primary, arguments)
10697 .ok(),
10698 (resolved, None) => resolved,
10699 (None, Some(_)) => None,
10700 };
10701 Some(CppScanBinding::from_type_name(
10702 normalized,
10703 resolved,
10704 fact.indirection,
10705 ))
10706}
10707
10708fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
10710 let Some(first) = candidates.first() else {
10711 return Err(TypeCandidateFailure::Unresolvable);
10712 };
10713 if candidates
10714 .iter()
10715 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
10716 {
10717 Ok((*first).clone())
10718 } else {
10719 Err(TypeCandidateFailure::Ambiguous)
10720 }
10721}
10722
10723fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
10724 logical_type_candidate(candidates).ok()
10725}
10726
10727fn unique_type_candidate_preserving_alias(
10728 analyzer: &CppGraphSource<'_>,
10729 candidates: &[&CodeUnit],
10730) -> Option<CodeUnit> {
10731 let first = *candidates.first()?;
10732 if declared_type_alias(analyzer, first) {
10733 return candidates
10734 .iter()
10735 .all(|candidate| {
10736 declared_type_alias(analyzer, candidate)
10737 && candidate.kind() == first.kind()
10738 && candidate.fq_name() == first.fq_name()
10739 && candidate.source() == first.source()
10740 })
10741 .then(|| first.clone());
10742 }
10743 candidates
10744 .iter()
10745 .all(|candidate| {
10746 !declared_type_alias(analyzer, candidate)
10747 && candidate.kind() == first.kind()
10748 && candidate.fq_name() == first.fq_name()
10749 })
10750 .then(|| first.clone())
10751}
10752
10753fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10754 is_type_alias(unit)
10755 || analyzer
10756 .type_alias_provider()
10757 .is_some_and(|provider| provider.is_type_alias(unit))
10758}
10759
10760pub fn field_declared_type_binding(
10761 analyzer: &CppGraphSource<'_>,
10762 visibility: &VisibilityIndex<'_>,
10763 visible_from: &ProjectFile,
10764 field: &CodeUnit,
10765) -> Option<(String, Option<CodeUnit>, i32)> {
10766 let fact = visibility.field_declared_type_fact(analyzer, field)?;
10767 let normalized = normalize_field_type_text(&fact.type_text);
10768 let primary = visibility.resolve_unique_canonical_type_for_declaration(
10769 analyzer,
10770 visible_from,
10771 field,
10772 &normalized,
10773 );
10774 let resolved = match (primary, fact.template_arguments.as_deref()) {
10775 (Some(primary), Some(arguments)) => visibility
10776 .resolve_template_arguments(visible_from, primary, arguments)
10777 .ok(),
10778 (resolved, None) => resolved,
10779 (None, Some(_)) => None,
10780 };
10781 Some((normalized, resolved, fact.indirection))
10782}
10783
10784fn decode_field_declared_type_fact(
10785 analyzer: &CppGraphSource<'_>,
10786 field: &CodeUnit,
10787) -> Option<DeclaredFieldTypeFact> {
10788 let declaration = analyzer.get_source(field, false)?;
10789 let mut parser = Parser::new();
10790 parser
10791 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10792 .ok()?;
10793 let tree = parser.parse(&declaration, None)?;
10794 let mut stack = vec![tree.root_node()];
10795 while let Some(node) = stack.pop() {
10796 if matches!(node.kind(), "declaration" | "field_declaration")
10797 && let Some(type_node) = node
10798 .child_by_field_name("type")
10799 .or_else(|| first_type_child(node))
10800 && let Some(indirection) =
10801 declared_name_indirection(node, type_node, field.identifier(), &declaration)
10802 {
10803 let declared_type = if matches!(
10804 type_node.kind(),
10805 "class_specifier" | "struct_specifier" | "union_specifier"
10806 ) {
10807 type_node.child_by_field_name("name")
10808 } else {
10809 Some(type_node)
10810 };
10811 let type_text = declared_type.map_or_else(
10812 || field.identifier().to_string(),
10813 |declared_type| node_text(declared_type, &declaration).to_string(),
10814 );
10815 return Some(DeclaredFieldTypeFact {
10816 type_text,
10817 indirection,
10818 template_arguments: declared_type.and_then(|declared_type| {
10819 cpp_template_reference_arguments(declared_type, &declaration)
10820 }),
10821 });
10822 }
10823 let mut cursor = node.walk();
10824 stack.extend(node.named_children(&mut cursor));
10825 }
10826 None
10827}
10828
10829pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
10843 let mut parser = Parser::new();
10844 parser
10845 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10846 .ok()?;
10847 let tree = parser.parse(declaration, None)?;
10848 let mut stack = vec![tree.root_node()];
10849 while let Some(node) = stack.pop() {
10850 let type_node = match node.kind() {
10851 "type_definition" => {
10852 let mut cursor = node.walk();
10853 if node
10854 .children_by_field_name("declarator", &mut cursor)
10855 .any(declarator_names_function_type)
10856 {
10857 return None;
10858 }
10859 node.child_by_field_name("type")?
10860 }
10861 "alias_declaration" => {
10862 let type_node = node.child_by_field_name("type")?;
10863 if type_node
10864 .child_by_field_name("declarator")
10865 .is_some_and(declarator_names_function_type)
10866 {
10867 return None;
10868 }
10869 type_node
10870 }
10871 _ => {
10872 let mut cursor = node.walk();
10873 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10874 stack.extend(children.into_iter().rev());
10875 continue;
10876 }
10877 };
10878 return Some(node_text(type_node, declaration).to_string());
10879 }
10880 None
10881}
10882
10883fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
10892 let mut parser = Parser::new();
10893 if parser
10894 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10895 .is_err()
10896 {
10897 return true;
10898 }
10899 let Some(tree) = parser.parse(declaration, None) else {
10900 return true;
10901 };
10902 let mut stack = vec![tree.root_node()];
10903 while let Some(node) = stack.pop() {
10904 let declarators = match node.kind() {
10905 "type_definition" => {
10906 let mut cursor = node.walk();
10907 node.children_by_field_name("declarator", &mut cursor)
10908 .collect::<Vec<_>>()
10909 }
10910 "alias_declaration" => node
10911 .child_by_field_name("type")
10912 .and_then(|type_node| type_node.child_by_field_name("declarator"))
10913 .into_iter()
10914 .collect::<Vec<_>>(),
10915 _ => {
10916 let mut cursor = node.walk();
10917 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10918 stack.extend(children.into_iter().rev());
10919 continue;
10920 }
10921 };
10922 return declarators.into_iter().any(cpp_declarator_adds_indirection);
10923 }
10924 true
10925}
10926
10927fn declarator_names_function_type(declarator: Node<'_>) -> bool {
10933 let mut current = Some(declarator);
10934 while let Some(node) = current {
10935 match node.kind() {
10936 "function_declarator" | "abstract_function_declarator" => return true,
10937 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
10938 current = node.named_child(0);
10939 }
10940 _ => current = node.child_by_field_name("declarator"),
10941 }
10942 }
10943 false
10944}
10945
10946pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
10950 let mut parser = Parser::new();
10951 if parser
10952 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10953 .is_err()
10954 {
10955 return false;
10956 }
10957 let Some(tree) = parser.parse(declaration, None) else {
10958 return false;
10959 };
10960 let mut stack = vec![tree.root_node()];
10961 while let Some(node) = stack.pop() {
10962 if matches!(node.kind(), "declaration" | "field_declaration") {
10963 let mut cursor = node.walk();
10964 if node
10965 .children_by_field_name("declarator", &mut cursor)
10966 .any(|declarator| {
10967 declarator_name_node(declarator).is_some_and(|name| {
10968 node_text(name, declaration) == field_name
10969 && declarator_names_function_type(declarator)
10970 })
10971 })
10972 {
10973 return true;
10974 }
10975 }
10976 let mut cursor = node.walk();
10977 stack.extend(node.named_children(&mut cursor));
10978 }
10979 false
10980}
10981
10982pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
10986 let mut parser = Parser::new();
10987 if parser
10988 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10989 .is_err()
10990 {
10991 return false;
10992 }
10993 let Some(tree) = parser.parse(declaration, None) else {
10994 return false;
10995 };
10996 let mut stack = vec![tree.root_node()];
10997 while let Some(node) = stack.pop() {
10998 match node.kind() {
10999 "type_definition" => {
11000 let mut cursor = node.walk();
11001 if node
11002 .children_by_field_name("declarator", &mut cursor)
11003 .any(|declarator| {
11004 extract_typedef_declarator_name(declarator, declaration)
11005 .is_some_and(|name| name == alias_name)
11006 && declarator_names_function_type(declarator)
11007 })
11008 {
11009 return true;
11010 }
11011 }
11012 "alias_declaration" => {
11013 let names_alias = node
11014 .child_by_field_name("name")
11015 .is_some_and(|name| node_text(name, declaration) == alias_name);
11016 if names_alias
11017 && node
11018 .child_by_field_name("type")
11019 .and_then(|type_node| type_node.child_by_field_name("declarator"))
11020 .is_some_and(declarator_names_function_type)
11021 {
11022 return true;
11023 }
11024 }
11025 _ => {}
11026 }
11027 let mut cursor = node.walk();
11028 stack.extend(node.named_children(&mut cursor));
11029 }
11030 false
11031}
11032
11033fn decode_structured_alias_target(
11034 analyzer: &CppGraphSource<'_>,
11035 unit: &CodeUnit,
11036) -> Option<StructuredAliasTarget> {
11037 analyzer
11038 .get_source(unit, false)
11039 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
11040 .or_else(|| {
11041 let signature = unit.signature()?;
11042 decode_structured_alias_target_source(unit, signature, false)
11043 })
11044}
11045
11046fn decode_structured_alias_target_source(
11047 unit: &CodeUnit,
11048 declaration: &str,
11049 require_top_level: bool,
11050) -> Option<StructuredAliasTarget> {
11051 let mut parser = Parser::new();
11052 parser
11053 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11054 .ok()?;
11055 let tree = parser.parse(declaration, None)?;
11056 let mut stack = vec![tree.root_node()];
11057 while let Some(node) = stack.pop() {
11058 let type_node = match node.kind() {
11059 "type_definition" => {
11060 if require_top_level
11061 && node
11062 .parent()
11063 .is_none_or(|parent| parent.kind() != "translation_unit")
11064 {
11065 let mut cursor = node.walk();
11066 stack.extend(node.named_children(&mut cursor));
11067 continue;
11068 }
11069 let mut declarator_cursor = node.walk();
11070 let declarator = node
11071 .children_by_field_name("declarator", &mut declarator_cursor)
11072 .find(|declarator| {
11073 extract_typedef_declarator_name(*declarator, declaration)
11074 .is_some_and(|name| name == unit.identifier())
11075 })?;
11076 if declarator_names_function_type(declarator) {
11077 return None;
11078 }
11079 node.child_by_field_name("type")?
11080 }
11081 "alias_declaration" => {
11082 if require_top_level
11083 && node
11084 .parent()
11085 .is_none_or(|parent| parent.kind() != "translation_unit")
11086 {
11087 let mut cursor = node.walk();
11088 stack.extend(node.named_children(&mut cursor));
11089 continue;
11090 }
11091 let name = node.child_by_field_name("name")?;
11092 if node_text(name, declaration) != unit.identifier() {
11093 return None;
11094 }
11095 let type_node = node.child_by_field_name("type")?;
11096 if type_node
11097 .child_by_field_name("declarator")
11098 .is_some_and(declarator_names_function_type)
11099 {
11100 return None;
11101 }
11102 type_node
11103 }
11104 _ => {
11105 let mut cursor = node.walk();
11106 stack.extend(node.named_children(&mut cursor));
11107 continue;
11108 }
11109 };
11110 return structured_alias_type_target(type_node, declaration);
11111 }
11112 None
11113}
11114
11115fn structured_alias_type_target(
11116 mut type_node: Node<'_>,
11117 source: &str,
11118) -> Option<StructuredAliasTarget> {
11119 while type_node.kind() == "type_descriptor" {
11120 type_node = type_node.child_by_field_name("type")?;
11121 }
11122 if type_node.kind() == "primitive_type" {
11123 return Some(StructuredAliasTarget::Builtin);
11124 }
11125 if matches!(
11126 type_node.kind(),
11127 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
11128 ) {
11129 type_node = type_node.child_by_field_name("name")?;
11130 }
11131 let global = type_node.child_by_field_name("scope").is_none()
11132 && type_node.child(0).is_some_and(|child| child.kind() == "::");
11133 let mut components = Vec::new();
11134 append_structured_type_components(type_node, source, &mut components)?;
11135 let arguments = cpp_template_reference_arguments(type_node, source);
11136 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
11137 components,
11138 global,
11139 arguments,
11140 })
11141}
11142
11143fn append_structured_type_components(
11144 node: Node<'_>,
11145 source: &str,
11146 out: &mut Vec<String>,
11147) -> Option<()> {
11148 match node.kind() {
11149 "identifier" | "namespace_identifier" | "type_identifier" => {
11150 out.push(node_text(node, source).to_string());
11151 Some(())
11152 }
11153 "template_type" => {
11154 append_structured_type_components(node.child_by_field_name("name")?, source, out)
11155 }
11156 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
11157 if let Some(scope) = node.child_by_field_name("scope") {
11158 append_structured_type_components(scope, source, out)?;
11159 }
11160 append_structured_type_components(node.child_by_field_name("name")?, source, out)
11161 }
11162 _ => None,
11163 }
11164}
11165
11166fn declared_name_indirection(
11167 declaration: Node<'_>,
11168 type_node: Node<'_>,
11169 field_name: &str,
11170 source: &str,
11171) -> Option<i32> {
11172 let mut stack = Vec::new();
11173 let mut cursor = declaration.walk();
11174 stack.extend(
11175 declaration
11176 .named_children(&mut cursor)
11177 .filter(|child| !same_node(*child, type_node)),
11178 );
11179 while let Some(node) = stack.pop() {
11180 if matches!(node.kind(), "identifier" | "field_identifier")
11181 && node_text(node, source) == field_name
11182 {
11183 let mut indirection = 0;
11184 let mut current = node.parent();
11185 while let Some(parent) = current {
11186 if same_node(parent, declaration) {
11187 return Some(indirection);
11188 }
11189 if parent.kind() == "pointer_declarator" {
11190 indirection += 1;
11191 }
11192 current = parent.parent();
11193 }
11194 return None;
11195 }
11196 let mut cursor = node.walk();
11197 stack.extend(node.named_children(&mut cursor));
11198 }
11199 None
11200}
11201
11202fn field_declaration_type_matches(
11203 declaration: &str,
11204 unit: &CodeUnit,
11205 ctx: &ScanCtx<'_>,
11206 owner: &CodeUnit,
11207) -> bool {
11208 ctx.visibility
11209 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
11210 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
11211 let normalized = normalize_field_type_text(type_text);
11212 ctx.visibility
11213 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
11214 || ctx.visibility.resolves_to_type(
11215 &ctx.analyzer,
11216 ctx.file,
11217 normalized.as_str(),
11218 owner,
11219 )
11220 })
11221}
11222
11223fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
11224 let declaration = declaration
11225 .split(['=', ';'])
11226 .next()
11227 .unwrap_or(declaration)
11228 .trim();
11229 let index = declaration.rfind(field_name)?;
11230 let before = &declaration[..index];
11231 let after = &declaration[index + field_name.len()..];
11232 if before.chars().next_back().is_some_and(is_identifier_char)
11233 || after.chars().next().is_some_and(is_identifier_char)
11234 {
11235 return None;
11236 }
11237 Some(before.trim())
11238}
11239
11240fn normalize_field_type_text(type_text: &str) -> String {
11241 const FIELD_SPECIFIERS: [&str; 8] = [
11242 "extern ",
11243 "static ",
11244 "mutable ",
11245 "constexpr ",
11246 "constinit ",
11247 "inline ",
11248 "volatile ",
11249 "const ",
11250 ];
11251
11252 let mut normalized = normalize_type_text(type_text);
11253 loop {
11254 let Some(stripped) = FIELD_SPECIFIERS
11255 .iter()
11256 .find_map(|specifier| normalized.strip_prefix(specifier))
11257 else {
11258 return normalized;
11259 };
11260 normalized = normalize_type_text(stripped);
11261 }
11262}
11263
11264fn is_identifier_char(ch: char) -> bool {
11265 ch == '_' || ch.is_ascii_alphanumeric()
11266}
11267
11268pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
11269 let Some(type_node) = node.child_by_field_name("type") else {
11270 return false;
11271 };
11272 ctx.visibility.resolves_to_type(
11273 &ctx.analyzer,
11274 ctx.file,
11275 node_text(type_node, ctx.source),
11276 owner,
11277 )
11278}
11279
11280pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
11281 !ctx.analyzer
11282 .declarations(ctx.file)
11283 .into_iter()
11284 .filter(|unit| unit.is_function())
11285 .any(|unit| {
11286 ctx.analyzer.ranges(&unit).iter().any(|range| {
11287 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
11288 })
11289 })
11290}
11291
11292pub enum DeclarationConstructorInitializer<'tree> {
11294 Arguments(Node<'tree>),
11297 Expression(Node<'tree>),
11300 Empty,
11302}
11303
11304pub fn declaration_constructor_initializer(
11305 node: Node<'_>,
11306) -> DeclarationConstructorInitializer<'_> {
11307 let mut cursor = node.walk();
11308 for child in node.named_children(&mut cursor) {
11309 if child.kind() == "init_declarator" {
11310 let Some(value) = child
11311 .child_by_field_name("value")
11312 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
11313 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
11314 else {
11315 return DeclarationConstructorInitializer::Empty;
11316 };
11317 return match value.kind() {
11318 "argument_list" | "initializer_list" => {
11319 DeclarationConstructorInitializer::Arguments(value)
11320 }
11321 "compound_literal_expression" => call_arguments_node(value)
11322 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
11323 DeclarationConstructorInitializer::Arguments(arguments)
11324 }),
11325 _ => DeclarationConstructorInitializer::Expression(value),
11326 };
11327 }
11328 if is_declarator_node(child) {
11329 return declarator_parameters(child)
11330 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
11331 DeclarationConstructorInitializer::Arguments(parameters)
11332 });
11333 }
11334 }
11335 DeclarationConstructorInitializer::Empty
11336}
11337
11338pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
11339 match declaration_constructor_initializer(node) {
11340 DeclarationConstructorInitializer::Arguments(arguments) => {
11341 argument_children(arguments).count()
11342 }
11343 DeclarationConstructorInitializer::Expression(_) => 1,
11344 DeclarationConstructorInitializer::Empty => 0,
11345 }
11346}
11347
11348fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
11352 let mut current = node;
11353 loop {
11354 if let Some(parameters) = current.child_by_field_name("parameters") {
11355 return Some(parameters);
11356 }
11357 current = current.child_by_field_name("declarator")?;
11358 }
11359}
11360
11361pub(super) fn first_named_child_of_kind<'tree>(
11362 node: Node<'tree>,
11363 kind: &str,
11364) -> Option<Node<'tree>> {
11365 let mut cursor = node.walk();
11366 node.named_children(&mut cursor)
11367 .find(|child| child.kind() == kind)
11368}
11369
11370fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
11371 let mut stack = vec![root];
11372 while let Some(node) = stack.pop() {
11373 if node.kind() == kind {
11374 return Some(node);
11375 }
11376 for index in (0..node.named_child_count()).rev() {
11377 if let Some(child) = node.named_child(index) {
11378 stack.push(child);
11379 }
11380 }
11381 }
11382 None
11383}
11384
11385fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
11386 if node.kind() == "identifier" {
11387 return true;
11388 }
11389 if node.kind() == "parenthesized_expression" {
11390 return false;
11391 }
11392 if node.kind() == "call_expression" {
11393 return node
11394 .child_by_field_name("function")
11395 .is_some_and(|function| function.kind() == "identifier");
11396 }
11397 let mut stack = vec![node];
11398 while let Some(descendant) = stack.pop() {
11399 if descendant != node && descendant.kind() == "parenthesized_expression" {
11400 continue;
11401 }
11402 if descendant.kind() == "identifier" {
11403 return true;
11404 }
11405 if descendant.kind() == "call_expression" {
11406 if descendant
11407 .child_by_field_name("function")
11408 .is_some_and(|function| function.kind() == "identifier")
11409 {
11410 return true;
11411 }
11412 continue;
11413 }
11414 for index in (0..descendant.named_child_count()).rev() {
11415 if let Some(child) = descendant.named_child(index) {
11416 stack.push(child);
11417 }
11418 }
11419 }
11420 false
11421}
11422
11423fn macro_expansion_shape_is_safe(
11424 node: Node<'_>,
11425 source: &str,
11426 parameters: &[String],
11427 environment: &MacroEnvironment,
11428) -> bool {
11429 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
11430 return true;
11431 }
11432 if node.kind() == "call_expression" {
11433 let Some(function) = node.child_by_field_name("function") else {
11434 return true;
11435 };
11436 if function.kind() != "identifier" {
11437 return true;
11438 }
11439 let function_name = node_text(function, source);
11440 if parameters
11441 .iter()
11442 .any(|parameter| parameter == function_name)
11443 {
11444 return false;
11445 }
11446 if !environment.may_bind(function_name) {
11447 return true;
11448 }
11449 let Some(arguments) = node.child_by_field_name("arguments") else {
11450 return false;
11451 };
11452 return argument_children(arguments).all(|argument| {
11453 if argument.kind() == "identifier"
11454 && parameters
11455 .iter()
11456 .any(|parameter| parameter == node_text(argument, source))
11457 {
11458 return false;
11459 }
11460 macro_expansion_shape_is_safe(argument, source, parameters, environment)
11461 });
11462 }
11463 let mut stack = vec![node];
11464 while let Some(descendant) = stack.pop() {
11465 if descendant != node {
11466 if descendant.kind() == "parenthesized_expression" {
11467 continue;
11468 }
11469 if descendant.kind() == "call_expression" {
11470 let expands = descendant
11471 .child_by_field_name("function")
11472 .filter(|function| function.kind() == "identifier")
11473 .is_some_and(|function| environment.may_bind(node_text(function, source)));
11474 if expands {
11475 return false;
11476 }
11477 continue;
11478 }
11479 }
11480 if descendant.kind() == "identifier" {
11481 let identifier = node_text(descendant, source);
11482 if parameters.iter().any(|parameter| parameter == identifier)
11483 || environment.may_bind(identifier)
11484 {
11485 return false;
11486 }
11487 }
11488 for index in (0..descendant.named_child_count()).rev() {
11489 if let Some(child) = descendant.named_child(index) {
11490 stack.push(child);
11491 }
11492 }
11493 }
11494 true
11495}
11496
11497fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
11498 let text = node_text(path, source);
11499 match path.kind() {
11500 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
11501 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
11502 _ => None,
11503 }
11504}
11505
11506fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
11507 let descendant = node;
11508 while let Some(parent) = node.parent() {
11509 if is_preprocessor_conditional(parent)
11510 && !is_file_covering_include_guard(parent, source)
11511 && preprocessor_conditional_contains_descendant(parent, descendant)
11512 {
11513 return true;
11514 }
11515 node = parent;
11516 }
11517 false
11518}
11519
11520fn is_preprocessor_conditional(node: Node<'_>) -> bool {
11521 matches!(
11522 node.kind(),
11523 "preproc_if"
11524 | "preproc_ifdef"
11525 | "preproc_ifndef"
11526 | "preproc_elif"
11527 | "preproc_elifdef"
11528 | "preproc_else"
11529 )
11530}
11531
11532fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
11533 node.parent()
11534 .filter(|parent| parent.kind() == "translation_unit")
11535 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
11536 && is_canonical_include_guard(node, source)
11537}
11538
11539fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
11540 if node.kind() != "preproc_ifdef"
11541 || node
11542 .child(0)
11543 .is_none_or(|directive| directive.kind() != "#ifndef")
11544 || node.child_by_field_name("alternative").is_some()
11545 {
11546 return false;
11547 }
11548 let Some(guard_name) = node.child_by_field_name("name") else {
11549 return false;
11550 };
11551 let mut cursor = node.walk();
11552 node.named_children(&mut cursor)
11553 .find(|child| *child != guard_name && child.kind() != "comment")
11554 .filter(|child| child.kind() == "preproc_def")
11555 .and_then(|definition| definition.child_by_field_name("name"))
11556 .is_some_and(|defined_name| {
11557 node_text(defined_name, source) == node_text(guard_name, source)
11558 })
11559}
11560
11561fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
11562 let mut guard = None;
11563 for index in 0..root.named_child_count() {
11564 let Some(child) = root.named_child(index) else {
11565 continue;
11566 };
11567 if child.kind() == "comment" || is_pragma_once(child, source) {
11568 continue;
11569 }
11570 if guard.is_none() && is_canonical_include_guard(child, source) {
11571 guard = Some(child);
11572 } else {
11573 return None;
11574 }
11575 }
11576 guard
11577 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
11578 .map(|name| node_text(name, source).to_string())
11579}
11580
11581fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
11582 if (0..root.named_child_count())
11583 .filter_map(|index| root.named_child(index))
11584 .any(|child| is_pragma_once(child, source))
11585 {
11586 return MacroIncludeProtection::PragmaOnce;
11587 }
11588 top_level_canonical_include_guard_name(root, source)
11589 .map(MacroIncludeProtection::MacroGuard)
11590 .unwrap_or(MacroIncludeProtection::None)
11591}
11592
11593fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
11594 node.kind() == "preproc_call"
11595 && node
11596 .child_by_field_name("directive")
11597 .is_some_and(|directive| node_text(directive, source) == "#pragma")
11598 && node
11599 .child_by_field_name("argument")
11600 .is_some_and(|argument| node_text(argument, source).trim() == "once")
11601}
11602
11603fn parse_preproc_identifier(argument: &str) -> Option<String> {
11604 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
11605 let mut parser = Parser::new();
11606 parser
11607 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11608 .ok()?;
11609 let tree = parser.parse(&sentinel, None)?;
11610 if tree.root_node().has_error() {
11611 return None;
11612 }
11613 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
11614 let identifier = statement.named_child(0)?;
11615 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
11616 .then(|| node_text(identifier, &sentinel).to_string())
11617}
11618
11619pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
11620 match node.kind() {
11621 "identifier" | "field_identifier" => {
11622 let name = node_text(node, source).trim();
11623 (!name.is_empty()).then(|| name.to_string())
11624 }
11625 "abstract_array_declarator"
11626 | "abstract_function_declarator"
11627 | "abstract_parenthesized_declarator"
11628 | "abstract_pointer_declarator"
11629 | "abstract_reference_declarator" => None,
11630 "function_declarator" => node
11631 .child_by_field_name("declarator")
11632 .or_else(|| node.child_by_field_name("name"))
11633 .and_then(|child| extract_variable_name(child, source)),
11634 _ => node
11635 .child_by_field_name("declarator")
11636 .or_else(|| node.child_by_field_name("name"))
11637 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
11638 .and_then(|child| extract_variable_name(child, source)),
11639 }
11640}
11641
11642pub fn is_c_source_file(file: &ProjectFile) -> bool {
11653 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
11654}
11655
11656pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
11663 if !is_c_source_file(file) || node.kind() != "identifier" {
11664 return false;
11665 }
11666 let mut operand = node;
11667 while let Some(parent) = operand.parent().filter(|parent| {
11668 parent.kind() == "parenthesized_expression"
11669 && parent.named_child_count() == 1
11670 && parent.named_child(0) == Some(operand)
11671 }) {
11672 operand = parent;
11673 }
11674 operand.parent().is_some_and(|parent| {
11675 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
11676 })
11677}
11678
11679pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
11695 if node.kind() != "type_identifier" {
11696 return false;
11697 }
11698 let Some(descriptor) = node
11699 .parent()
11700 .filter(|parent| parent.kind() == "type_descriptor")
11701 else {
11702 return false;
11703 };
11704 if descriptor.child_by_field_name("type") != Some(node) {
11705 return false;
11706 }
11707 let Some(arguments) = descriptor
11708 .parent()
11709 .filter(|parent| parent.kind() == "template_argument_list")
11710 else {
11711 return false;
11712 };
11713 arguments.parent().is_some_and(|owner| {
11714 matches!(
11715 owner.kind(),
11716 "template_type" | "template_function" | "template_method"
11717 ) && owner.child_by_field_name("arguments") == Some(arguments)
11718 })
11719}
11720
11721pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
11734 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
11735}
11736
11737pub fn is_declarator_node(node: Node<'_>) -> bool {
11738 matches!(
11739 node.kind(),
11740 "identifier"
11741 | "field_identifier"
11742 | "pointer_declarator"
11743 | "reference_declarator"
11744 | "array_declarator"
11745 | "parenthesized_declarator"
11746 | "function_declarator"
11747 )
11748}
11749
11750#[derive(Clone, Default)]
11751pub struct OrphanedNamespaceTypeScopeIndex {
11752 scopes: Vec<OrphanedNamespaceTypeScope>,
11753}
11754
11755#[derive(Clone)]
11756struct OrphanedNamespaceTypeScope {
11757 body_end: usize,
11758 scope_end: usize,
11759 components: Vec<String>,
11760}
11761
11762impl OrphanedNamespaceTypeScopeIndex {
11763 pub fn build(root: Node<'_>, source: &str) -> Self {
11770 let mut scopes = Vec::new();
11771 let mut stack = vec![root];
11772 while let Some(current) = stack.pop() {
11773 if current.kind() == "namespace_definition"
11774 && current.has_error()
11775 && let Some(body) = current.child_by_field_name("body")
11776 && current.end_byte() == body.end_byte()
11777 && let Some(name) = current.child_by_field_name("name")
11778 {
11779 let mut components =
11780 enclosing_namespace_components(current, source).unwrap_or_default();
11781 if append_cpp_name_components(name, source, &mut components).is_some()
11782 && !components.is_empty()
11783 {
11784 let mut scope_end = None;
11785 let mut following = current.next_named_sibling();
11786 while let Some(candidate) = following {
11787 if direct_unmatched_closing_brace(candidate)
11788 && !unmatched_closing_brace_is_followed_by_semicolon(candidate)
11789 {
11790 scope_end = Some(candidate.start_byte());
11791 break;
11792 }
11793 following = candidate.next_named_sibling();
11794 }
11795 let scope_end = scope_end.or_else(|| {
11796 std::iter::successors(current.parent(), |ancestor| ancestor.parent())
11797 .filter(|ancestor| ancestor.kind() == "namespace_definition")
11798 .filter_map(|ancestor| ancestor.child_by_field_name("body"))
11799 .map(|body| body.end_byte())
11800 .find(|end| *end > body.end_byte())
11801 });
11802 if let Some(scope_end) = scope_end {
11803 scopes.push(OrphanedNamespaceTypeScope {
11804 body_end: body.end_byte(),
11805 scope_end,
11806 components,
11807 });
11808 }
11809 }
11810 }
11811 if !current.has_error() {
11812 continue;
11813 }
11814 let mut cursor = current.walk();
11815 stack.extend(
11816 current
11817 .named_children(&mut cursor)
11818 .filter(|child| child.has_error()),
11819 );
11820 }
11821 Self { scopes }
11822 }
11823
11824 pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
11825 self.scopes
11826 .iter()
11827 .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
11828 .max_by_key(|scope| (scope.components.len(), scope.body_end))
11829 .map(|scope| (scope.body_end, scope.components.as_slice()))
11830 }
11831}
11832
11833#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11834pub enum RecoveredDeclaratorTypeContext {
11835 Declaration,
11836 FunctionDefinition,
11837 Parameter,
11838}
11839
11840pub fn recovered_macro_decorated_declarator_type(
11855 node: Node<'_>,
11856) -> Option<RecoveredDeclaratorTypeContext> {
11857 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
11858}
11859
11860pub fn recovered_macro_decorated_type_node(
11865 node: Node<'_>,
11866) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
11867 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
11868 return None;
11869 }
11870 let qualified = node.parent()?;
11871 if qualified.kind() != "qualified_identifier"
11872 || qualified.child_by_field_name("scope") != Some(node)
11873 || !(0..qualified.child_count())
11874 .filter_map(|index| qualified.child(index))
11875 .any(|child| child.kind() == "::" && child.is_missing())
11876 {
11877 return None;
11878 }
11879 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
11880 return None;
11881 }
11882
11883 let (declaration, context) = recovered_declarator_container(qualified)?;
11884 let type_node = declaration
11885 .child_by_field_name("type")
11886 .filter(|type_node| {
11887 *type_node != qualified
11888 && !type_node.is_missing()
11889 && type_node.start_byte() != type_node.end_byte()
11890 })?;
11891 Some((type_node, context))
11892}
11893
11894fn recovered_declarator_container(
11895 mut declarator: Node<'_>,
11896) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
11897 loop {
11898 let parent = declarator.parent()?;
11899 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
11900 return Some((
11901 parent
11902 .parent()
11903 .filter(|declaration| declaration.kind() == "declaration")?,
11904 RecoveredDeclaratorTypeContext::Declaration,
11905 ));
11906 }
11907 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
11908 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
11909 }
11910 if parent.kind() == "function_definition"
11911 && has_field_child(parent, "declarator", declarator)
11912 {
11913 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
11914 }
11915 if matches!(
11921 parent.kind(),
11922 "parameter_declaration" | "optional_parameter_declaration"
11923 ) && has_field_child(parent, "declarator", declarator)
11924 {
11925 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
11926 }
11927 if !matches!(
11928 parent.kind(),
11929 "array_declarator"
11930 | "function_declarator"
11931 | "parenthesized_declarator"
11932 | "pointer_declarator"
11933 | "pointer_type_declarator"
11934 | "reference_declarator"
11935 ) || !has_field_child(parent, "declarator", declarator)
11936 {
11937 return None;
11938 }
11939 declarator = parent;
11940 }
11941}
11942
11943fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
11944 let mut cursor = parent.walk();
11945 parent
11946 .children_by_field_name(field, &mut cursor)
11947 .any(|child| child == target)
11948}
11949
11950fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
11951 loop {
11952 if node.is_missing() || node.start_byte() == node.end_byte() {
11953 return false;
11954 }
11955 match node.kind() {
11956 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
11957 return true;
11958 }
11959 "array_declarator"
11960 | "function_declarator"
11961 | "parenthesized_declarator"
11962 | "pointer_declarator"
11963 | "pointer_type_declarator"
11964 | "reference_declarator" => {
11965 let Some(declarator) = node.child_by_field_name("declarator") else {
11966 return false;
11967 };
11968 node = declarator;
11969 }
11970 _ => return false,
11971 }
11972 }
11973}
11974
11975pub enum DesignatedInitializerOwner {
11977 Resolved(CodeUnit),
11978 Unresolved,
11979}
11980
11981pub fn designated_initializer_owner(
11992 visibility: &VisibilityIndex<'_>,
11993 file: &ProjectFile,
11994 source: &str,
11995 node: Node<'_>,
11996) -> Option<DesignatedInitializerOwner> {
11997 if let Some(designator) = node
11998 .parent()
11999 .filter(|parent| parent.kind() == "field_designator")
12000 {
12001 let pair = designator.parent()?;
12002 if pair.kind() != "initializer_pair"
12003 || pair.child_by_field_name("designator") != Some(designator)
12004 {
12005 return None;
12006 }
12007 let initializer = pair.parent()?;
12008 if initializer.kind() != "initializer_list" {
12009 return None;
12010 }
12011 return Some(classified_designated_owner(initializer_list_owner(
12012 visibility,
12013 file,
12014 source,
12015 initializer,
12016 )));
12017 }
12018
12019 let init_declarator = node.parent()?;
12020 if init_declarator.child_by_field_name("declarator") != Some(node)
12021 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
12022 {
12023 return None;
12024 }
12025 Some(classified_designated_owner(declaration_owner(
12026 visibility,
12027 file,
12028 source,
12029 init_declarator.parent()?,
12030 )))
12031}
12032
12033fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
12034 owner.map_or(
12035 DesignatedInitializerOwner::Unresolved,
12036 DesignatedInitializerOwner::Resolved,
12037 )
12038}
12039
12040fn initializer_list_owner(
12041 visibility: &VisibilityIndex<'_>,
12042 file: &ProjectFile,
12043 source: &str,
12044 initializer: Node<'_>,
12045) -> Option<CodeUnit> {
12046 let mut current = initializer;
12047 let mut outer_initializer_lists = 0usize;
12048 loop {
12049 let parent = current.parent()?;
12050 match parent.kind() {
12051 "initializer_pair" => return None,
12052 "initializer_list" => {
12053 outer_initializer_lists += 1;
12054 if outer_initializer_lists > 1 {
12055 return None;
12056 }
12057 current = parent;
12058 }
12059 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
12060 let declaration = parent.parent()?;
12061 if outer_initializer_lists == 1
12062 && !parent
12063 .child_by_field_name("declarator")
12064 .is_some_and(contains_array_declarator)
12065 {
12066 return None;
12067 }
12068 return declaration_owner(visibility, file, source, declaration);
12069 }
12070 "compound_literal_expression"
12071 if parent.child_by_field_name("value") == Some(current)
12072 && outer_initializer_lists == 0 =>
12073 {
12074 let type_node = parent.child_by_field_name("type")?;
12075 return resolve_designated_owner_type(visibility, file, source, type_node);
12076 }
12077 "ERROR" => current = parent,
12078 _ => return None,
12079 }
12080 }
12081}
12082
12083fn declaration_owner(
12084 visibility: &VisibilityIndex<'_>,
12085 file: &ProjectFile,
12086 source: &str,
12087 declaration: Node<'_>,
12088) -> Option<CodeUnit> {
12089 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
12090 return None;
12091 }
12092 let type_node = declaration
12093 .child_by_field_name("type")
12094 .or_else(|| first_type_child(declaration))?;
12095 resolve_designated_owner_type(visibility, file, source, type_node)
12096}
12097
12098fn resolve_designated_owner_type(
12099 visibility: &VisibilityIndex<'_>,
12100 file: &ProjectFile,
12101 source: &str,
12102 type_node: Node<'_>,
12103) -> Option<CodeUnit> {
12104 let type_name = normalize_type_text(node_text(type_node, source));
12105 visibility
12106 .resolve_type(file, &type_name)
12107 .filter(CodeUnit::is_class)
12108}
12109
12110fn contains_array_declarator(declarator: Node<'_>) -> bool {
12111 let mut stack = vec![declarator];
12112 while let Some(node) = stack.pop() {
12113 if node.kind() == "array_declarator" {
12114 return true;
12115 }
12116 if matches!(node.kind(), "initializer_list" | "compound_statement") {
12117 continue;
12118 }
12119 let mut cursor = node.walk();
12120 stack.extend(node.named_children(&mut cursor));
12121 }
12122 false
12123}
12124
12125pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
12126 let mut cursor = node.walk();
12127 node.named_children(&mut cursor).find(|child| {
12128 matches!(
12129 child.kind(),
12130 "type_identifier"
12131 | "primitive_type"
12132 | "qualified_identifier"
12133 | "scoped_type_identifier"
12134 | "struct_specifier"
12135 | "union_specifier"
12136 | "enum_specifier"
12137 )
12138 })
12139}
12140
12141pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
12142 visibility: &VisibilityIndex<'_>,
12143 file: &ProjectFile,
12144 source: &str,
12145 declarator: Node<'_>,
12146 type_text: Option<&str>,
12147 bindings: &LocalInferenceEngine<T>,
12148) -> bool {
12149 if !has_ancestor_kind(declarator, "compound_statement") {
12150 return false;
12151 }
12152 if declarator
12153 .child_by_field_name("declarator")
12154 .is_none_or(|declarator| declarator.kind() != "identifier")
12155 {
12156 return false;
12157 }
12158 if !type_text
12159 .and_then(|text| visibility.resolve_type(file, text))
12160 .is_some_and(|unit| unit.is_class())
12161 {
12162 return false;
12163 }
12164 declarator
12165 .child_by_field_name("parameters")
12166 .is_some_and(|parameters| {
12167 constructor_parameters_look_like_expressions(parameters, source, bindings)
12168 })
12169}
12170
12171fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
12172 parameters: Node<'_>,
12173 source: &str,
12174 bindings: &LocalInferenceEngine<T>,
12175) -> bool {
12176 let mut cursor = parameters.walk();
12177 parameters.named_children(&mut cursor).any(|parameter| {
12178 !matches!(
12179 parameter.kind(),
12180 "parameter_declaration" | "optional_parameter_declaration"
12181 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
12182 })
12183}
12184
12185fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
12186 parameter: Node<'_>,
12187 source: &str,
12188 bindings: &LocalInferenceEngine<T>,
12189) -> bool {
12190 let text = node_text(parameter, source).trim();
12191 if text
12192 .chars()
12193 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
12194 && bindings.is_shadowed(text)
12195 {
12196 return true;
12197 }
12198
12199 let Some(base) = parameter
12200 .child_by_field_name("type")
12201 .filter(|base| base.kind() == "type_identifier")
12202 else {
12203 return false;
12204 };
12205 let Some(subscript) = parameter
12206 .child_by_field_name("declarator")
12207 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
12208 else {
12209 return false;
12210 };
12211 subscript.child_by_field_name("size").is_some()
12212 && bindings.is_shadowed(node_text(base, source).trim())
12213}
12214
12215pub fn is_declaration_name(node: Node<'_>) -> bool {
12216 let Some(parent) = node.parent() else {
12217 return false;
12218 };
12219 if parent
12220 .child_by_field_name("name")
12221 .is_some_and(|name| same_node(name, node))
12222 {
12223 if matches!(
12224 parent.kind(),
12225 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
12226 ) {
12227 return cpp_tag_specifier_declares_name(parent);
12228 }
12229 if matches!(
12230 parent.kind(),
12231 "namespace_definition"
12232 | "namespace_alias_definition"
12233 | "alias_declaration"
12234 | "enumerator"
12235 ) {
12236 return true;
12237 }
12238 }
12239
12240 let mut current = Some(parent);
12241 while let Some(ancestor) = current {
12242 let type_definition = ancestor.kind() == "type_definition";
12243 let mut declarator_cursor = ancestor.walk();
12244 if ancestor
12245 .children_by_field_name("declarator", &mut declarator_cursor)
12246 .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
12247 {
12248 return true;
12249 }
12250 if matches!(
12251 ancestor.kind(),
12252 "declaration"
12253 | "field_declaration"
12254 | "parameter_declaration"
12255 | "optional_parameter_declaration"
12256 | "function_definition"
12257 | "type_definition"
12258 | "alias_declaration"
12259 | "class_specifier"
12260 | "struct_specifier"
12261 | "union_specifier"
12262 | "enum_specifier"
12263 ) {
12264 return false;
12265 }
12266 current = ancestor.parent();
12267 }
12268 false
12269}
12270
12271pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
12280 if !matches!(
12281 node.kind(),
12282 "qualified_identifier" | "scoped_type_identifier"
12283 ) {
12284 return false;
12285 }
12286 let Some(declaration) = node
12287 .parent()
12288 .filter(|parent| parent.kind() == "declaration")
12289 else {
12290 return false;
12291 };
12292 if declaration.child_by_field_name("declarator") != Some(node)
12293 || !declaration
12294 .child_by_field_name("type")
12295 .is_some_and(|friend| {
12296 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
12297 })
12298 {
12299 return false;
12300 }
12301 let mut cursor = declaration.walk();
12302 let mut errors = declaration
12303 .named_children(&mut cursor)
12304 .filter(|child| child.kind() == "ERROR");
12305 let Some(error) = errors.next() else {
12306 return false;
12307 };
12308 errors.next().is_none()
12309 && error.named_child_count() == 1
12310 && error.named_child(0).is_some_and(|class| {
12311 class.kind() == "identifier" && node_text(class, source) == "class"
12312 })
12313}
12314
12315pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
12316 if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
12317 return false;
12318 }
12319 if let Some(parent) = node.parent() {
12320 if parent.kind() == "call_expression"
12321 && parent.child_by_field_name("function") == Some(node)
12322 {
12323 return false;
12324 }
12325 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
12326 && parent.child_by_field_name("label") == Some(node)
12327 {
12328 return false;
12329 }
12330 }
12331 let mut current = node.parent();
12332 while let Some(ancestor) = current {
12333 match ancestor.kind() {
12334 "preproc_ifdef" | "preproc_ifndef" => {
12335 if ancestor
12336 .child_by_field_name("name")
12337 .is_some_and(|name| node_range_contains(name, node))
12338 {
12339 return false;
12340 }
12341 }
12342 "preproc_if" | "preproc_elif" => {
12343 if ancestor
12344 .child_by_field_name("condition")
12345 .is_some_and(|condition| node_range_contains(condition, node))
12346 {
12347 return false;
12348 }
12349 }
12350 "preproc_else" => {}
12351 kind if kind.starts_with("preproc_") => return false,
12352 _ => {}
12353 }
12354 if matches!(
12355 ancestor.kind(),
12356 "translation_unit" | "function_definition" | "compound_statement"
12357 ) {
12358 break;
12359 }
12360 current = ancestor.parent();
12361 }
12362 true
12363}
12364
12365fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
12366 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
12367}
12368
12369fn recovered_c_reference_node(
12370 visibility: &VisibilityIndex<'_>,
12371 file: &ProjectFile,
12372 node: Node<'_>,
12373 source: &str,
12374) -> bool {
12375 if node.start_byte() >= node.end_byte()
12376 || node.is_error()
12377 || node.is_missing()
12378 || !matches!(
12379 node.kind(),
12380 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
12381 )
12382 || recovered_c_macro_binding_role(node)
12383 || recovered_c_label_role(node)
12384 {
12385 return false;
12386 }
12387
12388 let name = node_text(node, source);
12389 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
12390 return true;
12391 }
12392 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
12393 return true;
12394 }
12395 if is_declaration_name(node) {
12396 return false;
12397 }
12398 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
12399 return true;
12400 }
12401 recovered_c_reference_anchor(node)
12402}
12403
12404fn recovered_c_explicit_assignment_callee(
12405 visibility: &VisibilityIndex<'_>,
12406 file: &ProjectFile,
12407 node: Node<'_>,
12408 name: &str,
12409) -> bool {
12410 let mut current = node;
12411 let error = loop {
12412 let Some(parent) = current.parent() else {
12413 return false;
12414 };
12415 if parent.is_error() {
12416 break parent;
12417 }
12418 current = parent;
12419 };
12420 let mut cursor = error.walk();
12421 let explicit_recovery_precedes_callee = error
12422 .named_children(&mut cursor)
12423 .take_while(|child| child.start_byte() < node.start_byte())
12424 .any(|child| child.kind() == "explicit_function_specifier");
12425 if !explicit_recovery_precedes_callee {
12426 return false;
12427 }
12428 visibility
12429 .cpp
12430 .declarations(file)
12431 .iter()
12432 .chain(visibility.visible_by_file.get(file).into_iter().flatten())
12433 .any(|candidate| candidate.identifier() == name && candidate.is_function())
12434}
12435
12436fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
12437 while let Some(parent) = node.parent() {
12438 if matches!(
12439 parent.kind(),
12440 "preproc_def" | "preproc_function_def" | "preproc_params"
12441 ) {
12442 return true;
12443 }
12444 if parent.is_error()
12445 || matches!(
12446 parent.kind(),
12447 "translation_unit" | "function_definition" | "compound_statement"
12448 )
12449 {
12450 return false;
12451 }
12452 node = parent;
12453 }
12454 false
12455}
12456
12457fn recovered_c_label_role(node: Node<'_>) -> bool {
12458 node.parent().is_some_and(|parent| {
12459 matches!(parent.kind(), "labeled_statement" | "goto_statement")
12460 && parent.child_by_field_name("label") == Some(node)
12461 })
12462}
12463
12464fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
12465 while let Some(parent) = node.parent() {
12466 if parent.is_error() {
12467 return false;
12468 }
12469 if parent.kind().ends_with("_expression")
12470 || matches!(
12471 parent.kind(),
12472 "argument_list"
12473 | "return_statement"
12474 | "expression_statement"
12475 | "case_statement"
12476 | "initializer_list"
12477 | "init_declarator"
12478 | "array_declarator"
12479 | "field_designator"
12480 | "enumerator"
12481 )
12482 {
12483 return true;
12484 }
12485 if matches!(
12486 parent.kind(),
12487 "translation_unit"
12488 | "function_definition"
12489 | "compound_statement"
12490 | "declaration"
12491 | "field_declaration"
12492 | "parameter_declaration"
12493 ) {
12494 return false;
12495 }
12496 node = parent;
12497 }
12498 false
12499}
12500
12501pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
12509 let mut current = parameter.parent();
12510 while let Some(ancestor) = current {
12511 if ancestor.kind() == "lambda_expression" {
12512 return ancestor
12513 .child_by_field_name("declarator")
12514 .is_some_and(|declarator| {
12515 declarator.start_byte() <= parameter.start_byte()
12516 && parameter.end_byte() <= declarator.end_byte()
12517 });
12518 }
12519 if ancestor.kind() == "function_definition" {
12520 return ancestor
12521 .child_by_field_name("declarator")
12522 .is_some_and(|declarator| {
12523 declarator.start_byte() <= parameter.start_byte()
12524 && parameter.end_byte() <= declarator.end_byte()
12525 });
12526 }
12527 current = ancestor.parent();
12528 }
12529 false
12530}
12531
12532pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
12533 let mut current = node.parent();
12534 while let Some(ancestor) = current {
12535 if matches!(
12536 ancestor.kind(),
12537 "parameter_declaration" | "optional_parameter_declaration"
12538 ) {
12539 return ancestor
12540 .child_by_field_name("type")
12541 .is_some_and(|type_node| {
12542 type_node.start_byte() <= node.start_byte()
12543 && node.end_byte() <= type_node.end_byte()
12544 });
12545 }
12546 if matches!(
12547 ancestor.kind(),
12548 "function_definition" | "lambda_expression" | "compound_statement"
12549 ) {
12550 return false;
12551 }
12552 current = ancestor.parent();
12553 }
12554 false
12555}
12556
12557fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
12558 if specifier.child_by_field_name("body").is_some() {
12559 return true;
12560 }
12561 let mut current = specifier.parent();
12562 while let Some(ancestor) = current {
12563 match ancestor.kind() {
12564 "type_descriptor"
12565 | "parameter_declaration"
12566 | "optional_parameter_declaration"
12567 | "template_argument_list"
12568 | "cast_expression" => return false,
12569 "declaration" | "field_declaration" => {
12570 let mut cursor = ancestor.walk();
12571 return ancestor
12572 .children_by_field_name("declarator", &mut cursor)
12573 .next()
12574 .is_none();
12575 }
12576 "translation_unit" => return true,
12577 _ => current = ancestor.parent(),
12578 }
12579 }
12580 false
12581}
12582
12583pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
12584 match node.kind() {
12585 "identifier"
12586 | "field_identifier"
12587 | "qualified_identifier"
12588 | "scoped_identifier"
12589 | "operator_name"
12590 | "destructor_name"
12591 | "literal_operator_name" => Some(node),
12592 "reference_declarator" | "parenthesized_declarator" => {
12593 node.named_child(0).and_then(declarator_name_node)
12594 }
12595 _ => node
12596 .child_by_field_name("declarator")
12597 .or_else(|| node.child_by_field_name("name"))
12598 .or_else(|| node.child_by_field_name("field"))
12599 .and_then(declarator_name_node),
12600 }
12601}
12602
12603fn declarator_name_path_contains(
12604 declarator: Node<'_>,
12605 candidate: Node<'_>,
12606 allow_type_identifier: bool,
12607) -> bool {
12608 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
12609 return false;
12610 };
12611 let mut current = Some(declarator);
12612 while let Some(node) = current {
12613 if same_node(node, candidate) {
12614 return true;
12615 }
12616 if same_node(node, name) {
12617 return false;
12618 }
12619 current = node
12620 .child_by_field_name("declarator")
12621 .or_else(|| node.child_by_field_name("name"))
12622 .or_else(|| node.child_by_field_name("field"));
12623 }
12624 false
12625}
12626
12627fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
12628 match node.kind() {
12629 "identifier"
12630 | "field_identifier"
12631 | "operator_name"
12632 | "destructor_name"
12633 | "literal_operator_name" => Some(node),
12634 "type_identifier" if allow_type_identifier => Some(node),
12635 _ => node
12636 .child_by_field_name("declarator")
12637 .or_else(|| node.child_by_field_name("name"))
12638 .or_else(|| node.child_by_field_name("field"))
12639 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
12640 }
12641}
12642
12643pub fn is_nested_type_node(node: Node<'_>) -> bool {
12646 node.parent().is_some_and(|parent| {
12647 matches!(
12648 parent.kind(),
12649 "qualified_identifier" | "scoped_type_identifier" | "template_type"
12650 )
12651 })
12652}
12653
12654pub struct OutOfLineMemberDefinitionOwners<'tree> {
12655 pub owners: Vec<(Node<'tree>, CodeUnit)>,
12656 innermost: Option<(Node<'tree>, CodeUnit)>,
12657}
12658
12659impl OutOfLineMemberDefinitionOwners<'_> {
12660 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
12661 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
12662 }
12663}
12664
12665pub struct QualifiedOwnerComponents<'tree> {
12666 pub nodes: Vec<Node<'tree>>,
12667 pub names: Vec<String>,
12668 pub global: bool,
12669}
12670
12671pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
12676 let mut stack = vec![node];
12677 let mut found_separator = false;
12678 while let Some(current) = stack.pop() {
12679 if !matches!(
12680 current.kind(),
12681 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12682 ) {
12683 continue;
12684 }
12685 let mut current_has_separator = false;
12686 for index in 0..current.child_count() {
12687 let Some(child) = current.child(index) else {
12688 continue;
12689 };
12690 if child.kind() == "::" {
12691 if child.is_missing() {
12692 return false;
12693 }
12694 current_has_separator = true;
12695 found_separator = true;
12696 }
12697 }
12698 if !current_has_separator {
12699 return false;
12700 }
12701 for field in ["scope", "name"] {
12702 if let Some(child) = current.child_by_field_name(field)
12703 && matches!(
12704 child.kind(),
12705 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12706 )
12707 {
12708 stack.push(child);
12709 }
12710 }
12711 }
12712 found_separator
12713}
12714
12715pub fn qualified_owner_components<'tree>(
12716 node: Node<'tree>,
12717 source: &str,
12718) -> Option<QualifiedOwnerComponents<'tree>> {
12719 if !qualified_name_has_concrete_scope_separators(node) {
12720 return None;
12721 }
12722 let mut nodes = cpp_name_component_nodes(node)?;
12723 nodes.pop()?;
12724 if nodes.is_empty() {
12725 return None;
12726 }
12727 let names = nodes
12728 .iter()
12729 .map(|component| node_text(*component, source).to_string())
12730 .collect();
12731 Some(QualifiedOwnerComponents {
12732 nodes,
12733 names,
12734 global: is_globally_qualified_cpp_name(node),
12735 })
12736}
12737
12738pub fn out_of_line_member_definition_owner<'tree>(
12739 analyzer: &CppGraphSource<'_>,
12740 visibility: &VisibilityIndex<'_>,
12741 file: &ProjectFile,
12742 source: &str,
12743 node: Node<'tree>,
12744) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
12745 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
12746 || !has_ancestor_kind(node, "function_definition")
12747 || !is_function_declarator_name_root(node)
12748 {
12749 return None;
12750 }
12751 let qualified = qualified_owner_components(node, source)?;
12752 let lexical_scope = enclosing_namespace_components(node, source)?;
12753 let mut owners = Vec::new();
12754 let mut innermost = None;
12755
12756 for component_count in 1..=qualified.names.len() {
12757 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
12758 .resolve_type_components_lexically(
12759 analyzer,
12760 file,
12761 &qualified.names[..component_count],
12762 qualified.global,
12763 &lexical_scope,
12764 )
12765 && !owners
12766 .iter()
12767 .any(|(_, existing)| same_visible_symbol(existing, &unit))
12768 {
12769 if component_count == qualified.names.len() {
12770 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
12771 }
12772 owners.push((qualified.nodes[component_count - 1], unit));
12773 }
12774 }
12775
12776 if innermost.is_none() {
12786 let indexed_owner_components = visibility
12787 .indexed_enclosing_owner_scope(analyzer, file, node)
12788 .or_else(|| {
12789 if qualified.names.len() <= 1 {
12794 return None;
12795 }
12796 let range = Range {
12797 start_byte: node.start_byte(),
12798 end_byte: node.end_byte(),
12799 start_line: node.start_position().row,
12800 end_line: node.end_position().row,
12801 };
12802 let start = analyzer.enclosing_code_unit(file, &range)?;
12803 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12804 brokk_bifrost_core::analyzer::Language::Cpp,
12805 &cpp_name_for(&start),
12806 );
12807 components.pop();
12808 Some(components)
12809 });
12810 if let Some(indexed_owner_components) = indexed_owner_components
12811 && indexed_owner_components.len() > qualified.names.len()
12812 && indexed_owner_components.ends_with(&qualified.names)
12813 && indexed_namespace_path_is_recoverable(
12814 &lexical_scope,
12815 &indexed_owner_components,
12816 qualified.names.len(),
12817 )
12818 && (qualified.names.len() > 1 || !qualified.global)
12823 {
12824 let namespace_count = indexed_owner_components.len() - qualified.names.len();
12825 for component_count in 1..=qualified.names.len() {
12826 let expected = &indexed_owner_components[..namespace_count + component_count];
12827 let owner_node = qualified.nodes[component_count - 1];
12828 for owner in visibility
12829 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
12830 .filter(|candidate| candidate.is_class())
12831 .filter(|candidate| {
12832 canonical_cpp_scope_components(candidate) == expected
12833 && visibility.external_type_candidate_visible_in_context(
12834 analyzer, file, candidate, node,
12835 )
12836 })
12837 {
12838 if component_count == qualified.names.len() && innermost.is_none() {
12839 innermost = Some((owner_node, owner.clone()));
12840 }
12841 if !owners
12842 .iter()
12843 .any(|(_, existing)| same_symbol(existing, owner))
12844 {
12845 owners.push((owner_node, owner.clone()));
12846 }
12847 }
12848 }
12849 }
12850 }
12851 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
12852}
12853
12854fn is_function_declarator_name_root(node: Node<'_>) -> bool {
12855 let mut current = node;
12856 while let Some(parent) = current.parent() {
12857 if parent.kind() == "function_declarator" {
12858 return parent.child_by_field_name("declarator") == Some(current);
12859 }
12860 if matches!(
12861 parent.kind(),
12862 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
12863 ) && parent.child_by_field_name("declarator") == Some(current)
12864 {
12865 current = parent;
12866 continue;
12867 }
12868 return false;
12869 }
12870 false
12871}
12872
12873pub fn append_cpp_name_components(
12874 node: Node<'_>,
12875 source: &str,
12876 out: &mut Vec<String>,
12877) -> Option<()> {
12878 out.extend(
12879 cpp_name_component_nodes(node)?
12880 .into_iter()
12881 .map(|component| node_text(component, source).to_string()),
12882 );
12883 Some(())
12884}
12885
12886pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
12887 let mut components = Vec::new();
12888 append_cpp_name_components(node, source, &mut components)?;
12889 Some(components)
12890}
12891
12892pub fn unique_macro_replacement_type_candidate(
12901 analyzer: &CppGraphSource<'_>,
12902 visibility: &VisibilityIndex<'_>,
12903 file: &ProjectFile,
12904 components: &[String],
12905) -> Option<CodeUnit> {
12906 let terminal = components.last()?;
12907 let mut candidates = Vec::new();
12908 for candidate in visibility
12909 .visible_identifier_candidates(file, terminal)
12910 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
12911 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
12912 {
12913 if !candidates
12914 .iter()
12915 .any(|existing| same_logical_symbol(existing, candidate))
12916 {
12917 candidates.push(candidate.clone());
12918 }
12919 }
12920 (candidates.len() == 1).then(|| candidates.remove(0))
12921}
12922
12923pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
12931 let mut parser = Parser::new();
12932 if parser
12933 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12934 .is_err()
12935 {
12936 return Vec::new();
12937 }
12938 let Some(tree) = parser.parse(source, None) else {
12939 return Vec::new();
12940 };
12941 let mut scopes = Vec::new();
12942 let mut pending = vec![tree.root_node()];
12943 while let Some(node) = pending.pop() {
12944 if node.kind() == "using_declaration" {
12945 let Some(imported) = node.named_child(0) else {
12946 continue;
12947 };
12948 let Some(mut components) = cpp_type_name_components(imported, source) else {
12949 continue;
12950 };
12951 if components.pop().as_deref() == Some(member) && !components.is_empty() {
12952 scopes.push(components.join("::"));
12953 }
12954 continue;
12955 }
12956 for index in (0..node.named_child_count()).rev() {
12957 if let Some(child) = node.named_child(index) {
12958 pending.push(child);
12959 }
12960 }
12961 }
12962 scopes
12963}
12964
12965pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
12969 qualified == scope
12970 || qualified
12971 .strip_suffix(scope)
12972 .is_some_and(|prefix| prefix.ends_with("::"))
12973}
12974
12975pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
12980 let Some(type_descriptor) = node.parent() else {
12981 return false;
12982 };
12983 if type_descriptor.kind() != "type_descriptor"
12984 || type_descriptor.child_by_field_name("type") != Some(node)
12985 {
12986 return false;
12987 }
12988 let Some(arguments) = type_descriptor.parent() else {
12989 return false;
12990 };
12991 if arguments.kind() != "template_argument_list" {
12992 return false;
12993 }
12994 arguments.parent().is_some_and(|parent| {
12995 matches!(parent.kind(), "template_type" | "template_function")
12996 && parent.child_by_field_name("arguments") == Some(arguments)
12997 })
12998}
12999
13000pub fn cpp_template_reference_arguments(
13001 mut node: Node<'_>,
13002 source: &str,
13003) -> Option<Vec<CppTemplateExpression>> {
13004 loop {
13005 match node.kind() {
13006 "template_type" | "template_function" => {
13007 let arguments = node.child_by_field_name("arguments")?;
13008 let mut cursor = arguments.walk();
13009 return Some(
13010 arguments
13011 .named_children(&mut cursor)
13012 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
13013 .map(|argument| CppTemplateExpression {
13014 text: normalize_cpp_whitespace(node_text(argument, source)),
13015 term: cpp_template_term(
13017 argument,
13018 source,
13019 &[],
13020 &ParentIndex::unindexed(),
13021 ),
13022 })
13023 .collect(),
13024 );
13025 }
13026 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
13027 node = node
13028 .child_by_field_name("name")
13029 .or_else(|| node.child_by_field_name("type"))?;
13030 }
13031 _ => return None,
13032 }
13033 }
13034}
13035
13036fn cpp_reconcile_primary_template_parameters(
13037 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
13038 preferred: &CodeUnit,
13039) -> Option<Vec<CppTemplateParameterMetadata>> {
13040 let canonical = candidates
13041 .iter()
13042 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
13043 let mut merged = canonical
13044 .parameters
13045 .iter()
13046 .map(|parameter| CppTemplateParameterMetadata {
13047 name: parameter.name.clone(),
13048 kind: parameter.kind,
13049 variadic: parameter.variadic,
13050 default: None,
13051 })
13052 .collect::<Vec<_>>();
13053
13054 for (_, metadata) in candidates {
13055 if metadata.parameters.len() != merged.len() {
13056 return None;
13057 }
13058 let rename_bindings = metadata
13059 .parameters
13060 .iter()
13061 .zip(&merged)
13062 .map(|(parameter, canonical)| {
13063 (
13064 parameter.name.clone(),
13065 CppTemplateTerm::Parameter(canonical.name.clone()),
13066 )
13067 })
13068 .collect::<HashMap<_, _>>();
13069 for ((parameter, canonical), merged_parameter) in metadata
13070 .parameters
13071 .iter()
13072 .zip(&canonical.parameters)
13073 .zip(&mut merged)
13074 {
13075 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
13076 return None;
13077 }
13078 let Some(default) = ¶meter.default else {
13079 continue;
13080 };
13081 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
13082 if let Some(existing) = &merged_parameter.default {
13083 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
13084 return None;
13085 }
13086 } else {
13087 merged_parameter.default = Some(CppTemplateExpression {
13088 text: default.text.clone(),
13089 term: normalized_term,
13090 });
13091 }
13092 }
13093 }
13094 Some(merged)
13095}
13096
13097pub fn cpp_bind_template_arguments(
13098 parameters: &[CppTemplateParameterMetadata],
13099 explicit_arguments: &[CppTemplateExpression],
13100) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
13101 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
13102 if variadic_index.is_some_and(|index| {
13103 index + 1 != parameters.len()
13104 || parameters[index + 1..]
13105 .iter()
13106 .any(|parameter| parameter.variadic)
13107 }) {
13108 return None;
13109 }
13110 let fixed_count = variadic_index.unwrap_or(parameters.len());
13111 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
13112 return None;
13113 }
13114 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
13115 let mut expanded = explicit_arguments[..explicit_fixed_count]
13116 .iter()
13117 .map(cpp_clone_template_expression_iterative)
13118 .collect::<Vec<_>>();
13119 let mut bindings = HashMap::default();
13120 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
13121 bindings.insert(
13122 parameter.name.clone(),
13123 cpp_clone_template_term_iterative(&argument.term),
13124 );
13125 }
13126 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
13127 let default = parameter.default.as_ref()?;
13128 let term = cpp_substitute_template_term(&default.term, &bindings)?;
13129 bindings.insert(parameter.name.clone(), term.clone());
13130 expanded.push(CppTemplateExpression {
13131 text: default.text.clone(),
13132 term,
13133 });
13134 }
13135 if let Some(index) = variadic_index {
13136 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
13137 expanded.extend(
13138 packed_arguments
13139 .iter()
13140 .map(cpp_clone_template_expression_iterative),
13141 );
13142 bindings.insert(
13143 parameters[index].name.clone(),
13144 CppTemplateTerm::Node {
13145 kind: "parameter_pack".to_string(),
13146 children: packed_arguments
13147 .iter()
13148 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
13149 .collect(),
13150 },
13151 );
13152 }
13153 Some((expanded, bindings))
13154}
13155
13156fn cpp_specialization_matches(
13157 metadata: &CppTemplateMetadata,
13158 arguments: &[CppTemplateExpression],
13159) -> bool {
13160 if metadata.specialization_arguments.len() != arguments.len() {
13161 return false;
13162 }
13163 let parameter_names = metadata
13164 .parameters
13165 .iter()
13166 .map(|parameter| parameter.name.as_str())
13167 .collect::<HashSet<_>>();
13168 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
13169 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
13170 if !cpp_unify_template_term(
13171 &pattern.term,
13172 &argument.term,
13173 ¶meter_names,
13174 &mut bindings,
13175 ) {
13176 return false;
13177 }
13178 }
13179 true
13180}
13181
13182fn cpp_specialization_more_specialized(
13183 candidate: &CppTemplateMetadata,
13184 other: &CppTemplateMetadata,
13185) -> bool {
13186 cpp_specialization_pattern_accepts(other, candidate)
13187 && !cpp_specialization_pattern_accepts(candidate, other)
13188}
13189
13190fn cpp_specialization_pattern_accepts(
13191 broader: &CppTemplateMetadata,
13192 narrower: &CppTemplateMetadata,
13193) -> bool {
13194 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
13195 return false;
13196 }
13197 let parameter_names = broader
13198 .parameters
13199 .iter()
13200 .map(|parameter| parameter.name.as_str())
13201 .collect::<HashSet<_>>();
13202 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
13203 broader
13204 .specialization_arguments
13205 .iter()
13206 .zip(&narrower.specialization_arguments)
13207 .all(|(pattern, argument)| {
13208 cpp_unify_template_term(
13209 &pattern.term,
13210 &argument.term,
13211 ¶meter_names,
13212 &mut bindings,
13213 )
13214 })
13215}
13216
13217pub fn cpp_substitute_template_term(
13218 term: &CppTemplateTerm,
13219 bindings: &HashMap<String, CppTemplateTerm>,
13220) -> Option<CppTemplateTerm> {
13221 enum Work<'a> {
13222 Visit(&'a CppTemplateTerm),
13223 Build { kind: String, child_count: usize },
13224 }
13225
13226 let mut work = vec![Work::Visit(term)];
13227 let mut substituted = Vec::new();
13228 while let Some(next) = work.pop() {
13229 match next {
13230 Work::Visit(CppTemplateTerm::Parameter(name)) => {
13231 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
13232 }
13233 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
13234 substituted.push(CppTemplateTerm::Atom {
13235 kind: kind.clone(),
13236 text: text.clone(),
13237 });
13238 }
13239 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
13240 work.push(Work::Build {
13241 kind: kind.clone(),
13242 child_count: children.len(),
13243 });
13244 work.extend(children.iter().rev().map(Work::Visit));
13245 }
13246 Work::Build { kind, child_count } => {
13247 let children = substituted.split_off(substituted.len() - child_count);
13248 substituted.push(CppTemplateTerm::Node { kind, children });
13249 }
13250 }
13251 }
13252 substituted.pop()
13253}
13254
13255pub fn cpp_substitute_template_arguments(
13256 arguments: &[CppTemplateExpression],
13257 bindings: &HashMap<String, CppTemplateTerm>,
13258) -> Option<Vec<CppTemplateExpression>> {
13259 let mut substituted = Vec::new();
13260 for argument in arguments {
13261 let CppTemplateTerm::Node { kind, children } = &argument.term else {
13262 substituted.push(CppTemplateExpression {
13263 text: argument.text.clone(),
13264 term: cpp_substitute_template_term(&argument.term, bindings)?,
13265 });
13266 continue;
13267 };
13268 if kind != "parameter_pack_expansion" {
13269 substituted.push(CppTemplateExpression {
13270 text: argument.text.clone(),
13271 term: cpp_substitute_template_term(&argument.term, bindings)?,
13272 });
13273 continue;
13274 }
13275 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
13276 return None;
13277 };
13278 if ellipsis != "..." {
13279 return None;
13280 }
13281
13282 let mut pack_names = Vec::new();
13283 let mut work = vec![pattern];
13284 while let Some(term) = work.pop() {
13285 match term {
13286 CppTemplateTerm::Parameter(name)
13287 if matches!(
13288 bindings.get(name),
13289 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
13290 ) =>
13291 {
13292 if !pack_names.contains(name) {
13293 pack_names.push(name.clone());
13294 }
13295 }
13296 CppTemplateTerm::Node { children, .. } => work.extend(children),
13297 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
13298 }
13299 }
13300 let first_pack = pack_names.first()?;
13301 let CppTemplateTerm::Node {
13302 children: first_elements,
13303 ..
13304 } = bindings.get(first_pack)?
13305 else {
13306 return None;
13307 };
13308 let pack_len = first_elements.len();
13309 for pack_name in &pack_names {
13310 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
13311 return None;
13312 };
13313 if children.len() != pack_len {
13314 return None;
13315 }
13316 }
13317 for index in 0..pack_len {
13318 let mut element_bindings = bindings.clone();
13319 for pack_name in &pack_names {
13320 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
13321 return None;
13322 };
13323 element_bindings.insert(
13324 pack_name.clone(),
13325 cpp_clone_template_term_iterative(&children[index]),
13326 );
13327 }
13328 substituted.push(CppTemplateExpression {
13329 text: argument.text.clone(),
13330 term: cpp_substitute_template_term(pattern, &element_bindings)?,
13331 });
13332 }
13333 }
13334 Some(substituted)
13335}
13336
13337fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
13338 enum Work<'a> {
13339 Visit(&'a CppTemplateTerm),
13340 Build { kind: String, child_count: usize },
13341 }
13342
13343 let mut work = vec![Work::Visit(term)];
13344 let mut cloned = Vec::new();
13345 while let Some(next) = work.pop() {
13346 match next {
13347 Work::Visit(CppTemplateTerm::Parameter(name)) => {
13348 cloned.push(CppTemplateTerm::Parameter(name.clone()));
13349 }
13350 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
13351 cloned.push(CppTemplateTerm::Atom {
13352 kind: kind.clone(),
13353 text: text.clone(),
13354 });
13355 }
13356 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
13357 work.push(Work::Build {
13358 kind: kind.clone(),
13359 child_count: children.len(),
13360 });
13361 work.extend(children.iter().rev().map(Work::Visit));
13362 }
13363 Work::Build { kind, child_count } => {
13364 let children = cloned.split_off(cloned.len() - child_count);
13365 cloned.push(CppTemplateTerm::Node { kind, children });
13366 }
13367 }
13368 }
13369 cloned
13370 .pop()
13371 .expect("template term traversal emits one root")
13372}
13373
13374fn cpp_clone_template_expression_iterative(
13375 expression: &CppTemplateExpression,
13376) -> CppTemplateExpression {
13377 CppTemplateExpression {
13378 text: expression.text.clone(),
13379 term: cpp_clone_template_term_iterative(&expression.term),
13380 }
13381}
13382
13383pub fn cpp_unify_template_term(
13384 pattern: &CppTemplateTerm,
13385 argument: &CppTemplateTerm,
13386 parameters: &HashSet<&str>,
13387 bindings: &mut HashMap<String, CppTemplateTerm>,
13388) -> bool {
13389 let mut work = vec![(pattern, argument)];
13390 while let Some((pattern, argument)) = work.pop() {
13391 match pattern {
13392 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
13393 if let Some(bound) = bindings.get(name) {
13394 if !cpp_template_terms_equal(bound, argument) {
13395 return false;
13396 }
13397 } else {
13398 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
13399 }
13400 }
13401 CppTemplateTerm::Atom {
13402 kind: pattern_kind,
13403 text: pattern_text,
13404 } => {
13405 if !matches!(
13406 argument,
13407 CppTemplateTerm::Atom { kind, text }
13408 if kind == pattern_kind && text == pattern_text
13409 ) {
13410 return false;
13411 }
13412 }
13413 CppTemplateTerm::Node {
13414 kind: pattern_kind,
13415 children: pattern_children,
13416 } => {
13417 let CppTemplateTerm::Node { kind, children } = argument else {
13418 return false;
13419 };
13420 if kind != pattern_kind || children.len() != pattern_children.len() {
13421 return false;
13422 }
13423 work.extend(pattern_children.iter().zip(children).rev());
13424 }
13425 CppTemplateTerm::Parameter(_) => return false,
13426 }
13427 }
13428 true
13429}
13430
13431fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
13432 let mut work = vec![(left, right)];
13433 while let Some((left, right)) = work.pop() {
13434 match (left, right) {
13435 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
13436 if left != right {
13437 return false;
13438 }
13439 }
13440 (
13441 CppTemplateTerm::Atom {
13442 kind: left_kind,
13443 text: left_text,
13444 },
13445 CppTemplateTerm::Atom {
13446 kind: right_kind,
13447 text: right_text,
13448 },
13449 ) => {
13450 if left_kind != right_kind || left_text != right_text {
13451 return false;
13452 }
13453 }
13454 (
13455 CppTemplateTerm::Node {
13456 kind: left_kind,
13457 children: left_children,
13458 },
13459 CppTemplateTerm::Node {
13460 kind: right_kind,
13461 children: right_children,
13462 },
13463 ) => {
13464 if left_kind != right_kind || left_children.len() != right_children.len() {
13465 return false;
13466 }
13467 work.extend(left_children.iter().zip(right_children).rev());
13468 }
13469 _ => return false,
13470 }
13471 }
13472 true
13473}
13474
13475pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
13476 let mut components = Vec::new();
13477 let mut stack = vec![node];
13478 while let Some(current) = stack.pop() {
13479 match current.kind() {
13480 "identifier"
13481 | "field_identifier"
13482 | "namespace_identifier"
13483 | "type_identifier"
13484 | "operator_name"
13485 | "destructor_name" => components.push(current),
13486 "template_type" | "template_function" => {
13487 stack.push(current.child_by_field_name("name")?);
13488 }
13489 "dependent_name" => stack.push(current.named_child(0)?),
13490 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13491 stack.push(current.child_by_field_name("name")?);
13492 if let Some(scope) = current.child_by_field_name("scope") {
13493 stack.push(scope);
13494 }
13495 }
13496 "nested_namespace_specifier" => {
13497 for index in (0..current.named_child_count()).rev() {
13498 stack.push(current.named_child(index)?);
13499 }
13500 }
13501 _ => return None,
13502 }
13503 }
13504 Some(components)
13505}
13506
13507pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
13508 node.child_by_field_name("scope").is_none()
13509 && node.child(0).is_some_and(|child| child.kind() == "::")
13510}
13511
13512fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
13513 let mut namespaces = Vec::new();
13514 let mut current = node.parent();
13515 while let Some(parent) = current {
13516 if parent.kind() == "namespace_definition"
13517 && let Some(name) = parent.child_by_field_name("name")
13518 {
13519 let mut components = Vec::new();
13520 append_cpp_name_components(name, source, &mut components)?;
13521 namespaces.push(components);
13522 }
13523 current = parent.parent();
13524 }
13525 namespaces.reverse();
13526 Some(namespaces.into_iter().flatten().collect())
13527}
13528
13529fn indexed_namespace_path_is_recoverable(
13540 lexical_scope: &[String],
13541 indexed_owner_scope: &[String],
13542 explicit_owner_component_count: usize,
13543) -> bool {
13544 if lexical_scope.is_empty() {
13545 return explicit_owner_component_count > 1;
13546 }
13547 if lexical_scope.len() >= indexed_owner_scope.len() {
13548 return false;
13549 }
13550 let mut indexed = indexed_owner_scope.iter();
13551 lexical_scope
13552 .iter()
13553 .all(|component| indexed.any(|candidate| candidate == component))
13554}
13555
13556pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
13557 let mut current = node.parent();
13558 while let Some(parent) = current {
13559 if parent.kind() == kind {
13560 return true;
13561 }
13562 current = parent.parent();
13563 }
13564 false
13565}
13566
13567pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
13573 let mut current = Some(node);
13574 while let Some(candidate) = current {
13575 if candidate.kind() == "declaration" {
13576 let Some(type_node) = candidate.child_by_field_name("type") else {
13577 return false;
13578 };
13579 if !(type_node.start_byte() <= node.start_byte()
13580 && node.end_byte() <= type_node.end_byte())
13581 {
13582 return false;
13583 }
13584 let mut cursor = candidate.walk();
13585 return candidate.named_children(&mut cursor).any(|child| {
13586 child.kind() == "init_declarator"
13587 && child
13588 .child_by_field_name("value")
13589 .is_some_and(|value| value.kind() == "cast_expression")
13590 });
13591 }
13592 current = candidate.parent();
13593 }
13594 false
13595}
13596
13597#[derive(Clone, Copy, PartialEq, Eq)]
13598pub(crate) enum QualifiedAliasReferenceKind {
13599 Ordinary,
13600 ConstructorWithExpressionArgument,
13601 ExhaustiveTemplate,
13602}
13603
13604pub(crate) fn qualified_alias_reference_preserves_target(
13611 node: Node<'_>,
13612 target: &CodeUnit,
13613 analyzer: &CppGraphSource<'_>,
13614 visibility: &VisibilityIndex<'_>,
13615 file: &ProjectFile,
13616 source: &str,
13617) -> Option<QualifiedAliasReferenceKind> {
13618 if !matches!(
13619 node.kind(),
13620 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
13621 ) {
13622 return None;
13623 }
13624 let components = cpp_type_name_components(node, source)?;
13625 let name = components.last()?;
13626 analyzer.type_alias_provider().and_then(|provider| {
13627 visibility
13628 .visible_identifier_candidates(file, name)
13629 .find_map(|candidate| {
13630 let proof = provider.is_type_alias(candidate)
13631 && canonical_cpp_scope_components(candidate) == components
13632 && visibility.external_type_candidate_visible_in_context(
13633 analyzer, file, candidate, node,
13634 )
13635 && match cpp_template_reference_arguments(node, source) {
13636 Some(arguments) => visibility.template_alias_arguments_preserve_target(
13637 analyzer, file, candidate, &arguments, target,
13638 ),
13639 None => visibility.structured_alias_primary_preserves_target(
13640 analyzer, file, candidate, target,
13641 ),
13642 };
13643 proof.then(|| {
13644 if cpp_template_reference_arguments(node, source).is_some()
13645 && visibility.is_exhaustive_same_fqn_type_declaration_family(
13646 analyzer, file, candidate,
13647 )
13648 {
13649 QualifiedAliasReferenceKind::ExhaustiveTemplate
13650 } else if qualified_alias_constructor_has_expression_argument(node)
13651 || qualified_alias_local_constructor_declaration(node)
13652 {
13653 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
13654 } else {
13655 QualifiedAliasReferenceKind::Ordinary
13656 }
13657 })
13658 })
13659 })
13660}
13661
13662pub(crate) fn qualified_alias_reference_requires_terminal(
13663 reference: Option<QualifiedAliasReferenceKind>,
13664) -> bool {
13665 matches!(
13666 reference,
13667 Some(
13668 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
13669 | QualifiedAliasReferenceKind::ExhaustiveTemplate
13670 )
13671 )
13672}
13673
13674fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
13675 let Some(declaration) = node.parent().filter(|parent| {
13676 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
13677 }) else {
13678 return false;
13679 };
13680 let mut cursor = declaration.walk();
13681 declaration.named_children(&mut cursor).any(|child| {
13682 child.kind() == "init_declarator"
13683 && child
13684 .child_by_field_name("value")
13685 .filter(|value| value.kind() == "argument_list")
13686 .is_some_and(|arguments| {
13687 let mut cursor = arguments.walk();
13688 arguments.named_children(&mut cursor).any(|argument| {
13689 let is_parameter = matches!(
13690 argument.kind(),
13691 "parameter_declaration" | "optional_parameter_declaration"
13692 );
13693 if is_parameter {
13694 argument
13695 .child_by_field_name("type")
13696 .is_some_and(|type_node| {
13697 type_node.kind() == "type_identifier"
13698 && argument.child_by_field_name("declarator").is_none()
13699 })
13700 } else {
13701 !argument.kind().ends_with("_literal")
13702 && !matches!(argument.kind(), "true" | "false" | "nullptr")
13703 }
13704 })
13705 })
13706 })
13707}
13708
13709fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
13714 let Some(declaration) = node.parent().filter(|parent| {
13715 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
13716 }) else {
13717 return false;
13718 };
13719 if declaration
13720 .parent()
13721 .is_none_or(|parent| parent.kind() != "compound_statement")
13722 {
13723 return false;
13724 }
13725 let mut cursor = declaration.walk();
13726 declaration
13727 .named_children(&mut cursor)
13728 .any(|child| child.kind() == "function_declarator")
13729}
13730
13731pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
13737 loop {
13738 let next = match node.kind() {
13739 "qualified_identifier"
13740 | "scoped_identifier"
13741 | "template_method"
13742 | "template_function"
13743 | "template_type" => node.child_by_field_name("name"),
13744 "field_expression" => node.child_by_field_name("field"),
13745 _ => None,
13746 };
13747 let Some(next) = next else {
13748 return node;
13749 };
13750 node = next;
13751 }
13752}
13753
13754#[derive(Clone, Copy)]
13755pub struct RecoveredRelationalTemplateMemberCall<'tree> {
13756 pub receiver: Node<'tree>,
13757 pub member: Node<'tree>,
13758 pub arity: usize,
13759}
13760
13761pub fn recovered_relational_template_member_call(
13769 field: Node<'_>,
13770) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
13771 if field.kind() != "field_expression" {
13772 return None;
13773 }
13774 let receiver = field
13775 .child_by_field_name("argument")
13776 .or_else(|| field.child_by_field_name("object"))?;
13777 let member = field.child_by_field_name("field")?;
13778 let less = field.parent()?;
13779 if less.kind() != "binary_expression"
13780 || less.child_by_field_name("left") != Some(field)
13781 || less
13782 .child_by_field_name("operator")
13783 .is_none_or(|operator| operator.kind() != "<")
13784 || less.child_by_field_name("right").is_none()
13785 {
13786 return None;
13787 }
13788 let greater = less.parent()?;
13789 if greater.kind() != "binary_expression"
13790 || greater.child_by_field_name("left") != Some(less)
13791 || greater
13792 .child_by_field_name("operator")
13793 .is_none_or(|operator| operator.kind() != ">")
13794 {
13795 return None;
13796 }
13797 let arguments = greater.child_by_field_name("right")?;
13798 if arguments.kind() != "parenthesized_expression" {
13799 return None;
13800 }
13801 let arity = parenthesized_call_argument_arity(arguments)?;
13802 Some(RecoveredRelationalTemplateMemberCall {
13803 receiver,
13804 member,
13805 arity,
13806 })
13807}
13808
13809fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
13810 let expression = arguments.named_child(0)?;
13811 if expression.kind() != "comma_expression" {
13812 return Some(1);
13813 }
13814 let mut arity = 0usize;
13815 let mut stack = vec![expression];
13816 while let Some(node) = stack.pop() {
13817 if node.kind() == "comma_expression" {
13818 stack.push(node.child_by_field_name("right")?);
13819 stack.push(node.child_by_field_name("left")?);
13820 } else {
13821 arity += 1;
13822 }
13823 }
13824 Some(arity)
13825}
13826
13827pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
13830 while let Some(parent) = node.parent() {
13831 match parent.kind() {
13832 "call_expression" => {
13833 return parent
13834 .child_by_field_name("function")
13835 .or_else(|| parent.named_child(0))
13836 == Some(node);
13837 }
13838 "qualified_identifier"
13839 | "scoped_identifier"
13840 | "template_function"
13841 | "template_type"
13842 | "field_expression" => node = parent,
13843 _ => return false,
13844 }
13845 }
13846 false
13847}
13848
13849pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
13850 if is_call_callee_node(node) {
13851 function_terminal_node(node)
13852 } else {
13853 node
13854 }
13855}
13856
13857pub fn normalize_type_text(value: &str) -> String {
13858 strip_tag_type_prefix(
13859 normalize_cpp_whitespace(value)
13860 .trim_start_matches("const ")
13861 .trim_end_matches('*')
13862 .trim_end_matches('&')
13863 .trim(),
13864 )
13865 .to_string()
13866}
13867
13868fn strip_tag_type_prefix(value: &str) -> &str {
13869 let value = value.trim_start_matches("const ");
13870 value
13871 .strip_prefix("struct ")
13872 .or_else(|| value.strip_prefix("class "))
13873 .or_else(|| value.strip_prefix("enum "))
13874 .unwrap_or(value)
13875 .trim()
13876}
13877
13878pub fn normalize_reference_name(value: &str) -> Option<String> {
13879 let normalized = normalize_cpp_reference_text(value);
13880 (!normalized.is_empty()).then_some(normalized)
13881}
13882
13883pub fn normalize_cpp_reference_text(value: &str) -> String {
13884 let mut text = normalize_cpp_whitespace(value)
13885 .trim_start_matches("new ")
13886 .trim()
13887 .to_string();
13888 if let Some(index) = text.find(['(', '{']) {
13889 text.truncate(index);
13890 }
13891 if let Some(index) = text.find('<') {
13892 text.truncate(index);
13893 }
13894 let normalized = text
13895 .trim()
13896 .trim_start_matches("const ")
13897 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
13898 .trim_matches(':')
13899 .trim();
13900 strip_tag_type_prefix(normalized).to_string()
13901}
13902
13903pub fn cpp_name_for(unit: &CodeUnit) -> String {
13904 let short = unit.short_name().replace(['.', '$'], "::");
13905 if unit.package_name().is_empty() {
13906 short
13907 } else {
13908 format!("{}::{}", unit.package_name(), short)
13909 }
13910}
13911
13912fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
13916 let fq = unit.fq();
13917 if fq.is_empty() {
13918 return None;
13919 }
13920 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
13921 Some(
13922 fq.segments()
13923 .iter()
13924 .map(|&segment| interner.resolve(segment).0)
13925 .collect::<Vec<_>>()
13926 .join("::"),
13927 )
13928}
13929
13930fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
13931 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
13932 || unit.fq().is_empty() && cpp_name_for(unit) == expected
13933}
13934
13935pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
13944 let fq = unit.fq();
13945 if !fq.is_empty() {
13946 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
13947 let scope = fq
13948 .segments()
13949 .iter()
13950 .filter_map(|&segment| {
13951 let (text, kind) = interner.resolve(segment);
13952 matches!(
13953 kind,
13954 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
13955 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
13956 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
13957 )
13958 .then(|| text.to_string())
13959 })
13960 .collect();
13961 return scope;
13962 }
13963 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13964 brokk_bifrost_core::analyzer::Language::Cpp,
13965 &cpp_name_for(unit),
13966 )
13967}
13968
13969pub fn terminal_name(value: &str) -> &str {
13980 value
13981 .rsplit("::")
13982 .next()
13983 .unwrap_or(value)
13984 .rsplit(['.', '-', '>'])
13985 .next()
13986 .unwrap_or(value)
13987 .trim()
13988}
13989
13990pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
13991 terminal_name(&normalize_cpp_reference_text(value)) == expected
13992}
13993
13994pub fn name_matches_callable(value: &str, expected: &str) -> bool {
13995 name_matches_terminal(value, expected)
13996 || expected.starts_with("operator")
13997 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
13998}
13999
14000pub fn name_mentions(value: &str, expected: &str) -> bool {
14001 normalize_cpp_reference_text(value)
14002 .split("::")
14003 .any(|part| part == expected)
14004}
14005
14006pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
14007 let cpp_name = cpp_name_for(unit);
14008 if reference.contains("::") {
14009 return reference == cpp_name;
14010 }
14011 reference == cpp_name
14012 || terminal_name(reference) == unit.identifier()
14013 && (unit.package_name().is_empty() || reference == unit.identifier())
14014}
14015
14016pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
14017 match kind {
14018 TargetKind::Type
14019 | TargetKind::Constructor
14020 | TargetKind::Method
14021 | TargetKind::MemberField => true,
14022 TargetKind::FreeFunction => unit.is_function(),
14023 TargetKind::GlobalField => unit.is_field(),
14024 TargetKind::Macro => unit.is_macro(),
14025 }
14026}
14027
14028pub fn is_type_alias(unit: &CodeUnit) -> bool {
14029 unit.kind() == CodeUnitType::Field
14030 && unit.signature().is_some_and(|signature| {
14031 signature.starts_with("typedef ") || signature.starts_with("using ")
14032 })
14033}
14034
14035fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
14036 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
14037 let target_name = cpp_name_for(target);
14038 if normalized.contains("::") {
14039 return normalized == target_name;
14040 }
14041 if let Some(namespace) = alias.namespace.as_deref() {
14042 return namespace_prefixes(namespace)
14043 .into_iter()
14044 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
14045 }
14046 target.package_name().is_empty() && normalized == target.identifier()
14047}
14048
14049pub fn cpp_function_return_type_text(
14052 analyzer: &CppGraphSource<'_>,
14053 function: &CodeUnit,
14054) -> Option<String> {
14055 let metadata = analyzer.signature_metadata(function);
14056 if !metadata.is_empty() {
14057 let first = metadata.first()?.return_type_text()?;
14058 return metadata
14059 .iter()
14060 .all(|metadata| metadata.return_type_text() == Some(first))
14061 .then(|| first.to_string());
14062 }
14063 let signature = cpp_function_signature_text(analyzer, function)?;
14064 cpp_function_return_type_text_from_signature(&signature)
14065}
14066
14067fn cpp_function_signature_text(
14068 analyzer: &CppGraphSource<'_>,
14069 function: &CodeUnit,
14070) -> Option<String> {
14071 function
14072 .signature()
14073 .filter(|signature| signature.contains(function.identifier()))
14074 .map(str::to_string)
14075 .or_else(|| analyzer.signatures(function).first().cloned())
14076 .or_else(|| analyzer.get_source(function, false))
14077}
14078
14079fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
14080 let open = signature.find('(')?;
14081 let name_at = cpp_function_name_start(signature, open)?;
14082 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
14083 return Some(return_type);
14084 }
14085 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
14086 .split_whitespace()
14087 .filter(|token| {
14088 !matches!(
14089 *token,
14090 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
14091 )
14092 })
14093 .collect::<Vec<_>>()
14094 .join(" ");
14095 let type_text = type_text.trim();
14096 (!type_text.is_empty()).then(|| type_text.to_string())
14097}
14098
14099fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
14100 let before_parameters = &signature[..open];
14101 if let Some(operator_at) = before_parameters.rfind("operator") {
14102 let boundary = operator_at == 0
14103 || before_parameters[..operator_at]
14104 .chars()
14105 .next_back()
14106 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
14107 if boundary {
14108 return Some(operator_at);
14109 }
14110 }
14111 before_parameters
14112 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
14113 .map(|index| index + 1)
14114}
14115
14116fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
14117 let open = signature_from_name.find('(')?;
14118 let mut depth = 0i32;
14119 for (offset, ch) in signature_from_name[open..].char_indices() {
14120 match ch {
14121 '(' => depth += 1,
14122 ')' => {
14123 depth -= 1;
14124 if depth == 0 {
14125 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
14126 let arrow = rest.find("->")?;
14127 let return_type = rest[arrow + 2..].trim_start();
14128 let return_type = return_type
14129 .split(['{', ';'])
14130 .next()
14131 .unwrap_or(return_type)
14132 .trim();
14133 return (!return_type.is_empty()).then(|| return_type.to_string());
14134 }
14135 }
14136 _ => {}
14137 }
14138 }
14139 None
14140}
14141
14142fn cpp_strip_leading_template_clause(text: &str) -> &str {
14145 let trimmed = text.trim_start();
14146 let Some(rest) = trimmed.strip_prefix("template") else {
14147 return text;
14148 };
14149 let rest = rest.trim_start();
14150 if !rest.starts_with('<') {
14151 return text;
14152 }
14153 let mut depth = 0i32;
14154 for (offset, ch) in rest.char_indices() {
14155 match ch {
14156 '<' => depth += 1,
14157 '>' => {
14158 depth -= 1;
14159 if depth == 0 {
14160 return rest[offset + ch.len_utf8()..].trim_start();
14161 }
14162 }
14163 _ => {}
14164 }
14165 }
14166 text
14167}
14168
14169pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
14170 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
14180 namespace
14181 .strip_prefix("anonymous_namespace::")
14182 .unwrap_or(namespace)
14183 .to_string()
14184 })
14185}
14186
14187fn namespace_prefixes(namespace: &str) -> Vec<String> {
14188 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
14194 brokk_bifrost_core::analyzer::Language::Cpp,
14195 namespace,
14196 );
14197 let mut prefixes = Vec::new();
14198 while !parts.is_empty() {
14199 prefixes.push(parts.join("::"));
14200 parts.pop();
14201 }
14202 prefixes
14203}
14204
14205fn nearest_namespace_candidates(
14206 candidates: Vec<CodeUnit>,
14207 normalized: &str,
14208 lexical_namespace: Option<&str>,
14209) -> Vec<CodeUnit> {
14210 if normalized.contains("::") {
14211 return candidates;
14212 }
14213 if let Some(namespace) = lexical_namespace {
14214 for prefix in namespace_prefixes(namespace) {
14215 let scoped = candidates
14216 .iter()
14217 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
14218 .cloned()
14219 .collect::<Vec<_>>();
14220 if !scoped.is_empty() {
14221 return scoped;
14222 }
14223 }
14224 }
14225 candidates
14226 .into_iter()
14227 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
14228 .collect()
14229}
14230
14231pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
14232 let mut namespaces = Vec::new();
14233 let mut current = node.parent();
14234 while let Some(parent) = current {
14235 if parent.kind() == "namespace_definition"
14236 && let Some(name) = parent.child_by_field_name("name")
14237 {
14238 let namespace = normalize_cpp_reference_text(node_text(name, source));
14239 if !namespace.is_empty() {
14240 namespaces.push(namespace);
14241 }
14242 }
14243 current = parent.parent();
14244 }
14245 if namespaces.is_empty() {
14246 None
14247 } else {
14248 namespaces.reverse();
14249 Some(namespaces.join("::"))
14250 }
14251}
14252
14253pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
14257 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
14258}
14259
14260fn type_owner_resolution(
14261 analyzer: &CppGraphSource<'_>,
14262 code_unit: &CodeUnit,
14263) -> Option<ResolvedTypeOwner> {
14264 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
14265}
14266
14267fn target_type_owner_resolution(
14268 analyzer: &CppGraphSource<'_>,
14269 code_unit: &CodeUnit,
14270) -> Option<ResolvedTypeOwner> {
14271 match type_owner_resolution(analyzer, code_unit) {
14272 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
14273 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
14274 }
14275}
14276
14277fn target_forward_owner_resolution(
14289 analyzer: &CppGraphSource<'_>,
14290 code_unit: &CodeUnit,
14291) -> Option<ResolvedTypeOwner> {
14292 if !code_unit.is_function() {
14293 return None;
14294 }
14295 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
14301 let cpp = analyzer.cpp?;
14302 let mut visible_files = HashSet::default();
14303 collect_include_closure(
14304 analyzer,
14305 cpp.include_target_index(),
14306 code_unit.source(),
14307 &mut visible_files,
14308 None,
14309 );
14310 let candidates = analyzer.workspace_definitions().exact(&owner_name);
14311 let visible_candidates = candidates
14312 .iter()
14313 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
14314 .cloned()
14315 .collect::<Vec<_>>();
14316 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
14317 DirectOwnerResolution::UniqueFull(unit) => {
14318 return Some(ResolvedTypeOwner {
14319 unit,
14320 is_forward_declaration: false,
14321 });
14322 }
14323 DirectOwnerResolution::ForwardsOnly(forwards) => {
14324 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
14325 unit: forwards.into_iter().next().unwrap(),
14326 is_forward_declaration: true,
14327 });
14328 }
14329 DirectOwnerResolution::Ambiguous => return None,
14330 DirectOwnerResolution::None => {}
14331 }
14332
14333 let candidates = candidates
14334 .into_iter()
14335 .filter(|candidate| candidate.is_class())
14336 .collect::<Vec<_>>();
14337 let (unit, is_forward_declaration) =
14338 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
14339 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
14340 DirectOwnerResolution::ForwardsOnly(forwards) => {
14341 (unique_logical_forward_owner(forwards)?, true)
14342 }
14343 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
14344 };
14345 Some(ResolvedTypeOwner {
14346 unit,
14347 is_forward_declaration,
14348 })
14349}
14350
14351pub fn precise_parent_of(
14352 analyzer: &CppGraphSource<'_>,
14353 visibility: &VisibilityIndex<'_>,
14354 code_unit: &CodeUnit,
14355) -> Option<CodeUnit> {
14356 visibility.cached_precise_parent_of(analyzer, code_unit)
14357}
14358
14359fn precise_parent_resolution(
14360 analyzer: &CppGraphSource<'_>,
14361 code_unit: &CodeUnit,
14362) -> Option<ResolvedTypeOwner> {
14363 #[cfg(any(test, feature = "test-support"))]
14364 if let Some(cpp) = analyzer.cpp {
14365 cpp.record_cpp_parent_resolution_for_test();
14366 }
14367 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
14368 return Some(ResolvedTypeOwner {
14369 unit,
14370 is_forward_declaration: false,
14371 });
14372 }
14373 let fallback = analyzer.parent_of(code_unit);
14374 if !code_unit.owner_is_type_scope() {
14375 return fallback.map(|unit| ResolvedTypeOwner {
14376 unit,
14377 is_forward_declaration: false,
14378 });
14379 }
14380 let owner_fq = code_unit
14381 .fq()
14382 .parent()
14383 .expect("a unit with an owner identifier has a structured parent");
14384 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
14385 match same_source_owner(analyzer, code_unit, &owner_candidates) {
14386 DirectOwnerResolution::UniqueFull(owner) => {
14387 return Some(ResolvedTypeOwner {
14388 unit: owner,
14389 is_forward_declaration: false,
14390 });
14391 }
14392 DirectOwnerResolution::Ambiguous => return None,
14393 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
14394 }
14395 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
14396 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
14397 unit: owner,
14398 is_forward_declaration: false,
14399 }),
14400 DirectOwnerResolution::Ambiguous => None,
14401 DirectOwnerResolution::ForwardsOnly(forwards) => {
14402 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
14403 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
14404 unit: owner,
14405 is_forward_declaration: false,
14406 }),
14407 FullOwnerResolution::None => {
14408 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
14409 unit,
14410 is_forward_declaration: true,
14411 })
14412 }
14413 FullOwnerResolution::Ambiguous => None,
14414 }
14415 }
14416 DirectOwnerResolution::None => {
14417 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
14418 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
14419 unit: owner,
14420 is_forward_declaration: false,
14421 }),
14422 FullOwnerResolution::Ambiguous => None,
14423 FullOwnerResolution::None => fallback
14424 .filter(|parent| {
14425 parent.source() == code_unit.source()
14426 && parent.fq() == &owner_fq
14427 && (!parent.is_class()
14428 || cpp_class_declaration_strength(analyzer, parent)
14429 == CppClassDeclarationStrength::Full)
14430 })
14431 .map(|unit| ResolvedTypeOwner {
14432 unit,
14433 is_forward_declaration: false,
14434 }),
14435 }
14436 }
14437 }
14438}
14439
14440fn exact_structural_type_parent(
14441 analyzer: &CppGraphSource<'_>,
14442 code_unit: &CodeUnit,
14443) -> Option<CodeUnit> {
14444 if !code_unit.is_function() && !code_unit.is_field() {
14445 return None;
14446 }
14447 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
14449 let parent = cpp.structural_parent_of(code_unit)?;
14450 (!parent.is_module()
14451 && parent.source() == code_unit.source()
14452 && parent.package_name() == code_unit.package_name()
14453 && parent.short_name() == encoded_owner)
14454 .then_some(parent)
14455}
14456
14457fn same_source_owner(
14458 analyzer: &CppGraphSource<'_>,
14459 code_unit: &CodeUnit,
14460 owner_candidates: &[CodeUnit],
14461) -> DirectOwnerResolution {
14462 let candidates = owner_candidates
14463 .iter()
14464 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
14465 .cloned()
14466 .collect::<Vec<_>>();
14467 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14468 classify_direct_owner_candidates(analyzer, candidates.into_iter())
14469}
14470
14471fn visible_full_cpp_owner(
14472 analyzer: &CppGraphSource<'_>,
14473 code_unit: &CodeUnit,
14474 owner_candidates: &[CodeUnit],
14475) -> FullOwnerResolution {
14476 let Some(cpp) = analyzer.cpp else {
14477 return FullOwnerResolution::None;
14478 };
14479 let mut visible_files = HashSet::default();
14480 collect_include_closure(
14481 analyzer,
14482 cpp.include_target_index(),
14483 code_unit.source(),
14484 &mut visible_files,
14485 None,
14486 );
14487 let candidates = owner_candidates
14488 .iter()
14489 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
14490 .cloned()
14491 .collect::<Vec<_>>();
14492 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14493 let mut full_definition = None;
14494 for candidate in candidates {
14495 match cpp_class_declaration_strength(analyzer, &candidate) {
14496 CppClassDeclarationStrength::Full if full_definition.is_some() => {
14497 return FullOwnerResolution::Ambiguous;
14498 }
14499 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
14500 CppClassDeclarationStrength::Forward => {}
14501 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
14502 }
14503 }
14504 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
14505}
14506
14507pub enum DirectOwnerResolution {
14508 None,
14509 ForwardsOnly(Vec<CodeUnit>),
14510 UniqueFull(CodeUnit),
14511 Ambiguous,
14512}
14513
14514enum FullOwnerResolution {
14515 None,
14516 Unique(CodeUnit),
14517 Ambiguous,
14518}
14519
14520#[derive(Clone, Copy, PartialEq, Eq)]
14521pub enum CppClassDeclarationStrength {
14522 Full,
14523 Forward,
14524 Unknown,
14525}
14526
14527fn directly_included_owner(
14528 analyzer: &CppGraphSource<'_>,
14529 code_unit: &CodeUnit,
14530 owner_candidates: &[CodeUnit],
14531) -> DirectOwnerResolution {
14532 let Some(cpp) = analyzer.cpp else {
14533 return DirectOwnerResolution::None;
14534 };
14535 let imports = analyzer.import_statements(code_unit.source());
14536 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
14537 .into_iter()
14538 .flat_map(|include| {
14539 resolve_include_targets_with_index(
14540 code_unit.source(),
14541 &include,
14542 cpp.include_target_index(),
14543 )
14544 })
14545 .collect();
14546 let candidates = owner_candidates
14547 .iter()
14548 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
14549 .cloned()
14550 .collect::<Vec<_>>();
14551 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14552 classify_direct_owner_candidates(analyzer, candidates.into_iter())
14553}
14554
14555fn prefer_member_declaring_owners(
14556 analyzer: &CppGraphSource<'_>,
14557 member: &CodeUnit,
14558 candidates: Vec<CodeUnit>,
14559) -> Vec<CodeUnit> {
14560 let matching = candidates
14561 .iter()
14562 .filter(|owner| owner_declares_member(analyzer, owner, member))
14563 .cloned()
14564 .collect::<Vec<_>>();
14565 if matching.is_empty() {
14566 candidates
14567 } else {
14568 matching
14569 }
14570}
14571
14572fn owner_declares_member(
14573 analyzer: &CppGraphSource<'_>,
14574 owner: &CodeUnit,
14575 member: &CodeUnit,
14576) -> bool {
14577 analyzer.direct_children(owner).into_iter().any(|child| {
14578 child.kind() == member.kind()
14579 && child.identifier() == member.identifier()
14580 && child.signature() == member.signature()
14581 })
14582}
14583
14584fn classify_direct_owner_candidates(
14585 analyzer: &CppGraphSource<'_>,
14586 candidates: impl Iterator<Item = CodeUnit>,
14587) -> DirectOwnerResolution {
14588 collapse_owner_candidates(candidates.map(|candidate| {
14589 let strength = cpp_class_declaration_strength(analyzer, &candidate);
14590 (candidate, strength)
14591 }))
14592}
14593
14594pub fn collapse_owner_candidates(
14595 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
14596) -> DirectOwnerResolution {
14597 let mut full_definition = None;
14598 let mut forwards = Vec::new();
14599 for (candidate, strength) in candidates {
14600 match strength {
14601 CppClassDeclarationStrength::Full if full_definition.is_some() => {
14602 return DirectOwnerResolution::Ambiguous;
14603 }
14604 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
14605 CppClassDeclarationStrength::Forward => forwards.push(candidate),
14606 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
14607 }
14608 }
14609 if let Some(owner) = full_definition {
14610 DirectOwnerResolution::UniqueFull(owner)
14611 } else if !forwards.is_empty() {
14612 DirectOwnerResolution::ForwardsOnly(forwards)
14613 } else {
14614 DirectOwnerResolution::None
14615 }
14616}
14617
14618#[cfg(any(test, feature = "test-support"))]
14619pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
14620 unique_logical_forward_owner(forwards)
14621}
14622
14623fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
14624 let first = forwards.pop()?;
14625 forwards
14626 .iter()
14627 .all(|forward| same_logical_symbol(forward, &first))
14628 .then_some(first)
14629}
14630
14631pub fn cpp_class_declaration_strength(
14632 analyzer: &CppGraphSource<'_>,
14633 candidate: &CodeUnit,
14634) -> CppClassDeclarationStrength {
14635 let Some(cpp) = analyzer.cpp else {
14643 return uncached_cpp_class_declaration_strength(analyzer, candidate);
14644 };
14645 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
14646 return strength;
14647 }
14648 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
14649 cpp.cache_class_declaration_strength(candidate, strength);
14650 strength
14651}
14652
14653fn uncached_cpp_class_declaration_strength(
14654 analyzer: &CppGraphSource<'_>,
14655 candidate: &CodeUnit,
14656) -> CppClassDeclarationStrength {
14657 if let Some(cpp) = analyzer.cpp
14658 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
14659 {
14660 return cpp_class_declaration_strength_in_tree(
14661 analyzer,
14662 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
14663 candidate,
14664 prepared.source(),
14665 prepared.tree().root_node(),
14666 );
14667 }
14668 let Some(source) = analyzer.indexed_source(candidate.source()) else {
14669 return CppClassDeclarationStrength::Unknown;
14670 };
14671 #[cfg(any(test, feature = "test-support"))]
14672 if let Some(cpp) = analyzer.cpp {
14673 cpp.record_cpp_class_strength_parse_for_test();
14674 }
14675 let mut parser = Parser::new();
14676 if parser
14677 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14678 .is_err()
14679 {
14680 return CppClassDeclarationStrength::Unknown;
14681 }
14682 let Some(tree) = parser.parse(&source, None) else {
14683 return CppClassDeclarationStrength::Unknown;
14684 };
14685 let recovered_export_classes =
14688 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
14689 cpp_class_declaration_strength_in_tree(
14690 analyzer,
14691 &recovered_export_classes,
14692 candidate,
14693 &source,
14694 tree.root_node(),
14695 )
14696}
14697
14698fn cpp_class_declaration_strength_in_tree(
14699 analyzer: &CppGraphSource<'_>,
14700 recovered_export_classes: &CppRecoveredExportClassIndex,
14701 candidate: &CodeUnit,
14702 source: &str,
14703 root: Node<'_>,
14704) -> CppClassDeclarationStrength {
14705 let ranges = analyzer.ranges(candidate);
14706 let mut saw_forward = false;
14707 for range in ranges {
14708 match recovered_class_body_at(
14711 recovered_export_classes,
14712 root,
14713 source,
14714 candidate.identifier(),
14715 &range,
14716 ) {
14717 Some(true) => return CppClassDeclarationStrength::Full,
14718 Some(false) => {
14719 saw_forward = true;
14720 continue;
14721 }
14722 None => {}
14723 }
14724 let covers_range_start = |node: &Node<'_>| {
14731 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
14732 };
14733 let mut stack = Vec::new();
14734 if covers_range_start(&root) {
14735 stack.push(root);
14736 }
14737 while let Some(node) = stack.pop() {
14738 if node.start_byte() == range.start_byte
14739 && node.end_byte() == range.end_byte
14740 && matches!(
14741 node.kind(),
14742 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14743 )
14744 {
14745 if cpp_class_node_has_body(node) {
14746 return CppClassDeclarationStrength::Full;
14747 }
14748 saw_forward = true;
14749 }
14750 let mut cursor = node.walk();
14751 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
14752 }
14753 }
14754 if saw_forward {
14755 CppClassDeclarationStrength::Forward
14756 } else {
14757 CppClassDeclarationStrength::Unknown
14758 }
14759}
14760
14761fn cpp_class_node_has_body(node: Node<'_>) -> bool {
14762 node.child_by_field_name("body").is_some() || {
14763 let mut cursor = node.walk();
14764 node.named_children(&mut cursor).any(|child| {
14765 matches!(
14766 child.kind(),
14767 "declaration_list" | "field_declaration_list" | "enumerator_list"
14768 )
14769 })
14770 }
14771}
14772
14773pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
14774 if !code_unit.owner_is_type_scope() {
14775 return None;
14776 }
14777 let owner_fq = code_unit.fq().parent()?;
14778 ctx.analyzer
14779 .workspace_definitions()
14780 .exact(&owner_fq)
14781 .into_iter()
14782 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
14783}
14784
14785pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14786 left.kind() == right.kind()
14787 && left.fq_name() == right.fq_name()
14788 && left.signature() == right.signature()
14789 && left.source() == right.source()
14790}
14791
14792pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14793 same_symbol(left, right) || same_logical_symbol(left, right)
14794}
14795
14796pub fn same_visible_global_field_symbol(
14797 analyzer: &CppGraphSource<'_>,
14798 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
14799 left: &CodeUnit,
14800 right: &CodeUnit,
14801) -> bool {
14802 if same_symbol(left, right) {
14803 return true;
14804 }
14805 if !same_logical_symbol(left, right) {
14806 return false;
14807 }
14808 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
14809 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
14810 {
14811 left.source() == right.source()
14812 } else {
14813 true
14814 }
14815}
14816
14817fn cpp_global_field_has_internal_linkage_cached(
14818 analyzer: &CppGraphSource<'_>,
14819 cache: &mut HashMap<CodeUnit, bool>,
14820 candidate: &CodeUnit,
14821) -> bool {
14822 if let Some(internal) = cache.get(candidate) {
14823 return *internal;
14824 }
14825 #[cfg(any(test, feature = "test-support"))]
14826 note_cpp_global_field_internal_linkage_classification_for_test();
14827 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
14828 cache.insert(candidate.clone(), internal);
14829 internal
14830}
14831
14832#[cfg(any(test, feature = "test-support"))]
14833thread_local! {
14834 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
14835}
14836
14837#[cfg(any(test, feature = "test-support"))]
14838fn note_cpp_global_field_internal_linkage_classification_for_test() {
14839 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
14840 count.set(count.get() + 1);
14841 });
14842}
14843
14844#[cfg(any(test, feature = "test-support"))]
14845pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
14846 body: impl FnOnce() -> T,
14847) -> (T, usize) {
14848 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
14849 count.set(0);
14850 let result = body();
14851 let observed = count.get();
14852 count.set(0);
14853 (result, observed)
14854 })
14855}
14856
14857pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14858 left.kind() == right.kind()
14859 && left.fq_name() == right.fq_name()
14860 && left.signature() == right.signature()
14861}
14862
14863pub fn cpp_global_field_has_internal_linkage(
14864 analyzer: &CppGraphSource<'_>,
14865 candidate: &CodeUnit,
14866) -> bool {
14867 if !candidate.is_field() || candidate.short_name().contains('.') {
14868 return false;
14869 }
14870 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
14871 return false;
14872 };
14873 match local_linkage {
14874 CppFieldLinkage::Internal => true,
14875 CppFieldLinkage::External => false,
14876 CppFieldLinkage::InternalUnlessExternalPeer => {
14877 !cpp_global_field_linkage_peers(analyzer, candidate)
14878 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
14879 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
14880 }
14881 }
14882}
14883
14884fn cpp_global_field_linkage_peers<'a>(
14885 analyzer: &CppGraphSource<'a>,
14886 candidate: &'a CodeUnit,
14887) -> impl Iterator<Item = CodeUnit> + 'a {
14888 let name = candidate.fq().clone();
14889 analyzer
14890 .workspace_definitions()
14891 .exact(&name)
14892 .into_iter()
14893 .filter(move |peer| {
14894 if peer == candidate {
14895 return false;
14896 }
14897 #[cfg(any(test, feature = "test-support"))]
14898 note_cpp_global_field_linkage_peer_inspection_for_test();
14899 same_logical_symbol(peer, candidate)
14900 })
14901}
14902
14903#[cfg(any(test, feature = "test-support"))]
14904thread_local! {
14905 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
14906}
14907
14908#[cfg(any(test, feature = "test-support"))]
14909fn note_cpp_global_field_linkage_peer_inspection_for_test() {
14910 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
14911 count.set(count.get() + 1);
14912 });
14913}
14914
14915#[cfg(any(test, feature = "test-support"))]
14916pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
14917 body: impl FnOnce() -> T,
14918) -> (T, usize) {
14919 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
14920 count.set(0);
14921 let result = body();
14922 let observed = count.get();
14923 count.set(0);
14924 (result, observed)
14925 })
14926}
14927
14928fn cpp_global_field_declaration_linkage(
14929 analyzer: &CppGraphSource<'_>,
14930 candidate: &CodeUnit,
14931) -> Option<CppFieldLinkage> {
14932 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
14933 return Some(linkage);
14934 }
14935 let cpp = analyzer.cpp?;
14936 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
14937 return cpp_global_field_declaration_linkage_in_tree(
14938 analyzer,
14939 candidate,
14940 prepared.source(),
14941 prepared.tree().root_node(),
14942 );
14943 }
14944 let source = analyzer.indexed_source(candidate.source())?;
14945 let mut parser = Parser::new();
14946 if parser
14947 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14948 .is_err()
14949 {
14950 return None;
14951 }
14952 let tree = parser.parse(&source, None)?;
14953 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
14954}
14955
14956fn cpp_global_field_declaration_linkage_in_tree(
14957 analyzer: &CppGraphSource<'_>,
14958 candidate: &CodeUnit,
14959 source: &str,
14960 root: Node<'_>,
14961) -> Option<CppFieldLinkage> {
14962 analyzer.ranges(candidate).iter().find_map(|range| {
14963 node_for_exact_range(root, range)
14964 .and_then(enclosing_cpp_field_declaration)
14965 .map(|declaration| {
14966 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
14968 })
14969 })
14970}
14971
14972fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
14973 loop {
14974 if matches!(node.kind(), "declaration" | "field_declaration") {
14975 return Some(node);
14976 }
14977 node = node.parent()?;
14978 }
14979}
14980
14981#[cfg(test)]
14982mod tests {
14983 use super::*;
14984
14985 #[test]
14986 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
14987 let source = "int size(void) { return sizeof(((Payload))); }\n";
14988 let mut parser = Parser::new();
14989 parser
14990 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14991 .expect("C++ grammar");
14992 let tree = parser.parse(source, None).expect("fixture tree");
14993 let start = source.find("Payload").expect("sizeof operand");
14994 let node = tree
14995 .root_node()
14996 .named_descendant_for_byte_range(start, start + "Payload".len())
14997 .expect("focused operand");
14998 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
14999 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
15000
15001 assert_eq!(node.kind(), "identifier");
15002 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
15003 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
15004 }
15005
15006 #[test]
15007 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
15008 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
15009 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
15010 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
15011 assert!(indexed_namespace_path_is_recoverable(
15012 &["cache".to_string()],
15013 &indexed,
15014 1,
15015 ));
15016 }
15017
15018 #[test]
15019 fn sort_lookup_units_totally_orders_every_identity_field() {
15020 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
15021 let base = CodeUnit::with_signature(
15022 file.clone(),
15023 CodeUnitType::Function,
15024 "scope",
15025 "value",
15026 Some("()".to_string()),
15027 false,
15028 );
15029 let different_kind = CodeUnit::with_signature(
15030 file.clone(),
15031 CodeUnitType::Field,
15032 "scope",
15033 "value",
15034 Some("()".to_string()),
15035 false,
15036 );
15037 let synthetic = base.with_synthetic(true);
15038
15039 let interner = segment_interner();
15040 let mut member_fq = FqName::new();
15041 member_fq.push(interner.intern("scope", SegmentKind::Package));
15042 member_fq.push(interner.intern("value", SegmentKind::Member));
15043 let different_package_boundary = CodeUnit::from_fq(
15044 file.clone(),
15045 CodeUnitType::Function,
15046 member_fq,
15047 0,
15048 Some("()".to_string()),
15049 false,
15050 );
15051
15052 let mut unknown_fq = FqName::new();
15053 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
15054 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
15055 let different_segment_kind = CodeUnit::from_fq(
15056 file,
15057 CodeUnitType::Function,
15058 unknown_fq,
15059 1,
15060 Some("()".to_string()),
15061 false,
15062 );
15063
15064 let input = vec![
15065 base,
15066 different_kind,
15067 synthetic,
15068 different_package_boundary,
15069 different_segment_kind,
15070 ];
15071 let mut expected = input.clone();
15072 sort_lookup_units(&mut expected);
15073 assert!(expected.windows(2).all(|pair| {
15074 let mut ordered = pair.to_vec();
15075 sort_lookup_units(&mut ordered);
15076 ordered == pair && pair[0] != pair[1]
15077 }));
15078
15079 let mut reversed = input.clone();
15080 reversed.reverse();
15081 sort_lookup_units(&mut reversed);
15082 assert_eq!(reversed, expected);
15083
15084 let mut rotated = input;
15085 rotated.rotate_left(2);
15086 sort_lookup_units(&mut rotated);
15087 assert_eq!(rotated, expected);
15088 }
15089
15090 #[test]
15091 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
15092 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";
15093 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
15094 let parse = |source: &str| {
15095 let mut parser = Parser::new();
15096 parser
15097 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15098 .expect("C++ grammar");
15099 parser.parse(source, None).expect("fixture tree")
15100 };
15101
15102 let tree = parse(damaged);
15103 let root = tree.root_node();
15104 let target = damaged.find("target").expect("target byte");
15105 let declaration = root
15106 .descendant_for_byte_range(target, target + "target".len())
15107 .and_then(|mut node| {
15108 loop {
15109 if node.kind() == "declaration" {
15110 break Some(node);
15111 }
15112 node = node.parent()?;
15113 }
15114 })
15115 .expect("declaration after the displaced terminator");
15116 let conditional = declaration
15117 .parent()
15118 .filter(|node| node.kind() == "preproc_ifdef")
15119 .expect("damaged inner conditional");
15120 let outer = conditional
15121 .parent()
15122 .filter(|node| node.kind() == "preproc_ifdef")
15123 .expect("ordinary outer include guard");
15124 let terminator = cpp_displaced_preprocessor_terminator(conditional)
15125 .expect("structured displaced #endif");
15126 assert_eq!(node_text(terminator, damaged), "#endif");
15127 assert!(terminator.end_byte() <= declaration.start_byte());
15128 assert!(!preprocessor_conditional_contains_descendant(
15129 conditional,
15130 declaration
15131 ));
15132 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
15133 assert!(preprocessor_conditional_contains_descendant(
15134 outer,
15135 declaration
15136 ));
15137
15138 let tree = parse(guarded);
15139 let conditional = tree
15140 .root_node()
15141 .named_child(0)
15142 .filter(|node| node.kind() == "preproc_ifdef")
15143 .expect("ordinary conditional");
15144 let declaration = conditional
15145 .named_children(&mut conditional.walk())
15146 .find(|node| node.kind() == "declaration")
15147 .expect("guarded declaration");
15148 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
15149 assert!(preprocessor_conditional_contains_descendant(
15150 conditional,
15151 declaration
15152 ));
15153
15154 let damaged_alternative = format!(
15155 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
15156 "UNUSED(value)\n".repeat(64)
15157 );
15158 let tree = parse(&damaged_alternative);
15159 let conditional = tree
15160 .root_node()
15161 .named_child(0)
15162 .filter(|node| node.kind() == "preproc_ifdef")
15163 .expect("outer conditional with an alternative");
15164 assert!(conditional.has_error());
15165 assert!(conditional.child_by_field_name("alternative").is_some());
15166 assert!(
15167 conditional
15168 .child(conditional.child_count() - 1)
15169 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
15170 );
15171 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
15172
15173 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";
15174 let tree = parse(split_declaration);
15175 let root = tree.root_node();
15176 let conditional = root
15177 .named_children(&mut root.walk())
15178 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
15179 .expect("split declaration conditional");
15180 let target = split_declaration
15181 .find("static int target")
15182 .expect("target byte");
15183 let boundary =
15184 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
15185 assert!(boundary.end_byte <= target, "{boundary:?}");
15186 assert_eq!(boundary.end_line, 9, "{boundary:?}");
15187 let target_node = root
15188 .descendant_for_byte_range(target, target + "static".len())
15189 .expect("target node");
15190 assert!(!preprocessor_conditional_contains_descendant(
15191 conditional,
15192 target_node
15193 ));
15194 }
15195
15196 #[test]
15197 fn fragmented_reference_guard_is_recovered() {
15198 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";
15199 let mut parser = Parser::new();
15200 parser
15201 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15202 .expect("C++ grammar");
15203 let tree = parser.parse(source, None).expect("fixture tree");
15204 let start = source.rfind("helper").expect("reference byte");
15205 let node = tree
15206 .root_node()
15207 .descendant_for_byte_range(start, start + "helper".len())
15208 .expect("reference node");
15209 let mut expected = HashSet::default();
15210 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
15211 vec![
15212 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
15213 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
15214 ],
15215 )));
15216 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
15217 }
15218
15219 #[test]
15220 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
15221 let source = r#"#ifdef _WIN32
15222#if defined(__cplusplus)
15223extern "C"
15224#endif
15225int platform_api(void);
15226#endif
15227
15228#ifdef _WIN32
15229static int entropy_target(void) { return 0; }
15230#else
15231#ifdef HAVE_COMMON_RANDOM
15232static int other_target(void) { return 0; }
15233#elif defined(HAVE_GETENTROPY)
15234static int entropy_target(void) { return 1; }
15235static int use_entropy(void) { return entropy_target(); }
15236#endif
15237#endif
15238"#;
15239 let mut parser = Parser::new();
15240 parser
15241 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15242 .expect("C++ grammar");
15243 let tree = parser.parse(source, None).expect("fixture tree");
15244 let start = source.rfind("entropy_target()").expect("reference");
15245 let node = tree
15246 .root_node()
15247 .descendant_for_byte_range(start, start + "entropy_target".len())
15248 .expect("reference node");
15249 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
15250 assert!(
15251 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
15252 "{guards:#?}"
15253 );
15254 assert!(
15255 guards.contains(&PreprocessorGuard::Undefined(
15256 "HAVE_COMMON_RANDOM".to_string()
15257 )),
15258 "{guards:#?}"
15259 );
15260 assert!(
15261 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
15262 "{guards:#?}"
15263 );
15264 assert!(
15265 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
15266 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
15267 );
15268 }
15269
15270 #[test]
15271 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
15272 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";
15273 let mut parser = Parser::new();
15274 parser
15275 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15276 .expect("C++ grammar");
15277 let tree = parser.parse(source, None).expect("fixture tree");
15278 let root = tree.root_node();
15279 let node_at = |text: &str, start: usize| {
15280 root.descendant_for_byte_range(start, start + text.len())
15281 .expect("token node")
15282 };
15283
15284 let key_start = source.find("case KEY").expect("case label") + "case ".len();
15285 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
15286 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
15287 assert!(!is_ordinary_macro_reference_node(node_at(
15288 "ENABLE_KEYS",
15289 guard_start,
15290 )));
15291 }
15292
15293 #[test]
15294 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
15295 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";
15296 let mut parser = Parser::new();
15297 parser
15298 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15299 .expect("C++ grammar");
15300 let tree = parser.parse(source, None).expect("fixture tree");
15301 let root = tree.root_node();
15302 let definition_start = source.find("target(void)").expect("definition");
15303 let reference_start = source.rfind("target()").expect("reference");
15304 let definition = root
15305 .descendant_for_byte_range(definition_start, definition_start + "target".len())
15306 .expect("definition node");
15307 let reference = root
15308 .descendant_for_byte_range(reference_start, reference_start + "target".len())
15309 .expect("reference node");
15310 let required =
15311 preprocessor_guard_environment(definition, source).expect("definition guard");
15312 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
15313 assert!(guard_requirements_hold_at_reference(
15314 &required,
15315 Some(&active)
15316 ));
15317 }
15318
15319 #[test]
15320 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
15321 let source = "g_autoptr(FuChunkArray) self = make_array();";
15322 let mut parser = Parser::new();
15323 parser
15324 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15325 .expect("C++ grammar");
15326 let tree = parser.parse(source, None).expect("fixture tree");
15327 let statement = tree.root_node().named_child(0).expect("statement");
15328 let binding =
15329 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
15330 assert_eq!(binding.name, "self");
15331 assert_eq!(binding.type_name, "FuChunkArray");
15332 assert_eq!(binding.pointer_depth, 1);
15333
15334 let near_miss = "holder(FuChunkArray) self = make_array();";
15335 let tree = parser.parse(near_miss, None).expect("near-miss tree");
15336 let statement = tree.root_node().named_child(0).expect("statement");
15337 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
15338 }
15339
15340 #[test]
15341 fn boolean_guard_normalization_proves_equivalence_and_implication() {
15342 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
15343 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
15344 let negated_windows_branch =
15345 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
15346 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
15347 assert_eq!(negated_windows_branch, portable);
15348
15349 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
15350 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
15351 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
15352 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
15353 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
15354 assert!(fallback_branch.implies(&fallback_declaration));
15355 assert!(
15356 BooleanGuardExpression::Truthy("FEATURE".to_string())
15357 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
15358 );
15359 assert!(
15360 BooleanGuardExpression::Undefined("FEATURE".to_string())
15361 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
15362 );
15363 assert!(
15364 !BooleanGuardExpression::Defined("FEATURE".to_string())
15365 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
15366 );
15367 assert!(!fallback_declaration.implies(&fallback_branch));
15368 }
15369
15370 #[test]
15371 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
15372 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";
15373 let mut parser = Parser::new();
15374 parser
15375 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15376 .expect("C++ grammar");
15377 let tree = parser.parse(source, None).expect("fixture tree");
15378 let root = tree.root_node();
15379 let call = |marker: &str| {
15380 let start = source.find(marker).expect("call marker");
15381 let mut node = root
15382 .descendant_for_byte_range(start, start + "helper".len())
15383 .expect("call name node");
15384 loop {
15385 if node.kind() == "call_expression" {
15386 break node;
15387 }
15388 node = node.parent().expect("call expression ancestor");
15389 }
15390 };
15391 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
15392 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
15393 let keyword_call = call("helper(NULL, template); /* bound */");
15394 let keyword_arguments = keyword_call
15395 .child_by_field_name("arguments")
15396 .expect("keyword argument list");
15397 assert_eq!(
15398 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
15399 1
15400 );
15401 assert_eq!(
15402 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
15403 0
15404 );
15405
15406 let unbound_call = call("helper(NULL, template); /* unbound */");
15407 let unbound_arguments = unbound_call
15408 .child_by_field_name("arguments")
15409 .expect("unbound argument list");
15410 assert_eq!(
15411 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
15412 0
15413 );
15414 }
15415
15416 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
15417 let mut parser = Parser::new();
15418 parser
15419 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15420 .expect("C++ grammar");
15421 let tree = parser.parse(source, None).expect("C++ fixture tree");
15422 let mut stack = vec![tree.root_node()];
15423 while let Some(node) = stack.pop() {
15424 if node.kind() == "enum_specifier" {
15425 return flattened_macro_namespace_components(node, source);
15426 }
15427 let mut cursor = node.walk();
15428 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
15429 stack.extend(children.into_iter().rev());
15430 }
15431 None
15432 }
15433
15434 #[test]
15435 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
15436 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
15437namespace detail
15438{
15439enum class value_t { null };
15440}
15441NLOHMANN_JSON_NAMESPACE_END
15442NLOHMANN_JSON_NAMESPACE_BEGIN
15443namespace next
15444{
15445struct next_type {};
15446}
15447NLOHMANN_JSON_NAMESPACE_END
15448"#;
15449 assert_eq!(
15450 first_enum_flattened_namespace(complete),
15451 Some(vec!["detail".to_string()])
15452 );
15453
15454 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
15455 assert_eq!(
15456 first_enum_flattened_namespace(&stale_end),
15457 Some(vec!["detail".to_string()]),
15458 "a stale end marker before the begin marker must not replace the intended namespace"
15459 );
15460
15461 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
15462namespace detail
15463{
15464enum class value_t { null };
15465}
15466struct next_type {};
15467"#;
15468 assert_eq!(first_enum_flattened_namespace(incomplete), None);
15469 }
15470}
15471
15472#[cfg(test)]
15488mod lookup_order_properties {
15489 use super::*;
15490 use proptest::prelude::*;
15491
15492 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
15496 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
15497 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
15501 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
15502 const KINDS: [CodeUnitType; 6] = [
15503 CodeUnitType::Class,
15504 CodeUnitType::Function,
15505 CodeUnitType::Field,
15506 CodeUnitType::Module,
15507 CodeUnitType::Macro,
15508 CodeUnitType::FileScope,
15509 ];
15510
15511 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
15514 enum ProbedOrder {
15515 Before,
15516 Tied,
15517 After,
15518 Contradictory,
15521 }
15522
15523 impl ProbedOrder {
15524 fn mirror(self) -> Self {
15525 match self {
15526 ProbedOrder::Before => ProbedOrder::After,
15527 ProbedOrder::After => ProbedOrder::Before,
15528 other => other,
15529 }
15530 }
15531
15532 fn signum(self) -> i8 {
15534 match self {
15535 ProbedOrder::Before => -1,
15536 ProbedOrder::Tied => 0,
15537 ProbedOrder::After => 1,
15538 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
15539 }
15540 }
15541 }
15542
15543 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
15551 if left == right {
15552 return ProbedOrder::Tied;
15555 }
15556 let mut forward = vec![left.clone(), right.clone()];
15557 sort_lookup_units(&mut forward);
15558 let mut backward = vec![right.clone(), left.clone()];
15559 sort_lookup_units(&mut backward);
15560 let left_first = backward[0] == *left;
15561 let right_first = forward[0] == *right;
15562 match (left_first, right_first) {
15563 (true, true) => ProbedOrder::Contradictory,
15564 (true, false) => ProbedOrder::Before,
15565 (false, true) => ProbedOrder::After,
15566 (false, false) => ProbedOrder::Tied,
15567 }
15568 }
15569
15570 fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
15573 let interner = segment_interner();
15574 unit.fq()
15575 .segments()
15576 .iter()
15577 .map(|&id| {
15578 let (text, kind) = interner.resolve(id);
15579 (kind.name(), text)
15580 })
15581 .collect()
15582 }
15583
15584 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
15585 (
15586 0..ROOT_NAMES.len(),
15587 0..REL_PATHS.len(),
15588 0..KINDS.len(),
15589 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
15590 0..3usize,
15591 0..SIGNATURES.len(),
15592 any::<bool>(),
15593 )
15594 .prop_map(
15595 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
15596 let source = ProjectFile::new(
15597 std::env::temp_dir().join(ROOT_NAMES[root]),
15598 REL_PATHS[rel_path],
15599 );
15600 let interner = segment_interner();
15601 let mut fq = FqName::new();
15602 for (atom, segment_kind) in &segments {
15603 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
15604 }
15605 let package_segment_count = package_prefix % fq.len();
15607 CodeUnit::from_fq(
15608 source,
15609 KINDS[kind],
15610 fq,
15611 package_segment_count,
15612 SIGNATURES[signature].map(str::to_string),
15613 synthetic,
15614 )
15615 },
15616 )
15617 }
15618
15619 proptest! {
15620 #![proptest_config(ProptestConfig::with_cases(256))]
15621
15622 #[test]
15625 fn lookup_order_is_reflexive_and_dual(
15626 left in code_unit_strategy(),
15627 right in code_unit_strategy(),
15628 ) {
15629 prop_assert_eq!(
15630 probe_order(&left, &left),
15631 ProbedOrder::Tied,
15632 "a unit must tie with itself: {:?}",
15633 left
15634 );
15635 let forward = probe_order(&left, &right);
15636 prop_assert_ne!(
15637 forward,
15638 ProbedOrder::Contradictory,
15639 "comparator put each of these strictly first: left={:?} right={:?}",
15640 left,
15641 right
15642 );
15643 prop_assert_eq!(
15644 probe_order(&right, &left),
15645 forward.mirror(),
15646 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
15647 left,
15648 right
15649 );
15650 }
15651
15652 #[test]
15654 fn lookup_order_is_transitive(
15655 a in code_unit_strategy(),
15656 b in code_unit_strategy(),
15657 c in code_unit_strategy(),
15658 ) {
15659 let ab = probe_order(&a, &b);
15660 let bc = probe_order(&b, &c);
15661 let ac = probe_order(&a, &c);
15662 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
15663 prop_assert_ne!(
15664 probed,
15665 ProbedOrder::Contradictory,
15666 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
15667 pair,
15668 a,
15669 b,
15670 c
15671 );
15672 }
15673 if ab.signum() <= 0 && bc.signum() <= 0 {
15674 prop_assert!(
15675 ac.signum() <= 0,
15676 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
15677 a={:?} b={:?} c={:?}",
15678 ab,
15679 bc,
15680 ac,
15681 a,
15682 b,
15683 c
15684 );
15685 }
15686 }
15687
15688 #[test]
15691 fn lookup_order_separates_distinct_identities(
15692 left in code_unit_strategy(),
15693 right in code_unit_strategy(),
15694 ) {
15695 if probe_order(&left, &right) == ProbedOrder::Tied {
15696 prop_assert_eq!(
15697 &left,
15698 &right,
15699 "distinct identities tied, so their order is whatever order they \
15700 arrived in: left_segments={:?} right_segments={:?}",
15701 fq_segments(&left),
15702 fq_segments(&right)
15703 );
15704 }
15705 }
15706
15707 #[test]
15710 fn lookup_sort_is_permutation_invariant(
15711 units in prop::collection::vec(code_unit_strategy(), 1..=8),
15712 ) {
15713 let mut sorted = units.clone();
15714 sort_lookup_units(&mut sorted);
15715 for rotation in 0..units.len() {
15716 for reversed in [false, true] {
15717 let mut permuted = units.clone();
15718 permuted.rotate_left(rotation);
15719 if reversed {
15720 permuted.reverse();
15721 }
15722 sort_lookup_units(&mut permuted);
15723 prop_assert_eq!(
15724 &permuted,
15725 &sorted,
15726 "sorting a permutation gave a different list \
15727 (rotation={}, reversed={}): input={:?}",
15728 rotation,
15729 reversed,
15730 units
15731 );
15732 }
15733 }
15734 }
15735 }
15736}