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: std::vec::IntoIter<Node<'tree>>,
13993 parsed_scope: Vec<String>,
13994 run: Option<RecoveredNamespaceRegion>,
13995 }
13996 fn frame<'tree>(
13997 node: Node<'tree>,
13998 mut parsed_scope: Vec<String>,
13999 source: &str,
14000 ) -> Frame<'tree> {
14001 if node.kind() == "namespace_definition"
14002 && let Some(name) = node.child_by_field_name("name")
14003 {
14004 let mut components = Vec::new();
14005 if append_cpp_name_components(name, source, &mut components).is_some() {
14006 parsed_scope.extend(components);
14007 }
14008 }
14009 let mut cursor = node.walk();
14010 Frame {
14011 node,
14012 children: node.children(&mut cursor).collect::<Vec<_>>().into_iter(),
14013 parsed_scope,
14014 run: None,
14015 }
14016 }
14017 let mut regions = Vec::new();
14018 let mut brace_closes = HashMap::default();
14019 let mut open = Vec::new();
14023 let mut lexical_scope = Vec::new();
14024 let mut frames = vec![frame(root, Vec::new(), source)];
14025 while let Some(current) = frames.last_mut() {
14026 let Some(child) = current.children.next() else {
14027 regions.extend(frames.pop().expect("the frame just borrowed").run);
14028 continue;
14029 };
14030 match child.kind() {
14031 "{" if !child.is_missing() => {
14032 regions.extend(current.run.take());
14033 let mut components = current
14034 .node
14035 .parent()
14036 .map(|parent| namespace_body_name_components(parent, current.node, source))
14037 .unwrap_or_default();
14038 if components.is_empty() {
14039 components = recovered_namespace_open_components(child, source);
14040 }
14041 open.push((child.start_byte(), lexical_scope.len()));
14042 lexical_scope.extend(components);
14043 continue;
14044 }
14045 "}" if !child.is_missing() => {
14046 regions.extend(current.run.take());
14047 if let Some((start, namespace_len)) = open.pop() {
14048 lexical_scope.truncate(namespace_len);
14049 brace_closes.insert(
14050 start,
14051 Range {
14052 start_byte: child.start_byte(),
14053 end_byte: child.end_byte(),
14054 start_line: child.start_position().row + 1,
14055 end_line: child.end_position().row + 1,
14056 },
14057 );
14058 }
14059 continue;
14060 }
14061 _ => {}
14062 }
14063 if current.node.kind() != "namespace_definition"
14067 && lexical_scope != current.parsed_scope
14068 {
14069 match &mut current.run {
14070 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14071 run => {
14072 regions.extend(run.take());
14073 *run = Some(RecoveredNamespaceRegion {
14074 start: child.start_byte(),
14075 end: child.end_byte(),
14076 components: lexical_scope.clone(),
14077 });
14078 }
14079 }
14080 } else {
14081 regions.extend(current.run.take());
14082 }
14083 if child.has_error() {
14087 let parsed_scope = current.parsed_scope.clone();
14088 frames.push(frame(child, parsed_scope, source));
14089 }
14090 }
14091 Self {
14092 regions,
14093 brace_closes,
14094 }
14095 }
14096
14097 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14100 self.brace_closes.get(&open).copied()
14101 }
14102
14103 pub fn is_empty(&self) -> bool {
14104 self.regions.is_empty()
14105 }
14106
14107 pub fn approximate_size(&self) -> usize {
14109 self.regions.iter().fold(
14110 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14111 |total, region| {
14112 total
14113 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14114 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14115 },
14116 )
14117 }
14118
14119 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14121 self.regions
14122 .iter()
14123 .filter(|region| region.start <= byte && byte < region.end)
14124 .min_by_key(|region| region.end - region.start)
14125 }
14126
14127 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14131 let mut parsed = Vec::new();
14132 let mut current = node.parent();
14133 while let Some(parent) = current {
14134 if parent.kind() == "namespace_definition"
14135 && let Some(name) = parent.child_by_field_name("name")
14136 {
14137 let mut components = Vec::new();
14138 if append_cpp_name_components(name, source, &mut components).is_some() {
14139 parsed.push((parent.start_byte(), components));
14140 }
14141 }
14142 current = parent.parent();
14143 }
14144 parsed.reverse();
14145 self.restore_enclosing_namespaces(parsed, node.start_byte())
14146 }
14147
14148 pub fn restore_enclosing_namespaces(
14154 &self,
14155 parsed: Vec<(usize, Vec<String>)>,
14156 node_start: usize,
14157 ) -> Vec<String> {
14158 let Some(region) = self.region_at(node_start) else {
14159 return parsed
14160 .into_iter()
14161 .flat_map(|(_, components)| components)
14162 .collect();
14163 };
14164 region
14165 .components
14166 .iter()
14167 .cloned()
14168 .chain(
14169 parsed
14170 .into_iter()
14171 .filter(|(start, _)| *start >= region.start)
14172 .flat_map(|(_, components)| components),
14173 )
14174 .collect()
14175 }
14176}
14177
14178fn recovered_namespace_open_components(open: Node<'_>, source: &str) -> Vec<String> {
14190 let mut head = Vec::new();
14191 let mut previous = open.prev_sibling();
14192 while let Some(node) = previous {
14193 if node.kind() != "comment" {
14194 head.push(node);
14195 if head.len() == 2 {
14196 break;
14197 }
14198 }
14199 previous = node.prev_sibling();
14200 }
14201 let [name, keyword] = head[..] else {
14202 return Vec::new();
14203 };
14204 if keyword.kind() != "namespace" {
14205 return Vec::new();
14206 }
14207 let mut components = Vec::new();
14208 if append_cpp_name_components(name, source, &mut components).is_none() {
14209 components.clear();
14210 }
14211 components
14212}
14213
14214fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14217 let mut components = Vec::new();
14218 if body.kind() == "declaration_list"
14219 && parent.kind() == "namespace_definition"
14220 && parent.child_by_field_name("body") == Some(body)
14221 && let Some(name) = parent.child_by_field_name("name")
14222 && append_cpp_name_components(name, source, &mut components).is_none()
14223 {
14224 components.clear();
14225 }
14226 components
14227}
14228
14229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14230pub enum RecoveredDeclaratorTypeContext {
14231 Declaration,
14232 FunctionDefinition,
14233 Parameter,
14234}
14235
14236pub fn recovered_macro_decorated_declarator_type(
14251 node: Node<'_>,
14252) -> Option<RecoveredDeclaratorTypeContext> {
14253 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14254}
14255
14256pub fn recovered_macro_decorated_type_node(
14261 node: Node<'_>,
14262) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14263 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14264 return None;
14265 }
14266 let qualified = node.parent()?;
14267 if qualified.kind() != "qualified_identifier"
14268 || qualified.child_by_field_name("scope") != Some(node)
14269 || !(0..qualified.child_count())
14270 .filter_map(|index| qualified.child(index))
14271 .any(|child| child.kind() == "::" && child.is_missing())
14272 {
14273 return None;
14274 }
14275 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14276 return None;
14277 }
14278
14279 let (declaration, context) = recovered_declarator_container(qualified)?;
14280 let type_node = declaration
14281 .child_by_field_name("type")
14282 .filter(|type_node| {
14283 *type_node != qualified
14284 && !type_node.is_missing()
14285 && type_node.start_byte() != type_node.end_byte()
14286 })?;
14287 Some((type_node, context))
14288}
14289
14290fn recovered_declarator_container(
14291 mut declarator: Node<'_>,
14292) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14293 loop {
14294 let parent = declarator.parent()?;
14295 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14296 return Some((
14297 parent
14298 .parent()
14299 .filter(|declaration| declaration.kind() == "declaration")?,
14300 RecoveredDeclaratorTypeContext::Declaration,
14301 ));
14302 }
14303 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14304 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14305 }
14306 if parent.kind() == "function_definition"
14307 && has_field_child(parent, "declarator", declarator)
14308 {
14309 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14310 }
14311 if matches!(
14317 parent.kind(),
14318 "parameter_declaration" | "optional_parameter_declaration"
14319 ) && has_field_child(parent, "declarator", declarator)
14320 {
14321 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14322 }
14323 if !matches!(
14324 parent.kind(),
14325 "array_declarator"
14326 | "function_declarator"
14327 | "parenthesized_declarator"
14328 | "pointer_declarator"
14329 | "pointer_type_declarator"
14330 | "reference_declarator"
14331 ) || !has_field_child(parent, "declarator", declarator)
14332 {
14333 return None;
14334 }
14335 declarator = parent;
14336 }
14337}
14338
14339fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14340 let mut cursor = parent.walk();
14341 parent
14342 .children_by_field_name(field, &mut cursor)
14343 .any(|child| child == target)
14344}
14345
14346fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14347 loop {
14348 if node.is_missing() || node.start_byte() == node.end_byte() {
14349 return false;
14350 }
14351 match node.kind() {
14352 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14353 return true;
14354 }
14355 "array_declarator"
14356 | "function_declarator"
14357 | "parenthesized_declarator"
14358 | "pointer_declarator"
14359 | "pointer_type_declarator"
14360 | "reference_declarator" => {
14361 let Some(declarator) = node.child_by_field_name("declarator") else {
14362 return false;
14363 };
14364 node = declarator;
14365 }
14366 _ => return false,
14367 }
14368 }
14369}
14370
14371pub enum DesignatedInitializerOwner {
14373 Resolved(CodeUnit),
14374 Unresolved,
14375}
14376
14377enum InitializerOwnerStep {
14378 Field(String),
14379 AggregateWrapper,
14380}
14381
14382pub fn designated_initializer_owner(
14392 analyzer: &CppGraphSource<'_>,
14393 visibility: &VisibilityIndex<'_>,
14394 file: &ProjectFile,
14395 source: &str,
14396 node: Node<'_>,
14397) -> Option<DesignatedInitializerOwner> {
14398 if let Some(designator) = node
14399 .parent()
14400 .filter(|parent| parent.kind() == "field_designator")
14401 {
14402 let pair = designator.parent()?;
14403 if pair.kind() != "initializer_pair" {
14404 return None;
14405 }
14406 let mut cursor = pair.walk();
14407 let designators = pair
14408 .children_by_field_name("designator", &mut cursor)
14409 .collect::<Vec<_>>();
14410 let position = designators
14411 .iter()
14412 .position(|candidate| same_node(*candidate, designator))?;
14413 let initializer = pair.parent()?;
14414 if initializer.kind() != "initializer_list" {
14415 return None;
14416 }
14417 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14418 for prior in &designators[..position] {
14419 let field = prior
14420 .child_by_field_name("field")
14421 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14422 owner = owner.and_then(|owner| {
14423 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14424 });
14425 }
14426 return Some(classified_designated_owner(owner));
14427 }
14428
14429 let init_declarator = node.parent()?;
14430 if init_declarator.child_by_field_name("declarator") != Some(node)
14431 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14432 {
14433 return None;
14434 }
14435 Some(classified_designated_owner(declaration_owner(
14436 analyzer,
14437 visibility,
14438 file,
14439 source,
14440 init_declarator.parent()?,
14441 )))
14442}
14443
14444fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14445 owner.map_or(
14446 DesignatedInitializerOwner::Unresolved,
14447 DesignatedInitializerOwner::Resolved,
14448 )
14449}
14450
14451fn initializer_list_owner(
14452 analyzer: &CppGraphSource<'_>,
14453 visibility: &VisibilityIndex<'_>,
14454 file: &ProjectFile,
14455 source: &str,
14456 initializer: Node<'_>,
14457) -> Option<CodeUnit> {
14458 let mut current = initializer;
14459 let mut steps = Vec::new();
14460 loop {
14461 let parent = current.parent()?;
14462 match parent.kind() {
14463 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14464 let designator = parent.child_by_field_name("designator")?;
14465 let step = designator
14466 .child_by_field_name("field")
14467 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14468 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14469 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14470 steps.push(step);
14471 current = parent.parent()?;
14472 }
14473 "initializer_list" => {
14474 current = parent;
14475 }
14476 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14477 let declaration = parent.parent()?;
14478 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14479 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14480 }
14481 "compound_literal_expression"
14482 if parent.child_by_field_name("value") == Some(current) =>
14483 {
14484 let type_node = parent.child_by_field_name("type")?;
14485 let owner =
14486 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
14487 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14488 }
14489 "ERROR" => current = parent,
14490 _ => return None,
14491 }
14492 }
14493}
14494
14495fn apply_initializer_owner_steps(
14496 analyzer: &CppGraphSource<'_>,
14497 visibility: &VisibilityIndex<'_>,
14498 file: &ProjectFile,
14499 mut owner: CodeUnit,
14500 steps: Vec<InitializerOwnerStep>,
14501) -> Option<CodeUnit> {
14502 for step in steps.into_iter().rev() {
14503 if let InitializerOwnerStep::Field(field_name) = step {
14504 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
14505 }
14506 }
14507 Some(owner)
14508}
14509
14510fn initializer_field_owner(
14511 analyzer: &CppGraphSource<'_>,
14512 visibility: &VisibilityIndex<'_>,
14513 file: &ProjectFile,
14514 owner: CodeUnit,
14515 field_name: &str,
14516) -> Option<CodeUnit> {
14517 let fields = visibility
14518 .visible_members_for_owner_name(file, &owner, field_name)
14519 .into_iter()
14520 .filter(|field| field.is_field())
14521 .collect::<Vec<_>>();
14522 let field = match fields.as_slice() {
14523 [field] => *field,
14524 _ => return None,
14525 };
14526 field_declared_binding(analyzer, visibility, file, field)?.unit
14527}
14528
14529fn declaration_owner(
14530 analyzer: &CppGraphSource<'_>,
14531 visibility: &VisibilityIndex<'_>,
14532 file: &ProjectFile,
14533 source: &str,
14534 declaration: Node<'_>,
14535) -> Option<CodeUnit> {
14536 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
14537 return None;
14538 }
14539 let type_node = declaration
14540 .child_by_field_name("type")
14541 .or_else(|| first_type_child(declaration))?;
14542 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
14543}
14544
14545fn resolve_designated_owner_type(
14546 analyzer: &CppGraphSource<'_>,
14547 visibility: &VisibilityIndex<'_>,
14548 file: &ProjectFile,
14549 source: &str,
14550 type_node: Node<'_>,
14551) -> Option<CodeUnit> {
14552 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
14553 return Some(owner);
14554 }
14555 let type_name = normalize_type_text(node_text(type_node, source));
14556 visibility
14557 .resolve_type(file, &type_name)
14558 .filter(CodeUnit::is_class)
14559}
14560
14561pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
14562 let mut cursor = node.walk();
14563 node.named_children(&mut cursor).find(|child| {
14564 matches!(
14565 child.kind(),
14566 "type_identifier"
14567 | "primitive_type"
14568 | "qualified_identifier"
14569 | "scoped_type_identifier"
14570 | "struct_specifier"
14571 | "union_specifier"
14572 | "enum_specifier"
14573 )
14574 })
14575}
14576
14577pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
14578 visibility: &VisibilityIndex<'_>,
14579 file: &ProjectFile,
14580 source: &str,
14581 declarator: Node<'_>,
14582 type_text: Option<&str>,
14583 bindings: &LocalInferenceEngine<T>,
14584) -> bool {
14585 if !has_ancestor_kind(declarator, "compound_statement") {
14586 return false;
14587 }
14588 if declarator
14589 .child_by_field_name("declarator")
14590 .is_none_or(|declarator| declarator.kind() != "identifier")
14591 {
14592 return false;
14593 }
14594 if !type_text
14595 .and_then(|text| visibility.resolve_type(file, text))
14596 .is_some_and(|unit| unit.is_class())
14597 {
14598 return false;
14599 }
14600 declarator
14601 .child_by_field_name("parameters")
14602 .is_some_and(|parameters| {
14603 constructor_parameters_look_like_expressions(parameters, source, bindings)
14604 })
14605}
14606
14607fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
14608 parameters: Node<'_>,
14609 source: &str,
14610 bindings: &LocalInferenceEngine<T>,
14611) -> bool {
14612 let mut cursor = parameters.walk();
14613 parameters.named_children(&mut cursor).any(|parameter| {
14614 !matches!(
14615 parameter.kind(),
14616 "parameter_declaration" | "optional_parameter_declaration"
14617 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
14618 })
14619}
14620
14621fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
14622 parameter: Node<'_>,
14623 source: &str,
14624 bindings: &LocalInferenceEngine<T>,
14625) -> bool {
14626 let text = node_text(parameter, source).trim();
14627 if text
14628 .chars()
14629 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
14630 && bindings.is_shadowed(text)
14631 {
14632 return true;
14633 }
14634
14635 let Some(base) = parameter
14636 .child_by_field_name("type")
14637 .filter(|base| base.kind() == "type_identifier")
14638 else {
14639 return false;
14640 };
14641 let Some(subscript) = parameter
14642 .child_by_field_name("declarator")
14643 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
14644 else {
14645 return false;
14646 };
14647 subscript.child_by_field_name("size").is_some()
14648 && bindings.is_shadowed(node_text(base, source).trim())
14649}
14650
14651pub fn is_declaration_name(node: Node<'_>) -> bool {
14652 let Some(parent) = node.parent() else {
14653 return false;
14654 };
14655 if parent
14656 .child_by_field_name("name")
14657 .is_some_and(|name| same_node(name, node))
14658 {
14659 if matches!(
14660 parent.kind(),
14661 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14662 ) {
14663 return cpp_tag_specifier_declares_name(parent);
14664 }
14665 if matches!(
14666 parent.kind(),
14667 "namespace_definition"
14668 | "namespace_alias_definition"
14669 | "alias_declaration"
14670 | "enumerator"
14671 ) {
14672 return true;
14673 }
14674 }
14675
14676 let mut current = Some(parent);
14677 while let Some(ancestor) = current {
14678 let type_definition = ancestor.kind() == "type_definition";
14679 let mut child_cursor = ancestor.walk();
14680 if ancestor.named_children(&mut child_cursor).any(|child| {
14681 declaration_declarator(ancestor, child).is_some_and(|declarator| {
14682 declarator_name_path_contains(declarator, node, type_definition)
14683 })
14684 }) {
14685 return true;
14686 }
14687 if matches!(
14688 ancestor.kind(),
14689 "declaration"
14690 | "field_declaration"
14691 | "parameter_declaration"
14692 | "optional_parameter_declaration"
14693 | "function_definition"
14694 | "type_definition"
14695 | "alias_declaration"
14696 | "template_instantiation"
14697 | "class_specifier"
14698 | "struct_specifier"
14699 | "union_specifier"
14700 | "enum_specifier"
14701 ) {
14702 return false;
14703 }
14704 current = ancestor.parent();
14705 }
14706 false
14707}
14708
14709pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
14718 if !matches!(
14719 node.kind(),
14720 "qualified_identifier" | "scoped_type_identifier"
14721 ) {
14722 return false;
14723 }
14724 let Some(declaration) = node
14725 .parent()
14726 .filter(|parent| parent.kind() == "declaration")
14727 else {
14728 return false;
14729 };
14730 if declaration.child_by_field_name("declarator") != Some(node)
14731 || !declaration
14732 .child_by_field_name("type")
14733 .is_some_and(|friend| {
14734 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
14735 })
14736 {
14737 return false;
14738 }
14739 let mut cursor = declaration.walk();
14740 let mut errors = declaration
14741 .named_children(&mut cursor)
14742 .filter(|child| child.kind() == "ERROR");
14743 let Some(error) = errors.next() else {
14744 return false;
14745 };
14746 errors.next().is_none()
14747 && error.named_child_count() == 1
14748 && error.named_child(0).is_some_and(|class| {
14749 class.kind() == "identifier" && node_text(class, source) == "class"
14750 })
14751}
14752
14753pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
14754 if !matches!(node.kind(), "identifier" | "field_identifier") {
14755 return false;
14756 }
14757 if let Some(parent) = node.parent() {
14758 if parent.kind() == "call_expression"
14759 && parent.child_by_field_name("function") == Some(node)
14760 {
14761 return false;
14762 }
14763 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
14764 && parent.child_by_field_name("label") == Some(node)
14765 {
14766 return false;
14767 }
14768 }
14769 if is_declaration_name(node) {
14770 return false;
14771 }
14772 let mut current = node.parent();
14773 while let Some(ancestor) = current {
14774 match ancestor.kind() {
14775 "preproc_ifdef" | "preproc_ifndef" => {
14776 if ancestor
14777 .child_by_field_name("name")
14778 .is_some_and(|name| node_range_contains(name, node))
14779 {
14780 return false;
14781 }
14782 }
14783 "preproc_if" | "preproc_elif" => {
14784 if ancestor
14785 .child_by_field_name("condition")
14786 .is_some_and(|condition| node_range_contains(condition, node))
14787 {
14788 return false;
14789 }
14790 }
14791 "preproc_else" => {}
14792 kind if kind.starts_with("preproc_") => return false,
14793 _ => {}
14794 }
14795 if matches!(
14796 ancestor.kind(),
14797 "translation_unit" | "function_definition" | "compound_statement"
14798 ) {
14799 break;
14800 }
14801 current = ancestor.parent();
14802 }
14803 true
14804}
14805
14806fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
14807 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
14808}
14809
14810fn recovered_c_reference_node(
14811 visibility: &VisibilityIndex<'_>,
14812 file: &ProjectFile,
14813 node: Node<'_>,
14814 source: &str,
14815) -> bool {
14816 if node.start_byte() >= node.end_byte()
14817 || node.is_error()
14818 || node.is_missing()
14819 || !matches!(
14820 node.kind(),
14821 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
14822 )
14823 || recovered_c_macro_binding_role(node)
14824 || recovered_c_label_role(node)
14825 {
14826 return false;
14827 }
14828 let name = node_text(node, source);
14836 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
14837 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
14838 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
14839 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
14840 if is_declaration_name(node)
14841 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
14842 && !recovered_function_call
14843 && !recovered_macro_call
14844 && !recovered_parenthesized_reference
14845 {
14846 return false;
14847 }
14848
14849 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
14850 return true;
14851 }
14852 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
14853 return true;
14854 }
14855 if recovered_parenthesized_reference {
14856 return true;
14857 }
14858 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
14859 if recovered_function_call {
14860 return true;
14861 }
14862 return visibility
14863 .visible_identifier_candidates(file, name)
14864 .any(|candidate| {
14865 candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
14866 });
14867 }
14868 let visible = visibility
14869 .visible_identifier_candidates(file, name)
14870 .next()
14871 .is_some();
14872 visible
14873 && (recovered_c_reference_anchor(node)
14874 || recovered_c_error_expression_leaf(node)
14875 || recovered_function_call)
14876}
14877
14878fn push_recovered_c_range(
14879 ranges: &mut Vec<Range>,
14880 seen: &mut HashSet<(usize, usize)>,
14881 start_byte: usize,
14882 end_byte: usize,
14883 node: Node<'_>,
14884 limit: usize,
14885) -> bool {
14886 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
14887 return true;
14888 }
14889 if ranges.len() >= limit {
14890 return false;
14891 }
14892 ranges.push(Range {
14893 start_byte,
14894 end_byte,
14895 start_line: node.start_position().row,
14896 end_line: node.end_position().row,
14897 });
14898 true
14899}
14900
14901fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
14906 let mut current = node.parent();
14907 while let Some(parent) = current {
14908 if parent.is_error() {
14909 let Some(anchor) = parent.parent() else {
14910 return false;
14911 };
14912 return anchor.kind().ends_with("_expression")
14913 || matches!(
14914 anchor.kind(),
14915 "argument_list"
14916 | "return_statement"
14917 | "expression_statement"
14918 | "case_statement"
14919 | "initializer_list"
14920 | "field_designator"
14921 | "enumerator"
14922 );
14923 }
14924 if matches!(
14925 parent.kind(),
14926 "translation_unit" | "function_definition" | "compound_statement"
14927 ) {
14928 return false;
14929 }
14930 current = parent.parent();
14931 }
14932 false
14933}
14934
14935fn recovered_c_function_call(
14942 visibility: &VisibilityIndex<'_>,
14943 file: &ProjectFile,
14944 node: Node<'_>,
14945 name: &str,
14946) -> bool {
14947 if !matches!(
14948 node.kind(),
14949 "identifier" | "field_identifier" | "type_identifier"
14950 ) {
14951 return false;
14952 }
14953 let error_call_prefix = node.parent().is_some_and(|error| {
14958 error.is_error()
14959 && error
14960 .parent()
14961 .is_some_and(|parent| parent.kind() == "compound_statement")
14962 }) && node
14963 .prev_sibling()
14964 .is_none_or(|previous| previous.kind() == ";")
14965 && node.next_sibling().is_some_and(|open| {
14966 open.kind() == "("
14967 && open.next_named_sibling().is_some_and(|argument| {
14968 argument.kind() == "parameter_declaration" && !argument.has_error()
14969 })
14970 });
14971 (error_call_prefix || recovered_c_function_declarator_invocation(node))
14972 && visibility
14973 .visible_identifier_candidates(file, name)
14974 .any(CodeUnit::is_function)
14975}
14976
14977fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
14987 let mut function_declarator = if node.parent().is_some_and(|parent| {
14988 parent.kind() == "function_declarator"
14989 && parent.child_by_field_name("declarator") == Some(node)
14990 }) {
14991 node.parent().expect("checked function declarator parent")
14992 } else {
14993 let Some(parameter) = node.parent().filter(|parent| {
14994 parent.kind() == "parameter_declaration"
14995 && parent.child_by_field_name("type") == Some(node)
14996 }) else {
14997 return false;
14998 };
14999 if !parameter
15000 .child_by_field_name("declarator")
15001 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15002 {
15003 return false;
15004 }
15005 let Some(parameters) = parameter
15006 .parent()
15007 .filter(|parent| parent.kind() == "parameter_list")
15008 else {
15009 return false;
15010 };
15011 let Some(function_declarator) = parameters
15012 .parent()
15013 .filter(|parent| parent.kind() == "function_declarator")
15014 else {
15015 return false;
15016 };
15017 function_declarator
15018 };
15019
15020 while let Some(parent) = function_declarator.parent().filter(|parent| {
15023 parent.kind() == "function_declarator"
15024 && parent.child_by_field_name("declarator") == Some(function_declarator)
15025 }) {
15026 function_declarator = parent;
15027 }
15028 let Some(mut current) = function_declarator
15029 .parent()
15030 .filter(|parent| parent.is_error())
15031 else {
15032 return false;
15033 };
15034 loop {
15035 let Some(parent) = current.parent() else {
15036 return false;
15037 };
15038 if matches!(
15039 parent.kind(),
15040 "translation_unit"
15041 | "compound_statement"
15042 | "preproc_if"
15043 | "preproc_ifdef"
15044 | "preproc_ifndef"
15045 | "preproc_else"
15046 | "preproc_elif"
15047 ) {
15048 return true;
15049 }
15050 if parent.kind() == "function_definition"
15051 && parent.child_by_field_name("declarator") == Some(current)
15052 && parent.named_child(0) == Some(current)
15053 && parent.child_by_field_name("body").is_some()
15054 {
15055 return true;
15056 }
15057 if parent.is_error()
15058 || matches!(
15059 parent.kind(),
15060 "parameter_declaration"
15061 | "parameter_list"
15062 | "function_declarator"
15063 | "abstract_function_declarator"
15064 | "parenthesized_declarator"
15065 )
15066 {
15067 current = parent;
15068 continue;
15069 }
15070 return false;
15071 }
15072}
15073
15074fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15080 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15081 return false;
15082 };
15083 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15084 return false;
15085 }
15086 let Some(declarator) = error
15087 .parent()
15088 .filter(|parent| parent.kind() == "parenthesized_declarator")
15089 else {
15090 return false;
15091 };
15092 let Some(declaration) = declarator
15093 .parent()
15094 .filter(|parent| parent.kind() == "declaration")
15095 else {
15096 return false;
15097 };
15098 if declaration.child_by_field_name("declarator") != Some(declarator) {
15099 return false;
15100 }
15101 let Some(type_node) = declaration.child_by_field_name("type") else {
15102 return false;
15103 };
15104 type_node.kind() == "dependent_type"
15105 && type_node
15106 .child(0)
15107 .is_some_and(|keyword| keyword.kind() == "typename")
15108}
15109
15110fn recovered_c_explicit_assignment_callee(
15111 visibility: &VisibilityIndex<'_>,
15112 file: &ProjectFile,
15113 node: Node<'_>,
15114 name: &str,
15115) -> bool {
15116 let mut current = node;
15117 let error = loop {
15118 let Some(parent) = current.parent() else {
15119 return false;
15120 };
15121 if parent.is_error() {
15122 break parent;
15123 }
15124 current = parent;
15125 };
15126 let mut cursor = error.walk();
15127 let explicit_recovery_precedes_callee = error
15128 .named_children(&mut cursor)
15129 .take_while(|child| child.start_byte() < node.start_byte())
15130 .any(|child| child.kind() == "explicit_function_specifier");
15131 if !explicit_recovery_precedes_callee {
15132 return false;
15133 }
15134 visibility
15135 .visible_identifier_candidates(file, name)
15136 .any(CodeUnit::is_function)
15137}
15138
15139fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15140 while let Some(parent) = node.parent() {
15141 if matches!(
15142 parent.kind(),
15143 "preproc_def" | "preproc_function_def" | "preproc_params"
15144 ) {
15145 return true;
15146 }
15147 if parent.is_error()
15148 || matches!(
15149 parent.kind(),
15150 "translation_unit" | "function_definition" | "compound_statement"
15151 )
15152 {
15153 return false;
15154 }
15155 node = parent;
15156 }
15157 false
15158}
15159
15160fn recovered_c_label_role(node: Node<'_>) -> bool {
15161 node.parent().is_some_and(|parent| {
15162 matches!(parent.kind(), "labeled_statement" | "goto_statement")
15163 && parent.child_by_field_name("label") == Some(node)
15164 })
15165}
15166
15167fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15168 while let Some(parent) = node.parent() {
15169 if parent.is_error() {
15170 return false;
15171 }
15172 if parent.kind() == "optional_parameter_declaration"
15177 && parent
15178 .child_by_field_name("default_value")
15179 .is_some_and(|value| node_range_contains(value, node))
15180 {
15181 return true;
15182 }
15183 if parent.kind().ends_with("_expression")
15184 || matches!(
15185 parent.kind(),
15186 "argument_list"
15187 | "return_statement"
15188 | "expression_statement"
15189 | "case_statement"
15190 | "initializer_list"
15191 | "init_declarator"
15192 | "array_declarator"
15193 | "field_designator"
15194 | "enumerator"
15195 )
15196 {
15197 return true;
15198 }
15199 if matches!(
15200 parent.kind(),
15201 "translation_unit"
15202 | "function_definition"
15203 | "compound_statement"
15204 | "declaration"
15205 | "field_declaration"
15206 | "parameter_declaration"
15207 ) {
15208 return false;
15209 }
15210 node = parent;
15211 }
15212 false
15213}
15214
15215pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15223 let mut current = parameter.parent();
15224 while let Some(ancestor) = current {
15225 if ancestor.kind() == "lambda_expression" {
15226 return ancestor
15227 .child_by_field_name("declarator")
15228 .is_some_and(|declarator| {
15229 declarator.start_byte() <= parameter.start_byte()
15230 && parameter.end_byte() <= declarator.end_byte()
15231 });
15232 }
15233 if ancestor.kind() == "function_definition" {
15234 return ancestor
15235 .child_by_field_name("declarator")
15236 .is_some_and(|declarator| {
15237 declarator.start_byte() <= parameter.start_byte()
15238 && parameter.end_byte() <= declarator.end_byte()
15239 });
15240 }
15241 current = ancestor.parent();
15242 }
15243 false
15244}
15245
15246pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15247 let mut current = node.parent();
15248 while let Some(ancestor) = current {
15249 if matches!(
15250 ancestor.kind(),
15251 "parameter_declaration" | "optional_parameter_declaration"
15252 ) {
15253 return ancestor
15254 .child_by_field_name("type")
15255 .is_some_and(|type_node| {
15256 type_node.start_byte() <= node.start_byte()
15257 && node.end_byte() <= type_node.end_byte()
15258 });
15259 }
15260 if matches!(
15261 ancestor.kind(),
15262 "function_definition" | "lambda_expression" | "compound_statement"
15263 ) {
15264 return false;
15265 }
15266 current = ancestor.parent();
15267 }
15268 false
15269}
15270
15271fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15272 if specifier.child_by_field_name("body").is_some() {
15273 return true;
15274 }
15275 let mut current = specifier.parent();
15276 while let Some(ancestor) = current {
15277 match ancestor.kind() {
15278 "type_descriptor"
15279 | "parameter_declaration"
15280 | "optional_parameter_declaration"
15281 | "template_argument_list"
15282 | "cast_expression" => return false,
15283 "declaration" | "field_declaration" => {
15284 let mut cursor = ancestor.walk();
15285 return ancestor
15286 .children_by_field_name("declarator", &mut cursor)
15287 .next()
15288 .is_none();
15289 }
15290 "translation_unit" => return true,
15291 _ => current = ancestor.parent(),
15292 }
15293 }
15294 false
15295}
15296
15297pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15298 match node.kind() {
15299 "identifier"
15300 | "field_identifier"
15301 | "qualified_identifier"
15302 | "scoped_identifier"
15303 | "operator_name"
15304 | "destructor_name"
15305 | "literal_operator_name" => Some(node),
15306 "reference_declarator" | "parenthesized_declarator" => {
15307 node.named_child(0).and_then(declarator_name_node)
15308 }
15309 _ => node
15310 .child_by_field_name("declarator")
15311 .or_else(|| node.child_by_field_name("name"))
15312 .or_else(|| node.child_by_field_name("field"))
15313 .and_then(declarator_name_node),
15314 }
15315}
15316
15317fn declarator_name_path_contains(
15318 declarator: Node<'_>,
15319 candidate: Node<'_>,
15320 allow_type_identifier: bool,
15321) -> bool {
15322 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15323 return false;
15324 };
15325 let mut current = Some(declarator);
15326 while let Some(node) = current {
15327 if same_node(node, candidate) {
15328 return true;
15329 }
15330 if same_node(node, name) {
15331 return false;
15332 }
15333 current = node
15334 .child_by_field_name("declarator")
15335 .or_else(|| node.child_by_field_name("name"))
15336 .or_else(|| node.child_by_field_name("field"));
15337 }
15338 false
15339}
15340
15341fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15342 match node.kind() {
15343 "identifier"
15344 | "field_identifier"
15345 | "operator_name"
15346 | "destructor_name"
15347 | "literal_operator_name" => Some(node),
15348 "type_identifier" if allow_type_identifier => Some(node),
15349 _ => node
15350 .child_by_field_name("declarator")
15351 .or_else(|| node.child_by_field_name("name"))
15352 .or_else(|| node.child_by_field_name("field"))
15353 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15354 }
15355}
15356
15357pub fn is_nested_type_node(node: Node<'_>) -> bool {
15360 node.parent().is_some_and(|parent| {
15361 matches!(
15362 parent.kind(),
15363 "qualified_identifier" | "scoped_type_identifier" | "template_type"
15364 )
15365 })
15366}
15367
15368pub struct OutOfLineMemberDefinitionOwners<'tree> {
15369 pub owners: Vec<(Node<'tree>, CodeUnit)>,
15370 innermost: Option<(Node<'tree>, CodeUnit)>,
15371}
15372
15373impl OutOfLineMemberDefinitionOwners<'_> {
15374 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15375 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15376 }
15377}
15378
15379pub struct QualifiedOwnerComponents<'tree> {
15380 pub nodes: Vec<Node<'tree>>,
15381 pub names: Vec<String>,
15382 pub global: bool,
15383}
15384
15385pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15390 let mut stack = vec![node];
15391 let mut found_separator = false;
15392 while let Some(current) = stack.pop() {
15393 if !matches!(
15394 current.kind(),
15395 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15396 ) {
15397 continue;
15398 }
15399 let mut current_has_separator = false;
15400 for child in children_iter(current) {
15401 if child.kind() == "::" {
15402 if child.is_missing() {
15403 return false;
15404 }
15405 current_has_separator = true;
15406 found_separator = true;
15407 }
15408 }
15409 if !current_has_separator {
15410 return false;
15411 }
15412 for field in ["scope", "name"] {
15413 if let Some(child) = current.child_by_field_name(field)
15414 && matches!(
15415 child.kind(),
15416 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15417 )
15418 {
15419 stack.push(child);
15420 }
15421 }
15422 }
15423 found_separator
15424}
15425
15426pub fn qualified_owner_components<'tree>(
15427 node: Node<'tree>,
15428 source: &str,
15429) -> Option<QualifiedOwnerComponents<'tree>> {
15430 if !qualified_name_has_concrete_scope_separators(node) {
15431 return None;
15432 }
15433 let mut nodes = cpp_name_component_nodes(node)?;
15434 nodes.pop()?;
15435 if nodes.is_empty() {
15436 return None;
15437 }
15438 let names = nodes
15439 .iter()
15440 .map(|component| node_text(*component, source).to_string())
15441 .collect();
15442 Some(QualifiedOwnerComponents {
15443 nodes,
15444 names,
15445 global: is_globally_qualified_cpp_name(node),
15446 })
15447}
15448
15449pub fn out_of_line_member_definition_owner<'tree>(
15450 analyzer: &CppGraphSource<'_>,
15451 visibility: &VisibilityIndex<'_>,
15452 file: &ProjectFile,
15453 source: &str,
15454 node: Node<'tree>,
15455) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15456 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15457 || !has_ancestor_kind(node, "function_definition")
15458 || !is_function_declarator_name_root(node)
15459 {
15460 return None;
15461 }
15462 let qualified = qualified_owner_components(node, source)?;
15463 let lexical_scope = enclosing_namespace_components(node, source)?;
15464 let mut owners = Vec::new();
15465 let mut innermost = None;
15466
15467 for component_count in 1..=qualified.names.len() {
15468 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15469 .resolve_type_components_lexically(
15470 analyzer,
15471 file,
15472 &qualified.names[..component_count],
15473 qualified.global,
15474 &lexical_scope,
15475 )
15476 && !owners
15477 .iter()
15478 .any(|(_, existing)| same_visible_symbol(existing, &unit))
15479 {
15480 if component_count == qualified.names.len() {
15481 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
15482 }
15483 owners.push((qualified.nodes[component_count - 1], unit));
15484 }
15485 }
15486
15487 if innermost.is_none() {
15497 let indexed_owner_components = visibility
15498 .indexed_enclosing_owner_scope(analyzer, file, node)
15499 .or_else(|| {
15500 if qualified.names.len() <= 1 {
15505 return None;
15506 }
15507 let range = Range {
15508 start_byte: node.start_byte(),
15509 end_byte: node.end_byte(),
15510 start_line: node.start_position().row,
15511 end_line: node.end_position().row,
15512 };
15513 let start = analyzer.enclosing_code_unit(file, &range)?;
15514 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15515 brokk_bifrost_core::analyzer::Language::Cpp,
15516 &cpp_name_for(&start),
15517 );
15518 components.pop();
15519 Some(components)
15520 });
15521 if let Some(indexed_owner_components) = indexed_owner_components
15522 && indexed_owner_components.len() > qualified.names.len()
15523 && indexed_owner_components.ends_with(&qualified.names)
15524 && indexed_namespace_path_is_recoverable(
15525 &lexical_scope,
15526 &indexed_owner_components,
15527 qualified.names.len(),
15528 )
15529 && (qualified.names.len() > 1 || !qualified.global)
15534 {
15535 let namespace_count = indexed_owner_components.len() - qualified.names.len();
15536 for component_count in 1..=qualified.names.len() {
15537 let expected = &indexed_owner_components[..namespace_count + component_count];
15538 let owner_node = qualified.nodes[component_count - 1];
15539 for owner in visibility
15540 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
15541 .filter(|candidate| candidate.is_class())
15542 .filter(|candidate| {
15543 canonical_cpp_scope_components(candidate) == expected
15544 && visibility.external_type_candidate_visible_in_context(
15545 analyzer, file, candidate, node,
15546 )
15547 })
15548 {
15549 if component_count == qualified.names.len() && innermost.is_none() {
15550 innermost = Some((owner_node, owner.clone()));
15551 }
15552 if !owners
15553 .iter()
15554 .any(|(_, existing)| same_symbol(existing, owner))
15555 {
15556 owners.push((owner_node, owner.clone()));
15557 }
15558 }
15559 }
15560 }
15561 }
15562 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
15563}
15564
15565fn is_function_declarator_name_root(node: Node<'_>) -> bool {
15566 let mut current = node;
15567 while let Some(parent) = current.parent() {
15568 if parent.kind() == "function_declarator" {
15569 return parent.child_by_field_name("declarator") == Some(current);
15570 }
15571 if matches!(
15572 parent.kind(),
15573 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
15574 ) && parent.child_by_field_name("declarator") == Some(current)
15575 {
15576 current = parent;
15577 continue;
15578 }
15579 return false;
15580 }
15581 false
15582}
15583
15584pub fn append_cpp_name_components(
15585 node: Node<'_>,
15586 source: &str,
15587 out: &mut Vec<String>,
15588) -> Option<()> {
15589 out.extend(
15590 cpp_name_component_nodes(node)?
15591 .into_iter()
15592 .map(|component| node_text(component, source).to_string()),
15593 );
15594 Some(())
15595}
15596
15597pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
15598 let mut components = Vec::new();
15599 append_cpp_name_components(node, source, &mut components)?;
15600 Some(components)
15601}
15602
15603pub fn unique_macro_replacement_type_candidate(
15612 analyzer: &CppGraphSource<'_>,
15613 visibility: &VisibilityIndex<'_>,
15614 file: &ProjectFile,
15615 components: &[String],
15616) -> Option<CodeUnit> {
15617 let terminal = components.last()?;
15618 let mut candidates = Vec::new();
15619 for candidate in visibility
15620 .visible_identifier_candidates(file, terminal)
15621 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
15622 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
15623 {
15624 if !candidates
15625 .iter()
15626 .any(|existing| same_logical_symbol(existing, candidate))
15627 {
15628 candidates.push(candidate.clone());
15629 }
15630 }
15631 (candidates.len() == 1).then(|| candidates.remove(0))
15632}
15633
15634pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
15642 let mut parser = Parser::new();
15643 if parser
15644 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15645 .is_err()
15646 {
15647 return Vec::new();
15648 }
15649 let Some(tree) = parser.parse(source, None) else {
15650 return Vec::new();
15651 };
15652 let mut scopes = Vec::new();
15653 let mut pending = vec![tree.root_node()];
15654 while let Some(node) = pending.pop() {
15655 if node.kind() == "using_declaration" {
15656 let Some(imported) = node.named_child(0) else {
15657 continue;
15658 };
15659 let Some(mut components) = cpp_type_name_components(imported, source) else {
15660 continue;
15661 };
15662 if components.pop().as_deref() == Some(member) && !components.is_empty() {
15663 scopes.push(components.join("::"));
15664 }
15665 continue;
15666 }
15667 push_named_children_reversed(node, &mut pending);
15668 }
15669 scopes
15670}
15671
15672pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
15676 qualified == scope
15677 || qualified
15678 .strip_suffix(scope)
15679 .is_some_and(|prefix| prefix.ends_with("::"))
15680}
15681
15682pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
15687 let Some(type_descriptor) = node.parent() else {
15688 return false;
15689 };
15690 if type_descriptor.kind() != "type_descriptor"
15691 || type_descriptor.child_by_field_name("type") != Some(node)
15692 {
15693 return false;
15694 }
15695 let Some(arguments) = type_descriptor.parent() else {
15696 return false;
15697 };
15698 if arguments.kind() != "template_argument_list" {
15699 return false;
15700 }
15701 arguments.parent().is_some_and(|parent| {
15702 matches!(parent.kind(), "template_type" | "template_function")
15703 && parent.child_by_field_name("arguments") == Some(arguments)
15704 })
15705}
15706
15707pub fn cpp_template_reference_arguments(
15708 mut node: Node<'_>,
15709 source: &str,
15710) -> Option<Vec<CppTemplateExpression>> {
15711 loop {
15712 match node.kind() {
15713 "template_type" | "template_function" => {
15714 let arguments = node.child_by_field_name("arguments")?;
15715 let mut cursor = arguments.walk();
15716 return Some(
15717 arguments
15718 .named_children(&mut cursor)
15719 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
15720 .map(|argument| CppTemplateExpression {
15721 text: normalize_cpp_whitespace(node_text(argument, source)),
15722 term: cpp_template_term(
15724 argument,
15725 source,
15726 &[],
15727 &ParentIndex::unindexed(),
15728 ),
15729 })
15730 .collect(),
15731 );
15732 }
15733 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
15734 node = node
15735 .child_by_field_name("name")
15736 .or_else(|| node.child_by_field_name("type"))?;
15737 }
15738 _ => return None,
15739 }
15740 }
15741}
15742
15743fn cpp_reconcile_primary_template_parameters(
15744 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
15745 preferred: &CodeUnit,
15746) -> Option<Vec<CppTemplateParameterMetadata>> {
15747 let canonical = candidates
15748 .iter()
15749 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
15750 let mut merged = canonical
15751 .parameters
15752 .iter()
15753 .map(|parameter| CppTemplateParameterMetadata {
15754 name: parameter.name.clone(),
15755 kind: parameter.kind,
15756 variadic: parameter.variadic,
15757 default: None,
15758 })
15759 .collect::<Vec<_>>();
15760
15761 for (_, metadata) in candidates {
15762 if metadata.parameters.len() != merged.len() {
15763 return None;
15764 }
15765 let rename_bindings = metadata
15766 .parameters
15767 .iter()
15768 .zip(&merged)
15769 .map(|(parameter, canonical)| {
15770 (
15771 parameter.name.clone(),
15772 CppTemplateTerm::Parameter(canonical.name.clone()),
15773 )
15774 })
15775 .collect::<HashMap<_, _>>();
15776 for ((parameter, canonical), merged_parameter) in metadata
15777 .parameters
15778 .iter()
15779 .zip(&canonical.parameters)
15780 .zip(&mut merged)
15781 {
15782 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
15783 return None;
15784 }
15785 let Some(default) = ¶meter.default else {
15786 continue;
15787 };
15788 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
15789 if let Some(existing) = &merged_parameter.default {
15790 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
15791 return None;
15792 }
15793 } else {
15794 merged_parameter.default = Some(CppTemplateExpression {
15795 text: default.text.clone(),
15796 term: normalized_term,
15797 });
15798 }
15799 }
15800 }
15801 Some(merged)
15802}
15803
15804pub fn cpp_bind_template_arguments(
15805 parameters: &[CppTemplateParameterMetadata],
15806 explicit_arguments: &[CppTemplateExpression],
15807) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
15808 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
15809 if variadic_index.is_some_and(|index| {
15810 index + 1 != parameters.len()
15811 || parameters[index + 1..]
15812 .iter()
15813 .any(|parameter| parameter.variadic)
15814 }) {
15815 return None;
15816 }
15817 let fixed_count = variadic_index.unwrap_or(parameters.len());
15818 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
15819 return None;
15820 }
15821 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
15822 let mut expanded = explicit_arguments[..explicit_fixed_count]
15823 .iter()
15824 .map(cpp_clone_template_expression_iterative)
15825 .collect::<Vec<_>>();
15826 let mut bindings = HashMap::default();
15827 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
15828 bindings.insert(
15829 parameter.name.clone(),
15830 cpp_clone_template_term_iterative(&argument.term),
15831 );
15832 }
15833 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
15834 let default = parameter.default.as_ref()?;
15835 let term = cpp_substitute_template_term(&default.term, &bindings)?;
15836 bindings.insert(parameter.name.clone(), term.clone());
15837 expanded.push(CppTemplateExpression {
15838 text: default.text.clone(),
15839 term,
15840 });
15841 }
15842 if let Some(index) = variadic_index {
15843 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
15844 expanded.extend(
15845 packed_arguments
15846 .iter()
15847 .map(cpp_clone_template_expression_iterative),
15848 );
15849 bindings.insert(
15850 parameters[index].name.clone(),
15851 CppTemplateTerm::Node {
15852 kind: "parameter_pack".to_string(),
15853 children: packed_arguments
15854 .iter()
15855 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
15856 .collect(),
15857 },
15858 );
15859 }
15860 Some((expanded, bindings))
15861}
15862
15863fn cpp_specialization_matches(
15864 metadata: &CppTemplateMetadata,
15865 arguments: &[CppTemplateExpression],
15866) -> bool {
15867 if metadata.specialization_arguments.len() != arguments.len() {
15868 return false;
15869 }
15870 let parameter_names = metadata
15871 .parameters
15872 .iter()
15873 .map(|parameter| parameter.name.as_str())
15874 .collect::<HashSet<_>>();
15875 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
15876 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
15877 if !cpp_unify_template_term(
15878 &pattern.term,
15879 &argument.term,
15880 ¶meter_names,
15881 &mut bindings,
15882 ) {
15883 return false;
15884 }
15885 }
15886 true
15887}
15888
15889fn cpp_specialization_more_specialized(
15890 candidate: &CppTemplateMetadata,
15891 other: &CppTemplateMetadata,
15892) -> bool {
15893 cpp_specialization_pattern_accepts(other, candidate)
15894 && !cpp_specialization_pattern_accepts(candidate, other)
15895}
15896
15897fn cpp_specialization_pattern_accepts(
15898 broader: &CppTemplateMetadata,
15899 narrower: &CppTemplateMetadata,
15900) -> bool {
15901 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
15902 return false;
15903 }
15904 let parameter_names = broader
15905 .parameters
15906 .iter()
15907 .map(|parameter| parameter.name.as_str())
15908 .collect::<HashSet<_>>();
15909 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
15910 broader
15911 .specialization_arguments
15912 .iter()
15913 .zip(&narrower.specialization_arguments)
15914 .all(|(pattern, argument)| {
15915 cpp_unify_template_term(
15916 &pattern.term,
15917 &argument.term,
15918 ¶meter_names,
15919 &mut bindings,
15920 )
15921 })
15922}
15923
15924pub fn cpp_substitute_template_term(
15925 term: &CppTemplateTerm,
15926 bindings: &HashMap<String, CppTemplateTerm>,
15927) -> Option<CppTemplateTerm> {
15928 enum Work<'a> {
15929 Visit(&'a CppTemplateTerm),
15930 Build { kind: String, child_count: usize },
15931 }
15932
15933 let mut work = vec![Work::Visit(term)];
15934 let mut substituted = Vec::new();
15935 while let Some(next) = work.pop() {
15936 match next {
15937 Work::Visit(CppTemplateTerm::Parameter(name)) => {
15938 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
15939 }
15940 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
15941 substituted.push(CppTemplateTerm::Atom {
15942 kind: kind.clone(),
15943 text: text.clone(),
15944 });
15945 }
15946 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
15947 work.push(Work::Build {
15948 kind: kind.clone(),
15949 child_count: children.len(),
15950 });
15951 work.extend(children.iter().rev().map(Work::Visit));
15952 }
15953 Work::Build { kind, child_count } => {
15954 let children = substituted.split_off(substituted.len() - child_count);
15955 substituted.push(CppTemplateTerm::Node { kind, children });
15956 }
15957 }
15958 }
15959 substituted.pop()
15960}
15961
15962pub fn cpp_substitute_template_arguments(
15963 arguments: &[CppTemplateExpression],
15964 bindings: &HashMap<String, CppTemplateTerm>,
15965) -> Option<Vec<CppTemplateExpression>> {
15966 let mut substituted = Vec::new();
15967 for argument in arguments {
15968 let CppTemplateTerm::Node { kind, children } = &argument.term else {
15969 substituted.push(CppTemplateExpression {
15970 text: argument.text.clone(),
15971 term: cpp_substitute_template_term(&argument.term, bindings)?,
15972 });
15973 continue;
15974 };
15975 if kind != "parameter_pack_expansion" {
15976 substituted.push(CppTemplateExpression {
15977 text: argument.text.clone(),
15978 term: cpp_substitute_template_term(&argument.term, bindings)?,
15979 });
15980 continue;
15981 }
15982 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
15983 return None;
15984 };
15985 if ellipsis != "..." {
15986 return None;
15987 }
15988
15989 let mut pack_names = Vec::new();
15990 let mut work = vec![pattern];
15991 while let Some(term) = work.pop() {
15992 match term {
15993 CppTemplateTerm::Parameter(name)
15994 if matches!(
15995 bindings.get(name),
15996 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
15997 ) =>
15998 {
15999 if !pack_names.contains(name) {
16000 pack_names.push(name.clone());
16001 }
16002 }
16003 CppTemplateTerm::Node { children, .. } => work.extend(children),
16004 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16005 }
16006 }
16007 let first_pack = pack_names.first()?;
16008 let CppTemplateTerm::Node {
16009 children: first_elements,
16010 ..
16011 } = bindings.get(first_pack)?
16012 else {
16013 return None;
16014 };
16015 let pack_len = first_elements.len();
16016 for pack_name in &pack_names {
16017 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16018 return None;
16019 };
16020 if children.len() != pack_len {
16021 return None;
16022 }
16023 }
16024 for index in 0..pack_len {
16025 let mut element_bindings = bindings.clone();
16026 for pack_name in &pack_names {
16027 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16028 return None;
16029 };
16030 element_bindings.insert(
16031 pack_name.clone(),
16032 cpp_clone_template_term_iterative(&children[index]),
16033 );
16034 }
16035 substituted.push(CppTemplateExpression {
16036 text: argument.text.clone(),
16037 term: cpp_substitute_template_term(pattern, &element_bindings)?,
16038 });
16039 }
16040 }
16041 Some(substituted)
16042}
16043
16044fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16045 enum Work<'a> {
16046 Visit(&'a CppTemplateTerm),
16047 Build { kind: String, child_count: usize },
16048 }
16049
16050 let mut work = vec![Work::Visit(term)];
16051 let mut cloned = Vec::new();
16052 while let Some(next) = work.pop() {
16053 match next {
16054 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16055 cloned.push(CppTemplateTerm::Parameter(name.clone()));
16056 }
16057 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16058 cloned.push(CppTemplateTerm::Atom {
16059 kind: kind.clone(),
16060 text: text.clone(),
16061 });
16062 }
16063 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16064 work.push(Work::Build {
16065 kind: kind.clone(),
16066 child_count: children.len(),
16067 });
16068 work.extend(children.iter().rev().map(Work::Visit));
16069 }
16070 Work::Build { kind, child_count } => {
16071 let children = cloned.split_off(cloned.len() - child_count);
16072 cloned.push(CppTemplateTerm::Node { kind, children });
16073 }
16074 }
16075 }
16076 cloned
16077 .pop()
16078 .expect("template term traversal emits one root")
16079}
16080
16081fn cpp_clone_template_expression_iterative(
16082 expression: &CppTemplateExpression,
16083) -> CppTemplateExpression {
16084 CppTemplateExpression {
16085 text: expression.text.clone(),
16086 term: cpp_clone_template_term_iterative(&expression.term),
16087 }
16088}
16089
16090pub fn cpp_unify_template_term(
16091 pattern: &CppTemplateTerm,
16092 argument: &CppTemplateTerm,
16093 parameters: &HashSet<&str>,
16094 bindings: &mut HashMap<String, CppTemplateTerm>,
16095) -> bool {
16096 let mut work = vec![(pattern, argument)];
16097 while let Some((pattern, argument)) = work.pop() {
16098 match pattern {
16099 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16100 if let Some(bound) = bindings.get(name) {
16101 if !cpp_template_terms_equal(bound, argument) {
16102 return false;
16103 }
16104 } else {
16105 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16106 }
16107 }
16108 CppTemplateTerm::Atom {
16109 kind: pattern_kind,
16110 text: pattern_text,
16111 } => {
16112 if !matches!(
16113 argument,
16114 CppTemplateTerm::Atom { kind, text }
16115 if kind == pattern_kind && text == pattern_text
16116 ) {
16117 return false;
16118 }
16119 }
16120 CppTemplateTerm::Node {
16121 kind: pattern_kind,
16122 children: pattern_children,
16123 } => {
16124 let CppTemplateTerm::Node { kind, children } = argument else {
16125 return false;
16126 };
16127 if kind != pattern_kind || children.len() != pattern_children.len() {
16128 return false;
16129 }
16130 work.extend(pattern_children.iter().zip(children).rev());
16131 }
16132 CppTemplateTerm::Parameter(_) => return false,
16133 }
16134 }
16135 true
16136}
16137
16138fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16139 let mut work = vec![(left, right)];
16140 while let Some((left, right)) = work.pop() {
16141 match (left, right) {
16142 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16143 if left != right {
16144 return false;
16145 }
16146 }
16147 (
16148 CppTemplateTerm::Atom {
16149 kind: left_kind,
16150 text: left_text,
16151 },
16152 CppTemplateTerm::Atom {
16153 kind: right_kind,
16154 text: right_text,
16155 },
16156 ) => {
16157 if left_kind != right_kind || left_text != right_text {
16158 return false;
16159 }
16160 }
16161 (
16162 CppTemplateTerm::Node {
16163 kind: left_kind,
16164 children: left_children,
16165 },
16166 CppTemplateTerm::Node {
16167 kind: right_kind,
16168 children: right_children,
16169 },
16170 ) => {
16171 if left_kind != right_kind || left_children.len() != right_children.len() {
16172 return false;
16173 }
16174 work.extend(left_children.iter().zip(right_children).rev());
16175 }
16176 _ => return false,
16177 }
16178 }
16179 true
16180}
16181
16182pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16183 let mut components = Vec::new();
16184 let mut stack = vec![node];
16185 while let Some(current) = stack.pop() {
16186 match current.kind() {
16187 "identifier"
16188 | "field_identifier"
16189 | "namespace_identifier"
16190 | "type_identifier"
16191 | "operator_name"
16192 | "destructor_name" => components.push(current),
16193 "template_type" | "template_function" => {
16194 stack.push(current.child_by_field_name("name")?);
16195 }
16196 "dependent_name" => stack.push(current.named_child(0)?),
16197 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16198 stack.push(current.child_by_field_name("name")?);
16199 if let Some(scope) = current.child_by_field_name("scope") {
16200 stack.push(scope);
16201 }
16202 }
16203 "nested_namespace_specifier" => {
16204 for index in (0..current.named_child_count()).rev() {
16205 stack.push(current.named_child(index)?);
16206 }
16207 }
16208 _ => return None,
16209 }
16210 }
16211 Some(components)
16212}
16213
16214pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16215 node.child_by_field_name("scope").is_none()
16216 && node.child(0).is_some_and(|child| child.kind() == "::")
16217}
16218
16219fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16220 let mut namespaces = Vec::new();
16221 let mut current = node.parent();
16222 while let Some(parent) = current {
16223 if parent.kind() == "namespace_definition"
16224 && let Some(name) = parent.child_by_field_name("name")
16225 {
16226 let mut components = Vec::new();
16227 append_cpp_name_components(name, source, &mut components)?;
16228 namespaces.push(components);
16229 }
16230 current = parent.parent();
16231 }
16232 namespaces.reverse();
16233 Some(namespaces.into_iter().flatten().collect())
16234}
16235
16236fn indexed_namespace_path_is_recoverable(
16247 lexical_scope: &[String],
16248 indexed_owner_scope: &[String],
16249 explicit_owner_component_count: usize,
16250) -> bool {
16251 if lexical_scope.is_empty() {
16252 return explicit_owner_component_count > 1;
16253 }
16254 if lexical_scope.len() >= indexed_owner_scope.len() {
16255 return false;
16256 }
16257 let mut indexed = indexed_owner_scope.iter();
16258 lexical_scope
16259 .iter()
16260 .all(|component| indexed.any(|candidate| candidate == component))
16261}
16262
16263pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16264 let mut current = node.parent();
16265 while let Some(parent) = current {
16266 if parent.kind() == kind {
16267 return true;
16268 }
16269 current = parent.parent();
16270 }
16271 false
16272}
16273
16274pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16280 let mut current = Some(node);
16281 while let Some(candidate) = current {
16282 if candidate.kind() == "declaration" {
16283 let Some(type_node) = candidate.child_by_field_name("type") else {
16284 return false;
16285 };
16286 if !(type_node.start_byte() <= node.start_byte()
16287 && node.end_byte() <= type_node.end_byte())
16288 {
16289 return false;
16290 }
16291 let mut cursor = candidate.walk();
16292 return candidate.named_children(&mut cursor).any(|child| {
16293 child.kind() == "init_declarator"
16294 && child
16295 .child_by_field_name("value")
16296 .is_some_and(|value| value.kind() == "cast_expression")
16297 });
16298 }
16299 current = candidate.parent();
16300 }
16301 false
16302}
16303
16304#[derive(Clone, Copy, PartialEq, Eq)]
16305pub(crate) enum QualifiedAliasReferenceKind {
16306 Ordinary,
16307 ConstructorWithExpressionArgument,
16308 ExhaustiveTemplate,
16309}
16310
16311pub(crate) fn qualified_alias_reference_preserves_target(
16318 node: Node<'_>,
16319 target: &CodeUnit,
16320 analyzer: &CppGraphSource<'_>,
16321 visibility: &VisibilityIndex<'_>,
16322 file: &ProjectFile,
16323 source: &str,
16324) -> Option<QualifiedAliasReferenceKind> {
16325 if !matches!(
16326 node.kind(),
16327 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16328 ) {
16329 return None;
16330 }
16331 let components = cpp_type_name_components(node, source)?;
16332 let name = components.last()?;
16333 analyzer.type_alias_provider().and_then(|provider| {
16334 visibility
16335 .visible_identifier_candidates(file, name)
16336 .find_map(|candidate| {
16337 let proof = provider.is_type_alias(candidate)
16338 && canonical_cpp_scope_components(candidate) == components
16339 && visibility.external_type_candidate_visible_in_context(
16340 analyzer, file, candidate, node,
16341 )
16342 && match cpp_template_reference_arguments(node, source) {
16343 Some(arguments) => visibility.template_alias_arguments_preserve_target(
16344 analyzer, file, candidate, &arguments, target,
16345 ),
16346 None => visibility.structured_alias_primary_preserves_target(
16347 analyzer, file, candidate, target,
16348 ),
16349 };
16350 proof.then(|| {
16351 if cpp_template_reference_arguments(node, source).is_some()
16352 && visibility.is_exhaustive_same_fqn_type_declaration_family(
16353 analyzer, file, candidate,
16354 )
16355 {
16356 QualifiedAliasReferenceKind::ExhaustiveTemplate
16357 } else if qualified_alias_constructor_has_expression_argument(node)
16358 || qualified_alias_local_constructor_declaration(node)
16359 {
16360 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16361 } else {
16362 QualifiedAliasReferenceKind::Ordinary
16363 }
16364 })
16365 })
16366 })
16367}
16368
16369pub(crate) fn qualified_alias_reference_requires_terminal(
16370 reference: Option<QualifiedAliasReferenceKind>,
16371) -> bool {
16372 matches!(
16373 reference,
16374 Some(
16375 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16376 | QualifiedAliasReferenceKind::ExhaustiveTemplate
16377 )
16378 )
16379}
16380
16381fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16382 let Some(declaration) = node.parent().filter(|parent| {
16383 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16384 }) else {
16385 return false;
16386 };
16387 let mut cursor = declaration.walk();
16388 declaration.named_children(&mut cursor).any(|child| {
16389 child.kind() == "init_declarator"
16390 && child
16391 .child_by_field_name("value")
16392 .filter(|value| value.kind() == "argument_list")
16393 .is_some_and(|arguments| {
16394 let mut cursor = arguments.walk();
16395 arguments.named_children(&mut cursor).any(|argument| {
16396 let is_parameter = matches!(
16397 argument.kind(),
16398 "parameter_declaration" | "optional_parameter_declaration"
16399 );
16400 if is_parameter {
16401 argument
16402 .child_by_field_name("type")
16403 .is_some_and(|type_node| {
16404 type_node.kind() == "type_identifier"
16405 && argument.child_by_field_name("declarator").is_none()
16406 })
16407 } else {
16408 !argument.kind().ends_with("_literal")
16409 && !matches!(argument.kind(), "true" | "false" | "nullptr")
16410 }
16411 })
16412 })
16413 })
16414}
16415
16416fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16421 let Some(declaration) = node.parent().filter(|parent| {
16422 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16423 }) else {
16424 return false;
16425 };
16426 if declaration
16427 .parent()
16428 .is_none_or(|parent| parent.kind() != "compound_statement")
16429 {
16430 return false;
16431 }
16432 let mut cursor = declaration.walk();
16433 declaration
16434 .named_children(&mut cursor)
16435 .any(|child| child.kind() == "function_declarator")
16436}
16437
16438pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16444 loop {
16445 let next = match node.kind() {
16446 "qualified_identifier"
16447 | "scoped_identifier"
16448 | "template_method"
16449 | "template_function"
16450 | "template_type" => node.child_by_field_name("name"),
16451 "field_expression" => node.child_by_field_name("field"),
16452 _ => None,
16453 };
16454 let Some(next) = next else {
16455 return node;
16456 };
16457 node = next;
16458 }
16459}
16460
16461#[derive(Clone, Copy)]
16462pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16463 pub receiver: Node<'tree>,
16464 pub member: Node<'tree>,
16465 pub arity: usize,
16466}
16467
16468pub fn recovered_relational_template_member_call(
16476 field: Node<'_>,
16477) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16478 if field.kind() != "field_expression" {
16479 return None;
16480 }
16481 let receiver = field
16482 .child_by_field_name("argument")
16483 .or_else(|| field.child_by_field_name("object"))?;
16484 let member = field.child_by_field_name("field")?;
16485 let less = field.parent()?;
16486 if less.kind() != "binary_expression"
16487 || less.child_by_field_name("left") != Some(field)
16488 || less
16489 .child_by_field_name("operator")
16490 .is_none_or(|operator| operator.kind() != "<")
16491 || less.child_by_field_name("right").is_none()
16492 {
16493 return None;
16494 }
16495 let greater = less.parent()?;
16496 if greater.kind() != "binary_expression"
16497 || greater.child_by_field_name("left") != Some(less)
16498 || greater
16499 .child_by_field_name("operator")
16500 .is_none_or(|operator| operator.kind() != ">")
16501 {
16502 return None;
16503 }
16504 let arguments = greater.child_by_field_name("right")?;
16505 if arguments.kind() != "parenthesized_expression" {
16506 return None;
16507 }
16508 let arity = parenthesized_call_argument_arity(arguments)?;
16509 Some(RecoveredRelationalTemplateMemberCall {
16510 receiver,
16511 member,
16512 arity,
16513 })
16514}
16515
16516fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
16517 let expression = arguments.named_child(0)?;
16518 if expression.kind() != "comma_expression" {
16519 return Some(1);
16520 }
16521 let mut arity = 0usize;
16522 let mut stack = vec![expression];
16523 while let Some(node) = stack.pop() {
16524 if node.kind() == "comma_expression" {
16525 stack.push(node.child_by_field_name("right")?);
16526 stack.push(node.child_by_field_name("left")?);
16527 } else {
16528 arity += 1;
16529 }
16530 }
16531 Some(arity)
16532}
16533
16534pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
16537 while let Some(parent) = node.parent() {
16538 match parent.kind() {
16539 "call_expression" => {
16540 return parent
16541 .child_by_field_name("function")
16542 .or_else(|| parent.named_child(0))
16543 == Some(node);
16544 }
16545 "qualified_identifier"
16546 | "scoped_identifier"
16547 | "template_function"
16548 | "template_type"
16549 | "field_expression" => node = parent,
16550 _ => return false,
16551 }
16552 }
16553 false
16554}
16555
16556pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
16557 if is_call_callee_node(node) {
16558 function_terminal_node(node)
16559 } else {
16560 node
16561 }
16562}
16563
16564pub fn normalize_type_text(value: &str) -> String {
16565 strip_tag_type_prefix(
16566 normalize_cpp_whitespace(value)
16567 .trim_start_matches("const ")
16568 .trim_end_matches('*')
16569 .trim_end_matches('&')
16570 .trim(),
16571 )
16572 .to_string()
16573}
16574
16575fn strip_tag_type_prefix(value: &str) -> &str {
16576 let value = value.trim_start_matches("const ");
16577 value
16578 .strip_prefix("struct ")
16579 .or_else(|| value.strip_prefix("class "))
16580 .or_else(|| value.strip_prefix("enum "))
16581 .unwrap_or(value)
16582 .trim()
16583}
16584
16585pub fn normalize_reference_name(value: &str) -> Option<String> {
16586 let normalized = normalize_cpp_reference_text(value);
16587 (!normalized.is_empty()).then_some(normalized)
16588}
16589
16590pub fn normalize_cpp_reference_text(value: &str) -> String {
16591 let mut text = normalize_cpp_whitespace(value)
16592 .trim_start_matches("new ")
16593 .trim()
16594 .to_string();
16595 if let Some(index) = text.find(['(', '{']) {
16596 text.truncate(index);
16597 }
16598 if let Some(index) = text.find('<') {
16599 text.truncate(index);
16600 }
16601 let normalized = text
16602 .trim()
16603 .trim_start_matches("const ")
16604 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
16605 .trim_matches(':')
16606 .trim();
16607 strip_tag_type_prefix(normalized).to_string()
16608}
16609
16610pub fn cpp_name_for(unit: &CodeUnit) -> String {
16611 let short = unit.short_name().replace(['.', '$'], "::");
16612 if unit.package_name().is_empty() {
16613 short
16614 } else {
16615 format!("{}::{}", unit.package_name(), short)
16616 }
16617}
16618
16619fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
16623 let fq = unit.fq();
16624 if fq.is_empty() {
16625 return None;
16626 }
16627 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16628 Some(
16629 fq.segments()
16630 .iter()
16631 .map(|&segment| interner.resolve(segment).0)
16632 .collect::<Vec<_>>()
16633 .join("::"),
16634 )
16635}
16636
16637fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
16638 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
16639 || unit.fq().is_empty() && cpp_name_for(unit) == expected
16640}
16641
16642pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
16651 let fq = unit.fq();
16652 if !fq.is_empty() {
16653 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16654 let scope = fq
16655 .segments()
16656 .iter()
16657 .filter_map(|&segment| {
16658 let (text, kind) = interner.resolve(segment);
16659 matches!(
16660 kind,
16661 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
16662 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
16663 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
16664 )
16665 .then(|| text.to_string())
16666 })
16667 .collect();
16668 return scope;
16669 }
16670 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16671 brokk_bifrost_core::analyzer::Language::Cpp,
16672 &cpp_name_for(unit),
16673 )
16674}
16675
16676pub fn terminal_name(value: &str) -> &str {
16687 value
16688 .rsplit("::")
16689 .next()
16690 .unwrap_or(value)
16691 .rsplit(['.', '-', '>'])
16692 .next()
16693 .unwrap_or(value)
16694 .trim()
16695}
16696
16697pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
16698 terminal_name(&normalize_cpp_reference_text(value)) == expected
16699}
16700
16701pub fn name_matches_callable(value: &str, expected: &str) -> bool {
16702 name_matches_terminal(value, expected)
16703 || expected.starts_with("operator")
16704 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
16705}
16706
16707pub fn name_mentions(value: &str, expected: &str) -> bool {
16708 normalize_cpp_reference_text(value)
16709 .split("::")
16710 .any(|part| part == expected)
16711}
16712
16713pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
16714 let cpp_name = cpp_name_for(unit);
16715 if reference.contains("::") {
16716 return reference == cpp_name;
16717 }
16718 reference == cpp_name
16719 || terminal_name(reference) == unit.identifier()
16720 && (unit.package_name().is_empty() || reference == unit.identifier())
16721}
16722
16723pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
16724 match kind {
16725 TargetKind::Type
16726 | TargetKind::Constructor
16727 | TargetKind::Method
16728 | TargetKind::MemberField => true,
16729 TargetKind::FreeFunction => unit.is_function(),
16730 TargetKind::GlobalField => unit.is_field(),
16731 TargetKind::Macro => unit.is_macro(),
16732 }
16733}
16734
16735pub fn is_type_alias(unit: &CodeUnit) -> bool {
16736 unit.kind() == CodeUnitType::Field
16737 && unit.signature().is_some_and(|signature| {
16738 signature.starts_with("typedef ") || signature.starts_with("using ")
16739 })
16740}
16741
16742fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
16743 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
16744 let target_name = cpp_name_for(target);
16745 if normalized.contains("::") {
16746 return normalized == target_name;
16747 }
16748 if let Some(namespace) = alias.namespace.as_deref() {
16749 return namespace_prefixes(namespace)
16750 .into_iter()
16751 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
16752 }
16753 target.package_name().is_empty() && normalized == target.identifier()
16754}
16755
16756pub fn cpp_function_return_type_text(
16759 analyzer: &CppGraphSource<'_>,
16760 function: &CodeUnit,
16761) -> Option<String> {
16762 let metadata = analyzer.signature_metadata(function);
16763 if !metadata.is_empty() {
16764 let first = metadata.first()?.return_type_text()?;
16765 return metadata
16766 .iter()
16767 .all(|metadata| metadata.return_type_text() == Some(first))
16768 .then(|| first.to_string());
16769 }
16770 let signature = cpp_function_signature_text(analyzer, function)?;
16771 cpp_function_return_type_text_from_signature(&signature)
16772}
16773
16774fn cpp_function_signature_text(
16775 analyzer: &CppGraphSource<'_>,
16776 function: &CodeUnit,
16777) -> Option<String> {
16778 function
16779 .signature()
16780 .filter(|signature| signature.contains(function.identifier()))
16781 .map(str::to_string)
16782 .or_else(|| analyzer.signatures(function).first().cloned())
16783 .or_else(|| analyzer.get_source(function, false))
16784}
16785
16786fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
16787 let open = signature.find('(')?;
16788 let name_at = cpp_function_name_start(signature, open)?;
16789 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
16790 return Some(return_type);
16791 }
16792 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
16793 .split_whitespace()
16794 .filter(|token| {
16795 !matches!(
16796 *token,
16797 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
16798 )
16799 })
16800 .collect::<Vec<_>>()
16801 .join(" ");
16802 let type_text = type_text.trim();
16803 (!type_text.is_empty()).then(|| type_text.to_string())
16804}
16805
16806fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
16807 let before_parameters = &signature[..open];
16808 if let Some(operator_at) = before_parameters.rfind("operator") {
16809 let boundary = operator_at == 0
16810 || before_parameters[..operator_at]
16811 .chars()
16812 .next_back()
16813 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
16814 if boundary {
16815 return Some(operator_at);
16816 }
16817 }
16818 before_parameters
16819 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
16820 .map(|index| index + 1)
16821}
16822
16823fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
16824 let open = signature_from_name.find('(')?;
16825 let mut depth = 0i32;
16826 for (offset, ch) in signature_from_name[open..].char_indices() {
16827 match ch {
16828 '(' => depth += 1,
16829 ')' => {
16830 depth -= 1;
16831 if depth == 0 {
16832 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
16833 let arrow = rest.find("->")?;
16834 let return_type = rest[arrow + 2..].trim_start();
16835 let return_type = return_type
16836 .split(['{', ';'])
16837 .next()
16838 .unwrap_or(return_type)
16839 .trim();
16840 return (!return_type.is_empty()).then(|| return_type.to_string());
16841 }
16842 }
16843 _ => {}
16844 }
16845 }
16846 None
16847}
16848
16849fn cpp_strip_leading_template_clause(text: &str) -> &str {
16852 let trimmed = text.trim_start();
16853 let Some(rest) = trimmed.strip_prefix("template") else {
16854 return text;
16855 };
16856 let rest = rest.trim_start();
16857 if !rest.starts_with('<') {
16858 return text;
16859 }
16860 let mut depth = 0i32;
16861 for (offset, ch) in rest.char_indices() {
16862 match ch {
16863 '<' => depth += 1,
16864 '>' => {
16865 depth -= 1;
16866 if depth == 0 {
16867 return rest[offset + ch.len_utf8()..].trim_start();
16868 }
16869 }
16870 _ => {}
16871 }
16872 }
16873 text
16874}
16875
16876pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
16877 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
16887 namespace
16888 .strip_prefix("anonymous_namespace::")
16889 .unwrap_or(namespace)
16890 .to_string()
16891 })
16892}
16893
16894fn namespace_prefixes(namespace: &str) -> Vec<String> {
16895 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16901 brokk_bifrost_core::analyzer::Language::Cpp,
16902 namespace,
16903 );
16904 let mut prefixes = Vec::new();
16905 while !parts.is_empty() {
16906 prefixes.push(parts.join("::"));
16907 parts.pop();
16908 }
16909 prefixes
16910}
16911
16912fn nearest_namespace_candidates(
16913 candidates: Vec<CodeUnit>,
16914 normalized: &str,
16915 lexical_namespace: Option<&str>,
16916) -> Vec<CodeUnit> {
16917 if normalized.contains("::") {
16918 return candidates;
16919 }
16920 if let Some(namespace) = lexical_namespace {
16921 for prefix in namespace_prefixes(namespace) {
16922 let scoped = candidates
16923 .iter()
16924 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
16925 .cloned()
16926 .collect::<Vec<_>>();
16927 if !scoped.is_empty() {
16928 return scoped;
16929 }
16930 }
16931 }
16932 candidates
16933 .into_iter()
16934 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
16935 .collect()
16936}
16937
16938pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
16939 let mut namespaces = Vec::new();
16940 let mut current = node.parent();
16941 while let Some(parent) = current {
16942 if parent.kind() == "namespace_definition"
16943 && let Some(name) = parent.child_by_field_name("name")
16944 {
16945 let namespace = normalize_cpp_reference_text(node_text(name, source));
16946 if !namespace.is_empty() {
16947 namespaces.push(namespace);
16948 }
16949 }
16950 current = parent.parent();
16951 }
16952 if namespaces.is_empty() {
16953 None
16954 } else {
16955 namespaces.reverse();
16956 Some(namespaces.join("::"))
16957 }
16958}
16959
16960pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
16964 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
16965}
16966
16967fn type_owner_resolution(
16968 analyzer: &CppGraphSource<'_>,
16969 code_unit: &CodeUnit,
16970) -> Option<ResolvedTypeOwner> {
16971 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
16972}
16973
16974fn target_type_owner_resolution(
16975 analyzer: &CppGraphSource<'_>,
16976 code_unit: &CodeUnit,
16977) -> Option<ResolvedTypeOwner> {
16978 match type_owner_resolution(analyzer, code_unit) {
16979 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
16980 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
16981 }
16982}
16983
16984fn target_forward_owner_resolution(
16996 analyzer: &CppGraphSource<'_>,
16997 code_unit: &CodeUnit,
16998) -> Option<ResolvedTypeOwner> {
16999 if !code_unit.is_function() {
17000 return None;
17001 }
17002 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17008 let cpp = analyzer.cpp?;
17009 let mut visible_files = HashSet::default();
17010 collect_include_closure(
17011 analyzer,
17012 cpp.include_target_index(),
17013 code_unit.source(),
17014 &mut visible_files,
17015 None,
17016 );
17017 let candidates = analyzer.workspace_definitions().exact(&owner_name);
17018 let visible_candidates = candidates
17019 .iter()
17020 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17021 .cloned()
17022 .collect::<Vec<_>>();
17023 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17024 DirectOwnerResolution::UniqueFull(unit) => {
17025 return Some(ResolvedTypeOwner {
17026 unit,
17027 is_forward_declaration: false,
17028 });
17029 }
17030 DirectOwnerResolution::ForwardsOnly(forwards) => {
17031 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17032 unit: forwards.into_iter().next().unwrap(),
17033 is_forward_declaration: true,
17034 });
17035 }
17036 DirectOwnerResolution::Ambiguous => return None,
17037 DirectOwnerResolution::None => {}
17038 }
17039
17040 let candidates = candidates
17041 .into_iter()
17042 .filter(|candidate| candidate.is_class())
17043 .collect::<Vec<_>>();
17044 let (unit, is_forward_declaration) =
17045 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17046 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17047 DirectOwnerResolution::ForwardsOnly(forwards) => {
17048 (unique_logical_forward_owner(forwards)?, true)
17049 }
17050 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17051 };
17052 Some(ResolvedTypeOwner {
17053 unit,
17054 is_forward_declaration,
17055 })
17056}
17057
17058pub fn precise_parent_of(
17059 analyzer: &CppGraphSource<'_>,
17060 visibility: &VisibilityIndex<'_>,
17061 code_unit: &CodeUnit,
17062) -> Option<CodeUnit> {
17063 visibility.cached_precise_parent_of(analyzer, code_unit)
17064}
17065
17066fn precise_parent_resolution(
17067 analyzer: &CppGraphSource<'_>,
17068 code_unit: &CodeUnit,
17069) -> Option<ResolvedTypeOwner> {
17070 #[cfg(any(test, feature = "test-support"))]
17071 if let Some(cpp) = analyzer.cpp {
17072 cpp.record_cpp_parent_resolution_for_test();
17073 }
17074 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17075 return Some(ResolvedTypeOwner {
17076 unit,
17077 is_forward_declaration: false,
17078 });
17079 }
17080 let fallback = analyzer.parent_of(code_unit);
17081 if !code_unit.owner_is_type_scope() {
17082 return fallback.map(|unit| ResolvedTypeOwner {
17083 unit,
17084 is_forward_declaration: false,
17085 });
17086 }
17087 let owner_fq = code_unit
17088 .fq()
17089 .parent()
17090 .expect("a unit with an owner identifier has a structured parent");
17091 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17092 match same_source_owner(analyzer, code_unit, &owner_candidates) {
17093 DirectOwnerResolution::UniqueFull(owner) => {
17094 return Some(ResolvedTypeOwner {
17095 unit: owner,
17096 is_forward_declaration: false,
17097 });
17098 }
17099 DirectOwnerResolution::Ambiguous => return None,
17100 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17101 }
17102 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17103 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17104 unit: owner,
17105 is_forward_declaration: false,
17106 }),
17107 DirectOwnerResolution::Ambiguous => None,
17108 DirectOwnerResolution::ForwardsOnly(forwards) => {
17109 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17110 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17111 unit: owner,
17112 is_forward_declaration: false,
17113 }),
17114 FullOwnerResolution::None => {
17115 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17116 unit,
17117 is_forward_declaration: true,
17118 })
17119 }
17120 FullOwnerResolution::Ambiguous => None,
17121 }
17122 }
17123 DirectOwnerResolution::None => {
17124 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17125 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17126 unit: owner,
17127 is_forward_declaration: false,
17128 }),
17129 FullOwnerResolution::Ambiguous => None,
17130 FullOwnerResolution::None => fallback
17131 .filter(|parent| {
17132 parent.source() == code_unit.source()
17133 && parent.fq() == &owner_fq
17134 && (!parent.is_class()
17135 || cpp_class_declaration_strength(analyzer, parent)
17136 == CppClassDeclarationStrength::Full)
17137 })
17138 .map(|unit| ResolvedTypeOwner {
17139 unit,
17140 is_forward_declaration: false,
17141 }),
17142 }
17143 }
17144 }
17145}
17146
17147fn exact_structural_type_parent(
17148 analyzer: &CppGraphSource<'_>,
17149 code_unit: &CodeUnit,
17150) -> Option<CodeUnit> {
17151 if !code_unit.is_function() && !code_unit.is_field() {
17152 return None;
17153 }
17154 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
17156 let parent = cpp.structural_parent_of(code_unit)?;
17157 (!parent.is_module()
17158 && parent.source() == code_unit.source()
17159 && parent.package_name() == code_unit.package_name()
17160 && parent.short_name() == encoded_owner)
17161 .then_some(parent)
17162}
17163
17164fn same_source_owner(
17165 analyzer: &CppGraphSource<'_>,
17166 code_unit: &CodeUnit,
17167 owner_candidates: &[CodeUnit],
17168) -> DirectOwnerResolution {
17169 let candidates = owner_candidates
17170 .iter()
17171 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17172 .cloned()
17173 .collect::<Vec<_>>();
17174 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17175 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17176}
17177
17178fn visible_full_cpp_owner(
17179 analyzer: &CppGraphSource<'_>,
17180 code_unit: &CodeUnit,
17181 owner_candidates: &[CodeUnit],
17182) -> FullOwnerResolution {
17183 let Some(cpp) = analyzer.cpp else {
17184 return FullOwnerResolution::None;
17185 };
17186 let mut visible_files = HashSet::default();
17187 collect_include_closure(
17188 analyzer,
17189 cpp.include_target_index(),
17190 code_unit.source(),
17191 &mut visible_files,
17192 None,
17193 );
17194 let candidates = owner_candidates
17195 .iter()
17196 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17197 .cloned()
17198 .collect::<Vec<_>>();
17199 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17200 let mut full_definition = None;
17201 for candidate in candidates {
17202 match cpp_class_declaration_strength(analyzer, &candidate) {
17203 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17204 return FullOwnerResolution::Ambiguous;
17205 }
17206 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17207 CppClassDeclarationStrength::Forward => {}
17208 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17209 }
17210 }
17211 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17212}
17213
17214pub enum DirectOwnerResolution {
17215 None,
17216 ForwardsOnly(Vec<CodeUnit>),
17217 UniqueFull(CodeUnit),
17218 Ambiguous,
17219}
17220
17221enum FullOwnerResolution {
17222 None,
17223 Unique(CodeUnit),
17224 Ambiguous,
17225}
17226
17227#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17228pub enum CppClassDeclarationStrength {
17229 Full,
17230 Forward,
17231 Unknown,
17232}
17233
17234fn directly_included_owner(
17235 analyzer: &CppGraphSource<'_>,
17236 code_unit: &CodeUnit,
17237 owner_candidates: &[CodeUnit],
17238) -> DirectOwnerResolution {
17239 let Some(cpp) = analyzer.cpp else {
17240 return DirectOwnerResolution::None;
17241 };
17242 let imports = analyzer.import_statements(code_unit.source());
17243 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
17244 .into_iter()
17245 .flat_map(|include| {
17246 resolve_include_targets_with_index(
17247 code_unit.source(),
17248 &include,
17249 cpp.include_target_index(),
17250 )
17251 })
17252 .collect();
17253 let candidates = owner_candidates
17254 .iter()
17255 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17256 .cloned()
17257 .collect::<Vec<_>>();
17258 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17259 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17260}
17261
17262fn prefer_member_declaring_owners(
17263 analyzer: &CppGraphSource<'_>,
17264 member: &CodeUnit,
17265 candidates: Vec<CodeUnit>,
17266) -> Vec<CodeUnit> {
17267 let matching = candidates
17268 .iter()
17269 .filter(|owner| owner_declares_member(analyzer, owner, member))
17270 .cloned()
17271 .collect::<Vec<_>>();
17272 if matching.is_empty() {
17273 candidates
17274 } else {
17275 matching
17276 }
17277}
17278
17279fn owner_declares_member(
17280 analyzer: &CppGraphSource<'_>,
17281 owner: &CodeUnit,
17282 member: &CodeUnit,
17283) -> bool {
17284 analyzer.direct_children(owner).into_iter().any(|child| {
17285 child.kind() == member.kind()
17286 && child.identifier() == member.identifier()
17287 && child.signature() == member.signature()
17288 })
17289}
17290
17291fn classify_direct_owner_candidates(
17292 analyzer: &CppGraphSource<'_>,
17293 candidates: impl Iterator<Item = CodeUnit>,
17294) -> DirectOwnerResolution {
17295 collapse_owner_candidates(candidates.map(|candidate| {
17296 let strength = cpp_class_declaration_strength(analyzer, &candidate);
17297 (candidate, strength)
17298 }))
17299}
17300
17301pub fn collapse_owner_candidates(
17302 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17303) -> DirectOwnerResolution {
17304 let mut full_definition = None;
17305 let mut forwards = Vec::new();
17306 for (candidate, strength) in candidates {
17307 match strength {
17308 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17309 return DirectOwnerResolution::Ambiguous;
17310 }
17311 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17312 CppClassDeclarationStrength::Forward => forwards.push(candidate),
17313 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17314 }
17315 }
17316 if let Some(owner) = full_definition {
17317 DirectOwnerResolution::UniqueFull(owner)
17318 } else if !forwards.is_empty() {
17319 DirectOwnerResolution::ForwardsOnly(forwards)
17320 } else {
17321 DirectOwnerResolution::None
17322 }
17323}
17324
17325#[cfg(any(test, feature = "test-support"))]
17326pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17327 unique_logical_forward_owner(forwards)
17328}
17329
17330fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17331 let first = forwards.pop()?;
17332 forwards
17333 .iter()
17334 .all(|forward| same_logical_symbol(forward, &first))
17335 .then_some(first)
17336}
17337
17338pub fn cpp_class_declaration_strength(
17339 analyzer: &CppGraphSource<'_>,
17340 candidate: &CodeUnit,
17341) -> CppClassDeclarationStrength {
17342 let Some(cpp) = analyzer.cpp else {
17350 return uncached_cpp_class_declaration_strength(analyzer, candidate);
17351 };
17352 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17353 return strength;
17354 }
17355 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17356 cpp.cache_class_declaration_strength(candidate, strength);
17357 strength
17358}
17359
17360fn uncached_cpp_class_declaration_strength(
17361 analyzer: &CppGraphSource<'_>,
17362 candidate: &CodeUnit,
17363) -> CppClassDeclarationStrength {
17364 if let Some(cpp) = analyzer.cpp
17365 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17366 {
17367 return cpp_class_declaration_strength_in_tree(
17368 analyzer,
17369 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17370 candidate,
17371 prepared.source(),
17372 prepared.tree().root_node(),
17373 );
17374 }
17375 let Some(source) = analyzer.indexed_source(candidate.source()) else {
17376 return CppClassDeclarationStrength::Unknown;
17377 };
17378 #[cfg(any(test, feature = "test-support"))]
17379 if let Some(cpp) = analyzer.cpp {
17380 cpp.record_cpp_class_strength_parse_for_test();
17381 }
17382 let mut parser = Parser::new();
17383 if parser
17384 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17385 .is_err()
17386 {
17387 return CppClassDeclarationStrength::Unknown;
17388 }
17389 let Some(tree) = parser.parse(&source, None) else {
17390 return CppClassDeclarationStrength::Unknown;
17391 };
17392 let recovered_export_classes =
17395 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17396 cpp_class_declaration_strength_in_tree(
17397 analyzer,
17398 &recovered_export_classes,
17399 candidate,
17400 &source,
17401 tree.root_node(),
17402 )
17403}
17404
17405fn cpp_class_declaration_strength_in_tree(
17406 analyzer: &CppGraphSource<'_>,
17407 recovered_export_classes: &CppRecoveredExportClassIndex,
17408 candidate: &CodeUnit,
17409 source: &str,
17410 root: Node<'_>,
17411) -> CppClassDeclarationStrength {
17412 let ranges = analyzer.ranges(candidate);
17413 let mut saw_forward = false;
17414 for range in ranges {
17415 match recovered_class_body_at(
17418 recovered_export_classes,
17419 root,
17420 source,
17421 candidate.identifier(),
17422 &range,
17423 ) {
17424 Some(true) => return CppClassDeclarationStrength::Full,
17425 Some(false) => {
17426 saw_forward = true;
17427 continue;
17428 }
17429 None => {}
17430 }
17431 let covers_range_start = |node: &Node<'_>| {
17438 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17439 };
17440 let mut stack = Vec::new();
17441 if covers_range_start(&root) {
17442 stack.push(root);
17443 }
17444 while let Some(node) = stack.pop() {
17445 if node.start_byte() == range.start_byte
17446 && node.end_byte() == range.end_byte
17447 && matches!(
17448 node.kind(),
17449 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17450 )
17451 {
17452 if cpp_class_node_has_body(node) {
17453 return CppClassDeclarationStrength::Full;
17454 }
17455 saw_forward = true;
17456 }
17457 let mut cursor = node.walk();
17458 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17459 }
17460 }
17461 if saw_forward {
17462 CppClassDeclarationStrength::Forward
17463 } else {
17464 CppClassDeclarationStrength::Unknown
17465 }
17466}
17467
17468fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17469 node.child_by_field_name("body").is_some() || {
17470 let mut cursor = node.walk();
17471 node.named_children(&mut cursor).any(|child| {
17472 matches!(
17473 child.kind(),
17474 "declaration_list" | "field_declaration_list" | "enumerator_list"
17475 )
17476 })
17477 }
17478}
17479
17480#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17481enum CppCTagKind {
17482 Struct,
17483 Union,
17484}
17485
17486fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
17487 let declaration = analyzer.get_source(code_unit, false)?;
17488 let mut parser = Parser::new();
17489 parser
17490 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17491 .ok()?;
17492 let tree = parser.parse(&declaration, None)?;
17493 let mut stack = vec![tree.root_node()];
17494 while let Some(node) = stack.pop() {
17495 let kind = match node.kind() {
17496 "struct_specifier" => CppCTagKind::Struct,
17497 "union_specifier" => CppCTagKind::Union,
17498 _ => {
17499 let mut cursor = node.walk();
17500 stack.extend(node.named_children(&mut cursor));
17501 continue;
17502 }
17503 };
17504 if node
17505 .child_by_field_name("name")
17506 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
17507 {
17508 return Some(kind);
17509 }
17510 let mut cursor = node.walk();
17511 stack.extend(node.named_children(&mut cursor));
17512 }
17513 None
17514}
17515
17516pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17517 if !code_unit.owner_is_type_scope() {
17518 return None;
17519 }
17520 let owner_fq = code_unit.fq().parent()?;
17521 ctx.analyzer
17522 .workspace_definitions()
17523 .exact(&owner_fq)
17524 .into_iter()
17525 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
17526}
17527
17528pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17529 left.kind() == right.kind()
17530 && left.fq_name() == right.fq_name()
17531 && left.signature() == right.signature()
17532 && left.source() == right.source()
17533}
17534
17535pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17536 same_symbol(left, right) || same_logical_symbol(left, right)
17537}
17538
17539pub fn same_visible_global_field_symbol(
17540 analyzer: &CppGraphSource<'_>,
17541 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
17542 left: &CodeUnit,
17543 right: &CodeUnit,
17544) -> bool {
17545 if same_symbol(left, right) {
17546 return true;
17547 }
17548 if !same_logical_symbol(left, right) {
17549 return false;
17550 }
17551 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
17552 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
17553 {
17554 left.source() == right.source()
17555 } else {
17556 true
17557 }
17558}
17559
17560fn cpp_global_field_has_internal_linkage_cached(
17561 analyzer: &CppGraphSource<'_>,
17562 cache: &mut HashMap<CodeUnit, bool>,
17563 candidate: &CodeUnit,
17564) -> bool {
17565 if let Some(internal) = cache.get(candidate) {
17566 return *internal;
17567 }
17568 #[cfg(any(test, feature = "test-support"))]
17569 note_cpp_global_field_internal_linkage_classification_for_test();
17570 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
17571 cache.insert(candidate.clone(), internal);
17572 internal
17573}
17574
17575#[cfg(any(test, feature = "test-support"))]
17576thread_local! {
17577 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17578}
17579
17580#[cfg(any(test, feature = "test-support"))]
17581fn note_cpp_global_field_internal_linkage_classification_for_test() {
17582 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17583 count.set(count.get() + 1);
17584 });
17585}
17586
17587#[cfg(any(test, feature = "test-support"))]
17588pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
17589 body: impl FnOnce() -> T,
17590) -> (T, usize) {
17591 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17592 count.set(0);
17593 let result = body();
17594 let observed = count.get();
17595 count.set(0);
17596 (result, observed)
17597 })
17598}
17599
17600pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17601 left.kind() == right.kind()
17602 && left.fq_name() == right.fq_name()
17603 && left.signature() == right.signature()
17604}
17605
17606pub fn cpp_global_field_has_internal_linkage(
17607 analyzer: &CppGraphSource<'_>,
17608 candidate: &CodeUnit,
17609) -> bool {
17610 if !candidate.is_field() || candidate.short_name().contains('.') {
17611 return false;
17612 }
17613 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
17614 return false;
17615 };
17616 match local_linkage {
17617 CppFieldLinkage::Internal => true,
17618 CppFieldLinkage::External => false,
17619 CppFieldLinkage::InternalUnlessExternalPeer => {
17620 !cpp_global_field_linkage_peers(analyzer, candidate)
17621 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
17622 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
17623 }
17624 }
17625}
17626
17627fn cpp_global_field_linkage_peers<'a>(
17628 analyzer: &CppGraphSource<'a>,
17629 candidate: &'a CodeUnit,
17630) -> impl Iterator<Item = CodeUnit> + 'a {
17631 let name = candidate.fq().clone();
17632 analyzer
17633 .workspace_definitions()
17634 .exact(&name)
17635 .into_iter()
17636 .filter(move |peer| {
17637 if peer == candidate {
17638 return false;
17639 }
17640 #[cfg(any(test, feature = "test-support"))]
17641 note_cpp_global_field_linkage_peer_inspection_for_test();
17642 same_logical_symbol(peer, candidate)
17643 })
17644}
17645
17646#[cfg(any(test, feature = "test-support"))]
17647thread_local! {
17648 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17649}
17650
17651#[cfg(any(test, feature = "test-support"))]
17652fn note_cpp_global_field_linkage_peer_inspection_for_test() {
17653 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17654 count.set(count.get() + 1);
17655 });
17656}
17657
17658#[cfg(any(test, feature = "test-support"))]
17659pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
17660 body: impl FnOnce() -> T,
17661) -> (T, usize) {
17662 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17663 count.set(0);
17664 let result = body();
17665 let observed = count.get();
17666 count.set(0);
17667 (result, observed)
17668 })
17669}
17670
17671fn cpp_global_field_declaration_linkage(
17672 analyzer: &CppGraphSource<'_>,
17673 candidate: &CodeUnit,
17674) -> Option<CppFieldLinkage> {
17675 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
17676 return Some(linkage);
17677 }
17678 let cpp = analyzer.cpp?;
17679 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
17680 return cpp_global_field_declaration_linkage_in_tree(
17681 analyzer,
17682 candidate,
17683 prepared.source(),
17684 prepared.tree().root_node(),
17685 );
17686 }
17687 let source = analyzer.indexed_source(candidate.source())?;
17688 let mut parser = Parser::new();
17689 if parser
17690 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17691 .is_err()
17692 {
17693 return None;
17694 }
17695 let tree = parser.parse(&source, None)?;
17696 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
17697}
17698
17699fn cpp_global_field_declaration_linkage_in_tree(
17700 analyzer: &CppGraphSource<'_>,
17701 candidate: &CodeUnit,
17702 source: &str,
17703 root: Node<'_>,
17704) -> Option<CppFieldLinkage> {
17705 analyzer.ranges(candidate).iter().find_map(|range| {
17706 node_for_exact_range(root, range)
17707 .and_then(enclosing_cpp_field_declaration)
17708 .map(|declaration| {
17709 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
17711 })
17712 })
17713}
17714
17715fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
17716 loop {
17717 if matches!(node.kind(), "declaration" | "field_declaration") {
17718 return Some(node);
17719 }
17720 node = node.parent()?;
17721 }
17722}
17723
17724#[cfg(test)]
17725mod tests {
17726 #[test]
17727 fn issue_3089_statement_formal_at_end_of_replacement() {
17728 let parameters = vec!["handle".to_owned(), "block".to_owned()];
17729 for replacement in [
17730 "do { header_event_t* event; if ((handle)->active) block } while (0)",
17731 "do { header_event_t* event; block } while (0)",
17732 ] {
17733 assert!(
17734 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
17735 .is_some(),
17736 "{replacement}"
17737 );
17738 }
17739 }
17740 use super::*;
17741
17742 #[test]
17743 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
17744 let source = "int size(void) { return sizeof(((Payload))); }\n";
17745 let mut parser = Parser::new();
17746 parser
17747 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17748 .expect("C++ grammar");
17749 let tree = parser.parse(source, None).expect("fixture tree");
17750 let start = source.find("Payload").expect("sizeof operand");
17751 let node = tree
17752 .root_node()
17753 .named_descendant_for_byte_range(start, start + "Payload".len())
17754 .expect("focused operand");
17755 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
17756 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
17757
17758 assert_eq!(node.kind(), "identifier");
17759 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
17760 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
17761 }
17762
17763 fn parse_cpp(source: &str) -> Tree {
17764 let mut parser = Parser::new();
17765 parser
17766 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17767 .expect("C++ grammar");
17768 parser.parse(source, None).expect("fixture tree")
17769 }
17770
17771 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
17772 let start = source.find(needle).expect("fixture needle");
17773 tree.root_node()
17774 .named_descendant_for_byte_range(start, start + needle.len())
17775 .expect("node at needle")
17776 }
17777
17778 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
17779 let mut parser = Parser::new();
17780 parser
17781 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17782 .expect("C++ grammar");
17783 let tree = parser.parse(source, None).expect("fixture tree");
17784 PreparedSyntaxTree::new(
17785 PreparedSyntaxSource::Exact(Arc::from(source)),
17786 tree,
17787 compute_line_starts(source),
17788 LanguageDialect::Standard(Language::Cpp),
17789 PreparedSourceOrigin::Disk,
17790 None,
17791 )
17792 }
17793
17794 fn unresolved_include_before(source: &str, reference: &str) -> bool {
17795 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
17796 let prepared = prepared_cpp(source);
17797 let facts = collect_structured_include_facts(&prepared);
17798 let include_targets = IncludeTargetIndex::build([&file]);
17799 has_unresolved_include_visible_before_in_prepared(
17800 &file,
17801 &prepared,
17802 &include_targets,
17803 &facts,
17804 source.find(reference).expect("reference fixture"),
17805 )
17806 }
17807
17808 #[test]
17809 fn unresolved_include_before_reference_is_visible() {
17810 let source = "#include \"missing.h\"\nint use = Missing;\n";
17811 assert!(unresolved_include_before(source, "Missing"));
17812 }
17813
17814 #[test]
17815 fn unresolved_include_after_reference_is_not_visible() {
17816 let source = "int use = Missing;\n#include \"missing.h\"\n";
17817 assert!(!unresolved_include_before(source, "Missing"));
17818 }
17819
17820 #[test]
17821 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
17822 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
17823 assert!(!unresolved_include_before(source, "Missing"));
17824 }
17825
17826 #[test]
17827 fn unresolved_include_in_current_branch_is_visible() {
17828 let source =
17829 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
17830 assert!(unresolved_include_before(source, "Missing"));
17831 }
17832
17833 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
17838namespace matchers {
17839 namespace detail {
17840 class API [[nodiscard]] First {
17841 public:
17842 int value() const { return count_ + 1; }
17843 private:
17844 int count_;
17845 };
17846 class API [[nodiscard]] Second {
17847 public:
17848 int value() const { return count_ + 2; }
17849 private:
17850 int count_;
17851 };
17852 } // namespace detail
17853
17854 template <typename T>
17855 void tail_function(MatcherBase<T> const& value);
17856
17857 class TailClass {};
17858} // namespace matchers
17859} // namespace app
17860
17861struct AfterAll {};
17862"#;
17863
17864 #[test]
17865 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
17866 let source = STOLEN_BRACE_CASCADE;
17867 let tree = parse_cpp(source);
17868 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17869
17870 let tail_class = named_node_at(&tree, source, "TailClass");
17871 assert!(
17872 !has_ancestor_kind(tail_class, "namespace_definition"),
17873 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
17874 );
17875 let displaced = named_node_at(&tree, source, "Second");
17876 assert_eq!(
17877 enclosing_namespace_components(displaced, source),
17878 Some(vec!["app".to_string(), "matchers".to_string()]),
17879 "the fixture must displace the second class out of detail"
17880 );
17881
17882 let components = |needle: &str| {
17883 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
17884 };
17885 assert_eq!(components("First"), ["app", "matchers", "detail"]);
17886 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
17887 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
17888 assert_eq!(components("tail_function"), ["app", "matchers"]);
17889 assert_eq!(components("TailClass"), ["app", "matchers"]);
17890 assert!(components("AfterAll").is_empty());
17891 }
17892
17893 #[test]
17894 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
17895 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
17896 let tree = parse_cpp(clean);
17897 assert!(!tree.root_node().has_error());
17898 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
17899
17900 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
17903 let tree = parse_cpp(damaged);
17904 assert!(tree.root_node().has_error());
17905 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
17906 assert_eq!(
17907 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
17908 ["a"]
17909 );
17910 }
17911
17912 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
17919
17920 class Target {
17921 int value_;
17922 };
17923
17924 template <typename T>
17925 class Holder {
17926 public:
17927 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
17928
17929#define HOLDER_DEFINE_OP( id, op ) \
17930 template <typename U> \
17931 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
17932 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
17933 return Target{}; \
17934 }
17935
17936 HOLDER_DEFINE_OP( equal, == )
17937#undef HOLDER_DEFINE_OP
17938 T m_lhs;
17939 };
17940
17941 class Tail {};
17942}
17943"#;
17944
17945 #[test]
17946 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
17947 let source = COLLAPSED_NAMESPACE_HEAD;
17948 let tree = parse_cpp(source);
17949 let target = named_node_at(&tree, source, "class Target");
17950
17951 assert!(
17952 !has_ancestor_kind(target, "namespace_definition"),
17953 "the fixture must reproduce the collapse: the class has no namespace ancestor"
17954 );
17955 let head = target.parent().expect("the collapsed namespace envelope");
17956 assert_eq!(
17957 head.kind(),
17958 "ERROR",
17959 "the fixture must keep the namespace head in an ERROR node"
17960 );
17961
17962 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17963 assert_eq!(
17964 index.enclosing_namespace_components(target, source),
17965 ["app"]
17966 );
17967 }
17968
17969 #[test]
17970 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
17971 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
17972 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
17973 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
17974 assert!(indexed_namespace_path_is_recoverable(
17975 &["cache".to_string()],
17976 &indexed,
17977 1,
17978 ));
17979 }
17980
17981 #[test]
17982 fn sort_lookup_units_totally_orders_every_identity_field() {
17983 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
17984 let base = CodeUnit::with_signature(
17985 file.clone(),
17986 CodeUnitType::Function,
17987 "scope",
17988 "value",
17989 Some("()".to_string()),
17990 false,
17991 );
17992 let different_kind = CodeUnit::with_signature(
17993 file.clone(),
17994 CodeUnitType::Field,
17995 "scope",
17996 "value",
17997 Some("()".to_string()),
17998 false,
17999 );
18000 let synthetic = base.with_synthetic(true);
18001
18002 let interner = segment_interner();
18003 let mut member_fq = FqName::new();
18004 member_fq.push(interner.intern("scope", SegmentKind::Package));
18005 member_fq.push(interner.intern("value", SegmentKind::Member));
18006 let different_package_boundary = CodeUnit::from_fq(
18007 file.clone(),
18008 CodeUnitType::Function,
18009 member_fq,
18010 0,
18011 Some("()".to_string()),
18012 false,
18013 );
18014
18015 let mut unknown_fq = FqName::new();
18016 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18017 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18018 let different_segment_kind = CodeUnit::from_fq(
18019 file,
18020 CodeUnitType::Function,
18021 unknown_fq,
18022 1,
18023 Some("()".to_string()),
18024 false,
18025 );
18026
18027 let input = vec![
18028 base,
18029 different_kind,
18030 synthetic,
18031 different_package_boundary,
18032 different_segment_kind,
18033 ];
18034 let mut expected = input.clone();
18035 sort_lookup_units(&mut expected);
18036 assert!(expected.windows(2).all(|pair| {
18037 let mut ordered = pair.to_vec();
18038 sort_lookup_units(&mut ordered);
18039 ordered == pair && pair[0] != pair[1]
18040 }));
18041
18042 let mut reversed = input.clone();
18043 reversed.reverse();
18044 sort_lookup_units(&mut reversed);
18045 assert_eq!(reversed, expected);
18046
18047 let mut rotated = input;
18048 rotated.rotate_left(2);
18049 sort_lookup_units(&mut rotated);
18050 assert_eq!(rotated, expected);
18051 }
18052
18053 #[test]
18054 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18055 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";
18056 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18057 let parse = |source: &str| {
18058 let mut parser = Parser::new();
18059 parser
18060 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18061 .expect("C++ grammar");
18062 parser.parse(source, None).expect("fixture tree")
18063 };
18064
18065 let tree = parse(damaged);
18066 let root = tree.root_node();
18067 let target = damaged.find("target").expect("target byte");
18068 let declaration = root
18069 .descendant_for_byte_range(target, target + "target".len())
18070 .and_then(|mut node| {
18071 loop {
18072 if node.kind() == "declaration" {
18073 break Some(node);
18074 }
18075 node = node.parent()?;
18076 }
18077 })
18078 .expect("declaration after the displaced terminator");
18079 let conditional = declaration
18080 .parent()
18081 .filter(|node| node.kind() == "preproc_ifdef")
18082 .expect("damaged inner conditional");
18083 let outer = conditional
18084 .parent()
18085 .filter(|node| node.kind() == "preproc_ifdef")
18086 .expect("ordinary outer include guard");
18087 let terminator = cpp_displaced_preprocessor_terminator(conditional)
18088 .expect("structured displaced #endif");
18089 assert_eq!(node_text(terminator, damaged), "#endif");
18090 assert!(terminator.end_byte() <= declaration.start_byte());
18091 assert!(!preprocessor_conditional_contains_descendant(
18092 conditional,
18093 declaration
18094 ));
18095 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18096 assert!(preprocessor_conditional_contains_descendant(
18097 outer,
18098 declaration
18099 ));
18100
18101 let tree = parse(guarded);
18102 let conditional = tree
18103 .root_node()
18104 .named_child(0)
18105 .filter(|node| node.kind() == "preproc_ifdef")
18106 .expect("ordinary conditional");
18107 let declaration = conditional
18108 .named_children(&mut conditional.walk())
18109 .find(|node| node.kind() == "declaration")
18110 .expect("guarded declaration");
18111 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18112 assert!(preprocessor_conditional_contains_descendant(
18113 conditional,
18114 declaration
18115 ));
18116
18117 let damaged_alternative = format!(
18118 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18119 "UNUSED(value)\n".repeat(64)
18120 );
18121 let tree = parse(&damaged_alternative);
18122 let conditional = tree
18123 .root_node()
18124 .named_child(0)
18125 .filter(|node| node.kind() == "preproc_ifdef")
18126 .expect("outer conditional with an alternative");
18127 assert!(conditional.has_error());
18128 assert!(conditional.child_by_field_name("alternative").is_some());
18129 assert!(
18130 conditional
18131 .child(conditional.child_count() - 1)
18132 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18133 );
18134 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18135
18136 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";
18137 let tree = parse(split_declaration);
18138 let root = tree.root_node();
18139 let conditional = root
18140 .named_children(&mut root.walk())
18141 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18142 .expect("split declaration conditional");
18143 let target = split_declaration
18144 .find("static int target")
18145 .expect("target byte");
18146 let boundary =
18147 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18148 assert!(boundary.end_byte <= target, "{boundary:?}");
18149 assert_eq!(boundary.end_line, 9, "{boundary:?}");
18150 let target_node = root
18151 .descendant_for_byte_range(target, target + "static".len())
18152 .expect("target node");
18153 assert!(!preprocessor_conditional_contains_descendant(
18154 conditional,
18155 target_node
18156 ));
18157 }
18158
18159 #[test]
18160 fn fragmented_reference_guard_is_recovered() {
18161 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";
18162 let mut parser = Parser::new();
18163 parser
18164 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18165 .expect("C++ grammar");
18166 let tree = parser.parse(source, None).expect("fixture tree");
18167 let start = source.rfind("helper").expect("reference byte");
18168 let node = tree
18169 .root_node()
18170 .descendant_for_byte_range(start, start + "helper".len())
18171 .expect("reference node");
18172 let mut expected = HashSet::default();
18173 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18174 vec![
18175 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18176 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18177 ],
18178 )));
18179 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18180 }
18181
18182 #[test]
18183 fn expression_defined_and_ifndef_guards_are_incompatible() {
18184 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18185 let mut parser = Parser::new();
18186 parser
18187 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18188 .expect("C++ grammar");
18189 let tree = parser.parse(source, None).expect("fixture tree");
18190 let root = tree.root_node();
18191 let selected_start = source.find("selected").expect("selected declaration");
18192 let rejected_start = source.find("rejected").expect("rejected declaration");
18193 let selected = root
18194 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18195 .expect("selected node");
18196 let rejected = root
18197 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18198 .expect("rejected node");
18199 let selected_guards =
18200 preprocessor_guard_environment(selected, source).expect("selected guards");
18201 let rejected_guards =
18202 preprocessor_guard_environment(rejected, source).expect("rejected guards");
18203
18204 assert!(
18205 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18206 "opposite spellings of one macro guard must contradict"
18207 );
18208 }
18209
18210 #[test]
18211 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18212 let source = r#"#ifdef _WIN32
18213#if defined(__cplusplus)
18214extern "C"
18215#endif
18216int platform_api(void);
18217#endif
18218
18219#ifdef _WIN32
18220static int entropy_target(void) { return 0; }
18221#else
18222#ifdef HAVE_COMMON_RANDOM
18223static int other_target(void) { return 0; }
18224#elif defined(HAVE_GETENTROPY)
18225static int entropy_target(void) { return 1; }
18226static int use_entropy(void) { return entropy_target(); }
18227#endif
18228#endif
18229"#;
18230 let mut parser = Parser::new();
18231 parser
18232 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18233 .expect("C++ grammar");
18234 let tree = parser.parse(source, None).expect("fixture tree");
18235 let start = source.rfind("entropy_target()").expect("reference");
18236 let node = tree
18237 .root_node()
18238 .descendant_for_byte_range(start, start + "entropy_target".len())
18239 .expect("reference node");
18240 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18241 assert!(
18242 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18243 "{guards:#?}"
18244 );
18245 assert!(
18246 guards.contains(&PreprocessorGuard::Undefined(
18247 "HAVE_COMMON_RANDOM".to_string()
18248 )),
18249 "{guards:#?}"
18250 );
18251 assert!(
18252 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18253 "{guards:#?}"
18254 );
18255 assert!(
18256 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18257 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18258 );
18259 }
18260
18261 #[test]
18262 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18263 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";
18264 let mut parser = Parser::new();
18265 parser
18266 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18267 .expect("C++ grammar");
18268 let tree = parser.parse(source, None).expect("fixture tree");
18269 let root = tree.root_node();
18270 let node_at = |text: &str, start: usize| {
18271 root.descendant_for_byte_range(start, start + text.len())
18272 .expect("token node")
18273 };
18274
18275 let key_start = source.find("case KEY").expect("case label") + "case ".len();
18276 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18277 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18278 assert!(!is_ordinary_macro_reference_node(node_at(
18279 "ENABLE_KEYS",
18280 guard_start,
18281 )));
18282 }
18283
18284 #[test]
18285 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18286 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";
18287 let mut parser = Parser::new();
18288 parser
18289 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18290 .expect("C++ grammar");
18291 let tree = parser.parse(source, None).expect("fixture tree");
18292 let root = tree.root_node();
18293 let definition_start = source.find("target(void)").expect("definition");
18294 let reference_start = source.rfind("target()").expect("reference");
18295 let definition = root
18296 .descendant_for_byte_range(definition_start, definition_start + "target".len())
18297 .expect("definition node");
18298 let reference = root
18299 .descendant_for_byte_range(reference_start, reference_start + "target".len())
18300 .expect("reference node");
18301 let required =
18302 preprocessor_guard_environment(definition, source).expect("definition guard");
18303 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
18304 assert!(guard_requirements_hold_at_reference(
18305 &required,
18306 Some(&active)
18307 ));
18308 }
18309
18310 #[test]
18311 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
18312 let source = "g_autoptr(FuChunkArray) self = make_array();";
18313 let mut parser = Parser::new();
18314 parser
18315 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18316 .expect("C++ grammar");
18317 let tree = parser.parse(source, None).expect("fixture tree");
18318 let statement = tree.root_node().named_child(0).expect("statement");
18319 let binding =
18320 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
18321 assert_eq!(binding.name, "self");
18322 assert_eq!(binding.type_name, "FuChunkArray");
18323 assert_eq!(binding.pointer_depth, 1);
18324
18325 let near_miss = "holder(FuChunkArray) self = make_array();";
18326 let tree = parser.parse(near_miss, None).expect("near-miss tree");
18327 let statement = tree.root_node().named_child(0).expect("statement");
18328 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
18329 }
18330
18331 #[test]
18332 fn boolean_guard_normalization_proves_equivalence_and_implication() {
18333 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
18334 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
18335 let negated_windows_branch =
18336 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
18337 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
18338 assert_eq!(negated_windows_branch, portable);
18339
18340 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
18341 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
18342 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
18343 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
18344 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
18345 assert!(fallback_branch.implies(&fallback_declaration));
18346 assert!(
18347 BooleanGuardExpression::Truthy("FEATURE".to_string())
18348 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
18349 );
18350 assert!(
18351 BooleanGuardExpression::Undefined("FEATURE".to_string())
18352 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
18353 );
18354 assert!(
18355 !BooleanGuardExpression::Defined("FEATURE".to_string())
18356 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
18357 );
18358 assert!(!fallback_declaration.implies(&fallback_branch));
18359 }
18360
18361 #[test]
18362 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
18363 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";
18364 let mut parser = Parser::new();
18365 parser
18366 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18367 .expect("C++ grammar");
18368 let tree = parser.parse(source, None).expect("fixture tree");
18369 let root = tree.root_node();
18370 let call = |marker: &str| {
18371 let start = source.find(marker).expect("call marker");
18372 let mut node = root
18373 .descendant_for_byte_range(start, start + "helper".len())
18374 .expect("call name node");
18375 loop {
18376 if node.kind() == "call_expression" {
18377 break node;
18378 }
18379 node = node.parent().expect("call expression ancestor");
18380 }
18381 };
18382 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
18383 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
18384 let keyword_call = call("helper(NULL, template); /* bound */");
18385 let keyword_arguments = keyword_call
18386 .child_by_field_name("arguments")
18387 .expect("keyword argument list");
18388 assert_eq!(
18389 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
18390 1
18391 );
18392 assert_eq!(
18393 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
18394 0
18395 );
18396
18397 let unbound_call = call("helper(NULL, template); /* unbound */");
18398 let unbound_arguments = unbound_call
18399 .child_by_field_name("arguments")
18400 .expect("unbound argument list");
18401 assert_eq!(
18402 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
18403 0
18404 );
18405 }
18406
18407 #[test]
18408 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
18409 let source = r#"#define MAKE(type) type *value
18410MAKE(int *);
18411typedef struct Item Item;
18412struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
18413void gen_op_movl(void *s, int first, int second) { }
18414const char *strZ(const char *value) { return value; }
18415int body(void *s) {
18416 MAKE(int *);
18417 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
18418 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
18419 execvp(strZ(value), UNCONSTIFY(char **, args));
18420}
18421 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
18422 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
18423int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
18424 if (d->units) {
18425 switch (target->nodetype) {
18426 case 1:
18427 case 2:
18428 break;
18429 default:
18430 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
18431 }
18432 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
18433 lysdict_remove(ctx, ((struct Item *)target)->units);
18434 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
18435 }
18436 return 0;
18437 }
18438STATIC EFI_STATUS Encode () { return 0; }
18439"#;
18440 let tree = parse_cpp(source);
18441 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
18442 let top_macro = tree
18443 .root_node()
18444 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
18445 .expect("top macro node");
18446 let body_macro_start = source
18447 .match_indices("MAKE(int *);")
18448 .nth(1)
18449 .expect("body macro")
18450 .0;
18451 let body_macro = tree
18452 .root_node()
18453 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
18454 .expect("body macro node");
18455 let function_call_start = source
18456 .find("gen_op_movl(s, offsetof(CPUX86State")
18457 .expect("function call");
18458 let function_call = tree
18459 .root_node()
18460 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
18461 .expect("function call node");
18462 let strz_start = source.find("strZ(value)").expect("nested function call");
18463 let strz = tree
18464 .root_node()
18465 .named_descendant_for_byte_range(strz_start, strz_start + 4)
18466 .expect("nested function call node");
18467 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
18468 let recovered_call = tree
18469 .root_node()
18470 .named_descendant_for_byte_range(
18471 recovered_call_start,
18472 recovered_call_start + "lysdict_remove".len(),
18473 )
18474 .expect("recovered call node");
18475 let binder_start = source.find("Encode").expect("binder");
18476 let binder = tree
18477 .root_node()
18478 .named_descendant_for_byte_range(binder_start, binder_start + 6)
18479 .expect("binder node");
18480
18481 assert!(recovered_c_function_declarator_invocation(top_macro));
18482 assert!(recovered_c_function_declarator_invocation(body_macro));
18483 assert!(recovered_c_function_declarator_invocation(function_call));
18484 assert!(recovered_c_function_declarator_invocation(strz));
18485 assert!(recovered_c_function_declarator_invocation(recovered_call));
18486 assert!(!recovered_c_function_declarator_invocation(binder));
18487 }
18488
18489 #[test]
18490 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
18491 let source = r#"typedef int krb5_context;
18492int helper(int first, int second) { return first + second; }
18493static krb5_context ctx;
18494int main(int argc, char **argv) {
18495 int ccinitial;
18496 const char *collection_name, *typename;
18497 typename = helper(ctx, ccinitial);
18498 return 0;
18499}
18500"#;
18501 let tree = parse_cpp(source);
18502 let ctx = tree
18503 .root_node()
18504 .descendant_for_byte_range(
18505 source.find("ctx, ccinitial").expect("ctx argument"),
18506 source.find("ctx, ccinitial").expect("ctx argument") + 3,
18507 )
18508 .expect("ctx node");
18509 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
18510 let ccinitial = tree
18511 .root_node()
18512 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
18513 .expect("sibling node");
18514 let typename = named_node_at(&tree, source, "typename = helper");
18515 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
18516
18517 assert_eq!(ctx.kind(), "identifier");
18518 assert!(recovered_c_parenthesized_declarator_reference(ctx));
18519 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
18520 assert!(!recovered_c_parenthesized_declarator_reference(typename));
18521 assert!(!recovered_c_parenthesized_declarator_reference(helper));
18522 }
18523
18524 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
18525 let mut parser = Parser::new();
18526 parser
18527 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18528 .expect("C++ grammar");
18529 let tree = parser.parse(source, None).expect("C++ fixture tree");
18530 let mut stack = vec![tree.root_node()];
18531 while let Some(node) = stack.pop() {
18532 if node.kind() == "enum_specifier" {
18533 return flattened_macro_namespace_components(node, source);
18534 }
18535 let mut cursor = node.walk();
18536 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
18537 stack.extend(children.into_iter().rev());
18538 }
18539 None
18540 }
18541
18542 #[test]
18543 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
18544 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18545namespace detail
18546{
18547enum class value_t { null };
18548}
18549NLOHMANN_JSON_NAMESPACE_END
18550NLOHMANN_JSON_NAMESPACE_BEGIN
18551namespace next
18552{
18553struct next_type {};
18554}
18555NLOHMANN_JSON_NAMESPACE_END
18556"#;
18557 assert_eq!(
18558 first_enum_flattened_namespace(complete),
18559 Some(vec!["detail".to_string()])
18560 );
18561
18562 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
18563 assert_eq!(
18564 first_enum_flattened_namespace(&stale_end),
18565 Some(vec!["detail".to_string()]),
18566 "a stale end marker before the begin marker must not replace the intended namespace"
18567 );
18568
18569 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18570namespace detail
18571{
18572enum class value_t { null };
18573}
18574struct next_type {};
18575"#;
18576 assert_eq!(first_enum_flattened_namespace(incomplete), None);
18577 }
18578}
18579
18580#[cfg(test)]
18596mod lookup_order_properties {
18597 use super::*;
18598 use proptest::prelude::*;
18599
18600 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
18604 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
18605 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
18609 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
18610 const KINDS: [CodeUnitType; 6] = [
18611 CodeUnitType::Class,
18612 CodeUnitType::Function,
18613 CodeUnitType::Field,
18614 CodeUnitType::Module,
18615 CodeUnitType::Macro,
18616 CodeUnitType::FileScope,
18617 ];
18618
18619 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18622 enum ProbedOrder {
18623 Before,
18624 Tied,
18625 After,
18626 Contradictory,
18629 }
18630
18631 impl ProbedOrder {
18632 fn mirror(self) -> Self {
18633 match self {
18634 ProbedOrder::Before => ProbedOrder::After,
18635 ProbedOrder::After => ProbedOrder::Before,
18636 other => other,
18637 }
18638 }
18639
18640 fn signum(self) -> i8 {
18642 match self {
18643 ProbedOrder::Before => -1,
18644 ProbedOrder::Tied => 0,
18645 ProbedOrder::After => 1,
18646 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
18647 }
18648 }
18649 }
18650
18651 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
18659 if left == right {
18660 return ProbedOrder::Tied;
18663 }
18664 let mut forward = vec![left.clone(), right.clone()];
18665 sort_lookup_units(&mut forward);
18666 let mut backward = vec![right.clone(), left.clone()];
18667 sort_lookup_units(&mut backward);
18668 let left_first = backward[0] == *left;
18669 let right_first = forward[0] == *right;
18670 match (left_first, right_first) {
18671 (true, true) => ProbedOrder::Contradictory,
18672 (true, false) => ProbedOrder::Before,
18673 (false, true) => ProbedOrder::After,
18674 (false, false) => ProbedOrder::Tied,
18675 }
18676 }
18677
18678 fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
18681 let interner = segment_interner();
18682 unit.fq()
18683 .segments()
18684 .iter()
18685 .map(|&id| {
18686 let (text, kind) = interner.resolve(id);
18687 (kind.name(), text)
18688 })
18689 .collect()
18690 }
18691
18692 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
18693 (
18694 0..ROOT_NAMES.len(),
18695 0..REL_PATHS.len(),
18696 0..KINDS.len(),
18697 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
18698 0..3usize,
18699 0..SIGNATURES.len(),
18700 any::<bool>(),
18701 )
18702 .prop_map(
18703 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
18704 let source = ProjectFile::new(
18705 std::env::temp_dir().join(ROOT_NAMES[root]),
18706 REL_PATHS[rel_path],
18707 );
18708 let interner = segment_interner();
18709 let mut fq = FqName::new();
18710 for (atom, segment_kind) in &segments {
18711 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
18712 }
18713 let package_segment_count = package_prefix % fq.len();
18715 CodeUnit::from_fq(
18716 source,
18717 KINDS[kind],
18718 fq,
18719 package_segment_count,
18720 SIGNATURES[signature].map(str::to_string),
18721 synthetic,
18722 )
18723 },
18724 )
18725 }
18726
18727 proptest! {
18728 #![proptest_config(ProptestConfig::with_cases(256))]
18729
18730 #[test]
18733 fn lookup_order_is_reflexive_and_dual(
18734 left in code_unit_strategy(),
18735 right in code_unit_strategy(),
18736 ) {
18737 prop_assert_eq!(
18738 probe_order(&left, &left),
18739 ProbedOrder::Tied,
18740 "a unit must tie with itself: {:?}",
18741 left
18742 );
18743 let forward = probe_order(&left, &right);
18744 prop_assert_ne!(
18745 forward,
18746 ProbedOrder::Contradictory,
18747 "comparator put each of these strictly first: left={:?} right={:?}",
18748 left,
18749 right
18750 );
18751 prop_assert_eq!(
18752 probe_order(&right, &left),
18753 forward.mirror(),
18754 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
18755 left,
18756 right
18757 );
18758 }
18759
18760 #[test]
18762 fn lookup_order_is_transitive(
18763 a in code_unit_strategy(),
18764 b in code_unit_strategy(),
18765 c in code_unit_strategy(),
18766 ) {
18767 let ab = probe_order(&a, &b);
18768 let bc = probe_order(&b, &c);
18769 let ac = probe_order(&a, &c);
18770 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
18771 prop_assert_ne!(
18772 probed,
18773 ProbedOrder::Contradictory,
18774 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
18775 pair,
18776 a,
18777 b,
18778 c
18779 );
18780 }
18781 if ab.signum() <= 0 && bc.signum() <= 0 {
18782 prop_assert!(
18783 ac.signum() <= 0,
18784 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
18785 a={:?} b={:?} c={:?}",
18786 ab,
18787 bc,
18788 ac,
18789 a,
18790 b,
18791 c
18792 );
18793 }
18794 }
18795
18796 #[test]
18799 fn lookup_order_separates_distinct_identities(
18800 left in code_unit_strategy(),
18801 right in code_unit_strategy(),
18802 ) {
18803 if probe_order(&left, &right) == ProbedOrder::Tied {
18804 prop_assert_eq!(
18805 &left,
18806 &right,
18807 "distinct identities tied, so their order is whatever order they \
18808 arrived in: left_segments={:?} right_segments={:?}",
18809 fq_segments(&left),
18810 fq_segments(&right)
18811 );
18812 }
18813 }
18814
18815 #[test]
18818 fn lookup_sort_is_permutation_invariant(
18819 units in prop::collection::vec(code_unit_strategy(), 1..=8),
18820 ) {
18821 let mut sorted = units.clone();
18822 sort_lookup_units(&mut sorted);
18823 for rotation in 0..units.len() {
18824 for reversed in [false, true] {
18825 let mut permuted = units.clone();
18826 permuted.rotate_left(rotation);
18827 if reversed {
18828 permuted.reverse();
18829 }
18830 sort_lookup_units(&mut permuted);
18831 prop_assert_eq!(
18832 &permuted,
18833 &sorted,
18834 "sorting a permutation gave a different list \
18835 (rotation={}, reversed={}): input={:?}",
18836 rotation,
18837 reversed,
18838 units
18839 );
18840 }
18841 }
18842 }
18843 }
18844}