1use crate::call_match::{
2 CppArgType, cpp_signature_param_types, cpp_split_top_level_commas, normalize_cpp_type_name,
3};
4use crate::compile_context::CppCompileContext;
5#[cfg(test)]
6use crate::declarations::cpp_displaced_preprocessor_terminator;
7use crate::declarations::{
8 CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
9 cpp_callable_identity_suffix, cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection,
10 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_field_declaration_linkage,
11 cpp_function_declarator_at, cpp_template_term, node_text, normalize_cpp_whitespace,
12 recovered_class_body_at, recovered_function_like_field_declarator,
13 recovered_pyobject_head_field,
14};
15use crate::graph::CppGraphSource;
16use crate::graph::extractor::ScanCtx;
17use crate::graph::syntax::{
18 function_macro_replacement_span, normalize_macro_continuations,
19 object_macro_replacement_type_references,
20};
21use crate::graph_support::CppSource;
22use crate::imports::{
23 IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
24};
25use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
26use brokk_bifrost_core::analyzer::model::{
27 CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
28 CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
29};
30use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
31use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
32#[cfg(test)]
33use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
34use brokk_bifrost_core::analyzer::query_token::QueryToken;
35use brokk_bifrost_core::analyzer::structural::adapter_helpers::field_name_in_parent;
36use brokk_bifrost_core::analyzer::tree_walk::{
37 ParentIndex, WalkControl, children_iter, named_children_iter, node_for_exact_range,
38 push_named_children_reversed, walk_named_tree_preorder,
39};
40use brokk_bifrost_core::analyzer::usages::common::same_node;
41use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
42use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
43use brokk_bifrost_core::cancellation::CancellationToken;
44use brokk_bifrost_core::hash::{HashMap, HashSet};
45#[cfg(test)]
46use brokk_bifrost_core::text_utils::compute_line_starts;
47use std::borrow::Cow;
48#[cfg(any(test, feature = "test-support"))]
49use std::cell::Cell;
50use std::cell::OnceCell;
51use std::cmp::Ordering as CmpOrdering;
52use std::collections::BTreeSet;
53use std::hash::Hash;
54use std::sync::atomic::{AtomicUsize, Ordering};
55use std::sync::{Arc, Mutex, OnceLock, RwLock};
56use std::time::{Duration, Instant};
57use tree_sitter::{Node, Parser, Tree};
58
59#[cfg(any(test, feature = "test-support"))]
60thread_local! {
61 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
62}
63
64#[derive(Clone, Copy, PartialEq, Eq)]
65pub enum TargetKind {
66 Type,
67 Constructor,
68 FreeFunction,
69 Method,
70 GlobalField,
71 MemberField,
72 Macro,
73}
74
75pub enum LexicalTypeResolution {
76 Resolved {
77 unit: CodeUnit,
78 components: Vec<String>,
79 candidates: Vec<CodeUnit>,
80 },
81 Ambiguous,
82 Missing,
83}
84
85#[derive(Clone, Copy)]
86enum TypeCandidateResolution<'a> {
87 Canonical,
88 PreserveAlias,
89 PreserveTarget(&'a CodeUnit),
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101enum TypeCandidateFailure {
102 Ambiguous,
103 Unresolvable,
104}
105
106impl TypeCandidateFailure {
107 fn lexical_resolution(self) -> LexicalTypeResolution {
108 match self {
109 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
110 Self::Unresolvable => LexicalTypeResolution::Missing,
111 }
112 }
113}
114
115pub enum LexicalCallableValueResolution {
116 Type(CodeUnit),
117 FreeFunction(CodeUnit),
118 Ambiguous,
119 Missing,
120}
121
122pub enum UsingEnumMemberResolution {
123 Resolved { owner: CodeUnit, member: CodeUnit },
124 Ambiguous,
125 Missing,
126}
127
128pub enum NamespaceValueResolution {
129 Resolved,
130 Ambiguous,
131 Missing,
132}
133
134#[derive(Clone, Debug, PartialEq, Eq)]
135pub enum OrdinaryMacroReferenceResolution {
136 Resolved(CodeUnit),
137 Ambiguous,
138 Missing,
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub enum RecoveredCReferenceRanges {
143 Complete(Vec<Range>),
144 LimitExceeded,
145}
146
147pub fn resolve_namespace_value(
148 analyzer: &CppGraphSource<'_>,
149 visibility: &VisibilityIndex<'_>,
150 file: &ProjectFile,
151 namespace: &str,
152 name: &str,
153 before_byte: usize,
154) -> NamespaceValueResolution {
155 let mut matches = Vec::new();
156 for candidate in visibility.visible_identifier_candidates(file, name) {
157 if type_owner_of(analyzer, candidate).is_some()
158 || candidate.package_name() != namespace
159 || (candidate.source() == file
160 && !analyzer
161 .ranges(candidate)
162 .iter()
163 .any(|range| range.start_byte < before_byte))
164 || matches
165 .iter()
166 .any(|existing| same_visible_symbol(existing, candidate))
167 {
168 continue;
169 }
170 matches.push(candidate.clone());
171 if matches.len() > 1 {
172 return NamespaceValueResolution::Ambiguous;
173 }
174 }
175 matches
176 .pop()
177 .map(|_| NamespaceValueResolution::Resolved)
178 .unwrap_or(NamespaceValueResolution::Missing)
179}
180
181pub(crate) struct ScopedUsingEnumOwners {
182 scopes: Vec<Vec<CodeUnit>>,
183}
184
185pub(crate) struct SemanticUsingEnumOwners {
190 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
191 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
192}
193
194pub(crate) enum SemanticUsingEnumMemberResolution {
195 Class(UsingEnumMemberResolution),
196 Namespace(UsingEnumMemberResolution),
197 Missing,
198}
199
200impl SemanticUsingEnumOwners {
201 pub(crate) fn new() -> Self {
202 Self {
203 class_imports: HashMap::default(),
204 namespace_imports: HashMap::default(),
205 }
206 }
207
208 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
209 let imports = self.class_imports.entry(class).or_default();
210 if !imports
211 .iter()
212 .any(|existing| same_visible_symbol(existing, &enum_owner))
213 {
214 imports.push(enum_owner);
215 }
216 }
217
218 pub fn import_namespace(
219 &mut self,
220 namespace: Vec<String>,
221 declaration_byte: usize,
222 enum_owner: CodeUnit,
223 ) {
224 let imports = self.namespace_imports.entry(namespace).or_default();
225 if !imports
226 .iter()
227 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
228 {
229 imports.push((declaration_byte, enum_owner));
230 }
231 }
232
233 pub fn resolve_member(
234 &self,
235 visibility: &VisibilityIndex<'_>,
236 file: &ProjectFile,
237 class: Option<&CodeUnit>,
238 namespace: &[String],
239 before_byte: usize,
240 name: &str,
241 ) -> SemanticUsingEnumMemberResolution {
242 if let Some(class) = class
243 && let Some((_, imports)) = self
244 .class_imports
245 .iter()
246 .find(|(owner, _)| same_visible_symbol(owner, class))
247 {
248 let resolution =
249 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
250 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
251 return SemanticUsingEnumMemberResolution::Class(resolution);
252 }
253 }
254 for prefix_len in (0..=namespace.len()).rev() {
255 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
256 continue;
257 };
258 let owners = imports
259 .iter()
260 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
261 .map(|(_, owner)| owner);
262 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
263 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
264 return SemanticUsingEnumMemberResolution::Namespace(resolution);
265 }
266 }
267 SemanticUsingEnumMemberResolution::Missing
268 }
269}
270
271fn resolve_using_enum_member_for_owners<'a>(
272 visibility: &VisibilityIndex<'_>,
273 file: &ProjectFile,
274 owners: impl IntoIterator<Item = &'a CodeUnit>,
275 name: &str,
276) -> UsingEnumMemberResolution {
277 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
278 for owner in owners {
279 for member in visibility.visible_members_for_owner_name(file, owner, name) {
280 if !member.is_field()
281 || matches.iter().any(|(existing_owner, existing_member)| {
282 same_visible_symbol(existing_owner, owner)
283 && same_visible_symbol(existing_member, member)
284 })
285 {
286 continue;
287 }
288 matches.push((owner.clone(), member.clone()));
289 }
290 }
291 match matches.len() {
292 0 => UsingEnumMemberResolution::Missing,
293 1 => {
294 let (owner, member) = matches.pop().expect("one using-enum match");
295 UsingEnumMemberResolution::Resolved { owner, member }
296 }
297 _ => UsingEnumMemberResolution::Ambiguous,
298 }
299}
300
301impl ScopedUsingEnumOwners {
302 pub(crate) fn new() -> Self {
303 Self {
304 scopes: vec![Vec::new()],
305 }
306 }
307
308 pub fn enter_scope(&mut self) {
309 self.scopes.push(Vec::new());
310 }
311
312 pub fn exit_scope(&mut self) {
313 if self.scopes.len() > 1 {
314 self.scopes.pop();
315 }
316 }
317
318 pub fn import(&mut self, owner: CodeUnit) {
319 let scope = self
320 .scopes
321 .last_mut()
322 .expect("using-enum scope stack is never empty");
323 if !scope
324 .iter()
325 .any(|existing| same_visible_symbol(existing, &owner))
326 {
327 scope.push(owner);
328 }
329 }
330
331 pub fn resolve_member(
332 &self,
333 visibility: &VisibilityIndex<'_>,
334 file: &ProjectFile,
335 name: &str,
336 ) -> UsingEnumMemberResolution {
337 for scope in self.scopes.iter().rev() {
338 let resolution =
339 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
340 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
341 return resolution;
342 }
343 }
344 UsingEnumMemberResolution::Missing
345 }
346}
347
348#[derive(Clone)]
349pub struct TargetSpec {
350 pub target: CodeUnit,
351 pub kind: TargetKind,
352 pub owner: Option<CodeUnit>,
353 pub member_name: String,
354 pub callable_arity: Option<CallableArity>,
355 pub activated_callable_arities: Vec<ActivatedCallableArity>,
356 pub param_types: Option<Vec<String>>,
357 pub enum_owner_kind: EnumOwnerKind,
358 pub owner_is_forward_declaration: bool,
359 pub callable_has_definition_body: bool,
360}
361
362#[derive(Clone, Copy)]
363pub struct ActivatedCallableArity {
364 pub activation_byte: usize,
365 pub arity: CallableArity,
366}
367
368#[derive(Debug, PartialEq, Eq, Hash)]
369pub struct TypeScanKey {
370 target: LogicalSymbolKey,
371 member_name: String,
372}
373
374#[derive(Clone, Debug, PartialEq, Eq, Hash)]
375struct LogicalSymbolKey {
376 kind: CodeUnitType,
377 fq_name: String,
378 signature: Option<String>,
379}
380
381struct ResolvedTypeOwner {
382 unit: CodeUnit,
383 is_forward_declaration: bool,
384}
385
386#[derive(Clone, Copy, PartialEq, Eq)]
387pub enum EnumOwnerKind {
388 Scoped,
389 Unscoped,
390 NonEnum,
391}
392
393impl TargetSpec {
394 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
395 (self.kind == TargetKind::Type).then(|| TypeScanKey {
396 target: logical_symbol_key(&self.target),
397 member_name: self.member_name.clone(),
398 })
399 }
400
401 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
402 if target.is_class() {
403 return Some(Self::new(
404 target.clone(),
405 TargetKind::Type,
406 Some(target.clone()),
407 target.identifier().to_string(),
408 None,
409 None,
410 ));
411 }
412
413 if target.is_field() {
414 let owner = type_owner_of(analyzer, target);
420 let kind = if owner.is_some() {
421 TargetKind::MemberField
422 } else {
423 TargetKind::GlobalField
424 };
425 let enum_owner_kind = owner
426 .as_ref()
427 .map(|owner| classify_enum_owner(analyzer, owner))
428 .unwrap_or(EnumOwnerKind::NonEnum);
429 let mut spec = Self::new(
430 target.clone(),
431 kind,
432 owner,
433 target.identifier().to_string(),
434 None,
435 None,
436 );
437 spec.enum_owner_kind = enum_owner_kind;
438 return Some(spec);
439 }
440
441 if target.is_function() {
442 let owner_resolution = target_type_owner_resolution(analyzer, target);
445 let owner_is_forward_declaration = owner_resolution
446 .as_ref()
447 .is_some_and(|owner| owner.is_forward_declaration);
448 let owner = owner_resolution.map(|owner| owner.unit);
449 let kind = if owner.as_ref().is_some_and(|owner| {
450 target.identifier() == owner.identifier()
451 || analyzer
452 .cpp
453 .and_then(|cpp| cpp.template_metadata(owner))
454 .is_some_and(|metadata| metadata.primary_name == target.identifier())
455 }) {
456 TargetKind::Constructor
457 } else if owner.is_some() {
458 TargetKind::Method
459 } else {
460 TargetKind::FreeFunction
461 };
462 let mut spec = Self::new(
463 target.clone(),
464 kind,
465 owner,
466 target.identifier().to_string(),
467 Some(cpp_callable_arity(analyzer, target)),
468 cpp_callable_parameter_types(analyzer, target),
469 );
470 spec.owner_is_forward_declaration = owner_is_forward_declaration;
471 spec.callable_has_definition_body =
472 callable_target_has_definition_body(analyzer, target);
473 return Some(spec);
474 }
475
476 if target.is_macro() {
477 return Some(Self::new(
478 target.clone(),
479 TargetKind::Macro,
480 None,
481 target.identifier().to_string(),
482 None,
483 None,
484 ));
485 }
486
487 None
488 }
489
490 pub fn with_visible_callable_arities<'a>(
491 &'a self,
492 analyzer: &CppGraphSource<'_>,
493 cpp: &dyn CppSource,
494 visibility: &VisibilityIndex<'_>,
495 file: &ProjectFile,
496 prepared: &PreparedSyntaxTree,
497 ) -> Cow<'a, Self> {
498 let macro_parameter_arity =
499 visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
500 let activated_callable_arities =
501 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
502 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
503 return Cow::Borrowed(self);
504 }
505 let mut effective = self.clone();
506 if let Some(macro_parameter_arity) = macro_parameter_arity {
507 effective.callable_arity = Some(macro_parameter_arity);
508 }
509 effective.activated_callable_arities = activated_callable_arities;
510 Cow::Owned(effective)
511 }
512
513 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
514 let base = self.callable_arity?;
515 Some(
516 self.activated_callable_arities
517 .iter()
518 .filter(|candidate| candidate.activation_byte <= byte)
519 .fold(base, |arity, candidate| {
520 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
521 }),
522 )
523 }
524
525 pub fn new(
526 target: CodeUnit,
527 kind: TargetKind,
528 owner: Option<CodeUnit>,
529 member_name: String,
530 callable_arity: Option<CallableArity>,
531 param_types: Option<Vec<String>>,
532 ) -> Self {
533 Self {
534 target,
535 kind,
536 owner,
537 member_name,
538 callable_arity,
539 activated_callable_arities: Vec::new(),
540 param_types,
541 enum_owner_kind: EnumOwnerKind::NonEnum,
542 owner_is_forward_declaration: false,
543 callable_has_definition_body: false,
544 }
545 }
546}
547
548fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
549 let Some(cpp) = analyzer.cpp else {
550 return false;
551 };
552 let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
553 return false;
554 };
555 analyzer.ranges(target).into_iter().any(|range| {
556 let end = range
557 .start_byte
558 .saturating_add(1)
559 .min(prepared.source().len());
560 let mut current = prepared
561 .tree()
562 .root_node()
563 .descendant_for_byte_range(range.start_byte, end);
564 while let Some(node) = current {
565 match node.kind() {
566 "function_definition" => return true,
567 "declaration" => return false,
568 _ => current = node.parent(),
569 }
570 }
571 false
572 })
573}
574
575fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
576 LogicalSymbolKey {
577 kind: unit.kind(),
578 fq_name: unit.fq_name(),
579 signature: unit.signature().map(str::to_string),
580 }
581}
582
583fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
584 let classify = |source: &str| {
585 let source = source.trim_start();
586 if source.starts_with("enum class ") || source.starts_with("enum struct ") {
587 Some(EnumOwnerKind::Scoped)
588 } else if source.starts_with("enum ") {
589 Some(EnumOwnerKind::Unscoped)
590 } else {
591 None
592 }
593 };
594 owner
595 .signature()
596 .and_then(classify)
597 .or_else(|| {
598 analyzer
599 .get_source(owner, false)
600 .as_deref()
601 .and_then(classify)
602 })
603 .unwrap_or(EnumOwnerKind::NonEnum)
604}
605
606#[derive(Clone, PartialEq, Eq, Hash)]
607pub struct CppScanBinding {
608 pub unit: Option<CodeUnit>,
609 pub type_name: Option<String>,
610 pub indirection: i32,
611}
612
613impl CppScanBinding {
614 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
615 Self {
616 type_name: Some(cpp_name_for(&unit)),
617 unit: Some(unit),
618 indirection,
619 }
620 }
621
622 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
623 Self {
624 type_name: Some(type_name),
625 unit,
626 indirection,
627 }
628 }
629
630 pub fn as_arg_type(&self) -> Option<CppArgType> {
631 let name = self
632 .type_name
633 .clone()
634 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
635 Some(CppArgType {
636 name,
637 unit: self.unit.clone(),
638 indirection: self.indirection,
639 pointee_const: false,
640 })
641 }
642}
643
644type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
645pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
646pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
647type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
648type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
649type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
650type MacroLexicalTemplateCache =
651 HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
652
653type MacroLocalBindingTemplateCache =
654 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
655type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
656type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
657type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
658
659struct StructuredIncludeFact {
660 start_byte: usize,
661 end_byte: usize,
662 path: String,
663}
664
665#[derive(Clone, Default)]
666pub struct MacroEnvironment {
667 bindings: HashMap<String, MacroBinding>,
668 known_undefined_names: HashSet<String>,
669 build_proven_defines: HashSet<String>,
674 unknown_names: bool,
675 applied_pragma_once_files: HashSet<ProjectFile>,
676 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
677}
678
679pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
688
689struct MacroEnvironmentCheckpoint {
691 frontier: usize,
693 environment: Arc<MacroEnvironment>,
694}
695
696struct MacroEnvironmentCheckpoints {
705 checkpoints: Vec<MacroEnvironmentCheckpoint>,
706}
707
708impl MacroEnvironmentCheckpoints {
709 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
711 let index = self
712 .checkpoints
713 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
714 assert!(
715 index > 0,
716 "a checkpoint vector starts at frontier zero, which precedes every request"
717 );
718 &self.checkpoints[index - 1]
719 }
720}
721
722impl MacroEnvironment {
723 fn binding(&self, name: &str) -> Option<&MacroBinding> {
724 self.bindings.get(name)
725 }
726
727 fn may_bind(&self, name: &str) -> bool {
728 self.bindings.contains_key(name) || self.unknown_names
729 }
730
731 fn insert(&mut self, name: String, binding: MacroBinding) {
732 self.known_undefined_names.remove(&name);
733 self.bindings.insert(name, binding);
734 }
735
736 fn remove(&mut self, name: &str) {
737 self.bindings.remove(name);
738 self.known_undefined_names.insert(name.to_string());
739 }
740
741 fn remove_known_undefined(&mut self, name: &str) {
742 self.known_undefined_names.remove(name);
743 }
744
745 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
746 for binding in self.bindings.values_mut() {
747 *binding = MacroBinding::uncertain_from(binding, source, byte);
748 }
749 self.known_undefined_names.clear();
750 self.build_proven_defines.clear();
755 self.unknown_names = true;
756 }
757
758 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
759 guards.iter().all(|guard| self.guard_may_hold(guard))
760 }
761
762 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
763 let Some(expression) = guard.as_boolean_expression() else {
764 return true;
765 };
766 self.boolean_guard_may_hold(&expression)
767 }
768
769 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
770 match expression {
771 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
772 BooleanGuardExpression::Undefined(name) => {
773 self.bindings
774 .get(name)
775 .is_none_or(|binding| !binding.is_exact())
776 && (!self.build_proven_defines.contains(name)
777 || self.known_undefined_names.contains(name))
778 }
779 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
780 BooleanGuardExpression::Opaque(_)
781 | BooleanGuardExpression::NegatedOpaque(_)
782 | BooleanGuardExpression::Constant(true) => true,
783 BooleanGuardExpression::Constant(false) => false,
784 BooleanGuardExpression::All(expressions) => expressions
785 .iter()
786 .all(|expression| self.boolean_guard_may_hold(expression)),
787 BooleanGuardExpression::Any(expressions) => expressions
788 .iter()
789 .any(|expression| self.boolean_guard_may_hold(expression)),
790 }
791 }
792}
793
794#[derive(Clone)]
795pub enum EffectiveUsingTarget {
796 Ordinary {
797 name: String,
798 target_components: Vec<String>,
799 global: bool,
800 },
801 Namespace {
802 namespace_components: Vec<String>,
803 global: bool,
804 },
805}
806
807#[derive(Clone)]
808pub struct OrdinaryTypeImport {
809 pub target: EffectiveUsingTarget,
810 pub source: ProjectFile,
811 pub declaration_byte: usize,
812 pub scope_start: usize,
813 pub scope_end: usize,
814 pub scope_depth: usize,
815 pub block_scope: bool,
816 pub lexical_depth: usize,
817 pub declaration_namespace: Vec<String>,
818 pub namespace_scope: Option<Vec<String>>,
819 pub resolved_target_components: Option<Vec<String>>,
820 pub required_guards: HashSet<PreprocessorGuard>,
821}
822
823#[derive(Clone)]
824pub struct ConditionalIncludeProjection {
825 pub activation_byte: usize,
826 pub required_guards: HashSet<PreprocessorGuard>,
827 pub partial_guards: HashSet<PreprocessorGuard>,
831}
832
833#[derive(Default)]
834pub struct SourceUsingIndex {
835 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
836 pub directives: Vec<OrdinaryTypeImport>,
837}
838
839#[derive(Default)]
840pub struct ProjectUsingIndex {
841 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
842 pub directives: Vec<OrdinaryTypeImport>,
843}
844
845type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
846
847pub struct EffectiveUsingIndex {
848 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
849}
850
851impl EffectiveUsingIndex {
852 fn new(_root: ProjectFile) -> Self {
853 Self {
854 projected_by_name: Mutex::new(HashMap::default()),
855 }
856 }
857
858 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
859 self.projected_by_name
860 .lock()
861 .expect("C++ effective-using projection cache poisoned")
862 .entry(name.to_string())
863 .or_default()
864 .clone()
865 }
866}
867
868pub enum OrdinaryTypeImportResolution {
869 Resolved {
870 target: CodeUnit,
871 target_components: Vec<String>,
872 lexical_depth: usize,
873 is_direct: bool,
874 },
875 Ambiguous {
876 lexical_depth: usize,
877 },
878 Missing,
879}
880
881type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
882type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
883type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
884type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
885type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
886type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
887type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
888type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
889
890struct ExtractedComparable {
895 shapes: Vec<CppComparableSlot>,
896 suffix: String,
897}
898
899const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
903
904#[derive(Clone, Copy, Debug, PartialEq, Eq)]
926enum IncludePathAdmission {
927 Proven,
928 Compatible,
929}
930
931impl IncludePathAdmission {
932 fn admits(
933 self,
934 required: &HashSet<PreprocessorGuard>,
935 partial: &HashSet<PreprocessorGuard>,
936 reference_guards: Option<&HashSet<PreprocessorGuard>>,
937 ) -> bool {
938 match self {
939 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
940 Self::Compatible => {
941 guard_requirements_hold_at_reference(partial, reference_guards)
942 && guards_compatible_at_reference(required, reference_guards)
943 }
944 }
945 }
946}
947
948pub struct VisibilityIndex<'a> {
959 cpp: &'a dyn CppSource,
960 token: QueryToken<'a>,
965 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
966 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
967 global_field_internal_linkage: HashMap<CodeUnit, bool>,
968 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
969 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
970 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
971 parser_alias_target_matches:
972 RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
973 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
974 project_using_index: OnceLock<ProjectUsingIndex>,
975 callable_reference_specs:
976 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
977 structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
978 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
979 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
980 include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
981 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
982 #[cfg(any(test, feature = "test-support"))]
983 conditional_include_projection_index_build_count: AtomicUsize,
984 #[cfg(any(test, feature = "test-support"))]
985 conditional_include_projection_state_count: AtomicUsize,
986 #[cfg(any(test, feature = "test-support"))]
987 conditional_include_target_state_count: AtomicUsize,
988 #[cfg(any(test, feature = "test-support"))]
989 include_activation_build_count: AtomicUsize,
990 #[cfg(any(test, feature = "test-support"))]
991 using_donor_activation_count: AtomicUsize,
992 #[cfg(any(test, feature = "test-support"))]
993 using_namespace_lookup_count: AtomicUsize,
994 #[cfg(any(test, feature = "test-support"))]
995 using_name_candidate_inspection_count: AtomicUsize,
996 #[cfg(any(test, feature = "test-support"))]
997 callable_reference_spec_build_count: AtomicUsize,
998 #[cfg(any(test, feature = "test-support"))]
999 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1000 #[cfg(any(test, feature = "test-support"))]
1001 visible_parser_alias_name_set_build_count: AtomicUsize,
1002 parser_alias_fallback_calls: AtomicUsize,
1003 parser_alias_fallback_files: AtomicUsize,
1004 parser_alias_source_parses: AtomicUsize,
1005 parser_alias_fallback_elapsed_micros: AtomicUsize,
1006 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1007 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1008 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1009 comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1010 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1011 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1012 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1013 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1014 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1015 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1016 macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1017 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1018 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1025 macro_replacements: Mutex<MacroReplacementCache>,
1026 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1027 pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1028 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1029 macro_type_parameters: Mutex<MacroTypeParameterCache>,
1030 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1031 #[cfg(any(test, feature = "test-support"))]
1032 pub macro_replacement_parse_count: AtomicUsize,
1033 #[cfg(any(test, feature = "test-support"))]
1034 pub macro_event_application_count: AtomicUsize,
1035 #[cfg(any(test, feature = "test-support"))]
1038 pub macro_environment_checkpoint_build_count: AtomicUsize,
1039 #[cfg(any(test, feature = "test-support"))]
1042 pub macro_environment_copy_count: AtomicUsize,
1043 #[cfg(any(test, feature = "test-support"))]
1044 pub macro_environment_request_count: AtomicUsize,
1045 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1046 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1047 #[cfg(any(test, feature = "test-support"))]
1048 qualified_candidate_inspections: AtomicUsize,
1049 #[cfg(any(test, feature = "test-support"))]
1050 target_preserving_type_resolution_count: AtomicUsize,
1051 #[cfg(any(test, feature = "test-support"))]
1052 visibility_identifier_lookup_count: usize,
1053 #[cfg(any(test, feature = "test-support"))]
1054 visibility_identifier_batch_count: usize,
1055}
1056
1057impl Drop for VisibilityIndex<'_> {
1058 fn drop(&mut self) {
1059 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1060 return;
1061 }
1062 #[cfg(any(test, feature = "test-support"))]
1063 eprintln!(
1064 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1065 self.macro_environment_request_count.load(Ordering::Relaxed),
1066 self.macro_environment_copy_count.load(Ordering::Relaxed),
1067 self.macro_environment_checkpoint_build_count
1068 .load(Ordering::Relaxed),
1069 self.macro_event_application_count.load(Ordering::Relaxed),
1070 );
1071 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1072 if calls == 0 {
1073 return;
1074 }
1075 eprintln!(
1076 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1077 calls,
1078 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1079 self.parser_alias_source_parses.load(Ordering::Relaxed),
1080 self.parser_alias_fallback_elapsed_micros
1081 .load(Ordering::Relaxed)
1082 / 1_000,
1083 );
1084 }
1085}
1086
1087#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1088pub enum PreprocessorGuard {
1089 Defined(String),
1090 Undefined(String),
1091 Boolean(BooleanGuardExpression),
1092 Expression(String),
1093 NegatedExpression(String),
1094 Constant(bool),
1095}
1096
1097#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1098pub enum BooleanGuardExpression {
1099 Defined(String),
1100 Undefined(String),
1101 Truthy(String),
1102 Falsy(String),
1103 Opaque(String),
1104 NegatedOpaque(String),
1105 All(Vec<BooleanGuardExpression>),
1106 Any(Vec<BooleanGuardExpression>),
1107 Constant(bool),
1108}
1109
1110impl BooleanGuardExpression {
1111 fn negated(&self) -> Self {
1112 match self {
1113 Self::Defined(name) => Self::Undefined(name.clone()),
1114 Self::Undefined(name) => Self::Defined(name.clone()),
1115 Self::Truthy(name) => Self::Falsy(name.clone()),
1116 Self::Falsy(name) => Self::Truthy(name.clone()),
1117 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1118 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1119 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1120 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1121 Self::Constant(value) => Self::Constant(!value),
1122 }
1123 }
1124
1125 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1126 Self::normalized(expressions, true)
1127 }
1128
1129 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1130 Self::normalized(expressions, false)
1131 }
1132
1133 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1134 let mut normalized = Vec::new();
1135 for expression in expressions {
1136 match expression {
1137 Self::All(nested) if conjunction => normalized.extend(nested),
1138 Self::Any(nested) if !conjunction => normalized.extend(nested),
1139 Self::Constant(value) if value == conjunction => {}
1140 Self::Constant(value) => return Self::Constant(value),
1141 expression => normalized.push(expression),
1142 }
1143 }
1144 normalized.sort_unstable();
1145 normalized.dedup();
1146 match normalized.len() {
1147 0 => Self::Constant(conjunction),
1148 1 => normalized.pop().expect("one Boolean guard expression"),
1149 _ if conjunction => Self::All(normalized),
1150 _ => Self::Any(normalized),
1151 }
1152 }
1153
1154 fn implies(&self, required: &Self) -> bool {
1155 if self == required
1156 || matches!(self, Self::Constant(false))
1157 || matches!(required, Self::Constant(true))
1158 {
1159 return true;
1160 }
1161 if matches!(
1162 (self, required),
1163 (Self::Truthy(active), Self::Defined(required))
1164 | (Self::Undefined(active), Self::Falsy(required))
1165 if active == required
1166 ) {
1167 return true;
1168 }
1169 match self {
1170 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1171 Self::All(active) => match required {
1172 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1173 _ => active.iter().any(|expression| expression.implies(required)),
1174 },
1175 _ => match required {
1176 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1177 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1178 _ => false,
1179 },
1180 }
1181 }
1182
1183 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1184 match self {
1185 Self::Defined(name)
1186 | Self::Undefined(name)
1187 | Self::Truthy(name)
1188 | Self::Falsy(name) => name == macro_name,
1189 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1192 Self::All(expressions) | Self::Any(expressions) => expressions
1193 .iter()
1194 .any(|expression| expression.may_depend_on_macro(macro_name)),
1195 Self::Constant(_) => false,
1196 }
1197 }
1198
1199 pub fn heap_size(&self) -> usize {
1200 match self {
1201 Self::Defined(value)
1202 | Self::Undefined(value)
1203 | Self::Truthy(value)
1204 | Self::Falsy(value)
1205 | Self::Opaque(value)
1206 | Self::NegatedOpaque(value) => value.len(),
1207 Self::All(expressions) | Self::Any(expressions) => {
1208 expressions
1209 .iter()
1210 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1211 size.saturating_add(std::mem::size_of::<Self>())
1212 .saturating_add(expression.heap_size())
1213 })
1214 }
1215 Self::Constant(_) => 0,
1216 }
1217 }
1218}
1219
1220impl PreprocessorGuard {
1221 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1222 match self {
1223 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1224 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1225 Self::Boolean(expression) => Some(expression.clone()),
1226 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1227 Self::Expression(_) | Self::NegatedExpression(_) => None,
1228 }
1229 }
1230
1231 fn negated(&self) -> Self {
1232 match self {
1233 Self::Defined(name) => Self::Undefined(name.clone()),
1234 Self::Undefined(name) => Self::Defined(name.clone()),
1235 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1236 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1237 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1238 Self::Constant(value) => Self::Constant(!value),
1239 }
1240 }
1241
1242 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1243 match self {
1244 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1245 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1246 Self::Expression(_) | Self::NegatedExpression(_) => true,
1249 Self::Constant(_) => false,
1250 }
1251 }
1252}
1253
1254#[derive(Clone, PartialEq, Eq)]
1255pub enum MacroDefinition {
1256 Object {
1257 replacement: String,
1258 },
1259 Function {
1260 parameters: Vec<String>,
1261 replacement: String,
1262 },
1263 VariadicFunction {
1264 parameters: Vec<String>,
1265 replacement: String,
1266 },
1267 Unsupported,
1268}
1269
1270#[derive(Clone, Debug, PartialEq, Eq)]
1271pub enum MacroIncludeProtection {
1272 MacroGuard(String),
1273 PragmaOnce,
1274 None,
1275}
1276
1277enum ParsedMacroReplacement {
1278 Parsed { source: String, tree: Tree },
1279 Unsupported,
1280}
1281
1282const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1286
1287pub struct ParsedReplacementBody {
1296 pub source: String,
1297 pub tree: Tree,
1298 pub body_offset: usize,
1299 pub parameters: Vec<String>,
1300 original_offsets: Box<[usize]>,
1305}
1306
1307impl ParsedReplacementBody {
1308 pub fn statements(&self) -> Option<Node<'_>> {
1310 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1311 .child_by_field_name("body")
1312 }
1313
1314 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1320 assert!(
1321 node.start_byte() >= self.body_offset,
1322 "synthetic sentinel node cannot be mapped to a macro replacement"
1323 );
1324 assert!(
1325 node.end_byte() >= self.body_offset,
1326 "synthetic sentinel node cannot be mapped to a macro replacement"
1327 );
1328 let start_offset = node.start_byte() - self.body_offset;
1329 let end_offset = node.end_byte() - self.body_offset;
1330 assert!(start_offset <= end_offset);
1331 let start_origin = *self
1332 .original_offsets
1333 .get(start_offset)
1334 .expect("replacement node start must have a source mapping");
1335 let end_origin = *self
1336 .original_offsets
1337 .get(end_offset)
1338 .expect("replacement node end must have a source mapping");
1339 assert!(start_origin <= end_origin);
1340 let start = replacement_start + start_origin;
1341 let end = replacement_start + end_origin;
1342 start..end
1343 }
1344
1345 fn expands_variadic_arguments(&self) -> bool {
1352 let mut stack = vec![self.tree.root_node()];
1353 while let Some(node) = stack.pop() {
1354 if matches!(
1355 node.kind(),
1356 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1357 ) && node_text(node, &self.source) == "__VA_ARGS__"
1358 {
1359 return true;
1360 }
1361 push_named_children_reversed(node, &mut stack);
1362 }
1363 false
1364 }
1365}
1366
1367fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1368 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1369 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1370 if compact.starts_with("0x") || compact.starts_with("0X") {
1371 (16, 2, |ch| ch.is_ascii_hexdigit())
1372 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1373 (2, 2, |ch| matches!(ch, '0' | '1'))
1374 } else if compact.starts_with('0') && compact.len() > 1 {
1375 (8, 0, |ch| matches!(ch, '0'..='7'))
1376 } else {
1377 (10, 0, |ch| ch.is_ascii_digit())
1378 };
1379 let digit_len = compact[digits_start..]
1380 .chars()
1381 .take_while(|ch| digit_matches(*ch))
1382 .map(char::len_utf8)
1383 .sum::<usize>();
1384 if digit_len == 0 {
1385 return None;
1386 }
1387 let digits_end = digits_start + digit_len;
1388 if !compact[digits_end..]
1389 .chars()
1390 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1391 {
1392 return None;
1393 }
1394 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1395}
1396
1397#[derive(Clone)]
1398enum MacroLocalBindingTypeTemplate {
1399 Parameter(usize),
1400 Fixed(String),
1401}
1402
1403#[derive(Clone)]
1404struct MacroLocalBindingTemplate {
1405 name: String,
1406 declared_type: MacroLocalBindingTypeTemplate,
1407 pointer_depth: i32,
1408}
1409
1410pub struct MacroLocalBinding<'tree> {
1418 pub name: String,
1419 pub type_name: String,
1420 pub type_node: Option<Node<'tree>>,
1421 pub pointer_depth: i32,
1422 pub proven_unit: Option<CodeUnit>,
1423}
1424
1425#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1431pub enum MacroLexicalBindingKind {
1432 Parameter,
1433 Local,
1434}
1435
1436#[derive(Clone, Debug, PartialEq, Eq)]
1437pub struct MacroLexicalBinding {
1438 pub definition: ProjectFile,
1439 pub kind: MacroLexicalBindingKind,
1440 pub name: String,
1441 pub name_range: std::ops::Range<usize>,
1442 pub declaration_range: std::ops::Range<usize>,
1443}
1444
1445#[derive(Clone, Debug, Default, PartialEq, Eq)]
1449pub struct MacroLexicalReferences {
1450 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1451 pub truncated: bool,
1452 pub cancelled: bool,
1453}
1454
1455fn macro_replacement_type_parameters(
1456 body: &ParsedReplacementBody,
1457 parameters: &[String],
1458) -> Option<Vec<usize>> {
1459 let mut found = Vec::new();
1460 let mut stack = vec![body.tree.root_node()];
1461 while let Some(node) = stack.pop() {
1462 let type_position = node.kind() == "type_identifier"
1463 || (node.kind() == "identifier"
1464 && node.parent().is_some_and(|parent| {
1465 parent.kind() == "type_descriptor"
1466 && parent.child_by_field_name("type") == Some(node)
1467 }));
1468 let offsetof_type_position = node.kind() == "identifier"
1469 && node
1470 .parent()
1471 .filter(|parent| parent.kind() == "argument_list")
1472 .and_then(|arguments| arguments.parent())
1473 .is_some_and(|call| {
1474 call.kind() == "call_expression"
1475 && call
1476 .child_by_field_name("function")
1477 .is_some_and(|function| {
1478 function.kind() == "identifier"
1479 && node_text(function, &body.source) == "offsetof"
1480 })
1481 && call
1482 .child_by_field_name("arguments")
1483 .is_some_and(|arguments| {
1484 argument_children(arguments).next() == Some(node)
1485 })
1486 });
1487 if (type_position || offsetof_type_position)
1488 && let Some(index) = parameters
1489 .iter()
1490 .position(|parameter| parameter == node_text(node, &body.source))
1491 && !found.contains(&index)
1492 {
1493 found.push(index);
1494 }
1495 push_named_children_reversed(node, &mut stack);
1496 }
1497 (!found.is_empty()).then_some(found)
1498}
1499
1500fn macro_replacement_type_parameter(
1501 body: &ParsedReplacementBody,
1502 parameters: &[String],
1503) -> Option<usize> {
1504 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1505 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1506}
1507
1508pub(crate) fn macro_type_argument_node<'tree>(
1509 node: Node<'tree>,
1510 source: &str,
1511) -> Option<Node<'tree>> {
1512 match node.kind() {
1513 "type_descriptor" => {
1514 let type_child = node
1515 .child_by_field_name("type")
1516 .or_else(|| first_type_child(node))?;
1517 for index in (0..node.named_child_count()).rev() {
1518 let child = node.named_child(index)?;
1519 if child != type_child
1520 && matches!(
1521 child.kind(),
1522 "identifier"
1523 | "type_identifier"
1524 | "qualified_identifier"
1525 | "scoped_type_identifier"
1526 )
1527 {
1528 return Some(child);
1529 }
1530 }
1531 macro_type_argument_node(type_child, source)
1532 }
1533 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1534 node.child_by_field_name("name")
1535 }
1536 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1537 .next_named_sibling()
1538 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1539 .and_then(|error| error.named_child(0))
1540 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1541 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1542 }
1543}
1544
1545fn c_function_macro_argument<'tree>(
1546 node: Node<'tree>,
1547) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1548 let mut current = node;
1549 while let Some(parent) = current.parent() {
1550 if parent.kind() == "argument_list" {
1551 let call = parent.parent().filter(|call| {
1552 call.kind() == "call_expression"
1553 && call.child_by_field_name("arguments") == Some(parent)
1554 && call
1555 .child_by_field_name("function")
1556 .is_some_and(|function| {
1557 function.kind() == "identifier"
1558 && !node_range_contains(function, current)
1559 })
1560 })?;
1561 let mut actuals = argument_children(parent).enumerate();
1562 let (index, argument) = actuals.find(|(_, argument)| {
1563 node_range_contains(*argument, node)
1564 && (argument.start_byte() == current.start_byte()
1565 || argument.end_byte() == current.end_byte())
1566 })?;
1567 return Some((call, index, argument));
1568 }
1569 current = parent;
1570 }
1571 None
1572}
1573
1574fn recognized_c_macro_declarator_binding<'tree>(
1579 statement: Node<'tree>,
1580 source: &str,
1581) -> Option<MacroLocalBinding<'tree>> {
1582 let assignment = match statement.kind() {
1583 "assignment_expression" => statement,
1584 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1585 _ => return None,
1586 };
1587 if assignment.kind() != "assignment_expression" {
1588 return None;
1589 }
1590 let call = assignment.child_by_field_name("left")?;
1591 if call.kind() != "call_expression" {
1592 return None;
1593 }
1594 let function = call.child_by_field_name("function")?;
1595 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1596 return None;
1597 }
1598 let arguments = call.child_by_field_name("arguments")?;
1599 let mut actuals = argument_children(arguments);
1600 let type_node = actuals.next()?;
1601 if actuals.next().is_some()
1602 || !matches!(
1603 type_node.kind(),
1604 "identifier"
1605 | "type_identifier"
1606 | "qualified_identifier"
1607 | "scoped_type_identifier"
1608 | "template_type"
1609 )
1610 {
1611 return None;
1612 }
1613 let name_node = (0..assignment.named_child_count())
1614 .filter_map(|index| assignment.named_child(index))
1615 .filter(|child| child.kind() == "ERROR")
1616 .filter_map(|error| {
1617 (error.named_child_count() == 1)
1618 .then(|| error.named_child(0))
1619 .flatten()
1620 })
1621 .find(|node| node.kind() == "identifier")?;
1622 let name = node_text(name_node, source).trim();
1623 let type_name = node_text(type_node, source).trim();
1624 if name.is_empty() || type_name.is_empty() {
1625 return None;
1626 }
1627 Some(MacroLocalBinding {
1628 name: name.to_string(),
1629 type_name: type_name.to_string(),
1630 type_node: Some(type_node),
1631 pointer_depth: 1,
1632 proven_unit: None,
1633 })
1634}
1635
1636#[derive(Clone, PartialEq, Eq)]
1637pub struct MacroBinding {
1638 source: ProjectFile,
1639 declaration_byte: usize,
1640 definition: MacroDefinition,
1641 exact: bool,
1642}
1643
1644impl MacroBinding {
1645 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1646 Self {
1647 source: source.clone(),
1648 declaration_byte,
1649 definition: MacroDefinition::Unsupported,
1650 exact: false,
1651 }
1652 }
1653
1654 fn is_exact(&self) -> bool {
1655 self.exact
1656 }
1657
1658 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1659 Self {
1660 source: source.clone(),
1661 declaration_byte,
1662 definition: current.definition.clone(),
1663 exact: false,
1664 }
1665 }
1666}
1667
1668type OwningPreprocessorConditionals = Box<[usize]>;
1680
1681#[derive(Clone)]
1682pub enum MacroEvent {
1683 Define {
1684 name: String,
1685 binding: MacroBinding,
1686 byte: usize,
1687 conditionals: OwningPreprocessorConditionals,
1688 },
1689 Undef {
1690 name: String,
1691 byte: usize,
1692 conditionals: OwningPreprocessorConditionals,
1693 },
1694 Include {
1695 targets: Vec<ProjectFile>,
1696 byte: usize,
1697 conditionals: OwningPreprocessorConditionals,
1698 },
1699 Invalidate {
1700 byte: usize,
1701 },
1702}
1703
1704impl MacroEvent {
1705 pub fn byte(&self) -> usize {
1706 match self {
1707 Self::Define { byte, .. }
1708 | Self::Undef { byte, .. }
1709 | Self::Include { byte, .. }
1710 | Self::Invalidate { byte } => *byte,
1711 }
1712 }
1713}
1714
1715#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1716pub enum CallArityEvidence {
1717 Exact(usize),
1718 Unknown,
1719}
1720
1721impl CallArityEvidence {
1722 pub fn exact(self) -> Option<usize> {
1723 match self {
1724 Self::Exact(arity) => Some(arity),
1725 Self::Unknown => None,
1726 }
1727 }
1728
1729 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1730 self.exact().map(|arity| expected.accepts(arity))
1731 }
1732}
1733
1734#[derive(Clone)]
1735struct DeclaredFieldTypeFact {
1736 type_text: String,
1737 indirection: i32,
1738 template_arguments: Option<Vec<CppTemplateExpression>>,
1739}
1740
1741#[derive(Clone, PartialEq, Eq)]
1742enum StructuredAliasTarget {
1743 Builtin,
1744 Named {
1745 components: Vec<String>,
1746 global: bool,
1747 arguments: Option<Vec<CppTemplateExpression>>,
1748 },
1749}
1750
1751struct CppAlias {
1752 name: String,
1753 target: String,
1754 namespace: Option<String>,
1755}
1756
1757type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1758
1759#[derive(Debug, Clone, PartialEq, Eq)]
1763pub enum CppTemplateResolutionError {
1764 AliasCycle { alias: CodeUnit },
1766 ArgumentBinding,
1768 Substitution,
1770 PrimarySelection,
1773 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1776}
1777
1778fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1781 let mut distinct: Vec<CodeUnit> = Vec::new();
1782 for unit in units {
1783 if !distinct
1784 .iter()
1785 .any(|existing| same_visible_symbol(existing, unit))
1786 {
1787 distinct.push(unit.clone());
1788 }
1789 }
1790 distinct
1791}
1792
1793pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1796 visibility: Factory,
1797 file: &ProjectFile,
1798 root: Node<'_>,
1799 source: &str,
1800 max_references: usize,
1801 cancelled: impl FnMut() -> bool,
1802) -> MacroLexicalReferences
1803where
1804 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1805{
1806 crate::graph::macro_lexical::all_references(
1807 visibility,
1808 file,
1809 root,
1810 source,
1811 max_references,
1812 cancelled,
1813 )
1814}
1815
1816impl<'a> VisibilityIndex<'a> {
1817 pub fn cpp(&self) -> &'a dyn CppSource {
1818 self.cpp
1819 }
1820
1821 pub fn token(&self) -> QueryToken<'a> {
1823 self.token
1824 }
1825
1826 pub fn has_unresolved_include_visible_before(
1834 &self,
1835 file: &ProjectFile,
1836 before_byte: usize,
1837 ) -> bool {
1838 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1839 return false;
1840 };
1841 let cell = self
1842 .structured_include_fact_cells
1843 .lock()
1844 .expect("C++ structured include-fact cache poisoned")
1845 .entry(file.clone())
1846 .or_default()
1847 .clone();
1848 let facts = cell.get_or_init(|| collect_structured_include_facts(prepared.as_ref()));
1849 has_unresolved_include_visible_before_in_prepared(
1850 file,
1851 prepared.as_ref(),
1852 self.cpp.include_target_index(),
1853 facts,
1854 before_byte,
1855 )
1856 }
1857
1858 #[cfg(any(test, feature = "test-support"))]
1866 pub fn from_visible_files_for_test(
1867 cpp: &'a dyn CppSource,
1868 token: QueryToken<'a>,
1869 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1870 ) -> Self {
1871 let visible_source_files_by_root = visible_by_file
1872 .iter()
1873 .map(|(file, visible)| {
1874 (
1875 file.clone(),
1876 visible
1877 .iter()
1878 .map(|unit| unit.source().clone())
1879 .chain(std::iter::once(file.clone()))
1880 .collect(),
1881 )
1882 })
1883 .collect();
1884 let mut global_field_internal_linkage = HashMap::default();
1885 Self {
1886 cpp,
1887 token,
1888 visible_by_identifier: build_visible_identifier_index(
1889 &CppGraphSource::from_source(cpp, token),
1890 &visible_by_file,
1891 &visible_source_files_by_root,
1892 &mut global_field_internal_linkage,
1893 ),
1894 global_field_internal_linkage,
1895 visible_by_file,
1896 visible_source_files_by_root,
1897 alias_cells: Mutex::new(HashMap::default()),
1898 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1899 parser_alias_target_matches: RwLock::new(HashMap::default()),
1900 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1901 project_using_index: OnceLock::new(),
1902 callable_reference_specs: Mutex::new(HashMap::default()),
1903 structured_include_fact_cells: Mutex::new(HashMap::default()),
1904 include_activation_cells: Mutex::new(HashMap::default()),
1905 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1906 include_path_admission_cells: Mutex::new(HashMap::default()),
1907 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1908 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1909 conditional_include_projection_state_count: AtomicUsize::new(0),
1910 conditional_include_target_state_count: AtomicUsize::new(0),
1911 include_activation_build_count: AtomicUsize::new(0),
1912 using_donor_activation_count: AtomicUsize::new(0),
1913 using_namespace_lookup_count: AtomicUsize::new(0),
1914 using_name_candidate_inspection_count: AtomicUsize::new(0),
1915 callable_reference_spec_build_count: AtomicUsize::new(0),
1916 alias_source_parse_counts: Mutex::new(HashMap::default()),
1917 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1918 parser_alias_fallback_calls: AtomicUsize::new(0),
1919 parser_alias_fallback_files: AtomicUsize::new(0),
1920 parser_alias_source_parses: AtomicUsize::new(0),
1921 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1922 field_type_facts: Mutex::new(HashMap::default()),
1923 structured_alias_targets: Mutex::new(HashMap::default()),
1924 callable_comparables: Mutex::new(HashMap::default()),
1925 comparable_name_declarations: Mutex::new(HashMap::default()),
1926 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1927 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1928 precise_parent_cache: Mutex::new(HashMap::default()),
1929 c_tag_kind_cache: Mutex::new(HashMap::default()),
1930 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1931 macro_event_cells: Mutex::new(HashMap::default()),
1932 macro_event_name_sets: Mutex::new(HashMap::default()),
1933 macro_include_protection_cells: Mutex::new(HashMap::default()),
1934 macro_environment_checkpoints: Mutex::new(HashMap::default()),
1935 macro_replacements: Mutex::new(HashMap::default()),
1936 macro_local_binding_templates: Mutex::new(HashMap::default()),
1937 macro_lexical_templates: Mutex::new(HashMap::default()),
1938 macro_replacement_bodies: Mutex::new(HashMap::default()),
1939 macro_type_parameters: Mutex::new(HashMap::default()),
1940 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1941 macro_replacement_parse_count: AtomicUsize::new(0),
1942 macro_event_application_count: AtomicUsize::new(0),
1943 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1944 macro_environment_copy_count: AtomicUsize::new(0),
1945 macro_environment_request_count: AtomicUsize::new(0),
1946 cpp_template_metadata: HashMap::default(),
1947 cpp_template_families: HashMap::default(),
1948 qualified_candidate_inspections: AtomicUsize::new(0),
1949 target_preserving_type_resolution_count: AtomicUsize::new(0),
1950 visibility_identifier_lookup_count: 0,
1951 visibility_identifier_batch_count: 0,
1952 }
1953 }
1954
1955 fn cpp_source(&self) -> CppGraphSource<'a> {
1962 CppGraphSource::from_source(self.cpp, self.token)
1963 }
1964
1965 pub fn build(
1966 cpp: &'a dyn CppSource,
1967 token: QueryToken<'a>,
1968 analyzer: &CppGraphSource<'_>,
1969 roots: &HashSet<ProjectFile>,
1970 ) -> Self {
1971 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1972 }
1973
1974 pub fn build_with_cancellation(
1975 cpp: &'a dyn CppSource,
1976 token: QueryToken<'a>,
1977 analyzer: &CppGraphSource<'_>,
1978 roots: &HashSet<ProjectFile>,
1979 cancellation: Option<&CancellationToken>,
1980 ) -> Self {
1981 let visibility_started = Instant::now();
1982 let include_targets = cpp.include_target_index();
1983 let includes_started = Instant::now();
1984 let mut include_graph = IncludeGraph::default();
1985 for root in roots {
1986 include_graph.extend_with(root, cancellation, &mut |file| {
1987 cpp_include_paths(&cpp.visibility_import_statements(token, file))
1988 .into_iter()
1989 .flat_map(|include| {
1990 resolve_include_targets_with_index(file, &include, include_targets)
1991 })
1992 .collect()
1993 });
1994 }
1995 let include_elapsed = includes_started.elapsed();
1996 let include_file_count = include_graph.files().count();
1997 let visible_source_files_by_root = roots
1998 .iter()
1999 .map(|root| {
2000 (
2001 root.clone(),
2002 include_graph.reachable_files(root, cancellation),
2003 )
2004 })
2005 .collect::<HashMap<_, _>>();
2006 let mut visibility_stats = BoundedVisibilityStats::default();
2007 let mut visible_by_file = build_bounded_visible_declarations(
2008 cpp,
2009 token,
2010 analyzer,
2011 roots,
2012 &visible_source_files_by_root,
2013 cancellation,
2014 &mut visibility_stats,
2015 );
2016 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2017 eprintln!(
2018 "BIFROST_CPP_VISIBILITY_STATS total_ms={} include_ms={} include_files={} rounds={} root_names={} identifier_lookups={} identifier_batches={} candidate_units={} candidate_sources={} declaration_reads={} declaration_units={} selected_units={} dependency_ast_nodes={} dependency_names={} lookup_ms={} declaration_ms={} dependency_ast_ms={}",
2019 visibility_started.elapsed().as_millis(),
2020 include_elapsed.as_millis(),
2021 include_file_count,
2022 visibility_stats.rounds,
2023 visibility_stats.root_names,
2024 visibility_stats.identifier_lookups,
2025 visibility_stats.identifier_batches,
2026 visibility_stats.candidate_units,
2027 visibility_stats.candidate_sources,
2028 visibility_stats.declaration_reads,
2029 visibility_stats.declaration_units,
2030 visibility_stats.selected_units,
2031 visibility_stats.dependency_ast_nodes,
2032 visibility_stats.dependency_names,
2033 visibility_stats.lookup_elapsed.as_millis(),
2034 visibility_stats.declaration_elapsed.as_millis(),
2035 visibility_stats.dependency_ast_elapsed.as_millis(),
2036 );
2037 }
2038 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2039 let finalize_started = Instant::now();
2040 if report_stats {
2041 eprintln!(
2042 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2043 visible_by_file.len(),
2044 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2045 );
2046 }
2047 let owner_started = Instant::now();
2048 if report_stats {
2049 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2050 }
2051 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2052 if report_stats {
2053 eprintln!(
2054 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2055 owner_stats.unseen_owners,
2056 owner_stats.definition_lookups,
2057 owner_stats.admitted,
2058 owner_started.elapsed().as_millis(),
2059 );
2060 }
2061 let mut global_field_internal_linkage = HashMap::default();
2062 let identifier_started = Instant::now();
2063 if report_stats {
2064 eprintln!(
2065 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2066 );
2067 }
2068 let visible_by_identifier = build_visible_identifier_index(
2069 analyzer,
2070 &visible_by_file,
2071 &visible_source_files_by_root,
2072 &mut global_field_internal_linkage,
2073 );
2074 if report_stats {
2075 eprintln!(
2076 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2077 visible_by_identifier.len(),
2078 visible_by_identifier
2079 .values()
2080 .map(HashMap::len)
2081 .sum::<usize>(),
2082 visible_by_identifier
2083 .values()
2084 .flat_map(HashMap::values)
2085 .map(Vec::len)
2086 .sum::<usize>(),
2087 identifier_started.elapsed().as_millis(),
2088 );
2089 }
2090 let mut cpp_template_metadata = HashMap::default();
2091 let metadata_started = Instant::now();
2092 let mut template_classes = 0usize;
2093 if report_stats {
2094 eprintln!(
2095 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2096 );
2097 }
2098 for unit in visible_by_file
2099 .values()
2100 .flatten()
2101 .filter(|unit| unit.is_class())
2102 {
2103 template_classes += 1;
2104 if cpp_template_metadata.contains_key(unit) {
2105 continue;
2106 }
2107 if let Some(metadata) = cpp.template_metadata(unit) {
2108 cpp_template_metadata.insert(unit.clone(), metadata);
2109 }
2110 }
2111 if report_stats {
2112 eprintln!(
2113 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2114 template_classes,
2115 cpp_template_metadata.len(),
2116 metadata_started.elapsed().as_millis(),
2117 );
2118 }
2119 let families_started = Instant::now();
2120 if report_stats {
2121 eprintln!(
2122 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2123 );
2124 }
2125 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2126 for (unit, metadata) in &cpp_template_metadata {
2127 cpp_template_families
2128 .entry(metadata.primary_fq_name.clone())
2129 .or_default()
2130 .push(unit.clone());
2131 }
2132 for family in cpp_template_families.values_mut() {
2141 sort_lookup_units(family);
2142 }
2143 if report_stats {
2144 eprintln!(
2145 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2146 cpp_template_families.len(),
2147 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2148 families_started.elapsed().as_millis(),
2149 );
2150 eprintln!(
2151 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2152 visible_by_file.len(),
2153 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2154 finalize_started.elapsed().as_millis(),
2155 visibility_started.elapsed().as_millis(),
2156 );
2157 }
2158 Self {
2159 cpp,
2160 token,
2161 visible_by_file,
2162 visible_by_identifier,
2163 global_field_internal_linkage,
2164 visible_source_files_by_root,
2165 alias_cells: Mutex::new(HashMap::default()),
2166 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2167 parser_alias_target_matches: RwLock::new(HashMap::default()),
2168 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2169 project_using_index: OnceLock::new(),
2170 callable_reference_specs: Mutex::new(HashMap::default()),
2171 structured_include_fact_cells: Mutex::new(HashMap::default()),
2172 include_activation_cells: Mutex::new(HashMap::default()),
2173 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2174 include_path_admission_cells: Mutex::new(HashMap::default()),
2175 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2176 #[cfg(any(test, feature = "test-support"))]
2177 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2178 #[cfg(any(test, feature = "test-support"))]
2179 conditional_include_projection_state_count: AtomicUsize::new(0),
2180 #[cfg(any(test, feature = "test-support"))]
2181 conditional_include_target_state_count: AtomicUsize::new(0),
2182 #[cfg(any(test, feature = "test-support"))]
2183 include_activation_build_count: AtomicUsize::new(0),
2184 #[cfg(any(test, feature = "test-support"))]
2185 using_donor_activation_count: AtomicUsize::new(0),
2186 #[cfg(any(test, feature = "test-support"))]
2187 using_namespace_lookup_count: AtomicUsize::new(0),
2188 #[cfg(any(test, feature = "test-support"))]
2189 using_name_candidate_inspection_count: AtomicUsize::new(0),
2190 #[cfg(any(test, feature = "test-support"))]
2191 callable_reference_spec_build_count: AtomicUsize::new(0),
2192 #[cfg(any(test, feature = "test-support"))]
2193 alias_source_parse_counts: Mutex::new(HashMap::default()),
2194 #[cfg(any(test, feature = "test-support"))]
2195 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2196 parser_alias_fallback_calls: AtomicUsize::new(0),
2197 parser_alias_fallback_files: AtomicUsize::new(0),
2198 parser_alias_source_parses: AtomicUsize::new(0),
2199 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2200 field_type_facts: Mutex::new(HashMap::default()),
2201 structured_alias_targets: Mutex::new(HashMap::default()),
2202 callable_comparables: Mutex::new(HashMap::default()),
2203 comparable_name_declarations: Mutex::new(HashMap::default()),
2204 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2205 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2206 precise_parent_cache: Mutex::new(HashMap::default()),
2207 c_tag_kind_cache: Mutex::new(HashMap::default()),
2208 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2209 macro_event_cells: Mutex::new(HashMap::default()),
2210 macro_event_name_sets: Mutex::new(HashMap::default()),
2211 macro_include_protection_cells: Mutex::new(HashMap::default()),
2212 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2213 macro_replacements: Mutex::new(HashMap::default()),
2214 macro_local_binding_templates: Mutex::new(HashMap::default()),
2215 macro_lexical_templates: Mutex::new(HashMap::default()),
2216 macro_replacement_bodies: Mutex::new(HashMap::default()),
2217 macro_type_parameters: Mutex::new(HashMap::default()),
2218 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2219 #[cfg(any(test, feature = "test-support"))]
2220 macro_replacement_parse_count: AtomicUsize::new(0),
2221 #[cfg(any(test, feature = "test-support"))]
2222 macro_event_application_count: AtomicUsize::new(0),
2223 #[cfg(any(test, feature = "test-support"))]
2224 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2225 #[cfg(any(test, feature = "test-support"))]
2226 macro_environment_copy_count: AtomicUsize::new(0),
2227 #[cfg(any(test, feature = "test-support"))]
2228 macro_environment_request_count: AtomicUsize::new(0),
2229 cpp_template_metadata,
2230 cpp_template_families,
2231 #[cfg(any(test, feature = "test-support"))]
2232 qualified_candidate_inspections: AtomicUsize::new(0),
2233 #[cfg(any(test, feature = "test-support"))]
2234 target_preserving_type_resolution_count: AtomicUsize::new(0),
2235 #[cfg(any(test, feature = "test-support"))]
2236 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2237 #[cfg(any(test, feature = "test-support"))]
2238 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2239 }
2240 }
2241
2242 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2243 if file == target.source() {
2244 return true;
2245 }
2246 if self.global_field_has_internal_linkage(target) {
2247 return self
2248 .visible_source_files_by_root
2249 .get(file)
2250 .is_some_and(|sources| sources.contains(target.source()));
2251 }
2252 self.visible_by_file
2253 .get(file)
2254 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2255 }
2256
2257 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2258 self.global_field_internal_linkage
2259 .get(unit)
2260 .copied()
2261 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2262 }
2263
2264 pub fn call_arity_evidence(
2265 &self,
2266 file: &ProjectFile,
2267 call: Node<'_>,
2268 source: &str,
2269 ) -> CallArityEvidence {
2270 self.call_arity_evidence_at(file, call, source, call.start_byte())
2271 }
2272
2273 pub fn call_arity_evidence_at(
2281 &self,
2282 file: &ProjectFile,
2283 call: Node<'_>,
2284 source: &str,
2285 environment_byte: usize,
2286 ) -> CallArityEvidence {
2287 let Some(arguments) = call
2288 .child_by_field_name("arguments")
2289 .or_else(|| call.child_by_field_name("parameters"))
2290 .or_else(|| call.child_by_field_name("value"))
2291 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2292 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2293 else {
2294 return CallArityEvidence::Exact(0);
2295 };
2296 let recovered_c_keyword_arguments =
2297 recovered_c_keyword_argument_count(file, call, arguments, source);
2298 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2299 let arguments = argument_children(arguments)
2300 .flat_map(|argument| {
2301 recovered_c_new_expression_arguments(argument, c_semantics)
2302 .map(Vec::from)
2303 .unwrap_or_else(|| vec![argument])
2304 })
2305 .collect::<Vec<_>>();
2306 if arguments
2307 .iter()
2308 .all(|argument| !argument_shape_may_change_arity(*argument))
2309 {
2310 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2311 }
2312 let environment = self.macro_environment(file, environment_byte);
2313 let mut stack = Vec::new();
2314 let mut total = recovered_c_keyword_arguments;
2315 for argument in arguments {
2316 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2317 return CallArityEvidence::Unknown;
2318 }
2319 let CallArityEvidence::Exact(spread) =
2320 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2321 else {
2322 return CallArityEvidence::Unknown;
2323 };
2324 total += spread;
2325 }
2326 CallArityEvidence::Exact(total)
2327 }
2328
2329 fn argument_arity_evidence(
2330 &self,
2331 argument: Node<'_>,
2332 source: &str,
2333 environment: &MacroEnvironment,
2334 stack: &mut Vec<(ProjectFile, usize)>,
2335 ) -> CallArityEvidence {
2336 let (name, invocation_arguments, function_like) = match argument.kind() {
2337 "identifier" => (node_text(argument, source), None, false),
2338 "call_expression" => {
2339 let Some(function) = argument.child_by_field_name("function") else {
2340 return CallArityEvidence::Exact(1);
2341 };
2342 if function.kind() != "identifier" {
2343 return CallArityEvidence::Exact(1);
2344 }
2345 let Some(arguments) = argument.child_by_field_name("arguments") else {
2346 return CallArityEvidence::Exact(1);
2347 };
2348 (node_text(function, source), Some(arguments), true)
2349 }
2350 _ => return CallArityEvidence::Exact(1),
2351 };
2352 let Some(binding) = environment.binding(name) else {
2353 return if environment.unknown_names {
2354 CallArityEvidence::Unknown
2355 } else {
2356 CallArityEvidence::Exact(1)
2357 };
2358 };
2359 if !binding.is_exact() {
2360 return CallArityEvidence::Unknown;
2361 }
2362 match (&binding.definition, invocation_arguments, function_like) {
2363 (MacroDefinition::Object { replacement }, None, false) => self
2364 .replacement_arity_evidence(
2365 replacement,
2366 &[],
2367 &[],
2368 source,
2369 environment,
2370 stack,
2371 binding,
2372 ),
2373 (
2374 MacroDefinition::Function {
2375 parameters,
2376 replacement,
2377 },
2378 Some(arguments),
2379 true,
2380 ) => {
2381 let actuals = argument_children(arguments).collect::<Vec<_>>();
2382 if actuals.len() != parameters.len() {
2383 CallArityEvidence::Unknown
2384 } else {
2385 self.replacement_arity_evidence(
2386 replacement,
2387 parameters,
2388 &actuals,
2389 source,
2390 environment,
2391 stack,
2392 binding,
2393 )
2394 }
2395 }
2396 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2397 _ => CallArityEvidence::Unknown,
2398 }
2399 }
2400
2401 #[allow(clippy::too_many_arguments)]
2402 fn replacement_arity_evidence(
2403 &self,
2404 replacement: &str,
2405 parameters: &[String],
2406 actuals: &[Node<'_>],
2407 actual_source: &str,
2408 environment: &MacroEnvironment,
2409 stack: &mut Vec<(ProjectFile, usize)>,
2410 binding: &MacroBinding,
2411 ) -> CallArityEvidence {
2412 let identity = (binding.source.clone(), binding.declaration_byte);
2413 if stack.contains(&identity) || replacement.trim().is_empty() {
2414 return CallArityEvidence::Unknown;
2415 }
2416 stack.push(identity);
2417 let parsed = self.parsed_macro_replacement(binding, replacement);
2418 let evidence = (|| {
2419 let ParsedMacroReplacement::Parsed {
2420 source: sentinel,
2421 tree,
2422 } = parsed.as_ref()
2423 else {
2424 return None;
2425 };
2426 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2427 let arguments = call.child_by_field_name("arguments")?;
2428 let mut total = 0usize;
2429 for argument in argument_children(arguments) {
2430 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2431 return None;
2432 }
2433 if argument.kind() == "identifier"
2434 && let Some(parameter_index) = parameters
2435 .iter()
2436 .position(|parameter| parameter == node_text(argument, sentinel))
2437 {
2438 if !macro_expansion_shape_is_safe(
2439 actuals[parameter_index],
2440 actual_source,
2441 &[],
2442 environment,
2443 ) {
2444 return None;
2445 }
2446 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2447 actuals[parameter_index],
2448 actual_source,
2449 environment,
2450 stack,
2451 ) else {
2452 return None;
2453 };
2454 total += spread;
2455 continue;
2456 }
2457 let CallArityEvidence::Exact(spread) =
2458 self.argument_arity_evidence(argument, sentinel, environment, stack)
2459 else {
2460 return None;
2461 };
2462 total += spread;
2463 }
2464 Some(CallArityEvidence::Exact(total))
2465 })()
2466 .unwrap_or(CallArityEvidence::Unknown);
2467 stack.pop();
2468 evidence
2469 }
2470
2471 fn parsed_macro_replacement(
2472 &self,
2473 binding: &MacroBinding,
2474 replacement: &str,
2475 ) -> Arc<ParsedMacroReplacement> {
2476 let key = (binding.source.clone(), binding.declaration_byte);
2477 let mut cache = self
2478 .macro_replacements
2479 .lock()
2480 .expect("C++ macro replacement cache poisoned");
2481 if let Some(parsed) = cache.get(&key) {
2482 return Arc::clone(parsed);
2483 }
2484 #[cfg(any(test, feature = "test-support"))]
2485 self.macro_replacement_parse_count
2486 .fetch_add(1, Ordering::Relaxed);
2487 let source =
2488 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2489 let mut parser = Parser::new();
2490 let parsed = parser
2491 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2492 .ok()
2493 .and_then(|()| parser.parse(&source, None))
2494 .filter(|tree| !tree.root_node().has_error())
2495 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2496 ParsedMacroReplacement::Parsed { source, tree }
2497 });
2498 let parsed = Arc::new(parsed);
2499 cache.insert(key, Arc::clone(&parsed));
2500 parsed
2501 }
2502
2503 pub fn function_macro_local_binding<'tree>(
2513 &self,
2514 file: &ProjectFile,
2515 statement: Node<'tree>,
2516 source: &str,
2517 ) -> Option<MacroLocalBinding<'tree>> {
2518 if !is_c_source_file(file) {
2519 return None;
2520 }
2521 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2522 return Some(binding);
2523 }
2524 let call = match statement.kind() {
2525 "call_expression" => statement,
2526 "expression_statement" if statement.named_child_count() == 1 => {
2527 statement.named_child(0)?
2528 }
2529 _ => return None,
2530 };
2531 if call.kind() != "call_expression" {
2532 return None;
2533 }
2534 let function = call.child_by_field_name("function")?;
2535 if function.kind() != "identifier" {
2536 return None;
2537 }
2538 let arguments = call.child_by_field_name("arguments")?;
2539 let actuals = argument_children(arguments).collect::<Vec<_>>();
2540 let environment = self.macro_environment(file, call.start_byte());
2541 let function_name = node_text(function, source);
2542 let binding = environment.binding(function_name)?;
2543 let MacroDefinition::Function {
2544 parameters,
2545 replacement,
2546 } = &binding.definition
2547 else {
2548 return None;
2549 };
2550 if actuals.len() != parameters.len() {
2551 return None;
2552 }
2553 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2554 let (type_name, type_node) = match &template.declared_type {
2555 MacroLocalBindingTypeTemplate::Parameter(index) => {
2556 let actual = *actuals.get(*index)?;
2557 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2558 return None;
2559 }
2560 (node_text(actual, source).trim().to_string(), Some(actual))
2561 }
2562 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2563 };
2564 if type_name.is_empty() {
2565 return None;
2566 }
2567 Some(MacroLocalBinding {
2568 name: template.name.clone(),
2569 type_name,
2570 type_node,
2571 pointer_depth: template.pointer_depth,
2572 proven_unit: None,
2573 })
2574 }
2575
2576 pub fn function_macro_container_binding<'tree>(
2585 &self,
2586 analyzer: &CppGraphSource<'_>,
2587 file: &ProjectFile,
2588 assignment: Node<'tree>,
2589 source: &str,
2590 ) -> Option<MacroLocalBinding<'tree>> {
2591 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2592 return None;
2593 }
2594 let name_node = assignment.child_by_field_name("left")?;
2595 if name_node.kind() != "identifier" {
2596 return None;
2597 }
2598 let call = assignment.child_by_field_name("right")?;
2599 if call.kind() != "call_expression" {
2600 return None;
2601 }
2602 let function = call.child_by_field_name("function")?;
2603 if function.kind() != "identifier" {
2604 return None;
2605 }
2606 let arguments = call.child_by_field_name("arguments")?;
2607 let actuals = argument_children(arguments).collect::<Vec<_>>();
2608 let function_name = node_text(function, source);
2609 let environment = self.macro_environment(file, call.start_byte());
2610 let binding = environment.binding(function_name)?;
2611 if !binding.is_exact() {
2612 return None;
2613 }
2614 let MacroDefinition::Function {
2615 parameters,
2616 replacement,
2617 } = &binding.definition
2618 else {
2619 return None;
2620 };
2621 if actuals.len() != parameters.len() {
2622 return None;
2623 }
2624 let body = self.parsed_macro_replacement_body(
2625 &(binding.source.clone(), binding.declaration_byte),
2626 parameters,
2627 replacement,
2628 )?;
2629 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2630 let type_argument = *actuals.get(type_parameter)?;
2631 let type_node = macro_type_argument_node(type_argument, source)?;
2632 let type_name = node_text(type_node, source).trim().to_string();
2633 if type_name.is_empty() {
2634 return None;
2635 }
2636 let explicit_tag = match node_text(type_argument, source) {
2637 "struct" => Some(CppCTagKind::Struct),
2638 "union" => Some(CppCTagKind::Union),
2639 _ => None,
2640 };
2641 let resolved_type = if let Some(tag) = explicit_tag {
2642 let candidates = self
2643 .visible_identifier_candidates(file, &type_name)
2644 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2645 .collect::<Vec<_>>();
2646 self.resolve_type_candidates(
2647 analyzer,
2648 file,
2649 &candidates,
2650 TypeCandidateResolution::Canonical,
2651 )
2652 .ok()
2653 } else {
2654 self.resolve_type_node_result(file, type_node, source)
2655 .ok()
2656 .flatten()
2657 };
2658 let proven_unit = resolved_type?;
2659 Some(MacroLocalBinding {
2660 name: node_text(name_node, source).to_string(),
2661 type_name,
2662 type_node: Some(type_node),
2663 pointer_depth: 1,
2664 proven_unit: Some(proven_unit),
2665 })
2666 }
2667
2668 pub fn macro_local_binding_at<'tree>(
2671 &self,
2672 file: &ProjectFile,
2673 root: Node<'tree>,
2674 source: &str,
2675 start_byte: usize,
2676 end_byte: usize,
2677 ) -> Option<MacroLocalBinding<'tree>> {
2678 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2679 }
2680
2681 pub fn macro_lexical_binding(
2685 &self,
2686 file: &ProjectFile,
2687 root: Node<'_>,
2688 source: &str,
2689 start_byte: usize,
2690 end_byte: usize,
2691 ) -> Option<MacroLexicalBinding> {
2692 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2693 }
2694
2695 pub fn macro_lexical_references(
2700 &self,
2701 file: &ProjectFile,
2702 root: Node<'_>,
2703 source: &str,
2704 max_references: usize,
2705 cancelled: impl FnMut() -> bool,
2706 ) -> MacroLexicalReferences {
2707 crate::graph::macro_lexical::all_references(
2708 || self,
2709 file,
2710 root,
2711 source,
2712 max_references,
2713 cancelled,
2714 )
2715 }
2716
2717 pub(crate) fn function_macro_binding_at(
2722 &self,
2723 file: &ProjectFile,
2724 name: &str,
2725 before_byte: usize,
2726 ) -> Option<(ProjectFile, usize)> {
2727 let environment = self.macro_environment(file, before_byte);
2728 let binding = environment.binding(name)?;
2729 if binding.is_exact()
2730 && matches!(
2731 binding.definition,
2732 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2733 )
2734 {
2735 return Some((binding.source.clone(), binding.declaration_byte));
2736 }
2737 if binding.source != *file {
2743 return None;
2744 }
2745 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2746 let root = prepared.tree().root_node();
2747 let source = prepared.source();
2748 let reference = root.descendant_for_byte_range(
2749 before_byte,
2750 before_byte.saturating_add(1).min(source.len()),
2751 )?;
2752 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2753 let cell = self.macro_event_cell(file);
2754 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2755 let event = events
2756 .iter()
2757 .find(|event| event.byte() == binding.declaration_byte)?;
2758 let MacroEvent::Define {
2759 name: defined_name,
2760 binding: definition,
2761 conditionals,
2762 ..
2763 } = event
2764 else {
2765 return None;
2766 };
2767 if defined_name != name
2768 || conditionals.is_empty()
2769 || !conditionals
2770 .iter()
2771 .all(|condition| reference_conditions.contains(condition))
2772 || !matches!(
2773 definition.definition,
2774 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2775 )
2776 {
2777 return None;
2778 }
2779 Some((definition.source.clone(), definition.declaration_byte))
2780 }
2781
2782 pub fn function_macro_type_argument<'tree>(
2789 &self,
2790 file: &ProjectFile,
2791 node: Node<'tree>,
2792 source: &str,
2793 ) -> Option<Node<'tree>> {
2794 if !is_c_source_file(file) {
2795 return None;
2796 }
2797 let (call, argument_index, argument) = c_function_macro_argument(node)?;
2798 let function = call.child_by_field_name("function")?;
2799 let function_name = node_text(function, source);
2800 if function_name.is_empty() {
2801 return None;
2802 }
2803 if !self.file_defines_macro_name(file, function_name)
2804 && !self
2805 .visible_identifier_candidates(file, function_name)
2806 .any(|candidate| candidate.is_macro())
2807 {
2808 return None;
2809 }
2810 let environment = self.macro_environment(file, call.start_byte());
2811 let binding = environment.binding(function_name)?;
2812 if !binding.is_exact() {
2813 return None;
2814 }
2815 let (parameters, replacement, variadic) = match &binding.definition {
2816 MacroDefinition::Function {
2817 parameters,
2818 replacement,
2819 } => (parameters, replacement, false),
2820 MacroDefinition::VariadicFunction {
2821 parameters,
2822 replacement,
2823 } => (parameters, replacement, true),
2824 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2825 };
2826 let actuals = call
2827 .child_by_field_name("arguments")
2828 .map(argument_children)
2829 .into_iter()
2830 .flatten()
2831 .collect::<Vec<_>>();
2832 let arity_matches = if variadic {
2833 actuals.len() >= parameters.len()
2834 } else {
2835 actuals.len() == parameters.len()
2836 };
2837 let type_parameters = self.macro_type_parameter_indices(
2838 &(binding.source.clone(), binding.declaration_byte),
2839 parameters,
2840 replacement,
2841 )?;
2842 if !arity_matches || !type_parameters.contains(&argument_index) {
2843 return None;
2844 }
2845 let type_node = macro_type_argument_node(argument, source)?;
2846 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2847 return None;
2848 }
2849 Some(type_node)
2850 }
2851
2852 fn macro_local_binding_template(
2853 &self,
2854 binding: &MacroBinding,
2855 parameters: &[String],
2856 replacement: &str,
2857 ) -> Option<Arc<MacroLocalBindingTemplate>> {
2858 let key = (binding.source.clone(), binding.declaration_byte);
2859 if let Some(template) = self
2860 .macro_local_binding_templates
2861 .lock()
2862 .expect("C++ macro local-binding cache poisoned")
2863 .get(&key)
2864 {
2865 return template.clone();
2866 }
2867 let template = (|| {
2868 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2869 let sentinel = body.source.as_str();
2870 let statements = body.statements()?;
2871 if statements.named_child_count() != 1 {
2872 return None;
2873 }
2874 let declaration = statements.named_child(0)?;
2875 if declaration.kind() != "declaration" {
2876 return None;
2877 }
2878 let type_node = declaration
2879 .child_by_field_name("type")
2880 .or_else(|| first_type_child(declaration))?;
2881 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2882 let mut cursor = declaration.walk();
2883 declaration.named_children(&mut cursor).find_map(|child| {
2884 if child.kind() == "init_declarator" {
2885 child.child_by_field_name("declarator")
2886 } else {
2887 is_declarator_node(child).then_some(child)
2888 }
2889 })
2890 })?;
2891 let name = extract_variable_name(declarator, sentinel)?;
2892 let pointer_depth = declared_name_indirection(declaration, type_node, &name, sentinel)?;
2893 let type_text = node_text(type_node, sentinel).trim();
2894 let declared_type = parameters
2895 .iter()
2896 .position(|parameter| parameter == type_text)
2897 .map(MacroLocalBindingTypeTemplate::Parameter)
2898 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2899 Some(Arc::new(MacroLocalBindingTemplate {
2900 name,
2901 declared_type,
2902 pointer_depth,
2903 }))
2904 })();
2905 self.macro_local_binding_templates
2906 .lock()
2907 .expect("C++ macro local-binding cache poisoned")
2908 .insert(key, template.clone());
2909 template
2910 }
2911
2912 pub fn function_macro_replacement_body(
2919 &self,
2920 file: &ProjectFile,
2921 definition: Node<'_>,
2922 source: &str,
2923 ) -> Option<Arc<ParsedReplacementBody>> {
2924 debug_assert_eq!(definition.kind(), "preproc_function_def");
2925 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
2926 MacroDefinition::Function {
2927 parameters,
2928 replacement,
2929 }
2930 | MacroDefinition::VariadicFunction {
2931 parameters,
2932 replacement,
2933 } => (parameters, replacement),
2934 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2935 };
2936 self.parsed_macro_replacement_body(
2937 &(file.clone(), definition.start_byte()),
2938 ¶meters,
2939 &replacement,
2940 )
2941 }
2942
2943 fn parsed_macro_replacement_body(
2951 &self,
2952 key: &(ProjectFile, usize),
2953 parameters: &[String],
2954 replacement: &str,
2955 ) -> Option<Arc<ParsedReplacementBody>> {
2956 if let Some(body) = self
2957 .macro_replacement_bodies
2958 .lock()
2959 .expect("C++ macro replacement body cache poisoned")
2960 .get(key)
2961 {
2962 return body.clone();
2963 }
2964 let body = (|| {
2965 if replacement.trim().is_empty() {
2966 return None;
2967 }
2968 let (source, tree, original_offsets) =
2969 Self::parse_macro_replacement_body(replacement, parameters)?;
2970 let body = ParsedReplacementBody {
2971 source,
2972 tree,
2973 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2974 parameters: parameters.to_vec(),
2975 original_offsets,
2976 };
2977 body.statements()?;
2978 if body.expands_variadic_arguments() {
2979 return None;
2980 }
2981 Some(Arc::new(body))
2982 })();
2983 self.macro_replacement_bodies
2984 .lock()
2985 .expect("C++ macro replacement body cache poisoned")
2986 .insert(key.clone(), body.clone());
2987 body
2988 }
2989
2990 fn parse_macro_replacement_body(
2998 replacement: &str,
2999 parameters: &[String],
3000 ) -> Option<(String, Tree, Box<[usize]>)> {
3001 let normalized = normalize_macro_continuations(replacement);
3002 let parse = |replacement: &str| {
3003 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3004 let mut parser = Parser::new();
3005 parser
3006 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3007 .ok()?;
3008 let tree = parser.parse(&source, None)?;
3009 Some((source, tree))
3010 };
3011 let (source, tree) = parse(&normalized)?;
3012 if !tree.root_node().has_error() {
3013 let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3014 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3015 return Some((source, tree, original_offsets.into_boxed_slice()));
3016 }
3017
3018 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3019 let mut insertion_points = Vec::new();
3020 let mut stack = vec![tree.root_node()];
3021 while let Some(node) = stack.pop() {
3022 if matches!(
3023 node.kind(),
3024 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3025 ) && parameters
3026 .iter()
3027 .any(|parameter| parameter.as_str() == node_text(node, &source))
3028 && Self::macro_formal_needs_statement_separator(node)
3029 {
3030 let point = node.end_byte().saturating_sub(body_offset);
3031 if point <= normalized.len() && !insertion_points.contains(&point) {
3032 insertion_points.push(point);
3033 }
3034 }
3035 push_named_children_reversed(node, &mut stack);
3036 }
3037 if insertion_points.is_empty() {
3038 return None;
3039 }
3040 insertion_points.sort_unstable();
3041 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3042 let mut original_offsets =
3043 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3044 let mut next_insertion = 0;
3045 for (index, byte) in normalized.bytes().enumerate() {
3046 recovered.push(byte);
3047 original_offsets.push(index);
3048 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3049 recovered.push(b';');
3050 original_offsets.push(index + 1);
3051 next_insertion += 1;
3052 }
3053 }
3054 original_offsets.push(normalized.len());
3055 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3056 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3057 let (source, tree) = parse(&recovered)?;
3058 if tree.root_node().has_error() {
3059 return None;
3060 }
3061 Some((source, tree, original_offsets.into_boxed_slice()))
3062 }
3063
3064 fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3069 offsets.extend([replacement_end; 3]);
3070 }
3071
3072 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3077 let mut current = node;
3078 let mut crossed_recovery = false;
3079 while let Some(parent) = current.parent() {
3080 if parent.is_error() {
3081 crossed_recovery = true;
3082 current = parent;
3083 continue;
3084 }
3085 if matches!(
3086 parent.kind(),
3087 "call_expression"
3088 | "argument_list"
3089 | "field_expression"
3090 | "binary_expression"
3091 | "unary_expression"
3092 | "assignment_expression"
3093 | "conditional_expression"
3094 | "parenthesized_expression"
3095 | "subscript_expression"
3096 ) {
3097 return false;
3098 }
3099 if parent.kind() == "expression_statement" {
3100 return parent.named_child_count() == 1;
3101 }
3102 if parent.kind() == "compound_statement" && current == node {
3103 return true;
3104 }
3105 if matches!(
3106 parent.kind(),
3107 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3108 ) {
3109 return crossed_recovery
3110 || parent.child_by_field_name("consequence") == Some(current);
3111 }
3112 if matches!(parent.kind(), "declaration" | "init_declarator") {
3113 return false;
3114 }
3115 current = parent;
3116 }
3117 false
3118 }
3119
3120 fn macro_type_parameter_indices(
3128 &self,
3129 key: &(ProjectFile, usize),
3130 parameters: &[String],
3131 replacement: &str,
3132 ) -> Option<Arc<[usize]>> {
3133 if let Some(indices) = self
3134 .macro_type_parameters
3135 .lock()
3136 .expect("C++ macro type-parameter cache poisoned")
3137 .get(key)
3138 {
3139 return indices.clone();
3140 }
3141 #[cfg(any(test, feature = "test-support"))]
3142 self.macro_replacement_parse_count
3143 .fetch_add(1, Ordering::Relaxed);
3144 let indices = (|| {
3145 if replacement.trim().is_empty() {
3146 return None;
3147 }
3148 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3152 let mut parser = Parser::new();
3153 parser
3154 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3155 .ok()?;
3156 let tree = parser.parse(&source, None)?;
3157 let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3158 Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3159 let body = ParsedReplacementBody {
3160 source,
3161 tree,
3162 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3163 parameters: parameters.to_vec(),
3164 original_offsets: original_offsets.into_boxed_slice(),
3165 };
3166 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3167 })();
3168 self.macro_type_parameters
3169 .lock()
3170 .expect("C++ macro type-parameter cache poisoned")
3171 .insert(key.clone(), indices.clone());
3172 indices
3173 }
3174
3175 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3176 let replacement = if node.kind() == "preproc_function_def" {
3177 function_macro_replacement_span(node, source)
3178 .and_then(|span| source.get(span))
3179 .map(str::to_owned)
3180 .or_else(|| {
3181 node.child_by_field_name("value")
3182 .map(|value| node_text(value, source).to_string())
3183 })
3184 .unwrap_or_default()
3185 } else {
3186 node.child_by_field_name("value")
3187 .map(|value| node_text(value, source).to_string())
3188 .unwrap_or_default()
3189 };
3190 if node.kind() == "preproc_def" {
3191 return MacroDefinition::Object { replacement };
3192 }
3193 let Some(parameters) = node.child_by_field_name("parameters") else {
3194 return MacroDefinition::Unsupported;
3195 };
3196 let variadic = (0..parameters.child_count()).any(|index| {
3197 parameters
3198 .child(index)
3199 .is_some_and(|child| child.kind() == "...")
3200 });
3201 let parameters = (0..parameters.named_child_count())
3202 .filter_map(|index| parameters.named_child(index))
3203 .map(|parameter| node_text(parameter, source).to_string())
3204 .collect::<Vec<_>>();
3205 if variadic {
3206 MacroDefinition::VariadicFunction {
3207 parameters,
3208 replacement,
3209 }
3210 } else {
3211 MacroDefinition::Function {
3212 parameters,
3213 replacement,
3214 }
3215 }
3216 }
3217
3218 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3219 self.macro_event_cells
3220 .lock()
3221 .expect("C++ macro event cache poisoned")
3222 .entry(file.clone())
3223 .or_default()
3224 .clone()
3225 }
3226
3227 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3228 if let Some(names) = self
3229 .macro_event_name_sets
3230 .lock()
3231 .expect("C++ macro event-name cache poisoned")
3232 .get(file)
3233 .cloned()
3234 {
3235 return names.contains(name);
3236 }
3237 let cell = self.macro_event_cell(file);
3238 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3239 let names = Arc::new(
3240 events
3241 .iter()
3242 .filter_map(|event| match event {
3243 MacroEvent::Define { name, .. } => Some(name.clone()),
3244 MacroEvent::Undef { .. }
3245 | MacroEvent::Include { .. }
3246 | MacroEvent::Invalidate { .. } => None,
3247 })
3248 .collect(),
3249 );
3250 self.macro_event_name_sets
3251 .lock()
3252 .expect("C++ macro event-name cache poisoned")
3253 .insert(file.clone(), Arc::clone(&names));
3254 names.contains(name)
3255 }
3256
3257 fn macro_environment_checkpoint_cell(
3258 &self,
3259 file: &ProjectFile,
3260 ) -> MacroEnvironmentCheckpointCell {
3261 self.macro_environment_checkpoints
3262 .lock()
3263 .expect("C++ macro environment checkpoint cache poisoned")
3264 .entry(file.clone())
3265 .or_default()
3266 .clone()
3267 }
3268
3269 pub fn macro_environment(
3271 &self,
3272 file: &ProjectFile,
3273 before_byte: usize,
3274 ) -> Arc<MacroEnvironment> {
3275 #[cfg(any(test, feature = "test-support"))]
3276 self.macro_environment_request_count
3277 .fetch_add(1, Ordering::Relaxed);
3278 let cell = self.macro_event_cell(file);
3279 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3280 let frontier = events.partition_point(|event| event.byte() < before_byte);
3281 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3282 let checkpoints =
3283 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3284 let checkpoint = checkpoints.at_or_before(frontier);
3285 if checkpoint.frontier == frontier {
3286 return Arc::clone(&checkpoint.environment);
3287 }
3288 #[cfg(any(test, feature = "test-support"))]
3289 self.macro_environment_copy_count
3290 .fetch_add(1, Ordering::Relaxed);
3291 let mut environment = checkpoint.environment.as_ref().clone();
3292 let mut include_stack = HashSet::from_iter([file.clone()]);
3293 for event in &events[checkpoint.frontier..frontier] {
3294 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3295 }
3296 Arc::new(environment)
3297 }
3298
3299 fn build_macro_environment_checkpoints(
3302 &self,
3303 file: &ProjectFile,
3304 events: &[MacroEvent],
3305 ) -> MacroEnvironmentCheckpoints {
3306 #[cfg(any(test, feature = "test-support"))]
3307 self.macro_environment_checkpoint_build_count
3308 .fetch_add(1, Ordering::Relaxed);
3309 let mut environment = MacroEnvironment {
3314 build_proven_defines: self
3315 .compile_proven_guards(file)
3316 .iter()
3317 .filter_map(|guard| match guard {
3318 PreprocessorGuard::Defined(name) => Some(name.clone()),
3319 _ => None,
3320 })
3321 .collect(),
3322 ..MacroEnvironment::default()
3323 };
3324 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3325 frontier: 0,
3326 environment: Arc::new(environment.clone()),
3327 }];
3328 let checkpoint_stride = events
3335 .len()
3336 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3337 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3338 let mut include_stack = HashSet::from_iter([file.clone()]);
3339 for (index, event) in events.iter().enumerate() {
3340 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3341 let frontier = index + 1;
3342 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3343 checkpoints.push(MacroEnvironmentCheckpoint {
3344 frontier,
3345 environment: Arc::new(environment.clone()),
3346 });
3347 }
3348 }
3349 MacroEnvironmentCheckpoints { checkpoints }
3350 }
3351
3352 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3361 self.macro_environment(file, before_byte)
3362 .binding(name)
3363 .is_some()
3364 }
3365
3366 pub fn macro_name_may_be_bound_at(
3367 &self,
3368 file: &ProjectFile,
3369 name: &str,
3370 before_byte: usize,
3371 ) -> bool {
3372 self.macro_environment(file, before_byte).may_bind(name)
3373 }
3374
3375 pub fn macro_binding_matches_target_at(
3379 &self,
3380 analyzer: &CppGraphSource<'_>,
3381 file: &ProjectFile,
3382 name: &str,
3383 before_byte: usize,
3384 target: &CodeUnit,
3385 ) -> bool {
3386 let ranges = analyzer.ranges(target);
3387 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3388 self.macro_binding_matches_target_declaration_at(
3389 file,
3390 name,
3391 before_byte,
3392 target.source(),
3393 &declaration_bytes,
3394 )
3395 }
3396
3397 pub(crate) fn macro_declaration_bytes(
3402 &self,
3403 target: &CodeUnit,
3404 ranges: &[Range],
3405 ) -> Vec<usize> {
3406 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3407 return Vec::new();
3408 };
3409 ranges
3410 .iter()
3411 .filter_map(|range| {
3412 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3413 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3414 node = node.parent()?;
3415 }
3416 Some(node.start_byte())
3417 })
3418 .collect()
3419 }
3420
3421 pub(crate) fn macro_binding_matches_target_declaration_at(
3422 &self,
3423 file: &ProjectFile,
3424 name: &str,
3425 before_byte: usize,
3426 target_source: &ProjectFile,
3427 target_declaration_bytes: &[usize],
3428 ) -> bool {
3429 let environment = self.macro_environment(file, before_byte);
3430 let Some(binding) = environment.binding(name) else {
3431 return false;
3432 };
3433 if binding.definition == MacroDefinition::Unsupported {
3434 return false;
3435 }
3436 if binding.source != *target_source {
3440 return false;
3441 }
3442 target_declaration_bytes.contains(&binding.declaration_byte)
3443 }
3444
3445 pub fn resolve_ordinary_macro_reference(
3452 &self,
3453 analyzer: &CppGraphSource<'_>,
3454 file: &ProjectFile,
3455 node: Node<'_>,
3456 source: &str,
3457 ) -> OrdinaryMacroReferenceResolution {
3458 if !is_ordinary_macro_reference_node(node) {
3459 return OrdinaryMacroReferenceResolution::Missing;
3460 }
3461 let name = node_text(node, source);
3462 if name.is_empty() {
3463 return OrdinaryMacroReferenceResolution::Missing;
3464 }
3465 let visible = self
3466 .visible_identifier_candidates(file, name)
3467 .filter(|candidate| candidate.is_macro())
3468 .cloned()
3469 .collect::<Vec<_>>();
3470 let mut exact = Vec::new();
3471 for candidate in &visible {
3472 if self.macro_binding_matches_target_at(
3473 analyzer,
3474 file,
3475 name,
3476 node.start_byte(),
3477 candidate,
3478 ) && !exact
3479 .iter()
3480 .any(|existing| same_visible_symbol(existing, candidate))
3481 {
3482 exact.push(candidate.clone());
3483 }
3484 }
3485 match exact.len() {
3486 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3487 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3488 0 if !visible.is_empty()
3489 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3490 {
3491 OrdinaryMacroReferenceResolution::Ambiguous
3492 }
3493 0 => OrdinaryMacroReferenceResolution::Missing,
3494 }
3495 }
3496
3497 pub fn recovered_c_reference_ranges(
3505 &self,
3506 file: &ProjectFile,
3507 root: Node<'_>,
3508 source: &str,
3509 limit: usize,
3510 ) -> RecoveredCReferenceRanges {
3511 if !is_c_source_file(file) {
3512 return RecoveredCReferenceRanges::Complete(Vec::new());
3513 }
3514 let mut ranges = Vec::new();
3515 let mut seen = HashSet::default();
3516 let mut stack = vec![(root, root.is_error())];
3517 while let Some((node, inside_error)) = stack.pop() {
3518 let inside_error = inside_error || node.is_error();
3519 if node.kind() == "preproc_arg" {
3520 let macro_value_kind = node.parent().and_then(|parent| {
3525 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3526 });
3527 if matches!(
3528 macro_value_kind,
3529 Some("preproc_def" | "preproc_function_def")
3530 ) {
3531 let name = node_text(node, source);
3532 if !name.is_empty()
3533 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3534 && !push_recovered_c_range(
3535 &mut ranges,
3536 &mut seen,
3537 node.start_byte(),
3538 node.end_byte(),
3539 node,
3540 limit,
3541 )
3542 {
3543 return RecoveredCReferenceRanges::LimitExceeded;
3544 }
3545 }
3546 if macro_value_kind == Some("preproc_def") {
3547 for reference in object_macro_replacement_type_references(node, source) {
3548 for range in reference.component_ranges {
3549 let visible = self
3550 .visible_identifier_candidates(file, &source[range.clone()])
3551 .any(|candidate| {
3552 candidate.is_class()
3553 || candidate.is_module()
3554 || is_type_alias(candidate)
3555 });
3556 if visible
3557 && !push_recovered_c_range(
3558 &mut ranges,
3559 &mut seen,
3560 range.start,
3561 range.end,
3562 node,
3563 limit,
3564 )
3565 {
3566 return RecoveredCReferenceRanges::LimitExceeded;
3567 }
3568 }
3569 }
3570 }
3571 }
3572 if inside_error
3573 && recovered_c_reference_node(self, file, node, source)
3574 && !push_recovered_c_range(
3575 &mut ranges,
3576 &mut seen,
3577 node.start_byte(),
3578 node.end_byte(),
3579 node,
3580 limit,
3581 )
3582 {
3583 return RecoveredCReferenceRanges::LimitExceeded;
3584 }
3585 let mut cursor = node.walk();
3586 for child in node.named_children(&mut cursor) {
3587 stack.push((child, inside_error));
3588 }
3589 }
3590 ranges.sort_unstable();
3591 RecoveredCReferenceRanges::Complete(ranges)
3592 }
3593
3594 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3600 self.visible_identifier_candidates(file, target.identifier())
3601 .filter(|candidate| candidate.is_macro())
3602 .any(|candidate| {
3603 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3604 })
3605 }
3606
3607 pub fn object_macro_replacement_at(
3608 &self,
3609 file: &ProjectFile,
3610 name: &str,
3611 before_byte: usize,
3612 ) -> Option<String> {
3613 let environment = self.macro_environment(file, before_byte);
3614 let binding = environment.binding(name)?;
3615 if !binding.exact {
3616 return None;
3617 }
3618 match &binding.definition {
3619 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3620 MacroDefinition::Function { .. }
3621 | MacroDefinition::VariadicFunction { .. }
3622 | MacroDefinition::Unsupported => None,
3623 }
3624 }
3625
3626 fn apply_macro_events(
3627 &self,
3628 file: &ProjectFile,
3629 before_byte: Option<usize>,
3630 environment: &mut MacroEnvironment,
3631 include_stack: &mut HashSet<ProjectFile>,
3632 ) {
3633 if !include_stack.insert(file.clone()) {
3634 return;
3635 }
3636 if self.cpp.prepared_syntax(self.token, file).is_none() {
3637 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3638 include_stack.remove(file);
3639 return;
3640 }
3641 match self.macro_include_protection(file) {
3642 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3643 Some(binding) if binding.is_exact() => {
3644 include_stack.remove(file);
3645 return;
3646 }
3647 Some(_) | None if environment.unknown_names => {
3648 let mut ambiguous_seen = HashSet::default();
3649 self.mark_macro_events_ambiguous(
3650 file,
3651 environment,
3652 &mut ambiguous_seen,
3653 file,
3654 before_byte.unwrap_or_default(),
3655 );
3656 include_stack.remove(file);
3657 return;
3658 }
3659 Some(_) => {
3660 let mut ambiguous_seen = HashSet::default();
3661 self.mark_macro_events_ambiguous(
3662 file,
3663 environment,
3664 &mut ambiguous_seen,
3665 file,
3666 before_byte.unwrap_or_default(),
3667 );
3668 include_stack.remove(file);
3669 return;
3670 }
3671 None => {}
3672 },
3673 MacroIncludeProtection::PragmaOnce => {
3674 if !environment.applied_pragma_once_files.insert(file.clone()) {
3675 include_stack.remove(file);
3676 return;
3677 }
3678 if environment.maybe_applied_pragma_once_files.remove(file) {
3679 let mut ambiguous_seen = HashSet::default();
3684 environment.applied_pragma_once_files.remove(file);
3685 self.mark_macro_events_ambiguous(
3686 file,
3687 environment,
3688 &mut ambiguous_seen,
3689 file,
3690 before_byte.unwrap_or_default(),
3691 );
3692 environment.maybe_applied_pragma_once_files.remove(file);
3693 environment.applied_pragma_once_files.insert(file.clone());
3694 include_stack.remove(file);
3695 return;
3696 }
3697 }
3698 MacroIncludeProtection::None => {}
3699 }
3700 let cell = self.macro_event_cell(file);
3701 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3702 for event in events {
3703 if before_byte.is_some_and(|limit| event.byte() >= limit) {
3704 break;
3705 }
3706 self.apply_macro_event(file, event, environment, include_stack);
3707 }
3708 include_stack.remove(file);
3709 }
3710
3711 fn apply_macro_event(
3712 &self,
3713 file: &ProjectFile,
3714 event: &MacroEvent,
3715 environment: &mut MacroEnvironment,
3716 include_stack: &mut HashSet<ProjectFile>,
3717 ) {
3718 #[cfg(any(test, feature = "test-support"))]
3719 self.macro_event_application_count
3720 .fetch_add(1, Ordering::Relaxed);
3721 match event {
3722 MacroEvent::Define {
3723 name,
3724 binding,
3725 conditionals,
3726 byte,
3727 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3728 Some(true) => environment.insert(name.clone(), binding.clone()),
3729 Some(false) => {}
3730 None => Self::merge_conditional_macro_definition(
3731 environment,
3732 name,
3733 binding,
3734 file,
3735 *byte,
3736 ),
3737 },
3738 MacroEvent::Undef {
3739 name,
3740 conditionals,
3741 byte,
3742 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3743 Some(true) => environment.remove(name),
3744 Some(false) => {}
3745 None => {
3746 if environment.binding(name).is_some() {
3747 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3748 }
3749 }
3750 },
3751 MacroEvent::Include {
3752 targets,
3753 conditionals,
3754 byte,
3755 } => {
3756 let condition =
3757 self.macro_event_condition_value(file, *byte, environment, conditionals);
3758 if condition == Some(false) {
3759 return;
3760 }
3761 if targets.is_empty() {
3762 environment.mark_unknown_names(file, *byte);
3763 return;
3764 }
3765 if condition.is_none() || targets.len() > 1 {
3766 let mut ambiguous_seen = HashSet::default();
3767 for target in targets {
3768 self.mark_macro_events_ambiguous(
3769 target,
3770 environment,
3771 &mut ambiguous_seen,
3772 file,
3773 *byte,
3774 );
3775 }
3776 } else if let Some(target) = targets.first() {
3777 self.apply_macro_events(target, None, environment, include_stack);
3778 }
3779 }
3780 MacroEvent::Invalidate { byte } => {
3781 for binding in environment.bindings.values_mut() {
3782 *binding = MacroBinding::uncertain_from(binding, file, *byte);
3783 }
3784 }
3785 }
3786 }
3787
3788 fn macro_event_condition_value(
3799 &self,
3800 file: &ProjectFile,
3801 event_byte: usize,
3802 environment: &MacroEnvironment,
3803 conditionals: &OwningPreprocessorConditionals,
3804 ) -> Option<bool> {
3805 if conditionals.is_empty() {
3806 return Some(true);
3807 }
3808 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3809 let source = prepared.source();
3810 let root = prepared.tree().root_node();
3811 let descendant = root.descendant_for_byte_range(
3812 event_byte,
3813 event_byte.saturating_add(1).min(source.len()),
3814 )?;
3815 let mut unknown = false;
3816 let mut current = descendant.parent();
3817 while let Some(conditional) = current {
3818 if matches!(
3819 conditional.kind(),
3820 "preproc_if" | "preproc_ifdef" | "preproc_elif"
3821 ) && conditionals.contains(&conditional.start_byte())
3822 {
3823 let mut value = match conditional.kind() {
3824 "preproc_ifdef" => {
3825 let name = conditional.child_by_field_name("name")?;
3826 let defined =
3827 self.macro_name_defined_value(environment, node_text(name, source));
3828 match conditional.child(0)?.kind() {
3829 "#ifdef" => defined,
3830 "#ifndef" => defined.map(|defined| !defined),
3831 _ => None,
3832 }
3833 }
3834 "preproc_if" | "preproc_elif" => conditional
3835 .child_by_field_name("condition")
3836 .and_then(|condition| {
3837 self.preprocessor_integer_value(
3838 condition,
3839 source,
3840 environment,
3841 &mut Vec::new(),
3842 0,
3843 )
3844 })
3845 .map(|value| value != 0),
3846 _ => unreachable!(),
3847 };
3848 if conditional
3849 .child_by_field_name("alternative")
3850 .is_some_and(|alternative| {
3851 alternative.start_byte() <= descendant.start_byte()
3852 && descendant.end_byte() <= alternative.end_byte()
3853 })
3854 {
3855 value = value.map(|value| !value);
3856 }
3857 match value {
3858 Some(true) => {}
3859 Some(false) => return Some(false),
3860 None => unknown = true,
3861 }
3862 }
3863 current = conditional.parent();
3864 }
3865 (!unknown).then_some(true)
3866 }
3867
3868 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3869 if environment.known_undefined_names.contains(name) {
3870 return Some(false);
3871 }
3872 if let Some(binding) = environment.binding(name) {
3873 return binding.is_exact().then_some(true);
3874 }
3875 environment
3876 .build_proven_defines
3877 .contains(name)
3878 .then_some(true)
3879 }
3880
3881 fn preprocessor_integer_value(
3882 &self,
3883 expression: Node<'_>,
3884 source: &str,
3885 environment: &MacroEnvironment,
3886 expansion_stack: &mut Vec<(ProjectFile, usize)>,
3887 depth: usize,
3888 ) -> Option<i128> {
3889 if depth >= 64 {
3892 return None;
3893 }
3894 match expression.kind() {
3895 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3896 "identifier" | "type_identifier" => {
3897 let binding = environment.binding(node_text(expression, source))?;
3898 if !binding.is_exact() {
3899 return None;
3900 }
3901 let MacroDefinition::Object { replacement } = &binding.definition else {
3902 return None;
3903 };
3904 let identity = (binding.source.clone(), binding.declaration_byte);
3905 if expansion_stack.contains(&identity) {
3906 return None;
3907 }
3908 expansion_stack.push(identity);
3909 let parsed = self.parsed_macro_replacement(binding, replacement);
3910 let value = match parsed.as_ref() {
3911 ParsedMacroReplacement::Parsed {
3912 source: replacement_source,
3913 tree,
3914 } => first_descendant_of_kind(tree.root_node(), "call_expression")
3915 .and_then(|call| call.child_by_field_name("arguments"))
3916 .and_then(|arguments| argument_children(arguments).next())
3917 .and_then(|argument| {
3918 self.preprocessor_integer_value(
3919 argument,
3920 replacement_source,
3921 environment,
3922 expansion_stack,
3923 depth + 1,
3924 )
3925 }),
3926 ParsedMacroReplacement::Unsupported => None,
3927 };
3928 expansion_stack.pop();
3929 value
3930 }
3931 "preproc_defined" => {
3932 let mut cursor = expression.walk();
3933 let name = expression
3934 .named_children(&mut cursor)
3935 .find(|child| child.kind() == "identifier")?;
3936 self.macro_name_defined_value(environment, node_text(name, source))
3937 .map(i128::from)
3938 }
3939 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3940 self.preprocessor_integer_value(
3941 child,
3942 source,
3943 environment,
3944 expansion_stack,
3945 depth + 1,
3946 )
3947 }),
3948 "unary_expression" => {
3949 let operator = expression.child_by_field_name("operator")?.kind();
3950 let argument = expression.child_by_field_name("argument")?;
3951 let value = self.preprocessor_integer_value(
3952 argument,
3953 source,
3954 environment,
3955 expansion_stack,
3956 depth + 1,
3957 )?;
3958 match operator {
3959 "+" => Some(value),
3960 "-" => value.checked_neg(),
3961 "!" => Some(i128::from(value == 0)),
3962 "~" => Some(!value),
3963 _ => None,
3964 }
3965 }
3966 "binary_expression" => {
3967 let left = self.preprocessor_integer_value(
3968 expression.child_by_field_name("left")?,
3969 source,
3970 environment,
3971 expansion_stack,
3972 depth + 1,
3973 )?;
3974 let right = self.preprocessor_integer_value(
3975 expression.child_by_field_name("right")?,
3976 source,
3977 environment,
3978 expansion_stack,
3979 depth + 1,
3980 )?;
3981 match expression.child_by_field_name("operator")?.kind() {
3982 "+" => left.checked_add(right),
3983 "-" => left.checked_sub(right),
3984 "*" => left.checked_mul(right),
3985 "/" => left.checked_div(right),
3986 "%" => left.checked_rem(right),
3987 "<<" => u32::try_from(right)
3988 .ok()
3989 .and_then(|shift| left.checked_shl(shift)),
3990 ">>" => u32::try_from(right)
3991 .ok()
3992 .and_then(|shift| left.checked_shr(shift)),
3993 "<" => Some(i128::from(left < right)),
3994 "<=" => Some(i128::from(left <= right)),
3995 ">" => Some(i128::from(left > right)),
3996 ">=" => Some(i128::from(left >= right)),
3997 "==" => Some(i128::from(left == right)),
3998 "!=" => Some(i128::from(left != right)),
3999 "&" => Some(left & right),
4000 "|" => Some(left | right),
4001 "^" => Some(left ^ right),
4002 "&&" => Some(i128::from(left != 0 && right != 0)),
4003 "||" => Some(i128::from(left != 0 || right != 0)),
4004 _ => None,
4005 }
4006 }
4007 _ => None,
4008 }
4009 }
4010
4011 fn mark_macro_events_ambiguous(
4012 &self,
4013 file: &ProjectFile,
4014 environment: &mut MacroEnvironment,
4015 include_stack: &mut HashSet<ProjectFile>,
4016 conditional_file: &ProjectFile,
4017 conditional_byte: usize,
4018 ) {
4019 if !include_stack.insert(file.clone()) {
4020 return;
4021 }
4022 if self.cpp.prepared_syntax(self.token, file).is_none() {
4023 environment.mark_unknown_names(conditional_file, conditional_byte);
4024 return;
4025 }
4026 match self.macro_include_protection(file) {
4027 MacroIncludeProtection::MacroGuard(guard) => {
4028 if environment
4029 .binding(&guard)
4030 .is_some_and(MacroBinding::is_exact)
4031 {
4032 return;
4033 }
4034 }
4035 MacroIncludeProtection::PragmaOnce => {
4036 if environment.applied_pragma_once_files.contains(file) {
4037 return;
4038 }
4039 environment
4040 .maybe_applied_pragma_once_files
4041 .insert(file.clone());
4042 }
4043 MacroIncludeProtection::None => {}
4044 }
4045 let cell = self.macro_event_cell(file);
4046 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4047 for event in events {
4048 #[cfg(any(test, feature = "test-support"))]
4049 self.macro_event_application_count
4050 .fetch_add(1, Ordering::Relaxed);
4051 match event {
4052 MacroEvent::Define { name, binding, .. } => {
4053 Self::merge_conditional_macro_definition(
4054 environment,
4055 name,
4056 binding,
4057 conditional_file,
4058 conditional_byte,
4059 );
4060 }
4061 MacroEvent::Undef { name, .. } => {
4062 if environment.binding(name).is_some() {
4063 environment.insert(
4064 name.clone(),
4065 MacroBinding::ambiguous(conditional_file, conditional_byte),
4066 );
4067 } else {
4068 environment.remove_known_undefined(name);
4069 }
4070 }
4071 MacroEvent::Include { targets, .. } => {
4072 if targets.is_empty() {
4073 environment.mark_unknown_names(conditional_file, conditional_byte);
4074 continue;
4075 }
4076 for target in targets {
4077 self.mark_macro_events_ambiguous(
4078 target,
4079 environment,
4080 include_stack,
4081 conditional_file,
4082 conditional_byte,
4083 );
4084 }
4085 }
4086 MacroEvent::Invalidate { .. } => {
4087 for binding in environment.bindings.values_mut() {
4088 *binding = MacroBinding::uncertain_from(
4089 binding,
4090 conditional_file,
4091 conditional_byte,
4092 );
4093 }
4094 }
4095 }
4096 }
4097 }
4098
4099 fn merge_conditional_macro_definition(
4100 environment: &mut MacroEnvironment,
4101 name: &str,
4102 possible_binding: &MacroBinding,
4103 conditional_file: &ProjectFile,
4104 conditional_byte: usize,
4105 ) {
4106 if environment.binding(name).is_some_and(|current| {
4111 current.definition != MacroDefinition::Unsupported
4112 && current.definition == possible_binding.definition
4113 }) {
4114 return;
4115 }
4116 environment.insert(
4117 name.to_string(),
4118 MacroBinding::ambiguous(conditional_file, conditional_byte),
4119 );
4120 }
4121
4122 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4123 let cell = self
4124 .macro_include_protection_cells
4125 .lock()
4126 .expect("C++ include protection cache poisoned")
4127 .entry(file.clone())
4128 .or_default()
4129 .clone();
4130 cell.get_or_init(|| {
4131 self.cpp.prepared_syntax(self.token, file).map_or(
4132 MacroIncludeProtection::None,
4133 |prepared| {
4134 top_level_macro_include_protection(
4135 prepared.tree().root_node(),
4136 prepared.source(),
4137 )
4138 },
4139 )
4140 })
4141 .clone()
4142 }
4143
4144 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4145 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4146 return Vec::new();
4147 };
4148 let source = prepared.source();
4149 let mut events = Vec::new();
4150 let root = prepared.tree().root_node();
4151 let mut stack = vec![root];
4152 while let Some(node) = stack.pop() {
4153 match node.kind() {
4154 "preproc_def" | "preproc_function_def" => {
4155 let Some(name) = node.child_by_field_name("name") else {
4156 continue;
4157 };
4158 let name = node_text(name, source).to_string();
4159 events.push(MacroEvent::Define {
4160 name,
4161 binding: MacroBinding {
4162 source: file.clone(),
4163 declaration_byte: node.start_byte(),
4164 definition: Self::decode_macro_definition(node, source),
4165 exact: true,
4166 },
4167 byte: node.start_byte(),
4168 conditionals: owning_preprocessor_conditionals(root, node, source),
4169 });
4170 continue;
4171 }
4172 "preproc_include" => {
4173 let Some(path) = node.child_by_field_name("path") else {
4174 events.push(MacroEvent::Include {
4175 targets: Vec::new(),
4176 byte: node.start_byte(),
4177 conditionals: owning_preprocessor_conditionals(root, node, source),
4178 });
4179 continue;
4180 };
4181 let include = structured_include_path(path, source);
4182 let targets = include.map_or_else(Vec::new, |include| {
4183 resolve_include_targets_with_index(
4184 file,
4185 include,
4186 self.cpp.include_target_index(),
4187 )
4188 });
4189 if targets.is_empty()
4200 && include.is_some_and(|include| {
4201 !self.cpp.include_target_index().names_indexed_file(include)
4202 })
4203 {
4204 continue;
4205 }
4206 events.push(MacroEvent::Include {
4207 targets,
4208 byte: node.start_byte(),
4209 conditionals: owning_preprocessor_conditionals(root, node, source),
4210 });
4211 continue;
4212 }
4213 "preproc_call" => {
4214 let Some(directive) = node.child_by_field_name("directive") else {
4215 continue;
4216 };
4217 if node_text(directive, source) != "#undef" {
4218 continue;
4219 }
4220 let name = node
4221 .child_by_field_name("argument")
4222 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
4223 if let Some(name) = name {
4224 events.push(MacroEvent::Undef {
4225 name,
4226 byte: node.start_byte(),
4227 conditionals: owning_preprocessor_conditionals(root, node, source),
4228 });
4229 } else {
4230 events.push(MacroEvent::Invalidate {
4231 byte: node.start_byte(),
4232 });
4233 }
4234 continue;
4235 }
4236 _ => {}
4237 }
4238 push_named_children_reversed(node, &mut stack);
4239 }
4240 events.sort_by_key(MacroEvent::byte);
4241 events
4242 }
4243
4244 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4245 self.ordinary_type_import_cells
4246 .lock()
4247 .expect("C++ ordinary type import cache poisoned")
4248 .entry(file.clone())
4249 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4250 .clone()
4251 }
4252
4253 pub fn project_using_index(
4254 &self,
4255 build: impl FnOnce() -> ProjectUsingIndex,
4256 ) -> &ProjectUsingIndex {
4257 self.project_using_index.get_or_init(build)
4258 }
4259
4260 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4261 let mut files = self
4262 .visible_source_files_by_root
4263 .values()
4264 .flatten()
4265 .cloned()
4266 .collect::<HashSet<_>>()
4267 .into_iter()
4268 .collect::<Vec<_>>();
4269 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4270 files
4271 }
4272
4273 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4274 self.visible_source_files_by_root
4275 .get(root)
4276 .is_some_and(|files| files.contains(source))
4277 }
4278
4279 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4280 let cached = self
4281 .visible_parser_alias_name_sets
4282 .read()
4283 .expect("visible parser alias-name cache poisoned")
4284 .get(file)
4285 .cloned();
4286 let cell = if let Some(cached) = cached {
4287 cached
4288 } else {
4289 let mut cells = self
4290 .visible_parser_alias_name_sets
4291 .write()
4292 .expect("visible parser alias-name cache poisoned");
4293 Arc::clone(
4294 cells
4295 .entry(file.clone())
4296 .or_insert_with(|| Arc::new(OnceLock::new())),
4297 )
4298 };
4299 cell.get_or_init(|| {
4300 #[cfg(any(test, feature = "test-support"))]
4301 self.visible_parser_alias_name_set_build_count
4302 .fetch_add(1, Ordering::Relaxed);
4303 let mut names = HashSet::default();
4304 let visible_files = self
4305 .visible_source_files_by_root
4306 .get(file)
4307 .cloned()
4308 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4309 for visible_file in visible_files {
4310 let aliases = {
4311 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4312 Arc::clone(
4313 cells
4314 .entry(visible_file.clone())
4315 .or_insert_with(|| Arc::new(OnceLock::new())),
4316 )
4317 };
4318 for alias in aliases
4319 .get_or_init(|| {
4320 self.parser_alias_source_parses
4321 .fetch_add(1, Ordering::Relaxed);
4322 #[cfg(any(test, feature = "test-support"))]
4323 {
4324 *self
4325 .alias_source_parse_counts
4326 .lock()
4327 .expect("alias source parse count lock")
4328 .entry(visible_file.clone())
4329 .or_default() += 1;
4330 }
4331 aliases_from_prepared_source(self.cpp, self.token, &visible_file)
4332 .into_boxed_slice()
4333 })
4334 .iter()
4335 {
4336 names.insert(alias.name.clone());
4337 }
4338 }
4339 names
4340 })
4341 .contains(name)
4342 }
4343
4344 pub fn parser_alias_name_may_resolve_to_target(
4345 &self,
4346 file: &ProjectFile,
4347 alias_name: &str,
4348 target: &CodeUnit,
4349 ) -> bool {
4350 let started = std::time::Instant::now();
4351 self.parser_alias_fallback_calls
4352 .fetch_add(1, Ordering::Relaxed);
4353 let key = (
4354 file.clone(),
4355 alias_name.to_string(),
4356 logical_symbol_key(target),
4357 );
4358 let cached = self
4359 .parser_alias_target_matches
4360 .read()
4361 .expect("parser alias target-match cache poisoned")
4362 .get(&key)
4363 .cloned();
4364 let cell = if let Some(cached) = cached {
4365 cached
4366 } else {
4367 let mut cells = self
4368 .parser_alias_target_matches
4369 .write()
4370 .expect("parser alias target-match cache poisoned");
4371 Arc::clone(
4372 cells
4373 .entry(key)
4374 .or_insert_with(|| Arc::new(OnceLock::new())),
4375 )
4376 };
4377 let matched = *cell.get_or_init(|| match self.visible_source_files_by_root.get(file) {
4378 None => {
4379 self.parser_alias_fallback_files
4380 .fetch_add(1, Ordering::Relaxed);
4381 self.file_alias_matches(self.cpp, file, alias_name, target)
4382 }
4383 Some(visible_files) => visible_files.iter().any(|visible_file| {
4384 self.parser_alias_fallback_files
4385 .fetch_add(1, Ordering::Relaxed);
4386 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4387 }),
4388 });
4389 self.parser_alias_fallback_elapsed_micros.fetch_add(
4390 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4391 Ordering::Relaxed,
4392 );
4393 matched
4394 }
4395
4396 fn file_alias_matches(
4397 &self,
4398 cpp: &dyn CppSource,
4399 file: &ProjectFile,
4400 alias_name: &str,
4401 target: &CodeUnit,
4402 ) -> bool {
4403 let cell = {
4404 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4405 Arc::clone(
4406 cells
4407 .entry(file.clone())
4408 .or_insert_with(|| Arc::new(OnceLock::new())),
4409 )
4410 };
4411 cell.get_or_init(|| {
4412 self.parser_alias_source_parses
4413 .fetch_add(1, Ordering::Relaxed);
4414 #[cfg(any(test, feature = "test-support"))]
4415 {
4416 *self
4417 .alias_source_parse_counts
4418 .lock()
4419 .expect("alias source parse count lock")
4420 .entry(file.clone())
4421 .or_default() += 1;
4422 }
4423 aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
4424 })
4425 .iter()
4426 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
4427 }
4428
4429 fn callable_arities_for_target(
4430 &self,
4431 analyzer: &CppGraphSource<'_>,
4432 cpp: &dyn CppSource,
4433 file: &ProjectFile,
4434 prepared: &PreparedSyntaxTree,
4435 spec: &TargetSpec,
4436 ) -> Vec<ActivatedCallableArity> {
4437 let Some(signature) = spec.target.signature() else {
4438 return Vec::new();
4439 };
4440 let Some(candidates) = self
4441 .visible_by_identifier
4442 .get(file)
4443 .and_then(|by_name| by_name.get(&spec.member_name))
4444 else {
4445 return Vec::new();
4446 };
4447 let differing_candidates = candidates
4448 .iter()
4449 .filter(|candidate| {
4450 candidate.is_function()
4451 && candidate.fq_name() == spec.target.fq_name()
4452 && candidate.signature() == Some(signature)
4453 })
4454 .filter_map(|candidate| {
4455 analyzer
4456 .signature_metadata(candidate)
4457 .into_iter()
4458 .find_map(|metadata| metadata.callable_arity())
4459 .filter(|arity| Some(*arity) != spec.callable_arity)
4460 .map(|arity| (candidate, arity))
4461 })
4462 .collect::<Vec<_>>();
4463 if differing_candidates.is_empty() {
4464 return Vec::new();
4465 }
4466 let mut arities = Vec::with_capacity(differing_candidates.len());
4467 let reference = CallableReferenceContext {
4470 file,
4471 position: None,
4472 };
4473 for (candidate, candidate_arity) in differing_candidates {
4474 let declaration_activation = if candidate.source() == file {
4475 callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
4476 } else {
4477 cpp.prepared_syntax(self.token, candidate.source())
4478 .and_then(|syntax| {
4479 callable_declaration_activation_in_file(
4480 analyzer,
4481 syntax.as_ref(),
4482 candidate,
4483 &reference,
4484 )
4485 })
4486 };
4487 let Some(declaration_activation) = declaration_activation else {
4488 continue;
4489 };
4490 let activation_byte = if candidate.source() == file {
4491 Some(declaration_activation)
4492 } else {
4493 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4494 };
4495 if let Some(activation_byte) = activation_byte {
4496 arities.push(ActivatedCallableArity {
4497 activation_byte,
4498 arity: candidate_arity,
4499 });
4500 }
4501 }
4502 arities
4503 }
4504
4505 fn callable_parameter_macro_arity(
4506 &self,
4507 target: &CodeUnit,
4508 signature: Option<&str>,
4509 ) -> Option<CallableArity> {
4510 let parameter_types = cpp_signature_param_types(signature?)?;
4511 let [macro_name] = parameter_types.as_slice() else {
4512 return None;
4513 };
4514 if macro_name.is_empty()
4515 || !macro_name
4516 .chars()
4517 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4518 {
4519 return None;
4520 }
4521 let cache_key = (target.source().clone(), macro_name.clone());
4522 if let Some(cached) = self
4523 .callable_parameter_macro_arities
4524 .lock()
4525 .expect("C++ callable parameter-macro arity cache poisoned")
4526 .get(&cache_key)
4527 .copied()
4528 {
4529 return cached;
4530 }
4531 let mut visible_files = HashSet::default();
4532 collect_include_closure(
4533 &self.cpp_source(),
4534 self.cpp.include_target_index(),
4535 target.source(),
4536 &mut visible_files,
4537 None,
4538 );
4539 let mut arities = Vec::new();
4540 for visible_file in visible_files {
4541 let cell = self.macro_event_cell(&visible_file);
4542 for event in
4543 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4544 {
4545 let MacroEvent::Define { name, binding, .. } = event else {
4546 continue;
4547 };
4548 if name != macro_name {
4549 continue;
4550 }
4551 let MacroDefinition::Object { replacement } = &binding.definition else {
4552 continue;
4553 };
4554 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4555 continue;
4556 };
4557 if !arities.contains(&arity) {
4558 arities.push(arity);
4559 }
4560 }
4561 }
4562 let resolved = (|| {
4563 let required = arities
4564 .iter()
4565 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4566 .min()?;
4567 let total = arities.iter().map(|arity| arity.total()).max()?;
4568 let repeated = arities
4569 .iter()
4570 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4571 Some(CallableArity::new(required, total, repeated))
4576 })();
4577 self.callable_parameter_macro_arities
4578 .lock()
4579 .expect("C++ callable parameter-macro arity cache poisoned")
4580 .insert(cache_key, resolved);
4581 resolved
4582 }
4583
4584 pub fn include_activation_for_source(
4585 &self,
4586 cpp: &dyn CppSource,
4587 file: &ProjectFile,
4588 prepared: &PreparedSyntaxTree,
4589 donor_source: &ProjectFile,
4590 ) -> Option<usize> {
4591 let key = (file.clone(), donor_source.clone());
4592 if let Some(cached) = self
4593 .include_activation_cells
4594 .lock()
4595 .expect("C++ include activation cache poisoned")
4596 .get(&key)
4597 .copied()
4598 {
4599 return cached;
4600 }
4601 #[cfg(any(test, feature = "test-support"))]
4602 self.include_activation_build_count
4603 .fetch_add(1, Ordering::Relaxed);
4604 let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
4605 let mut cells = self
4606 .include_activation_cells
4607 .lock()
4608 .expect("C++ include activation cache poisoned");
4609 *cells.entry(key).or_insert(activation)
4610 }
4611
4612 pub fn conditional_include_projections_for_source(
4613 &self,
4614 file: &ProjectFile,
4615 prepared: &PreparedSyntaxTree,
4616 donor_source: &ProjectFile,
4617 ) -> Arc<[ConditionalIncludeProjection]> {
4618 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4619 let cell = self
4620 .conditional_include_projection_cells
4621 .lock()
4622 .expect("C++ conditional include projection cache poisoned")
4623 .entry(file.clone())
4624 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4625 .clone();
4626 let index = cell.get_or_build_pool_independent(|| {
4627 #[cfg(any(test, feature = "test-support"))]
4628 self.conditional_include_projection_index_build_count
4629 .fetch_add(1, Ordering::Relaxed);
4630 find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
4631 #[cfg(any(test, feature = "test-support"))]
4632 self.conditional_include_projection_state_count
4633 .fetch_add(1, Ordering::Relaxed);
4634 })
4635 });
4636 index
4637 .get(donor_source)
4638 .cloned()
4639 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4640 }
4641
4642 #[cfg(any(test, feature = "test-support"))]
4643 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4644 (
4645 self.conditional_include_projection_index_build_count
4646 .load(Ordering::Relaxed),
4647 self.conditional_include_projection_state_count
4648 .load(Ordering::Relaxed),
4649 )
4650 }
4651
4652 #[cfg(any(test, feature = "test-support"))]
4653 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4654 self.conditional_include_target_state_count
4655 .load(Ordering::Relaxed)
4656 }
4657
4658 #[cfg(any(test, feature = "test-support"))]
4659 pub fn include_activation_build_count_for_test(&self) -> usize {
4660 self.include_activation_build_count.load(Ordering::Relaxed)
4661 }
4662
4663 #[cfg(any(test, feature = "test-support"))]
4664 pub fn note_using_donor_activation_for_test(&self) {
4665 self.using_donor_activation_count
4666 .fetch_add(1, Ordering::Relaxed);
4667 }
4668
4669 #[cfg(not(any(test, feature = "test-support")))]
4670 pub fn note_using_donor_activation_for_test(&self) {}
4671
4672 #[cfg(any(test, feature = "test-support"))]
4673 pub fn note_using_namespace_lookup_for_test(&self) {
4674 self.using_namespace_lookup_count
4675 .fetch_add(1, Ordering::Relaxed);
4676 }
4677
4678 #[cfg(not(any(test, feature = "test-support")))]
4679 pub fn note_using_namespace_lookup_for_test(&self) {}
4680
4681 #[cfg(any(test, feature = "test-support"))]
4682 pub fn note_using_name_candidate_inspection_for_test(&self) {
4683 self.using_name_candidate_inspection_count
4684 .fetch_add(1, Ordering::Relaxed);
4685 }
4686
4687 #[cfg(not(any(test, feature = "test-support")))]
4688 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4689
4690 #[cfg(any(test, feature = "test-support"))]
4691 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4692 (
4693 self.using_donor_activation_count.load(Ordering::Relaxed),
4694 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4695 self.callable_reference_spec_build_count
4696 .load(Ordering::Relaxed),
4697 self.using_name_candidate_inspection_count
4698 .load(Ordering::Relaxed),
4699 )
4700 }
4701
4702 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4703 file == target.source()
4704 || self
4705 .visible_by_file
4706 .get(file)
4707 .is_some_and(|visible| visible.contains(target))
4708 }
4709
4710 pub fn declaration_visible_at(
4722 &self,
4723 analyzer: &CppGraphSource<'_>,
4724 file: &ProjectFile,
4725 declaration: &CodeUnit,
4726 reference_byte: usize,
4727 ) -> bool {
4728 let reference_guards = OnceCell::new();
4729 self.visible_identifier_candidates(file, declaration.identifier())
4730 .filter(|candidate| {
4731 self.same_logical_callable(analyzer, candidate, declaration)
4732 || flattened_macro_namespace_declaration_matches(
4733 analyzer,
4734 self.cpp,
4735 file,
4736 candidate,
4737 declaration,
4738 reference_byte,
4739 )
4740 })
4741 .any(|candidate| {
4742 self.physical_declaration_visible_at(
4743 analyzer,
4744 file,
4745 candidate,
4746 reference_byte,
4747 &reference_guards,
4748 )
4749 })
4750 }
4751
4752 pub fn declaration_visible_at_reference(
4759 &self,
4760 analyzer: &CppGraphSource<'_>,
4761 file: &ProjectFile,
4762 declaration: &CodeUnit,
4763 reference: Node<'_>,
4764 ) -> bool {
4765 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4766 return false;
4767 };
4768 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4769 let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4770 if !declaration_guards.iter().any(|(_, required)| {
4771 guards_compatible_at_reference(required, reference_guards.as_ref())
4772 }) {
4773 return false;
4774 }
4775 if declaration.source() == file
4776 && !analyzer.reference_uses_c_semantics(file)
4777 && (declaration.is_field() || declaration.is_callable())
4778 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4779 self.indexed_enclosing_owner_scope(analyzer, file, reference)
4780 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4781 })
4782 {
4783 return true;
4787 }
4788 if declaration.is_field() && declaration.source() == file {
4789 let reference_byte = reference.start_byte();
4790 let reference_function =
4791 real_function_definition_ancestor(reference, prepared.source());
4792 let guards = OnceCell::new();
4793 let field_reference = CallableReferenceContext {
4794 file,
4795 position: Some(CallableReferencePosition {
4796 prepared: prepared.as_ref(),
4797 byte: reference_byte,
4798 guards: &guards,
4799 }),
4800 };
4801 let mut has_local_declaration = false;
4802 let mut local_declaration_visible = false;
4803 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4804 {
4805 let Some(declaration_function) =
4806 real_function_definition_ancestor(declaration, prepared.source())
4807 else {
4808 continue;
4809 };
4810 has_local_declaration = true;
4811 if reference_function.is_some_and(|reference_function| {
4812 reference_function.start_byte() == declaration_function.start_byte()
4813 && reference_function.end_byte() == declaration_function.end_byte()
4814 }) && callable_preprocessor_context_is_visible_for_reference(
4815 declaration,
4816 prepared.source(),
4817 &field_reference,
4818 ) && callable_declaration_activation_byte(declaration) < reference_byte
4819 {
4820 local_declaration_visible = true;
4821 break;
4822 }
4823 }
4824 if has_local_declaration {
4825 return local_declaration_visible;
4826 }
4827 }
4828 let guards = OnceCell::new();
4829 self.physical_declaration_visible_at(
4830 analyzer,
4831 file,
4832 declaration,
4833 reference.start_byte(),
4834 &guards,
4835 )
4836 }
4837
4838 pub fn declaration_visible_for_c_forward_call(
4844 &self,
4845 analyzer: &CppGraphSource<'_>,
4846 file: &ProjectFile,
4847 declaration: &CodeUnit,
4848 reference_byte: usize,
4849 ) -> bool {
4850 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4851 return true;
4852 }
4853 if declaration.source() != file {
4854 return false;
4855 }
4856 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4857 return false;
4858 };
4859 let reference_guards = prepared
4860 .tree()
4861 .root_node()
4862 .descendant_for_byte_range(reference_byte, reference_byte)
4863 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4864 declaration_guard_requirements(analyzer, self.cpp, declaration)
4865 .into_iter()
4866 .any(|(_, required)| {
4867 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4868 })
4869 }
4870
4871 pub fn callable_arity_at_reference(
4872 &self,
4873 analyzer: &CppGraphSource<'_>,
4874 file: &ProjectFile,
4875 candidate: &CodeUnit,
4876 reference_byte: usize,
4877 ) -> Option<CallableArity> {
4878 let key = (file.clone(), logical_symbol_key(candidate));
4879 let cell = self
4880 .callable_reference_specs
4881 .lock()
4882 .expect("C++ callable reference-spec cache poisoned")
4883 .entry(key)
4884 .or_default()
4885 .clone();
4886 let spec = cell.get_or_init(|| {
4887 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4888 let spec = TargetSpec::from_target(analyzer, candidate)?;
4889 let spec = spec
4890 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4891 .into_owned();
4892 #[cfg(any(test, feature = "test-support"))]
4893 self.callable_reference_spec_build_count
4894 .fetch_add(1, Ordering::Relaxed);
4895 Some(spec)
4896 });
4897 spec.as_ref()?.callable_arity_at(reference_byte)
4898 }
4899
4900 fn physical_declaration_visible_at(
4901 &self,
4902 analyzer: &CppGraphSource<'_>,
4903 file: &ProjectFile,
4904 declaration: &CodeUnit,
4905 reference_byte: usize,
4906 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4907 ) -> bool {
4908 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4909 return false;
4910 };
4911 let reference = CallableReferenceContext {
4912 file,
4913 position: Some(CallableReferencePosition {
4914 prepared: prepared.as_ref(),
4915 byte: reference_byte,
4916 guards: reference_guards,
4917 }),
4918 };
4919 if declaration.source() == file {
4920 return callable_declaration_activation_in_file(
4921 analyzer,
4922 prepared.as_ref(),
4923 declaration,
4924 &reference,
4925 )
4926 .or_else(|| {
4927 self.exhaustive_guard_family_activation(
4928 analyzer,
4929 prepared.as_ref(),
4930 declaration,
4931 &reference,
4932 )
4933 })
4934 .is_some_and(|activation| activation < reference_byte);
4935 }
4936 let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
4937 return false;
4938 };
4939 if self
4940 .foreign_callable_declaration_activation(
4941 analyzer,
4942 donor_syntax.as_ref(),
4943 declaration,
4944 &reference,
4945 )
4946 .or_else(|| {
4947 self.exhaustive_guard_family_activation(
4948 analyzer,
4949 donor_syntax.as_ref(),
4950 declaration,
4951 &reference,
4952 )
4953 })
4954 .is_none()
4955 {
4956 return false;
4957 }
4958 declaration_guard_requirements(analyzer, self.cpp, declaration)
4959 .into_iter()
4960 .any(|(_, declaration_guards)| {
4961 self.foreign_declaration_reachable_at_reference(
4962 file,
4963 prepared.as_ref(),
4964 declaration.source(),
4965 &declaration_guards,
4966 reference.guards(),
4967 reference_byte,
4968 )
4969 })
4970 }
4971
4972 fn foreign_callable_declaration_activation(
4987 &self,
4988 analyzer: &CppGraphSource<'_>,
4989 donor_syntax: &PreparedSyntaxTree,
4990 declaration: &CodeUnit,
4991 reference: &CallableReferenceContext<'_>,
4992 ) -> Option<usize> {
4993 let build_decides = !self.compile_context_is_absent(reference.file);
4994 let proven = self.compile_proven_guards(reference.file);
4995 let augmented;
4996 let active = match reference.guards() {
4997 Some(active) if !proven.is_empty() => {
4998 augmented = active.union(&proven).cloned().collect();
4999 Some(&augmented)
5000 }
5001 other => other,
5002 };
5003 nameable_callable_declaration_nodes(analyzer, donor_syntax, declaration)
5004 .into_iter()
5005 .filter(|node| {
5006 let Some(required) = callable_declaration_guard_requirements(
5007 *node,
5008 donor_syntax.source(),
5009 reference,
5010 ) else {
5011 return false;
5012 };
5013 if required.is_empty() {
5014 return true;
5015 }
5016 if build_decides {
5017 guard_requirements_hold_at_reference(&required, active)
5018 } else {
5019 guards_compatible_at_reference(&required, reference.guards())
5020 }
5021 })
5022 .map(callable_declaration_activation_byte)
5023 .min()
5024 }
5025
5026 pub fn external_type_candidate_visible_at(
5027 &self,
5028 file: &ProjectFile,
5029 candidate: &CodeUnit,
5030 reference_byte: usize,
5031 ) -> bool {
5032 if candidate.source() == file {
5033 return true;
5034 }
5035 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5036 return false;
5037 };
5038 self.visible_identifier_candidates(file, candidate.identifier())
5039 .filter(|peer| same_logical_symbol(candidate, peer))
5040 .any(|peer| {
5041 peer.source() == file
5042 || self
5043 .include_activation_for_source(
5044 self.cpp,
5045 file,
5046 prepared.as_ref(),
5047 peer.source(),
5048 )
5049 .is_some_and(|activation| activation <= reference_byte)
5050 })
5051 }
5052
5053 pub fn external_type_declaration_visible_at(
5054 &self,
5055 file: &ProjectFile,
5056 candidate: &CodeUnit,
5057 reference_byte: usize,
5058 ) -> bool {
5059 if candidate.source() == file {
5060 return true;
5061 }
5062 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5063 return false;
5064 };
5065 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5066 .is_some_and(|activation| activation <= reference_byte)
5067 }
5068
5069 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5088 if let Some(cached) = self
5089 .compile_proven_guard_cells
5090 .lock()
5091 .expect("C++ compile-proven guard cache poisoned")
5092 .get(file)
5093 {
5094 return Arc::clone(cached);
5095 }
5096 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5097 Some(names) => names,
5098 None => {
5099 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5100 let seed = translation_units.next().and_then(|translation_unit| {
5101 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5102 });
5103 match seed {
5104 None => HashSet::default(),
5105 Some(mut names) => {
5106 for translation_unit in translation_units {
5107 let Some(reached) = context_fact_names(
5108 self.cpp.compile_contexts_for(&translation_unit),
5109 ) else {
5110 names.clear();
5111 break;
5112 };
5113 names.retain(|name| reached.contains(name));
5114 if names.is_empty() {
5115 break;
5116 }
5117 }
5118 names
5119 }
5120 }
5121 }
5122 };
5123 let proven = Arc::new(
5124 names
5125 .into_iter()
5126 .map(PreprocessorGuard::Defined)
5127 .collect::<HashSet<_>>(),
5128 );
5129 self.compile_proven_guard_cells
5130 .lock()
5131 .expect("C++ compile-proven guard cache poisoned")
5132 .insert(file.clone(), Arc::clone(&proven));
5133 proven
5134 }
5135
5136 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5146 if let Some(cached) = self
5147 .include_path_admission_cells
5148 .lock()
5149 .expect("C++ include-path admission cache poisoned")
5150 .get(file)
5151 .copied()
5152 {
5153 return cached;
5154 }
5155 let admission = if self.compile_context_is_absent(file) {
5156 IncludePathAdmission::Compatible
5157 } else {
5158 IncludePathAdmission::Proven
5159 };
5160 self.include_path_admission_cells
5161 .lock()
5162 .expect("C++ include-path admission cache poisoned")
5163 .insert(file.clone(), admission);
5164 admission
5165 }
5166
5167 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5174 if !self.cpp.compile_contexts_for(file).is_empty() {
5175 return false;
5176 }
5177 let translation_units = self.cpp.reaching_translation_units(file);
5178 translation_units.is_empty()
5179 || translation_units
5180 .iter()
5181 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5182 }
5183
5184 pub fn miss_requires_compile_context(
5196 &self,
5197 file: &ProjectFile,
5198 identifier: &str,
5199 reference: Node<'_>,
5200 ) -> bool {
5201 if !self.compile_context_is_absent(file) {
5202 return false;
5203 }
5204 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5205 return false;
5206 };
5207 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5208 let reference_byte = reference.start_byte();
5209 let mut sources = self
5210 .visible_identifier_candidates(file, identifier)
5211 .map(CodeUnit::source)
5212 .filter(|source| *source != file)
5213 .collect::<Vec<_>>();
5214 sources.sort();
5215 sources.dedup();
5216 sources.into_iter().any(|declaration_source| {
5217 self.conditional_include_projections_for_source(
5218 file,
5219 prepared.as_ref(),
5220 declaration_source,
5221 )
5222 .iter()
5223 .any(|projection| {
5224 projection.activation_byte <= reference_byte
5225 && !guard_requirements_hold_at_reference(
5226 &projection.required_guards,
5227 reference_guards.as_ref(),
5228 )
5229 && guards_compatible_at_reference(
5230 &projection.required_guards,
5231 reference_guards.as_ref(),
5232 )
5233 })
5234 })
5235 }
5236
5237 fn foreign_declaration_reachable_at_reference(
5249 &self,
5250 file: &ProjectFile,
5251 prepared: &PreparedSyntaxTree,
5252 declaration_source: &ProjectFile,
5253 declaration_guards: &HashSet<PreprocessorGuard>,
5254 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5255 reference_byte: usize,
5256 ) -> bool {
5257 let proven = self.compile_proven_guards(file);
5263 let augmented;
5264 let reference_guards = match reference_guards {
5265 Some(active) if !proven.is_empty() => {
5266 augmented = active.union(&proven).cloned().collect();
5267 Some(&augmented)
5268 }
5269 other => other,
5270 };
5271 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5272 eprintln!(
5273 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5274 declaration_source.rel_path().display(),
5275 );
5276 }
5277 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5278 return false;
5279 }
5280 if self
5281 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5282 .is_some_and(|activation| activation <= reference_byte)
5283 {
5284 return true;
5285 }
5286 let projections =
5287 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5288 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5289 eprintln!(
5290 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5291 declaration_source.rel_path().display(),
5292 declaration_guards.len(),
5293 proven.len(),
5294 projections.len(),
5295 );
5296 }
5297 let admission = self.include_path_admission(file);
5298 projections.iter().any(|projection| {
5299 projection.activation_byte <= reference_byte
5300 && admission.admits(
5301 &projection.required_guards,
5302 &projection.partial_guards,
5303 reference_guards,
5304 )
5305 && self.preprocessor_guards_stable_between(
5306 file,
5307 projection.activation_byte,
5308 reference_byte,
5309 &projection.required_guards,
5310 )
5311 })
5312 }
5313
5314 fn foreign_declaration_may_be_reachable_from_raw_guards(
5315 &self,
5316 file: &ProjectFile,
5317 prepared: &PreparedSyntaxTree,
5318 declaration_source: &ProjectFile,
5319 declaration_guards: &HashSet<PreprocessorGuard>,
5320 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5321 reference_byte: usize,
5322 ) -> bool {
5323 let proven = self.compile_proven_guards(file);
5324 let augmented;
5325 let reference_guards = match reference_guards {
5326 Some(active) if !proven.is_empty() => {
5327 augmented = active.union(&proven).cloned().collect();
5328 Some(&augmented)
5329 }
5330 other => other,
5331 };
5332 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5333 return false;
5334 }
5335 if self
5336 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5337 .is_some_and(|activation| activation <= reference_byte)
5338 {
5339 return true;
5340 }
5341 let reachable = find_conditional_include_projection_for_source(
5342 self.cpp,
5343 self.token,
5344 file,
5345 prepared,
5346 declaration_source,
5347 self.include_path_admission(file),
5348 reference_guards,
5349 reference_byte,
5350 &|| {
5351 #[cfg(any(test, feature = "test-support"))]
5352 self.conditional_include_target_state_count
5353 .fetch_add(1, Ordering::Relaxed);
5354 },
5355 );
5356 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5357 eprintln!(
5358 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5359 declaration_source.rel_path().display(),
5360 declaration_guards.len(),
5361 proven.len(),
5362 reference_guards.map_or(0, HashSet::len),
5363 );
5364 }
5365 reachable
5366 }
5367
5368 fn foreign_declaration_reachable_from_compile_proven_guards(
5369 &self,
5370 file: &ProjectFile,
5371 prepared: &PreparedSyntaxTree,
5372 declaration_source: &ProjectFile,
5373 declaration_guards: &HashSet<PreprocessorGuard>,
5374 reference_byte: usize,
5375 ) -> bool {
5376 let proven = self.compile_proven_guards(file);
5377 if proven.is_empty()
5378 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5379 {
5380 return false;
5381 }
5382 let projections =
5383 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5384 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5385 eprintln!(
5386 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5387 declaration_source.rel_path().display(),
5388 declaration_guards.len(),
5389 proven.len(),
5390 projections.len(),
5391 );
5392 }
5393 projections.iter().any(|projection| {
5394 projection.activation_byte <= reference_byte
5395 && guard_requirements_hold_at_reference(
5396 &projection.required_guards,
5397 Some(proven.as_ref()),
5398 )
5399 && self.preprocessor_guards_stable_between(
5404 file,
5405 0,
5406 projection.activation_byte,
5407 &projection.required_guards,
5408 )
5409 })
5410 }
5411
5412 pub fn external_type_candidate_visible_in_context(
5413 &self,
5414 analyzer: &CppGraphSource<'_>,
5415 file: &ProjectFile,
5416 candidate: &CodeUnit,
5417 reference: Node<'_>,
5418 ) -> bool {
5419 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5420 if report_stats {
5421 eprintln!(
5422 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5423 candidate.fq_name(),
5424 candidate.source().rel_path().display(),
5425 file.rel_path().display(),
5426 reference.start_byte(),
5427 );
5428 }
5429 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5430 return false;
5431 };
5432 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5433 let reference_guards = OnceCell::new();
5434 let reference_guards_at_site = || {
5435 reference_guards.get_or_init(|| {
5436 let started = Instant::now();
5437 if report_stats {
5438 eprintln!(
5439 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5440 file.rel_path().display(),
5441 reference.start_byte(),
5442 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5443 );
5444 }
5445 let macro_environment = self.macro_environment(file, reference.start_byte());
5446 let filtered = raw_reference_guards
5447 .clone()
5448 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5449 if report_stats {
5450 eprintln!(
5451 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5452 filtered.is_some(),
5453 started.elapsed().as_millis(),
5454 );
5455 }
5456 filtered
5457 })
5458 };
5459
5460 let peers = self
5461 .visible_identifier_candidates(file, candidate.identifier())
5462 .filter(|peer| same_logical_symbol(candidate, peer))
5463 .collect::<Vec<_>>();
5464 if report_stats {
5465 let peer_sources = peers
5466 .iter()
5467 .map(|peer| peer.source().rel_path().display().to_string())
5468 .collect::<Vec<_>>();
5469 eprintln!(
5470 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5471 candidate.fq_name(),
5472 );
5473 }
5474 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5475 declaration_guard_requirements(analyzer, self.cpp, peer)
5476 .into_iter()
5477 .any(|(declaration_byte, declaration_guards)| {
5478 if peer.source() == file {
5479 let visible = declaration_byte < reference.start_byte()
5480 && declaration_guards.is_empty();
5481 if report_stats {
5482 eprintln!(
5483 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5484 peer.source().rel_path().display(),
5485 declaration_guards.len(),
5486 );
5487 }
5488 return visible;
5489 }
5490 let direct = declaration_guards.is_empty()
5491 && self
5492 .include_activation_for_source(
5493 self.cpp,
5494 file,
5495 prepared.as_ref(),
5496 peer.source(),
5497 )
5498 .is_some_and(|activation| activation <= reference.start_byte());
5499 let compile_proven = !direct
5500 && self.foreign_declaration_reachable_from_compile_proven_guards(
5501 file,
5502 prepared.as_ref(),
5503 peer.source(),
5504 &declaration_guards,
5505 reference.start_byte(),
5506 );
5507 if report_stats {
5508 eprintln!(
5509 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5510 peer.source().rel_path().display(),
5511 declaration_guards.len(),
5512 );
5513 }
5514 direct || compile_proven
5515 })
5516 });
5517 if directly_visible_without_reference_environment {
5518 if report_stats {
5519 eprintln!(
5520 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5521 candidate.fq_name(),
5522 );
5523 }
5524 return true;
5525 }
5526 let directly_visible = peers.iter().any(|peer| {
5527 declaration_guard_requirements(analyzer, self.cpp, peer)
5528 .into_iter()
5529 .any(|(declaration_byte, declaration_guards)| {
5530 if peer.source() == file {
5531 if declaration_byte >= reference.start_byte() {
5532 return false;
5533 }
5534 if !guard_requirements_hold_at_reference(
5535 &declaration_guards,
5536 raw_reference_guards.as_ref(),
5537 ) {
5538 return false;
5539 }
5540 return guard_requirements_hold_at_reference(
5541 &declaration_guards,
5542 reference_guards_at_site().as_ref(),
5543 ) && self.preprocessor_guards_stable_between(
5544 file,
5545 declaration_byte,
5546 reference.start_byte(),
5547 &declaration_guards,
5548 );
5549 }
5550 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5551 file,
5552 prepared.as_ref(),
5553 peer.source(),
5554 &declaration_guards,
5555 raw_reference_guards.as_ref(),
5556 reference.start_byte(),
5557 );
5558 if report_stats {
5559 eprintln!(
5560 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5561 peer.source().rel_path().display(),
5562 declaration_guards.len(),
5563 );
5564 }
5565 if !raw_feasible {
5566 return false;
5567 }
5568 self.foreign_declaration_reachable_at_reference(
5569 file,
5570 prepared.as_ref(),
5571 peer.source(),
5572 &declaration_guards,
5573 reference_guards_at_site().as_ref(),
5574 reference.start_byte(),
5575 )
5576 })
5577 });
5578 if directly_visible {
5579 if report_stats {
5580 eprintln!(
5581 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5582 candidate.fq_name(),
5583 );
5584 }
5585 return true;
5586 }
5587 let complementary = self
5588 .visible_identifier_candidates(file, candidate.identifier())
5589 .filter(|peer| {
5590 peer.kind() == candidate.kind()
5591 && peer.fq_name() == candidate.fq_name()
5592 && peer.source() == candidate.source()
5593 })
5594 .collect::<Vec<_>>();
5595 let complementary_family =
5600 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5601 let raw_candidate_branch_compatible = complementary_family
5602 && raw_reference_guards.as_ref().is_some_and(|active| {
5603 declaration_guard_requirements(analyzer, self.cpp, candidate)
5604 .iter()
5605 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5606 });
5607 if report_stats {
5608 eprintln!(
5609 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5610 candidate.fq_name(),
5611 complementary.len(),
5612 complementary_family,
5613 raw_candidate_branch_compatible,
5614 );
5615 }
5616 let candidate_branch_compatible = raw_candidate_branch_compatible
5617 && reference_guards_at_site().as_ref().is_some_and(|active| {
5618 declaration_guard_requirements(analyzer, self.cpp, candidate)
5619 .iter()
5620 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5621 });
5622 let complementary_visible = candidate_branch_compatible
5623 && if candidate.source() == file {
5624 declaration_guard_requirements(analyzer, self.cpp, candidate)
5625 .iter()
5626 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5627 } else {
5628 self.include_activation_for_source(
5629 self.cpp,
5630 file,
5631 prepared.as_ref(),
5632 candidate.source(),
5633 )
5634 .is_some_and(|activation| activation <= reference.start_byte())
5635 };
5636 if report_stats {
5637 eprintln!(
5638 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5639 if complementary_visible {
5640 "complementary"
5641 } else {
5642 "missing"
5643 },
5644 candidate.fq_name(),
5645 );
5646 }
5647 complementary_visible
5648 }
5649
5650 pub fn is_exhaustive_same_fqn_type_declaration_family(
5651 &self,
5652 analyzer: &CppGraphSource<'_>,
5653 file: &ProjectFile,
5654 candidate: &CodeUnit,
5655 ) -> bool {
5656 let candidates = self
5657 .visible_identifier_candidates(file, candidate.identifier())
5658 .filter(|peer| {
5659 peer.kind() == candidate.kind()
5660 && peer.fq_name() == candidate.fq_name()
5661 && peer.source() == candidate.source()
5662 })
5663 .collect::<Vec<_>>();
5664 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5665 }
5666
5667 pub fn dependent_member_pointer_alias_visible_in_context(
5682 &self,
5683 analyzer: &CppGraphSource<'_>,
5684 file: &ProjectFile,
5685 candidate: &CodeUnit,
5686 owner_components: &[String],
5687 reference: Node<'_>,
5688 ) -> bool {
5689 if !analyzer
5690 .type_alias_provider()
5691 .is_some_and(|provider| provider.is_type_alias(candidate))
5692 {
5693 return false;
5694 }
5695 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5696 return false;
5697 };
5698 if terminal != candidate.identifier()
5699 || canonical_cpp_scope_components(candidate) != owner_components
5700 {
5701 return false;
5702 }
5703 let Some(expected_parent_fq_name) =
5704 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5705 else {
5706 return false;
5707 };
5708 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5709 return false;
5710 };
5711 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5712 || parent_anchor.source() != candidate.source()
5713 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5714 {
5715 return false;
5716 }
5717
5718 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5724 || candidate.source() == file
5725 && !analyzer
5726 .ranges(candidate)
5727 .iter()
5728 .any(|range| range.start_byte < reference.start_byte())
5729 {
5730 return false;
5731 }
5732
5733 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5734 if candidate_guards.is_empty() {
5735 return false;
5736 }
5737 let same_guard_sets =
5738 |left: &[(usize, HashSet<PreprocessorGuard>)],
5739 right: &[(usize, HashSet<PreprocessorGuard>)]| {
5740 left.iter().all(|(_, left_guards)| {
5741 right
5742 .iter()
5743 .any(|(_, right_guards)| left_guards == right_guards)
5744 })
5745 };
5746 let parent_candidates = self
5747 .visible_identifier_candidates(file, parent_anchor.identifier())
5748 .filter(|peer| {
5749 peer.kind() == parent_anchor.kind()
5750 && peer.fq_name() == expected_parent_fq_name.as_str()
5751 && peer.source() == parent_anchor.source()
5752 && canonical_cpp_scope_components(peer) == owner_prefix
5753 })
5754 .filter_map(|peer| {
5755 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5756 (candidate_guards.len() == parent_guards.len()
5757 && same_guard_sets(&candidate_guards, &parent_guards)
5758 && same_guard_sets(&parent_guards, &candidate_guards))
5759 .then(|| (peer.clone(), parent_guards))
5760 })
5761 .collect::<Vec<_>>();
5762 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5763 return false;
5764 };
5765
5766 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5767 return false;
5768 };
5769 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5770 else {
5771 return false;
5772 };
5773 if !candidate_guards.iter().any(|(_, target_guards)| {
5778 guards_compatible_at_reference(target_guards, Some(&reference_guards))
5779 && (candidate.source() != file
5780 || self.preprocessor_guards_stable_between(
5781 file,
5782 0,
5783 reference.start_byte(),
5784 target_guards,
5785 ))
5786 }) {
5787 return false;
5788 }
5789
5790 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5791 }
5792
5793 pub fn external_type_candidate_guard_compatible_in_context(
5803 &self,
5804 analyzer: &CppGraphSource<'_>,
5805 file: &ProjectFile,
5806 candidate: &CodeUnit,
5807 reference: Node<'_>,
5808 ) -> bool {
5809 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5810 return false;
5811 };
5812 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5813
5814 self.visible_identifier_candidates(file, candidate.identifier())
5815 .filter(|peer| same_logical_symbol(candidate, peer))
5816 .any(|peer| {
5817 declaration_guard_requirements(analyzer, self.cpp, peer)
5818 .into_iter()
5819 .any(|(declaration_byte, declaration_guards)| {
5820 if peer.source() == file {
5821 let (start, end) = if declaration_byte <= reference.start_byte() {
5822 (declaration_byte, reference.start_byte())
5823 } else {
5824 (reference.start_byte(), declaration_byte)
5825 };
5826 return guard_requirements_hold_at_reference(
5827 &declaration_guards,
5828 reference_guards.as_ref(),
5829 ) && self.preprocessor_guards_stable_between(
5830 file,
5831 start,
5832 end,
5833 &declaration_guards,
5834 );
5835 }
5836 self.foreign_declaration_reachable_at_reference(
5837 file,
5838 prepared.as_ref(),
5839 peer.source(),
5840 &declaration_guards,
5841 reference_guards.as_ref(),
5842 reference.start_byte(),
5843 )
5844 })
5845 })
5846 }
5847
5848 pub fn same_file_callable_guard_compatible_ignoring_order(
5856 &self,
5857 analyzer: &CppGraphSource<'_>,
5858 file: &ProjectFile,
5859 candidate: &CodeUnit,
5860 reference: Node<'_>,
5861 ) -> bool {
5862 if candidate.source() != file || !candidate.is_callable() {
5863 return false;
5864 }
5865 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5866 return false;
5867 };
5868 let guards = OnceCell::new();
5869 let context = CallableReferenceContext {
5870 file,
5871 position: Some(CallableReferencePosition {
5872 prepared: prepared.as_ref(),
5873 byte: reference.start_byte(),
5874 guards: &guards,
5875 }),
5876 };
5877 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5878 .into_iter()
5879 .any(|declaration| {
5880 callable_preprocessor_context_is_visible_for_reference(
5881 declaration,
5882 prepared.source(),
5883 &context,
5884 )
5885 })
5886 }
5887
5888 pub fn type_candidate_may_be_visible_before_reference(
5889 &self,
5890 analyzer: &CppGraphSource<'_>,
5891 file: &ProjectFile,
5892 candidate: &CodeUnit,
5893 reference_byte: usize,
5894 ) -> bool {
5895 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5896 return false;
5897 };
5898 let root = prepared.tree().root_node();
5899 let end_byte = reference_byte
5900 .saturating_add(1)
5901 .min(prepared.source().len());
5902 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5903 return false;
5904 };
5905 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5906 }
5907
5908 pub fn preprocessor_guards_stable_between(
5909 &self,
5910 file: &ProjectFile,
5911 start_byte: usize,
5912 end_byte: usize,
5913 guards: &HashSet<PreprocessorGuard>,
5914 ) -> bool {
5915 if guards.is_empty() || start_byte >= end_byte {
5916 return true;
5917 }
5918 let cell = self.macro_event_cell(file);
5919 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5920 let mut visited = HashSet::from_iter([file.clone()]);
5921 !events.iter().any(|event| {
5922 event.byte() >= start_byte
5923 && event.byte() < end_byte
5924 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5925 })
5926 }
5927
5928 fn macro_event_may_mutate_guards(
5929 &self,
5930 event: &MacroEvent,
5931 guards: &HashSet<PreprocessorGuard>,
5932 visited: &mut HashSet<ProjectFile>,
5933 ) -> bool {
5934 match event {
5935 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5936 guards.iter().any(|guard| guard.may_depend_on_macro(name))
5937 }
5938 MacroEvent::Include { targets, .. } => {
5939 targets.is_empty()
5940 || targets
5941 .iter()
5942 .any(|target| self.source_may_mutate_guards(target, guards, visited))
5943 }
5944 MacroEvent::Invalidate { .. } => true,
5945 }
5946 }
5947
5948 fn source_may_mutate_guards(
5949 &self,
5950 file: &ProjectFile,
5951 guards: &HashSet<PreprocessorGuard>,
5952 visited: &mut HashSet<ProjectFile>,
5953 ) -> bool {
5954 if !visited.insert(file.clone()) {
5955 return false;
5956 }
5957 let cell = self.macro_event_cell(file);
5958 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5959 events
5960 .iter()
5961 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5962 }
5963
5964 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5965 let normalized = normalize_reference_name(raw_name)?;
5966 self.type_candidates(file, &normalized)
5967 .into_iter()
5968 .next()
5969 .cloned()
5970 }
5971
5972 pub fn unique_visible_parameter_type_fallback(
5981 &self,
5982 analyzer: &CppGraphSource<'_>,
5983 file: &ProjectFile,
5984 node: Node<'_>,
5985 source: &str,
5986 ) -> Option<CodeUnit> {
5987 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5988 return None;
5989 }
5990 let name = node_text(node, source);
5991 let candidates = self
5992 .visible_identifier_candidates(file, name)
5993 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5994 .filter(|candidate| {
5995 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5996 })
5997 .collect::<Vec<_>>();
5998 self.unique_canonical_type_candidate(analyzer, file, &candidates)
5999 }
6000
6001 pub fn resolve_type_node_result(
6002 &self,
6003 file: &ProjectFile,
6004 node: Node<'_>,
6005 source: &str,
6006 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6007 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6008 return Ok(None);
6009 };
6010 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6011 return Ok(Some(primary));
6012 };
6013 self.resolve_template_arguments(file, primary, &arguments)
6014 .map(Some)
6015 }
6016
6017 pub fn resolve_type_node_primary(
6018 &self,
6019 file: &ProjectFile,
6020 node: Node<'_>,
6021 source: &str,
6022 ) -> Option<CodeUnit> {
6023 let components = cpp_type_name_components(node, source)?;
6024 self.resolve_type(file, &components.join("::"))
6025 }
6026
6027 pub fn resolve_template_arguments(
6028 &self,
6029 file: &ProjectFile,
6030 primary: CodeUnit,
6031 arguments: &[CppTemplateExpression],
6032 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6033 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6034 }
6035
6036 fn resolve_template_arguments_inner(
6037 &self,
6038 file: &ProjectFile,
6039 primary: CodeUnit,
6040 arguments: &[CppTemplateExpression],
6041 seen_aliases: &mut HashSet<CodeUnit>,
6042 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6043 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6044 && let Some(alias_target) = &metadata.alias_target
6045 {
6046 if !seen_aliases.insert(primary.clone()) {
6047 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6048 }
6049 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6050 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6051 let target_name = alias_target.components.join("::");
6052 let target_primary = if alias_target.global {
6053 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6054 } else {
6055 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6056 };
6057 let Some(target_primary) = target_primary else {
6058 return Ok(primary);
6062 };
6063 let Some(target_arguments) = &alias_target.arguments else {
6064 return Ok(target_primary);
6065 };
6066 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6067 .ok_or(CppTemplateResolutionError::Substitution)?;
6068 return self.resolve_template_arguments_inner(
6069 file,
6070 target_primary,
6071 &target_arguments,
6072 seen_aliases,
6073 );
6074 }
6075
6076 let primary_fq_name = self
6077 .cpp_template_metadata
6078 .get(&primary)
6079 .map(|metadata| metadata.primary_fq_name.clone())
6080 .unwrap_or_else(|| primary.fq_name());
6081 let has_specialization_metadata = self
6082 .cpp_template_families
6083 .get(&primary_fq_name)
6084 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6085 if !has_specialization_metadata {
6086 return Ok(primary);
6087 }
6088 self.select_template_specialization(file, &primary, arguments)
6089 }
6090
6091 fn select_template_specialization(
6092 &self,
6093 file: &ProjectFile,
6094 resolved: &CodeUnit,
6095 explicit_arguments: &[CppTemplateExpression],
6096 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6097 let primary_fq_name = self
6098 .cpp_template_metadata
6099 .get(resolved)
6100 .map(|metadata| metadata.primary_fq_name.clone())
6101 .unwrap_or_else(|| resolved.fq_name());
6102 let family = self
6103 .cpp_template_families
6104 .get(&primary_fq_name)
6105 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6106 let primary_candidates = family
6107 .iter()
6108 .filter_map(|unit| {
6109 let metadata = self.cpp_template_metadata.get(unit)?;
6110 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6111 })
6112 .collect::<Vec<_>>();
6113 let primary_unit = primary_candidates
6114 .iter()
6115 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6116 .or_else(|| {
6117 primary_candidates
6118 .iter()
6119 .map(|(unit, _)| *unit)
6120 .min_by_key(|unit| {
6121 (
6122 unit.source().to_string(),
6123 unit.signature().unwrap_or_default(),
6124 )
6125 })
6126 })
6127 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6128 let primary_parameters =
6129 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6130 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6131 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6132 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6133
6134 let mut applicable = Vec::new();
6135 for unit in family {
6136 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6137 continue;
6138 };
6139 if metadata.is_primary() || !self.is_visible(file, unit) {
6140 continue;
6141 }
6142 if !cpp_specialization_matches(metadata, &expanded) {
6143 continue;
6144 }
6145 applicable.push((unit, metadata));
6146 }
6147 if applicable.is_empty() {
6148 return Ok(primary_unit.clone());
6149 }
6150
6151 let winners = applicable
6156 .iter()
6157 .filter(|(candidate, candidate_metadata)| {
6158 applicable.iter().all(|(other, other_metadata)| {
6159 same_visible_symbol(candidate, other)
6160 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6161 })
6162 })
6163 .copied()
6164 .collect::<Vec<_>>();
6165 let Some((selected, _)) = winners.first() else {
6166 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6169 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6170 });
6171 };
6172 if winners
6173 .iter()
6174 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6175 {
6176 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6177 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6178 });
6179 }
6180 Ok((*selected).clone())
6181 }
6182
6183 pub fn resolve_type_components_lexically(
6184 &self,
6185 analyzer: &CppGraphSource<'_>,
6186 file: &ProjectFile,
6187 components: &[String],
6188 global: bool,
6189 lexical_scope: &[String],
6190 ) -> LexicalTypeResolution {
6191 self.resolve_type_components_lexically_inner(
6192 analyzer,
6193 file,
6194 components,
6195 global,
6196 lexical_scope,
6197 TypeCandidateResolution::Canonical,
6198 )
6199 }
6200
6201 pub fn resolve_type_components_lexically_for_forward(
6202 &self,
6203 analyzer: &CppGraphSource<'_>,
6204 file: &ProjectFile,
6205 components: &[String],
6206 global: bool,
6207 lexical_scope: &[String],
6208 ) -> LexicalTypeResolution {
6209 self.resolve_type_components_lexically_inner(
6210 analyzer,
6211 file,
6212 components,
6213 global,
6214 lexical_scope,
6215 TypeCandidateResolution::PreserveAlias,
6216 )
6217 }
6218
6219 pub fn resolve_type_components_lexically_for_target(
6220 &self,
6221 analyzer: &CppGraphSource<'_>,
6222 file: &ProjectFile,
6223 components: &[String],
6224 global: bool,
6225 lexical_scope: &[String],
6226 target: &CodeUnit,
6227 ) -> LexicalTypeResolution {
6228 #[cfg(any(test, feature = "test-support"))]
6229 self.target_preserving_type_resolution_count
6230 .fetch_add(1, Ordering::Relaxed);
6231 self.resolve_type_components_lexically_inner(
6232 analyzer,
6233 file,
6234 components,
6235 global,
6236 lexical_scope,
6237 TypeCandidateResolution::PreserveTarget(target),
6238 )
6239 }
6240
6241 pub fn coarse_unqualified_type_reference_may_resolve(
6242 &self,
6243 file: &ProjectFile,
6244 name: &str,
6245 ) -> bool {
6246 if name.is_empty() {
6247 return true;
6248 }
6249 self.visible_identifier_candidates(file, name)
6250 .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
6251 || self.visible_parser_alias_name_is_visible(file, name)
6252 }
6253
6254 #[allow(clippy::too_many_arguments)]
6255 pub fn structured_type_reference_may_resolve_to_target(
6256 &self,
6257 analyzer: &CppGraphSource<'_>,
6258 file: &ProjectFile,
6259 components: &[String],
6260 global: bool,
6261 lexical_scope: &[String],
6262 target: &CodeUnit,
6263 ) -> bool {
6264 if components.is_empty() {
6265 return true;
6266 }
6267 let Some(terminal) = components.last() else {
6268 return true;
6269 };
6270 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6271 .map(|qualified| qualified.join("::"))
6272 .collect::<Vec<_>>();
6273 let target_name = cpp_name_for(target);
6274 if qualified_tiers
6275 .iter()
6276 .any(|qualified| qualified == &target_name)
6277 {
6278 return true;
6279 }
6280
6281 let mut saw_shape_candidate = false;
6282 for candidate in self.visible_identifier_candidates(file, terminal) {
6283 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6284 {
6285 continue;
6286 }
6287 let candidate_name = cpp_name_for(candidate);
6288 let shape_matches = if global || components.len() > 1 {
6289 qualified_tiers
6290 .iter()
6291 .any(|qualified| qualified == &candidate_name)
6292 } else {
6293 true
6294 };
6295 if !shape_matches {
6296 continue;
6297 }
6298 saw_shape_candidate = true;
6299 if same_visible_symbol(candidate, target)
6300 || self.c_tag_declaration_family_matches_target(
6301 analyzer,
6302 file,
6303 std::slice::from_ref(&candidate),
6304 target,
6305 )
6306 || self.compatible_primary_template_redeclarations(candidate, target)
6307 || (declared_type_alias(analyzer, candidate)
6308 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6309 {
6310 return true;
6311 }
6312 }
6313
6314 !saw_shape_candidate
6315 }
6316
6317 fn cached_c_tag_kind(
6324 &self,
6325 analyzer: &CppGraphSource<'_>,
6326 candidate: &CodeUnit,
6327 ) -> Option<CppCTagKind> {
6328 if let Some(kind) = self
6329 .c_tag_kind_cache
6330 .lock()
6331 .expect("C tag kind cache poisoned")
6332 .get(candidate)
6333 {
6334 return *kind;
6335 }
6336 let kind = indexed_c_tag_kind(analyzer, candidate);
6337 self.c_tag_kind_cache
6338 .lock()
6339 .expect("C tag kind cache poisoned")
6340 .insert(candidate.clone(), kind);
6341 kind
6342 }
6343
6344 fn cached_unique_c_tag_complete_definition(
6345 &self,
6346 analyzer: &CppGraphSource<'_>,
6347 target: &CodeUnit,
6348 target_tag: CppCTagKind,
6349 ) -> Option<CodeUnit> {
6350 if let Some(definition) = self
6351 .c_tag_complete_definition_cache
6352 .lock()
6353 .expect("C tag complete-definition cache poisoned")
6354 .get(target)
6355 {
6356 return definition.clone();
6357 }
6358 let complete_definitions = analyzer
6359 .definitions(&target.fq_name())
6360 .filter(|candidate| {
6361 candidate.is_class()
6362 && !declared_type_alias(analyzer, candidate)
6363 && is_c_source_file(candidate.source())
6364 && analyzer.parent_of(candidate).is_none()
6365 && cpp_class_declaration_strength(analyzer, candidate)
6366 == CppClassDeclarationStrength::Full
6367 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6368 })
6369 .collect::<HashSet<_>>();
6370 let definition = (complete_definitions.len() == 1)
6371 .then(|| complete_definitions.into_iter().next())
6372 .flatten()
6373 .filter(|candidate| same_visible_symbol(candidate, target));
6374 self.c_tag_complete_definition_cache
6375 .lock()
6376 .expect("C tag complete-definition cache poisoned")
6377 .insert(target.clone(), definition.clone());
6378 definition
6379 }
6380
6381 pub fn c_tag_declaration_family_matches_target(
6382 &self,
6383 analyzer: &CppGraphSource<'_>,
6384 visible_from: &ProjectFile,
6385 candidates: &[&CodeUnit],
6386 target: &CodeUnit,
6387 ) -> bool {
6388 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6389 let candidate_evidence = candidates
6390 .iter()
6391 .map(|candidate| {
6392 (
6393 candidate.fq_name(),
6394 candidate.source().rel_path().to_path_buf(),
6395 cpp_class_declaration_strength(analyzer, candidate),
6396 indexed_c_tag_kind(analyzer, candidate),
6397 self.is_physically_visible(visible_from, candidate),
6398 )
6399 })
6400 .collect::<Vec<_>>();
6401 eprintln!(
6402 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6403 visible_from.rel_path().display(),
6404 target.fq_name(),
6405 target.source().rel_path().display(),
6406 cpp_class_declaration_strength(analyzer, target),
6407 indexed_c_tag_kind(analyzer, target),
6408 );
6409 }
6410 if candidates.is_empty()
6411 || !target.is_class()
6412 || declared_type_alias(analyzer, target)
6413 || !is_c_source_file(target.source())
6414 || analyzer.parent_of(target).is_some()
6415 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6416 {
6417 return false;
6418 }
6419 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6420 return false;
6421 };
6422 if self
6423 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6424 .is_none()
6425 {
6426 return false;
6427 }
6428 let mut saw_visible_forward = false;
6429 for candidate in candidates.iter().copied() {
6430 if candidate == target {
6431 continue;
6432 }
6433 if !candidate.is_class()
6434 || declared_type_alias(analyzer, candidate)
6435 || candidate.fq_name() != target.fq_name()
6436 || analyzer.parent_of(candidate).is_some()
6437 || cpp_class_declaration_strength(analyzer, candidate)
6438 != CppClassDeclarationStrength::Forward
6439 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6440 || !self.is_physically_visible(visible_from, candidate)
6441 {
6442 return false;
6443 }
6444 saw_visible_forward = true;
6445 }
6446 saw_visible_forward
6447 }
6448
6449 fn unique_c_tag_declaration_family(
6454 &self,
6455 analyzer: &CppGraphSource<'_>,
6456 visible_from: &ProjectFile,
6457 candidates: &[&CodeUnit],
6458 ) -> Option<CodeUnit> {
6459 let first = candidates.first()?;
6460 let target_fq_name = first.fq_name();
6461 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6462 let mut full = None;
6463 let mut saw_forward = false;
6464 for candidate in candidates.iter().copied() {
6465 if !candidate.is_class()
6466 || declared_type_alias(analyzer, candidate)
6467 || candidate.fq_name() != target_fq_name
6468 || analyzer.parent_of(candidate).is_some()
6469 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6470 {
6471 return None;
6472 }
6473 match cpp_class_declaration_strength(analyzer, candidate) {
6474 CppClassDeclarationStrength::Full
6475 if is_c_source_file(candidate.source())
6476 && full.replace(candidate.clone()).is_none() => {}
6477 CppClassDeclarationStrength::Forward
6478 if self.is_physically_visible(visible_from, candidate) =>
6479 {
6480 saw_forward = true
6481 }
6482 _ => return None,
6483 }
6484 }
6485 if saw_forward { full } else { None }
6486 }
6487
6488 pub fn target_preserving_reference_namespace(
6489 &self,
6490 analyzer: &CppGraphSource<'_>,
6491 file: &ProjectFile,
6492 identifier: &str,
6493 target: &CodeUnit,
6494 ) -> Option<Vec<String>> {
6495 let mut namespace = None;
6496 for candidate in self.visible_identifier_candidates(file, identifier) {
6497 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6498 {
6499 continue;
6500 }
6501 if !(same_visible_symbol(candidate, target)
6502 || self.compatible_primary_template_redeclarations(candidate, target)
6503 || declared_type_alias(analyzer, candidate)
6504 && self.structured_alias_primary_preserves_target(
6505 analyzer, file, candidate, target,
6506 ))
6507 {
6508 continue;
6509 }
6510 if namespace
6511 .as_ref()
6512 .is_some_and(|existing| existing != candidate.package_name())
6513 {
6514 return None;
6515 }
6516 namespace = Some(candidate.package_name().to_string());
6517 }
6518 let namespace = namespace?;
6519 Some(
6520 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6521 brokk_bifrost_core::analyzer::Language::Cpp,
6522 &namespace,
6523 ),
6524 )
6525 }
6526
6527 pub fn resolve_imported_type_candidate(
6528 &self,
6529 analyzer: &CppGraphSource<'_>,
6530 file: &ProjectFile,
6531 target: &CodeUnit,
6532 target_components: &[String],
6533 direct_target: Option<&CodeUnit>,
6534 preserve_alias: bool,
6535 ) -> LexicalTypeResolution {
6536 let candidates = [target];
6537 let resolution = if preserve_alias {
6538 TypeCandidateResolution::PreserveAlias
6539 } else {
6540 direct_target.map_or(
6541 TypeCandidateResolution::Canonical,
6542 TypeCandidateResolution::PreserveTarget,
6543 )
6544 };
6545 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6549 Ok(unit) => LexicalTypeResolution::Resolved {
6550 unit,
6551 components: target_components.to_vec(),
6552 candidates: vec![target.clone()],
6553 },
6554 Err(failure) => failure.lexical_resolution(),
6555 }
6556 }
6557
6558 fn resolve_type_components_lexically_inner(
6559 &self,
6560 analyzer: &CppGraphSource<'_>,
6561 file: &ProjectFile,
6562 components: &[String],
6563 global: bool,
6564 lexical_scope: &[String],
6565 resolution: TypeCandidateResolution<'_>,
6566 ) -> LexicalTypeResolution {
6567 if components.is_empty() {
6568 return LexicalTypeResolution::Missing;
6569 }
6570 let mut injected = self.resolve_injected_class_name(
6580 analyzer,
6581 file,
6582 components,
6583 global,
6584 lexical_scope,
6585 resolution,
6586 );
6587 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6588 let prefix_len = qualified.len().saturating_sub(components.len());
6589 if injected
6590 .as_ref()
6591 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6592 {
6593 return injected
6594 .take()
6595 .expect("injected class resolution was just present")
6596 .1;
6597 }
6598 let qualified_name = qualified.join("::");
6599 let candidates = self
6600 .type_candidates(file, &qualified_name)
6601 .into_iter()
6602 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6603 .collect::<Vec<_>>();
6604 if candidates.is_empty() {
6605 if !global && components.len() == 1 {
6606 match self.resolve_inherited_type_for_lexical_scope(
6607 analyzer,
6608 file,
6609 &qualified[..prefix_len],
6610 &components[0],
6611 resolution,
6612 ) {
6613 LexicalTypeResolution::Missing => {}
6614 inherited => return inherited,
6615 }
6616 }
6617 continue;
6618 }
6619 let candidates =
6620 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6621 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6622 Ok(unit) => unit,
6623 Err(failure) => return failure.lexical_resolution(),
6624 };
6625 return LexicalTypeResolution::Resolved {
6626 unit,
6627 components: qualified,
6628 candidates: candidates.into_iter().cloned().collect(),
6629 };
6630 }
6631 LexicalTypeResolution::Missing
6632 }
6633
6634 fn resolve_injected_class_name(
6635 &self,
6636 analyzer: &CppGraphSource<'_>,
6637 file: &ProjectFile,
6638 components: &[String],
6639 global: bool,
6640 lexical_scope: &[String],
6641 resolution: TypeCandidateResolution<'_>,
6642 ) -> Option<(usize, LexicalTypeResolution)> {
6643 if global
6644 || components.len() != 1
6645 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6646 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6647 {
6648 return None;
6649 }
6650 let name = components.first()?;
6651 let mut matches: Vec<&CodeUnit> = Vec::new();
6652 let mut owner_len = 0;
6653 for candidate in self.visible_identifier_candidates(file, name) {
6654 if !candidate.is_class()
6655 || declared_type_alias(analyzer, candidate)
6656 || candidate.identifier() != name
6657 {
6658 continue;
6659 }
6660 let candidate_scope = canonical_cpp_scope_components(candidate);
6661 if candidate_scope.len() > lexical_scope.len()
6662 || !lexical_scope.starts_with(&candidate_scope)
6663 || candidate_scope.last().is_none_or(|last| last != name)
6664 {
6665 continue;
6666 }
6667 if candidate_scope.len() > owner_len {
6668 owner_len = candidate_scope.len();
6669 matches.clear();
6670 }
6671 if candidate_scope.len() == owner_len
6672 && !matches
6673 .iter()
6674 .any(|existing| same_logical_symbol(existing, candidate))
6675 {
6676 matches.push(candidate);
6677 }
6678 }
6679 if matches.is_empty() {
6680 return None;
6681 }
6682 let owner_components = lexical_scope[..owner_len].to_vec();
6689 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6690 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6691 Ok(unit) => LexicalTypeResolution::Resolved {
6692 unit,
6693 components: owner_components,
6694 candidates: matches.into_iter().cloned().collect(),
6695 },
6696 Err(failure) => failure.lexical_resolution(),
6697 };
6698 Some((owner_len, resolution))
6699 }
6700
6701 fn resolve_inherited_type_for_lexical_scope(
6702 &self,
6703 analyzer: &CppGraphSource<'_>,
6704 file: &ProjectFile,
6705 lexical_scope: &[String],
6706 name: &str,
6707 resolution: TypeCandidateResolution<'_>,
6708 ) -> LexicalTypeResolution {
6709 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6710 return LexicalTypeResolution::Missing;
6711 };
6712 let lexical_owner_name = lexical_scope.join("::");
6713 if lexical_owner_name.is_empty() {
6714 return LexicalTypeResolution::Missing;
6715 }
6716 let owner_candidates = self
6717 .type_candidates(file, &lexical_owner_name)
6718 .into_iter()
6719 .filter(|candidate| {
6720 canonical_cpp_name_matches(candidate, &lexical_owner_name)
6721 && !declared_type_alias(analyzer, candidate)
6722 })
6723 .collect::<Vec<_>>();
6724 if owner_candidates.is_empty() {
6725 return LexicalTypeResolution::Missing;
6726 }
6727 let physical_owner_candidates = owner_candidates
6732 .iter()
6733 .copied()
6734 .filter(|candidate| candidate.source() == file)
6735 .collect::<Vec<_>>();
6736 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6737 owner_candidates
6738 } else {
6739 physical_owner_candidates
6740 };
6741 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6742 return LexicalTypeResolution::Ambiguous;
6743 };
6744
6745 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6746 let mut visited_owners = HashSet::default();
6747 while !frontier.is_empty() {
6748 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6749 let mut next_frontier = Vec::new();
6750 for owner in frontier {
6751 if !visited_owners.insert(owner.fq_name()) {
6752 continue;
6753 }
6754 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6755 let candidates = self
6756 .type_candidates(file, &qualified_name)
6757 .into_iter()
6758 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6759 .collect::<Vec<_>>();
6760 if candidates.is_empty() {
6761 for ancestor in hierarchy.get_direct_ancestors(&owner) {
6762 if !next_frontier
6763 .iter()
6764 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6765 {
6766 next_frontier.push(ancestor);
6767 }
6768 }
6769 continue;
6770 }
6771 let candidates =
6772 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6773 let unit =
6774 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6775 Ok(unit) => unit,
6776 Err(failure) => return failure.lexical_resolution(),
6777 };
6778 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6779 }
6780 if let Some((unit, candidates)) = level_matches.first().cloned() {
6781 let Some(first_declaration) = candidates.first() else {
6782 return LexicalTypeResolution::Ambiguous;
6783 };
6784 if !level_matches.iter().all(|(_, declarations)| {
6785 declarations
6786 .iter()
6787 .all(|declaration| same_logical_symbol(first_declaration, declaration))
6788 }) {
6789 return LexicalTypeResolution::Ambiguous;
6790 }
6791 let mut components = lexical_scope.to_vec();
6792 components.push(name.to_string());
6793 return LexicalTypeResolution::Resolved {
6794 unit,
6795 components,
6796 candidates,
6797 };
6798 }
6799 frontier = next_frontier;
6800 }
6801 LexicalTypeResolution::Missing
6802 }
6803
6804 pub fn inherited_injected_class_owner(
6821 &self,
6822 analyzer: &CppGraphSource<'_>,
6823 file: &ProjectFile,
6824 enclosing_owner: &CodeUnit,
6825 injected_name: &str,
6826 ) -> Option<CodeUnit> {
6827 let hierarchy = analyzer.type_hierarchy_provider()?;
6828 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6829 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6830 while !frontier.is_empty() {
6831 let mut level_matches = Vec::new();
6832 let mut next_frontier = Vec::new();
6833 for raw_owner in frontier {
6834 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6835 else {
6836 if raw_owner.identifier() == injected_name {
6837 return None;
6838 }
6839 continue;
6840 };
6841 let propagated = propagated_counts.entry(owner.clone()).or_default();
6842 if *propagated == 2 {
6843 continue;
6844 }
6845 *propagated += 1;
6846 if owner.identifier() == injected_name {
6847 level_matches.push(owner.clone());
6848 }
6849 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6850 }
6851 match level_matches.as_slice() {
6852 [owner] => return Some(owner.clone()),
6853 [_, ..] => return None,
6854 [] => {}
6855 }
6856 frontier = next_frontier;
6857 }
6858 None
6859 }
6860
6861 fn resolve_type_candidates(
6866 &self,
6867 analyzer: &CppGraphSource<'_>,
6868 file: &ProjectFile,
6869 candidates: &[&CodeUnit],
6870 resolution: TypeCandidateResolution<'_>,
6871 ) -> Result<CodeUnit, TypeCandidateFailure> {
6872 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6873 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6874 {
6875 return Ok(unit);
6876 }
6877 match resolution {
6878 TypeCandidateResolution::Canonical => {
6879 self.canonical_type_candidate_resolution(analyzer, file, candidates)
6880 }
6881 TypeCandidateResolution::PreserveAlias => {
6882 let same_fqn_alias_family = candidates.len() > 1
6889 && candidates.iter().all(|candidate| {
6890 declared_type_alias(analyzer, candidate)
6891 && same_logical_symbol(candidates[0], candidate)
6892 })
6893 && candidates
6894 .iter()
6895 .any(|candidate| candidate.source() != candidates[0].source());
6896 if same_fqn_alias_family {
6897 let physically_visible = candidates
6898 .iter()
6899 .copied()
6900 .filter(|candidate| self.is_physically_visible(file, candidate))
6901 .collect::<Vec<_>>();
6902 let one_structured_target = physically_visible.len() > 1
6912 && physically_visible.iter().skip(1).all(|candidate| {
6913 let target = self.structured_alias_target(analyzer, candidate);
6914 target.is_some()
6915 && target
6916 == self.structured_alias_target(analyzer, physically_visible[0])
6917 });
6918 if physically_visible.len() == 1 || one_structured_target {
6919 return Ok(physically_visible[0].clone());
6920 }
6921 }
6922 unique_type_candidate_preserving_alias(analyzer, file, candidates)
6923 .ok_or(TypeCandidateFailure::Ambiguous)
6924 }
6925 TypeCandidateResolution::PreserveTarget(target) => self
6926 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6927 .ok_or(TypeCandidateFailure::Ambiguous),
6928 }
6929 }
6930
6931 fn candidates_for_type_resolution<'b>(
6932 &self,
6933 analyzer: &CppGraphSource<'_>,
6934 file: &ProjectFile,
6935 candidates: &[&'b CodeUnit],
6936 resolution: TypeCandidateResolution<'_>,
6937 ) -> Vec<&'b CodeUnit> {
6938 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6939 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6940 if compile_proven.len() == 1 {
6941 return compile_proven;
6942 }
6943 }
6944 candidates.to_vec()
6945 }
6946
6947 fn compile_proven_type_candidates<'b>(
6954 &self,
6955 analyzer: &CppGraphSource<'_>,
6956 file: &ProjectFile,
6957 candidates: &[&'b CodeUnit],
6958 ) -> Vec<&'b CodeUnit> {
6959 let proven = self.compile_proven_guards(file);
6960 if proven.is_empty() {
6961 return Vec::new();
6962 }
6963 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6964 return Vec::new();
6965 };
6966 candidates
6967 .iter()
6968 .copied()
6969 .filter(|candidate| {
6970 let declaration_guards =
6971 declaration_guard_requirements(analyzer, self.cpp, candidate);
6972 if declaration_guards.is_empty() {
6973 return false;
6974 }
6975 if candidate.source() == file {
6976 return declaration_guards.iter().any(|(_, required)| {
6977 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6978 });
6979 }
6980 declaration_guards.iter().any(|(_, required)| {
6981 self.foreign_declaration_reachable_from_compile_proven_guards(
6982 file,
6983 prepared.as_ref(),
6984 candidate.source(),
6985 required,
6986 usize::MAX,
6987 )
6988 })
6989 })
6990 .collect()
6991 }
6992
6993 pub fn resolve_callable_value_components_lexically(
6994 &self,
6995 analyzer: &CppGraphSource<'_>,
6996 file: &ProjectFile,
6997 owner_components: &[String],
6998 member_name: &str,
6999 global: bool,
7000 lexical_scope: &[String],
7001 ) -> LexicalCallableValueResolution {
7002 if owner_components.is_empty() || member_name.is_empty() {
7003 return LexicalCallableValueResolution::Missing;
7004 }
7005 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7006 let owner_name = qualified_owner.join("::");
7007 let type_candidates = self
7008 .type_candidates(file, &owner_name)
7009 .into_iter()
7010 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7011 .collect::<Vec<_>>();
7012 let resolved_type = if type_candidates.is_empty() {
7013 None
7014 } else {
7015 let Some(unit) =
7016 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7017 else {
7018 return LexicalCallableValueResolution::Ambiguous;
7019 };
7020 Some(unit)
7021 };
7022
7023 let mut qualified_callable = qualified_owner;
7024 qualified_callable.push(member_name.to_string());
7025 let callable_name = qualified_callable.join("::");
7026 let free_function = self
7027 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7028 .into_iter()
7029 .find(|candidate| {
7030 canonical_cpp_name_matches(candidate, &callable_name)
7031 && type_owner_of(analyzer, candidate).is_none()
7032 })
7033 .cloned();
7034
7035 match (resolved_type, free_function) {
7036 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7037 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7038 (None, Some(function)) => {
7039 return LexicalCallableValueResolution::FreeFunction(function);
7040 }
7041 (None, None) => {}
7042 }
7043 }
7044 LexicalCallableValueResolution::Missing
7045 }
7046
7047 fn resolve_type_for_declaration(
7048 &self,
7049 visible_from: &ProjectFile,
7050 declaration: &CodeUnit,
7051 raw_name: &str,
7052 ) -> Option<CodeUnit> {
7053 let normalized = normalize_reference_name(raw_name)?;
7054 if !normalized.contains("::")
7055 && let Some(namespace) = cpp_namespace_for(declaration)
7056 {
7057 for prefix in namespace_prefixes(&namespace) {
7058 let qualified = format!("{prefix}::{normalized}");
7059 if let Some(unit) = self
7060 .type_candidates(visible_from, &qualified)
7061 .into_iter()
7062 .next()
7063 {
7064 return Some(unit.clone());
7065 }
7066 }
7067 }
7068 self.resolve_type(visible_from, raw_name)
7069 }
7070
7071 fn resolve_unique_canonical_type_for_declaration(
7072 &self,
7073 analyzer: &CppGraphSource<'_>,
7074 visible_from: &ProjectFile,
7075 declaration: &CodeUnit,
7076 raw_name: &str,
7077 ) -> Option<CodeUnit> {
7078 let mut current = self.resolve_defining_type_for_declaration(
7079 analyzer,
7080 visible_from,
7081 declaration,
7082 raw_name,
7083 )?;
7084 let mut seen_aliases = HashSet::default();
7085 loop {
7086 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7087 return current.is_class().then_some(current);
7088 };
7089 if matches!(target, StructuredAliasTarget::Builtin) {
7090 return current.is_class().then_some(current);
7091 }
7092 if !seen_aliases.insert(current.clone()) {
7093 return None;
7094 }
7095 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
7096 }
7097 }
7098
7099 pub fn canonical_type_unit(
7100 &self,
7101 analyzer: &CppGraphSource<'_>,
7102 visible_from: &ProjectFile,
7103 unit: &CodeUnit,
7104 ) -> Option<CodeUnit> {
7105 self.canonical_type_resolution(analyzer, visible_from, unit)
7106 .ok()
7107 }
7108
7109 pub fn canonical_type_unit_in_context(
7117 &self,
7118 analyzer: &CppGraphSource<'_>,
7119 visible_from: &ProjectFile,
7120 reference: Node<'_>,
7121 unit: &CodeUnit,
7122 ) -> Option<CodeUnit> {
7123 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7124 {
7125 return None;
7126 }
7127 self.canonical_type_resolution(analyzer, visible_from, unit)
7128 .ok()
7129 }
7130
7131 fn canonical_type_resolution(
7139 &self,
7140 analyzer: &CppGraphSource<'_>,
7141 visible_from: &ProjectFile,
7142 unit: &CodeUnit,
7143 ) -> Result<CodeUnit, TypeCandidateFailure> {
7144 let mut current = unit.clone();
7145 let mut seen_aliases = HashSet::default();
7146 loop {
7147 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7148 return current
7149 .is_class()
7150 .then_some(current)
7151 .ok_or(TypeCandidateFailure::Unresolvable);
7152 };
7153 if matches!(target, StructuredAliasTarget::Builtin) {
7154 return current
7155 .is_class()
7156 .then_some(current)
7157 .ok_or(TypeCandidateFailure::Unresolvable);
7158 }
7159 if !seen_aliases.insert(current.clone()) {
7160 return Err(TypeCandidateFailure::Unresolvable);
7161 }
7162 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7163 }
7164 }
7165
7166 pub fn canonical_visible_full_type_unit(
7167 &self,
7168 analyzer: &CppGraphSource<'_>,
7169 visible_from: &ProjectFile,
7170 unit: &CodeUnit,
7171 ) -> Option<CodeUnit> {
7172 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7173 if cpp_class_declaration_strength(analyzer, &canonical)
7174 != CppClassDeclarationStrength::Forward
7175 {
7176 return Some(canonical);
7177 }
7178 let mut full = Vec::new();
7179 for candidate in self
7180 .visible_identifier_candidates(visible_from, canonical.identifier())
7181 .filter(|candidate| {
7182 candidate.is_class()
7183 && candidate.fq_name() == canonical.fq_name()
7184 && cpp_class_declaration_strength(analyzer, candidate)
7185 == CppClassDeclarationStrength::Full
7186 })
7187 {
7188 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7189 full.push(candidate.clone());
7190 }
7191 }
7192 match full.len() {
7193 0 => Some(canonical),
7194 1 => full.pop(),
7195 _ => None,
7196 }
7197 }
7198
7199 fn resolve_structured_alias_target(
7200 &self,
7201 visible_from: &ProjectFile,
7202 declaration: &CodeUnit,
7203 target: &StructuredAliasTarget,
7204 ) -> Option<CodeUnit> {
7205 self.structured_alias_target_resolution(visible_from, declaration, target)
7206 .ok()
7207 }
7208
7209 fn structured_alias_target_resolution(
7210 &self,
7211 visible_from: &ProjectFile,
7212 declaration: &CodeUnit,
7213 target: &StructuredAliasTarget,
7214 ) -> Result<CodeUnit, TypeCandidateFailure> {
7215 let primary =
7216 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7217 let StructuredAliasTarget::Named { arguments, .. } = target else {
7218 return Err(TypeCandidateFailure::Unresolvable);
7219 };
7220 match arguments {
7221 Some(arguments) => self
7222 .resolve_template_arguments(visible_from, primary, arguments)
7223 .map_err(|error| match error {
7224 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7225 TypeCandidateFailure::Ambiguous
7226 }
7227 _ => TypeCandidateFailure::Unresolvable,
7228 }),
7229 None => Ok(primary),
7230 }
7231 }
7232
7233 fn resolve_structured_alias_primary(
7234 &self,
7235 visible_from: &ProjectFile,
7236 declaration: &CodeUnit,
7237 target: &StructuredAliasTarget,
7238 ) -> Option<CodeUnit> {
7239 self.structured_alias_primary_resolution(visible_from, declaration, target)
7240 .ok()
7241 }
7242
7243 fn structured_alias_primary_resolution(
7244 &self,
7245 visible_from: &ProjectFile,
7246 declaration: &CodeUnit,
7247 target: &StructuredAliasTarget,
7248 ) -> Result<CodeUnit, TypeCandidateFailure> {
7249 let StructuredAliasTarget::Named {
7250 components, global, ..
7251 } = target
7252 else {
7253 return Err(TypeCandidateFailure::Unresolvable);
7254 };
7255 let qualified = components.join("::");
7256 let candidates = if *global {
7257 let mut candidates = self.type_candidates(visible_from, &qualified);
7264 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7265 candidates
7266 } else {
7267 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7268 };
7269 logical_type_candidate(candidates)
7270 }
7271
7272 pub fn structured_alias_primary_preserves_target(
7273 &self,
7274 analyzer: &CppGraphSource<'_>,
7275 visible_from: &ProjectFile,
7276 candidate: &CodeUnit,
7277 target: &CodeUnit,
7278 ) -> bool {
7279 let mut current = candidate.clone();
7280 let mut seen = HashSet::default();
7281 let mut matched_target = false;
7282 loop {
7283 if same_visible_symbol(¤t, target)
7284 || self.compatible_primary_template_redeclarations(¤t, target)
7285 {
7286 matched_target = true;
7287 }
7288 if !seen.insert(current.clone()) {
7289 return false;
7290 }
7291 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7292 return matched_target;
7293 };
7294 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7295 return matched_target;
7296 };
7297 let Some(primary) =
7298 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7299 else {
7300 return matched_target;
7306 };
7307 current = primary;
7308 }
7309 }
7310
7311 pub fn structured_class_alias_resolves_to_target(
7312 &self,
7313 analyzer: &CppGraphSource<'_>,
7314 visible_from: &ProjectFile,
7315 alias: &CodeUnit,
7316 target: &CodeUnit,
7317 ) -> bool {
7318 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7319 return false;
7320 };
7321 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7322 return false;
7323 };
7324 let StructuredAliasTarget::Named {
7325 components, global, ..
7326 } = &alias_target
7327 else {
7328 return false;
7329 };
7330 let lexical_scope = canonical_cpp_scope_components(&owner);
7331 match self.resolve_type_components_lexically_for_target(
7332 analyzer,
7333 visible_from,
7334 components,
7335 *global,
7336 &lexical_scope,
7337 target,
7338 ) {
7339 LexicalTypeResolution::Resolved {
7340 unit, candidates, ..
7341 } => {
7342 same_visible_symbol(&unit, target)
7343 || self.same_template_member_identity(analyzer, &unit, target)
7344 || candidates.iter().any(|candidate| {
7345 same_visible_symbol(candidate, target)
7346 || self.same_template_member_identity(analyzer, candidate, target)
7347 })
7348 }
7349 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7350 self.structured_alias_primary_preserves_target(
7351 analyzer,
7352 visible_from,
7353 alias,
7354 target,
7355 ) || self.flattened_macro_namespace_alias_target_matches(
7356 analyzer,
7357 visible_from,
7358 alias,
7359 &alias_target,
7360 target,
7361 )
7362 }
7363 }
7364 }
7365
7366 pub fn structured_class_alias_path_preserves_target(
7374 &self,
7375 analyzer: &CppGraphSource<'_>,
7376 visible_from: &ProjectFile,
7377 alias: &CodeUnit,
7378 target: &CodeUnit,
7379 ) -> bool {
7380 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7381 return false;
7382 };
7383 let Some(StructuredAliasTarget::Named {
7384 components, global, ..
7385 }) = self.structured_alias_target(analyzer, alias)
7386 else {
7387 return false;
7388 };
7389 let lexical_scope = canonical_cpp_scope_components(&owner);
7390 (1..components.len()).rev().any(|component_count| {
7391 matches!(
7392 self.resolve_type_components_lexically_for_target(
7393 analyzer,
7394 visible_from,
7395 &components[..component_count],
7396 global,
7397 &lexical_scope,
7398 target,
7399 ),
7400 LexicalTypeResolution::Resolved {
7401 ref unit,
7402 ref candidates,
7403 ..
7404 } if same_visible_symbol(unit, target)
7405 || self.same_template_member_identity(analyzer, unit, target)
7406 || candidates.iter().any(|candidate| {
7407 same_visible_symbol(candidate, target)
7408 || self.same_template_member_identity(analyzer, candidate, target)
7409 })
7410 )
7411 })
7412 }
7413
7414 fn flattened_macro_namespace_alias_target_matches(
7415 &self,
7416 analyzer: &CppGraphSource<'_>,
7417 visible_from: &ProjectFile,
7418 alias: &CodeUnit,
7419 alias_target: &StructuredAliasTarget,
7420 target: &CodeUnit,
7421 ) -> bool {
7422 let StructuredAliasTarget::Named {
7423 components,
7424 global: false,
7425 arguments: None,
7426 } = alias_target
7427 else {
7428 return false;
7429 };
7430 let Some((target_name, namespace_components)) = components.split_last() else {
7431 return false;
7432 };
7433 if namespace_components.is_empty()
7434 || target_name != target.identifier()
7435 || alias.source() != target.source()
7436 || alias.source() != visible_from
7437 || !target.is_class()
7438 || declared_type_alias(analyzer, target)
7439 {
7440 return false;
7441 }
7442 if self
7443 .resolve_structured_alias_target(visible_from, alias, alias_target)
7444 .is_some()
7445 {
7446 return false;
7447 }
7448
7449 let alias_ranges = analyzer.ranges(alias);
7450 let target_ranges = analyzer.ranges(target);
7451 if alias_ranges.is_empty() || target_ranges.is_empty() {
7452 return false;
7453 }
7454 let alias_start = alias_ranges
7455 .iter()
7456 .map(|range| range.start_byte)
7457 .min()
7458 .expect("non-empty alias ranges have a minimum");
7459 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
7460 return false;
7461 };
7462 let root = prepared.tree().root_node();
7463 let has_matching_declaration = target_ranges
7464 .iter()
7465 .filter(|range| range.end_byte <= alias_start)
7466 .filter_map(|range| node_for_exact_range(root, range))
7467 .any(|node| {
7468 flattened_macro_namespace_components(node, prepared.source())
7469 .is_some_and(|recovered| recovered == namespace_components)
7470 });
7471 if !has_matching_declaration {
7472 return false;
7473 }
7474
7475 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7476 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7477 guard_requirement_sets_match(&alias_guards, &target_guards)
7478 }
7479
7480 pub fn template_alias_arguments_preserve_target(
7481 &self,
7482 analyzer: &CppGraphSource<'_>,
7483 visible_from: &ProjectFile,
7484 alias: &CodeUnit,
7485 arguments: &[CppTemplateExpression],
7486 target: &CodeUnit,
7487 ) -> bool {
7488 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7489 return false;
7490 };
7491 if metadata.alias_target.is_none()
7492 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7493 {
7494 return false;
7495 }
7496 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7497 }
7498
7499 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7500 self.cpp_template_metadata
7501 .get(unit)
7502 .is_some_and(CppTemplateMetadata::is_primary)
7503 }
7504
7505 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7506 self.cpp_template_metadata
7507 .get(unit)
7508 .is_some_and(CppTemplateMetadata::is_specialization)
7509 }
7510
7511 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7512 same_visible_symbol(left, right)
7513 || self.compatible_primary_template_redeclarations(left, right)
7514 }
7515
7516 pub fn same_template_member_identity(
7517 &self,
7518 analyzer: &CppGraphSource<'_>,
7519 left: &CodeUnit,
7520 right: &CodeUnit,
7521 ) -> bool {
7522 if same_visible_symbol(left, right) {
7523 return true;
7524 }
7525 if left.kind() != right.kind()
7526 || left.identifier() != right.identifier()
7527 || left.signature() != right.signature()
7528 {
7529 return false;
7530 }
7531 let (Some(left_owner), Some(right_owner)) =
7532 (analyzer.parent_of(left), analyzer.parent_of(right))
7533 else {
7534 return false;
7535 };
7536 left_owner.is_class()
7537 && right_owner.is_class()
7538 && self.same_template_owner_identity(&left_owner, &right_owner)
7539 }
7540
7541 fn unique_canonical_type_candidate(
7542 &self,
7543 analyzer: &CppGraphSource<'_>,
7544 visible_from: &ProjectFile,
7545 candidates: &[&CodeUnit],
7546 ) -> Option<CodeUnit> {
7547 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7548 .ok()
7549 }
7550
7551 fn canonical_type_candidate_resolution(
7552 &self,
7553 analyzer: &CppGraphSource<'_>,
7554 visible_from: &ProjectFile,
7555 candidates: &[&CodeUnit],
7556 ) -> Result<CodeUnit, TypeCandidateFailure> {
7557 let mut canonical = Vec::new();
7558 for candidate in candidates {
7559 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7560 if canonical
7561 .iter()
7562 .any(|existing| same_visible_symbol(existing, &resolved))
7563 {
7564 continue;
7565 }
7566 if let Some(existing) = canonical.iter_mut().find(|existing| {
7567 self.compatible_primary_template_redeclarations(existing, &resolved)
7568 }) {
7569 if matches!(
7578 (
7579 cpp_class_declaration_strength(analyzer, existing),
7580 cpp_class_declaration_strength(analyzer, &resolved),
7581 ),
7582 (
7583 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7584 CppClassDeclarationStrength::Full,
7585 ) | (
7586 CppClassDeclarationStrength::Unknown,
7587 CppClassDeclarationStrength::Forward,
7588 )
7589 ) {
7590 *existing = resolved;
7591 }
7592 continue;
7593 }
7594 canonical.push(resolved);
7595 if canonical.len() > 1 {
7596 return Err(TypeCandidateFailure::Ambiguous);
7597 }
7598 }
7599 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7600 }
7601
7602 pub fn unique_type_candidate_preserving_target(
7603 &self,
7604 analyzer: &CppGraphSource<'_>,
7605 visible_from: &ProjectFile,
7606 candidates: &[&CodeUnit],
7607 target: &CodeUnit,
7608 ) -> Option<CodeUnit> {
7609 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7620 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7621 {
7622 return Some(target.clone());
7623 }
7624 let mut resolved_candidates = Vec::new();
7625 for candidate in candidates {
7626 let Some(resolved) =
7632 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7633 else {
7634 continue;
7635 };
7636 if resolved_candidates
7637 .iter()
7638 .any(|existing| same_visible_symbol(existing, &resolved))
7639 {
7640 continue;
7641 }
7642 resolved_candidates.push(resolved);
7643 }
7644 match resolved_candidates.as_slice() {
7645 [] => None,
7646 [single] => Some(single.clone()),
7647 _ => self
7652 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7653 .map(|_| target.clone()),
7654 }
7655 }
7656
7657 pub fn same_fqn_type_spelling_for_target<'b>(
7674 &self,
7675 analyzer: &CppGraphSource<'_>,
7676 visible_from: &ProjectFile,
7677 candidates: &[&'b CodeUnit],
7678 target: &CodeUnit,
7679 ) -> Option<&'b CodeUnit> {
7680 let [first, rest @ ..] = candidates else {
7681 return None;
7682 };
7683 if rest.is_empty()
7684 || !rest.iter().all(|candidate| {
7685 candidate.kind() == first.kind()
7686 && candidate.fq_name() == first.fq_name()
7687 && candidate.source() == first.source()
7688 })
7689 {
7690 return None;
7691 }
7692 candidates
7693 .iter()
7694 .copied()
7695 .find(|candidate| same_symbol(candidate, target))
7696 .or_else(|| {
7697 candidates.iter().copied().find(|candidate| {
7698 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7699 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7700 })
7701 })
7702 }
7703
7704 pub fn alternate_same_fqn_type_declarations(
7705 &self,
7706 analyzer: &CppGraphSource<'_>,
7707 candidates: &[&CodeUnit],
7708 target: &CodeUnit,
7709 ) -> bool {
7710 let Some(first) = candidates.first() else {
7711 return false;
7712 };
7713 let same_api = first.kind() == target.kind()
7714 && first.fq_name() == target.fq_name()
7715 && first.source() == target.source()
7716 && candidates.iter().all(|candidate| {
7717 candidate.kind() == target.kind()
7718 && candidate.fq_name() == target.fq_name()
7719 && candidate.source() == target.source()
7720 })
7721 && candidates
7722 .iter()
7723 .any(|candidate| same_symbol(candidate, target))
7724 && candidates
7725 .iter()
7726 .any(|candidate| !same_logical_symbol(candidate, target));
7727 if !same_api {
7728 return false;
7729 }
7730
7731 let requirements = candidates
7732 .iter()
7733 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7734 .collect::<Vec<_>>();
7735 requirements.len() > 1
7736 && requirements
7737 .iter()
7738 .all(|requirement| !requirement.is_empty())
7739 && requirements.iter().enumerate().all(|(index, left)| {
7740 requirements[index + 1..].iter().all(|right| {
7741 left.iter().all(|(_, left_guards)| {
7742 right.iter().all(|(_, right_guards)| {
7743 merge_preprocessor_guards(left_guards, right_guards).is_none()
7744 })
7745 })
7746 })
7747 })
7748 }
7749
7750 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7751 let mut pending = vec![terms.to_vec()];
7752 while let Some(branch_terms) = pending.pop() {
7753 let mut normalized = Vec::new();
7754 let mut covers_branch = false;
7755 for term in branch_terms {
7756 if term.iter().any(|guard| term.contains(&guard.negated())) {
7757 continue;
7758 }
7759 if term.is_empty() {
7760 covers_branch = true;
7761 break;
7762 }
7763 if !normalized.iter().any(|existing| existing == &term) {
7764 normalized.push(term);
7765 }
7766 }
7767 if covers_branch {
7768 continue;
7769 }
7770 let Some(split_guard) = normalized
7771 .iter()
7772 .flat_map(|term| term.iter())
7773 .next()
7774 .cloned()
7775 else {
7776 return false;
7777 };
7778 let negated_guard = split_guard.negated();
7779 let mut when_defined = Vec::new();
7780 let mut when_undefined = Vec::new();
7781 for term in normalized {
7782 if term.contains(&negated_guard) {
7783 } else if term.contains(&split_guard) {
7785 let mut reduced = term.clone();
7786 reduced.remove(&split_guard);
7787 when_defined.push(reduced);
7788 } else {
7789 when_defined.push(term.clone());
7790 }
7791 if term.contains(&split_guard) {
7792 } else if term.contains(&negated_guard) {
7794 let mut reduced = term;
7795 reduced.remove(&negated_guard);
7796 when_undefined.push(reduced);
7797 } else {
7798 when_undefined.push(term);
7799 }
7800 }
7801 pending.push(when_defined);
7802 pending.push(when_undefined);
7803 }
7804 true
7805 }
7806
7807 fn declarations_share_exhaustive_conditional_family(
7816 &self,
7817 analyzer: &CppGraphSource<'_>,
7818 candidates: &[&CodeUnit],
7819 ) -> Option<(usize, usize)> {
7820 let mut family_range = None;
7821 for candidate in candidates {
7822 let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
7823 let root = prepared.tree().root_node();
7824 let mut candidate_family = None;
7825 for range in analyzer.ranges(candidate) {
7826 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7827 let family = preprocessor_conditional_family_for_declaration(node)?;
7828 let key = (family.start_byte(), family.end_byte());
7829 if candidate_family.is_some_and(|existing| existing != key) {
7830 return None;
7831 }
7832 candidate_family = Some(key);
7833 }
7834 let candidate_family = candidate_family?;
7835 if family_range.is_some_and(|existing| existing != candidate_family) {
7836 return None;
7837 }
7838 family_range = Some(candidate_family);
7839 }
7840 family_range
7841 }
7842
7843 pub fn complementary_same_fqn_type_declarations(
7844 &self,
7845 analyzer: &CppGraphSource<'_>,
7846 candidates: &[&CodeUnit],
7847 target: &CodeUnit,
7848 ) -> bool {
7849 if candidates.len() < 2
7850 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7851 || self
7852 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7853 .is_none()
7854 {
7855 return false;
7856 }
7857 Self::preprocessor_guard_terms_cover_all_paths(
7858 &self.declaration_family_guard_terms(analyzer, candidates),
7859 )
7860 }
7861
7862 fn declaration_family_guard_terms(
7863 &self,
7864 analyzer: &CppGraphSource<'_>,
7865 candidates: &[&CodeUnit],
7866 ) -> Vec<HashSet<PreprocessorGuard>> {
7867 candidates
7868 .iter()
7869 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7870 .map(|(_, guards)| guards)
7871 .collect()
7872 }
7873
7874 fn exhaustive_guard_family_activation(
7890 &self,
7891 analyzer: &CppGraphSource<'_>,
7892 prepared: &PreparedSyntaxTree,
7893 candidate: &CodeUnit,
7894 reference: &CallableReferenceContext<'_>,
7895 ) -> Option<usize> {
7896 if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
7899 return None;
7900 }
7901 let family = self
7902 .visible_identifier_candidates(candidate.source(), candidate.identifier())
7903 .filter(|peer| {
7904 peer.kind() == candidate.kind()
7905 && peer.fq_name() == candidate.fq_name()
7906 && peer.source() == candidate.source()
7907 })
7908 .collect::<Vec<_>>();
7909 let (_, family_end) =
7910 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7911 if !Self::preprocessor_guard_terms_cover_all_paths(
7912 &self.declaration_family_guard_terms(analyzer, &family),
7913 ) {
7914 return None;
7915 }
7916 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7920 .iter()
7921 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7922 {
7923 return None;
7924 }
7925 (first_declaration_byte(analyzer, candidate)?
7926 == family
7927 .iter()
7928 .filter_map(|peer| first_declaration_byte(analyzer, peer))
7929 .min()?)
7930 .then_some(family_end)
7931 }
7932
7933 fn type_candidate_preserving_target(
7934 &self,
7935 analyzer: &CppGraphSource<'_>,
7936 visible_from: &ProjectFile,
7937 candidate: &CodeUnit,
7938 target: &CodeUnit,
7939 ) -> Option<CodeUnit> {
7940 let mut current = candidate.clone();
7941 let mut matched_target = same_visible_symbol(¤t, target)
7942 || self.compatible_primary_template_redeclarations(¤t, target);
7943 let mut seen = HashSet::default();
7944 loop {
7945 if !seen.insert(current.clone()) {
7946 return None;
7947 }
7948 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7949 return matched_target
7950 .then(|| target.clone())
7951 .or_else(|| current.is_class().then_some(current));
7952 };
7953 if self.flattened_macro_namespace_alias_target_matches(
7954 analyzer,
7955 visible_from,
7956 ¤t,
7957 &alias_target,
7958 target,
7959 ) {
7960 return Some(target.clone());
7961 }
7962 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7963 return matched_target
7964 .then(|| target.clone())
7965 .or_else(|| current.is_class().then_some(current));
7966 }
7967 if !self.cpp_template_metadata.contains_key(¤t)
7975 && let Some(primary) =
7976 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7977 && (same_visible_symbol(&primary, target)
7978 || self.compatible_primary_template_redeclarations(&primary, target))
7979 {
7980 return Some(target.clone());
7981 }
7982 if same_visible_symbol(¤t, target) {
7983 return Some(target.clone());
7984 }
7985 if self.cpp_template_metadata.contains_key(¤t) {
7986 return None;
7987 }
7988 let Some(next) =
7989 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
7990 else {
7991 return matched_target.then(|| target.clone());
7992 };
7993 current = next;
7994 matched_target |= same_visible_symbol(¤t, target)
7995 || self.compatible_primary_template_redeclarations(¤t, target);
7996 }
7997 }
7998
7999 fn compatible_primary_template_redeclarations(
8000 &self,
8001 left: &CodeUnit,
8002 right: &CodeUnit,
8003 ) -> bool {
8004 let (Some(left_metadata), Some(right_metadata)) = (
8005 self.cpp_template_metadata.get(left),
8006 self.cpp_template_metadata.get(right),
8007 ) else {
8008 return false;
8009 };
8010 left_metadata.primary_fq_name == right_metadata.primary_fq_name
8011 && left_metadata.is_primary()
8012 && right_metadata.is_primary()
8013 && cpp_reconcile_primary_template_parameters(
8014 &[(left, left_metadata), (right, right_metadata)],
8015 right,
8016 )
8017 .is_some()
8018 }
8019
8020 fn alias_candidate_may_preserve_target(
8021 &self,
8022 analyzer: &CppGraphSource<'_>,
8023 visible_from: &ProjectFile,
8024 candidate: &CodeUnit,
8025 target: &CodeUnit,
8026 ) -> bool {
8027 let mut current = candidate.clone();
8028 let mut seen = HashSet::default();
8029 loop {
8030 if same_visible_symbol(¤t, target)
8031 || self.compatible_primary_template_redeclarations(¤t, target)
8032 {
8033 return true;
8034 }
8035 if self.cpp_template_metadata.contains_key(¤t) {
8036 return true;
8037 }
8038 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8039 return false;
8040 };
8041 let StructuredAliasTarget::Named {
8042 components,
8043 global,
8044 arguments,
8045 } = alias_target
8046 else {
8047 return false;
8048 };
8049 if arguments.is_some() || !seen.insert(current.clone()) {
8050 return true;
8051 }
8052 let qualified = components.join("::");
8053 let next = if global {
8054 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8055 } else {
8056 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8057 };
8058 let Some(next) = next else {
8059 return true;
8060 };
8061 current = next;
8062 }
8063 }
8064
8065 fn type_candidates_for_declaration<'b>(
8069 &'b self,
8070 visible_from: &ProjectFile,
8071 declaration: &CodeUnit,
8072 raw_name: &str,
8073 ) -> Vec<&'b CodeUnit> {
8074 let Some(normalized) = normalize_reference_name(raw_name) else {
8075 return Vec::new();
8076 };
8077 if let Some(namespace) = cpp_namespace_for(declaration) {
8078 for prefix in namespace_prefixes(&namespace) {
8079 let qualified = format!("{prefix}::{normalized}");
8080 let candidates = self.type_candidates(visible_from, &qualified);
8081 if !candidates.is_empty() {
8082 return candidates;
8083 }
8084 }
8085 }
8086 self.type_candidates(visible_from, &normalized)
8087 }
8088
8089 fn resolve_unique_type_for_declaration(
8090 &self,
8091 visible_from: &ProjectFile,
8092 declaration: &CodeUnit,
8093 raw_name: &str,
8094 ) -> Option<CodeUnit> {
8095 unique_logical_type_candidate(self.type_candidates_for_declaration(
8096 visible_from,
8097 declaration,
8098 raw_name,
8099 ))
8100 }
8101
8102 fn resolve_defining_type_for_declaration(
8120 &self,
8121 analyzer: &CppGraphSource<'_>,
8122 visible_from: &ProjectFile,
8123 declaration: &CodeUnit,
8124 raw_name: &str,
8125 ) -> Option<CodeUnit> {
8126 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8127 let logical = unique_logical_type_candidate(candidates.clone())?;
8128 let mut defining = candidates.into_iter().filter(|candidate| {
8129 candidate.is_class()
8130 && cpp_class_declaration_strength(analyzer, candidate)
8131 == CppClassDeclarationStrength::Full
8132 });
8133 match (defining.next(), defining.next()) {
8134 (Some(unique_definition), None) => Some(unique_definition.clone()),
8135 _ => Some(logical),
8136 }
8137 }
8138
8139 pub fn resolves_to_type(
8140 &self,
8141 analyzer: &CppGraphSource<'_>,
8142 file: &ProjectFile,
8143 raw_name: &str,
8144 target: &CodeUnit,
8145 ) -> bool {
8146 let Some(normalized) = normalize_reference_name(raw_name) else {
8147 return false;
8148 };
8149 let candidates = self.type_candidates(file, &normalized);
8150 if candidates.is_empty() {
8151 return self.parser_alias_resolves_to_type(file, raw_name, target);
8152 }
8153 let Some(resolved) =
8154 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8155 else {
8156 return false;
8157 };
8158 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8159 }
8160
8161 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8162 let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
8163 let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
8164 match resolved.kind() {
8165 CodeUnitType::Class => Some(resolved),
8166 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
8167 _ => None,
8168 }
8169 }
8170
8171 pub fn same_logical_callable(
8186 &self,
8187 analyzer: &CppGraphSource<'_>,
8188 left: &CodeUnit,
8189 right: &CodeUnit,
8190 ) -> bool {
8191 if same_logical_symbol(left, right) {
8192 return true;
8193 }
8194 if left.kind() != right.kind()
8195 || !left.is_callable()
8196 || !right.is_callable()
8197 || left.fq_name() != right.fq_name()
8198 {
8199 return false;
8200 }
8201 if self.callable_is_template_declaration(analyzer, left)
8207 || self.callable_is_template_declaration(analyzer, right)
8208 {
8209 return false;
8210 }
8211 let (Some(left_comparable), Some(right_comparable)) = (
8212 self.callable_comparable(analyzer, left),
8213 self.callable_comparable(analyzer, right),
8214 ) else {
8215 return false;
8216 };
8217 if left_comparable.suffix != right_comparable.suffix
8222 || left_comparable.shapes.len() != right_comparable.shapes.len()
8223 {
8224 return false;
8225 }
8226 left_comparable
8227 .shapes
8228 .iter()
8229 .zip(right_comparable.shapes.iter())
8230 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8231 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8232 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8233 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8234 }
8235 _ => false,
8239 })
8240 }
8241
8242 fn comparable_shapes_agree(
8248 &self,
8249 analyzer: &CppGraphSource<'_>,
8250 left: &CppComparableParameter,
8251 right: &CppComparableParameter,
8252 ) -> bool {
8253 let mut stack = vec![(left.root(), right.root())];
8254 while let Some((left_index, right_index)) = stack.pop() {
8255 match (left.node(left_index), right.node(right_index)) {
8256 (
8257 CppComparableNode::Named {
8258 name: left_name,
8259 primitive: left_primitive,
8260 konst: left_konst,
8261 volatil: left_volatil,
8262 },
8263 CppComparableNode::Named {
8264 name: right_name,
8265 primitive: right_primitive,
8266 konst: right_konst,
8267 volatil: right_volatil,
8268 },
8269 ) => {
8270 if left_konst != right_konst
8271 || left_volatil != right_volatil
8272 || left_primitive != right_primitive
8273 || !self.comparable_names_agree(
8274 analyzer,
8275 left_name,
8276 right_name,
8277 *left_primitive,
8278 )
8279 {
8280 return false;
8281 }
8282 }
8283 (
8284 CppComparableNode::Pointer {
8285 inner: left_inner,
8286 konst: left_konst,
8287 volatil: left_volatil,
8288 },
8289 CppComparableNode::Pointer {
8290 inner: right_inner,
8291 konst: right_konst,
8292 volatil: right_volatil,
8293 },
8294 ) => {
8295 if left_konst != right_konst || left_volatil != right_volatil {
8296 return false;
8297 }
8298 stack.push((*left_inner, *right_inner));
8299 }
8300 (
8301 CppComparableNode::Reference { inner: left_inner },
8302 CppComparableNode::Reference { inner: right_inner },
8303 )
8304 | (
8305 CppComparableNode::Array { inner: left_inner },
8306 CppComparableNode::Array { inner: right_inner },
8307 ) => stack.push((*left_inner, *right_inner)),
8308 (
8309 CppComparableNode::Generic {
8310 base: left_base,
8311 arguments: left_arguments,
8312 },
8313 CppComparableNode::Generic {
8314 base: right_base,
8315 arguments: right_arguments,
8316 },
8317 ) => {
8318 if left_arguments.len() != right_arguments.len() {
8319 return false;
8320 }
8321 stack.push((*left_base, *right_base));
8322 stack.extend(
8323 left_arguments.iter().zip(right_arguments.iter()).map(
8324 |(left_argument, right_argument)| (*left_argument, *right_argument),
8325 ),
8326 );
8327 }
8328 _ => return false,
8329 }
8330 }
8331 true
8332 }
8333
8334 fn comparable_names_agree(
8344 &self,
8345 analyzer: &CppGraphSource<'_>,
8346 left: &StructuredTypeName,
8347 right: &StructuredTypeName,
8348 primitive: bool,
8349 ) -> bool {
8350 if primitive {
8351 return left.path() == right.path();
8352 }
8353 match (
8354 self.comparable_name_terminal(analyzer, left),
8355 self.comparable_name_terminal(analyzer, right),
8356 ) {
8357 (Some(left_terminal), Some(right_terminal)) => {
8358 same_logical_symbol(&left_terminal, &right_terminal)
8359 }
8360 (None, None) => {
8361 left.path() == right.path() && left.is_absolute() == right.is_absolute()
8362 }
8363 _ => false,
8364 }
8365 }
8366
8367 fn comparable_name_terminal(
8377 &self,
8378 analyzer: &CppGraphSource<'_>,
8379 name: &StructuredTypeName,
8380 ) -> Option<CodeUnit> {
8381 let mut current = self.comparable_name_declaration(analyzer, name)?;
8382 let mut visited = HashSet::default();
8383 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8384 if !declared_type_alias(analyzer, ¤t) {
8391 return current.is_class().then_some(current);
8392 }
8393 if !visited.insert(current.clone()) {
8394 return None;
8395 }
8396 let signature = current.signature()?;
8397 if cpp_alias_declaration_adds_indirection(signature) {
8402 return None;
8403 }
8404 let raw_target = cpp_alias_declaration_target_text(signature)?;
8405 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
8406 }
8407 None
8408 }
8409
8410 fn comparable_alias_target(
8421 &self,
8422 analyzer: &CppGraphSource<'_>,
8423 alias: &CodeUnit,
8424 raw_target: &str,
8425 ) -> Option<CodeUnit> {
8426 let absolute = raw_target.trim_start().starts_with("::");
8431 let normalized = normalize_reference_name(raw_target)?;
8432 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8433 brokk_bifrost_core::analyzer::Language::Cpp,
8434 &normalized,
8435 );
8436 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8437 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8438 brokk_bifrost_core::analyzer::Language::Cpp,
8439 &namespace,
8440 )
8441 });
8442 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8443 self.comparable_name_declaration(analyzer, &name)
8444 }
8445
8446 fn comparable_name_declaration(
8454 &self,
8455 analyzer: &CppGraphSource<'_>,
8456 name: &StructuredTypeName,
8457 ) -> Option<CodeUnit> {
8458 let mut cache = self
8465 .comparable_name_declarations
8466 .lock()
8467 .expect("C++ comparable name declaration cache poisoned");
8468 if let Some(cached) = cache.get(name) {
8469 return cached.clone();
8470 }
8471 let interner = segment_interner();
8472 let identifier = name.path().last()?;
8473 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8474 let first_depth = if name.is_absolute() {
8475 0
8476 } else {
8477 name.lexical_scope().len()
8478 };
8479 let mut resolved = None;
8480 for depth in (0..=first_depth).rev() {
8481 let mut structured = FqName::new();
8482 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8483 structured.push(interner.intern(component, SegmentKind::Unknown));
8484 }
8485 let mut candidates = candidates_by_identifier
8486 .iter()
8487 .filter(|unit| unit.fq().same_segment_texts(&structured))
8488 .filter(|unit| {
8489 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8490 })
8491 .cloned();
8492 let Some(first) = candidates.next() else {
8493 continue;
8494 };
8495 resolved = candidates
8496 .all(|unit| same_logical_symbol(&unit, &first))
8497 .then_some(first);
8498 break;
8499 }
8500 cache.insert(name.clone(), resolved.clone());
8501 resolved
8502 }
8503
8504 fn callable_comparable(
8510 &self,
8511 analyzer: &CppGraphSource<'_>,
8512 unit: &CodeUnit,
8513 ) -> Option<Arc<ExtractedComparable>> {
8514 if let Some(cached) = self
8515 .callable_comparables
8516 .lock()
8517 .expect("C++ callable comparable cache poisoned")
8518 .get(unit)
8519 .cloned()
8520 {
8521 return cached;
8522 }
8523 let extracted = self
8524 .extract_callable_comparable(analyzer, unit)
8525 .map(Arc::new);
8526 self.callable_comparables
8527 .lock()
8528 .expect("C++ callable comparable cache poisoned")
8529 .insert(unit.clone(), extracted.clone());
8530 extracted
8531 }
8532
8533 fn extract_callable_comparable(
8534 &self,
8535 analyzer: &CppGraphSource<'_>,
8536 unit: &CodeUnit,
8537 ) -> Option<ExtractedComparable> {
8538 let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
8539 let root = prepared.tree().root_node();
8540 let declarator = analyzer
8541 .ranges(unit)
8542 .into_iter()
8543 .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
8544 Some(ExtractedComparable {
8545 shapes: cpp_comparable_parameter_shapes(
8548 declarator,
8549 prepared.source(),
8550 &ParentIndex::unindexed(),
8551 ),
8552 suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
8553 })
8554 }
8555
8556 pub fn canonical_type_for_reference(
8557 &self,
8558 file: &ProjectFile,
8559 raw_name: &str,
8560 ) -> Option<CodeUnit> {
8561 let resolved = self.resolve_type(file, raw_name)?;
8562 self.alias_target(&resolved).or(Some(resolved))
8563 }
8564
8565 pub fn parser_alias_resolves_to_type(
8566 &self,
8567 file: &ProjectFile,
8568 raw_name: &str,
8569 target: &CodeUnit,
8570 ) -> bool {
8571 let Some(alias_name) = normalize_reference_name(raw_name) else {
8572 return false;
8573 };
8574 self.parser_alias_name_may_resolve_to_target(file, &alias_name, target)
8575 }
8576
8577 #[cfg(any(test, feature = "test-support"))]
8578 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8579 self.visible_source_files_by_root
8580 .get(file)
8581 .cloned()
8582 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8583 }
8584
8585 #[cfg(any(test, feature = "test-support"))]
8586 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8587 self.alias_source_parse_counts
8588 .lock()
8589 .expect("alias source parse count lock")
8590 .get(file)
8591 .copied()
8592 .unwrap_or(0)
8593 }
8594
8595 #[cfg(any(test, feature = "test-support"))]
8596 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8597 self.parser_alias_fallback_files.load(Ordering::Relaxed)
8598 }
8599
8600 pub fn resolve_named(
8601 &self,
8602 file: &ProjectFile,
8603 raw_name: &str,
8604 kind: TargetKind,
8605 ) -> Option<CodeUnit> {
8606 let normalized = normalize_reference_name(raw_name)?;
8607 self.named_candidates_for_normalized(file, &normalized, kind)
8608 .into_iter()
8609 .next()
8610 .cloned()
8611 }
8612
8613 pub fn contains_named_symbol(
8614 &self,
8615 file: &ProjectFile,
8616 raw_name: &str,
8617 kind: TargetKind,
8618 target: &CodeUnit,
8619 ) -> bool {
8620 let Some(normalized) = normalize_reference_name(raw_name) else {
8621 return false;
8622 };
8623 self.named_candidates_for_normalized(file, &normalized, kind)
8624 .into_iter()
8625 .any(|unit| {
8626 matches_kind_for_lookup(unit, kind)
8627 && reference_matches_unit(&normalized, unit)
8628 && same_visible_symbol(unit, target)
8629 })
8630 }
8631
8632 pub fn named_candidates(
8633 &self,
8634 file: &ProjectFile,
8635 raw_name: &str,
8636 kind: TargetKind,
8637 ) -> Vec<CodeUnit> {
8638 let Some(normalized) = normalize_reference_name(raw_name) else {
8639 return Vec::new();
8640 };
8641 self.named_candidates_for_normalized(file, &normalized, kind)
8642 .into_iter()
8643 .cloned()
8644 .collect()
8645 }
8646
8647 pub fn resolve_known_non_target(
8648 &self,
8649 file: &ProjectFile,
8650 raw_name: &str,
8651 kind: TargetKind,
8652 target: &CodeUnit,
8653 ) -> bool {
8654 let Some(normalized) = normalize_reference_name(raw_name) else {
8655 return false;
8656 };
8657 normalized.contains("::")
8658 && self
8659 .named_candidates_for_normalized(file, &normalized, kind)
8660 .into_iter()
8661 .any(|unit| {
8662 matches_kind_for_lookup(unit, kind)
8663 && reference_matches_unit(&normalized, unit)
8664 && !same_visible_symbol(unit, target)
8665 })
8666 }
8667
8668 pub fn resolve_call_return_binding(
8669 &self,
8670 analyzer: &CppGraphSource<'_>,
8671 file: &ProjectFile,
8672 raw_name: &str,
8673 arity: usize,
8674 lexical_namespace: Option<&str>,
8675 direct_type: Option<&CodeUnit>,
8676 ) -> Option<CppScanBinding> {
8677 let normalized = normalize_reference_name(raw_name)?;
8678 let mut candidates = Vec::new();
8679 for function in
8680 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8681 {
8682 if cpp_callable_arity(analyzer, function).accepts(arity)
8683 && !direct_type.is_some_and(|direct_type| {
8684 self.callable_is_constructor_declaration(analyzer, function)
8685 && type_owner_of(analyzer, function)
8686 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8687 })
8688 {
8689 candidates.push(function.clone());
8690 }
8691 }
8692 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8693 unanimous_return_binding(analyzer, self, file, &candidates)
8694 }
8695
8696 pub fn resolve_call_return_binding_without_arity(
8697 &self,
8698 analyzer: &CppGraphSource<'_>,
8699 file: &ProjectFile,
8700 raw_name: &str,
8701 lexical_namespace: Option<&str>,
8702 direct_type: Option<&CodeUnit>,
8703 ) -> (bool, Option<CppScanBinding>) {
8704 let Some(normalized) = normalize_reference_name(raw_name) else {
8705 return (false, None);
8706 };
8707 let mut candidates = self
8708 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8709 .into_iter()
8710 .filter(|function| {
8711 function.is_function()
8712 && !direct_type.is_some_and(|direct_type| {
8713 self.callable_is_constructor_declaration(analyzer, function)
8714 && type_owner_of(analyzer, function)
8715 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8716 })
8717 })
8718 .cloned()
8719 .collect::<Vec<_>>();
8720 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8721 let has_candidates = !candidates.is_empty();
8722 (
8723 has_candidates,
8724 unanimous_return_binding(analyzer, self, file, &candidates),
8725 )
8726 }
8727
8728 pub fn visible_identifier_candidates<'b>(
8729 &'b self,
8730 file: &ProjectFile,
8731 identifier: &str,
8732 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8733 self.visible_by_identifier
8734 .get(file)
8735 .and_then(|by_name| by_name.get(identifier))
8736 .into_iter()
8737 .flatten()
8738 }
8739
8740 pub fn visible_type_reference_component_names_for_target(
8748 &self,
8749 analyzer: &CppGraphSource<'_>,
8750 file: &ProjectFile,
8751 target: &CodeUnit,
8752 ) -> HashSet<String> {
8753 let mut names = HashSet::from_iter([target.identifier().to_string()]);
8754 if let Some(metadata) = self.cpp_template_metadata.get(target) {
8755 names.insert(metadata.primary_name.clone());
8756 }
8757
8758 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8759 for (identifier, candidates) in by_identifier {
8760 if candidates.iter().any(|candidate| {
8761 (candidate.is_class()
8762 && (same_visible_symbol(candidate, target)
8763 || self.compatible_primary_template_redeclarations(candidate, target)))
8764 || (declared_type_alias(analyzer, candidate)
8765 && self.alias_candidate_may_preserve_target(
8766 analyzer, file, candidate, target,
8767 ))
8768 }) {
8769 names.insert(identifier.clone());
8770 }
8771 }
8772 }
8773
8774 names
8775 }
8776
8777 pub fn indexed_structural_class_scope(
8778 &self,
8779 file: &ProjectFile,
8780 class: Node<'_>,
8781 source: &str,
8782 ) -> Option<Vec<String>> {
8783 let key = (file.clone(), class.start_byte(), class.end_byte());
8784 if let Some(cached) = self
8785 .indexed_structural_class_scopes
8786 .lock()
8787 .expect("C++ indexed structural-class scope cache poisoned")
8788 .get(&key)
8789 .cloned()
8790 {
8791 return cached;
8792 }
8793 let resolved = (|| {
8794 let name = class.child_by_field_name("name")?;
8795 let identifier = if name.kind() == "template_type" {
8796 node_text(name.child_by_field_name("name")?, source).to_string()
8797 } else {
8798 let mut components = Vec::new();
8799 append_cpp_name_components(name, source, &mut components)?;
8800 components.last()?.clone()
8801 };
8802 let visible = self
8803 .visible_identifier_candidates(file, &identifier)
8804 .cloned()
8805 .collect::<Vec<_>>();
8806 let mut visible = visible;
8807 for candidate in
8808 self.visible_by_file
8809 .get(file)
8810 .into_iter()
8811 .flatten()
8812 .filter(|candidate| {
8813 self.cpp_template_metadata
8814 .get(candidate)
8815 .is_some_and(|metadata| metadata.primary_name == identifier)
8816 })
8817 {
8818 if !visible
8819 .iter()
8820 .any(|existing| same_logical_symbol(existing, candidate))
8821 {
8822 visible.push(candidate.clone());
8823 }
8824 }
8825 let cpp_source = self.cpp_source();
8828 let candidates = visible
8829 .iter()
8830 .filter(|candidate| {
8831 candidate.source() == file
8832 && candidate.is_class()
8833 && !declared_type_alias(&cpp_source, candidate)
8834 && self.cpp.ranges(candidate).iter().any(|range| {
8835 range.start_byte <= class.start_byte()
8836 && class.end_byte() <= range.end_byte
8837 })
8838 })
8839 .collect::<Vec<_>>();
8840 let owner = if name.kind() == "template_type" {
8841 let expected = normalize_cpp_whitespace(node_text(name, source));
8842 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8843 let exact = candidates
8844 .iter()
8845 .copied()
8846 .filter(|candidate| {
8847 candidate
8848 .fq()
8849 .segments()
8850 .iter()
8851 .rev()
8852 .find_map(|&segment| {
8853 let (text, kind) = interner.resolve(segment);
8854 matches!(
8855 kind,
8856 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8857 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8858 )
8859 .then_some(text)
8860 })
8861 .is_some_and(|text| text == expected)
8862 })
8863 .collect::<Vec<_>>();
8864 unique_logical_type_candidate(exact)
8865 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
8866 } else {
8867 unique_logical_type_candidate(candidates)?
8868 };
8869 Some(canonical_cpp_scope_components(&owner))
8870 })();
8871 self.indexed_structural_class_scopes
8872 .lock()
8873 .expect("C++ indexed structural-class scope cache poisoned")
8874 .insert(key, resolved.clone());
8875 resolved
8876 }
8877
8878 pub fn indexed_enclosing_owner_scope(
8879 &self,
8880 analyzer: &CppGraphSource<'_>,
8881 file: &ProjectFile,
8882 node: Node<'_>,
8883 ) -> Option<Vec<String>> {
8884 let anchor = std::iter::successors(Some(node), |current| current.parent())
8885 .find(|current| {
8886 matches!(
8887 current.kind(),
8888 "function_definition"
8889 | "class_specifier"
8890 | "struct_specifier"
8891 | "union_specifier"
8892 )
8893 })
8894 .unwrap_or(node);
8895 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
8896 if let Some(cached) = self
8897 .indexed_enclosing_owner_scopes
8898 .lock()
8899 .expect("C++ indexed enclosing-owner scope cache poisoned")
8900 .get(&key)
8901 .cloned()
8902 {
8903 return cached;
8904 }
8905 let resolved = (|| {
8906 let range = Range {
8907 start_byte: node.start_byte(),
8908 end_byte: node.end_byte(),
8909 start_line: node.start_position().row,
8910 end_line: node.end_position().row,
8911 };
8912 let start = analyzer.enclosing_code_unit(file, &range)?;
8913 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
8914 start,
8915 |unit| self.cached_precise_parent_of(analyzer, unit),
8916 )
8917 .find(|unit| {
8918 unit.is_class()
8919 && !analyzer
8920 .type_alias_provider()
8921 .is_some_and(|provider| provider.is_type_alias(unit))
8922 })?;
8923 Some(canonical_cpp_scope_components(&owner))
8924 })();
8925 self.indexed_enclosing_owner_scopes
8926 .lock()
8927 .expect("C++ indexed enclosing-owner scope cache poisoned")
8928 .insert(key, resolved.clone());
8929 resolved
8930 }
8931
8932 fn cached_precise_parent_of(
8933 &self,
8934 analyzer: &CppGraphSource<'_>,
8935 code_unit: &CodeUnit,
8936 ) -> Option<CodeUnit> {
8937 if let Some(cached) = self
8938 .precise_parent_cache
8939 .lock()
8940 .expect("C++ precise-parent cache poisoned")
8941 .get(code_unit)
8942 .cloned()
8943 {
8944 return cached;
8945 }
8946 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
8947 self.precise_parent_cache
8948 .lock()
8949 .expect("C++ precise-parent cache poisoned")
8950 .insert(code_unit.clone(), resolved.clone());
8951 resolved
8952 }
8953
8954 pub fn callable_is_constructor_declaration(
8955 &self,
8956 analyzer: &CppGraphSource<'_>,
8957 candidate: &CodeUnit,
8958 ) -> bool {
8959 if !candidate.is_function() {
8960 return false;
8961 }
8962 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
8963 return false;
8964 };
8965 let root = prepared.tree().root_node();
8966 let candidate_ranges = analyzer.ranges(candidate);
8967 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
8968 let mut current = root
8969 .descendant_for_byte_range(range.start_byte, range.end_byte)
8970 .and_then(|node| node.parent());
8971 while let Some(node) = current {
8972 if matches!(
8973 node.kind(),
8974 "class_specifier" | "struct_specifier" | "union_specifier"
8975 ) {
8976 return node
8977 .child_by_field_name("name")
8978 .map(|name| terminal_name(node_text(name, prepared.source())))
8979 .is_some_and(|name| name == candidate.identifier());
8980 }
8981 current = node.parent();
8982 }
8983 false
8984 });
8985 if enclosed_by_matching_type {
8986 return true;
8987 }
8988 let indexed_containment = analyzer
8989 .declarations(candidate.source())
8990 .into_iter()
8991 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
8992 .any(|owner| {
8993 analyzer.ranges(&owner).iter().any(|owner_range| {
8994 candidate_ranges.iter().any(|candidate_range| {
8995 owner_range.start_byte <= candidate_range.start_byte
8996 && candidate_range.end_byte <= owner_range.end_byte
8997 })
8998 })
8999 });
9000 if indexed_containment {
9001 return true;
9002 }
9003 let metadata = analyzer.signature_metadata(candidate);
9004 !metadata.is_empty()
9005 && metadata
9006 .iter()
9007 .all(|signature| signature.return_type_text().is_none())
9008 }
9009
9010 pub fn callable_is_deduction_guide_declaration(
9018 &self,
9019 analyzer: &CppGraphSource<'_>,
9020 candidate: &CodeUnit,
9021 ) -> bool {
9022 if !candidate.is_function() {
9023 return false;
9024 }
9025 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9026 return false;
9027 };
9028 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
9029 .into_iter()
9030 .any(|declaration| {
9031 if declaration.kind() != "declaration"
9032 || declaration.child_by_field_name("type").is_some()
9033 {
9034 return false;
9035 }
9036 let Some(declarator) = declaration.child_by_field_name("declarator") else {
9037 return false;
9038 };
9039 if declarator.kind() != "function_declarator" {
9040 return false;
9041 }
9042 let mut cursor = declarator.walk();
9043 let has_trailing_return = declarator
9044 .named_children(&mut cursor)
9045 .any(|child| child.kind() == "trailing_return_type");
9046 has_trailing_return
9047 && declarator_name_node(declarator).is_some_and(|name| {
9048 node_text(name, prepared.source()) == candidate.identifier()
9049 })
9050 })
9051 }
9052
9053 pub fn callable_is_template_declaration(
9057 &self,
9058 analyzer: &CppGraphSource<'_>,
9059 candidate: &CodeUnit,
9060 ) -> bool {
9061 if !candidate.is_function() {
9062 return false;
9063 }
9064 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9065 return false;
9066 };
9067 let root = prepared.tree().root_node();
9068 analyzer.ranges(candidate).iter().any(|range| {
9069 let Some(node) = node_for_exact_range(root, range)
9070 .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
9071 else {
9072 return false;
9073 };
9074 node.parent().is_some_and(|parent| {
9075 parent.kind() == "template_declaration"
9076 && parent
9077 .named_child(parent.named_child_count().saturating_sub(1))
9078 .is_some_and(|declaration| same_node(declaration, node))
9079 })
9080 })
9081 }
9082
9083 pub fn type_name_candidates<'b>(
9084 &'b self,
9085 file: &ProjectFile,
9086 normalized: &str,
9087 ) -> Vec<&'b CodeUnit> {
9088 self.candidate_units(file, normalized, TargetKind::Type)
9089 }
9090
9091 pub fn visible_members_for_owner_name<'b>(
9092 &'b self,
9093 file: &ProjectFile,
9094 owner: &CodeUnit,
9095 name: &str,
9096 ) -> Vec<&'b CodeUnit> {
9097 self.visible_identifier_candidates(file, name)
9098 .filter(|unit| {
9099 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9103 .is_some_and(|parent| parent == owner.fq_name())
9104 })
9105 .collect()
9106 }
9107
9108 pub fn visible_member_for_owner_name(
9109 &self,
9110 file: &ProjectFile,
9111 owner: &CodeUnit,
9112 name: &str,
9113 ) -> VisibleMemberResolution {
9114 let candidates = self.visible_members_for_owner_name(file, owner, name);
9115 let mut callables = Vec::new();
9116 let mut non_callable = None;
9117 for candidate in candidates {
9118 if candidate.is_function() {
9119 callables.push(candidate.clone());
9120 } else if non_callable.is_none() {
9121 non_callable = Some(candidate.clone());
9122 }
9123 }
9124 match (callables.is_empty(), non_callable) {
9125 (false, None) => VisibleMemberResolution::Callable(callables),
9126 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9127 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9128 (true, None) => VisibleMemberResolution::Missing,
9129 }
9130 }
9131
9132 fn field_declared_type_fact(
9133 &self,
9134 analyzer: &CppGraphSource<'_>,
9135 field: &CodeUnit,
9136 ) -> Option<DeclaredFieldTypeFact> {
9137 if let Some(cached) = self
9138 .field_type_facts
9139 .lock()
9140 .expect("C++ field type fact cache poisoned")
9141 .get(field)
9142 .cloned()
9143 {
9144 return cached;
9145 }
9146 let decoded = decode_field_declared_type_fact(analyzer, field);
9147 self.field_type_facts
9148 .lock()
9149 .expect("C++ field type fact cache poisoned")
9150 .insert(field.clone(), decoded.clone());
9151 decoded
9152 }
9153
9154 fn structured_alias_target(
9155 &self,
9156 analyzer: &CppGraphSource<'_>,
9157 unit: &CodeUnit,
9158 ) -> Option<StructuredAliasTarget> {
9159 if let Some(cached) = self
9160 .structured_alias_targets
9161 .lock()
9162 .expect("C++ structured alias target cache poisoned")
9163 .get(unit)
9164 .cloned()
9165 {
9166 return cached;
9167 }
9168 let decoded = decode_structured_alias_target(analyzer, unit);
9169 self.structured_alias_targets
9170 .lock()
9171 .expect("C++ structured alias target cache poisoned")
9172 .insert(unit.clone(), decoded.clone());
9173 decoded
9174 }
9175
9176 pub fn type_candidates<'b>(
9177 &'b self,
9178 file: &ProjectFile,
9179 normalized: &str,
9180 ) -> Vec<&'b CodeUnit> {
9181 let mut candidates = self
9182 .candidate_units(file, normalized, TargetKind::Type)
9183 .into_iter()
9184 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
9185 .collect::<Vec<_>>();
9186 dedup_unit_refs(&mut candidates);
9187 candidates
9188 }
9189
9190 pub fn named_candidates_for_normalized<'b>(
9191 &'b self,
9192 file: &ProjectFile,
9193 normalized: &str,
9194 kind: TargetKind,
9195 ) -> Vec<&'b CodeUnit> {
9196 let mut candidates = self
9197 .candidate_units(file, normalized, kind)
9198 .into_iter()
9199 .filter(|unit| {
9200 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9201 })
9202 .collect::<Vec<_>>();
9203 dedup_unit_refs(&mut candidates);
9204 candidates
9205 }
9206
9207 pub fn candidate_units<'b>(
9208 &'b self,
9209 file: &ProjectFile,
9210 normalized: &str,
9211 kind: TargetKind,
9212 ) -> Vec<&'b CodeUnit> {
9213 if normalized.contains("::") {
9214 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9223 brokk_bifrost_core::analyzer::Language::Cpp,
9224 normalized,
9225 )
9226 .pop() else {
9227 return Vec::new();
9228 };
9229 let fqns = cpp_reference_fqn_candidates(normalized, kind);
9230 return self
9231 .visible_identifier_candidates(file, &identifier)
9232 .filter(|unit| {
9233 #[cfg(any(test, feature = "test-support"))]
9234 self.qualified_candidate_inspections
9235 .fetch_add(1, Ordering::Relaxed);
9236 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9237 || canonical_cpp_name_matches(unit, normalized)
9238 })
9239 .collect();
9240 }
9241 self.visible_identifier_candidates(file, normalized)
9242 .collect()
9243 }
9244
9245 #[cfg(any(test, feature = "test-support"))]
9246 pub fn reset_qualified_candidate_inspections(&self) {
9247 self.qualified_candidate_inspections
9248 .store(0, Ordering::Relaxed);
9249 }
9250
9251 #[cfg(any(test, feature = "test-support"))]
9252 pub fn qualified_candidate_inspections(&self) -> usize {
9253 self.qualified_candidate_inspections.load(Ordering::Relaxed)
9254 }
9255
9256 #[cfg(any(test, feature = "test-support"))]
9257 pub fn visibility_identifier_lookup_count(&self) -> usize {
9258 self.visibility_identifier_lookup_count
9259 }
9260
9261 #[cfg(any(test, feature = "test-support"))]
9262 pub fn visibility_identifier_batch_count(&self) -> usize {
9263 self.visibility_identifier_batch_count
9264 }
9265
9266 #[cfg(any(test, feature = "test-support"))]
9267 pub fn reset_target_preserving_type_resolution_count(&self) {
9268 self.target_preserving_type_resolution_count
9269 .store(0, Ordering::Relaxed);
9270 }
9271
9272 #[cfg(any(test, feature = "test-support"))]
9273 pub fn target_preserving_type_resolution_count(&self) -> usize {
9274 self.target_preserving_type_resolution_count
9275 .load(Ordering::Relaxed)
9276 }
9277
9278 #[cfg(any(test, feature = "test-support"))]
9279 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9280 self.visible_parser_alias_name_set_build_count
9281 .load(Ordering::Relaxed)
9282 }
9283}
9284
9285#[derive(Default)]
9286struct IncludeGraph {
9287 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9288}
9289
9290impl IncludeGraph {
9291 fn extend_with<F>(
9292 &mut self,
9293 root: &ProjectFile,
9294 cancellation: Option<&CancellationToken>,
9295 targets_for: &mut F,
9296 ) where
9297 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9298 {
9299 let mut stack = vec![root.clone()];
9300 while let Some(file) = stack.pop() {
9301 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9302 break;
9303 }
9304 if self.targets_by_file.contains_key(&file) {
9305 continue;
9306 }
9307 let targets = targets_for(&file);
9308 stack.extend(targets.iter().cloned());
9309 self.targets_by_file.insert(file, targets);
9310 }
9311 }
9312
9313 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9314 self.targets_by_file.keys()
9315 }
9316
9317 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9318 self.targets_by_file
9319 .get(file)
9320 .map(Vec::as_slice)
9321 .unwrap_or_default()
9322 }
9323
9324 fn reachable_files(
9325 &self,
9326 root: &ProjectFile,
9327 cancellation: Option<&CancellationToken>,
9328 ) -> HashSet<ProjectFile> {
9329 let mut pending = vec![root.clone()];
9330 let mut visited = HashSet::default();
9331 while let Some(file) = pending.pop() {
9332 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9333 break;
9334 }
9335 if visited.insert(file.clone()) {
9336 pending.extend(self.targets(&file).iter().cloned());
9337 }
9338 }
9339 visited
9340 }
9341}
9342
9343fn build_bounded_visible_declarations(
9344 cpp: &dyn CppSource,
9345 token: QueryToken<'_>,
9346 analyzer: &CppGraphSource<'_>,
9347 roots: &HashSet<ProjectFile>,
9348 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9349 cancellation: Option<&CancellationToken>,
9350 stats: &mut BoundedVisibilityStats,
9351) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9352 let mut candidates_by_identifier = HashMap::default();
9353 roots
9354 .iter()
9355 .map(|root| {
9356 let reading_is_c = analyzer.reference_uses_c_semantics(root);
9357 let declarations_started = Instant::now();
9358 let root_declarations =
9359 bounded_visibility_declarations_in_reading(analyzer, root, reading_is_c);
9360 stats.declaration_elapsed += declarations_started.elapsed();
9361 stats.declaration_reads += 1;
9362 stats.declaration_units += root_declarations.len();
9363 let mut visible = root_declarations.into_iter().collect::<HashSet<_>>();
9364 let mut pending_names = HashSet::default();
9365 if let Some(prepared) = cpp.prepared_syntax(token, root) {
9366 let mut cursor = prepared.tree().walk();
9371 let mut pending_nodes = vec![prepared.tree().root_node()];
9372 while let Some(node) = pending_nodes.pop() {
9373 if matches!(
9374 node.kind(),
9375 "identifier"
9376 | "type_identifier"
9377 | "field_identifier"
9378 | "namespace_identifier"
9379 ) {
9380 pending_names.insert(node_text(node, prepared.source()).to_string());
9381 }
9382 if node.kind() == "preproc_arg" {
9383 for reference in
9384 object_macro_replacement_type_references(node, prepared.source())
9385 {
9386 pending_names.extend(reference.components);
9387 }
9388 }
9389 pending_nodes.extend(node.named_children(&mut cursor));
9390 }
9391 }
9392 stats.root_names += pending_names.len();
9393 let mut completed_names = HashSet::default();
9394 while !pending_names.is_empty() {
9395 stats.rounds += 1;
9396 let round_names = std::mem::take(&mut pending_names);
9397 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9398 HashMap::default();
9399 let mut identifiers = Vec::new();
9400 for identifier in round_names {
9401 if !completed_names.insert(identifier.clone())
9402 || cancellation.is_some_and(CancellationToken::is_cancelled)
9403 {
9404 continue;
9405 }
9406 identifiers.push(identifier);
9407 }
9408 let missing_identifiers = identifiers
9409 .iter()
9410 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9411 .cloned()
9412 .collect::<HashSet<_>>();
9413 if !missing_identifiers.is_empty() {
9414 let lookup_started = Instant::now();
9415 let mut candidates = cpp
9416 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9417 stats.lookup_elapsed += lookup_started.elapsed();
9418 stats.identifier_lookups += missing_identifiers.len();
9419 stats.identifier_batches += 1;
9420 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9421 break;
9422 }
9423 for identifier in missing_identifiers {
9424 let units = candidates.remove(&identifier).unwrap_or_default();
9425 let candidate_count = units.len();
9426 let candidate_sources = units
9427 .into_iter()
9428 .map(|unit| unit.source().clone())
9429 .collect::<HashSet<_>>();
9430 candidates_by_identifier
9431 .insert(identifier, (candidate_sources, candidate_count));
9432 }
9433 }
9434 for identifier in identifiers {
9435 let (candidate_sources, candidate_count) = candidates_by_identifier
9436 .get(&identifier)
9437 .expect("every missing identifier was inserted after the batch lookup");
9438 stats.candidate_units += *candidate_count;
9439 for source in candidate_sources.iter().cloned() {
9440 if source != *root
9441 && visible_sources
9442 .get(root)
9443 .is_some_and(|files| files.contains(&source))
9444 {
9445 requested_names_by_source
9446 .entry(source)
9447 .or_default()
9448 .insert(identifier.clone());
9449 }
9450 }
9451 }
9452 stats.candidate_sources += requested_names_by_source.len();
9453 for (source, requested_names) in requested_names_by_source {
9454 let declarations_started = Instant::now();
9455 let declarations =
9456 bounded_visibility_declarations_in_reading(analyzer, &source, reading_is_c);
9457 stats.declaration_elapsed += declarations_started.elapsed();
9458 stats.declaration_reads += 1;
9459 stats.declaration_units += declarations.len();
9460 for unit in declarations {
9461 let template_metadata = unit
9462 .is_class()
9463 .then(|| cpp.template_metadata(&unit))
9464 .flatten();
9465 if !requested_names.contains(unit.identifier())
9466 && !template_metadata.as_ref().is_some_and(|metadata| {
9467 requested_names.contains(&metadata.primary_name)
9468 })
9469 {
9470 continue;
9471 }
9472 stats.selected_units += 1;
9473 if let Some(prepared) = cpp.prepared_syntax(token, &source) {
9474 let ast_started = Instant::now();
9475 let mut cursor = prepared.tree().walk();
9476 for range in analyzer.ranges(&unit) {
9477 let Some(declaration) =
9478 node_for_exact_range(prepared.tree().root_node(), &range)
9479 else {
9480 continue;
9481 };
9482 let mut pending_nodes = vec![declaration];
9483 while let Some(node) = pending_nodes.pop() {
9484 stats.dependency_ast_nodes += 1;
9485 if matches!(
9486 node.kind(),
9487 "type_identifier" | "namespace_identifier"
9488 ) {
9489 let name = node_text(node, prepared.source());
9490 if !completed_names.contains(name)
9491 && pending_names.insert(name.to_string())
9492 {
9493 stats.dependency_names += 1;
9494 }
9495 }
9496 pending_nodes.extend(node.named_children(&mut cursor));
9497 }
9498 }
9499 stats.dependency_ast_elapsed += ast_started.elapsed();
9500 }
9501 if let Some(metadata) = template_metadata
9502 && !completed_names.contains(&metadata.primary_name)
9503 {
9504 pending_names.insert(metadata.primary_name);
9505 }
9506 visible.insert(unit);
9507 }
9508 }
9509 }
9510 (root.clone(), visible)
9511 })
9512 .collect()
9513}
9514
9515#[derive(Default)]
9516struct BoundedVisibilityStats {
9517 rounds: usize,
9518 root_names: usize,
9519 identifier_lookups: usize,
9520 identifier_batches: usize,
9521 candidate_units: usize,
9522 candidate_sources: usize,
9523 declaration_reads: usize,
9524 declaration_units: usize,
9525 selected_units: usize,
9526 dependency_ast_nodes: usize,
9527 dependency_names: usize,
9528 lookup_elapsed: Duration,
9529 declaration_elapsed: Duration,
9530 dependency_ast_elapsed: Duration,
9531}
9532
9533fn bounded_visibility_declarations_in_reading(
9534 analyzer: &CppGraphSource<'_>,
9535 file: &ProjectFile,
9536 c_semantics: bool,
9537) -> BTreeSet<CodeUnit> {
9538 #[cfg(any(test, feature = "test-support"))]
9539 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9540 analyzer.declarations_in_reading(file, c_semantics)
9541}
9542
9543#[cfg(any(test, feature = "test-support"))]
9544pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9545 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9546}
9547
9548#[cfg(any(test, feature = "test-support"))]
9549pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9550 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9551}
9552
9553pub struct VisibilityData {
9554 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9555 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9556}
9557
9558pub fn build_visibility_data<F, R, D>(
9568 roots: &HashSet<ProjectFile>,
9569 cancellation: Option<&CancellationToken>,
9570 mut targets_for: F,
9571 mut reading_is_c_for: R,
9572 mut declarations_for: D,
9573) -> VisibilityData
9574where
9575 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9576 R: FnMut(&ProjectFile) -> bool,
9577 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9578{
9579 let mut include_graph = IncludeGraph::default();
9580 for file in roots {
9581 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9582 break;
9583 }
9584 include_graph.extend_with(file, cancellation, &mut targets_for);
9585 }
9586 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9587 .files()
9588 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9589 .map(|file| (file.clone(), declarations_for(file, false)))
9590 .collect();
9591 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9592 let mut visible_by_file = HashMap::default();
9593 let mut visible_source_files_by_root = HashMap::default();
9594 for file in roots {
9595 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9596 break;
9597 }
9598 let mut visited = HashSet::default();
9599 let mut visible = HashSet::default();
9600 let declarations_by_file = if reading_is_c_for(file) {
9601 for reached in cpp_declarations_by_file.keys() {
9602 if !c_declarations_by_file.contains_key(reached) {
9603 let declarations = declarations_for(reached, true);
9604 c_declarations_by_file.insert(reached.clone(), declarations);
9605 }
9606 }
9607 &c_declarations_by_file
9608 } else {
9609 &cpp_declarations_by_file
9610 };
9611 collect_visible_declarations(
9612 &include_graph,
9613 declarations_by_file,
9614 file,
9615 &mut visited,
9616 &mut visible,
9617 cancellation,
9618 );
9619 visible_by_file.insert(file.clone(), visible);
9620 visible_source_files_by_root.insert(file.clone(), visited);
9621 }
9622 VisibilityData {
9623 visible_by_file,
9624 visible_source_files_by_root,
9625 }
9626}
9627
9628#[derive(Default)]
9647struct OutOfLineOwnerBindingStats {
9648 unseen_owners: usize,
9649 definition_lookups: usize,
9650 admitted: usize,
9651}
9652
9653fn extend_with_out_of_line_owner_bindings(
9654 cpp: &dyn CppSource,
9655 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9656) -> OutOfLineOwnerBindingStats {
9657 let mut stats = OutOfLineOwnerBindingStats::default();
9658 for (file, visible) in visible_by_file.iter_mut() {
9659 let mut unseen_owners: HashSet<String> = visible
9663 .iter()
9664 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9665 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9666 .collect();
9667 if unseen_owners.is_empty() {
9668 continue;
9669 }
9670 for unit in visible.iter().filter(|unit| unit.is_class()) {
9671 unseen_owners.remove(&unit.fq_name());
9672 }
9673 stats.unseen_owners += unseen_owners.len();
9674 stats.definition_lookups += unseen_owners.len();
9675 let admitted = unseen_owners
9676 .iter()
9677 .flat_map(|owner| cpp.definitions(owner))
9678 .filter(CodeUnit::is_class)
9679 .collect::<Vec<_>>();
9680 stats.admitted += admitted.len();
9681 visible.extend(admitted);
9682 }
9683 stats
9684}
9685
9686pub enum VisibleMemberResolution {
9687 Callable(Vec<CodeUnit>),
9688 NonCallable,
9689 AmbiguousKind,
9690 Missing,
9691}
9692
9693#[derive(Clone)]
9694pub enum EnclosingMemberOwnerResolution {
9695 Owner(CodeUnit),
9696 Ambiguous,
9697 Missing,
9698}
9699
9700pub fn resolve_declaring_member_owner(
9701 analyzer: &CppGraphSource<'_>,
9702 visibility: &VisibilityIndex<'_>,
9703 file: &ProjectFile,
9704 receiver_owner: &CodeUnit,
9705 member_name: &str,
9706) -> EnclosingMemberOwnerResolution {
9707 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9708 return EnclosingMemberOwnerResolution::Missing;
9709 };
9710 let Some(receiver_owner) =
9711 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9712 else {
9713 return EnclosingMemberOwnerResolution::Ambiguous;
9714 };
9715 let resolve_level = |frontier: &[CodeUnit]| {
9716 let mut member_owners = Vec::new();
9717 for raw_owner in frontier {
9718 let Some(owner) =
9719 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9720 else {
9721 return EnclosingMemberOwnerResolution::Ambiguous;
9722 };
9723 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9724 if !member.is_field() && !member.is_function() {
9729 continue;
9730 }
9731 let Some(member_owner) = type_owner_of(analyzer, member) else {
9732 return EnclosingMemberOwnerResolution::Ambiguous;
9733 };
9734 if !member_owners
9735 .iter()
9736 .any(|existing| same_visible_symbol(existing, &member_owner))
9737 {
9738 member_owners.push(member_owner);
9739 }
9740 }
9741 }
9742 match member_owners.len() {
9743 0 => EnclosingMemberOwnerResolution::Missing,
9744 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9745 _ => EnclosingMemberOwnerResolution::Ambiguous,
9746 }
9747 };
9748 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9752 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9753 return direct;
9754 }
9755 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9756 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9757 let mut path_matches = Vec::new();
9758 while let Some(raw_owner) = stack.pop() {
9759 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9760 else {
9761 return EnclosingMemberOwnerResolution::Ambiguous;
9762 };
9763 let propagated = propagated_counts.entry(owner.clone()).or_default();
9767 if *propagated == 2 {
9768 continue;
9769 }
9770 *propagated += 1;
9771 match resolve_level(std::slice::from_ref(&owner)) {
9772 EnclosingMemberOwnerResolution::Owner(owner) => {
9773 path_matches.push(owner);
9774 if path_matches.len() == 2 {
9775 return EnclosingMemberOwnerResolution::Ambiguous;
9776 }
9777 }
9778 EnclosingMemberOwnerResolution::Ambiguous => {
9779 return EnclosingMemberOwnerResolution::Ambiguous;
9780 }
9781 EnclosingMemberOwnerResolution::Missing => {
9782 stack.extend(hierarchy.get_direct_ancestors(&owner));
9783 }
9784 }
9785 }
9786 match path_matches.len() {
9787 0 => EnclosingMemberOwnerResolution::Missing,
9788 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9789 _ => unreachable!("base-path matches are capped at one before returning"),
9790 }
9791}
9792
9793pub fn resolve_declaring_callable_owner(
9808 analyzer: &CppGraphSource<'_>,
9809 visibility: &VisibilityIndex<'_>,
9810 file: &ProjectFile,
9811 ordinary: EnclosingMemberOwnerResolution,
9812 member_name: &str,
9813 call_arity: usize,
9814) -> EnclosingMemberOwnerResolution {
9815 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9816 return ordinary;
9817 };
9818 if visibility
9819 .visible_members_for_owner_name(file, ordinary_owner, member_name)
9820 .into_iter()
9821 .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
9822 {
9823 return ordinary;
9824 }
9825
9826 let mut pending = match member_using_declaration_bases(
9827 analyzer,
9828 visibility,
9829 file,
9830 ordinary_owner,
9831 member_name,
9832 ) {
9833 Ok(bases) => bases,
9834 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9835 };
9836 let mut visited = HashSet::default();
9837 let mut introduced_owners = Vec::new();
9838 while let Some(owner) = pending.pop() {
9839 if !visited.insert(owner.clone()) {
9840 continue;
9841 }
9842 let accepts_arity = visibility
9843 .visible_members_for_owner_name(file, &owner, member_name)
9844 .into_iter()
9845 .any(|unit| {
9846 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
9847 });
9848 if accepts_arity {
9849 if !introduced_owners
9850 .iter()
9851 .any(|existing| same_visible_symbol(existing, &owner))
9852 {
9853 introduced_owners.push(owner);
9854 }
9855 continue;
9856 }
9857 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
9858 Ok(bases) => pending.extend(bases),
9859 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9860 }
9861 }
9862 match introduced_owners.as_slice() {
9863 [] => ordinary,
9864 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
9865 _ => EnclosingMemberOwnerResolution::Ambiguous,
9866 }
9867}
9868
9869fn member_using_declaration_bases(
9870 analyzer: &CppGraphSource<'_>,
9871 visibility: &VisibilityIndex<'_>,
9872 file: &ProjectFile,
9873 owner: &CodeUnit,
9874 member_name: &str,
9875) -> Result<Vec<CodeUnit>, ()> {
9876 let Some(source) = analyzer.get_source(owner, false) else {
9877 return Ok(Vec::new());
9878 };
9879 let scopes = cpp_member_using_declaration_scopes(&source, member_name);
9880 if scopes.is_empty() {
9881 return Ok(Vec::new());
9882 }
9883 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9884 return Ok(Vec::new());
9885 };
9886 let mut bases = Vec::new();
9887 for raw_ancestor in hierarchy.get_ancestors(owner) {
9888 let Some(ancestor) =
9889 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
9890 else {
9891 return Err(());
9892 };
9893 let qualified = cpp_name_for(&ancestor);
9894 if scopes
9895 .iter()
9896 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
9897 && !bases
9898 .iter()
9899 .any(|existing| same_visible_symbol(existing, &ancestor))
9900 {
9901 bases.push(ancestor);
9902 }
9903 }
9904 Ok(bases)
9905}
9906
9907pub fn lexical_component_tiers<'a>(
9908 components: &'a [String],
9909 global: bool,
9910 lexical_scope: &'a [String],
9911) -> impl Iterator<Item = Vec<String>> + 'a {
9912 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
9913 (0..=first_prefix_len).rev().map(move |prefix_len| {
9914 let mut qualified = Vec::with_capacity(prefix_len + components.len());
9915 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
9916 qualified.extend_from_slice(components);
9917 qualified
9918 })
9919}
9920
9921pub fn build_visible_identifier_index(
9922 analyzer: &CppGraphSource<'_>,
9923 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
9924 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9925 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
9926) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
9927 let mut out = HashMap::default();
9928 for (file, visible) in visible_by_file {
9929 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9930 for unit in visible {
9931 if unit.is_field()
9932 && !visible_source_files_by_root
9933 .get(file)
9934 .is_some_and(|sources| sources.contains(unit.source()))
9935 && cpp_global_field_has_internal_linkage_cached(
9936 analyzer,
9937 global_field_internal_linkage,
9938 unit,
9939 )
9940 {
9941 continue;
9942 }
9943 by_identifier
9944 .entry(unit.identifier().to_string())
9945 .or_default()
9946 .push(unit.clone());
9947 }
9948 for units in by_identifier.values_mut() {
9949 sort_lookup_units(units);
9950 units.dedup();
9951 }
9952 out.insert(file.clone(), by_identifier);
9953 }
9954 out
9955}
9956
9957fn sort_lookup_units(units: &mut [CodeUnit]) {
9958 units.sort_by(|left, right| {
9959 left.fq_name()
9960 .cmp(&right.fq_name())
9961 .then_with(|| left.signature().cmp(&right.signature()))
9962 .then_with(|| left.source().cmp(right.source()))
9963 .then_with(|| left.kind().cmp(&right.kind()))
9964 .then_with(|| {
9965 left.package_segment_count()
9966 .cmp(&right.package_segment_count())
9967 })
9968 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
9969 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
9970 });
9971}
9972
9973fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
9974 let interner = segment_interner();
9975 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
9976 let (left_text, left_kind) = interner.resolve(left_id);
9977 let (right_text, right_kind) = interner.resolve(right_id);
9978 let order = left_text
9979 .cmp(right_text)
9980 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
9981 if order != CmpOrdering::Equal {
9982 return order;
9983 }
9984 }
9985 left.len().cmp(&right.len())
9986}
9987
9988const fn segment_kind_order(kind: SegmentKind) -> u8 {
9989 match kind {
9990 SegmentKind::Path => 0,
9991 SegmentKind::Package => 1,
9992 SegmentKind::Type => 2,
9993 SegmentKind::Companion => 3,
9994 SegmentKind::Nested => 4,
9995 SegmentKind::Member => 5,
9996 SegmentKind::Unknown => 6,
9997 }
9998}
9999
10000fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10001 let mut deduped = Vec::with_capacity(units.len());
10002 for unit in units.drain(..) {
10003 if !deduped.contains(&unit) {
10004 deduped.push(unit);
10005 }
10006 }
10007 *units = deduped;
10008}
10009
10010pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10011 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10015 brokk_bifrost_core::analyzer::Language::Cpp,
10016 reference,
10017 );
10018 if parts.is_empty() {
10019 return Vec::new();
10020 }
10021
10022 let mut candidates = Vec::new();
10023 for package_len in 0..parts.len() {
10024 let package = parts[..package_len].join("::");
10025 let rest = &parts[package_len..];
10026 if rest.is_empty() {
10027 continue;
10028 }
10029 match kind {
10030 TargetKind::Type | TargetKind::Constructor => {
10031 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10032 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10033 }
10034 TargetKind::FreeFunction
10035 | TargetKind::Method
10036 | TargetKind::GlobalField
10037 | TargetKind::MemberField
10038 | TargetKind::Macro => {
10039 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10040 if rest.len() > 1 {
10041 let owner = rest[..rest.len() - 1].join("$");
10042 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10043 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10044 }
10045 }
10046 }
10047 }
10048 candidates
10049}
10050
10051fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10052 let fqn = if package.is_empty() {
10053 short.to_string()
10054 } else {
10055 format!("{package}.{short}")
10056 };
10057 if !out.contains(&fqn) {
10058 out.push(fqn);
10059 }
10060}
10061
10062pub fn infer_cpp_initializer_type(
10063 analyzer: &CppGraphSource<'_>,
10064 visibility: &VisibilityIndex<'_>,
10065 file: &ProjectFile,
10066 source: &str,
10067 node: Node<'_>,
10068) -> Option<CodeUnit> {
10069 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10070 .and_then(|binding| binding.unit)
10071}
10072
10073pub fn infer_cpp_initializer_binding(
10074 analyzer: &CppGraphSource<'_>,
10075 visibility: &VisibilityIndex<'_>,
10076 file: &ProjectFile,
10077 source: &str,
10078 node: Node<'_>,
10079 receiver_resolver: Option<&ReceiverResolver<'_>>,
10080) -> Option<CppScanBinding> {
10081 match node.kind() {
10082 "new_expression" => {
10083 let text = normalize_cpp_whitespace(node_text(node, source));
10084 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
10085 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
10086 let name = normalize_cpp_type_name(type_text);
10087 Some(CppScanBinding::from_type_name(
10088 name.clone(),
10089 visibility.resolve_type(file, &name),
10090 1,
10091 ))
10092 }
10093 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10094 if function.kind() == "field_expression" {
10102 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10103 return resolve_field_method_call_return_binding(
10104 analyzer,
10105 visibility,
10106 file,
10107 source,
10108 function,
10109 arity,
10110 receiver_resolver,
10111 );
10112 }
10113 let function_text = node_text(function, source);
10114 let direct_type_binding = visibility
10115 .resolve_type(file, function_text)
10116 .map(|unit| CppScanBinding::from_unit(unit, 0));
10117 if function.kind() == "template_function" && direct_type_binding.is_some() {
10118 let lexical_namespace = enclosing_namespace_context(node, source);
10119 let arity = visibility.call_arity_evidence(file, node, source).exact();
10120 if let Some(arity) = arity
10121 && let Some(binding) = visibility.resolve_call_return_binding(
10122 analyzer,
10123 file,
10124 function_text,
10125 arity,
10126 lexical_namespace.as_deref(),
10127 direct_type_binding
10128 .as_ref()
10129 .and_then(|binding| binding.unit.as_ref()),
10130 )
10131 {
10132 return Some(binding);
10133 }
10134 let (has_callable, callable_binding) = visibility
10135 .resolve_call_return_binding_without_arity(
10136 analyzer,
10137 file,
10138 function_text,
10139 lexical_namespace.as_deref(),
10140 direct_type_binding
10141 .as_ref()
10142 .and_then(|binding| binding.unit.as_ref()),
10143 );
10144 if let Some(binding) = callable_binding {
10145 return Some(binding);
10146 }
10147 if has_callable {
10148 return None;
10149 }
10150 return direct_type_binding;
10151 }
10152 let arity = visibility.call_arity_evidence(file, node, source).exact();
10157 if let Some(arity) = arity {
10158 let direct_type_binding_for_call = direct_type_binding.clone();
10159 if let Some(binding) = resolve_static_method_call_return_binding(
10160 analyzer, visibility, file, source, function, arity,
10161 )
10162 .or_else(|| {
10163 visibility.resolve_call_return_binding(
10168 analyzer,
10169 file,
10170 function_text,
10171 arity,
10172 enclosing_namespace_context(node, source).as_deref(),
10173 direct_type_binding_for_call
10174 .as_ref()
10175 .and_then(|binding| binding.unit.as_ref()),
10176 )
10177 }) {
10178 return Some(binding);
10179 }
10180 }
10181 direct_type_binding
10182 }),
10183 _ => None,
10184 }
10185}
10186
10187fn resolve_static_method_call_return_binding(
10188 analyzer: &CppGraphSource<'_>,
10189 visibility: &VisibilityIndex<'_>,
10190 file: &ProjectFile,
10191 source: &str,
10192 function: Node<'_>,
10193 arity: usize,
10194) -> Option<CppScanBinding> {
10195 if function.kind() != "qualified_identifier" {
10196 return None;
10197 }
10198 let qualified = normalize_cpp_reference_text(node_text(function, source));
10199 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10206 brokk_bifrost_core::analyzer::Language::Cpp,
10207 &qualified,
10208 );
10209 let (owner_text, member_name) = match parts.split_last() {
10210 Some((member, owner_parts)) if !owner_parts.is_empty() => {
10211 (owner_parts.join("::"), member.clone())
10212 }
10213 _ => {
10214 let scope = function.child_by_field_name("scope")?;
10215 let name = function.child_by_field_name("name")?;
10216 (
10217 node_text(scope, source).to_string(),
10218 node_text(name, source).to_string(),
10219 )
10220 }
10221 };
10222 let owner = visibility.resolve_type(file, &owner_text)?;
10223 let candidates = visibility
10224 .visible_members_for_owner_name(file, &owner, &member_name)
10225 .into_iter()
10226 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
10227 .cloned()
10228 .collect::<Vec<_>>();
10229 unanimous_return_binding(analyzer, visibility, file, &candidates)
10230}
10231
10232fn resolve_field_method_call_return_binding(
10233 analyzer: &CppGraphSource<'_>,
10234 visibility: &VisibilityIndex<'_>,
10235 file: &ProjectFile,
10236 source: &str,
10237 function: Node<'_>,
10238 arity: usize,
10239 receiver_resolver: Option<&ReceiverResolver<'_>>,
10240) -> Option<CppScanBinding> {
10241 debug_assert_eq!(
10242 function.kind(),
10243 "field_expression",
10244 "the member-call return binding answers only for a field-expression callee"
10245 );
10246 let receiver_resolver = receiver_resolver?;
10247 let field = function.child_by_field_name("field")?;
10248 let member_name = node_text(function_terminal_node(field), source);
10249 let receiver = function
10250 .child_by_field_name("argument")
10251 .or_else(|| function.named_child(0))?;
10252 let owners = receiver_resolver(receiver, source);
10253 let mut candidates = Vec::new();
10254 for owner in owners {
10255 let declaring_owner =
10256 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10257 EnclosingMemberOwnerResolution::Owner(owner) => owner,
10258 EnclosingMemberOwnerResolution::Missing => continue,
10259 EnclosingMemberOwnerResolution::Ambiguous => return None,
10260 };
10261 candidates.extend(
10262 visibility
10263 .visible_members_for_owner_name(file, &declaring_owner, member_name)
10264 .into_iter()
10265 .filter(|unit| {
10266 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
10267 })
10268 .cloned(),
10269 );
10270 }
10271 unanimous_return_binding(analyzer, visibility, file, &candidates)
10272}
10273
10274fn unanimous_return_binding(
10275 analyzer: &CppGraphSource<'_>,
10276 visibility: &VisibilityIndex<'_>,
10277 file: &ProjectFile,
10278 candidates: &[CodeUnit],
10279) -> Option<CppScanBinding> {
10280 let mut resolved_return: Option<CppScanBinding> = None;
10281 for function in candidates {
10282 let metadata = analyzer.signature_metadata(function);
10283 let return_types = if metadata.is_empty() {
10284 vec![cpp_function_return_type_text(analyzer, function)?]
10285 } else {
10286 metadata
10287 .iter()
10288 .map(|metadata| metadata.return_type_text().map(str::to_string))
10289 .collect::<Option<Vec<_>>>()?
10290 };
10291 for return_text in return_types {
10292 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10293 let name = normalize_cpp_type_name(&return_text);
10294 let binding = CppScanBinding::from_type_name(
10295 name.clone(),
10296 visibility
10297 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10298 indirection,
10299 );
10300 if let Some(existing) = resolved_return.as_ref()
10301 && (existing.indirection != binding.indirection
10302 || match (&existing.unit, &binding.unit) {
10303 (Some(left), Some(right)) => !same_visible_symbol(left, right),
10304 (None, None) => existing.type_name != binding.type_name,
10305 (Some(_), None) | (None, Some(_)) => true,
10306 })
10307 {
10308 return None;
10309 }
10310 resolved_return = Some(binding);
10311 }
10312 }
10313 resolved_return
10314}
10315
10316fn aliases_from_prepared_source(
10317 cpp: &dyn CppSource,
10318 token: QueryToken<'_>,
10319 file: &ProjectFile,
10320) -> Vec<CppAlias> {
10321 let Some(prepared) = cpp.prepared_syntax(token, file) else {
10322 return Vec::new();
10323 };
10324 let mut aliases = Vec::new();
10325 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
10326 aliases
10327}
10328
10329fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10330 walk_named_tree_preorder(root, true, |node| {
10331 match node.kind() {
10332 "alias_declaration" if alias_has_visible_file_scope(node) => {
10333 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
10334 out.push(alias);
10335 }
10336 }
10337 "type_definition" if alias_has_visible_file_scope(node) => {
10338 collect_typedef_aliases(node, source, out)
10339 }
10340 _ => {}
10341 }
10342 WalkControl::Continue
10343 });
10344}
10345
10346fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10347 let mut current = node.parent();
10348 while let Some(parent) = current {
10349 match parent.kind() {
10350 "translation_unit"
10351 | "namespace_definition"
10352 | "declaration_list"
10353 | "linkage_specification" => current = parent.parent(),
10354 "template_declaration" => current = parent.parent(),
10355 _ => return false,
10356 }
10357 }
10358 true
10359}
10360
10361fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
10362 let name = node
10363 .child_by_field_name("name")
10364 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10365 let target = node
10366 .child_by_field_name("type")
10367 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10368 Some(CppAlias {
10369 name,
10370 target,
10371 namespace: enclosing_namespace_context(node, source),
10372 })
10373}
10374
10375fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10376 let Some(type_node) = node.child_by_field_name("type") else {
10377 return;
10378 };
10379 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
10380 return;
10381 };
10382
10383 let mut cursor = node.walk();
10384 for child in node.named_children(&mut cursor) {
10385 if same_node(child, type_node) {
10386 continue;
10387 }
10388 if let Some(name) = extract_typedef_declarator_name(child, source) {
10389 out.push(CppAlias {
10390 name,
10391 target: target.clone(),
10392 namespace: enclosing_namespace_context(node, source),
10393 });
10394 }
10395 }
10396}
10397
10398fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
10399 match node.kind() {
10400 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
10401 normalize_reference_name(node_text(node, source))
10402 }
10403 _ => node
10404 .child_by_field_name("declarator")
10405 .or_else(|| node.child_by_field_name("name"))
10406 .or_else(|| last_named_child(node))
10407 .and_then(|child| extract_typedef_declarator_name(child, source)),
10408 }
10409}
10410
10411fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
10412 let count = node.named_child_count();
10413 if count == 0 {
10414 None
10415 } else {
10416 node.named_child(count - 1)
10417 }
10418}
10419
10420pub fn collect_include_closure(
10421 analyzer: &CppGraphSource<'_>,
10422 include_targets: &IncludeTargetIndex,
10423 file: &ProjectFile,
10424 out: &mut HashSet<ProjectFile>,
10425 cancellation: Option<&CancellationToken>,
10426) {
10427 let mut stack = vec![file.clone()];
10428 while let Some(file) = stack.pop() {
10429 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10430 break;
10431 }
10432 if !out.insert(file.clone()) {
10433 continue;
10434 }
10435 let imports = analyzer.import_statements(&file);
10436 for include in cpp_include_paths(&imports) {
10437 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10438 stack.push(target);
10439 }
10440 }
10441 }
10442}
10443
10444fn collect_visible_declarations(
10445 include_graph: &IncludeGraph,
10446 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10447 file: &ProjectFile,
10448 visited: &mut HashSet<ProjectFile>,
10449 out: &mut HashSet<CodeUnit>,
10450 cancellation: Option<&CancellationToken>,
10451) {
10452 let mut stack = vec![file.clone()];
10453 while let Some(file) = stack.pop() {
10454 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10455 break;
10456 }
10457 if !visited.insert(file.clone()) {
10458 continue;
10459 }
10460 if let Some(declarations) = declarations_by_file.get(&file) {
10461 out.extend(declarations.iter().cloned());
10462 }
10463 stack.extend(include_graph.targets(&file).iter().cloned());
10464 }
10465}
10466
10467pub fn signature_arity(signature: Option<&str>) -> usize {
10468 let Some(signature) = signature else {
10469 return 0;
10470 };
10471 let inner = signature
10472 .find('(')
10473 .and_then(|open| {
10474 signature[open + 1..]
10475 .find(')')
10476 .map(|close| &signature[open + 1..open + 1 + close])
10477 })
10478 .unwrap_or(signature)
10479 .trim();
10480 if inner.is_empty() || inner == "void" {
10481 return 0;
10482 }
10483 cpp_split_top_level_commas(inner).count()
10484}
10485
10486fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10487 let source = format!("void __bifrost_macro_parameters({replacement});");
10488 let mut parser = Parser::new();
10489 parser
10490 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10491 .ok()?;
10492 let tree = parser.parse(&source, None)?;
10493 let root = tree.root_node();
10494 if root.has_error() {
10495 return None;
10496 }
10497 let declaration = root.named_child(0)?;
10498 let declarator = declaration.child_by_field_name("declarator")?;
10499 let parameters = declarator.child_by_field_name("parameters")?;
10500 let mut required = 0;
10501 let mut total = 0;
10502 let mut repeated = false;
10503 let mut cursor = parameters.walk();
10504 for parameter in parameters.children(&mut cursor) {
10505 match parameter.kind() {
10506 "parameter_declaration" => {
10507 if parameter.child_by_field_name("declarator").is_none()
10508 && parameter
10509 .child_by_field_name("type")
10510 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
10511 {
10512 continue;
10513 }
10514 required += 1;
10515 total += 1;
10516 }
10517 "optional_parameter_declaration" => total += 1,
10518 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
10519 repeated = true;
10520 }
10521 _ => {}
10522 }
10523 }
10524 Some(CallableArity::new(required, total, repeated))
10525}
10526
10527pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
10528 analyzer
10529 .signature_metadata(unit)
10530 .into_iter()
10531 .find_map(|metadata| metadata.callable_arity())
10532 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
10533}
10534
10535pub fn cpp_callable_parameter_types(
10536 analyzer: &CppGraphSource<'_>,
10537 unit: &CodeUnit,
10538) -> Option<Vec<String>> {
10539 analyzer
10540 .signature_metadata(unit)
10541 .into_iter()
10542 .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
10543 .or_else(|| unit.signature().and_then(cpp_signature_param_types))
10544}
10545
10546fn merge_compatible_callable_arities(
10547 left: CallableArity,
10548 right: CallableArity,
10549) -> Option<CallableArity> {
10550 let total = left.total();
10551 let left_repeated = left.accepts(total.saturating_add(1));
10552 let right_repeated = right.accepts(right.total().saturating_add(1));
10553 if total != right.total() || left_repeated != right_repeated {
10554 return None;
10555 }
10556 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10557 Some(CallableArity::new(required, total, left_repeated))
10558}
10559
10560fn find_include_activation(
10561 cpp: &dyn CppSource,
10562 token: QueryToken<'_>,
10563 file: &ProjectFile,
10564 prepared: &PreparedSyntaxTree,
10565 donor_source: &ProjectFile,
10566) -> Option<usize> {
10567 let include_targets = cpp.include_target_index();
10568 let mut direct_includes = Vec::new();
10569 let mut nodes = vec![prepared.tree().root_node()];
10570 let reference = CallableReferenceContext {
10573 file,
10574 position: None,
10575 };
10576 while let Some(node) = nodes.pop() {
10577 if node.kind() == "preproc_include" {
10578 if callable_preprocessor_context_is_visible_for_reference(
10579 node,
10580 prepared.source(),
10581 &reference,
10582 ) {
10583 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10584 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10585 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10586 file,
10587 &include,
10588 include_targets,
10589 )) {
10590 direct_includes.push((node.end_byte(), target));
10591 }
10592 }
10593 }
10594 continue;
10595 }
10596 push_named_children_reversed(node, &mut nodes);
10597 }
10598 direct_includes.sort_by_key(|(activation, _)| *activation);
10599 let mut known_missing = HashSet::default();
10600 direct_includes
10601 .into_iter()
10602 .find(|(_, direct)| {
10603 unconditional_include_reaches(
10604 cpp,
10605 token,
10606 include_targets,
10607 direct,
10608 donor_source,
10609 file,
10610 &mut known_missing,
10611 )
10612 })
10613 .map(|(activation, _)| activation)
10614}
10615
10616fn find_conditional_include_projection_index(
10617 cpp: &dyn CppSource,
10618 token: QueryToken<'_>,
10619 file: &ProjectFile,
10620 prepared: &PreparedSyntaxTree,
10621 on_state: &dyn Fn(),
10622) -> ConditionalIncludeProjectionIndex {
10623 let reference_is_c = reference_uses_c_semantics(cpp, file);
10624 let include_targets = cpp.include_target_index();
10625 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10626 HashMap::default();
10627 let mut pending = Vec::new();
10628 let mut nodes = vec![prepared.tree().root_node()];
10629 while let Some(node) = nodes.pop() {
10630 if node.kind() == "preproc_include" {
10631 let Some(required_guards) =
10632 include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10633 else {
10634 continue;
10635 };
10636 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10637 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10638 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10639 file,
10640 &include,
10641 include_targets,
10642 )) else {
10643 continue;
10644 };
10645 pending.push((target, node.end_byte(), required_guards.clone()));
10646 }
10647 continue;
10648 }
10649 push_named_children_reversed(node, &mut nodes);
10650 }
10651
10652 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10664 HashMap::default();
10665 while let Some((current_file, activation_byte, path)) = pending.pop() {
10666 let guard_sets = expanded
10667 .entry((current_file.clone(), activation_byte))
10668 .or_default();
10669 if guard_sets
10673 .iter()
10674 .any(|existing| existing.is_subset(&path.all))
10675 {
10676 continue;
10677 }
10678 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10679 .drain(..)
10680 .partition(|existing| path.all.is_subset(existing));
10681 *guard_sets = kept;
10682 guard_sets.push(path.all.clone());
10683 if !evicted.is_empty()
10684 && let Some(projections) = projections_by_source.get_mut(¤t_file)
10685 {
10686 projections.retain(|projection| {
10687 projection.activation_byte != activation_byte
10688 || !evicted.contains(&projection.required_guards)
10689 });
10690 }
10691 on_state();
10692
10693 projections_by_source
10696 .entry(current_file.clone())
10697 .or_default()
10698 .push(ConditionalIncludeProjection {
10699 activation_byte,
10700 required_guards: path.all.clone(),
10701 partial_guards: path.partial.clone(),
10702 });
10703
10704 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10705 continue;
10706 };
10707 let mut nodes = vec![current_prepared.tree().root_node()];
10708 while let Some(node) = nodes.pop() {
10709 if node.kind() == "preproc_include" {
10710 let Some(include_guards) = include_directive_guard_requirements(
10711 node,
10712 current_prepared.source(),
10713 reference_is_c,
10714 ) else {
10715 continue;
10716 };
10717 let Some(reached) = path.merged(&include_guards) else {
10718 continue;
10719 };
10720 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
10721 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10722 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10723 ¤t_file,
10724 &include,
10725 include_targets,
10726 )) else {
10727 continue;
10728 };
10729 pending.push((target, activation_byte, reached.clone()));
10730 }
10731 continue;
10732 }
10733 push_named_children_reversed(node, &mut nodes);
10734 }
10735 }
10736
10737 projections_by_source
10738 .into_iter()
10739 .map(|(source, mut projections)| {
10740 projections.sort_by_key(|projection| projection.activation_byte);
10741 (source, Arc::from(projections))
10742 })
10743 .collect()
10744}
10745
10746#[allow(clippy::too_many_arguments)]
10752fn find_conditional_include_projection_for_source(
10753 cpp: &dyn CppSource,
10754 token: QueryToken<'_>,
10755 file: &ProjectFile,
10756 prepared: &PreparedSyntaxTree,
10757 donor_source: &ProjectFile,
10758 admission: IncludePathAdmission,
10759 reference_guards: Option<&HashSet<PreprocessorGuard>>,
10760 reference_byte: usize,
10761 on_state: &dyn Fn(),
10762) -> bool {
10763 let Some(reference_guards) = reference_guards else {
10764 return false;
10765 };
10766 let reference_is_c = reference_uses_c_semantics(cpp, file);
10767 let include_targets = cpp.include_target_index();
10768 let mut pending = Vec::new();
10769 let mut nodes = vec![prepared.tree().root_node()];
10770 while let Some(node) = nodes.pop() {
10771 if node.kind() == "preproc_include" {
10772 let Some(required_guards) =
10773 include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10774 else {
10775 continue;
10776 };
10777 if node.end_byte() > reference_byte
10778 || !admission.admits(
10779 &required_guards.all,
10780 &required_guards.partial,
10781 Some(reference_guards),
10782 )
10783 {
10784 continue;
10785 }
10786 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10787 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10788 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10789 file,
10790 &include,
10791 include_targets,
10792 )) else {
10793 continue;
10794 };
10795 if &target == donor_source {
10796 return true;
10797 }
10798 pending.push((target, required_guards.clone()));
10799 }
10800 continue;
10801 }
10802 push_named_children_reversed(node, &mut nodes);
10803 }
10804
10805 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
10806 while let Some((current_file, path)) = pending.pop() {
10807 let guard_sets = expanded.entry(current_file.clone()).or_default();
10808 if guard_sets.contains(&path.all) {
10809 continue;
10810 }
10811 guard_sets.push(path.all.clone());
10812 on_state();
10813
10814 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10815 continue;
10816 };
10817 let mut nodes = vec![current_prepared.tree().root_node()];
10818 while let Some(node) = nodes.pop() {
10819 if node.kind() == "preproc_include" {
10820 let Some(include_guards) = include_directive_guard_requirements(
10821 node,
10822 current_prepared.source(),
10823 reference_is_c,
10824 ) else {
10825 continue;
10826 };
10827 let Some(reached) = path.merged(&include_guards) else {
10828 continue;
10829 };
10830 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
10831 continue;
10832 }
10833 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
10834 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10835 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10836 ¤t_file,
10837 &include,
10838 include_targets,
10839 )) else {
10840 continue;
10841 };
10842 if &target == donor_source {
10843 return true;
10844 }
10845 pending.push((target, reached.clone()));
10846 }
10847 continue;
10848 }
10849 push_named_children_reversed(node, &mut nodes);
10850 }
10851 }
10852 false
10853}
10854
10855pub fn cpp_include_closure_reaches(
10868 cpp: &dyn CppSource,
10869 token: QueryToken<'_>,
10870 translation_unit: &ProjectFile,
10871 header: &ProjectFile,
10872) -> bool {
10873 unconditional_include_reaches(
10874 cpp,
10875 token,
10876 cpp.include_target_index(),
10877 translation_unit,
10878 header,
10879 translation_unit,
10880 &mut HashSet::default(),
10881 )
10882}
10883
10884fn unconditional_include_reaches(
10885 cpp: &dyn CppSource,
10886 token: QueryToken<'_>,
10887 include_targets: &IncludeTargetIndex,
10888 first: &ProjectFile,
10889 donor_source: &ProjectFile,
10890 reference_file: &ProjectFile,
10891 known_missing: &mut HashSet<ProjectFile>,
10892) -> bool {
10893 if first == donor_source {
10894 return true;
10895 }
10896 if known_missing.contains(first) {
10897 return false;
10898 }
10899 let reference_is_c = reference_file
10900 .rel_path()
10901 .extension()
10902 .and_then(|extension| extension.to_str())
10903 == Some("c");
10904 if let Some(reaches) =
10905 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
10906 {
10907 return reaches;
10908 }
10909 let mut visited = HashSet::default();
10910 let mut files = vec![first.clone()];
10911 let reference = CallableReferenceContext {
10914 file: reference_file,
10915 position: None,
10916 };
10917 while let Some(file) = files.pop() {
10918 if file == *donor_source {
10919 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
10920 return true;
10921 }
10922 if known_missing.contains(&file) || !visited.insert(file.clone()) {
10923 continue;
10924 }
10925 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
10926 continue;
10927 };
10928 let mut cursor = prepared.tree().walk();
10930 let mut nodes = vec![prepared.tree().root_node()];
10931 while let Some(node) = nodes.pop() {
10932 if node.kind() == "preproc_include" {
10933 if callable_preprocessor_context_is_visible_for_reference(
10934 node,
10935 prepared.source(),
10936 &reference,
10937 ) {
10938 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10939 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10940 if let Some(target) = unique_include_target(
10941 resolve_include_targets_with_index(&file, &include, include_targets),
10942 ) {
10943 files.push(target);
10944 }
10945 }
10946 }
10947 continue;
10948 }
10949 let first_pushed = nodes.len();
10950 nodes.extend(node.named_children(&mut cursor));
10951 nodes[first_pushed..].reverse();
10952 }
10953 }
10954 known_missing.extend(visited);
10955 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
10956 false
10957}
10958
10959fn declaration_guard_requirements(
10960 analyzer: &CppGraphSource<'_>,
10961 cpp: &dyn CppSource,
10962 candidate: &CodeUnit,
10963) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
10964 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
10965 return Vec::new();
10966 };
10967 let root = prepared.tree().root_node();
10968 analyzer
10969 .ranges(candidate)
10970 .into_iter()
10971 .filter_map(|range| {
10972 root.descendant_for_byte_range(range.start_byte, range.end_byte)
10973 .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
10974 .map(|required| (range.start_byte, required))
10978 })
10979 .collect()
10980}
10981
10982fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
10983 analyzer
10984 .ranges(candidate)
10985 .into_iter()
10986 .map(|range| range.start_byte)
10987 .min()
10988}
10989
10990fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
10996 let (first, rest) = contexts.split_first()?;
10997 Some(
10998 first
10999 .defined_macros
11000 .iter()
11001 .filter(|name| {
11002 rest.iter()
11003 .all(|context| context.defined_macros.contains(*name))
11004 })
11005 .cloned()
11006 .collect(),
11007 )
11008}
11009
11010pub fn guard_requirements_hold_at_reference(
11011 required: &HashSet<PreprocessorGuard>,
11012 reference: Option<&HashSet<PreprocessorGuard>>,
11013) -> bool {
11014 reference.is_some_and(|active| {
11015 required
11016 .iter()
11017 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11018 })
11019}
11020
11021fn preprocessor_guard_holds_at_reference(
11022 required: &PreprocessorGuard,
11023 active: &HashSet<PreprocessorGuard>,
11024) -> bool {
11025 if active.contains(required) {
11026 return true;
11027 }
11028 let active_expression = BooleanGuardExpression::all(
11029 active
11030 .iter()
11031 .filter_map(PreprocessorGuard::as_boolean_expression),
11032 );
11033 required
11034 .as_boolean_expression()
11035 .is_some_and(|required| active_expression.implies(&required))
11036}
11037
11038fn guards_compatible_at_reference(
11043 declaration: &HashSet<PreprocessorGuard>,
11044 reference: Option<&HashSet<PreprocessorGuard>>,
11045) -> bool {
11046 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11047}
11048
11049pub fn preprocessor_conditional_family_range(
11058 root: Node<'_>,
11059 start_byte: usize,
11060 end_byte: usize,
11061) -> Option<(usize, usize)> {
11062 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11063 let mut ancestor = Some(node);
11064 while let Some(current) = ancestor {
11065 if is_preprocessor_conditional(current)
11066 && preprocessor_conditional_contains_descendant(current, node)
11067 {
11068 let family = preprocessor_conditional_family_root(current);
11069 return Some((family.start_byte(), family.end_byte()));
11070 }
11071 ancestor = current.parent();
11072 }
11073 None
11074}
11075
11076fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
11077 let mut ancestor = node.parent();
11078 while let Some(current) = ancestor {
11079 if is_preprocessor_conditional(current)
11080 && preprocessor_conditional_contains_descendant(current, node)
11081 {
11082 let family = preprocessor_conditional_family_root(current);
11083 if preprocessor_conditional_family_has_terminal_else(family) {
11084 return Some(family);
11085 }
11086 }
11087 ancestor = current.parent();
11088 }
11089 None
11090}
11091
11092fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11093 while let Some(parent) = conditional.parent() {
11094 let is_alternative = parent
11095 .child_by_field_name("alternative")
11096 .is_some_and(|alternative| {
11097 alternative.start_byte() == conditional.start_byte()
11098 && alternative.end_byte() == conditional.end_byte()
11099 });
11100 if !is_alternative {
11101 break;
11102 }
11103 conditional = parent;
11104 }
11105 conditional
11106}
11107
11108fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11109 loop {
11110 let Some(alternative) = conditional.child_by_field_name("alternative") else {
11111 return false;
11112 };
11113 match alternative.kind() {
11114 "preproc_else" => return true,
11115 "preproc_elif" => conditional = alternative,
11116 _ => return false,
11117 }
11118 }
11119}
11120
11121fn include_directive_guard_requirements(
11134 node: Node<'_>,
11135 source: &str,
11136 reference_is_c: bool,
11137) -> Option<PreprocessorGuardEnvironment> {
11138 let required = preprocessor_guard_environment_by_family(node, source)?;
11139 let excluded_by_language = required.all.iter().any(|guard| match guard {
11140 PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11141 PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11142 _ => false,
11143 });
11144 (!excluded_by_language).then_some(required)
11145}
11146
11147pub fn preprocessor_guard_environment(
11148 node: Node<'_>,
11149 source: &str,
11150) -> Option<HashSet<PreprocessorGuard>> {
11151 preprocessor_guard_environment_by_family(node, source).map(|environment| environment.all)
11152}
11153
11154#[derive(Clone)]
11165struct PreprocessorGuardEnvironment {
11166 all: HashSet<PreprocessorGuard>,
11167 partial: HashSet<PreprocessorGuard>,
11168}
11169
11170impl PreprocessorGuardEnvironment {
11171 fn merged(&self, other: &Self) -> Option<Self> {
11175 Some(Self {
11176 all: merge_preprocessor_guards(&self.all, &other.all)?,
11177 partial: self.partial.union(&other.partial).cloned().collect(),
11178 })
11179 }
11180}
11181
11182fn preprocessor_guard_environment_by_family(
11183 node: Node<'_>,
11184 source: &str,
11185) -> Option<PreprocessorGuardEnvironment> {
11186 let mut all = HashSet::default();
11187 let mut partial = HashSet::default();
11188 let mut ancestor = node.parent();
11189 while let Some(conditional) = ancestor {
11190 if matches!(
11191 conditional.kind(),
11192 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11193 ) && !is_file_covering_include_guard(conditional, source)
11194 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11195 && preprocessor_conditional_contains_descendant(conditional, node)
11196 {
11197 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11198 match guard {
11199 PreprocessorGuard::Constant(true) => {
11200 ancestor = conditional.parent();
11201 continue;
11202 }
11203 PreprocessorGuard::Constant(false) => return None,
11204 _ => {}
11205 }
11206 if all.contains(&guard.negated()) {
11207 return None;
11208 }
11209 if !preprocessor_conditional_family_has_terminal_else(
11210 preprocessor_conditional_family_root(conditional),
11211 ) {
11212 partial.insert(guard.clone());
11213 }
11214 all.insert(guard);
11215 }
11216 ancestor = conditional.parent();
11217 }
11218 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11219 match guard {
11220 PreprocessorGuard::Constant(true) => {}
11221 PreprocessorGuard::Constant(false) => return None,
11222 _ => {
11223 if all.contains(&guard.negated()) {
11224 return None;
11225 }
11226 partial.insert(guard.clone());
11230 all.insert(guard);
11231 }
11232 }
11233 }
11234 Some(PreprocessorGuardEnvironment { all, partial })
11235}
11236
11237fn fragmented_statement_preprocessor_guard(
11238 descendant: Node<'_>,
11239 source: &str,
11240) -> Option<PreprocessorGuard> {
11241 let mut ancestor = descendant.parent();
11247 while let Some(statement) = ancestor {
11248 if statement.kind() == "if_statement"
11249 && let (Some(consequence), Some(alternative)) = (
11250 statement.child_by_field_name("consequence"),
11251 statement.child_by_field_name("alternative"),
11252 )
11253 && alternative.start_byte() <= descendant.start_byte()
11254 && descendant.end_byte() <= alternative.end_byte()
11255 {
11256 let mut cursor = consequence.walk();
11257 let openers = consequence
11258 .named_children(&mut cursor)
11259 .filter(|child| {
11260 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11261 && child
11262 .child(child.child_count().saturating_sub(1))
11263 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11264 })
11265 .collect::<Vec<_>>();
11266 if openers.len() != 1 {
11267 ancestor = statement.parent();
11268 continue;
11269 }
11270
11271 let mut terminators = Vec::new();
11272 let mut stack = vec![alternative];
11273 while let Some(node) = stack.pop() {
11274 if node.kind() == "preproc_call"
11275 && node.start_byte() >= descendant.end_byte()
11276 && node
11277 .child_by_field_name("directive")
11278 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11279 {
11280 terminators.push(node);
11281 continue;
11282 }
11283 push_named_children_reversed(node, &mut stack);
11284 }
11285 if terminators.len() == 1 {
11286 return simple_preprocessor_guard(openers[0], source);
11287 }
11288 }
11289 ancestor = statement.parent();
11290 }
11291 None
11292}
11293
11294fn preprocessor_guard_for_descendant(
11295 conditional: Node<'_>,
11296 descendant: Node<'_>,
11297 source: &str,
11298) -> Option<PreprocessorGuard> {
11299 let mut guard = simple_preprocessor_guard(conditional, source)?;
11300 if conditional
11301 .child_by_field_name("alternative")
11302 .is_some_and(|alternative| {
11303 alternative.start_byte() <= descendant.start_byte()
11304 && descendant.end_byte() <= alternative.end_byte()
11305 })
11306 {
11307 let alternative = conditional.child_by_field_name("alternative")?;
11308 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11312 return None;
11313 }
11314 guard = guard.negated();
11315 }
11316 Some(guard)
11317}
11318
11319fn preprocessor_conditional_contains_descendant(
11320 conditional: Node<'_>,
11321 descendant: Node<'_>,
11322) -> bool {
11323 cpp_displaced_preprocessor_boundary(conditional)
11324 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11325}
11326
11327pub fn merge_preprocessor_guards(
11328 left: &HashSet<PreprocessorGuard>,
11329 right: &HashSet<PreprocessorGuard>,
11330) -> Option<HashSet<PreprocessorGuard>> {
11331 let mut merged = left.clone();
11332 for guard in right {
11333 let boolean_negation = guard
11334 .as_boolean_expression()
11335 .map(|expression| expression.negated());
11336 if merged.contains(&guard.negated())
11337 || boolean_negation.is_some_and(|negated| {
11338 merged
11339 .iter()
11340 .filter_map(PreprocessorGuard::as_boolean_expression)
11341 .any(|existing| existing == negated)
11342 })
11343 {
11344 return None;
11345 }
11346 merged.insert(guard.clone());
11347 }
11348 Some(merged)
11349}
11350
11351fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11352 if conditional.kind() == "preproc_ifdef" {
11353 let name = conditional.child_by_field_name("name")?;
11354 let name = node_text(name, source).to_string();
11355 return match conditional.child(0)?.kind() {
11356 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11357 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11358 _ => None,
11359 };
11360 }
11361 let condition = conditional.child_by_field_name("condition")?;
11362 simple_preprocessor_expression_guard(condition, source).or_else(|| {
11363 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11364 node_text(condition, source),
11365 )))
11366 })
11367}
11368
11369fn simple_preprocessor_expression_guard(
11370 expression: Node<'_>,
11371 source: &str,
11372) -> Option<PreprocessorGuard> {
11373 match expression.kind() {
11374 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11375 node_text(expression, source).to_string(),
11376 ))),
11377 "number_literal" => match node_text(expression, source).trim() {
11378 "0" => Some(PreprocessorGuard::Constant(false)),
11379 "1" => Some(PreprocessorGuard::Constant(true)),
11380 _ => None,
11381 },
11382 "preproc_defined" => {
11383 let identifier = (0..expression.named_child_count())
11384 .filter_map(|index| expression.named_child(index))
11385 .find(|child| child.kind() == "identifier")?;
11386 Some(PreprocessorGuard::Defined(
11387 node_text(identifier, source).to_string(),
11388 ))
11389 }
11390 "unary_expression"
11391 if expression
11392 .child_by_field_name("operator")
11393 .is_some_and(|operator| operator.kind() == "!") =>
11394 {
11395 simple_preprocessor_expression_guard(
11396 expression.child_by_field_name("argument")?,
11397 source,
11398 )
11399 .map(|guard| guard.negated())
11400 }
11401 "parenthesized_expression" => (0..expression.named_child_count())
11402 .filter_map(|index| expression.named_child(index))
11403 .next()
11404 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11405 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11406 expression, source,
11407 ))),
11408 _ => None,
11409 }
11410}
11411
11412fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11413 match expression.kind() {
11414 "number_literal" => match node_text(expression, source).trim() {
11415 "0" => BooleanGuardExpression::Constant(false),
11416 "1" => BooleanGuardExpression::Constant(true),
11417 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11418 expression, source,
11419 ))),
11420 },
11421 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11422 "preproc_defined" => {
11423 let identifier = (0..expression.named_child_count())
11424 .filter_map(|index| expression.named_child(index))
11425 .find(|child| child.kind() == "identifier");
11426 identifier.map_or_else(
11427 || {
11428 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11429 expression, source,
11430 )))
11431 },
11432 |identifier| {
11433 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11434 },
11435 )
11436 }
11437 "unary_expression"
11438 if expression
11439 .child_by_field_name("operator")
11440 .is_some_and(|operator| operator.kind() == "!") =>
11441 {
11442 expression.child_by_field_name("argument").map_or_else(
11443 || {
11444 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11445 expression, source,
11446 )))
11447 },
11448 |argument| boolean_preprocessor_expression(argument, source).negated(),
11449 )
11450 }
11451 "parenthesized_expression" => (0..expression.named_child_count())
11452 .filter_map(|index| expression.named_child(index))
11453 .next()
11454 .map_or_else(
11455 || {
11456 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11457 expression, source,
11458 )))
11459 },
11460 |child| boolean_preprocessor_expression(child, source),
11461 ),
11462 "binary_expression" => {
11463 let operands = || {
11464 Some((
11465 boolean_preprocessor_expression(
11466 expression.child_by_field_name("left")?,
11467 source,
11468 ),
11469 boolean_preprocessor_expression(
11470 expression.child_by_field_name("right")?,
11471 source,
11472 ),
11473 ))
11474 };
11475 match expression
11476 .child_by_field_name("operator")
11477 .map(|operator| operator.kind())
11478 {
11479 Some("&&") => operands().map_or_else(
11480 || {
11481 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11482 expression, source,
11483 )))
11484 },
11485 |(left, right)| BooleanGuardExpression::all([left, right]),
11486 ),
11487 Some("||") => operands().map_or_else(
11488 || {
11489 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11490 expression, source,
11491 )))
11492 },
11493 |(left, right)| BooleanGuardExpression::any([left, right]),
11494 ),
11495 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11496 expression, source,
11497 ))),
11498 }
11499 }
11500 _ => {
11501 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
11502 }
11503 }
11504}
11505
11506fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
11507 if targets.len() == 1 {
11508 targets.pop()
11509 } else {
11510 None
11511 }
11512}
11513
11514fn nameable_callable_declaration_nodes<'tree>(
11523 analyzer: &CppGraphSource<'_>,
11524 prepared: &'tree PreparedSyntaxTree,
11525 candidate: &CodeUnit,
11526) -> Vec<Node<'tree>> {
11527 callable_declaration_nodes(analyzer, prepared, candidate)
11528 .into_iter()
11529 .filter(|declaration| {
11530 let mut ancestor = declaration.parent();
11531 while let Some(node) = ancestor {
11532 if node.kind() == "function_definition"
11533 && is_recovered_declaration_scope_container(node, prepared.source())
11534 {
11535 ancestor = node.parent();
11536 continue;
11537 }
11538 if node.kind() == "compound_statement"
11539 && node.parent().is_some_and(|parent| {
11540 is_recovered_declaration_scope_container(parent, prepared.source())
11541 })
11542 {
11543 ancestor = node.parent().and_then(|parent| parent.parent());
11544 continue;
11545 }
11546 if matches!(
11547 node.kind(),
11548 "compound_statement" | "function_definition" | "lambda_expression"
11549 ) {
11550 return false;
11551 }
11552 ancestor = node.parent();
11553 }
11554 true
11555 })
11556 .collect()
11557}
11558
11559fn callable_declaration_nodes<'tree>(
11560 analyzer: &CppGraphSource<'_>,
11561 prepared: &'tree PreparedSyntaxTree,
11562 candidate: &CodeUnit,
11563) -> Vec<Node<'tree>> {
11564 let root = prepared.tree().root_node();
11565 analyzer
11566 .ranges(candidate)
11567 .into_iter()
11568 .filter_map(|range| {
11569 let mut declaration =
11570 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11571 while !matches!(
11584 declaration.kind(),
11585 "declaration" | "field_declaration" | "function_definition"
11586 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
11587 declaration,
11588 prepared.source(),
11589 ) {
11590 let Some(parent) = declaration.parent() else {
11591 break;
11592 };
11593 declaration = parent;
11594 }
11595 Some(declaration)
11596 })
11597 .collect()
11598}
11599
11600fn real_function_definition_ancestor<'tree>(
11601 node: Node<'tree>,
11602 source: &str,
11603) -> Option<Node<'tree>> {
11604 let mut ancestor = node.parent();
11605 while let Some(node) = ancestor {
11606 if node.kind() == "function_definition"
11607 && !is_recovered_declaration_scope_container(node, source)
11608 {
11609 return Some(node);
11610 }
11611 ancestor = node.parent();
11612 }
11613 None
11614}
11615
11616fn callable_declaration_activation_in_file(
11617 analyzer: &CppGraphSource<'_>,
11618 prepared: &PreparedSyntaxTree,
11619 candidate: &CodeUnit,
11620 reference: &CallableReferenceContext<'_>,
11621) -> Option<usize> {
11622 nameable_callable_declaration_nodes(analyzer, prepared, candidate)
11623 .into_iter()
11624 .filter(|declaration| {
11625 callable_preprocessor_context_is_visible_for_reference(
11626 *declaration,
11627 prepared.source(),
11628 reference,
11629 )
11630 })
11631 .map(callable_declaration_activation_byte)
11632 .min()
11633}
11634
11635fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
11640 if declaration.kind() != "function_definition" {
11641 return declaration.end_byte();
11642 }
11643 declaration
11644 .child_by_field_name("declarator")
11645 .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
11646}
11647
11648struct CallableReferenceContext<'a> {
11654 file: &'a ProjectFile,
11655 position: Option<CallableReferencePosition<'a>>,
11656}
11657
11658struct CallableReferencePosition<'a> {
11662 prepared: &'a PreparedSyntaxTree,
11663 byte: usize,
11664 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
11665}
11666
11667impl CallableReferenceContext<'_> {
11668 fn is_c(&self) -> bool {
11669 self.file
11670 .rel_path()
11671 .extension()
11672 .and_then(|extension| extension.to_str())
11673 == Some("c")
11674 }
11675
11676 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
11677 let position = self.position.as_ref()?;
11678 position
11679 .guards
11680 .get_or_init(|| {
11681 position
11682 .prepared
11683 .tree()
11684 .root_node()
11685 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
11686 .and_then(|node| {
11687 preprocessor_guard_environment(node, position.prepared.source())
11688 })
11689 })
11690 .as_ref()
11691 }
11692}
11693
11694fn callable_declaration_guard_requirements(
11706 node: Node<'_>,
11707 source: &str,
11708 reference: &CallableReferenceContext<'_>,
11709) -> Option<HashSet<PreprocessorGuard>> {
11710 let reference_is_c = reference.is_c();
11711 let mut required = HashSet::default();
11712 let mut ancestor = node.parent();
11713 while let Some(conditional) = ancestor {
11714 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11715 && !is_file_covering_include_guard(conditional, source)
11716 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11717 && preprocessor_conditional_contains_descendant(conditional, node)
11718 {
11719 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11720 match guard {
11721 PreprocessorGuard::Constant(true) => {}
11722 PreprocessorGuard::Constant(false) => return None,
11723 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
11724 if reference_is_c {
11725 return None;
11726 }
11727 }
11728 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
11729 if !reference_is_c {
11730 return None;
11731 }
11732 }
11733 guard => {
11734 required.insert(guard);
11735 }
11736 }
11737 }
11738 ancestor = conditional.parent();
11739 }
11740 Some(required)
11741}
11742
11743fn callable_preprocessor_context_is_visible_for_reference(
11747 node: Node<'_>,
11748 source: &str,
11749 reference: &CallableReferenceContext<'_>,
11750) -> bool {
11751 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
11752 return false;
11753 };
11754 required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards())
11755}
11756
11757fn flattened_macro_namespace_declaration_matches(
11758 analyzer: &CppGraphSource<'_>,
11759 cpp: &dyn CppSource,
11760 reference_file: &ProjectFile,
11761 visible_declaration: &CodeUnit,
11762 qualified_candidate: &CodeUnit,
11763 reference_byte: usize,
11764) -> bool {
11765 if visible_declaration.kind() != qualified_candidate.kind()
11771 || visible_declaration.identifier() != qualified_candidate.identifier()
11772 || visible_declaration.signature() != qualified_candidate.signature()
11773 || !visible_declaration.package_name().is_empty()
11774 || qualified_candidate.package_name().is_empty()
11775 {
11776 return false;
11777 }
11778
11779 let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
11780 return false;
11781 };
11782 let root = prepared.tree().root_node();
11783 let closing_brace_limit = if visible_declaration.source() == reference_file {
11784 reference_byte
11785 } else {
11786 usize::MAX
11787 };
11788
11789 analyzer
11790 .ranges(visible_declaration)
11791 .into_iter()
11792 .any(|range| {
11793 let Some(mut declaration) =
11794 root.descendant_for_byte_range(range.start_byte, range.end_byte)
11795 else {
11796 return false;
11797 };
11798 while !matches!(
11799 declaration.kind(),
11800 "declaration" | "field_declaration" | "function_definition"
11801 ) {
11802 let Some(parent) = declaration.parent() else {
11803 return false;
11804 };
11805 declaration = parent;
11806 }
11807 if declaration
11808 .parent()
11809 .is_none_or(|parent| parent.kind() != "translation_unit")
11810 || !macro_displaced_cpp_return_type(declaration, prepared.source())
11811 {
11812 return false;
11813 }
11814
11815 let mut cursor = root.walk();
11816 root.named_children(&mut cursor).any(|sibling| {
11817 sibling.start_byte() >= declaration.end_byte()
11818 && sibling.start_byte() < closing_brace_limit
11819 && direct_unmatched_closing_brace(sibling)
11820 })
11821 })
11822}
11823
11824fn flattened_macro_namespace_components(
11825 declaration: Node<'_>,
11826 source: &str,
11827) -> Option<Vec<String>> {
11828 flattened_macro_function_namespace_components(declaration, source)
11829 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
11830}
11831
11832fn flattened_macro_function_namespace_components(
11833 declaration: Node<'_>,
11834 source: &str,
11835) -> Option<Vec<String>> {
11836 let body = declaration
11837 .parent()
11838 .filter(|parent| parent.kind() == "compound_statement")?;
11839 let function = body.parent()?;
11840 if function.child_by_field_name("body") != Some(body) {
11841 return None;
11842 }
11843 let namespace_name = recovered_macro_namespace_name(function, source)?;
11844 let mut components = enclosing_namespace_components(declaration, source)?;
11845 components.push(namespace_name);
11846 Some(components)
11847}
11848
11849fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
11860 if function.kind() != "function_definition" || !function.has_error() {
11861 return None;
11862 }
11863 let body = function
11864 .child_by_field_name("body")
11865 .filter(|body| body.kind() == "compound_statement")?;
11866 let mut cursor = function.walk();
11867 let prefix = function
11868 .named_children(&mut cursor)
11869 .take_while(|child| child.start_byte() < body.start_byte())
11870 .filter(|child| child.kind() != "comment")
11871 .collect::<Vec<_>>();
11872 let begin_index = prefix.iter().rposition(|child| {
11873 flattened_macro_sentinel_name(*child, source)
11874 .is_some_and(|name| is_namespace_begin_sentinel(&name))
11875 })?;
11876 let mut identifiers = Vec::new();
11877 let mut stack = prefix[begin_index + 1..]
11878 .iter()
11879 .rev()
11880 .copied()
11881 .collect::<Vec<_>>();
11882 while let Some(current) = stack.pop() {
11883 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
11884 identifiers.push(identifier);
11885 continue;
11886 }
11887 let mut cursor = current.walk();
11888 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
11889 stack.extend(children.into_iter().rev());
11890 }
11891 let [keyword, namespace_name] = identifiers.as_slice() else {
11892 return None;
11893 };
11894 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
11895 {
11896 return None;
11897 }
11898 let mut next = function.next_named_sibling();
11899 let next = loop {
11900 let candidate = next?;
11901 next = candidate.next_named_sibling();
11902 if candidate.kind() != "comment" {
11903 break candidate;
11904 }
11905 };
11906 flattened_macro_sentinel_name(next, source)
11907 .is_some_and(|name| is_namespace_end_sentinel(&name))
11908 .then(|| namespace_name.clone())
11909}
11910
11911fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
11916 crate::declarations::is_recovered_exported_class_container(node, source)
11917 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
11918 node, source,
11919 )
11920 || recovered_macro_namespace_name(node, source).is_some()
11921}
11922
11923fn flattened_macro_error_namespace_components(
11924 declaration: Node<'_>,
11925 source: &str,
11926) -> Option<Vec<String>> {
11927 let parent = declaration
11928 .parent()
11929 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
11930 let mut cursor = parent.walk();
11931 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
11932 let declaration_index = siblings
11933 .iter()
11934 .position(|candidate| same_node(*candidate, declaration))?;
11935 let begin_index = (0..declaration_index).rev().find(|index| {
11936 flattened_macro_sentinel_name(siblings[*index], source)
11937 .is_some_and(|name| is_namespace_begin_sentinel(&name))
11938 })?;
11939
11940 let significant = siblings[begin_index + 1..declaration_index]
11941 .iter()
11942 .copied()
11943 .filter(|node| node.kind() != "comment")
11944 .collect::<Vec<_>>();
11945 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
11946 return None;
11947 };
11948 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
11949 return None;
11950 }
11951 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
11952 if significant[2..].iter().any(|node| {
11953 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
11954 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
11955 })
11956 }) {
11957 return None;
11958 }
11959
11960 let mut saw_namespace_close = false;
11961 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
11962 if sibling.kind() == "comment" {
11963 continue;
11964 }
11965 if !saw_namespace_close {
11966 if direct_unmatched_closing_brace(sibling) {
11967 saw_namespace_close = true;
11968 continue;
11969 }
11970 if flattened_macro_sentinel_name(sibling, source).is_some() {
11971 return None;
11972 }
11973 continue;
11974 }
11975 if !flattened_macro_sentinel_name(sibling, source)
11976 .is_some_and(|name| is_namespace_end_sentinel(&name))
11977 {
11978 return None;
11979 }
11980 let mut components = enclosing_namespace_components(declaration, source)?;
11981 components.push(namespace_name);
11982 return Some(components);
11983 }
11984 None
11985}
11986
11987fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
11988 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
11992 node.named_child(0)?
11993 } else {
11994 node
11995 };
11996 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
11997 node.child_by_field_name("type")
11998 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
11999 })?;
12000 (cpp_export_macro_token(&candidate)
12001 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12002 .then_some(candidate)
12003}
12004
12005fn is_namespace_begin_sentinel(name: &str) -> bool {
12008 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
12009}
12010
12011fn is_namespace_end_sentinel(name: &str) -> bool {
12012 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
12013}
12014
12015fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12016 if node.kind() != "ERROR" || node.named_child_count() != 1 {
12017 return None;
12018 }
12019 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12020 (!cpp_export_macro_token(&name)).then_some(name)
12021}
12022
12023fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12024 if !matches!(
12025 node.kind(),
12026 "identifier" | "namespace_identifier" | "type_identifier"
12027 ) {
12028 return None;
12029 }
12030 let name = normalize_cpp_whitespace(node_text(node, source));
12031 (!name.is_empty()).then_some(name)
12032}
12033
12034fn guard_requirement_sets_match(
12035 left: &[(usize, HashSet<PreprocessorGuard>)],
12036 right: &[(usize, HashSet<PreprocessorGuard>)],
12037) -> bool {
12038 left.len() == right.len()
12039 && left.iter().all(|(_, left_guards)| {
12040 right
12041 .iter()
12042 .any(|(_, right_guards)| left_guards == right_guards)
12043 })
12044 && right.iter().all(|(_, right_guards)| {
12045 left.iter()
12046 .any(|(_, left_guards)| right_guards == left_guards)
12047 })
12048}
12049
12050fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
12051 let Some(type_node) = declaration.child_by_field_name("type") else {
12052 return false;
12053 };
12054 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
12055 !type_name.is_empty()
12056 && type_name
12057 .chars()
12058 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
12059 && (0..declaration.named_child_count()).any(|index| {
12060 declaration
12061 .named_child(index)
12062 .is_some_and(|child| child.kind() == "ERROR")
12063 })
12064}
12065
12066fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12067 node.kind() == "ERROR"
12068 && (0..node.child_count())
12069 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12070}
12071
12072pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12073 let mut ancestor = node.parent();
12074 while let Some(parent) = ancestor {
12075 if is_preprocessor_conditional(parent)
12076 && !is_file_covering_include_guard(parent, source)
12077 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12078 {
12079 return false;
12080 }
12081 ancestor = parent.parent();
12082 }
12083 true
12084}
12085
12086fn is_split_cpp_language_linkage_wrapper(
12087 conditional: Node<'_>,
12088 descendant: Node<'_>,
12089 source: &str,
12090) -> bool {
12091 if conditional.child_by_field_name("alternative").is_some()
12092 || !matches!(
12093 simple_preprocessor_guard(conditional, source),
12094 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12095 )
12096 {
12097 return false;
12098 }
12099 let mut current = descendant.parent();
12100 let linkage = loop {
12101 let Some(node) = current else {
12102 return false;
12103 };
12104 if node == conditional {
12105 return false;
12106 }
12107 if node.kind() == "linkage_specification" {
12108 break node;
12109 }
12110 current = node.parent();
12111 };
12112 if linkage
12113 .child_by_field_name("value")
12114 .is_none_or(|value| node_text(value, source) != "\"C\"")
12115 {
12116 return false;
12117 }
12118 let Some(body) = linkage.child_by_field_name("body") else {
12119 return false;
12120 };
12121 let closes_opening_branch = (0..body.named_child_count())
12122 .filter_map(|index| body.named_child(index))
12123 .take_while(|child| child.end_byte() <= descendant.start_byte())
12124 .any(|child| {
12125 child.kind() == "preproc_call"
12126 && child
12127 .child_by_field_name("directive")
12128 .is_some_and(|directive| node_text(directive, source) == "#endif")
12129 });
12130 let reopens_for_closing_brace = (0..body.named_child_count())
12131 .filter_map(|index| body.named_child(index))
12132 .skip_while(|child| child.start_byte() < descendant.end_byte())
12133 .any(|child| {
12134 matches!(
12135 simple_preprocessor_guard(child, source),
12136 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12137 ) && (0..child.child_count()).any(|index| {
12138 child
12139 .child(index)
12140 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
12141 })
12142 });
12143 closes_opening_branch && reopens_for_closing_brace
12144}
12145
12146pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12150 node.child_by_field_name("arguments")
12151 .or_else(|| node.child_by_field_name("parameters"))
12152 .or_else(|| node.child_by_field_name("value"))
12153 .or_else(|| first_named_child_of_kind(node, "argument_list"))
12154 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12155}
12156
12157pub fn call_arity(node: Node<'_>) -> usize {
12158 call_arguments_node(node)
12159 .map(|args| argument_children(args).count())
12160 .unwrap_or(0)
12161}
12162
12163pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12164 let recovered_block_arguments = recovered_block_literal_arguments(node);
12165 (0..node.child_count())
12166 .filter_map(move |index| node.child(index))
12167 .filter(|child| child.is_named() && !child.is_extra())
12168 .flat_map(move |child| {
12169 if let Some((raw, left, right)) = recovered_block_arguments
12170 && child == raw
12171 {
12172 [Some(left), Some(right)]
12173 } else {
12174 [Some(child), None]
12175 }
12176 })
12177 .flatten()
12178}
12179
12180pub fn recovered_c_new_expression_arguments(
12189 node: Node<'_>,
12190 uses_c_semantics: bool,
12191) -> Option<[Node<'_>; 2]> {
12192 if !uses_c_semantics || node.kind() != "new_expression" {
12193 return None;
12194 }
12195 let parent = node.parent()?;
12196 if parent.kind() != "argument_list" {
12197 return None;
12198 }
12199 let keyword = node.child(0)?;
12200 let error = node.child(1)?;
12201 let trailing = node.child(2)?;
12202 if node.child(3).is_some()
12203 || keyword.kind() != "new"
12204 || keyword.is_named()
12205 || keyword.child_count() != 0
12206 || error.kind() != "ERROR"
12207 || !error.is_extra()
12208 || error.child_count() != 1
12209 || error.child(0).is_none_or(|comma| comma.kind() != ",")
12210 || node.child_by_field_name("type") != Some(trailing)
12211 || trailing.kind() != "type_identifier"
12212 {
12213 return None;
12214 }
12215 Some([keyword, trailing])
12216}
12217
12218pub fn recovered_c_new_expression_argument_at(
12221 mut node: Node<'_>,
12222 start_byte: usize,
12223 end_byte: usize,
12224 uses_c_semantics: bool,
12225) -> Option<Node<'_>> {
12226 if !uses_c_semantics {
12232 return None;
12233 }
12234 loop {
12235 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12236 return arguments.into_iter().find(|argument| {
12237 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12238 });
12239 }
12240 node = node.parent()?;
12241 }
12242}
12243
12244fn recovered_c_keyword_argument_count(
12245 file: &ProjectFile,
12246 call: Node<'_>,
12247 arguments: Node<'_>,
12248 source: &str,
12249) -> usize {
12250 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12255 return 0;
12256 }
12257 let mut ancestor = Some(call);
12258 let function = loop {
12259 let Some(current) = ancestor else {
12260 return 0;
12261 };
12262 if current.kind() == "function_definition" {
12263 break current;
12264 }
12265 ancestor = current.parent();
12266 };
12267 let Some(parameters) = function
12268 .child_by_field_name("declarator")
12269 .and_then(|declarator| declarator.child_by_field_name("parameters"))
12270 else {
12271 return 0;
12272 };
12273 let displaced_parameter_keywords = (0..parameters.child_count())
12274 .filter_map(|index| parameters.child(index))
12275 .filter(|error| error.kind() == "ERROR")
12276 .filter_map(|error| {
12277 let parameter = error.prev_named_sibling()?;
12278 if parameter.kind() != "parameter_declaration"
12279 || parameter.end_byte() != error.start_byte()
12280 || extract_variable_name(parameter, source).is_some()
12281 {
12282 return None;
12283 }
12284 let mut children = (0..error.child_count())
12285 .filter_map(|index| error.child(index))
12286 .filter(|child| !child.is_extra() && !child.is_missing());
12287 let keyword = children.next()?;
12288 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12289 .then_some(keyword)
12290 })
12291 .collect::<Vec<_>>();
12292 if displaced_parameter_keywords.is_empty() {
12293 return 0;
12294 }
12295
12296 (0..arguments.child_count())
12297 .filter_map(|index| arguments.child(index))
12298 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12299 .filter(|error| {
12300 let mut children = (0..error.child_count())
12301 .filter_map(|index| error.child(index))
12302 .filter(|child| !child.is_extra() && !child.is_missing());
12303 let Some(comma) = children.next() else {
12304 return false;
12305 };
12306 let Some(keyword) = children.next() else {
12307 return false;
12308 };
12309 children.next().is_none()
12310 && comma.kind() == ","
12311 && !keyword.is_named()
12312 && keyword.child_count() == 0
12313 && displaced_parameter_keywords
12314 .iter()
12315 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12316 })
12317 .count()
12318}
12319
12320fn recovered_block_literal_arguments<'tree>(
12321 arguments: Node<'tree>,
12322) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12323 if arguments.kind() != "argument_list" {
12324 return None;
12325 }
12326 let mut raw_arguments = (0..arguments.child_count())
12327 .filter_map(|index| arguments.child(index))
12328 .filter(|child| child.is_named() && !child.is_extra());
12329 let raw = raw_arguments.next()?;
12330 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12331 return None;
12332 }
12333
12334 let left = raw.child_by_field_name("left")?;
12335 if left.is_missing() || left.start_byte() == left.end_byte() {
12336 return None;
12337 }
12338 let right = raw.child_by_field_name("right")?;
12339 if right.kind() != "compound_literal_expression"
12340 || right.is_missing()
12341 || right
12342 .child_by_field_name("type")
12343 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12344 || right
12345 .child_by_field_name("value")
12346 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12347 {
12348 return None;
12349 }
12350 let has_intervening_error = (0..raw.child_count())
12351 .filter_map(|index| raw.child(index))
12352 .any(|child| {
12353 child.kind() == "ERROR"
12354 && !child.is_missing()
12355 && child.start_byte() >= left.end_byte()
12356 && child.end_byte() <= right.start_byte()
12357 });
12358 has_intervening_error.then_some((raw, left, right))
12359}
12360
12361pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12362 match node.kind() {
12363 "new_expression" => node
12364 .child_by_field_name("type")
12365 .or_else(|| node.named_child(0)),
12366 "compound_literal_expression" => node.child_by_field_name("type"),
12367 "call_expression" => node.child_by_field_name("function"),
12368 _ => None,
12369 }
12370}
12371
12372pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12378 if node.kind() != "cast_expression" {
12379 return None;
12380 }
12381 let descriptor = node.child_by_field_name("type")?;
12382 if descriptor.kind() == "type_descriptor" {
12383 descriptor.child_by_field_name("type")
12384 } else {
12385 Some(descriptor)
12386 }
12387}
12388
12389pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
12404 debug_assert_eq!(field.kind(), "field_expression");
12405 let operator = field.child_by_field_name("operator")?;
12406 let mut cursor = field.walk();
12407 let receiver = field
12408 .named_children(&mut cursor)
12409 .filter(|child| child.end_byte() <= operator.start_byte())
12410 .last()?;
12411 if receiver.kind() != "ERROR" {
12412 return Some(receiver);
12413 }
12414 (receiver.named_child_count() == 1)
12415 .then(|| receiver.named_child(0))
12416 .flatten()
12417}
12418
12419pub fn field_initializer_constructs_target(
12420 node: Node<'_>,
12421 ctx: &ScanCtx<'_>,
12422 owner: &CodeUnit,
12423) -> bool {
12424 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
12433 return qualified_base_initializer_constructs_target(node, ctx, owner);
12434 }
12435 let Some(name) = node
12436 .child_by_field_name("name")
12437 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
12438 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
12439 else {
12440 return false;
12441 };
12442 let field_name = node_text(name, ctx.source);
12443 ctx.visibility
12444 .visible_identifier_candidates(ctx.file, field_name)
12445 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
12446 .any(|unit| field_declares_type(unit, ctx, owner))
12447}
12448
12449fn qualified_base_initializer_constructs_target(
12450 node: Node<'_>,
12451 ctx: &ScanCtx<'_>,
12452 owner: &CodeUnit,
12453) -> bool {
12454 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
12455 return false;
12456 };
12457 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
12458 return false;
12459 };
12460 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
12461 return false;
12462 };
12463 let resolves_target = |components: &[String]| {
12464 matches!(
12465 ctx.visibility.resolve_type_components_lexically_for_target(
12466 &ctx.analyzer,
12467 ctx.file,
12468 components,
12469 is_globally_qualified_cpp_name(qualified),
12470 &lexical_scope,
12471 owner,
12472 ),
12473 LexicalTypeResolution::Resolved { unit, .. }
12474 if same_visible_symbol(&unit, owner)
12475 )
12476 };
12477 if resolves_target(&components) {
12478 return true;
12479 }
12480
12481 components
12487 .last()
12488 .is_some_and(|terminal| terminal == owner.identifier())
12489 && resolves_target(&components[..components.len() - 1])
12490}
12491
12492fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12493 unit.signature()
12494 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
12495 || ctx
12496 .analyzer
12497 .get_source(unit, false)
12498 .is_some_and(|declaration| {
12499 field_declaration_type_matches(&declaration, unit, ctx, owner)
12500 })
12501}
12502
12503pub fn field_declared_binding(
12504 analyzer: &CppGraphSource<'_>,
12505 visibility: &VisibilityIndex<'_>,
12506 visible_from: &ProjectFile,
12507 field: &CodeUnit,
12508) -> Option<CppScanBinding> {
12509 let fact = visibility.field_declared_type_fact(analyzer, field)?;
12510 let normalized = normalize_field_type_text(&fact.type_text);
12511 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
12512 analyzer,
12513 visible_from,
12514 field,
12515 &normalized,
12516 );
12517 let resolved = match (resolved, fact.template_arguments.as_deref()) {
12518 (Some(primary), Some(arguments)) => visibility
12519 .resolve_template_arguments(visible_from, primary, arguments)
12520 .ok(),
12521 (resolved, None) => resolved,
12522 (None, Some(_)) => None,
12523 }
12524 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
12525 Some(CppScanBinding::from_type_name(
12526 normalized,
12527 resolved,
12528 fact.indirection,
12529 ))
12530}
12531
12532fn anonymous_aggregate_field_owner(
12539 analyzer: &CppGraphSource<'_>,
12540 visibility: &VisibilityIndex<'_>,
12541 visible_from: &ProjectFile,
12542 field: &CodeUnit,
12543) -> Option<CodeUnit> {
12544 let owner = type_owner_of(analyzer, field)?;
12545 if !owner.is_class() {
12546 return None;
12547 }
12548 let declaration = analyzer.get_source(field, false)?;
12549 let mut parser = Parser::new();
12550 parser
12551 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12552 .ok()?;
12553 let tree = parser.parse(&declaration, None)?;
12554 let mut stack = vec![tree.root_node()];
12555 while let Some(node) = stack.pop() {
12556 if matches!(node.kind(), "declaration" | "field_declaration")
12557 && let Some(type_node) = node
12558 .child_by_field_name("type")
12559 .or_else(|| first_type_child(node))
12560 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12561 && type_node.child_by_field_name("name").is_none()
12562 && declared_name_indirection(node, type_node, field.identifier(), &declaration)
12563 .is_some()
12564 {
12565 let matches = visibility
12566 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12567 .into_iter()
12568 .filter(|child| child.is_class() && child.identifier() == field.identifier())
12569 .collect::<Vec<_>>();
12570 return match matches.as_slice() {
12571 [child] => Some((*child).clone()),
12572 _ => None,
12573 };
12574 }
12575 let mut cursor = node.walk();
12576 stack.extend(node.named_children(&mut cursor));
12577 }
12578 None
12579}
12580
12581pub fn anonymous_aggregate_owner(
12588 analyzer: &CppGraphSource<'_>,
12589 file: &ProjectFile,
12590 node: Node<'_>,
12591) -> Option<CodeUnit> {
12592 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12593 || node.child_by_field_name("name").is_some()
12594 {
12595 return None;
12596 }
12597 let mut candidates = analyzer
12598 .declarations(file)
12599 .into_iter()
12600 .filter(|candidate| {
12601 candidate.is_class()
12602 && analyzer.ranges(candidate).into_iter().any(|range| {
12603 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12604 })
12605 })
12606 .collect::<Vec<_>>();
12607 candidates.sort_by_key(|candidate| candidate.fq_name());
12608 candidates.dedup();
12609 match candidates.as_slice() {
12610 [candidate] => Some(candidate.clone()),
12611 _ => None,
12612 }
12613}
12614
12615fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12617 let Some(first) = candidates.first() else {
12618 return Err(TypeCandidateFailure::Unresolvable);
12619 };
12620 if candidates
12621 .iter()
12622 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12623 {
12624 Ok((*first).clone())
12625 } else {
12626 Err(TypeCandidateFailure::Ambiguous)
12627 }
12628}
12629
12630fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12631 logical_type_candidate(candidates).ok()
12632}
12633
12634fn unique_type_candidate_preserving_alias(
12635 analyzer: &CppGraphSource<'_>,
12636 file: &ProjectFile,
12637 candidates: &[&CodeUnit],
12638) -> Option<CodeUnit> {
12639 let first = *candidates.first()?;
12640 if declared_type_alias(analyzer, first) {
12641 return candidates
12642 .iter()
12643 .all(|candidate| {
12644 declared_type_alias(analyzer, candidate)
12645 && candidate.kind() == first.kind()
12646 && candidate.fq_name() == first.fq_name()
12647 && candidate.source() == first.source()
12648 })
12649 .then(|| first.clone());
12650 }
12651 if analyzer.reference_uses_c_semantics(file)
12652 && first.is_class()
12653 && indexed_c_tag_kind(analyzer, first).is_some()
12654 {
12655 let mut full_source = None;
12656 let mut tag_kind = None;
12657 for candidate in candidates.iter().copied() {
12658 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12659 if tag_kind
12660 .replace(candidate_tag_kind)
12661 .is_some_and(|existing| existing != candidate_tag_kind)
12662 {
12663 return None;
12664 }
12665 if cpp_class_declaration_strength(analyzer, candidate)
12666 == CppClassDeclarationStrength::Full
12667 && full_source
12668 .replace(candidate.source())
12669 .is_some_and(|existing| existing != candidate.source())
12670 {
12671 return None;
12672 }
12673 }
12674 }
12675 candidates
12676 .iter()
12677 .all(|candidate| {
12678 !declared_type_alias(analyzer, candidate)
12679 && candidate.kind() == first.kind()
12680 && candidate.fq_name() == first.fq_name()
12681 })
12682 .then(|| first.clone())
12683}
12684
12685fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
12686 is_type_alias(unit)
12687 || analyzer
12688 .type_alias_provider()
12689 .is_some_and(|provider| provider.is_type_alias(unit))
12690}
12691
12692pub fn field_declared_type_binding(
12693 analyzer: &CppGraphSource<'_>,
12694 visibility: &VisibilityIndex<'_>,
12695 visible_from: &ProjectFile,
12696 field: &CodeUnit,
12697) -> Option<(String, Option<CodeUnit>, i32)> {
12698 let fact = visibility.field_declared_type_fact(analyzer, field)?;
12699 let normalized = normalize_field_type_text(&fact.type_text);
12700 let primary = visibility.resolve_unique_canonical_type_for_declaration(
12701 analyzer,
12702 visible_from,
12703 field,
12704 &normalized,
12705 );
12706 let resolved = match (primary, fact.template_arguments.as_deref()) {
12707 (Some(primary), Some(arguments)) => visibility
12708 .resolve_template_arguments(visible_from, primary, arguments)
12709 .ok(),
12710 (resolved, None) => resolved,
12711 (None, Some(_)) => None,
12712 };
12713 Some((normalized, resolved, fact.indirection))
12714}
12715
12716fn decode_field_declared_type_fact(
12717 analyzer: &CppGraphSource<'_>,
12718 field: &CodeUnit,
12719) -> Option<DeclaredFieldTypeFact> {
12720 let Some(declaration) = analyzer.get_source(field, false) else {
12721 return decode_indexed_field_declared_type_fact(analyzer, field);
12722 };
12723 let mut parser = Parser::new();
12724 parser
12725 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12726 .ok()?;
12727 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
12731 let contextual_tree = parser.parse(&contextual_declaration, None)?;
12732 let mut stack = vec![contextual_tree.root_node()];
12733 while let Some(node) = stack.pop() {
12734 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
12735 && node_text(recovered.name, &contextual_declaration) == field.identifier()
12736 {
12737 return Some(DeclaredFieldTypeFact {
12738 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
12739 indirection: recovered.pointer_depth(),
12740 template_arguments: None,
12741 });
12742 }
12743 if let Some(recovered) =
12744 recovered_function_like_field_declarator(node, &contextual_declaration)
12745 && node_text(recovered.name, &contextual_declaration) == field.identifier()
12746 {
12747 let type_node = node
12748 .child_by_field_name("type")
12749 .or_else(|| first_type_child(node))?;
12750 return Some(DeclaredFieldTypeFact {
12751 type_text: node_text(type_node, &contextual_declaration).to_string(),
12752 indirection: recovered.pointer_depth(),
12753 template_arguments: cpp_template_reference_arguments(
12754 type_node,
12755 &contextual_declaration,
12756 ),
12757 });
12758 }
12759 if let Some(fact) =
12760 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
12761 {
12762 return Some(fact);
12763 }
12764 let mut cursor = node.walk();
12765 stack.extend(node.named_children(&mut cursor));
12766 }
12767 let tree = parser.parse(&declaration, None)?;
12768 let mut stack = vec![tree.root_node()];
12769 while let Some(node) = stack.pop() {
12770 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
12771 {
12772 return Some(fact);
12773 }
12774 let mut cursor = node.walk();
12775 stack.extend(node.named_children(&mut cursor));
12776 }
12777 None
12778}
12779
12780fn decode_indexed_field_declared_type_fact(
12785 analyzer: &CppGraphSource<'_>,
12786 field: &CodeUnit,
12787) -> Option<DeclaredFieldTypeFact> {
12788 let cpp = analyzer.cpp?;
12789 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
12790 let source = prepared.source();
12791 let root = prepared.tree().root_node();
12792 for range in analyzer.ranges(field) {
12793 let end = range.start_byte.saturating_add(1).min(source.len());
12794 let mut current = root.descendant_for_byte_range(range.start_byte, end);
12795 while let Some(node) = current {
12796 if matches!(node.kind(), "declaration" | "field_declaration")
12797 && let Some(fact) =
12798 decode_declared_field_type_node(node, field.identifier(), source)
12799 {
12800 return Some(fact);
12801 }
12802 current = node.parent();
12803 }
12804 }
12805 None
12806}
12807
12808fn decode_declared_field_type_node(
12809 node: Node<'_>,
12810 field_name: &str,
12811 source: &str,
12812) -> Option<DeclaredFieldTypeFact> {
12813 if !matches!(node.kind(), "declaration" | "field_declaration") {
12814 return None;
12815 }
12816 let type_node = node
12817 .child_by_field_name("type")
12818 .or_else(|| first_type_child(node))?;
12819 let indirection = declared_name_indirection(node, type_node, field_name, source)?;
12820 let declared_type = if matches!(
12821 type_node.kind(),
12822 "class_specifier" | "struct_specifier" | "union_specifier"
12823 ) {
12824 type_node.child_by_field_name("name")
12825 } else {
12826 Some(type_node)
12827 };
12828 Some(DeclaredFieldTypeFact {
12829 type_text: declared_type.map_or_else(
12830 || field_name.to_string(),
12831 |declared_type| node_text(declared_type, source).to_string(),
12832 ),
12833 indirection,
12834 template_arguments: declared_type
12835 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
12836 })
12837}
12838
12839pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
12853 let mut parser = Parser::new();
12854 parser
12855 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12856 .ok()?;
12857 let tree = parser.parse(declaration, None)?;
12858 let mut stack = vec![tree.root_node()];
12859 while let Some(node) = stack.pop() {
12860 let type_node = match node.kind() {
12861 "type_definition" => {
12862 let mut cursor = node.walk();
12863 if node
12864 .children_by_field_name("declarator", &mut cursor)
12865 .any(declarator_names_function_type)
12866 {
12867 return None;
12868 }
12869 node.child_by_field_name("type")?
12870 }
12871 "alias_declaration" => {
12872 let type_node = node.child_by_field_name("type")?;
12873 if type_node
12874 .child_by_field_name("declarator")
12875 .is_some_and(declarator_names_function_type)
12876 {
12877 return None;
12878 }
12879 type_node
12880 }
12881 _ => {
12882 let mut cursor = node.walk();
12883 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
12884 stack.extend(children.into_iter().rev());
12885 continue;
12886 }
12887 };
12888 return Some(node_text(type_node, declaration).to_string());
12889 }
12890 None
12891}
12892
12893fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
12902 let mut parser = Parser::new();
12903 if parser
12904 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12905 .is_err()
12906 {
12907 return true;
12908 }
12909 let Some(tree) = parser.parse(declaration, None) else {
12910 return true;
12911 };
12912 let mut stack = vec![tree.root_node()];
12913 while let Some(node) = stack.pop() {
12914 let declarators = match node.kind() {
12915 "type_definition" => {
12916 let mut cursor = node.walk();
12917 node.children_by_field_name("declarator", &mut cursor)
12918 .collect::<Vec<_>>()
12919 }
12920 "alias_declaration" => node
12921 .child_by_field_name("type")
12922 .and_then(|type_node| type_node.child_by_field_name("declarator"))
12923 .into_iter()
12924 .collect::<Vec<_>>(),
12925 _ => {
12926 let mut cursor = node.walk();
12927 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
12928 stack.extend(children.into_iter().rev());
12929 continue;
12930 }
12931 };
12932 return declarators.into_iter().any(cpp_declarator_adds_indirection);
12933 }
12934 true
12935}
12936
12937fn declarator_names_function_type(declarator: Node<'_>) -> bool {
12943 let mut current = Some(declarator);
12944 while let Some(node) = current {
12945 match node.kind() {
12946 "function_declarator" | "abstract_function_declarator" => return true,
12947 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
12948 current = node.named_child(0);
12949 }
12950 _ => current = node.child_by_field_name("declarator"),
12951 }
12952 }
12953 false
12954}
12955
12956pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
12960 let mut parser = Parser::new();
12961 if parser
12962 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12963 .is_err()
12964 {
12965 return false;
12966 }
12967 let Some(tree) = parser.parse(declaration, None) else {
12968 return false;
12969 };
12970 let mut stack = vec![tree.root_node()];
12971 while let Some(node) = stack.pop() {
12972 if matches!(node.kind(), "declaration" | "field_declaration") {
12973 let mut cursor = node.walk();
12974 if node
12975 .children_by_field_name("declarator", &mut cursor)
12976 .any(|declarator| {
12977 declarator_name_node(declarator).is_some_and(|name| {
12978 node_text(name, declaration) == field_name
12979 && declarator_names_function_type(declarator)
12980 })
12981 })
12982 {
12983 return true;
12984 }
12985 }
12986 let mut cursor = node.walk();
12987 stack.extend(node.named_children(&mut cursor));
12988 }
12989 false
12990}
12991
12992pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
12996 let mut parser = Parser::new();
12997 if parser
12998 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12999 .is_err()
13000 {
13001 return false;
13002 }
13003 let Some(tree) = parser.parse(declaration, None) else {
13004 return false;
13005 };
13006 let mut stack = vec![tree.root_node()];
13007 while let Some(node) = stack.pop() {
13008 match node.kind() {
13009 "type_definition" => {
13010 let mut cursor = node.walk();
13011 if node
13012 .children_by_field_name("declarator", &mut cursor)
13013 .any(|declarator| {
13014 extract_typedef_declarator_name(declarator, declaration)
13015 .is_some_and(|name| name == alias_name)
13016 && declarator_names_function_type(declarator)
13017 })
13018 {
13019 return true;
13020 }
13021 }
13022 "alias_declaration" => {
13023 let names_alias = node
13024 .child_by_field_name("name")
13025 .is_some_and(|name| node_text(name, declaration) == alias_name);
13026 if names_alias
13027 && node
13028 .child_by_field_name("type")
13029 .and_then(|type_node| type_node.child_by_field_name("declarator"))
13030 .is_some_and(declarator_names_function_type)
13031 {
13032 return true;
13033 }
13034 }
13035 _ => {}
13036 }
13037 let mut cursor = node.walk();
13038 stack.extend(node.named_children(&mut cursor));
13039 }
13040 false
13041}
13042
13043fn decode_structured_alias_target(
13044 analyzer: &CppGraphSource<'_>,
13045 unit: &CodeUnit,
13046) -> Option<StructuredAliasTarget> {
13047 analyzer
13048 .get_source(unit, false)
13049 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
13050 .or_else(|| {
13051 let signature = unit.signature()?;
13052 decode_structured_alias_target_source(unit, signature, false)
13053 })
13054}
13055
13056fn decode_structured_alias_target_source(
13057 unit: &CodeUnit,
13058 declaration: &str,
13059 require_top_level: bool,
13060) -> Option<StructuredAliasTarget> {
13061 let mut parser = Parser::new();
13062 parser
13063 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13064 .ok()?;
13065 let tree = parser.parse(declaration, None)?;
13066 let mut stack = vec![tree.root_node()];
13067 while let Some(node) = stack.pop() {
13068 let type_node = match node.kind() {
13069 "type_definition" => {
13070 if require_top_level
13071 && node
13072 .parent()
13073 .is_none_or(|parent| parent.kind() != "translation_unit")
13074 {
13075 let mut cursor = node.walk();
13076 stack.extend(node.named_children(&mut cursor));
13077 continue;
13078 }
13079 let mut declarator_cursor = node.walk();
13080 let declarator = node
13081 .children_by_field_name("declarator", &mut declarator_cursor)
13082 .find(|declarator| {
13083 extract_typedef_declarator_name(*declarator, declaration)
13084 .is_some_and(|name| name == unit.identifier())
13085 })?;
13086 if declarator_names_function_type(declarator) {
13087 return None;
13088 }
13089 node.child_by_field_name("type")?
13090 }
13091 "alias_declaration" => {
13092 if require_top_level
13093 && node
13094 .parent()
13095 .is_none_or(|parent| parent.kind() != "translation_unit")
13096 {
13097 let mut cursor = node.walk();
13098 stack.extend(node.named_children(&mut cursor));
13099 continue;
13100 }
13101 let name = node.child_by_field_name("name")?;
13102 if node_text(name, declaration) != unit.identifier() {
13103 return None;
13104 }
13105 let type_node = node.child_by_field_name("type")?;
13106 if type_node
13107 .child_by_field_name("declarator")
13108 .is_some_and(declarator_names_function_type)
13109 {
13110 return None;
13111 }
13112 type_node
13113 }
13114 _ => {
13115 let mut cursor = node.walk();
13116 stack.extend(node.named_children(&mut cursor));
13117 continue;
13118 }
13119 };
13120 return structured_alias_type_target(type_node, declaration);
13121 }
13122 None
13123}
13124
13125fn structured_alias_type_target(
13126 mut type_node: Node<'_>,
13127 source: &str,
13128) -> Option<StructuredAliasTarget> {
13129 while type_node.kind() == "type_descriptor" {
13130 type_node = type_node.child_by_field_name("type")?;
13131 }
13132 if type_node.kind() == "primitive_type" {
13133 return Some(StructuredAliasTarget::Builtin);
13134 }
13135 if matches!(
13136 type_node.kind(),
13137 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13138 ) {
13139 type_node = type_node.child_by_field_name("name")?;
13140 }
13141 let global = type_node.child_by_field_name("scope").is_none()
13142 && type_node.child(0).is_some_and(|child| child.kind() == "::");
13143 let mut components = Vec::new();
13144 append_structured_type_components(type_node, source, &mut components)?;
13145 let arguments = cpp_template_reference_arguments(type_node, source);
13146 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13147 components,
13148 global,
13149 arguments,
13150 })
13151}
13152
13153fn append_structured_type_components(
13154 node: Node<'_>,
13155 source: &str,
13156 out: &mut Vec<String>,
13157) -> Option<()> {
13158 match node.kind() {
13159 "identifier" | "namespace_identifier" | "type_identifier" => {
13160 out.push(node_text(node, source).to_string());
13161 Some(())
13162 }
13163 "template_type" => {
13164 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13165 }
13166 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13167 if let Some(scope) = node.child_by_field_name("scope") {
13168 append_structured_type_components(scope, source, out)?;
13169 }
13170 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13171 }
13172 _ => None,
13173 }
13174}
13175
13176pub(crate) fn declared_name_indirection(
13177 declaration: Node<'_>,
13178 type_node: Node<'_>,
13179 field_name: &str,
13180 source: &str,
13181) -> Option<i32> {
13182 let mut stack = Vec::new();
13183 let mut cursor = declaration.walk();
13184 stack.extend(
13185 declaration
13186 .named_children(&mut cursor)
13187 .filter(|child| !same_node(*child, type_node)),
13188 );
13189 while let Some(node) = stack.pop() {
13190 if matches!(node.kind(), "identifier" | "field_identifier")
13191 && node_text(node, source) == field_name
13192 {
13193 let mut indirection = 0;
13194 let mut current = node.parent();
13195 while let Some(parent) = current {
13196 if same_node(parent, declaration) {
13197 return Some(indirection);
13198 }
13199 if parent.kind() == "pointer_declarator" {
13200 indirection += 1;
13201 }
13202 current = parent.parent();
13203 }
13204 return None;
13205 }
13206 let mut cursor = node.walk();
13207 stack.extend(node.named_children(&mut cursor));
13208 }
13209 None
13210}
13211
13212fn field_declaration_type_matches(
13213 declaration: &str,
13214 unit: &CodeUnit,
13215 ctx: &ScanCtx<'_>,
13216 owner: &CodeUnit,
13217) -> bool {
13218 ctx.visibility
13219 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
13220 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
13221 let normalized = normalize_field_type_text(type_text);
13222 ctx.visibility
13223 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
13224 || ctx.visibility.resolves_to_type(
13225 &ctx.analyzer,
13226 ctx.file,
13227 normalized.as_str(),
13228 owner,
13229 )
13230 })
13231}
13232
13233fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
13234 let declaration = declaration
13235 .split(['=', ';'])
13236 .next()
13237 .unwrap_or(declaration)
13238 .trim();
13239 let index = declaration.rfind(field_name)?;
13240 let before = &declaration[..index];
13241 let after = &declaration[index + field_name.len()..];
13242 if before.chars().next_back().is_some_and(is_identifier_char)
13243 || after.chars().next().is_some_and(is_identifier_char)
13244 {
13245 return None;
13246 }
13247 Some(before.trim())
13248}
13249
13250fn normalize_field_type_text(type_text: &str) -> String {
13251 const FIELD_SPECIFIERS: [&str; 8] = [
13252 "extern ",
13253 "static ",
13254 "mutable ",
13255 "constexpr ",
13256 "constinit ",
13257 "inline ",
13258 "volatile ",
13259 "const ",
13260 ];
13261
13262 let mut normalized = normalize_type_text(type_text);
13263 loop {
13264 let Some(stripped) = FIELD_SPECIFIERS
13265 .iter()
13266 .find_map(|specifier| normalized.strip_prefix(specifier))
13267 else {
13268 return normalized;
13269 };
13270 normalized = normalize_type_text(stripped);
13271 }
13272}
13273
13274fn is_identifier_char(ch: char) -> bool {
13275 ch == '_' || ch.is_ascii_alphanumeric()
13276}
13277
13278pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13279 let Some(type_node) = node.child_by_field_name("type") else {
13280 return false;
13281 };
13282 ctx.visibility.resolves_to_type(
13283 &ctx.analyzer,
13284 ctx.file,
13285 node_text(type_node, ctx.source),
13286 owner,
13287 )
13288}
13289
13290pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13291 !ctx.analyzer
13292 .declarations(ctx.file)
13293 .into_iter()
13294 .filter(|unit| unit.is_function())
13295 .any(|unit| {
13296 ctx.analyzer.ranges(&unit).iter().any(|range| {
13297 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13298 })
13299 })
13300}
13301
13302pub enum DeclarationConstructorInitializer<'tree> {
13304 Arguments(Node<'tree>),
13307 Expression(Node<'tree>),
13310 Empty,
13312}
13313
13314pub fn declaration_constructor_initializer(
13315 node: Node<'_>,
13316) -> DeclarationConstructorInitializer<'_> {
13317 let mut cursor = node.walk();
13318 for child in node.named_children(&mut cursor) {
13319 if child.kind() == "init_declarator" {
13320 let Some(value) = child
13321 .child_by_field_name("value")
13322 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13323 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13324 else {
13325 return DeclarationConstructorInitializer::Empty;
13326 };
13327 return match value.kind() {
13328 "argument_list" | "initializer_list" => {
13329 DeclarationConstructorInitializer::Arguments(value)
13330 }
13331 "compound_literal_expression" => call_arguments_node(value)
13332 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13333 DeclarationConstructorInitializer::Arguments(arguments)
13334 }),
13335 _ => DeclarationConstructorInitializer::Expression(value),
13336 };
13337 }
13338 if let Some(declarator) = declaration_declarator(node, child) {
13339 return declarator_parameters(declarator)
13340 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13341 DeclarationConstructorInitializer::Arguments(parameters)
13342 });
13343 }
13344 }
13345 DeclarationConstructorInitializer::Empty
13346}
13347
13348pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13349 match declaration_constructor_initializer(node) {
13350 DeclarationConstructorInitializer::Arguments(arguments) => {
13351 argument_children(arguments).count()
13352 }
13353 DeclarationConstructorInitializer::Expression(_) => 1,
13354 DeclarationConstructorInitializer::Empty => 0,
13355 }
13356}
13357
13358fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13362 let mut current = node;
13363 loop {
13364 if let Some(parameters) = current.child_by_field_name("parameters") {
13365 return Some(parameters);
13366 }
13367 current = current.child_by_field_name("declarator")?;
13368 }
13369}
13370
13371pub(super) fn first_named_child_of_kind<'tree>(
13372 node: Node<'tree>,
13373 kind: &str,
13374) -> Option<Node<'tree>> {
13375 let mut cursor = node.walk();
13376 node.named_children(&mut cursor)
13377 .find(|child| child.kind() == kind)
13378}
13379
13380fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13381 let mut stack = vec![root];
13382 while let Some(node) = stack.pop() {
13383 if node.kind() == kind {
13384 return Some(node);
13385 }
13386 push_named_children_reversed(node, &mut stack);
13387 }
13388 None
13389}
13390
13391fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13392 if node.kind() == "identifier" {
13393 return true;
13394 }
13395 if node.kind() == "parenthesized_expression" {
13396 return false;
13397 }
13398 if node.kind() == "call_expression" {
13399 return node
13400 .child_by_field_name("function")
13401 .is_some_and(|function| function.kind() == "identifier");
13402 }
13403 let mut stack = vec![node];
13404 while let Some(descendant) = stack.pop() {
13405 if descendant != node && descendant.kind() == "parenthesized_expression" {
13406 continue;
13407 }
13408 if descendant.kind() == "identifier" {
13409 return true;
13410 }
13411 if descendant.kind() == "call_expression" {
13412 if descendant
13413 .child_by_field_name("function")
13414 .is_some_and(|function| function.kind() == "identifier")
13415 {
13416 return true;
13417 }
13418 continue;
13419 }
13420 push_named_children_reversed(descendant, &mut stack);
13421 }
13422 false
13423}
13424
13425fn macro_expansion_shape_is_safe(
13426 node: Node<'_>,
13427 source: &str,
13428 parameters: &[String],
13429 environment: &MacroEnvironment,
13430) -> bool {
13431 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13432 return true;
13433 }
13434 if node.kind() == "call_expression" {
13435 let Some(function) = node.child_by_field_name("function") else {
13436 return true;
13437 };
13438 if function.kind() != "identifier" {
13439 return true;
13440 }
13441 let function_name = node_text(function, source);
13442 if parameters
13443 .iter()
13444 .any(|parameter| parameter == function_name)
13445 {
13446 return false;
13447 }
13448 if !environment.may_bind(function_name) {
13449 return true;
13450 }
13451 let Some(arguments) = node.child_by_field_name("arguments") else {
13452 return false;
13453 };
13454 return argument_children(arguments).all(|argument| {
13455 if argument.kind() == "identifier"
13456 && parameters
13457 .iter()
13458 .any(|parameter| parameter == node_text(argument, source))
13459 {
13460 return false;
13461 }
13462 macro_expansion_shape_is_safe(argument, source, parameters, environment)
13463 });
13464 }
13465 let mut stack = vec![node];
13466 while let Some(descendant) = stack.pop() {
13467 if descendant != node {
13468 if descendant.kind() == "parenthesized_expression" {
13469 continue;
13470 }
13471 if descendant.kind() == "call_expression" {
13472 let expands = descendant
13473 .child_by_field_name("function")
13474 .filter(|function| function.kind() == "identifier")
13475 .is_some_and(|function| environment.may_bind(node_text(function, source)));
13476 if expands {
13477 return false;
13478 }
13479 continue;
13480 }
13481 }
13482 if descendant.kind() == "identifier" {
13483 let identifier = node_text(descendant, source);
13484 if parameters.iter().any(|parameter| parameter == identifier)
13485 || environment.may_bind(identifier)
13486 {
13487 return false;
13488 }
13489 }
13490 push_named_children_reversed(descendant, &mut stack);
13491 }
13492 true
13493}
13494
13495fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13496 let text = node_text(path, source);
13497 match path.kind() {
13498 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13499 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13500 _ => None,
13501 }
13502}
13503
13504fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13505 let source = prepared.source();
13506 let mut facts = Vec::new();
13507 let mut nodes = vec![prepared.tree().root_node()];
13508 while let Some(node) = nodes.pop() {
13509 if node.kind() == "preproc_include" {
13510 let Some(path) = node
13511 .child_by_field_name("path")
13512 .and_then(|path| structured_include_path(path, source))
13513 .map(str::to_owned)
13514 else {
13515 continue;
13516 };
13517 facts.push(StructuredIncludeFact {
13518 start_byte: node.start_byte(),
13519 end_byte: node.end_byte(),
13520 path,
13521 });
13522 continue;
13523 }
13524 push_named_children_reversed(node, &mut nodes);
13525 }
13526 Arc::from(facts.into_boxed_slice())
13527}
13528
13529fn has_unresolved_include_visible_before_in_prepared(
13530 file: &ProjectFile,
13531 prepared: &PreparedSyntaxTree,
13532 include_targets: &IncludeTargetIndex,
13533 facts: &[StructuredIncludeFact],
13534 before_byte: usize,
13535) -> bool {
13536 let guards = OnceCell::new();
13537 let reference = CallableReferenceContext {
13538 file,
13539 position: Some(CallableReferencePosition {
13540 prepared,
13541 byte: before_byte,
13542 guards: &guards,
13543 }),
13544 };
13545 let root = prepared.tree().root_node();
13546 facts
13547 .iter()
13548 .filter(|fact| fact.end_byte <= before_byte)
13549 .any(|fact| {
13550 let node = root
13551 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13552 .expect("structured include fact range must be in prepared tree");
13553 assert_eq!(
13554 node.kind(),
13555 "preproc_include",
13556 "structured include fact range must identify its include node"
13557 );
13558 callable_preprocessor_context_is_visible_for_reference(
13559 node,
13560 prepared.source(),
13561 &reference,
13562 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13563 })
13564}
13565
13566fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13567 let descendant = node;
13568 while let Some(parent) = node.parent() {
13569 if is_preprocessor_conditional(parent)
13570 && !is_file_covering_include_guard(parent, source)
13571 && preprocessor_conditional_contains_descendant(parent, descendant)
13572 {
13573 return true;
13574 }
13575 node = parent;
13576 }
13577 false
13578}
13579
13580fn owning_preprocessor_conditionals(
13592 root: Node<'_>,
13593 event: Node<'_>,
13594 source: &str,
13595) -> OwningPreprocessorConditionals {
13596 if !has_preprocessor_conditional_ancestor(event, source) {
13597 return OwningPreprocessorConditionals::default();
13598 }
13599 let start = event.start_byte();
13600 let descendant = root
13601 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13602 .expect("a byte inside the parsed tree names a descendant");
13603 let mut owners = Vec::new();
13604 let mut current = descendant.parent();
13605 while let Some(conditional) = current {
13606 if is_preprocessor_conditional(conditional)
13607 && !is_file_covering_include_guard(conditional, source)
13608 && preprocessor_conditional_contains_descendant(conditional, descendant)
13609 {
13610 owners.push(conditional.start_byte());
13611 }
13612 current = conditional.parent();
13613 }
13614 owners.into_boxed_slice()
13615}
13616
13617fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13618 matches!(
13619 node.kind(),
13620 "preproc_if"
13621 | "preproc_ifdef"
13622 | "preproc_ifndef"
13623 | "preproc_elif"
13624 | "preproc_elifdef"
13625 | "preproc_else"
13626 )
13627}
13628
13629fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13630 node.parent()
13631 .filter(|parent| parent.kind() == "translation_unit")
13632 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13633 && is_canonical_include_guard(node, source)
13634}
13635
13636fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13637 if node.kind() != "preproc_ifdef"
13638 || node
13639 .child(0)
13640 .is_none_or(|directive| directive.kind() != "#ifndef")
13641 || node.child_by_field_name("alternative").is_some()
13642 {
13643 return false;
13644 }
13645 let Some(guard_name) = node.child_by_field_name("name") else {
13646 return false;
13647 };
13648 let mut cursor = node.walk();
13649 node.named_children(&mut cursor)
13650 .find(|child| *child != guard_name && child.kind() != "comment")
13651 .filter(|child| child.kind() == "preproc_def")
13652 .and_then(|definition| definition.child_by_field_name("name"))
13653 .is_some_and(|defined_name| {
13654 node_text(defined_name, source) == node_text(guard_name, source)
13655 })
13656}
13657
13658fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
13659 let mut guard = None;
13660 for child in named_children_iter(root) {
13661 if child.kind() == "comment" || is_pragma_once(child, source) {
13662 continue;
13663 }
13664 if guard.is_none() && is_canonical_include_guard(child, source) {
13665 guard = Some(child);
13666 } else {
13667 return None;
13668 }
13669 }
13670 guard
13671 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
13672 .map(|name| node_text(name, source).to_string())
13673}
13674
13675fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
13676 if (0..root.named_child_count())
13677 .filter_map(|index| root.named_child(index))
13678 .any(|child| is_pragma_once(child, source))
13679 {
13680 return MacroIncludeProtection::PragmaOnce;
13681 }
13682 top_level_canonical_include_guard_name(root, source)
13683 .map(MacroIncludeProtection::MacroGuard)
13684 .unwrap_or(MacroIncludeProtection::None)
13685}
13686
13687fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
13688 node.kind() == "preproc_call"
13689 && node
13690 .child_by_field_name("directive")
13691 .is_some_and(|directive| node_text(directive, source) == "#pragma")
13692 && node
13693 .child_by_field_name("argument")
13694 .is_some_and(|argument| node_text(argument, source).trim() == "once")
13695}
13696
13697fn parse_preproc_identifier(argument: &str) -> Option<String> {
13698 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
13699 let mut parser = Parser::new();
13700 parser
13701 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13702 .ok()?;
13703 let tree = parser.parse(&sentinel, None)?;
13704 if tree.root_node().has_error() {
13705 return None;
13706 }
13707 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
13708 let identifier = statement.named_child(0)?;
13709 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
13710 .then(|| node_text(identifier, &sentinel).to_string())
13711}
13712
13713pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
13714 match node.kind() {
13715 "identifier" | "field_identifier" => {
13716 let name = node_text(node, source).trim();
13717 (!name.is_empty()).then(|| name.to_string())
13718 }
13719 "abstract_array_declarator"
13720 | "abstract_function_declarator"
13721 | "abstract_parenthesized_declarator"
13722 | "abstract_pointer_declarator"
13723 | "abstract_reference_declarator" => None,
13724 "function_declarator" => node
13725 .child_by_field_name("declarator")
13726 .or_else(|| node.child_by_field_name("name"))
13727 .and_then(|child| extract_variable_name(child, source)),
13728 _ => node
13729 .child_by_field_name("declarator")
13730 .or_else(|| node.child_by_field_name("name"))
13731 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
13732 .and_then(|child| extract_variable_name(child, source)),
13733 }
13734}
13735
13736pub fn is_c_source_file(file: &ProjectFile) -> bool {
13747 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
13748}
13749
13750pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
13757 if !is_c_source_file(file) || node.kind() != "identifier" {
13758 return false;
13759 }
13760 let mut operand = node;
13761 while let Some(parent) = operand.parent().filter(|parent| {
13762 parent.kind() == "parenthesized_expression"
13763 && parent.named_child_count() == 1
13764 && parent.named_child(0) == Some(operand)
13765 }) {
13766 operand = parent;
13767 }
13768 operand.parent().is_some_and(|parent| {
13769 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
13770 })
13771}
13772
13773pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
13782 if node.kind() != "field_identifier" {
13783 return None;
13784 }
13785 let expression = node.parent().filter(|parent| {
13786 parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
13787 })?;
13788 if expression.has_error() {
13789 return None;
13790 }
13791 let closing = expression.child(expression.child_count().saturating_sub(1))?;
13792 if closing.kind() != ")" || closing.is_missing() {
13793 return None;
13794 }
13795 let type_descriptor = expression.child_by_field_name("type")?;
13796 if type_descriptor.kind() != "type_descriptor"
13797 || type_descriptor.is_missing()
13798 || type_descriptor.has_error()
13799 {
13800 return None;
13801 }
13802 let type_specifier = type_descriptor.child_by_field_name("type")?;
13803 if type_specifier.is_missing() || type_specifier.has_error() {
13804 return None;
13805 }
13806 let type_reference = match type_specifier.kind() {
13807 "class_specifier" | "struct_specifier" | "union_specifier" => {
13808 type_specifier.child_by_field_name("name")?
13809 }
13810 _ => type_specifier,
13811 };
13812 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
13813}
13814
13815pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
13820 node.kind() == "field_identifier"
13821 && node.parent().is_some_and(|parent| {
13822 parent.kind() == "offsetof_expression"
13823 && parent.child_by_field_name("member") == Some(node)
13824 })
13825}
13826
13827pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
13843 if node.kind() != "type_identifier" {
13844 return false;
13845 }
13846 let Some(descriptor) = node
13847 .parent()
13848 .filter(|parent| parent.kind() == "type_descriptor")
13849 else {
13850 return false;
13851 };
13852 if descriptor.child_by_field_name("type") != Some(node) {
13853 return false;
13854 }
13855 let Some(arguments) = descriptor
13856 .parent()
13857 .filter(|parent| parent.kind() == "template_argument_list")
13858 else {
13859 return false;
13860 };
13861 arguments.parent().is_some_and(|owner| {
13862 matches!(
13863 owner.kind(),
13864 "template_type" | "template_function" | "template_method"
13865 ) && owner.child_by_field_name("arguments") == Some(arguments)
13866 })
13867}
13868
13869pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
13882 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
13883}
13884
13885pub fn is_declarator_node(node: Node<'_>) -> bool {
13886 matches!(
13887 node.kind(),
13888 "identifier"
13889 | "field_identifier"
13890 | "qualified_identifier"
13891 | "scoped_identifier"
13892 | "pointer_declarator"
13893 | "reference_declarator"
13894 | "array_declarator"
13895 | "parenthesized_declarator"
13896 | "function_declarator"
13897 )
13898}
13899
13900pub fn declaration_declarator<'tree>(
13909 declaration: Node<'tree>,
13910 child: Node<'tree>,
13911) -> Option<Node<'tree>> {
13912 if !matches!(
13913 declaration.kind(),
13914 "declaration"
13915 | "field_declaration"
13916 | "parameter_declaration"
13917 | "optional_parameter_declaration"
13918 | "function_definition"
13919 | "type_definition"
13920 | "alias_declaration"
13921 | "template_instantiation"
13922 ) {
13923 return None;
13924 }
13925 if declaration
13926 .child_by_field_name("type")
13927 .is_some_and(|type_node| same_node(type_node, child))
13928 {
13929 return None;
13930 }
13931 if child.kind() == "init_declarator" {
13932 return child.child_by_field_name("declarator");
13933 }
13934 let field = field_name_in_parent(declaration, child);
13935 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
13936 || (declaration.kind() == "type_definition"
13937 && child.kind() == "type_identifier"
13938 && matches!(field, Some("declarator") | None))
13939 {
13940 Some(child)
13941 } else {
13942 None
13943 }
13944}
13945
13946#[derive(Clone, Debug, PartialEq, Eq)]
13949pub struct RecoveredNamespaceRegion {
13950 pub start: usize,
13952 pub end: usize,
13954 pub components: Vec<String>,
13957}
13958
13959#[derive(Clone, Debug, Default)]
13980pub struct OrphanedNamespaceScopeIndex {
13981 regions: Vec<RecoveredNamespaceRegion>,
13982 brace_closes: HashMap<usize, Range>,
13983}
13984
13985impl OrphanedNamespaceScopeIndex {
13986 pub fn build(root: Node<'_>, source: &str) -> Self {
13987 if !root.has_error() {
13988 return Self::default();
13989 }
13990 struct Frame<'tree> {
13991 node: Node<'tree>,
13992 children: Vec<Node<'tree>>,
13993 next: usize,
13994 parsed_scope: Vec<String>,
13995 run: Option<RecoveredNamespaceRegion>,
13996 }
13997 fn frame<'tree>(
13998 node: Node<'tree>,
13999 mut parsed_scope: Vec<String>,
14000 source: &str,
14001 ) -> Frame<'tree> {
14002 if node.kind() == "namespace_definition"
14003 && let Some(name) = node.child_by_field_name("name")
14004 {
14005 let mut components = Vec::new();
14006 if append_cpp_name_components(name, source, &mut components).is_some() {
14007 parsed_scope.extend(components);
14008 }
14009 }
14010 let mut cursor = node.walk();
14011 Frame {
14012 node,
14013 children: node.children(&mut cursor).collect(),
14014 next: 0,
14015 parsed_scope,
14016 run: None,
14017 }
14018 }
14019 let mut regions = Vec::new();
14020 let mut brace_closes = HashMap::default();
14021 let mut open = Vec::new();
14025 let mut lexical_scope = Vec::new();
14026 let mut frames = vec![frame(root, Vec::new(), source)];
14027 while !frames.is_empty() {
14032 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14033 let current = frames.last_mut().expect("frames is non-empty");
14034 if current.next == current.children.len() {
14035 regions.extend(frames.pop().expect("the frame just borrowed").run);
14036 continue;
14037 }
14038 let child = current.children[current.next];
14039 current.next += 1;
14040 match child.kind() {
14041 "{" if !child.is_missing() => {
14042 regions.extend(current.run.take());
14043 let mut components = parent_node
14044 .map(|parent| namespace_body_name_components(parent, current.node, source))
14045 .unwrap_or_default();
14046 if components.is_empty() {
14047 components = recovered_namespace_open_components(
14048 ¤t.children[..current.next - 1],
14049 source,
14050 );
14051 }
14052 open.push((child.start_byte(), lexical_scope.len()));
14053 lexical_scope.extend(components);
14054 continue;
14055 }
14056 "}" if !child.is_missing() => {
14057 regions.extend(current.run.take());
14058 if let Some((start, namespace_len)) = open.pop() {
14059 lexical_scope.truncate(namespace_len);
14060 brace_closes.insert(
14061 start,
14062 Range {
14063 start_byte: child.start_byte(),
14064 end_byte: child.end_byte(),
14065 start_line: child.start_position().row + 1,
14066 end_line: child.end_position().row + 1,
14067 },
14068 );
14069 }
14070 continue;
14071 }
14072 _ => {}
14073 }
14074 if current.node.kind() != "namespace_definition"
14078 && lexical_scope != current.parsed_scope
14079 {
14080 match &mut current.run {
14081 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14082 run => {
14083 regions.extend(run.take());
14084 *run = Some(RecoveredNamespaceRegion {
14085 start: child.start_byte(),
14086 end: child.end_byte(),
14087 components: lexical_scope.clone(),
14088 });
14089 }
14090 }
14091 } else {
14092 regions.extend(current.run.take());
14093 }
14094 if child.has_error() {
14098 let parsed_scope = current.parsed_scope.clone();
14099 frames.push(frame(child, parsed_scope, source));
14100 }
14101 }
14102 Self {
14103 regions,
14104 brace_closes,
14105 }
14106 }
14107
14108 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14111 self.brace_closes.get(&open).copied()
14112 }
14113
14114 pub fn is_empty(&self) -> bool {
14115 self.regions.is_empty()
14116 }
14117
14118 pub fn approximate_size(&self) -> usize {
14120 self.regions.iter().fold(
14121 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14122 |total, region| {
14123 total
14124 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14125 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14126 },
14127 )
14128 }
14129
14130 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14132 self.regions
14133 .iter()
14134 .filter(|region| region.start <= byte && byte < region.end)
14135 .min_by_key(|region| region.end - region.start)
14136 }
14137
14138 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14142 let mut parsed = Vec::new();
14143 let mut current = node.parent();
14144 while let Some(parent) = current {
14145 if parent.kind() == "namespace_definition"
14146 && let Some(name) = parent.child_by_field_name("name")
14147 {
14148 let mut components = Vec::new();
14149 if append_cpp_name_components(name, source, &mut components).is_some() {
14150 parsed.push((parent.start_byte(), components));
14151 }
14152 }
14153 current = parent.parent();
14154 }
14155 parsed.reverse();
14156 self.restore_enclosing_namespaces(parsed, node.start_byte())
14157 }
14158
14159 pub fn restore_enclosing_namespaces(
14165 &self,
14166 parsed: Vec<(usize, Vec<String>)>,
14167 node_start: usize,
14168 ) -> Vec<String> {
14169 let Some(region) = self.region_at(node_start) else {
14170 return parsed
14171 .into_iter()
14172 .flat_map(|(_, components)| components)
14173 .collect();
14174 };
14175 region
14176 .components
14177 .iter()
14178 .cloned()
14179 .chain(
14180 parsed
14181 .into_iter()
14182 .filter(|(start, _)| *start >= region.start)
14183 .flat_map(|(_, components)| components),
14184 )
14185 .collect()
14186 }
14187}
14188
14189fn recovered_namespace_open_components(preceding: &[Node<'_>], source: &str) -> Vec<String> {
14205 let mut head = Vec::new();
14206 for &sibling in preceding.iter().rev() {
14207 if sibling.kind() != "comment" {
14208 head.push(sibling);
14209 if head.len() == 2 {
14210 break;
14211 }
14212 }
14213 }
14214 let [name, keyword] = head[..] else {
14215 return Vec::new();
14216 };
14217 if keyword.kind() != "namespace" {
14218 return Vec::new();
14219 }
14220 let mut components = Vec::new();
14221 if append_cpp_name_components(name, source, &mut components).is_none() {
14222 components.clear();
14223 }
14224 components
14225}
14226
14227fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14230 let mut components = Vec::new();
14231 if body.kind() == "declaration_list"
14232 && parent.kind() == "namespace_definition"
14233 && parent.child_by_field_name("body") == Some(body)
14234 && let Some(name) = parent.child_by_field_name("name")
14235 && append_cpp_name_components(name, source, &mut components).is_none()
14236 {
14237 components.clear();
14238 }
14239 components
14240}
14241
14242#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14243pub enum RecoveredDeclaratorTypeContext {
14244 Declaration,
14245 FunctionDefinition,
14246 Parameter,
14247}
14248
14249pub fn recovered_macro_decorated_declarator_type(
14264 node: Node<'_>,
14265) -> Option<RecoveredDeclaratorTypeContext> {
14266 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14267}
14268
14269pub fn recovered_macro_decorated_type_node(
14274 node: Node<'_>,
14275) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14276 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14277 return None;
14278 }
14279 let qualified = node.parent()?;
14280 if qualified.kind() != "qualified_identifier"
14281 || qualified.child_by_field_name("scope") != Some(node)
14282 || !(0..qualified.child_count())
14283 .filter_map(|index| qualified.child(index))
14284 .any(|child| child.kind() == "::" && child.is_missing())
14285 {
14286 return None;
14287 }
14288 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14289 return None;
14290 }
14291
14292 let (declaration, context) = recovered_declarator_container(qualified)?;
14293 let type_node = declaration
14294 .child_by_field_name("type")
14295 .filter(|type_node| {
14296 *type_node != qualified
14297 && !type_node.is_missing()
14298 && type_node.start_byte() != type_node.end_byte()
14299 })?;
14300 Some((type_node, context))
14301}
14302
14303fn recovered_declarator_container(
14304 mut declarator: Node<'_>,
14305) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14306 loop {
14307 let parent = declarator.parent()?;
14308 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14309 return Some((
14310 parent
14311 .parent()
14312 .filter(|declaration| declaration.kind() == "declaration")?,
14313 RecoveredDeclaratorTypeContext::Declaration,
14314 ));
14315 }
14316 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14317 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14318 }
14319 if parent.kind() == "function_definition"
14320 && has_field_child(parent, "declarator", declarator)
14321 {
14322 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14323 }
14324 if matches!(
14330 parent.kind(),
14331 "parameter_declaration" | "optional_parameter_declaration"
14332 ) && has_field_child(parent, "declarator", declarator)
14333 {
14334 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14335 }
14336 if !matches!(
14337 parent.kind(),
14338 "array_declarator"
14339 | "function_declarator"
14340 | "parenthesized_declarator"
14341 | "pointer_declarator"
14342 | "pointer_type_declarator"
14343 | "reference_declarator"
14344 ) || !has_field_child(parent, "declarator", declarator)
14345 {
14346 return None;
14347 }
14348 declarator = parent;
14349 }
14350}
14351
14352fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14353 let mut cursor = parent.walk();
14354 parent
14355 .children_by_field_name(field, &mut cursor)
14356 .any(|child| child == target)
14357}
14358
14359fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14360 loop {
14361 if node.is_missing() || node.start_byte() == node.end_byte() {
14362 return false;
14363 }
14364 match node.kind() {
14365 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14366 return true;
14367 }
14368 "array_declarator"
14369 | "function_declarator"
14370 | "parenthesized_declarator"
14371 | "pointer_declarator"
14372 | "pointer_type_declarator"
14373 | "reference_declarator" => {
14374 let Some(declarator) = node.child_by_field_name("declarator") else {
14375 return false;
14376 };
14377 node = declarator;
14378 }
14379 _ => return false,
14380 }
14381 }
14382}
14383
14384pub enum DesignatedInitializerOwner {
14386 Resolved(CodeUnit),
14387 Unresolved,
14388}
14389
14390enum InitializerOwnerStep {
14391 Field(String),
14392 AggregateWrapper,
14393}
14394
14395pub fn designated_initializer_owner(
14405 analyzer: &CppGraphSource<'_>,
14406 visibility: &VisibilityIndex<'_>,
14407 file: &ProjectFile,
14408 source: &str,
14409 node: Node<'_>,
14410) -> Option<DesignatedInitializerOwner> {
14411 if let Some(designator) = node
14412 .parent()
14413 .filter(|parent| parent.kind() == "field_designator")
14414 {
14415 let pair = designator.parent()?;
14416 if pair.kind() != "initializer_pair" {
14417 return None;
14418 }
14419 let mut cursor = pair.walk();
14420 let designators = pair
14421 .children_by_field_name("designator", &mut cursor)
14422 .collect::<Vec<_>>();
14423 let position = designators
14424 .iter()
14425 .position(|candidate| same_node(*candidate, designator))?;
14426 let initializer = pair.parent()?;
14427 if initializer.kind() != "initializer_list" {
14428 return None;
14429 }
14430 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14431 for prior in &designators[..position] {
14432 let field = prior
14433 .child_by_field_name("field")
14434 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14435 owner = owner.and_then(|owner| {
14436 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14437 });
14438 }
14439 return Some(classified_designated_owner(owner));
14440 }
14441
14442 let init_declarator = node.parent()?;
14443 if init_declarator.child_by_field_name("declarator") != Some(node)
14444 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14445 {
14446 return None;
14447 }
14448 Some(classified_designated_owner(declaration_owner(
14449 analyzer,
14450 visibility,
14451 file,
14452 source,
14453 init_declarator.parent()?,
14454 )))
14455}
14456
14457fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14458 owner.map_or(
14459 DesignatedInitializerOwner::Unresolved,
14460 DesignatedInitializerOwner::Resolved,
14461 )
14462}
14463
14464fn initializer_list_owner(
14465 analyzer: &CppGraphSource<'_>,
14466 visibility: &VisibilityIndex<'_>,
14467 file: &ProjectFile,
14468 source: &str,
14469 initializer: Node<'_>,
14470) -> Option<CodeUnit> {
14471 let mut current = initializer;
14472 let mut steps = Vec::new();
14473 loop {
14474 let parent = current.parent()?;
14475 match parent.kind() {
14476 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14477 let designator = parent.child_by_field_name("designator")?;
14478 let step = designator
14479 .child_by_field_name("field")
14480 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14481 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14482 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14483 steps.push(step);
14484 current = parent.parent()?;
14485 }
14486 "initializer_list" => {
14487 current = parent;
14488 }
14489 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14490 let declaration = parent.parent()?;
14491 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14492 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14493 }
14494 "compound_literal_expression"
14495 if parent.child_by_field_name("value") == Some(current) =>
14496 {
14497 let type_node = parent.child_by_field_name("type")?;
14498 let owner =
14499 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
14500 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14501 }
14502 "ERROR" => current = parent,
14503 _ => return None,
14504 }
14505 }
14506}
14507
14508fn apply_initializer_owner_steps(
14509 analyzer: &CppGraphSource<'_>,
14510 visibility: &VisibilityIndex<'_>,
14511 file: &ProjectFile,
14512 mut owner: CodeUnit,
14513 steps: Vec<InitializerOwnerStep>,
14514) -> Option<CodeUnit> {
14515 for step in steps.into_iter().rev() {
14516 if let InitializerOwnerStep::Field(field_name) = step {
14517 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
14518 }
14519 }
14520 Some(owner)
14521}
14522
14523fn initializer_field_owner(
14524 analyzer: &CppGraphSource<'_>,
14525 visibility: &VisibilityIndex<'_>,
14526 file: &ProjectFile,
14527 owner: CodeUnit,
14528 field_name: &str,
14529) -> Option<CodeUnit> {
14530 let fields = visibility
14531 .visible_members_for_owner_name(file, &owner, field_name)
14532 .into_iter()
14533 .filter(|field| field.is_field())
14534 .collect::<Vec<_>>();
14535 let field = match fields.as_slice() {
14536 [field] => *field,
14537 _ => return None,
14538 };
14539 field_declared_binding(analyzer, visibility, file, field)?.unit
14540}
14541
14542fn declaration_owner(
14543 analyzer: &CppGraphSource<'_>,
14544 visibility: &VisibilityIndex<'_>,
14545 file: &ProjectFile,
14546 source: &str,
14547 declaration: Node<'_>,
14548) -> Option<CodeUnit> {
14549 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
14550 return None;
14551 }
14552 let type_node = declaration
14553 .child_by_field_name("type")
14554 .or_else(|| first_type_child(declaration))?;
14555 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
14556}
14557
14558fn resolve_designated_owner_type(
14559 analyzer: &CppGraphSource<'_>,
14560 visibility: &VisibilityIndex<'_>,
14561 file: &ProjectFile,
14562 source: &str,
14563 type_node: Node<'_>,
14564) -> Option<CodeUnit> {
14565 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
14566 return Some(owner);
14567 }
14568 let type_name = normalize_type_text(node_text(type_node, source));
14569 visibility
14570 .resolve_type(file, &type_name)
14571 .filter(CodeUnit::is_class)
14572}
14573
14574pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
14575 let mut cursor = node.walk();
14576 node.named_children(&mut cursor).find(|child| {
14577 matches!(
14578 child.kind(),
14579 "type_identifier"
14580 | "primitive_type"
14581 | "qualified_identifier"
14582 | "scoped_type_identifier"
14583 | "struct_specifier"
14584 | "union_specifier"
14585 | "enum_specifier"
14586 )
14587 })
14588}
14589
14590pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
14591 visibility: &VisibilityIndex<'_>,
14592 file: &ProjectFile,
14593 source: &str,
14594 declarator: Node<'_>,
14595 type_text: Option<&str>,
14596 bindings: &LocalInferenceEngine<T>,
14597) -> bool {
14598 if !has_ancestor_kind(declarator, "compound_statement") {
14599 return false;
14600 }
14601 if declarator
14602 .child_by_field_name("declarator")
14603 .is_none_or(|declarator| declarator.kind() != "identifier")
14604 {
14605 return false;
14606 }
14607 if !type_text
14608 .and_then(|text| visibility.resolve_type(file, text))
14609 .is_some_and(|unit| unit.is_class())
14610 {
14611 return false;
14612 }
14613 declarator
14614 .child_by_field_name("parameters")
14615 .is_some_and(|parameters| {
14616 constructor_parameters_look_like_expressions(parameters, source, bindings)
14617 })
14618}
14619
14620fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
14621 parameters: Node<'_>,
14622 source: &str,
14623 bindings: &LocalInferenceEngine<T>,
14624) -> bool {
14625 let mut cursor = parameters.walk();
14626 parameters.named_children(&mut cursor).any(|parameter| {
14627 !matches!(
14628 parameter.kind(),
14629 "parameter_declaration" | "optional_parameter_declaration"
14630 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
14631 })
14632}
14633
14634fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
14635 parameter: Node<'_>,
14636 source: &str,
14637 bindings: &LocalInferenceEngine<T>,
14638) -> bool {
14639 let text = node_text(parameter, source).trim();
14640 if text
14641 .chars()
14642 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
14643 && bindings.is_shadowed(text)
14644 {
14645 return true;
14646 }
14647
14648 let Some(base) = parameter
14649 .child_by_field_name("type")
14650 .filter(|base| base.kind() == "type_identifier")
14651 else {
14652 return false;
14653 };
14654 let Some(subscript) = parameter
14655 .child_by_field_name("declarator")
14656 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
14657 else {
14658 return false;
14659 };
14660 subscript.child_by_field_name("size").is_some()
14661 && bindings.is_shadowed(node_text(base, source).trim())
14662}
14663
14664pub fn is_declaration_name(node: Node<'_>) -> bool {
14665 let Some(parent) = node.parent() else {
14666 return false;
14667 };
14668 if parent
14669 .child_by_field_name("name")
14670 .is_some_and(|name| same_node(name, node))
14671 {
14672 if matches!(
14673 parent.kind(),
14674 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14675 ) {
14676 return cpp_tag_specifier_declares_name(parent);
14677 }
14678 if matches!(
14679 parent.kind(),
14680 "namespace_definition"
14681 | "namespace_alias_definition"
14682 | "alias_declaration"
14683 | "enumerator"
14684 ) {
14685 return true;
14686 }
14687 }
14688
14689 let mut current = Some(parent);
14690 while let Some(ancestor) = current {
14691 let type_definition = ancestor.kind() == "type_definition";
14692 let mut child_cursor = ancestor.walk();
14693 if ancestor.named_children(&mut child_cursor).any(|child| {
14694 declaration_declarator(ancestor, child).is_some_and(|declarator| {
14695 declarator_name_path_contains(declarator, node, type_definition)
14696 })
14697 }) {
14698 return true;
14699 }
14700 if matches!(
14701 ancestor.kind(),
14702 "declaration"
14703 | "field_declaration"
14704 | "parameter_declaration"
14705 | "optional_parameter_declaration"
14706 | "function_definition"
14707 | "type_definition"
14708 | "alias_declaration"
14709 | "template_instantiation"
14710 | "class_specifier"
14711 | "struct_specifier"
14712 | "union_specifier"
14713 | "enum_specifier"
14714 ) {
14715 return false;
14716 }
14717 current = ancestor.parent();
14718 }
14719 false
14720}
14721
14722pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
14731 if !matches!(
14732 node.kind(),
14733 "qualified_identifier" | "scoped_type_identifier"
14734 ) {
14735 return false;
14736 }
14737 let Some(declaration) = node
14738 .parent()
14739 .filter(|parent| parent.kind() == "declaration")
14740 else {
14741 return false;
14742 };
14743 if declaration.child_by_field_name("declarator") != Some(node)
14744 || !declaration
14745 .child_by_field_name("type")
14746 .is_some_and(|friend| {
14747 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
14748 })
14749 {
14750 return false;
14751 }
14752 let mut cursor = declaration.walk();
14753 let mut errors = declaration
14754 .named_children(&mut cursor)
14755 .filter(|child| child.kind() == "ERROR");
14756 let Some(error) = errors.next() else {
14757 return false;
14758 };
14759 errors.next().is_none()
14760 && error.named_child_count() == 1
14761 && error.named_child(0).is_some_and(|class| {
14762 class.kind() == "identifier" && node_text(class, source) == "class"
14763 })
14764}
14765
14766pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
14767 if !matches!(node.kind(), "identifier" | "field_identifier") {
14768 return false;
14769 }
14770 if let Some(parent) = node.parent() {
14771 if parent.kind() == "call_expression"
14772 && parent.child_by_field_name("function") == Some(node)
14773 {
14774 return false;
14775 }
14776 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
14777 && parent.child_by_field_name("label") == Some(node)
14778 {
14779 return false;
14780 }
14781 }
14782 if is_declaration_name(node) {
14783 return false;
14784 }
14785 let mut current = node.parent();
14786 while let Some(ancestor) = current {
14787 match ancestor.kind() {
14788 "preproc_ifdef" | "preproc_ifndef" => {
14789 if ancestor
14790 .child_by_field_name("name")
14791 .is_some_and(|name| node_range_contains(name, node))
14792 {
14793 return false;
14794 }
14795 }
14796 "preproc_if" | "preproc_elif" => {
14797 if ancestor
14798 .child_by_field_name("condition")
14799 .is_some_and(|condition| node_range_contains(condition, node))
14800 {
14801 return false;
14802 }
14803 }
14804 "preproc_else" => {}
14805 kind if kind.starts_with("preproc_") => return false,
14806 _ => {}
14807 }
14808 if matches!(
14809 ancestor.kind(),
14810 "translation_unit" | "function_definition" | "compound_statement"
14811 ) {
14812 break;
14813 }
14814 current = ancestor.parent();
14815 }
14816 true
14817}
14818
14819fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
14820 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
14821}
14822
14823fn recovered_c_reference_node(
14824 visibility: &VisibilityIndex<'_>,
14825 file: &ProjectFile,
14826 node: Node<'_>,
14827 source: &str,
14828) -> bool {
14829 if node.start_byte() >= node.end_byte()
14830 || node.is_error()
14831 || node.is_missing()
14832 || !matches!(
14833 node.kind(),
14834 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
14835 )
14836 || recovered_c_macro_binding_role(node)
14837 || recovered_c_label_role(node)
14838 {
14839 return false;
14840 }
14841 let name = node_text(node, source);
14849 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
14850 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
14851 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
14852 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
14853 if is_declaration_name(node)
14854 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
14855 && !recovered_function_call
14856 && !recovered_macro_call
14857 && !recovered_parenthesized_reference
14858 {
14859 return false;
14860 }
14861
14862 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
14863 return true;
14864 }
14865 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
14866 return true;
14867 }
14868 if recovered_parenthesized_reference {
14869 return true;
14870 }
14871 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
14872 if recovered_function_call {
14873 return true;
14874 }
14875 return visibility
14876 .visible_identifier_candidates(file, name)
14877 .any(|candidate| {
14878 candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
14879 });
14880 }
14881 let visible = visibility
14882 .visible_identifier_candidates(file, name)
14883 .next()
14884 .is_some();
14885 visible
14886 && (recovered_c_reference_anchor(node)
14887 || recovered_c_error_expression_leaf(node)
14888 || recovered_function_call)
14889}
14890
14891fn push_recovered_c_range(
14892 ranges: &mut Vec<Range>,
14893 seen: &mut HashSet<(usize, usize)>,
14894 start_byte: usize,
14895 end_byte: usize,
14896 node: Node<'_>,
14897 limit: usize,
14898) -> bool {
14899 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
14900 return true;
14901 }
14902 if ranges.len() >= limit {
14903 return false;
14904 }
14905 ranges.push(Range {
14906 start_byte,
14907 end_byte,
14908 start_line: node.start_position().row,
14909 end_line: node.end_position().row,
14910 });
14911 true
14912}
14913
14914fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
14919 let mut current = node.parent();
14920 while let Some(parent) = current {
14921 if parent.is_error() {
14922 let Some(anchor) = parent.parent() else {
14923 return false;
14924 };
14925 return anchor.kind().ends_with("_expression")
14926 || matches!(
14927 anchor.kind(),
14928 "argument_list"
14929 | "return_statement"
14930 | "expression_statement"
14931 | "case_statement"
14932 | "initializer_list"
14933 | "field_designator"
14934 | "enumerator"
14935 );
14936 }
14937 if matches!(
14938 parent.kind(),
14939 "translation_unit" | "function_definition" | "compound_statement"
14940 ) {
14941 return false;
14942 }
14943 current = parent.parent();
14944 }
14945 false
14946}
14947
14948fn recovered_c_function_call(
14955 visibility: &VisibilityIndex<'_>,
14956 file: &ProjectFile,
14957 node: Node<'_>,
14958 name: &str,
14959) -> bool {
14960 if !matches!(
14961 node.kind(),
14962 "identifier" | "field_identifier" | "type_identifier"
14963 ) {
14964 return false;
14965 }
14966 let error_call_prefix = node.parent().is_some_and(|error| {
14971 error.is_error()
14972 && error
14973 .parent()
14974 .is_some_and(|parent| parent.kind() == "compound_statement")
14975 }) && node
14976 .prev_sibling()
14977 .is_none_or(|previous| previous.kind() == ";")
14978 && node.next_sibling().is_some_and(|open| {
14979 open.kind() == "("
14980 && open.next_named_sibling().is_some_and(|argument| {
14981 argument.kind() == "parameter_declaration" && !argument.has_error()
14982 })
14983 });
14984 (error_call_prefix || recovered_c_function_declarator_invocation(node))
14985 && visibility
14986 .visible_identifier_candidates(file, name)
14987 .any(CodeUnit::is_function)
14988}
14989
14990fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15000 let mut function_declarator = if node.parent().is_some_and(|parent| {
15001 parent.kind() == "function_declarator"
15002 && parent.child_by_field_name("declarator") == Some(node)
15003 }) {
15004 node.parent().expect("checked function declarator parent")
15005 } else {
15006 let Some(parameter) = node.parent().filter(|parent| {
15007 parent.kind() == "parameter_declaration"
15008 && parent.child_by_field_name("type") == Some(node)
15009 }) else {
15010 return false;
15011 };
15012 if !parameter
15013 .child_by_field_name("declarator")
15014 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15015 {
15016 return false;
15017 }
15018 let Some(parameters) = parameter
15019 .parent()
15020 .filter(|parent| parent.kind() == "parameter_list")
15021 else {
15022 return false;
15023 };
15024 let Some(function_declarator) = parameters
15025 .parent()
15026 .filter(|parent| parent.kind() == "function_declarator")
15027 else {
15028 return false;
15029 };
15030 function_declarator
15031 };
15032
15033 while let Some(parent) = function_declarator.parent().filter(|parent| {
15036 parent.kind() == "function_declarator"
15037 && parent.child_by_field_name("declarator") == Some(function_declarator)
15038 }) {
15039 function_declarator = parent;
15040 }
15041 let Some(mut current) = function_declarator
15042 .parent()
15043 .filter(|parent| parent.is_error())
15044 else {
15045 return false;
15046 };
15047 loop {
15048 let Some(parent) = current.parent() else {
15049 return false;
15050 };
15051 if matches!(
15052 parent.kind(),
15053 "translation_unit"
15054 | "compound_statement"
15055 | "preproc_if"
15056 | "preproc_ifdef"
15057 | "preproc_ifndef"
15058 | "preproc_else"
15059 | "preproc_elif"
15060 ) {
15061 return true;
15062 }
15063 if parent.kind() == "function_definition"
15064 && parent.child_by_field_name("declarator") == Some(current)
15065 && parent.named_child(0) == Some(current)
15066 && parent.child_by_field_name("body").is_some()
15067 {
15068 return true;
15069 }
15070 if parent.is_error()
15071 || matches!(
15072 parent.kind(),
15073 "parameter_declaration"
15074 | "parameter_list"
15075 | "function_declarator"
15076 | "abstract_function_declarator"
15077 | "parenthesized_declarator"
15078 )
15079 {
15080 current = parent;
15081 continue;
15082 }
15083 return false;
15084 }
15085}
15086
15087fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15093 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15094 return false;
15095 };
15096 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15097 return false;
15098 }
15099 let Some(declarator) = error
15100 .parent()
15101 .filter(|parent| parent.kind() == "parenthesized_declarator")
15102 else {
15103 return false;
15104 };
15105 let Some(declaration) = declarator
15106 .parent()
15107 .filter(|parent| parent.kind() == "declaration")
15108 else {
15109 return false;
15110 };
15111 if declaration.child_by_field_name("declarator") != Some(declarator) {
15112 return false;
15113 }
15114 let Some(type_node) = declaration.child_by_field_name("type") else {
15115 return false;
15116 };
15117 type_node.kind() == "dependent_type"
15118 && type_node
15119 .child(0)
15120 .is_some_and(|keyword| keyword.kind() == "typename")
15121}
15122
15123fn recovered_c_explicit_assignment_callee(
15124 visibility: &VisibilityIndex<'_>,
15125 file: &ProjectFile,
15126 node: Node<'_>,
15127 name: &str,
15128) -> bool {
15129 let mut current = node;
15130 let error = loop {
15131 let Some(parent) = current.parent() else {
15132 return false;
15133 };
15134 if parent.is_error() {
15135 break parent;
15136 }
15137 current = parent;
15138 };
15139 let mut cursor = error.walk();
15140 let explicit_recovery_precedes_callee = error
15141 .named_children(&mut cursor)
15142 .take_while(|child| child.start_byte() < node.start_byte())
15143 .any(|child| child.kind() == "explicit_function_specifier");
15144 if !explicit_recovery_precedes_callee {
15145 return false;
15146 }
15147 visibility
15148 .visible_identifier_candidates(file, name)
15149 .any(CodeUnit::is_function)
15150}
15151
15152fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15153 while let Some(parent) = node.parent() {
15154 if matches!(
15155 parent.kind(),
15156 "preproc_def" | "preproc_function_def" | "preproc_params"
15157 ) {
15158 return true;
15159 }
15160 if parent.is_error()
15161 || matches!(
15162 parent.kind(),
15163 "translation_unit" | "function_definition" | "compound_statement"
15164 )
15165 {
15166 return false;
15167 }
15168 node = parent;
15169 }
15170 false
15171}
15172
15173fn recovered_c_label_role(node: Node<'_>) -> bool {
15174 node.parent().is_some_and(|parent| {
15175 matches!(parent.kind(), "labeled_statement" | "goto_statement")
15176 && parent.child_by_field_name("label") == Some(node)
15177 })
15178}
15179
15180fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15181 while let Some(parent) = node.parent() {
15182 if parent.is_error() {
15183 return false;
15184 }
15185 if parent.kind() == "optional_parameter_declaration"
15190 && parent
15191 .child_by_field_name("default_value")
15192 .is_some_and(|value| node_range_contains(value, node))
15193 {
15194 return true;
15195 }
15196 if parent.kind().ends_with("_expression")
15197 || matches!(
15198 parent.kind(),
15199 "argument_list"
15200 | "return_statement"
15201 | "expression_statement"
15202 | "case_statement"
15203 | "initializer_list"
15204 | "init_declarator"
15205 | "array_declarator"
15206 | "field_designator"
15207 | "enumerator"
15208 )
15209 {
15210 return true;
15211 }
15212 if matches!(
15213 parent.kind(),
15214 "translation_unit"
15215 | "function_definition"
15216 | "compound_statement"
15217 | "declaration"
15218 | "field_declaration"
15219 | "parameter_declaration"
15220 ) {
15221 return false;
15222 }
15223 node = parent;
15224 }
15225 false
15226}
15227
15228pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15236 let mut current = parameter.parent();
15237 while let Some(ancestor) = current {
15238 if ancestor.kind() == "lambda_expression" {
15239 return ancestor
15240 .child_by_field_name("declarator")
15241 .is_some_and(|declarator| {
15242 declarator.start_byte() <= parameter.start_byte()
15243 && parameter.end_byte() <= declarator.end_byte()
15244 });
15245 }
15246 if ancestor.kind() == "function_definition" {
15247 return ancestor
15248 .child_by_field_name("declarator")
15249 .is_some_and(|declarator| {
15250 declarator.start_byte() <= parameter.start_byte()
15251 && parameter.end_byte() <= declarator.end_byte()
15252 });
15253 }
15254 current = ancestor.parent();
15255 }
15256 false
15257}
15258
15259pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15260 let mut current = node.parent();
15261 while let Some(ancestor) = current {
15262 if matches!(
15263 ancestor.kind(),
15264 "parameter_declaration" | "optional_parameter_declaration"
15265 ) {
15266 return ancestor
15267 .child_by_field_name("type")
15268 .is_some_and(|type_node| {
15269 type_node.start_byte() <= node.start_byte()
15270 && node.end_byte() <= type_node.end_byte()
15271 });
15272 }
15273 if matches!(
15274 ancestor.kind(),
15275 "function_definition" | "lambda_expression" | "compound_statement"
15276 ) {
15277 return false;
15278 }
15279 current = ancestor.parent();
15280 }
15281 false
15282}
15283
15284fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15285 if specifier.child_by_field_name("body").is_some() {
15286 return true;
15287 }
15288 let mut current = specifier.parent();
15289 while let Some(ancestor) = current {
15290 match ancestor.kind() {
15291 "type_descriptor"
15292 | "parameter_declaration"
15293 | "optional_parameter_declaration"
15294 | "template_argument_list"
15295 | "cast_expression" => return false,
15296 "declaration" | "field_declaration" => {
15297 let mut cursor = ancestor.walk();
15298 return ancestor
15299 .children_by_field_name("declarator", &mut cursor)
15300 .next()
15301 .is_none();
15302 }
15303 "translation_unit" => return true,
15304 _ => current = ancestor.parent(),
15305 }
15306 }
15307 false
15308}
15309
15310pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15311 match node.kind() {
15312 "identifier"
15313 | "field_identifier"
15314 | "qualified_identifier"
15315 | "scoped_identifier"
15316 | "operator_name"
15317 | "destructor_name"
15318 | "literal_operator_name" => Some(node),
15319 "reference_declarator" | "parenthesized_declarator" => {
15320 node.named_child(0).and_then(declarator_name_node)
15321 }
15322 _ => node
15323 .child_by_field_name("declarator")
15324 .or_else(|| node.child_by_field_name("name"))
15325 .or_else(|| node.child_by_field_name("field"))
15326 .and_then(declarator_name_node),
15327 }
15328}
15329
15330fn declarator_name_path_contains(
15331 declarator: Node<'_>,
15332 candidate: Node<'_>,
15333 allow_type_identifier: bool,
15334) -> bool {
15335 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15336 return false;
15337 };
15338 let mut current = Some(declarator);
15339 while let Some(node) = current {
15340 if same_node(node, candidate) {
15341 return true;
15342 }
15343 if same_node(node, name) {
15344 return false;
15345 }
15346 current = node
15347 .child_by_field_name("declarator")
15348 .or_else(|| node.child_by_field_name("name"))
15349 .or_else(|| node.child_by_field_name("field"));
15350 }
15351 false
15352}
15353
15354fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15355 match node.kind() {
15356 "identifier"
15357 | "field_identifier"
15358 | "operator_name"
15359 | "destructor_name"
15360 | "literal_operator_name" => Some(node),
15361 "type_identifier" if allow_type_identifier => Some(node),
15362 _ => node
15363 .child_by_field_name("declarator")
15364 .or_else(|| node.child_by_field_name("name"))
15365 .or_else(|| node.child_by_field_name("field"))
15366 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15367 }
15368}
15369
15370pub fn is_nested_type_node(node: Node<'_>) -> bool {
15373 node.parent().is_some_and(|parent| {
15374 matches!(
15375 parent.kind(),
15376 "qualified_identifier" | "scoped_type_identifier" | "template_type"
15377 )
15378 })
15379}
15380
15381pub struct OutOfLineMemberDefinitionOwners<'tree> {
15382 pub owners: Vec<(Node<'tree>, CodeUnit)>,
15383 innermost: Option<(Node<'tree>, CodeUnit)>,
15384}
15385
15386impl OutOfLineMemberDefinitionOwners<'_> {
15387 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15388 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15389 }
15390}
15391
15392pub struct QualifiedOwnerComponents<'tree> {
15393 pub nodes: Vec<Node<'tree>>,
15394 pub names: Vec<String>,
15395 pub global: bool,
15396}
15397
15398pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15403 let mut stack = vec![node];
15404 let mut found_separator = false;
15405 while let Some(current) = stack.pop() {
15406 if !matches!(
15407 current.kind(),
15408 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15409 ) {
15410 continue;
15411 }
15412 let mut current_has_separator = false;
15413 for child in children_iter(current) {
15414 if child.kind() == "::" {
15415 if child.is_missing() {
15416 return false;
15417 }
15418 current_has_separator = true;
15419 found_separator = true;
15420 }
15421 }
15422 if !current_has_separator {
15423 return false;
15424 }
15425 for field in ["scope", "name"] {
15426 if let Some(child) = current.child_by_field_name(field)
15427 && matches!(
15428 child.kind(),
15429 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15430 )
15431 {
15432 stack.push(child);
15433 }
15434 }
15435 }
15436 found_separator
15437}
15438
15439pub fn qualified_owner_components<'tree>(
15440 node: Node<'tree>,
15441 source: &str,
15442) -> Option<QualifiedOwnerComponents<'tree>> {
15443 if !qualified_name_has_concrete_scope_separators(node) {
15444 return None;
15445 }
15446 let mut nodes = cpp_name_component_nodes(node)?;
15447 nodes.pop()?;
15448 if nodes.is_empty() {
15449 return None;
15450 }
15451 let names = nodes
15452 .iter()
15453 .map(|component| node_text(*component, source).to_string())
15454 .collect();
15455 Some(QualifiedOwnerComponents {
15456 nodes,
15457 names,
15458 global: is_globally_qualified_cpp_name(node),
15459 })
15460}
15461
15462pub fn out_of_line_member_definition_owner<'tree>(
15463 analyzer: &CppGraphSource<'_>,
15464 visibility: &VisibilityIndex<'_>,
15465 file: &ProjectFile,
15466 source: &str,
15467 node: Node<'tree>,
15468) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15469 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15470 || !has_ancestor_kind(node, "function_definition")
15471 || !is_function_declarator_name_root(node)
15472 {
15473 return None;
15474 }
15475 let qualified = qualified_owner_components(node, source)?;
15476 let lexical_scope = enclosing_namespace_components(node, source)?;
15477 let mut owners = Vec::new();
15478 let mut innermost = None;
15479
15480 for component_count in 1..=qualified.names.len() {
15481 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15482 .resolve_type_components_lexically(
15483 analyzer,
15484 file,
15485 &qualified.names[..component_count],
15486 qualified.global,
15487 &lexical_scope,
15488 )
15489 && !owners
15490 .iter()
15491 .any(|(_, existing)| same_visible_symbol(existing, &unit))
15492 {
15493 if component_count == qualified.names.len() {
15494 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
15495 }
15496 owners.push((qualified.nodes[component_count - 1], unit));
15497 }
15498 }
15499
15500 if innermost.is_none() {
15510 let indexed_owner_components = visibility
15511 .indexed_enclosing_owner_scope(analyzer, file, node)
15512 .or_else(|| {
15513 if qualified.names.len() <= 1 {
15518 return None;
15519 }
15520 let range = Range {
15521 start_byte: node.start_byte(),
15522 end_byte: node.end_byte(),
15523 start_line: node.start_position().row,
15524 end_line: node.end_position().row,
15525 };
15526 let start = analyzer.enclosing_code_unit(file, &range)?;
15527 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15528 brokk_bifrost_core::analyzer::Language::Cpp,
15529 &cpp_name_for(&start),
15530 );
15531 components.pop();
15532 Some(components)
15533 });
15534 if let Some(indexed_owner_components) = indexed_owner_components
15535 && indexed_owner_components.len() > qualified.names.len()
15536 && indexed_owner_components.ends_with(&qualified.names)
15537 && indexed_namespace_path_is_recoverable(
15538 &lexical_scope,
15539 &indexed_owner_components,
15540 qualified.names.len(),
15541 )
15542 && (qualified.names.len() > 1 || !qualified.global)
15547 {
15548 let namespace_count = indexed_owner_components.len() - qualified.names.len();
15549 for component_count in 1..=qualified.names.len() {
15550 let expected = &indexed_owner_components[..namespace_count + component_count];
15551 let owner_node = qualified.nodes[component_count - 1];
15552 for owner in visibility
15553 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
15554 .filter(|candidate| candidate.is_class())
15555 .filter(|candidate| {
15556 canonical_cpp_scope_components(candidate) == expected
15557 && visibility.external_type_candidate_visible_in_context(
15558 analyzer, file, candidate, node,
15559 )
15560 })
15561 {
15562 if component_count == qualified.names.len() && innermost.is_none() {
15563 innermost = Some((owner_node, owner.clone()));
15564 }
15565 if !owners
15566 .iter()
15567 .any(|(_, existing)| same_symbol(existing, owner))
15568 {
15569 owners.push((owner_node, owner.clone()));
15570 }
15571 }
15572 }
15573 }
15574 }
15575 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
15576}
15577
15578fn is_function_declarator_name_root(node: Node<'_>) -> bool {
15579 let mut current = node;
15580 while let Some(parent) = current.parent() {
15581 if parent.kind() == "function_declarator" {
15582 return parent.child_by_field_name("declarator") == Some(current);
15583 }
15584 if matches!(
15585 parent.kind(),
15586 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
15587 ) && parent.child_by_field_name("declarator") == Some(current)
15588 {
15589 current = parent;
15590 continue;
15591 }
15592 return false;
15593 }
15594 false
15595}
15596
15597pub fn append_cpp_name_components(
15598 node: Node<'_>,
15599 source: &str,
15600 out: &mut Vec<String>,
15601) -> Option<()> {
15602 out.extend(
15603 cpp_name_component_nodes(node)?
15604 .into_iter()
15605 .map(|component| node_text(component, source).to_string()),
15606 );
15607 Some(())
15608}
15609
15610pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
15611 let mut components = Vec::new();
15612 append_cpp_name_components(node, source, &mut components)?;
15613 Some(components)
15614}
15615
15616pub fn unique_macro_replacement_type_candidate(
15625 analyzer: &CppGraphSource<'_>,
15626 visibility: &VisibilityIndex<'_>,
15627 file: &ProjectFile,
15628 components: &[String],
15629) -> Option<CodeUnit> {
15630 let terminal = components.last()?;
15631 let mut candidates = Vec::new();
15632 for candidate in visibility
15633 .visible_identifier_candidates(file, terminal)
15634 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
15635 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
15636 {
15637 if !candidates
15638 .iter()
15639 .any(|existing| same_logical_symbol(existing, candidate))
15640 {
15641 candidates.push(candidate.clone());
15642 }
15643 }
15644 (candidates.len() == 1).then(|| candidates.remove(0))
15645}
15646
15647pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
15655 let mut parser = Parser::new();
15656 if parser
15657 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15658 .is_err()
15659 {
15660 return Vec::new();
15661 }
15662 let Some(tree) = parser.parse(source, None) else {
15663 return Vec::new();
15664 };
15665 let mut scopes = Vec::new();
15666 let mut pending = vec![tree.root_node()];
15667 while let Some(node) = pending.pop() {
15668 if node.kind() == "using_declaration" {
15669 let Some(imported) = node.named_child(0) else {
15670 continue;
15671 };
15672 let Some(mut components) = cpp_type_name_components(imported, source) else {
15673 continue;
15674 };
15675 if components.pop().as_deref() == Some(member) && !components.is_empty() {
15676 scopes.push(components.join("::"));
15677 }
15678 continue;
15679 }
15680 push_named_children_reversed(node, &mut pending);
15681 }
15682 scopes
15683}
15684
15685pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
15689 qualified == scope
15690 || qualified
15691 .strip_suffix(scope)
15692 .is_some_and(|prefix| prefix.ends_with("::"))
15693}
15694
15695pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
15700 let Some(type_descriptor) = node.parent() else {
15701 return false;
15702 };
15703 if type_descriptor.kind() != "type_descriptor"
15704 || type_descriptor.child_by_field_name("type") != Some(node)
15705 {
15706 return false;
15707 }
15708 let Some(arguments) = type_descriptor.parent() else {
15709 return false;
15710 };
15711 if arguments.kind() != "template_argument_list" {
15712 return false;
15713 }
15714 arguments.parent().is_some_and(|parent| {
15715 matches!(parent.kind(), "template_type" | "template_function")
15716 && parent.child_by_field_name("arguments") == Some(arguments)
15717 })
15718}
15719
15720pub fn cpp_template_reference_arguments(
15721 mut node: Node<'_>,
15722 source: &str,
15723) -> Option<Vec<CppTemplateExpression>> {
15724 loop {
15725 match node.kind() {
15726 "template_type" | "template_function" => {
15727 let arguments = node.child_by_field_name("arguments")?;
15728 let mut cursor = arguments.walk();
15729 return Some(
15730 arguments
15731 .named_children(&mut cursor)
15732 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
15733 .map(|argument| CppTemplateExpression {
15734 text: normalize_cpp_whitespace(node_text(argument, source)),
15735 term: cpp_template_term(
15737 argument,
15738 source,
15739 &[],
15740 &ParentIndex::unindexed(),
15741 ),
15742 })
15743 .collect(),
15744 );
15745 }
15746 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
15747 node = node
15748 .child_by_field_name("name")
15749 .or_else(|| node.child_by_field_name("type"))?;
15750 }
15751 _ => return None,
15752 }
15753 }
15754}
15755
15756fn cpp_reconcile_primary_template_parameters(
15757 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
15758 preferred: &CodeUnit,
15759) -> Option<Vec<CppTemplateParameterMetadata>> {
15760 let canonical = candidates
15761 .iter()
15762 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
15763 let mut merged = canonical
15764 .parameters
15765 .iter()
15766 .map(|parameter| CppTemplateParameterMetadata {
15767 name: parameter.name.clone(),
15768 kind: parameter.kind,
15769 variadic: parameter.variadic,
15770 default: None,
15771 })
15772 .collect::<Vec<_>>();
15773
15774 for (_, metadata) in candidates {
15775 if metadata.parameters.len() != merged.len() {
15776 return None;
15777 }
15778 let rename_bindings = metadata
15779 .parameters
15780 .iter()
15781 .zip(&merged)
15782 .map(|(parameter, canonical)| {
15783 (
15784 parameter.name.clone(),
15785 CppTemplateTerm::Parameter(canonical.name.clone()),
15786 )
15787 })
15788 .collect::<HashMap<_, _>>();
15789 for ((parameter, canonical), merged_parameter) in metadata
15790 .parameters
15791 .iter()
15792 .zip(&canonical.parameters)
15793 .zip(&mut merged)
15794 {
15795 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
15796 return None;
15797 }
15798 let Some(default) = ¶meter.default else {
15799 continue;
15800 };
15801 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
15802 if let Some(existing) = &merged_parameter.default {
15803 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
15804 return None;
15805 }
15806 } else {
15807 merged_parameter.default = Some(CppTemplateExpression {
15808 text: default.text.clone(),
15809 term: normalized_term,
15810 });
15811 }
15812 }
15813 }
15814 Some(merged)
15815}
15816
15817pub fn cpp_bind_template_arguments(
15818 parameters: &[CppTemplateParameterMetadata],
15819 explicit_arguments: &[CppTemplateExpression],
15820) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
15821 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
15822 if variadic_index.is_some_and(|index| {
15823 index + 1 != parameters.len()
15824 || parameters[index + 1..]
15825 .iter()
15826 .any(|parameter| parameter.variadic)
15827 }) {
15828 return None;
15829 }
15830 let fixed_count = variadic_index.unwrap_or(parameters.len());
15831 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
15832 return None;
15833 }
15834 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
15835 let mut expanded = explicit_arguments[..explicit_fixed_count]
15836 .iter()
15837 .map(cpp_clone_template_expression_iterative)
15838 .collect::<Vec<_>>();
15839 let mut bindings = HashMap::default();
15840 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
15841 bindings.insert(
15842 parameter.name.clone(),
15843 cpp_clone_template_term_iterative(&argument.term),
15844 );
15845 }
15846 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
15847 let default = parameter.default.as_ref()?;
15848 let term = cpp_substitute_template_term(&default.term, &bindings)?;
15849 bindings.insert(parameter.name.clone(), term.clone());
15850 expanded.push(CppTemplateExpression {
15851 text: default.text.clone(),
15852 term,
15853 });
15854 }
15855 if let Some(index) = variadic_index {
15856 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
15857 expanded.extend(
15858 packed_arguments
15859 .iter()
15860 .map(cpp_clone_template_expression_iterative),
15861 );
15862 bindings.insert(
15863 parameters[index].name.clone(),
15864 CppTemplateTerm::Node {
15865 kind: "parameter_pack".to_string(),
15866 children: packed_arguments
15867 .iter()
15868 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
15869 .collect(),
15870 },
15871 );
15872 }
15873 Some((expanded, bindings))
15874}
15875
15876fn cpp_specialization_matches(
15877 metadata: &CppTemplateMetadata,
15878 arguments: &[CppTemplateExpression],
15879) -> bool {
15880 if metadata.specialization_arguments.len() != arguments.len() {
15881 return false;
15882 }
15883 let parameter_names = metadata
15884 .parameters
15885 .iter()
15886 .map(|parameter| parameter.name.as_str())
15887 .collect::<HashSet<_>>();
15888 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
15889 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
15890 if !cpp_unify_template_term(
15891 &pattern.term,
15892 &argument.term,
15893 ¶meter_names,
15894 &mut bindings,
15895 ) {
15896 return false;
15897 }
15898 }
15899 true
15900}
15901
15902fn cpp_specialization_more_specialized(
15903 candidate: &CppTemplateMetadata,
15904 other: &CppTemplateMetadata,
15905) -> bool {
15906 cpp_specialization_pattern_accepts(other, candidate)
15907 && !cpp_specialization_pattern_accepts(candidate, other)
15908}
15909
15910fn cpp_specialization_pattern_accepts(
15911 broader: &CppTemplateMetadata,
15912 narrower: &CppTemplateMetadata,
15913) -> bool {
15914 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
15915 return false;
15916 }
15917 let parameter_names = broader
15918 .parameters
15919 .iter()
15920 .map(|parameter| parameter.name.as_str())
15921 .collect::<HashSet<_>>();
15922 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
15923 broader
15924 .specialization_arguments
15925 .iter()
15926 .zip(&narrower.specialization_arguments)
15927 .all(|(pattern, argument)| {
15928 cpp_unify_template_term(
15929 &pattern.term,
15930 &argument.term,
15931 ¶meter_names,
15932 &mut bindings,
15933 )
15934 })
15935}
15936
15937pub fn cpp_substitute_template_term(
15938 term: &CppTemplateTerm,
15939 bindings: &HashMap<String, CppTemplateTerm>,
15940) -> Option<CppTemplateTerm> {
15941 enum Work<'a> {
15942 Visit(&'a CppTemplateTerm),
15943 Build { kind: String, child_count: usize },
15944 }
15945
15946 let mut work = vec![Work::Visit(term)];
15947 let mut substituted = Vec::new();
15948 while let Some(next) = work.pop() {
15949 match next {
15950 Work::Visit(CppTemplateTerm::Parameter(name)) => {
15951 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
15952 }
15953 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
15954 substituted.push(CppTemplateTerm::Atom {
15955 kind: kind.clone(),
15956 text: text.clone(),
15957 });
15958 }
15959 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
15960 work.push(Work::Build {
15961 kind: kind.clone(),
15962 child_count: children.len(),
15963 });
15964 work.extend(children.iter().rev().map(Work::Visit));
15965 }
15966 Work::Build { kind, child_count } => {
15967 let children = substituted.split_off(substituted.len() - child_count);
15968 substituted.push(CppTemplateTerm::Node { kind, children });
15969 }
15970 }
15971 }
15972 substituted.pop()
15973}
15974
15975pub fn cpp_substitute_template_arguments(
15976 arguments: &[CppTemplateExpression],
15977 bindings: &HashMap<String, CppTemplateTerm>,
15978) -> Option<Vec<CppTemplateExpression>> {
15979 let mut substituted = Vec::new();
15980 for argument in arguments {
15981 let CppTemplateTerm::Node { kind, children } = &argument.term else {
15982 substituted.push(CppTemplateExpression {
15983 text: argument.text.clone(),
15984 term: cpp_substitute_template_term(&argument.term, bindings)?,
15985 });
15986 continue;
15987 };
15988 if kind != "parameter_pack_expansion" {
15989 substituted.push(CppTemplateExpression {
15990 text: argument.text.clone(),
15991 term: cpp_substitute_template_term(&argument.term, bindings)?,
15992 });
15993 continue;
15994 }
15995 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
15996 return None;
15997 };
15998 if ellipsis != "..." {
15999 return None;
16000 }
16001
16002 let mut pack_names = Vec::new();
16003 let mut work = vec![pattern];
16004 while let Some(term) = work.pop() {
16005 match term {
16006 CppTemplateTerm::Parameter(name)
16007 if matches!(
16008 bindings.get(name),
16009 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16010 ) =>
16011 {
16012 if !pack_names.contains(name) {
16013 pack_names.push(name.clone());
16014 }
16015 }
16016 CppTemplateTerm::Node { children, .. } => work.extend(children),
16017 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16018 }
16019 }
16020 let first_pack = pack_names.first()?;
16021 let CppTemplateTerm::Node {
16022 children: first_elements,
16023 ..
16024 } = bindings.get(first_pack)?
16025 else {
16026 return None;
16027 };
16028 let pack_len = first_elements.len();
16029 for pack_name in &pack_names {
16030 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16031 return None;
16032 };
16033 if children.len() != pack_len {
16034 return None;
16035 }
16036 }
16037 for index in 0..pack_len {
16038 let mut element_bindings = bindings.clone();
16039 for pack_name in &pack_names {
16040 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16041 return None;
16042 };
16043 element_bindings.insert(
16044 pack_name.clone(),
16045 cpp_clone_template_term_iterative(&children[index]),
16046 );
16047 }
16048 substituted.push(CppTemplateExpression {
16049 text: argument.text.clone(),
16050 term: cpp_substitute_template_term(pattern, &element_bindings)?,
16051 });
16052 }
16053 }
16054 Some(substituted)
16055}
16056
16057fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16058 enum Work<'a> {
16059 Visit(&'a CppTemplateTerm),
16060 Build { kind: String, child_count: usize },
16061 }
16062
16063 let mut work = vec![Work::Visit(term)];
16064 let mut cloned = Vec::new();
16065 while let Some(next) = work.pop() {
16066 match next {
16067 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16068 cloned.push(CppTemplateTerm::Parameter(name.clone()));
16069 }
16070 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16071 cloned.push(CppTemplateTerm::Atom {
16072 kind: kind.clone(),
16073 text: text.clone(),
16074 });
16075 }
16076 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16077 work.push(Work::Build {
16078 kind: kind.clone(),
16079 child_count: children.len(),
16080 });
16081 work.extend(children.iter().rev().map(Work::Visit));
16082 }
16083 Work::Build { kind, child_count } => {
16084 let children = cloned.split_off(cloned.len() - child_count);
16085 cloned.push(CppTemplateTerm::Node { kind, children });
16086 }
16087 }
16088 }
16089 cloned
16090 .pop()
16091 .expect("template term traversal emits one root")
16092}
16093
16094fn cpp_clone_template_expression_iterative(
16095 expression: &CppTemplateExpression,
16096) -> CppTemplateExpression {
16097 CppTemplateExpression {
16098 text: expression.text.clone(),
16099 term: cpp_clone_template_term_iterative(&expression.term),
16100 }
16101}
16102
16103pub fn cpp_unify_template_term(
16104 pattern: &CppTemplateTerm,
16105 argument: &CppTemplateTerm,
16106 parameters: &HashSet<&str>,
16107 bindings: &mut HashMap<String, CppTemplateTerm>,
16108) -> bool {
16109 let mut work = vec![(pattern, argument)];
16110 while let Some((pattern, argument)) = work.pop() {
16111 match pattern {
16112 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16113 if let Some(bound) = bindings.get(name) {
16114 if !cpp_template_terms_equal(bound, argument) {
16115 return false;
16116 }
16117 } else {
16118 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16119 }
16120 }
16121 CppTemplateTerm::Atom {
16122 kind: pattern_kind,
16123 text: pattern_text,
16124 } => {
16125 if !matches!(
16126 argument,
16127 CppTemplateTerm::Atom { kind, text }
16128 if kind == pattern_kind && text == pattern_text
16129 ) {
16130 return false;
16131 }
16132 }
16133 CppTemplateTerm::Node {
16134 kind: pattern_kind,
16135 children: pattern_children,
16136 } => {
16137 let CppTemplateTerm::Node { kind, children } = argument else {
16138 return false;
16139 };
16140 if kind != pattern_kind || children.len() != pattern_children.len() {
16141 return false;
16142 }
16143 work.extend(pattern_children.iter().zip(children).rev());
16144 }
16145 CppTemplateTerm::Parameter(_) => return false,
16146 }
16147 }
16148 true
16149}
16150
16151fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16152 let mut work = vec![(left, right)];
16153 while let Some((left, right)) = work.pop() {
16154 match (left, right) {
16155 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16156 if left != right {
16157 return false;
16158 }
16159 }
16160 (
16161 CppTemplateTerm::Atom {
16162 kind: left_kind,
16163 text: left_text,
16164 },
16165 CppTemplateTerm::Atom {
16166 kind: right_kind,
16167 text: right_text,
16168 },
16169 ) => {
16170 if left_kind != right_kind || left_text != right_text {
16171 return false;
16172 }
16173 }
16174 (
16175 CppTemplateTerm::Node {
16176 kind: left_kind,
16177 children: left_children,
16178 },
16179 CppTemplateTerm::Node {
16180 kind: right_kind,
16181 children: right_children,
16182 },
16183 ) => {
16184 if left_kind != right_kind || left_children.len() != right_children.len() {
16185 return false;
16186 }
16187 work.extend(left_children.iter().zip(right_children).rev());
16188 }
16189 _ => return false,
16190 }
16191 }
16192 true
16193}
16194
16195pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16196 let mut components = Vec::new();
16197 let mut stack = vec![node];
16198 while let Some(current) = stack.pop() {
16199 match current.kind() {
16200 "identifier"
16201 | "field_identifier"
16202 | "namespace_identifier"
16203 | "type_identifier"
16204 | "operator_name"
16205 | "destructor_name" => components.push(current),
16206 "template_type" | "template_function" => {
16207 stack.push(current.child_by_field_name("name")?);
16208 }
16209 "dependent_name" => stack.push(current.named_child(0)?),
16210 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16211 stack.push(current.child_by_field_name("name")?);
16212 if let Some(scope) = current.child_by_field_name("scope") {
16213 stack.push(scope);
16214 }
16215 }
16216 "nested_namespace_specifier" => {
16217 for index in (0..current.named_child_count()).rev() {
16218 stack.push(current.named_child(index)?);
16219 }
16220 }
16221 _ => return None,
16222 }
16223 }
16224 Some(components)
16225}
16226
16227pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16228 node.child_by_field_name("scope").is_none()
16229 && node.child(0).is_some_and(|child| child.kind() == "::")
16230}
16231
16232fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16233 let mut namespaces = Vec::new();
16234 let mut current = node.parent();
16235 while let Some(parent) = current {
16236 if parent.kind() == "namespace_definition"
16237 && let Some(name) = parent.child_by_field_name("name")
16238 {
16239 let mut components = Vec::new();
16240 append_cpp_name_components(name, source, &mut components)?;
16241 namespaces.push(components);
16242 }
16243 current = parent.parent();
16244 }
16245 namespaces.reverse();
16246 Some(namespaces.into_iter().flatten().collect())
16247}
16248
16249fn indexed_namespace_path_is_recoverable(
16260 lexical_scope: &[String],
16261 indexed_owner_scope: &[String],
16262 explicit_owner_component_count: usize,
16263) -> bool {
16264 if lexical_scope.is_empty() {
16265 return explicit_owner_component_count > 1;
16266 }
16267 if lexical_scope.len() >= indexed_owner_scope.len() {
16268 return false;
16269 }
16270 let mut indexed = indexed_owner_scope.iter();
16271 lexical_scope
16272 .iter()
16273 .all(|component| indexed.any(|candidate| candidate == component))
16274}
16275
16276pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16277 let mut current = node.parent();
16278 while let Some(parent) = current {
16279 if parent.kind() == kind {
16280 return true;
16281 }
16282 current = parent.parent();
16283 }
16284 false
16285}
16286
16287pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16293 let mut current = Some(node);
16294 while let Some(candidate) = current {
16295 if candidate.kind() == "declaration" {
16296 let Some(type_node) = candidate.child_by_field_name("type") else {
16297 return false;
16298 };
16299 if !(type_node.start_byte() <= node.start_byte()
16300 && node.end_byte() <= type_node.end_byte())
16301 {
16302 return false;
16303 }
16304 let mut cursor = candidate.walk();
16305 return candidate.named_children(&mut cursor).any(|child| {
16306 child.kind() == "init_declarator"
16307 && child
16308 .child_by_field_name("value")
16309 .is_some_and(|value| value.kind() == "cast_expression")
16310 });
16311 }
16312 current = candidate.parent();
16313 }
16314 false
16315}
16316
16317#[derive(Clone, Copy, PartialEq, Eq)]
16318pub(crate) enum QualifiedAliasReferenceKind {
16319 Ordinary,
16320 ConstructorWithExpressionArgument,
16321 ExhaustiveTemplate,
16322}
16323
16324pub(crate) fn qualified_alias_reference_preserves_target(
16331 node: Node<'_>,
16332 target: &CodeUnit,
16333 analyzer: &CppGraphSource<'_>,
16334 visibility: &VisibilityIndex<'_>,
16335 file: &ProjectFile,
16336 source: &str,
16337) -> Option<QualifiedAliasReferenceKind> {
16338 if !matches!(
16339 node.kind(),
16340 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16341 ) {
16342 return None;
16343 }
16344 let components = cpp_type_name_components(node, source)?;
16345 let name = components.last()?;
16346 analyzer.type_alias_provider().and_then(|provider| {
16347 visibility
16348 .visible_identifier_candidates(file, name)
16349 .find_map(|candidate| {
16350 let proof = provider.is_type_alias(candidate)
16351 && canonical_cpp_scope_components(candidate) == components
16352 && visibility.external_type_candidate_visible_in_context(
16353 analyzer, file, candidate, node,
16354 )
16355 && match cpp_template_reference_arguments(node, source) {
16356 Some(arguments) => visibility.template_alias_arguments_preserve_target(
16357 analyzer, file, candidate, &arguments, target,
16358 ),
16359 None => visibility.structured_alias_primary_preserves_target(
16360 analyzer, file, candidate, target,
16361 ),
16362 };
16363 proof.then(|| {
16364 if cpp_template_reference_arguments(node, source).is_some()
16365 && visibility.is_exhaustive_same_fqn_type_declaration_family(
16366 analyzer, file, candidate,
16367 )
16368 {
16369 QualifiedAliasReferenceKind::ExhaustiveTemplate
16370 } else if qualified_alias_constructor_has_expression_argument(node)
16371 || qualified_alias_local_constructor_declaration(node)
16372 {
16373 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16374 } else {
16375 QualifiedAliasReferenceKind::Ordinary
16376 }
16377 })
16378 })
16379 })
16380}
16381
16382pub(crate) fn qualified_alias_reference_requires_terminal(
16383 reference: Option<QualifiedAliasReferenceKind>,
16384) -> bool {
16385 matches!(
16386 reference,
16387 Some(
16388 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16389 | QualifiedAliasReferenceKind::ExhaustiveTemplate
16390 )
16391 )
16392}
16393
16394fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16395 let Some(declaration) = node.parent().filter(|parent| {
16396 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16397 }) else {
16398 return false;
16399 };
16400 let mut cursor = declaration.walk();
16401 declaration.named_children(&mut cursor).any(|child| {
16402 child.kind() == "init_declarator"
16403 && child
16404 .child_by_field_name("value")
16405 .filter(|value| value.kind() == "argument_list")
16406 .is_some_and(|arguments| {
16407 let mut cursor = arguments.walk();
16408 arguments.named_children(&mut cursor).any(|argument| {
16409 let is_parameter = matches!(
16410 argument.kind(),
16411 "parameter_declaration" | "optional_parameter_declaration"
16412 );
16413 if is_parameter {
16414 argument
16415 .child_by_field_name("type")
16416 .is_some_and(|type_node| {
16417 type_node.kind() == "type_identifier"
16418 && argument.child_by_field_name("declarator").is_none()
16419 })
16420 } else {
16421 !argument.kind().ends_with("_literal")
16422 && !matches!(argument.kind(), "true" | "false" | "nullptr")
16423 }
16424 })
16425 })
16426 })
16427}
16428
16429fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16434 let Some(declaration) = node.parent().filter(|parent| {
16435 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16436 }) else {
16437 return false;
16438 };
16439 if declaration
16440 .parent()
16441 .is_none_or(|parent| parent.kind() != "compound_statement")
16442 {
16443 return false;
16444 }
16445 let mut cursor = declaration.walk();
16446 declaration
16447 .named_children(&mut cursor)
16448 .any(|child| child.kind() == "function_declarator")
16449}
16450
16451pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16457 loop {
16458 let next = match node.kind() {
16459 "qualified_identifier"
16460 | "scoped_identifier"
16461 | "template_method"
16462 | "template_function"
16463 | "template_type" => node.child_by_field_name("name"),
16464 "field_expression" => node.child_by_field_name("field"),
16465 _ => None,
16466 };
16467 let Some(next) = next else {
16468 return node;
16469 };
16470 node = next;
16471 }
16472}
16473
16474#[derive(Clone, Copy)]
16475pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16476 pub receiver: Node<'tree>,
16477 pub member: Node<'tree>,
16478 pub arity: usize,
16479}
16480
16481pub fn recovered_relational_template_member_call(
16489 field: Node<'_>,
16490) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16491 if field.kind() != "field_expression" {
16492 return None;
16493 }
16494 let receiver = field
16495 .child_by_field_name("argument")
16496 .or_else(|| field.child_by_field_name("object"))?;
16497 let member = field.child_by_field_name("field")?;
16498 let less = field.parent()?;
16499 if less.kind() != "binary_expression"
16500 || less.child_by_field_name("left") != Some(field)
16501 || less
16502 .child_by_field_name("operator")
16503 .is_none_or(|operator| operator.kind() != "<")
16504 || less.child_by_field_name("right").is_none()
16505 {
16506 return None;
16507 }
16508 let greater = less.parent()?;
16509 if greater.kind() != "binary_expression"
16510 || greater.child_by_field_name("left") != Some(less)
16511 || greater
16512 .child_by_field_name("operator")
16513 .is_none_or(|operator| operator.kind() != ">")
16514 {
16515 return None;
16516 }
16517 let arguments = greater.child_by_field_name("right")?;
16518 if arguments.kind() != "parenthesized_expression" {
16519 return None;
16520 }
16521 let arity = parenthesized_call_argument_arity(arguments)?;
16522 Some(RecoveredRelationalTemplateMemberCall {
16523 receiver,
16524 member,
16525 arity,
16526 })
16527}
16528
16529fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
16530 let expression = arguments.named_child(0)?;
16531 if expression.kind() != "comma_expression" {
16532 return Some(1);
16533 }
16534 let mut arity = 0usize;
16535 let mut stack = vec![expression];
16536 while let Some(node) = stack.pop() {
16537 if node.kind() == "comma_expression" {
16538 stack.push(node.child_by_field_name("right")?);
16539 stack.push(node.child_by_field_name("left")?);
16540 } else {
16541 arity += 1;
16542 }
16543 }
16544 Some(arity)
16545}
16546
16547pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
16550 while let Some(parent) = node.parent() {
16551 match parent.kind() {
16552 "call_expression" => {
16553 return parent
16554 .child_by_field_name("function")
16555 .or_else(|| parent.named_child(0))
16556 == Some(node);
16557 }
16558 "qualified_identifier"
16559 | "scoped_identifier"
16560 | "template_function"
16561 | "template_type"
16562 | "field_expression" => node = parent,
16563 _ => return false,
16564 }
16565 }
16566 false
16567}
16568
16569pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
16570 if is_call_callee_node(node) {
16571 function_terminal_node(node)
16572 } else {
16573 node
16574 }
16575}
16576
16577pub fn normalize_type_text(value: &str) -> String {
16578 strip_tag_type_prefix(
16579 normalize_cpp_whitespace(value)
16580 .trim_start_matches("const ")
16581 .trim_end_matches('*')
16582 .trim_end_matches('&')
16583 .trim(),
16584 )
16585 .to_string()
16586}
16587
16588fn strip_tag_type_prefix(value: &str) -> &str {
16589 let value = value.trim_start_matches("const ");
16590 value
16591 .strip_prefix("struct ")
16592 .or_else(|| value.strip_prefix("class "))
16593 .or_else(|| value.strip_prefix("enum "))
16594 .unwrap_or(value)
16595 .trim()
16596}
16597
16598pub fn normalize_reference_name(value: &str) -> Option<String> {
16599 let normalized = normalize_cpp_reference_text(value);
16600 (!normalized.is_empty()).then_some(normalized)
16601}
16602
16603pub fn normalize_cpp_reference_text(value: &str) -> String {
16604 let mut text = normalize_cpp_whitespace(value)
16605 .trim_start_matches("new ")
16606 .trim()
16607 .to_string();
16608 if let Some(index) = text.find(['(', '{']) {
16609 text.truncate(index);
16610 }
16611 if let Some(index) = text.find('<') {
16612 text.truncate(index);
16613 }
16614 let normalized = text
16615 .trim()
16616 .trim_start_matches("const ")
16617 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
16618 .trim_matches(':')
16619 .trim();
16620 strip_tag_type_prefix(normalized).to_string()
16621}
16622
16623pub fn cpp_name_for(unit: &CodeUnit) -> String {
16624 let short = unit.short_name().replace(['.', '$'], "::");
16625 if unit.package_name().is_empty() {
16626 short
16627 } else {
16628 format!("{}::{}", unit.package_name(), short)
16629 }
16630}
16631
16632fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
16636 let fq = unit.fq();
16637 if fq.is_empty() {
16638 return None;
16639 }
16640 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16641 Some(
16642 fq.segments()
16643 .iter()
16644 .map(|&segment| interner.resolve(segment).0)
16645 .collect::<Vec<_>>()
16646 .join("::"),
16647 )
16648}
16649
16650fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
16651 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
16652 || unit.fq().is_empty() && cpp_name_for(unit) == expected
16653}
16654
16655pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
16664 let fq = unit.fq();
16665 if !fq.is_empty() {
16666 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16667 let scope = fq
16668 .segments()
16669 .iter()
16670 .filter_map(|&segment| {
16671 let (text, kind) = interner.resolve(segment);
16672 matches!(
16673 kind,
16674 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
16675 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
16676 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
16677 )
16678 .then(|| text.to_string())
16679 })
16680 .collect();
16681 return scope;
16682 }
16683 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16684 brokk_bifrost_core::analyzer::Language::Cpp,
16685 &cpp_name_for(unit),
16686 )
16687}
16688
16689pub fn terminal_name(value: &str) -> &str {
16700 value
16701 .rsplit("::")
16702 .next()
16703 .unwrap_or(value)
16704 .rsplit(['.', '-', '>'])
16705 .next()
16706 .unwrap_or(value)
16707 .trim()
16708}
16709
16710pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
16711 terminal_name(&normalize_cpp_reference_text(value)) == expected
16712}
16713
16714pub fn name_matches_callable(value: &str, expected: &str) -> bool {
16715 name_matches_terminal(value, expected)
16716 || expected.starts_with("operator")
16717 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
16718}
16719
16720pub fn name_mentions(value: &str, expected: &str) -> bool {
16721 normalize_cpp_reference_text(value)
16722 .split("::")
16723 .any(|part| part == expected)
16724}
16725
16726pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
16727 let cpp_name = cpp_name_for(unit);
16728 if reference.contains("::") {
16729 return reference == cpp_name;
16730 }
16731 reference == cpp_name
16732 || terminal_name(reference) == unit.identifier()
16733 && (unit.package_name().is_empty() || reference == unit.identifier())
16734}
16735
16736pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
16737 match kind {
16738 TargetKind::Type
16739 | TargetKind::Constructor
16740 | TargetKind::Method
16741 | TargetKind::MemberField => true,
16742 TargetKind::FreeFunction => unit.is_function(),
16743 TargetKind::GlobalField => unit.is_field(),
16744 TargetKind::Macro => unit.is_macro(),
16745 }
16746}
16747
16748pub fn is_type_alias(unit: &CodeUnit) -> bool {
16749 unit.kind() == CodeUnitType::Field
16750 && unit.signature().is_some_and(|signature| {
16751 signature.starts_with("typedef ") || signature.starts_with("using ")
16752 })
16753}
16754
16755fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
16756 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
16757 let target_name = cpp_name_for(target);
16758 if normalized.contains("::") {
16759 return normalized == target_name;
16760 }
16761 if let Some(namespace) = alias.namespace.as_deref() {
16762 return namespace_prefixes(namespace)
16763 .into_iter()
16764 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
16765 }
16766 target.package_name().is_empty() && normalized == target.identifier()
16767}
16768
16769pub fn cpp_function_return_type_text(
16772 analyzer: &CppGraphSource<'_>,
16773 function: &CodeUnit,
16774) -> Option<String> {
16775 let metadata = analyzer.signature_metadata(function);
16776 if !metadata.is_empty() {
16777 let first = metadata.first()?.return_type_text()?;
16778 return metadata
16779 .iter()
16780 .all(|metadata| metadata.return_type_text() == Some(first))
16781 .then(|| first.to_string());
16782 }
16783 let signature = cpp_function_signature_text(analyzer, function)?;
16784 cpp_function_return_type_text_from_signature(&signature)
16785}
16786
16787fn cpp_function_signature_text(
16788 analyzer: &CppGraphSource<'_>,
16789 function: &CodeUnit,
16790) -> Option<String> {
16791 function
16792 .signature()
16793 .filter(|signature| signature.contains(function.identifier()))
16794 .map(str::to_string)
16795 .or_else(|| analyzer.signatures(function).first().cloned())
16796 .or_else(|| analyzer.get_source(function, false))
16797}
16798
16799fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
16800 let open = signature.find('(')?;
16801 let name_at = cpp_function_name_start(signature, open)?;
16802 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
16803 return Some(return_type);
16804 }
16805 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
16806 .split_whitespace()
16807 .filter(|token| {
16808 !matches!(
16809 *token,
16810 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
16811 )
16812 })
16813 .collect::<Vec<_>>()
16814 .join(" ");
16815 let type_text = type_text.trim();
16816 (!type_text.is_empty()).then(|| type_text.to_string())
16817}
16818
16819fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
16820 let before_parameters = &signature[..open];
16821 if let Some(operator_at) = before_parameters.rfind("operator") {
16822 let boundary = operator_at == 0
16823 || before_parameters[..operator_at]
16824 .chars()
16825 .next_back()
16826 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
16827 if boundary {
16828 return Some(operator_at);
16829 }
16830 }
16831 before_parameters
16832 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
16833 .map(|index| index + 1)
16834}
16835
16836fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
16837 let open = signature_from_name.find('(')?;
16838 let mut depth = 0i32;
16839 for (offset, ch) in signature_from_name[open..].char_indices() {
16840 match ch {
16841 '(' => depth += 1,
16842 ')' => {
16843 depth -= 1;
16844 if depth == 0 {
16845 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
16846 let arrow = rest.find("->")?;
16847 let return_type = rest[arrow + 2..].trim_start();
16848 let return_type = return_type
16849 .split(['{', ';'])
16850 .next()
16851 .unwrap_or(return_type)
16852 .trim();
16853 return (!return_type.is_empty()).then(|| return_type.to_string());
16854 }
16855 }
16856 _ => {}
16857 }
16858 }
16859 None
16860}
16861
16862fn cpp_strip_leading_template_clause(text: &str) -> &str {
16865 let trimmed = text.trim_start();
16866 let Some(rest) = trimmed.strip_prefix("template") else {
16867 return text;
16868 };
16869 let rest = rest.trim_start();
16870 if !rest.starts_with('<') {
16871 return text;
16872 }
16873 let mut depth = 0i32;
16874 for (offset, ch) in rest.char_indices() {
16875 match ch {
16876 '<' => depth += 1,
16877 '>' => {
16878 depth -= 1;
16879 if depth == 0 {
16880 return rest[offset + ch.len_utf8()..].trim_start();
16881 }
16882 }
16883 _ => {}
16884 }
16885 }
16886 text
16887}
16888
16889pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
16890 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
16900 namespace
16901 .strip_prefix("anonymous_namespace::")
16902 .unwrap_or(namespace)
16903 .to_string()
16904 })
16905}
16906
16907fn namespace_prefixes(namespace: &str) -> Vec<String> {
16908 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16914 brokk_bifrost_core::analyzer::Language::Cpp,
16915 namespace,
16916 );
16917 let mut prefixes = Vec::new();
16918 while !parts.is_empty() {
16919 prefixes.push(parts.join("::"));
16920 parts.pop();
16921 }
16922 prefixes
16923}
16924
16925fn nearest_namespace_candidates(
16926 candidates: Vec<CodeUnit>,
16927 normalized: &str,
16928 lexical_namespace: Option<&str>,
16929) -> Vec<CodeUnit> {
16930 if normalized.contains("::") {
16931 return candidates;
16932 }
16933 if let Some(namespace) = lexical_namespace {
16934 for prefix in namespace_prefixes(namespace) {
16935 let scoped = candidates
16936 .iter()
16937 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
16938 .cloned()
16939 .collect::<Vec<_>>();
16940 if !scoped.is_empty() {
16941 return scoped;
16942 }
16943 }
16944 }
16945 candidates
16946 .into_iter()
16947 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
16948 .collect()
16949}
16950
16951pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
16952 let mut namespaces = Vec::new();
16953 let mut current = node.parent();
16954 while let Some(parent) = current {
16955 if parent.kind() == "namespace_definition"
16956 && let Some(name) = parent.child_by_field_name("name")
16957 {
16958 let namespace = normalize_cpp_reference_text(node_text(name, source));
16959 if !namespace.is_empty() {
16960 namespaces.push(namespace);
16961 }
16962 }
16963 current = parent.parent();
16964 }
16965 if namespaces.is_empty() {
16966 None
16967 } else {
16968 namespaces.reverse();
16969 Some(namespaces.join("::"))
16970 }
16971}
16972
16973pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
16977 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
16978}
16979
16980fn type_owner_resolution(
16981 analyzer: &CppGraphSource<'_>,
16982 code_unit: &CodeUnit,
16983) -> Option<ResolvedTypeOwner> {
16984 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
16985}
16986
16987fn target_type_owner_resolution(
16988 analyzer: &CppGraphSource<'_>,
16989 code_unit: &CodeUnit,
16990) -> Option<ResolvedTypeOwner> {
16991 match type_owner_resolution(analyzer, code_unit) {
16992 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
16993 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
16994 }
16995}
16996
16997fn target_forward_owner_resolution(
17009 analyzer: &CppGraphSource<'_>,
17010 code_unit: &CodeUnit,
17011) -> Option<ResolvedTypeOwner> {
17012 if !code_unit.is_function() {
17013 return None;
17014 }
17015 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17021 let cpp = analyzer.cpp?;
17022 let mut visible_files = HashSet::default();
17023 collect_include_closure(
17024 analyzer,
17025 cpp.include_target_index(),
17026 code_unit.source(),
17027 &mut visible_files,
17028 None,
17029 );
17030 let candidates = analyzer.workspace_definitions().exact(&owner_name);
17031 let visible_candidates = candidates
17032 .iter()
17033 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17034 .cloned()
17035 .collect::<Vec<_>>();
17036 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17037 DirectOwnerResolution::UniqueFull(unit) => {
17038 return Some(ResolvedTypeOwner {
17039 unit,
17040 is_forward_declaration: false,
17041 });
17042 }
17043 DirectOwnerResolution::ForwardsOnly(forwards) => {
17044 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17045 unit: forwards.into_iter().next().unwrap(),
17046 is_forward_declaration: true,
17047 });
17048 }
17049 DirectOwnerResolution::Ambiguous => return None,
17050 DirectOwnerResolution::None => {}
17051 }
17052
17053 let candidates = candidates
17054 .into_iter()
17055 .filter(|candidate| candidate.is_class())
17056 .collect::<Vec<_>>();
17057 let (unit, is_forward_declaration) =
17058 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17059 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17060 DirectOwnerResolution::ForwardsOnly(forwards) => {
17061 (unique_logical_forward_owner(forwards)?, true)
17062 }
17063 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17064 };
17065 Some(ResolvedTypeOwner {
17066 unit,
17067 is_forward_declaration,
17068 })
17069}
17070
17071pub fn precise_parent_of(
17072 analyzer: &CppGraphSource<'_>,
17073 visibility: &VisibilityIndex<'_>,
17074 code_unit: &CodeUnit,
17075) -> Option<CodeUnit> {
17076 visibility.cached_precise_parent_of(analyzer, code_unit)
17077}
17078
17079fn precise_parent_resolution(
17080 analyzer: &CppGraphSource<'_>,
17081 code_unit: &CodeUnit,
17082) -> Option<ResolvedTypeOwner> {
17083 #[cfg(any(test, feature = "test-support"))]
17084 if let Some(cpp) = analyzer.cpp {
17085 cpp.record_cpp_parent_resolution_for_test();
17086 }
17087 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17088 return Some(ResolvedTypeOwner {
17089 unit,
17090 is_forward_declaration: false,
17091 });
17092 }
17093 let fallback = analyzer.parent_of(code_unit);
17094 if !code_unit.owner_is_type_scope() {
17095 return fallback.map(|unit| ResolvedTypeOwner {
17096 unit,
17097 is_forward_declaration: false,
17098 });
17099 }
17100 let owner_fq = code_unit
17101 .fq()
17102 .parent()
17103 .expect("a unit with an owner identifier has a structured parent");
17104 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17105 match same_source_owner(analyzer, code_unit, &owner_candidates) {
17106 DirectOwnerResolution::UniqueFull(owner) => {
17107 return Some(ResolvedTypeOwner {
17108 unit: owner,
17109 is_forward_declaration: false,
17110 });
17111 }
17112 DirectOwnerResolution::Ambiguous => return None,
17113 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17114 }
17115 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17116 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17117 unit: owner,
17118 is_forward_declaration: false,
17119 }),
17120 DirectOwnerResolution::Ambiguous => None,
17121 DirectOwnerResolution::ForwardsOnly(forwards) => {
17122 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17123 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17124 unit: owner,
17125 is_forward_declaration: false,
17126 }),
17127 FullOwnerResolution::None => {
17128 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17129 unit,
17130 is_forward_declaration: true,
17131 })
17132 }
17133 FullOwnerResolution::Ambiguous => None,
17134 }
17135 }
17136 DirectOwnerResolution::None => {
17137 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17138 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17139 unit: owner,
17140 is_forward_declaration: false,
17141 }),
17142 FullOwnerResolution::Ambiguous => None,
17143 FullOwnerResolution::None => fallback
17144 .filter(|parent| {
17145 parent.source() == code_unit.source()
17146 && parent.fq() == &owner_fq
17147 && (!parent.is_class()
17148 || cpp_class_declaration_strength(analyzer, parent)
17149 == CppClassDeclarationStrength::Full)
17150 })
17151 .map(|unit| ResolvedTypeOwner {
17152 unit,
17153 is_forward_declaration: false,
17154 }),
17155 }
17156 }
17157 }
17158}
17159
17160fn exact_structural_type_parent(
17161 analyzer: &CppGraphSource<'_>,
17162 code_unit: &CodeUnit,
17163) -> Option<CodeUnit> {
17164 if !code_unit.is_function() && !code_unit.is_field() {
17165 return None;
17166 }
17167 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
17169 let parent = cpp.structural_parent_of(code_unit)?;
17170 (!parent.is_module()
17171 && parent.source() == code_unit.source()
17172 && parent.package_name() == code_unit.package_name()
17173 && parent.short_name() == encoded_owner)
17174 .then_some(parent)
17175}
17176
17177fn same_source_owner(
17178 analyzer: &CppGraphSource<'_>,
17179 code_unit: &CodeUnit,
17180 owner_candidates: &[CodeUnit],
17181) -> DirectOwnerResolution {
17182 let candidates = owner_candidates
17183 .iter()
17184 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17185 .cloned()
17186 .collect::<Vec<_>>();
17187 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17188 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17189}
17190
17191fn visible_full_cpp_owner(
17192 analyzer: &CppGraphSource<'_>,
17193 code_unit: &CodeUnit,
17194 owner_candidates: &[CodeUnit],
17195) -> FullOwnerResolution {
17196 let Some(cpp) = analyzer.cpp else {
17197 return FullOwnerResolution::None;
17198 };
17199 let mut visible_files = HashSet::default();
17200 collect_include_closure(
17201 analyzer,
17202 cpp.include_target_index(),
17203 code_unit.source(),
17204 &mut visible_files,
17205 None,
17206 );
17207 let candidates = owner_candidates
17208 .iter()
17209 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17210 .cloned()
17211 .collect::<Vec<_>>();
17212 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17213 let mut full_definition = None;
17214 for candidate in candidates {
17215 match cpp_class_declaration_strength(analyzer, &candidate) {
17216 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17217 return FullOwnerResolution::Ambiguous;
17218 }
17219 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17220 CppClassDeclarationStrength::Forward => {}
17221 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17222 }
17223 }
17224 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17225}
17226
17227pub enum DirectOwnerResolution {
17228 None,
17229 ForwardsOnly(Vec<CodeUnit>),
17230 UniqueFull(CodeUnit),
17231 Ambiguous,
17232}
17233
17234enum FullOwnerResolution {
17235 None,
17236 Unique(CodeUnit),
17237 Ambiguous,
17238}
17239
17240#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17241pub enum CppClassDeclarationStrength {
17242 Full,
17243 Forward,
17244 Unknown,
17245}
17246
17247fn directly_included_owner(
17248 analyzer: &CppGraphSource<'_>,
17249 code_unit: &CodeUnit,
17250 owner_candidates: &[CodeUnit],
17251) -> DirectOwnerResolution {
17252 let Some(cpp) = analyzer.cpp else {
17253 return DirectOwnerResolution::None;
17254 };
17255 let imports = analyzer.import_statements(code_unit.source());
17256 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
17257 .into_iter()
17258 .flat_map(|include| {
17259 resolve_include_targets_with_index(
17260 code_unit.source(),
17261 &include,
17262 cpp.include_target_index(),
17263 )
17264 })
17265 .collect();
17266 let candidates = owner_candidates
17267 .iter()
17268 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17269 .cloned()
17270 .collect::<Vec<_>>();
17271 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17272 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17273}
17274
17275fn prefer_member_declaring_owners(
17276 analyzer: &CppGraphSource<'_>,
17277 member: &CodeUnit,
17278 candidates: Vec<CodeUnit>,
17279) -> Vec<CodeUnit> {
17280 let matching = candidates
17281 .iter()
17282 .filter(|owner| owner_declares_member(analyzer, owner, member))
17283 .cloned()
17284 .collect::<Vec<_>>();
17285 if matching.is_empty() {
17286 candidates
17287 } else {
17288 matching
17289 }
17290}
17291
17292fn owner_declares_member(
17293 analyzer: &CppGraphSource<'_>,
17294 owner: &CodeUnit,
17295 member: &CodeUnit,
17296) -> bool {
17297 analyzer.direct_children(owner).into_iter().any(|child| {
17298 child.kind() == member.kind()
17299 && child.identifier() == member.identifier()
17300 && child.signature() == member.signature()
17301 })
17302}
17303
17304fn classify_direct_owner_candidates(
17305 analyzer: &CppGraphSource<'_>,
17306 candidates: impl Iterator<Item = CodeUnit>,
17307) -> DirectOwnerResolution {
17308 collapse_owner_candidates(candidates.map(|candidate| {
17309 let strength = cpp_class_declaration_strength(analyzer, &candidate);
17310 (candidate, strength)
17311 }))
17312}
17313
17314pub fn collapse_owner_candidates(
17315 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17316) -> DirectOwnerResolution {
17317 let mut full_definition = None;
17318 let mut forwards = Vec::new();
17319 for (candidate, strength) in candidates {
17320 match strength {
17321 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17322 return DirectOwnerResolution::Ambiguous;
17323 }
17324 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17325 CppClassDeclarationStrength::Forward => forwards.push(candidate),
17326 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17327 }
17328 }
17329 if let Some(owner) = full_definition {
17330 DirectOwnerResolution::UniqueFull(owner)
17331 } else if !forwards.is_empty() {
17332 DirectOwnerResolution::ForwardsOnly(forwards)
17333 } else {
17334 DirectOwnerResolution::None
17335 }
17336}
17337
17338#[cfg(any(test, feature = "test-support"))]
17339pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17340 unique_logical_forward_owner(forwards)
17341}
17342
17343fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17344 let first = forwards.pop()?;
17345 forwards
17346 .iter()
17347 .all(|forward| same_logical_symbol(forward, &first))
17348 .then_some(first)
17349}
17350
17351pub fn cpp_class_declaration_strength(
17352 analyzer: &CppGraphSource<'_>,
17353 candidate: &CodeUnit,
17354) -> CppClassDeclarationStrength {
17355 let Some(cpp) = analyzer.cpp else {
17363 return uncached_cpp_class_declaration_strength(analyzer, candidate);
17364 };
17365 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17366 return strength;
17367 }
17368 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17369 cpp.cache_class_declaration_strength(candidate, strength);
17370 strength
17371}
17372
17373fn uncached_cpp_class_declaration_strength(
17374 analyzer: &CppGraphSource<'_>,
17375 candidate: &CodeUnit,
17376) -> CppClassDeclarationStrength {
17377 if let Some(cpp) = analyzer.cpp
17378 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17379 {
17380 return cpp_class_declaration_strength_in_tree(
17381 analyzer,
17382 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17383 candidate,
17384 prepared.source(),
17385 prepared.tree().root_node(),
17386 );
17387 }
17388 let Some(source) = analyzer.indexed_source(candidate.source()) else {
17389 return CppClassDeclarationStrength::Unknown;
17390 };
17391 #[cfg(any(test, feature = "test-support"))]
17392 if let Some(cpp) = analyzer.cpp {
17393 cpp.record_cpp_class_strength_parse_for_test();
17394 }
17395 let mut parser = Parser::new();
17396 if parser
17397 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17398 .is_err()
17399 {
17400 return CppClassDeclarationStrength::Unknown;
17401 }
17402 let Some(tree) = parser.parse(&source, None) else {
17403 return CppClassDeclarationStrength::Unknown;
17404 };
17405 let recovered_export_classes =
17408 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17409 cpp_class_declaration_strength_in_tree(
17410 analyzer,
17411 &recovered_export_classes,
17412 candidate,
17413 &source,
17414 tree.root_node(),
17415 )
17416}
17417
17418fn cpp_class_declaration_strength_in_tree(
17419 analyzer: &CppGraphSource<'_>,
17420 recovered_export_classes: &CppRecoveredExportClassIndex,
17421 candidate: &CodeUnit,
17422 source: &str,
17423 root: Node<'_>,
17424) -> CppClassDeclarationStrength {
17425 let ranges = analyzer.ranges(candidate);
17426 let mut saw_forward = false;
17427 for range in ranges {
17428 match recovered_class_body_at(
17431 recovered_export_classes,
17432 root,
17433 source,
17434 candidate.identifier(),
17435 &range,
17436 ) {
17437 Some(true) => return CppClassDeclarationStrength::Full,
17438 Some(false) => {
17439 saw_forward = true;
17440 continue;
17441 }
17442 None => {}
17443 }
17444 let covers_range_start = |node: &Node<'_>| {
17451 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17452 };
17453 let mut stack = Vec::new();
17454 if covers_range_start(&root) {
17455 stack.push(root);
17456 }
17457 while let Some(node) = stack.pop() {
17458 if node.start_byte() == range.start_byte
17459 && node.end_byte() == range.end_byte
17460 && matches!(
17461 node.kind(),
17462 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17463 )
17464 {
17465 if cpp_class_node_has_body(node) {
17466 return CppClassDeclarationStrength::Full;
17467 }
17468 saw_forward = true;
17469 }
17470 let mut cursor = node.walk();
17471 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17472 }
17473 }
17474 if saw_forward {
17475 CppClassDeclarationStrength::Forward
17476 } else {
17477 CppClassDeclarationStrength::Unknown
17478 }
17479}
17480
17481fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17482 node.child_by_field_name("body").is_some() || {
17483 let mut cursor = node.walk();
17484 node.named_children(&mut cursor).any(|child| {
17485 matches!(
17486 child.kind(),
17487 "declaration_list" | "field_declaration_list" | "enumerator_list"
17488 )
17489 })
17490 }
17491}
17492
17493#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17494enum CppCTagKind {
17495 Struct,
17496 Union,
17497}
17498
17499fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
17500 let declaration = analyzer.get_source(code_unit, false)?;
17501 let mut parser = Parser::new();
17502 parser
17503 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17504 .ok()?;
17505 let tree = parser.parse(&declaration, None)?;
17506 let mut stack = vec![tree.root_node()];
17507 while let Some(node) = stack.pop() {
17508 let kind = match node.kind() {
17509 "struct_specifier" => CppCTagKind::Struct,
17510 "union_specifier" => CppCTagKind::Union,
17511 _ => {
17512 let mut cursor = node.walk();
17513 stack.extend(node.named_children(&mut cursor));
17514 continue;
17515 }
17516 };
17517 if node
17518 .child_by_field_name("name")
17519 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
17520 {
17521 return Some(kind);
17522 }
17523 let mut cursor = node.walk();
17524 stack.extend(node.named_children(&mut cursor));
17525 }
17526 None
17527}
17528
17529pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17530 if !code_unit.owner_is_type_scope() {
17531 return None;
17532 }
17533 let owner_fq = code_unit.fq().parent()?;
17534 ctx.analyzer
17535 .workspace_definitions()
17536 .exact(&owner_fq)
17537 .into_iter()
17538 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
17539}
17540
17541pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17542 left.kind() == right.kind()
17543 && left.fq_name() == right.fq_name()
17544 && left.signature() == right.signature()
17545 && left.source() == right.source()
17546}
17547
17548pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17549 same_symbol(left, right) || same_logical_symbol(left, right)
17550}
17551
17552pub fn same_visible_global_field_symbol(
17553 analyzer: &CppGraphSource<'_>,
17554 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
17555 left: &CodeUnit,
17556 right: &CodeUnit,
17557) -> bool {
17558 if same_symbol(left, right) {
17559 return true;
17560 }
17561 if !same_logical_symbol(left, right) {
17562 return false;
17563 }
17564 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
17565 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
17566 {
17567 left.source() == right.source()
17568 } else {
17569 true
17570 }
17571}
17572
17573fn cpp_global_field_has_internal_linkage_cached(
17574 analyzer: &CppGraphSource<'_>,
17575 cache: &mut HashMap<CodeUnit, bool>,
17576 candidate: &CodeUnit,
17577) -> bool {
17578 if let Some(internal) = cache.get(candidate) {
17579 return *internal;
17580 }
17581 #[cfg(any(test, feature = "test-support"))]
17582 note_cpp_global_field_internal_linkage_classification_for_test();
17583 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
17584 cache.insert(candidate.clone(), internal);
17585 internal
17586}
17587
17588#[cfg(any(test, feature = "test-support"))]
17589thread_local! {
17590 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17591}
17592
17593#[cfg(any(test, feature = "test-support"))]
17594fn note_cpp_global_field_internal_linkage_classification_for_test() {
17595 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17596 count.set(count.get() + 1);
17597 });
17598}
17599
17600#[cfg(any(test, feature = "test-support"))]
17601pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
17602 body: impl FnOnce() -> T,
17603) -> (T, usize) {
17604 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17605 count.set(0);
17606 let result = body();
17607 let observed = count.get();
17608 count.set(0);
17609 (result, observed)
17610 })
17611}
17612
17613pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17614 left.kind() == right.kind()
17615 && left.fq_name() == right.fq_name()
17616 && left.signature() == right.signature()
17617}
17618
17619pub fn cpp_global_field_has_internal_linkage(
17620 analyzer: &CppGraphSource<'_>,
17621 candidate: &CodeUnit,
17622) -> bool {
17623 if !candidate.is_field() || candidate.short_name().contains('.') {
17624 return false;
17625 }
17626 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
17627 return false;
17628 };
17629 match local_linkage {
17630 CppFieldLinkage::Internal => true,
17631 CppFieldLinkage::External => false,
17632 CppFieldLinkage::InternalUnlessExternalPeer => {
17633 !cpp_global_field_linkage_peers(analyzer, candidate)
17634 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
17635 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
17636 }
17637 }
17638}
17639
17640fn cpp_global_field_linkage_peers<'a>(
17641 analyzer: &CppGraphSource<'a>,
17642 candidate: &'a CodeUnit,
17643) -> impl Iterator<Item = CodeUnit> + 'a {
17644 let name = candidate.fq().clone();
17645 analyzer
17646 .workspace_definitions()
17647 .exact(&name)
17648 .into_iter()
17649 .filter(move |peer| {
17650 if peer == candidate {
17651 return false;
17652 }
17653 #[cfg(any(test, feature = "test-support"))]
17654 note_cpp_global_field_linkage_peer_inspection_for_test();
17655 same_logical_symbol(peer, candidate)
17656 })
17657}
17658
17659#[cfg(any(test, feature = "test-support"))]
17660thread_local! {
17661 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17662}
17663
17664#[cfg(any(test, feature = "test-support"))]
17665fn note_cpp_global_field_linkage_peer_inspection_for_test() {
17666 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17667 count.set(count.get() + 1);
17668 });
17669}
17670
17671#[cfg(any(test, feature = "test-support"))]
17672pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
17673 body: impl FnOnce() -> T,
17674) -> (T, usize) {
17675 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17676 count.set(0);
17677 let result = body();
17678 let observed = count.get();
17679 count.set(0);
17680 (result, observed)
17681 })
17682}
17683
17684fn cpp_global_field_declaration_linkage(
17685 analyzer: &CppGraphSource<'_>,
17686 candidate: &CodeUnit,
17687) -> Option<CppFieldLinkage> {
17688 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
17689 return Some(linkage);
17690 }
17691 let cpp = analyzer.cpp?;
17692 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
17693 return cpp_global_field_declaration_linkage_in_tree(
17694 analyzer,
17695 candidate,
17696 prepared.source(),
17697 prepared.tree().root_node(),
17698 );
17699 }
17700 let source = analyzer.indexed_source(candidate.source())?;
17701 let mut parser = Parser::new();
17702 if parser
17703 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17704 .is_err()
17705 {
17706 return None;
17707 }
17708 let tree = parser.parse(&source, None)?;
17709 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
17710}
17711
17712fn cpp_global_field_declaration_linkage_in_tree(
17713 analyzer: &CppGraphSource<'_>,
17714 candidate: &CodeUnit,
17715 source: &str,
17716 root: Node<'_>,
17717) -> Option<CppFieldLinkage> {
17718 analyzer.ranges(candidate).iter().find_map(|range| {
17719 node_for_exact_range(root, range)
17720 .and_then(enclosing_cpp_field_declaration)
17721 .map(|declaration| {
17722 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
17724 })
17725 })
17726}
17727
17728fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
17729 loop {
17730 if matches!(node.kind(), "declaration" | "field_declaration") {
17731 return Some(node);
17732 }
17733 node = node.parent()?;
17734 }
17735}
17736
17737#[cfg(test)]
17738mod tests {
17739 #[test]
17740 fn issue_3089_statement_formal_at_end_of_replacement() {
17741 let parameters = vec!["handle".to_owned(), "block".to_owned()];
17742 for replacement in [
17743 "do { header_event_t* event; if ((handle)->active) block } while (0)",
17744 "do { header_event_t* event; block } while (0)",
17745 ] {
17746 assert!(
17747 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
17748 .is_some(),
17749 "{replacement}"
17750 );
17751 }
17752 }
17753 use super::*;
17754
17755 #[test]
17756 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
17757 let source = "int size(void) { return sizeof(((Payload))); }\n";
17758 let mut parser = Parser::new();
17759 parser
17760 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17761 .expect("C++ grammar");
17762 let tree = parser.parse(source, None).expect("fixture tree");
17763 let start = source.find("Payload").expect("sizeof operand");
17764 let node = tree
17765 .root_node()
17766 .named_descendant_for_byte_range(start, start + "Payload".len())
17767 .expect("focused operand");
17768 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
17769 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
17770
17771 assert_eq!(node.kind(), "identifier");
17772 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
17773 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
17774 }
17775
17776 fn parse_cpp(source: &str) -> Tree {
17777 let mut parser = Parser::new();
17778 parser
17779 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17780 .expect("C++ grammar");
17781 parser.parse(source, None).expect("fixture tree")
17782 }
17783
17784 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
17785 let start = source.find(needle).expect("fixture needle");
17786 tree.root_node()
17787 .named_descendant_for_byte_range(start, start + needle.len())
17788 .expect("node at needle")
17789 }
17790
17791 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
17792 let mut parser = Parser::new();
17793 parser
17794 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17795 .expect("C++ grammar");
17796 let tree = parser.parse(source, None).expect("fixture tree");
17797 PreparedSyntaxTree::new(
17798 PreparedSyntaxSource::Exact(Arc::from(source)),
17799 tree,
17800 compute_line_starts(source),
17801 LanguageDialect::Standard(Language::Cpp),
17802 PreparedSourceOrigin::Disk,
17803 None,
17804 )
17805 }
17806
17807 fn unresolved_include_before(source: &str, reference: &str) -> bool {
17808 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
17809 let prepared = prepared_cpp(source);
17810 let facts = collect_structured_include_facts(&prepared);
17811 let include_targets = IncludeTargetIndex::build([&file]);
17812 has_unresolved_include_visible_before_in_prepared(
17813 &file,
17814 &prepared,
17815 &include_targets,
17816 &facts,
17817 source.find(reference).expect("reference fixture"),
17818 )
17819 }
17820
17821 #[test]
17822 fn unresolved_include_before_reference_is_visible() {
17823 let source = "#include \"missing.h\"\nint use = Missing;\n";
17824 assert!(unresolved_include_before(source, "Missing"));
17825 }
17826
17827 #[test]
17828 fn unresolved_include_after_reference_is_not_visible() {
17829 let source = "int use = Missing;\n#include \"missing.h\"\n";
17830 assert!(!unresolved_include_before(source, "Missing"));
17831 }
17832
17833 #[test]
17834 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
17835 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
17836 assert!(!unresolved_include_before(source, "Missing"));
17837 }
17838
17839 #[test]
17840 fn unresolved_include_in_current_branch_is_visible() {
17841 let source =
17842 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
17843 assert!(unresolved_include_before(source, "Missing"));
17844 }
17845
17846 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
17851namespace matchers {
17852 namespace detail {
17853 class API [[nodiscard]] First {
17854 public:
17855 int value() const { return count_ + 1; }
17856 private:
17857 int count_;
17858 };
17859 class API [[nodiscard]] Second {
17860 public:
17861 int value() const { return count_ + 2; }
17862 private:
17863 int count_;
17864 };
17865 } // namespace detail
17866
17867 template <typename T>
17868 void tail_function(MatcherBase<T> const& value);
17869
17870 class TailClass {};
17871} // namespace matchers
17872} // namespace app
17873
17874struct AfterAll {};
17875"#;
17876
17877 #[test]
17878 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
17879 let source = STOLEN_BRACE_CASCADE;
17880 let tree = parse_cpp(source);
17881 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17882
17883 let tail_class = named_node_at(&tree, source, "TailClass");
17884 assert!(
17885 !has_ancestor_kind(tail_class, "namespace_definition"),
17886 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
17887 );
17888 let displaced = named_node_at(&tree, source, "Second");
17889 assert_eq!(
17890 enclosing_namespace_components(displaced, source),
17891 Some(vec!["app".to_string(), "matchers".to_string()]),
17892 "the fixture must displace the second class out of detail"
17893 );
17894
17895 let components = |needle: &str| {
17896 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
17897 };
17898 assert_eq!(components("First"), ["app", "matchers", "detail"]);
17899 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
17900 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
17901 assert_eq!(components("tail_function"), ["app", "matchers"]);
17902 assert_eq!(components("TailClass"), ["app", "matchers"]);
17903 assert!(components("AfterAll").is_empty());
17904 }
17905
17906 #[test]
17907 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
17908 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
17909 let tree = parse_cpp(clean);
17910 assert!(!tree.root_node().has_error());
17911 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
17912
17913 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
17916 let tree = parse_cpp(damaged);
17917 assert!(tree.root_node().has_error());
17918 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
17919 assert_eq!(
17920 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
17921 ["a"]
17922 );
17923 }
17924
17925 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
17932
17933 class Target {
17934 int value_;
17935 };
17936
17937 template <typename T>
17938 class Holder {
17939 public:
17940 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
17941
17942#define HOLDER_DEFINE_OP( id, op ) \
17943 template <typename U> \
17944 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
17945 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
17946 return Target{}; \
17947 }
17948
17949 HOLDER_DEFINE_OP( equal, == )
17950#undef HOLDER_DEFINE_OP
17951 T m_lhs;
17952 };
17953
17954 class Tail {};
17955}
17956"#;
17957
17958 #[test]
17959 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
17960 let source = COLLAPSED_NAMESPACE_HEAD;
17961 let tree = parse_cpp(source);
17962 let target = named_node_at(&tree, source, "class Target");
17963
17964 assert!(
17965 !has_ancestor_kind(target, "namespace_definition"),
17966 "the fixture must reproduce the collapse: the class has no namespace ancestor"
17967 );
17968 let head = target.parent().expect("the collapsed namespace envelope");
17969 assert_eq!(
17970 head.kind(),
17971 "ERROR",
17972 "the fixture must keep the namespace head in an ERROR node"
17973 );
17974
17975 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17976 assert_eq!(
17977 index.enclosing_namespace_components(target, source),
17978 ["app"]
17979 );
17980 }
17981
17982 #[test]
17983 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
17984 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
17985 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
17986 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
17987 assert!(indexed_namespace_path_is_recoverable(
17988 &["cache".to_string()],
17989 &indexed,
17990 1,
17991 ));
17992 }
17993
17994 #[test]
17995 fn sort_lookup_units_totally_orders_every_identity_field() {
17996 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
17997 let base = CodeUnit::with_signature(
17998 file.clone(),
17999 CodeUnitType::Function,
18000 "scope",
18001 "value",
18002 Some("()".to_string()),
18003 false,
18004 );
18005 let different_kind = CodeUnit::with_signature(
18006 file.clone(),
18007 CodeUnitType::Field,
18008 "scope",
18009 "value",
18010 Some("()".to_string()),
18011 false,
18012 );
18013 let synthetic = base.with_synthetic(true);
18014
18015 let interner = segment_interner();
18016 let mut member_fq = FqName::new();
18017 member_fq.push(interner.intern("scope", SegmentKind::Package));
18018 member_fq.push(interner.intern("value", SegmentKind::Member));
18019 let different_package_boundary = CodeUnit::from_fq(
18020 file.clone(),
18021 CodeUnitType::Function,
18022 member_fq,
18023 0,
18024 Some("()".to_string()),
18025 false,
18026 );
18027
18028 let mut unknown_fq = FqName::new();
18029 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18030 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18031 let different_segment_kind = CodeUnit::from_fq(
18032 file,
18033 CodeUnitType::Function,
18034 unknown_fq,
18035 1,
18036 Some("()".to_string()),
18037 false,
18038 );
18039
18040 let input = vec![
18041 base,
18042 different_kind,
18043 synthetic,
18044 different_package_boundary,
18045 different_segment_kind,
18046 ];
18047 let mut expected = input.clone();
18048 sort_lookup_units(&mut expected);
18049 assert!(expected.windows(2).all(|pair| {
18050 let mut ordered = pair.to_vec();
18051 sort_lookup_units(&mut ordered);
18052 ordered == pair && pair[0] != pair[1]
18053 }));
18054
18055 let mut reversed = input.clone();
18056 reversed.reverse();
18057 sort_lookup_units(&mut reversed);
18058 assert_eq!(reversed, expected);
18059
18060 let mut rotated = input;
18061 rotated.rotate_left(2);
18062 sort_lookup_units(&mut rotated);
18063 assert_eq!(rotated, expected);
18064 }
18065
18066 #[test]
18067 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18068 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";
18069 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18070 let parse = |source: &str| {
18071 let mut parser = Parser::new();
18072 parser
18073 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18074 .expect("C++ grammar");
18075 parser.parse(source, None).expect("fixture tree")
18076 };
18077
18078 let tree = parse(damaged);
18079 let root = tree.root_node();
18080 let target = damaged.find("target").expect("target byte");
18081 let declaration = root
18082 .descendant_for_byte_range(target, target + "target".len())
18083 .and_then(|mut node| {
18084 loop {
18085 if node.kind() == "declaration" {
18086 break Some(node);
18087 }
18088 node = node.parent()?;
18089 }
18090 })
18091 .expect("declaration after the displaced terminator");
18092 let conditional = declaration
18093 .parent()
18094 .filter(|node| node.kind() == "preproc_ifdef")
18095 .expect("damaged inner conditional");
18096 let outer = conditional
18097 .parent()
18098 .filter(|node| node.kind() == "preproc_ifdef")
18099 .expect("ordinary outer include guard");
18100 let terminator = cpp_displaced_preprocessor_terminator(conditional)
18101 .expect("structured displaced #endif");
18102 assert_eq!(node_text(terminator, damaged), "#endif");
18103 assert!(terminator.end_byte() <= declaration.start_byte());
18104 assert!(!preprocessor_conditional_contains_descendant(
18105 conditional,
18106 declaration
18107 ));
18108 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18109 assert!(preprocessor_conditional_contains_descendant(
18110 outer,
18111 declaration
18112 ));
18113
18114 let tree = parse(guarded);
18115 let conditional = tree
18116 .root_node()
18117 .named_child(0)
18118 .filter(|node| node.kind() == "preproc_ifdef")
18119 .expect("ordinary conditional");
18120 let declaration = conditional
18121 .named_children(&mut conditional.walk())
18122 .find(|node| node.kind() == "declaration")
18123 .expect("guarded declaration");
18124 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18125 assert!(preprocessor_conditional_contains_descendant(
18126 conditional,
18127 declaration
18128 ));
18129
18130 let damaged_alternative = format!(
18131 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18132 "UNUSED(value)\n".repeat(64)
18133 );
18134 let tree = parse(&damaged_alternative);
18135 let conditional = tree
18136 .root_node()
18137 .named_child(0)
18138 .filter(|node| node.kind() == "preproc_ifdef")
18139 .expect("outer conditional with an alternative");
18140 assert!(conditional.has_error());
18141 assert!(conditional.child_by_field_name("alternative").is_some());
18142 assert!(
18143 conditional
18144 .child(conditional.child_count() - 1)
18145 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18146 );
18147 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18148
18149 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";
18150 let tree = parse(split_declaration);
18151 let root = tree.root_node();
18152 let conditional = root
18153 .named_children(&mut root.walk())
18154 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18155 .expect("split declaration conditional");
18156 let target = split_declaration
18157 .find("static int target")
18158 .expect("target byte");
18159 let boundary =
18160 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18161 assert!(boundary.end_byte <= target, "{boundary:?}");
18162 assert_eq!(boundary.end_line, 9, "{boundary:?}");
18163 let target_node = root
18164 .descendant_for_byte_range(target, target + "static".len())
18165 .expect("target node");
18166 assert!(!preprocessor_conditional_contains_descendant(
18167 conditional,
18168 target_node
18169 ));
18170 }
18171
18172 #[test]
18173 fn fragmented_reference_guard_is_recovered() {
18174 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";
18175 let mut parser = Parser::new();
18176 parser
18177 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18178 .expect("C++ grammar");
18179 let tree = parser.parse(source, None).expect("fixture tree");
18180 let start = source.rfind("helper").expect("reference byte");
18181 let node = tree
18182 .root_node()
18183 .descendant_for_byte_range(start, start + "helper".len())
18184 .expect("reference node");
18185 let mut expected = HashSet::default();
18186 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18187 vec![
18188 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18189 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18190 ],
18191 )));
18192 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18193 }
18194
18195 #[test]
18196 fn expression_defined_and_ifndef_guards_are_incompatible() {
18197 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18198 let mut parser = Parser::new();
18199 parser
18200 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18201 .expect("C++ grammar");
18202 let tree = parser.parse(source, None).expect("fixture tree");
18203 let root = tree.root_node();
18204 let selected_start = source.find("selected").expect("selected declaration");
18205 let rejected_start = source.find("rejected").expect("rejected declaration");
18206 let selected = root
18207 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18208 .expect("selected node");
18209 let rejected = root
18210 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18211 .expect("rejected node");
18212 let selected_guards =
18213 preprocessor_guard_environment(selected, source).expect("selected guards");
18214 let rejected_guards =
18215 preprocessor_guard_environment(rejected, source).expect("rejected guards");
18216
18217 assert!(
18218 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18219 "opposite spellings of one macro guard must contradict"
18220 );
18221 }
18222
18223 #[test]
18224 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18225 let source = r#"#ifdef _WIN32
18226#if defined(__cplusplus)
18227extern "C"
18228#endif
18229int platform_api(void);
18230#endif
18231
18232#ifdef _WIN32
18233static int entropy_target(void) { return 0; }
18234#else
18235#ifdef HAVE_COMMON_RANDOM
18236static int other_target(void) { return 0; }
18237#elif defined(HAVE_GETENTROPY)
18238static int entropy_target(void) { return 1; }
18239static int use_entropy(void) { return entropy_target(); }
18240#endif
18241#endif
18242"#;
18243 let mut parser = Parser::new();
18244 parser
18245 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18246 .expect("C++ grammar");
18247 let tree = parser.parse(source, None).expect("fixture tree");
18248 let start = source.rfind("entropy_target()").expect("reference");
18249 let node = tree
18250 .root_node()
18251 .descendant_for_byte_range(start, start + "entropy_target".len())
18252 .expect("reference node");
18253 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18254 assert!(
18255 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18256 "{guards:#?}"
18257 );
18258 assert!(
18259 guards.contains(&PreprocessorGuard::Undefined(
18260 "HAVE_COMMON_RANDOM".to_string()
18261 )),
18262 "{guards:#?}"
18263 );
18264 assert!(
18265 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18266 "{guards:#?}"
18267 );
18268 assert!(
18269 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18270 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18271 );
18272 }
18273
18274 #[test]
18275 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18276 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";
18277 let mut parser = Parser::new();
18278 parser
18279 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18280 .expect("C++ grammar");
18281 let tree = parser.parse(source, None).expect("fixture tree");
18282 let root = tree.root_node();
18283 let node_at = |text: &str, start: usize| {
18284 root.descendant_for_byte_range(start, start + text.len())
18285 .expect("token node")
18286 };
18287
18288 let key_start = source.find("case KEY").expect("case label") + "case ".len();
18289 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18290 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18291 assert!(!is_ordinary_macro_reference_node(node_at(
18292 "ENABLE_KEYS",
18293 guard_start,
18294 )));
18295 }
18296
18297 #[test]
18298 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18299 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";
18300 let mut parser = Parser::new();
18301 parser
18302 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18303 .expect("C++ grammar");
18304 let tree = parser.parse(source, None).expect("fixture tree");
18305 let root = tree.root_node();
18306 let definition_start = source.find("target(void)").expect("definition");
18307 let reference_start = source.rfind("target()").expect("reference");
18308 let definition = root
18309 .descendant_for_byte_range(definition_start, definition_start + "target".len())
18310 .expect("definition node");
18311 let reference = root
18312 .descendant_for_byte_range(reference_start, reference_start + "target".len())
18313 .expect("reference node");
18314 let required =
18315 preprocessor_guard_environment(definition, source).expect("definition guard");
18316 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
18317 assert!(guard_requirements_hold_at_reference(
18318 &required,
18319 Some(&active)
18320 ));
18321 }
18322
18323 #[test]
18324 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
18325 let source = "g_autoptr(FuChunkArray) self = make_array();";
18326 let mut parser = Parser::new();
18327 parser
18328 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18329 .expect("C++ grammar");
18330 let tree = parser.parse(source, None).expect("fixture tree");
18331 let statement = tree.root_node().named_child(0).expect("statement");
18332 let binding =
18333 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
18334 assert_eq!(binding.name, "self");
18335 assert_eq!(binding.type_name, "FuChunkArray");
18336 assert_eq!(binding.pointer_depth, 1);
18337
18338 let near_miss = "holder(FuChunkArray) self = make_array();";
18339 let tree = parser.parse(near_miss, None).expect("near-miss tree");
18340 let statement = tree.root_node().named_child(0).expect("statement");
18341 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
18342 }
18343
18344 #[test]
18345 fn boolean_guard_normalization_proves_equivalence_and_implication() {
18346 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
18347 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
18348 let negated_windows_branch =
18349 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
18350 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
18351 assert_eq!(negated_windows_branch, portable);
18352
18353 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
18354 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
18355 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
18356 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
18357 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
18358 assert!(fallback_branch.implies(&fallback_declaration));
18359 assert!(
18360 BooleanGuardExpression::Truthy("FEATURE".to_string())
18361 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
18362 );
18363 assert!(
18364 BooleanGuardExpression::Undefined("FEATURE".to_string())
18365 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
18366 );
18367 assert!(
18368 !BooleanGuardExpression::Defined("FEATURE".to_string())
18369 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
18370 );
18371 assert!(!fallback_declaration.implies(&fallback_branch));
18372 }
18373
18374 #[test]
18375 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
18376 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";
18377 let mut parser = Parser::new();
18378 parser
18379 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18380 .expect("C++ grammar");
18381 let tree = parser.parse(source, None).expect("fixture tree");
18382 let root = tree.root_node();
18383 let call = |marker: &str| {
18384 let start = source.find(marker).expect("call marker");
18385 let mut node = root
18386 .descendant_for_byte_range(start, start + "helper".len())
18387 .expect("call name node");
18388 loop {
18389 if node.kind() == "call_expression" {
18390 break node;
18391 }
18392 node = node.parent().expect("call expression ancestor");
18393 }
18394 };
18395 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
18396 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
18397 let keyword_call = call("helper(NULL, template); /* bound */");
18398 let keyword_arguments = keyword_call
18399 .child_by_field_name("arguments")
18400 .expect("keyword argument list");
18401 assert_eq!(
18402 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
18403 1
18404 );
18405 assert_eq!(
18406 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
18407 0
18408 );
18409
18410 let unbound_call = call("helper(NULL, template); /* unbound */");
18411 let unbound_arguments = unbound_call
18412 .child_by_field_name("arguments")
18413 .expect("unbound argument list");
18414 assert_eq!(
18415 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
18416 0
18417 );
18418 }
18419
18420 #[test]
18421 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
18422 let source = r#"#define MAKE(type) type *value
18423MAKE(int *);
18424typedef struct Item Item;
18425struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
18426void gen_op_movl(void *s, int first, int second) { }
18427const char *strZ(const char *value) { return value; }
18428int body(void *s) {
18429 MAKE(int *);
18430 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
18431 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
18432 execvp(strZ(value), UNCONSTIFY(char **, args));
18433}
18434 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
18435 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
18436int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
18437 if (d->units) {
18438 switch (target->nodetype) {
18439 case 1:
18440 case 2:
18441 break;
18442 default:
18443 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
18444 }
18445 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
18446 lysdict_remove(ctx, ((struct Item *)target)->units);
18447 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
18448 }
18449 return 0;
18450 }
18451STATIC EFI_STATUS Encode () { return 0; }
18452"#;
18453 let tree = parse_cpp(source);
18454 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
18455 let top_macro = tree
18456 .root_node()
18457 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
18458 .expect("top macro node");
18459 let body_macro_start = source
18460 .match_indices("MAKE(int *);")
18461 .nth(1)
18462 .expect("body macro")
18463 .0;
18464 let body_macro = tree
18465 .root_node()
18466 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
18467 .expect("body macro node");
18468 let function_call_start = source
18469 .find("gen_op_movl(s, offsetof(CPUX86State")
18470 .expect("function call");
18471 let function_call = tree
18472 .root_node()
18473 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
18474 .expect("function call node");
18475 let strz_start = source.find("strZ(value)").expect("nested function call");
18476 let strz = tree
18477 .root_node()
18478 .named_descendant_for_byte_range(strz_start, strz_start + 4)
18479 .expect("nested function call node");
18480 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
18481 let recovered_call = tree
18482 .root_node()
18483 .named_descendant_for_byte_range(
18484 recovered_call_start,
18485 recovered_call_start + "lysdict_remove".len(),
18486 )
18487 .expect("recovered call node");
18488 let binder_start = source.find("Encode").expect("binder");
18489 let binder = tree
18490 .root_node()
18491 .named_descendant_for_byte_range(binder_start, binder_start + 6)
18492 .expect("binder node");
18493
18494 assert!(recovered_c_function_declarator_invocation(top_macro));
18495 assert!(recovered_c_function_declarator_invocation(body_macro));
18496 assert!(recovered_c_function_declarator_invocation(function_call));
18497 assert!(recovered_c_function_declarator_invocation(strz));
18498 assert!(recovered_c_function_declarator_invocation(recovered_call));
18499 assert!(!recovered_c_function_declarator_invocation(binder));
18500 }
18501
18502 #[test]
18503 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
18504 let source = r#"typedef int krb5_context;
18505int helper(int first, int second) { return first + second; }
18506static krb5_context ctx;
18507int main(int argc, char **argv) {
18508 int ccinitial;
18509 const char *collection_name, *typename;
18510 typename = helper(ctx, ccinitial);
18511 return 0;
18512}
18513"#;
18514 let tree = parse_cpp(source);
18515 let ctx = tree
18516 .root_node()
18517 .descendant_for_byte_range(
18518 source.find("ctx, ccinitial").expect("ctx argument"),
18519 source.find("ctx, ccinitial").expect("ctx argument") + 3,
18520 )
18521 .expect("ctx node");
18522 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
18523 let ccinitial = tree
18524 .root_node()
18525 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
18526 .expect("sibling node");
18527 let typename = named_node_at(&tree, source, "typename = helper");
18528 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
18529
18530 assert_eq!(ctx.kind(), "identifier");
18531 assert!(recovered_c_parenthesized_declarator_reference(ctx));
18532 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
18533 assert!(!recovered_c_parenthesized_declarator_reference(typename));
18534 assert!(!recovered_c_parenthesized_declarator_reference(helper));
18535 }
18536
18537 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
18538 let mut parser = Parser::new();
18539 parser
18540 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18541 .expect("C++ grammar");
18542 let tree = parser.parse(source, None).expect("C++ fixture tree");
18543 let mut stack = vec![tree.root_node()];
18544 while let Some(node) = stack.pop() {
18545 if node.kind() == "enum_specifier" {
18546 return flattened_macro_namespace_components(node, source);
18547 }
18548 let mut cursor = node.walk();
18549 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
18550 stack.extend(children.into_iter().rev());
18551 }
18552 None
18553 }
18554
18555 #[test]
18556 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
18557 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18558namespace detail
18559{
18560enum class value_t { null };
18561}
18562NLOHMANN_JSON_NAMESPACE_END
18563NLOHMANN_JSON_NAMESPACE_BEGIN
18564namespace next
18565{
18566struct next_type {};
18567}
18568NLOHMANN_JSON_NAMESPACE_END
18569"#;
18570 assert_eq!(
18571 first_enum_flattened_namespace(complete),
18572 Some(vec!["detail".to_string()])
18573 );
18574
18575 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
18576 assert_eq!(
18577 first_enum_flattened_namespace(&stale_end),
18578 Some(vec!["detail".to_string()]),
18579 "a stale end marker before the begin marker must not replace the intended namespace"
18580 );
18581
18582 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18583namespace detail
18584{
18585enum class value_t { null };
18586}
18587struct next_type {};
18588"#;
18589 assert_eq!(first_enum_flattened_namespace(incomplete), None);
18590 }
18591}
18592
18593#[cfg(test)]
18609mod lookup_order_properties {
18610 use super::*;
18611 use proptest::prelude::*;
18612
18613 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
18617 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
18618 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
18622 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
18623 const KINDS: [CodeUnitType; 6] = [
18624 CodeUnitType::Class,
18625 CodeUnitType::Function,
18626 CodeUnitType::Field,
18627 CodeUnitType::Module,
18628 CodeUnitType::Macro,
18629 CodeUnitType::FileScope,
18630 ];
18631
18632 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18635 enum ProbedOrder {
18636 Before,
18637 Tied,
18638 After,
18639 Contradictory,
18642 }
18643
18644 impl ProbedOrder {
18645 fn mirror(self) -> Self {
18646 match self {
18647 ProbedOrder::Before => ProbedOrder::After,
18648 ProbedOrder::After => ProbedOrder::Before,
18649 other => other,
18650 }
18651 }
18652
18653 fn signum(self) -> i8 {
18655 match self {
18656 ProbedOrder::Before => -1,
18657 ProbedOrder::Tied => 0,
18658 ProbedOrder::After => 1,
18659 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
18660 }
18661 }
18662 }
18663
18664 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
18672 if left == right {
18673 return ProbedOrder::Tied;
18676 }
18677 let mut forward = vec![left.clone(), right.clone()];
18678 sort_lookup_units(&mut forward);
18679 let mut backward = vec![right.clone(), left.clone()];
18680 sort_lookup_units(&mut backward);
18681 let left_first = backward[0] == *left;
18682 let right_first = forward[0] == *right;
18683 match (left_first, right_first) {
18684 (true, true) => ProbedOrder::Contradictory,
18685 (true, false) => ProbedOrder::Before,
18686 (false, true) => ProbedOrder::After,
18687 (false, false) => ProbedOrder::Tied,
18688 }
18689 }
18690
18691 fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
18694 let interner = segment_interner();
18695 unit.fq()
18696 .segments()
18697 .iter()
18698 .map(|&id| {
18699 let (text, kind) = interner.resolve(id);
18700 (kind.name(), text)
18701 })
18702 .collect()
18703 }
18704
18705 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
18706 (
18707 0..ROOT_NAMES.len(),
18708 0..REL_PATHS.len(),
18709 0..KINDS.len(),
18710 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
18711 0..3usize,
18712 0..SIGNATURES.len(),
18713 any::<bool>(),
18714 )
18715 .prop_map(
18716 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
18717 let source = ProjectFile::new(
18718 std::env::temp_dir().join(ROOT_NAMES[root]),
18719 REL_PATHS[rel_path],
18720 );
18721 let interner = segment_interner();
18722 let mut fq = FqName::new();
18723 for (atom, segment_kind) in &segments {
18724 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
18725 }
18726 let package_segment_count = package_prefix % fq.len();
18728 CodeUnit::from_fq(
18729 source,
18730 KINDS[kind],
18731 fq,
18732 package_segment_count,
18733 SIGNATURES[signature].map(str::to_string),
18734 synthetic,
18735 )
18736 },
18737 )
18738 }
18739
18740 proptest! {
18741 #![proptest_config(ProptestConfig::with_cases(256))]
18742
18743 #[test]
18746 fn lookup_order_is_reflexive_and_dual(
18747 left in code_unit_strategy(),
18748 right in code_unit_strategy(),
18749 ) {
18750 prop_assert_eq!(
18751 probe_order(&left, &left),
18752 ProbedOrder::Tied,
18753 "a unit must tie with itself: {:?}",
18754 left
18755 );
18756 let forward = probe_order(&left, &right);
18757 prop_assert_ne!(
18758 forward,
18759 ProbedOrder::Contradictory,
18760 "comparator put each of these strictly first: left={:?} right={:?}",
18761 left,
18762 right
18763 );
18764 prop_assert_eq!(
18765 probe_order(&right, &left),
18766 forward.mirror(),
18767 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
18768 left,
18769 right
18770 );
18771 }
18772
18773 #[test]
18775 fn lookup_order_is_transitive(
18776 a in code_unit_strategy(),
18777 b in code_unit_strategy(),
18778 c in code_unit_strategy(),
18779 ) {
18780 let ab = probe_order(&a, &b);
18781 let bc = probe_order(&b, &c);
18782 let ac = probe_order(&a, &c);
18783 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
18784 prop_assert_ne!(
18785 probed,
18786 ProbedOrder::Contradictory,
18787 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
18788 pair,
18789 a,
18790 b,
18791 c
18792 );
18793 }
18794 if ab.signum() <= 0 && bc.signum() <= 0 {
18795 prop_assert!(
18796 ac.signum() <= 0,
18797 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
18798 a={:?} b={:?} c={:?}",
18799 ab,
18800 bc,
18801 ac,
18802 a,
18803 b,
18804 c
18805 );
18806 }
18807 }
18808
18809 #[test]
18812 fn lookup_order_separates_distinct_identities(
18813 left in code_unit_strategy(),
18814 right in code_unit_strategy(),
18815 ) {
18816 if probe_order(&left, &right) == ProbedOrder::Tied {
18817 prop_assert_eq!(
18818 &left,
18819 &right,
18820 "distinct identities tied, so their order is whatever order they \
18821 arrived in: left_segments={:?} right_segments={:?}",
18822 fq_segments(&left),
18823 fq_segments(&right)
18824 );
18825 }
18826 }
18827
18828 #[test]
18831 fn lookup_sort_is_permutation_invariant(
18832 units in prop::collection::vec(code_unit_strategy(), 1..=8),
18833 ) {
18834 let mut sorted = units.clone();
18835 sort_lookup_units(&mut sorted);
18836 for rotation in 0..units.len() {
18837 for reversed in [false, true] {
18838 let mut permuted = units.clone();
18839 permuted.rotate_left(rotation);
18840 if reversed {
18841 permuted.reverse();
18842 }
18843 sort_lookup_units(&mut permuted);
18844 prop_assert_eq!(
18845 &permuted,
18846 &sorted,
18847 "sorting a permutation gave a different list \
18848 (rotation={}, reversed={}): input={:?}",
18849 rotation,
18850 reversed,
18851 units
18852 );
18853 }
18854 }
18855 }
18856 }
18857}