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 static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
63}
64
65#[derive(Clone, Copy, PartialEq, Eq)]
66pub enum TargetKind {
67 Type,
68 Constructor,
69 FreeFunction,
70 Method,
71 GlobalField,
72 MemberField,
73 Macro,
74}
75
76pub enum LexicalTypeResolution {
77 Resolved {
78 unit: CodeUnit,
79 components: Vec<String>,
80 candidates: Vec<CodeUnit>,
81 },
82 Ambiguous,
83 Missing,
84}
85
86#[derive(Clone, Copy)]
87enum TypeCandidateResolution<'a> {
88 Canonical,
89 PreserveAlias,
90 PreserveTarget(&'a CodeUnit),
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102enum TypeCandidateFailure {
103 Ambiguous,
104 Unresolvable,
105}
106
107impl TypeCandidateFailure {
108 fn lexical_resolution(self) -> LexicalTypeResolution {
109 match self {
110 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
111 Self::Unresolvable => LexicalTypeResolution::Missing,
112 }
113 }
114}
115
116pub enum LexicalCallableValueResolution {
117 Type(CodeUnit),
118 FreeFunction(CodeUnit),
119 Ambiguous,
120 Missing,
121}
122
123pub enum UsingEnumMemberResolution {
124 Resolved { owner: CodeUnit, member: CodeUnit },
125 Ambiguous,
126 Missing,
127}
128
129pub enum NamespaceValueResolution {
130 Resolved,
131 Ambiguous,
132 Missing,
133}
134
135#[derive(Clone, Debug, PartialEq, Eq)]
136pub enum OrdinaryMacroReferenceResolution {
137 Resolved(CodeUnit),
138 Ambiguous,
139 Missing,
140}
141
142#[derive(Clone, Debug, PartialEq, Eq)]
143pub enum RecoveredCReferenceRanges {
144 Complete(Vec<Range>),
145 LimitExceeded,
146}
147
148pub fn resolve_namespace_value(
149 analyzer: &CppGraphSource<'_>,
150 visibility: &VisibilityIndex<'_>,
151 file: &ProjectFile,
152 namespace: &str,
153 name: &str,
154 before_byte: usize,
155) -> NamespaceValueResolution {
156 let mut matches = Vec::new();
157 for candidate in visibility.visible_identifier_candidates(file, name) {
158 if type_owner_of(analyzer, candidate).is_some()
159 || candidate.package_name() != namespace
160 || (candidate.source() == file
161 && !analyzer
162 .ranges(candidate)
163 .iter()
164 .any(|range| range.start_byte < before_byte))
165 || matches
166 .iter()
167 .any(|existing| same_visible_symbol(existing, candidate))
168 {
169 continue;
170 }
171 matches.push(candidate.clone());
172 if matches.len() > 1 {
173 return NamespaceValueResolution::Ambiguous;
174 }
175 }
176 matches
177 .pop()
178 .map(|_| NamespaceValueResolution::Resolved)
179 .unwrap_or(NamespaceValueResolution::Missing)
180}
181
182pub(crate) struct ScopedUsingEnumOwners {
183 scopes: Vec<Vec<CodeUnit>>,
184}
185
186pub(crate) struct SemanticUsingEnumOwners {
191 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
192 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
193}
194
195pub(crate) enum SemanticUsingEnumMemberResolution {
196 Class(UsingEnumMemberResolution),
197 Namespace(UsingEnumMemberResolution),
198 Missing,
199}
200
201impl SemanticUsingEnumOwners {
202 pub(crate) fn new() -> Self {
203 Self {
204 class_imports: HashMap::default(),
205 namespace_imports: HashMap::default(),
206 }
207 }
208
209 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
210 let imports = self.class_imports.entry(class).or_default();
211 if !imports
212 .iter()
213 .any(|existing| same_visible_symbol(existing, &enum_owner))
214 {
215 imports.push(enum_owner);
216 }
217 }
218
219 pub fn import_namespace(
220 &mut self,
221 namespace: Vec<String>,
222 declaration_byte: usize,
223 enum_owner: CodeUnit,
224 ) {
225 let imports = self.namespace_imports.entry(namespace).or_default();
226 if !imports
227 .iter()
228 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
229 {
230 imports.push((declaration_byte, enum_owner));
231 }
232 }
233
234 pub fn resolve_member(
235 &self,
236 visibility: &VisibilityIndex<'_>,
237 file: &ProjectFile,
238 class: Option<&CodeUnit>,
239 namespace: &[String],
240 before_byte: usize,
241 name: &str,
242 ) -> SemanticUsingEnumMemberResolution {
243 if let Some(class) = class
244 && let Some((_, imports)) = self
245 .class_imports
246 .iter()
247 .find(|(owner, _)| same_visible_symbol(owner, class))
248 {
249 let resolution =
250 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
251 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
252 return SemanticUsingEnumMemberResolution::Class(resolution);
253 }
254 }
255 for prefix_len in (0..=namespace.len()).rev() {
256 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
257 continue;
258 };
259 let owners = imports
260 .iter()
261 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
262 .map(|(_, owner)| owner);
263 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
264 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
265 return SemanticUsingEnumMemberResolution::Namespace(resolution);
266 }
267 }
268 SemanticUsingEnumMemberResolution::Missing
269 }
270}
271
272fn resolve_using_enum_member_for_owners<'a>(
273 visibility: &VisibilityIndex<'_>,
274 file: &ProjectFile,
275 owners: impl IntoIterator<Item = &'a CodeUnit>,
276 name: &str,
277) -> UsingEnumMemberResolution {
278 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
279 for owner in owners {
280 for member in visibility.visible_members_for_owner_name(file, owner, name) {
281 if !member.is_field()
282 || matches.iter().any(|(existing_owner, existing_member)| {
283 same_visible_symbol(existing_owner, owner)
284 && same_visible_symbol(existing_member, member)
285 })
286 {
287 continue;
288 }
289 matches.push((owner.clone(), member.clone()));
290 }
291 }
292 match matches.len() {
293 0 => UsingEnumMemberResolution::Missing,
294 1 => {
295 let (owner, member) = matches.pop().expect("one using-enum match");
296 UsingEnumMemberResolution::Resolved { owner, member }
297 }
298 _ => UsingEnumMemberResolution::Ambiguous,
299 }
300}
301
302impl ScopedUsingEnumOwners {
303 pub(crate) fn new() -> Self {
304 Self {
305 scopes: vec![Vec::new()],
306 }
307 }
308
309 pub fn enter_scope(&mut self) {
310 self.scopes.push(Vec::new());
311 }
312
313 pub fn exit_scope(&mut self) {
314 if self.scopes.len() > 1 {
315 self.scopes.pop();
316 }
317 }
318
319 pub fn import(&mut self, owner: CodeUnit) {
320 let scope = self
321 .scopes
322 .last_mut()
323 .expect("using-enum scope stack is never empty");
324 if !scope
325 .iter()
326 .any(|existing| same_visible_symbol(existing, &owner))
327 {
328 scope.push(owner);
329 }
330 }
331
332 pub fn resolve_member(
333 &self,
334 visibility: &VisibilityIndex<'_>,
335 file: &ProjectFile,
336 name: &str,
337 ) -> UsingEnumMemberResolution {
338 for scope in self.scopes.iter().rev() {
339 let resolution =
340 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
341 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
342 return resolution;
343 }
344 }
345 UsingEnumMemberResolution::Missing
346 }
347}
348
349#[derive(Clone)]
350pub struct TargetSpec {
351 pub target: CodeUnit,
352 pub kind: TargetKind,
353 pub owner: Option<CodeUnit>,
354 pub member_name: String,
355 pub callable_arity: Option<CallableArity>,
356 pub activated_callable_arities: Vec<ActivatedCallableArity>,
357 pub param_types: Option<Vec<String>>,
358 pub enum_owner_kind: EnumOwnerKind,
359 pub owner_is_forward_declaration: bool,
360 pub callable_has_definition_body: bool,
361}
362
363#[derive(Clone, Copy)]
364pub struct ActivatedCallableArity {
365 pub activation_byte: usize,
366 pub arity: CallableArity,
367}
368
369#[derive(Debug, PartialEq, Eq, Hash)]
370pub struct TypeScanKey {
371 target: LogicalSymbolKey,
372 member_name: String,
373}
374
375#[derive(Clone, Debug, PartialEq, Eq, Hash)]
376struct LogicalSymbolKey {
377 kind: CodeUnitType,
378 fq_name: String,
379 signature: Option<String>,
380}
381
382struct ResolvedTypeOwner {
383 unit: CodeUnit,
384 is_forward_declaration: bool,
385}
386
387#[derive(Clone, Copy, PartialEq, Eq)]
388pub enum EnumOwnerKind {
389 Scoped,
390 Unscoped,
391 NonEnum,
392}
393
394impl TargetSpec {
395 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
396 (self.kind == TargetKind::Type).then(|| TypeScanKey {
397 target: logical_symbol_key(&self.target),
398 member_name: self.member_name.clone(),
399 })
400 }
401
402 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
403 if target.is_class() {
404 return Some(Self::new(
405 target.clone(),
406 TargetKind::Type,
407 Some(target.clone()),
408 target.identifier().to_string(),
409 None,
410 None,
411 ));
412 }
413
414 if target.is_field() {
415 let owner = type_owner_of(analyzer, target);
421 let kind = if owner.is_some() {
422 TargetKind::MemberField
423 } else {
424 TargetKind::GlobalField
425 };
426 let enum_owner_kind = owner
427 .as_ref()
428 .map(|owner| classify_enum_owner(analyzer, owner))
429 .unwrap_or(EnumOwnerKind::NonEnum);
430 let mut spec = Self::new(
431 target.clone(),
432 kind,
433 owner,
434 target.identifier().to_string(),
435 None,
436 None,
437 );
438 spec.enum_owner_kind = enum_owner_kind;
439 return Some(spec);
440 }
441
442 if target.is_function() {
443 let owner_resolution = target_type_owner_resolution(analyzer, target);
446 let owner_is_forward_declaration = owner_resolution
447 .as_ref()
448 .is_some_and(|owner| owner.is_forward_declaration);
449 let owner = owner_resolution.map(|owner| owner.unit);
450 let kind = if owner.as_ref().is_some_and(|owner| {
451 target.identifier() == owner.identifier()
452 || analyzer
453 .cpp
454 .and_then(|cpp| cpp.template_metadata(owner))
455 .is_some_and(|metadata| metadata.primary_name == target.identifier())
456 }) {
457 TargetKind::Constructor
458 } else if owner.is_some() {
459 TargetKind::Method
460 } else {
461 TargetKind::FreeFunction
462 };
463 let mut spec = Self::new(
464 target.clone(),
465 kind,
466 owner,
467 target.identifier().to_string(),
468 Some(cpp_callable_arity(analyzer, target)),
469 cpp_callable_parameter_types(analyzer, target),
470 );
471 spec.owner_is_forward_declaration = owner_is_forward_declaration;
472 spec.callable_has_definition_body =
473 callable_target_has_definition_body(analyzer, target);
474 return Some(spec);
475 }
476
477 if target.is_macro() {
478 return Some(Self::new(
479 target.clone(),
480 TargetKind::Macro,
481 None,
482 target.identifier().to_string(),
483 None,
484 None,
485 ));
486 }
487
488 None
489 }
490
491 pub fn with_visible_callable_arities<'a>(
492 &'a self,
493 analyzer: &CppGraphSource<'_>,
494 cpp: &dyn CppSource,
495 visibility: &VisibilityIndex<'_>,
496 file: &ProjectFile,
497 prepared: &PreparedSyntaxTree,
498 ) -> Cow<'a, Self> {
499 let macro_parameter_arity =
500 visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
501 let activated_callable_arities =
502 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
503 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
504 return Cow::Borrowed(self);
505 }
506 let mut effective = self.clone();
507 if let Some(macro_parameter_arity) = macro_parameter_arity {
508 effective.callable_arity = Some(macro_parameter_arity);
509 }
510 effective.activated_callable_arities = activated_callable_arities;
511 Cow::Owned(effective)
512 }
513
514 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
515 let base = self.callable_arity?;
516 Some(
517 self.activated_callable_arities
518 .iter()
519 .filter(|candidate| candidate.activation_byte <= byte)
520 .fold(base, |arity, candidate| {
521 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
522 }),
523 )
524 }
525
526 pub fn new(
527 target: CodeUnit,
528 kind: TargetKind,
529 owner: Option<CodeUnit>,
530 member_name: String,
531 callable_arity: Option<CallableArity>,
532 param_types: Option<Vec<String>>,
533 ) -> Self {
534 Self {
535 target,
536 kind,
537 owner,
538 member_name,
539 callable_arity,
540 activated_callable_arities: Vec::new(),
541 param_types,
542 enum_owner_kind: EnumOwnerKind::NonEnum,
543 owner_is_forward_declaration: false,
544 callable_has_definition_body: false,
545 }
546 }
547}
548
549fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
550 let Some(cpp) = analyzer.cpp else {
551 return false;
552 };
553 let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
554 return false;
555 };
556 analyzer.ranges(target).into_iter().any(|range| {
557 let end = range
558 .start_byte
559 .saturating_add(1)
560 .min(prepared.source().len());
561 let mut current = prepared
562 .tree()
563 .root_node()
564 .descendant_for_byte_range(range.start_byte, end);
565 while let Some(node) = current {
566 match node.kind() {
567 "function_definition" => return true,
568 "declaration" => return false,
569 _ => current = node.parent(),
570 }
571 }
572 false
573 })
574}
575
576fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
577 LogicalSymbolKey {
578 kind: unit.kind(),
579 fq_name: unit.fq_name(),
580 signature: unit.signature().map(str::to_string),
581 }
582}
583
584fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
585 let classify = |source: &str| {
586 let source = source.trim_start();
587 if source.starts_with("enum class ") || source.starts_with("enum struct ") {
588 Some(EnumOwnerKind::Scoped)
589 } else if source.starts_with("enum ") {
590 Some(EnumOwnerKind::Unscoped)
591 } else {
592 None
593 }
594 };
595 owner
596 .signature()
597 .and_then(classify)
598 .or_else(|| {
599 analyzer
600 .get_source(owner, false)
601 .as_deref()
602 .and_then(classify)
603 })
604 .unwrap_or(EnumOwnerKind::NonEnum)
605}
606
607#[derive(Clone, PartialEq, Eq, Hash)]
608pub struct CppScanBinding {
609 pub unit: Option<CodeUnit>,
610 pub type_name: Option<String>,
611 pub indirection: i32,
612}
613
614impl CppScanBinding {
615 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
616 Self {
617 type_name: Some(cpp_name_for(&unit)),
618 unit: Some(unit),
619 indirection,
620 }
621 }
622
623 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
624 Self {
625 type_name: Some(type_name),
626 unit,
627 indirection,
628 }
629 }
630
631 pub fn as_arg_type(&self) -> Option<CppArgType> {
632 let name = self
633 .type_name
634 .clone()
635 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
636 Some(CppArgType {
637 name,
638 unit: self.unit.clone(),
639 indirection: self.indirection,
640 pointee_const: false,
641 })
642 }
643}
644
645type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
646pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
647pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
648type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
649type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
650type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
651type MacroLexicalTemplateCache =
652 HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
653
654type MacroLocalBindingTemplateCache =
655 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
656type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
657type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
658type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
659
660struct StructuredIncludeFact {
661 start_byte: usize,
662 end_byte: usize,
663 path: String,
664}
665
666#[derive(Clone, Default)]
667pub struct MacroEnvironment {
668 bindings: HashMap<String, MacroBinding>,
669 known_undefined_names: HashSet<String>,
670 build_proven_defines: HashSet<String>,
675 unknown_names: bool,
676 applied_pragma_once_files: HashSet<ProjectFile>,
677 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
678}
679
680pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
689
690struct MacroEnvironmentCheckpoint {
692 frontier: usize,
694 environment: Arc<MacroEnvironment>,
695}
696
697struct MacroEnvironmentCheckpoints {
706 checkpoints: Vec<MacroEnvironmentCheckpoint>,
707}
708
709impl MacroEnvironmentCheckpoints {
710 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
712 let index = self
713 .checkpoints
714 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
715 assert!(
716 index > 0,
717 "a checkpoint vector starts at frontier zero, which precedes every request"
718 );
719 &self.checkpoints[index - 1]
720 }
721}
722
723impl MacroEnvironment {
724 fn binding(&self, name: &str) -> Option<&MacroBinding> {
725 self.bindings.get(name)
726 }
727
728 fn may_bind(&self, name: &str) -> bool {
729 self.bindings.contains_key(name) || self.unknown_names
730 }
731
732 fn insert(&mut self, name: String, binding: MacroBinding) {
733 self.known_undefined_names.remove(&name);
734 self.bindings.insert(name, binding);
735 }
736
737 fn remove(&mut self, name: &str) {
738 self.bindings.remove(name);
739 self.known_undefined_names.insert(name.to_string());
740 }
741
742 fn remove_known_undefined(&mut self, name: &str) {
743 self.known_undefined_names.remove(name);
744 }
745
746 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
747 for binding in self.bindings.values_mut() {
748 *binding = MacroBinding::uncertain_from(binding, source, byte);
749 }
750 self.known_undefined_names.clear();
751 self.build_proven_defines.clear();
756 self.unknown_names = true;
757 }
758
759 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
760 guards.iter().all(|guard| self.guard_may_hold(guard))
761 }
762
763 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
764 let Some(expression) = guard.as_boolean_expression() else {
765 return true;
766 };
767 self.boolean_guard_may_hold(&expression)
768 }
769
770 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
771 match expression {
772 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
773 BooleanGuardExpression::Undefined(name) => {
774 self.bindings
775 .get(name)
776 .is_none_or(|binding| !binding.is_exact())
777 && (!self.build_proven_defines.contains(name)
778 || self.known_undefined_names.contains(name))
779 }
780 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
781 BooleanGuardExpression::Opaque(_)
782 | BooleanGuardExpression::NegatedOpaque(_)
783 | BooleanGuardExpression::Constant(true) => true,
784 BooleanGuardExpression::Constant(false) => false,
785 BooleanGuardExpression::All(expressions) => expressions
786 .iter()
787 .all(|expression| self.boolean_guard_may_hold(expression)),
788 BooleanGuardExpression::Any(expressions) => expressions
789 .iter()
790 .any(|expression| self.boolean_guard_may_hold(expression)),
791 }
792 }
793}
794
795#[derive(Clone)]
796pub enum EffectiveUsingTarget {
797 Ordinary {
798 name: String,
799 target_components: Vec<String>,
800 global: bool,
801 },
802 Namespace {
803 namespace_components: Vec<String>,
804 global: bool,
805 },
806}
807
808#[derive(Clone)]
809pub struct OrdinaryTypeImport {
810 pub target: EffectiveUsingTarget,
811 pub source: ProjectFile,
812 pub declaration_byte: usize,
813 pub scope_start: usize,
814 pub scope_end: usize,
815 pub scope_depth: usize,
816 pub block_scope: bool,
817 pub lexical_depth: usize,
818 pub declaration_namespace: Vec<String>,
819 pub namespace_scope: Option<Vec<String>>,
820 pub resolved_target_components: Option<Vec<String>>,
821 pub required_guards: HashSet<PreprocessorGuard>,
822}
823
824#[derive(Clone)]
825pub struct ConditionalIncludeProjection {
826 pub activation_byte: usize,
827 pub required_guards: HashSet<PreprocessorGuard>,
828 pub partial_guards: HashSet<PreprocessorGuard>,
832}
833
834#[derive(Default)]
835pub struct SourceUsingIndex {
836 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
837 pub directives: Vec<OrdinaryTypeImport>,
838}
839
840#[derive(Default)]
841pub struct ProjectUsingIndex {
842 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
843 pub directives: Vec<OrdinaryTypeImport>,
844}
845
846type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
847
848pub struct EffectiveUsingIndex {
849 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
850}
851
852impl EffectiveUsingIndex {
853 fn new(_root: ProjectFile) -> Self {
854 Self {
855 projected_by_name: Mutex::new(HashMap::default()),
856 }
857 }
858
859 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
860 self.projected_by_name
861 .lock()
862 .expect("C++ effective-using projection cache poisoned")
863 .entry(name.to_string())
864 .or_default()
865 .clone()
866 }
867}
868
869pub enum OrdinaryTypeImportResolution {
870 Resolved {
871 target: CodeUnit,
872 target_components: Vec<String>,
873 lexical_depth: usize,
874 is_direct: bool,
875 },
876 Ambiguous {
877 lexical_depth: usize,
878 },
879 Missing,
880}
881
882type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
883type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
884type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
885type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
886type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
887type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
888type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
889type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
890
891struct ExtractedComparable {
896 shapes: Vec<CppComparableSlot>,
897 suffix: String,
898}
899
900const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
904
905#[derive(Clone, Copy, Debug, PartialEq, Eq)]
927enum IncludePathAdmission {
928 Proven,
929 Compatible,
930}
931
932impl IncludePathAdmission {
933 fn admits(
934 self,
935 required: &HashSet<PreprocessorGuard>,
936 partial: &HashSet<PreprocessorGuard>,
937 reference_guards: Option<&HashSet<PreprocessorGuard>>,
938 ) -> bool {
939 match self {
940 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
941 Self::Compatible => {
942 guard_requirements_hold_at_reference(partial, reference_guards)
943 && guards_compatible_at_reference(required, reference_guards)
944 }
945 }
946 }
947}
948
949pub struct VisibilityIndex<'a> {
960 cpp: &'a dyn CppSource,
961 token: QueryToken<'a>,
966 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
967 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
968 global_field_internal_linkage: HashMap<CodeUnit, bool>,
969 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
970 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
971 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
972 parser_alias_target_matches:
973 RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
974 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
975 project_using_index: OnceLock<ProjectUsingIndex>,
976 callable_reference_specs:
977 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
978 structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
979 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
980 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
981 include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
982 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
983 #[cfg(any(test, feature = "test-support"))]
984 conditional_include_projection_index_build_count: AtomicUsize,
985 #[cfg(any(test, feature = "test-support"))]
986 conditional_include_projection_state_count: AtomicUsize,
987 #[cfg(any(test, feature = "test-support"))]
988 conditional_include_target_state_count: AtomicUsize,
989 #[cfg(any(test, feature = "test-support"))]
990 include_activation_build_count: AtomicUsize,
991 #[cfg(any(test, feature = "test-support"))]
992 using_donor_activation_count: AtomicUsize,
993 #[cfg(any(test, feature = "test-support"))]
994 using_namespace_lookup_count: AtomicUsize,
995 #[cfg(any(test, feature = "test-support"))]
996 using_name_candidate_inspection_count: AtomicUsize,
997 #[cfg(any(test, feature = "test-support"))]
998 callable_reference_spec_build_count: AtomicUsize,
999 #[cfg(any(test, feature = "test-support"))]
1000 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1001 #[cfg(any(test, feature = "test-support"))]
1002 visible_parser_alias_name_set_build_count: AtomicUsize,
1003 parser_alias_fallback_calls: AtomicUsize,
1004 parser_alias_fallback_files: AtomicUsize,
1005 parser_alias_source_parses: AtomicUsize,
1006 parser_alias_fallback_elapsed_micros: AtomicUsize,
1007 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1008 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1009 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1010 comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1011 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1012 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1013 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1014 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1015 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1016 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1017 macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1018 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1019 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1026 macro_replacements: Mutex<MacroReplacementCache>,
1027 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1028 pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1029 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1030 macro_type_parameters: Mutex<MacroTypeParameterCache>,
1031 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1032 #[cfg(any(test, feature = "test-support"))]
1033 pub macro_replacement_parse_count: AtomicUsize,
1034 #[cfg(any(test, feature = "test-support"))]
1035 pub macro_event_application_count: AtomicUsize,
1036 #[cfg(any(test, feature = "test-support"))]
1039 pub macro_environment_checkpoint_build_count: AtomicUsize,
1040 #[cfg(any(test, feature = "test-support"))]
1043 pub macro_environment_copy_count: AtomicUsize,
1044 #[cfg(any(test, feature = "test-support"))]
1045 pub macro_environment_request_count: AtomicUsize,
1046 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1047 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1048 #[cfg(any(test, feature = "test-support"))]
1049 qualified_candidate_inspections: AtomicUsize,
1050 #[cfg(any(test, feature = "test-support"))]
1051 target_preserving_type_resolution_count: AtomicUsize,
1052 #[cfg(any(test, feature = "test-support"))]
1053 visibility_identifier_lookup_count: usize,
1054 #[cfg(any(test, feature = "test-support"))]
1055 visibility_identifier_batch_count: usize,
1056}
1057
1058impl Drop for VisibilityIndex<'_> {
1059 fn drop(&mut self) {
1060 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1061 return;
1062 }
1063 #[cfg(any(test, feature = "test-support"))]
1064 eprintln!(
1065 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1066 self.macro_environment_request_count.load(Ordering::Relaxed),
1067 self.macro_environment_copy_count.load(Ordering::Relaxed),
1068 self.macro_environment_checkpoint_build_count
1069 .load(Ordering::Relaxed),
1070 self.macro_event_application_count.load(Ordering::Relaxed),
1071 );
1072 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1073 if calls == 0 {
1074 return;
1075 }
1076 eprintln!(
1077 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1078 calls,
1079 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1080 self.parser_alias_source_parses.load(Ordering::Relaxed),
1081 self.parser_alias_fallback_elapsed_micros
1082 .load(Ordering::Relaxed)
1083 / 1_000,
1084 );
1085 }
1086}
1087
1088#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1089pub enum PreprocessorGuard {
1090 Defined(String),
1091 Undefined(String),
1092 Boolean(BooleanGuardExpression),
1093 Expression(String),
1094 NegatedExpression(String),
1095 Constant(bool),
1096}
1097
1098#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1099pub enum BooleanGuardExpression {
1100 Defined(String),
1101 Undefined(String),
1102 Truthy(String),
1103 Falsy(String),
1104 Opaque(String),
1105 NegatedOpaque(String),
1106 All(Vec<BooleanGuardExpression>),
1107 Any(Vec<BooleanGuardExpression>),
1108 Constant(bool),
1109}
1110
1111impl BooleanGuardExpression {
1112 fn negated(&self) -> Self {
1113 match self {
1114 Self::Defined(name) => Self::Undefined(name.clone()),
1115 Self::Undefined(name) => Self::Defined(name.clone()),
1116 Self::Truthy(name) => Self::Falsy(name.clone()),
1117 Self::Falsy(name) => Self::Truthy(name.clone()),
1118 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1119 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1120 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1121 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1122 Self::Constant(value) => Self::Constant(!value),
1123 }
1124 }
1125
1126 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1127 Self::normalized(expressions, true)
1128 }
1129
1130 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1131 Self::normalized(expressions, false)
1132 }
1133
1134 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1135 let mut normalized = Vec::new();
1136 for expression in expressions {
1137 match expression {
1138 Self::All(nested) if conjunction => normalized.extend(nested),
1139 Self::Any(nested) if !conjunction => normalized.extend(nested),
1140 Self::Constant(value) if value == conjunction => {}
1141 Self::Constant(value) => return Self::Constant(value),
1142 expression => normalized.push(expression),
1143 }
1144 }
1145 normalized.sort_unstable();
1146 normalized.dedup();
1147 match normalized.len() {
1148 0 => Self::Constant(conjunction),
1149 1 => normalized.pop().expect("one Boolean guard expression"),
1150 _ if conjunction => Self::All(normalized),
1151 _ => Self::Any(normalized),
1152 }
1153 }
1154
1155 fn implies(&self, required: &Self) -> bool {
1156 if self == required
1157 || matches!(self, Self::Constant(false))
1158 || matches!(required, Self::Constant(true))
1159 {
1160 return true;
1161 }
1162 if matches!(
1163 (self, required),
1164 (Self::Truthy(active), Self::Defined(required))
1165 | (Self::Undefined(active), Self::Falsy(required))
1166 if active == required
1167 ) {
1168 return true;
1169 }
1170 match self {
1171 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1172 Self::All(active) => match required {
1173 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1174 _ => active.iter().any(|expression| expression.implies(required)),
1175 },
1176 _ => match required {
1177 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1178 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1179 _ => false,
1180 },
1181 }
1182 }
1183
1184 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1185 match self {
1186 Self::Defined(name)
1187 | Self::Undefined(name)
1188 | Self::Truthy(name)
1189 | Self::Falsy(name) => name == macro_name,
1190 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1193 Self::All(expressions) | Self::Any(expressions) => expressions
1194 .iter()
1195 .any(|expression| expression.may_depend_on_macro(macro_name)),
1196 Self::Constant(_) => false,
1197 }
1198 }
1199
1200 pub fn heap_size(&self) -> usize {
1201 match self {
1202 Self::Defined(value)
1203 | Self::Undefined(value)
1204 | Self::Truthy(value)
1205 | Self::Falsy(value)
1206 | Self::Opaque(value)
1207 | Self::NegatedOpaque(value) => value.len(),
1208 Self::All(expressions) | Self::Any(expressions) => {
1209 expressions
1210 .iter()
1211 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1212 size.saturating_add(std::mem::size_of::<Self>())
1213 .saturating_add(expression.heap_size())
1214 })
1215 }
1216 Self::Constant(_) => 0,
1217 }
1218 }
1219}
1220
1221impl PreprocessorGuard {
1222 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1223 match self {
1224 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1225 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1226 Self::Boolean(expression) => Some(expression.clone()),
1227 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1228 Self::Expression(_) | Self::NegatedExpression(_) => None,
1229 }
1230 }
1231
1232 fn negated(&self) -> Self {
1233 match self {
1234 Self::Defined(name) => Self::Undefined(name.clone()),
1235 Self::Undefined(name) => Self::Defined(name.clone()),
1236 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1237 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1238 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1239 Self::Constant(value) => Self::Constant(!value),
1240 }
1241 }
1242
1243 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1244 match self {
1245 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1246 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1247 Self::Expression(_) | Self::NegatedExpression(_) => true,
1250 Self::Constant(_) => false,
1251 }
1252 }
1253}
1254
1255#[derive(Clone, PartialEq, Eq)]
1256pub enum MacroDefinition {
1257 Object {
1258 replacement: String,
1259 },
1260 Function {
1261 parameters: Vec<String>,
1262 replacement: String,
1263 },
1264 VariadicFunction {
1265 parameters: Vec<String>,
1266 replacement: String,
1267 },
1268 Unsupported,
1269}
1270
1271#[derive(Clone, Debug, PartialEq, Eq)]
1272pub enum MacroIncludeProtection {
1273 MacroGuard(String),
1274 PragmaOnce,
1275 None,
1276}
1277
1278enum ParsedMacroReplacement {
1279 Parsed { source: String, tree: Tree },
1280 Unsupported,
1281}
1282
1283const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1287
1288pub struct ParsedReplacementBody {
1297 pub source: String,
1298 pub tree: Tree,
1299 pub body_offset: usize,
1300 pub parameters: Vec<String>,
1301 original_offsets: Box<[usize]>,
1306}
1307
1308impl ParsedReplacementBody {
1309 pub fn statements(&self) -> Option<Node<'_>> {
1311 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1312 .child_by_field_name("body")
1313 }
1314
1315 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1321 assert!(
1322 node.start_byte() >= self.body_offset,
1323 "synthetic sentinel node cannot be mapped to a macro replacement"
1324 );
1325 assert!(
1326 node.end_byte() >= self.body_offset,
1327 "synthetic sentinel node cannot be mapped to a macro replacement"
1328 );
1329 let start_offset = node.start_byte() - self.body_offset;
1330 let end_offset = node.end_byte() - self.body_offset;
1331 assert!(start_offset <= end_offset);
1332 let start_origin = *self
1333 .original_offsets
1334 .get(start_offset)
1335 .expect("replacement node start must have a source mapping");
1336 let end_origin = *self
1337 .original_offsets
1338 .get(end_offset)
1339 .expect("replacement node end must have a source mapping");
1340 assert!(start_origin <= end_origin);
1341 let start = replacement_start + start_origin;
1342 let end = replacement_start + end_origin;
1343 start..end
1344 }
1345
1346 fn expands_variadic_arguments(&self) -> bool {
1353 let mut stack = vec![self.tree.root_node()];
1354 while let Some(node) = stack.pop() {
1355 if matches!(
1356 node.kind(),
1357 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1358 ) && node_text(node, &self.source) == "__VA_ARGS__"
1359 {
1360 return true;
1361 }
1362 push_named_children_reversed(node, &mut stack);
1363 }
1364 false
1365 }
1366}
1367
1368fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1369 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1370 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1371 if compact.starts_with("0x") || compact.starts_with("0X") {
1372 (16, 2, |ch| ch.is_ascii_hexdigit())
1373 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1374 (2, 2, |ch| matches!(ch, '0' | '1'))
1375 } else if compact.starts_with('0') && compact.len() > 1 {
1376 (8, 0, |ch| matches!(ch, '0'..='7'))
1377 } else {
1378 (10, 0, |ch| ch.is_ascii_digit())
1379 };
1380 let digit_len = compact[digits_start..]
1381 .chars()
1382 .take_while(|ch| digit_matches(*ch))
1383 .map(char::len_utf8)
1384 .sum::<usize>();
1385 if digit_len == 0 {
1386 return None;
1387 }
1388 let digits_end = digits_start + digit_len;
1389 if !compact[digits_end..]
1390 .chars()
1391 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1392 {
1393 return None;
1394 }
1395 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1396}
1397
1398#[derive(Clone)]
1399enum MacroLocalBindingTypeTemplate {
1400 Parameter(usize),
1401 Fixed(String),
1402}
1403
1404#[derive(Clone)]
1405struct MacroLocalBindingTemplate {
1406 name: String,
1407 declared_type: MacroLocalBindingTypeTemplate,
1408 pointer_depth: i32,
1409}
1410
1411pub struct MacroLocalBinding<'tree> {
1419 pub name: String,
1420 pub type_name: String,
1421 pub type_node: Option<Node<'tree>>,
1422 pub pointer_depth: i32,
1423 pub proven_unit: Option<CodeUnit>,
1424}
1425
1426#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1432pub enum MacroLexicalBindingKind {
1433 Parameter,
1434 Local,
1435}
1436
1437#[derive(Clone, Debug, PartialEq, Eq)]
1438pub struct MacroLexicalBinding {
1439 pub definition: ProjectFile,
1440 pub kind: MacroLexicalBindingKind,
1441 pub name: String,
1442 pub name_range: std::ops::Range<usize>,
1443 pub declaration_range: std::ops::Range<usize>,
1444}
1445
1446#[derive(Clone, Debug, Default, PartialEq, Eq)]
1450pub struct MacroLexicalReferences {
1451 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1452 pub truncated: bool,
1453 pub cancelled: bool,
1454}
1455
1456fn macro_replacement_type_parameters(
1457 body: &ParsedReplacementBody,
1458 parameters: &[String],
1459) -> Option<Vec<usize>> {
1460 let mut found = Vec::new();
1461 let mut stack = vec![body.tree.root_node()];
1462 while let Some(node) = stack.pop() {
1463 let type_position = node.kind() == "type_identifier"
1464 || (node.kind() == "identifier"
1465 && node.parent().is_some_and(|parent| {
1466 parent.kind() == "type_descriptor"
1467 && parent.child_by_field_name("type") == Some(node)
1468 }));
1469 let offsetof_type_position = node.kind() == "identifier"
1470 && node
1471 .parent()
1472 .filter(|parent| parent.kind() == "argument_list")
1473 .and_then(|arguments| arguments.parent())
1474 .is_some_and(|call| {
1475 call.kind() == "call_expression"
1476 && call
1477 .child_by_field_name("function")
1478 .is_some_and(|function| {
1479 function.kind() == "identifier"
1480 && node_text(function, &body.source) == "offsetof"
1481 })
1482 && call
1483 .child_by_field_name("arguments")
1484 .is_some_and(|arguments| {
1485 argument_children(arguments).next() == Some(node)
1486 })
1487 });
1488 if (type_position || offsetof_type_position)
1489 && let Some(index) = parameters
1490 .iter()
1491 .position(|parameter| parameter == node_text(node, &body.source))
1492 && !found.contains(&index)
1493 {
1494 found.push(index);
1495 }
1496 push_named_children_reversed(node, &mut stack);
1497 }
1498 (!found.is_empty()).then_some(found)
1499}
1500
1501fn macro_replacement_type_parameter(
1502 body: &ParsedReplacementBody,
1503 parameters: &[String],
1504) -> Option<usize> {
1505 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1506 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1507}
1508
1509pub(crate) fn macro_type_argument_node<'tree>(
1510 node: Node<'tree>,
1511 source: &str,
1512) -> Option<Node<'tree>> {
1513 match node.kind() {
1514 "type_descriptor" => {
1515 let type_child = node
1516 .child_by_field_name("type")
1517 .or_else(|| first_type_child(node))?;
1518 for index in (0..node.named_child_count()).rev() {
1519 let child = node.named_child(index)?;
1520 if child != type_child
1521 && matches!(
1522 child.kind(),
1523 "identifier"
1524 | "type_identifier"
1525 | "qualified_identifier"
1526 | "scoped_type_identifier"
1527 )
1528 {
1529 return Some(child);
1530 }
1531 }
1532 macro_type_argument_node(type_child, source)
1533 }
1534 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1535 node.child_by_field_name("name")
1536 }
1537 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1538 .next_named_sibling()
1539 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1540 .and_then(|error| error.named_child(0))
1541 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1542 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1543 }
1544}
1545
1546fn c_function_macro_argument<'tree>(
1547 node: Node<'tree>,
1548) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1549 let mut current = node;
1550 while let Some(parent) = current.parent() {
1551 if parent.kind() == "argument_list" {
1552 let call = parent.parent().filter(|call| {
1553 call.kind() == "call_expression"
1554 && call.child_by_field_name("arguments") == Some(parent)
1555 && call
1556 .child_by_field_name("function")
1557 .is_some_and(|function| {
1558 function.kind() == "identifier"
1559 && !node_range_contains(function, current)
1560 })
1561 })?;
1562 let mut actuals = argument_children(parent).enumerate();
1563 let (index, argument) = actuals.find(|(_, argument)| {
1564 node_range_contains(*argument, node)
1565 && (argument.start_byte() == current.start_byte()
1566 || argument.end_byte() == current.end_byte())
1567 })?;
1568 return Some((call, index, argument));
1569 }
1570 current = parent;
1571 }
1572 None
1573}
1574
1575fn recognized_c_macro_declarator_binding<'tree>(
1580 statement: Node<'tree>,
1581 source: &str,
1582) -> Option<MacroLocalBinding<'tree>> {
1583 let assignment = match statement.kind() {
1584 "assignment_expression" => statement,
1585 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1586 _ => return None,
1587 };
1588 if assignment.kind() != "assignment_expression" {
1589 return None;
1590 }
1591 let call = assignment.child_by_field_name("left")?;
1592 if call.kind() != "call_expression" {
1593 return None;
1594 }
1595 let function = call.child_by_field_name("function")?;
1596 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1597 return None;
1598 }
1599 let arguments = call.child_by_field_name("arguments")?;
1600 let mut actuals = argument_children(arguments);
1601 let type_node = actuals.next()?;
1602 if actuals.next().is_some()
1603 || !matches!(
1604 type_node.kind(),
1605 "identifier"
1606 | "type_identifier"
1607 | "qualified_identifier"
1608 | "scoped_type_identifier"
1609 | "template_type"
1610 )
1611 {
1612 return None;
1613 }
1614 let name_node = (0..assignment.named_child_count())
1615 .filter_map(|index| assignment.named_child(index))
1616 .filter(|child| child.kind() == "ERROR")
1617 .filter_map(|error| {
1618 (error.named_child_count() == 1)
1619 .then(|| error.named_child(0))
1620 .flatten()
1621 })
1622 .find(|node| node.kind() == "identifier")?;
1623 let name = node_text(name_node, source).trim();
1624 let type_name = node_text(type_node, source).trim();
1625 if name.is_empty() || type_name.is_empty() {
1626 return None;
1627 }
1628 Some(MacroLocalBinding {
1629 name: name.to_string(),
1630 type_name: type_name.to_string(),
1631 type_node: Some(type_node),
1632 pointer_depth: 1,
1633 proven_unit: None,
1634 })
1635}
1636
1637#[derive(Clone, PartialEq, Eq)]
1638pub struct MacroBinding {
1639 source: ProjectFile,
1640 declaration_byte: usize,
1641 definition: MacroDefinition,
1642 exact: bool,
1643}
1644
1645impl MacroBinding {
1646 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1647 Self {
1648 source: source.clone(),
1649 declaration_byte,
1650 definition: MacroDefinition::Unsupported,
1651 exact: false,
1652 }
1653 }
1654
1655 fn is_exact(&self) -> bool {
1656 self.exact
1657 }
1658
1659 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1660 Self {
1661 source: source.clone(),
1662 declaration_byte,
1663 definition: current.definition.clone(),
1664 exact: false,
1665 }
1666 }
1667}
1668
1669type OwningPreprocessorConditionals = Box<[usize]>;
1681
1682#[derive(Clone)]
1683pub enum MacroEvent {
1684 Define {
1685 name: String,
1686 binding: MacroBinding,
1687 byte: usize,
1688 conditionals: OwningPreprocessorConditionals,
1689 },
1690 Undef {
1691 name: String,
1692 byte: usize,
1693 conditionals: OwningPreprocessorConditionals,
1694 },
1695 Include {
1696 targets: Vec<ProjectFile>,
1697 byte: usize,
1698 conditionals: OwningPreprocessorConditionals,
1699 },
1700 Invalidate {
1701 byte: usize,
1702 },
1703}
1704
1705impl MacroEvent {
1706 pub fn byte(&self) -> usize {
1707 match self {
1708 Self::Define { byte, .. }
1709 | Self::Undef { byte, .. }
1710 | Self::Include { byte, .. }
1711 | Self::Invalidate { byte } => *byte,
1712 }
1713 }
1714}
1715
1716#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1717pub enum CallArityEvidence {
1718 Exact(usize),
1719 Unknown,
1720}
1721
1722impl CallArityEvidence {
1723 pub fn exact(self) -> Option<usize> {
1724 match self {
1725 Self::Exact(arity) => Some(arity),
1726 Self::Unknown => None,
1727 }
1728 }
1729
1730 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1731 self.exact().map(|arity| expected.accepts(arity))
1732 }
1733}
1734
1735#[derive(Clone)]
1736struct DeclaredFieldTypeFact {
1737 type_text: String,
1738 indirection: i32,
1739 template_arguments: Option<Vec<CppTemplateExpression>>,
1740}
1741
1742#[derive(Clone, PartialEq, Eq)]
1743enum StructuredAliasTarget {
1744 Builtin,
1745 Named {
1746 components: Vec<String>,
1747 global: bool,
1748 arguments: Option<Vec<CppTemplateExpression>>,
1749 },
1750}
1751
1752struct CppAlias {
1753 name: String,
1754 target: String,
1755 namespace: Option<String>,
1756}
1757
1758type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1759
1760#[derive(Debug, Clone, PartialEq, Eq)]
1764pub enum CppTemplateResolutionError {
1765 AliasCycle { alias: CodeUnit },
1767 ArgumentBinding,
1769 Substitution,
1771 PrimarySelection,
1774 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1777}
1778
1779fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1782 let mut distinct: Vec<CodeUnit> = Vec::new();
1783 for unit in units {
1784 if !distinct
1785 .iter()
1786 .any(|existing| same_visible_symbol(existing, unit))
1787 {
1788 distinct.push(unit.clone());
1789 }
1790 }
1791 distinct
1792}
1793
1794pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1797 visibility: Factory,
1798 file: &ProjectFile,
1799 root: Node<'_>,
1800 source: &str,
1801 max_references: usize,
1802 cancelled: impl FnMut() -> bool,
1803) -> MacroLexicalReferences
1804where
1805 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1806{
1807 crate::graph::macro_lexical::all_references(
1808 visibility,
1809 file,
1810 root,
1811 source,
1812 max_references,
1813 cancelled,
1814 )
1815}
1816
1817impl<'a> VisibilityIndex<'a> {
1818 pub fn cpp(&self) -> &'a dyn CppSource {
1819 self.cpp
1820 }
1821
1822 pub fn token(&self) -> QueryToken<'a> {
1824 self.token
1825 }
1826
1827 pub fn has_unresolved_include_visible_before(
1835 &self,
1836 file: &ProjectFile,
1837 before_byte: usize,
1838 ) -> bool {
1839 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1840 return false;
1841 };
1842 let cell = self
1843 .structured_include_fact_cells
1844 .lock()
1845 .expect("C++ structured include-fact cache poisoned")
1846 .entry(file.clone())
1847 .or_default()
1848 .clone();
1849 let facts = cell.get_or_init(|| collect_structured_include_facts(prepared.as_ref()));
1850 has_unresolved_include_visible_before_in_prepared(
1851 file,
1852 prepared.as_ref(),
1853 self.cpp.include_target_index(),
1854 facts,
1855 before_byte,
1856 )
1857 }
1858
1859 #[cfg(any(test, feature = "test-support"))]
1867 pub fn from_visible_files_for_test(
1868 cpp: &'a dyn CppSource,
1869 token: QueryToken<'a>,
1870 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1871 ) -> Self {
1872 let visible_source_files_by_root = visible_by_file
1873 .iter()
1874 .map(|(file, visible)| {
1875 (
1876 file.clone(),
1877 visible
1878 .iter()
1879 .map(|unit| unit.source().clone())
1880 .chain(std::iter::once(file.clone()))
1881 .collect(),
1882 )
1883 })
1884 .collect();
1885 let mut global_field_internal_linkage = HashMap::default();
1886 Self {
1887 cpp,
1888 token,
1889 visible_by_identifier: build_visible_identifier_index(
1890 &CppGraphSource::from_source(cpp, token),
1891 &visible_by_file,
1892 &visible_source_files_by_root,
1893 &mut global_field_internal_linkage,
1894 ),
1895 global_field_internal_linkage,
1896 visible_by_file,
1897 visible_source_files_by_root,
1898 alias_cells: Mutex::new(HashMap::default()),
1899 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1900 parser_alias_target_matches: RwLock::new(HashMap::default()),
1901 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1902 project_using_index: OnceLock::new(),
1903 callable_reference_specs: Mutex::new(HashMap::default()),
1904 structured_include_fact_cells: Mutex::new(HashMap::default()),
1905 include_activation_cells: Mutex::new(HashMap::default()),
1906 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1907 include_path_admission_cells: Mutex::new(HashMap::default()),
1908 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1909 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1910 conditional_include_projection_state_count: AtomicUsize::new(0),
1911 conditional_include_target_state_count: AtomicUsize::new(0),
1912 include_activation_build_count: AtomicUsize::new(0),
1913 using_donor_activation_count: AtomicUsize::new(0),
1914 using_namespace_lookup_count: AtomicUsize::new(0),
1915 using_name_candidate_inspection_count: AtomicUsize::new(0),
1916 callable_reference_spec_build_count: AtomicUsize::new(0),
1917 alias_source_parse_counts: Mutex::new(HashMap::default()),
1918 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1919 parser_alias_fallback_calls: AtomicUsize::new(0),
1920 parser_alias_fallback_files: AtomicUsize::new(0),
1921 parser_alias_source_parses: AtomicUsize::new(0),
1922 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1923 field_type_facts: Mutex::new(HashMap::default()),
1924 structured_alias_targets: Mutex::new(HashMap::default()),
1925 callable_comparables: Mutex::new(HashMap::default()),
1926 comparable_name_declarations: Mutex::new(HashMap::default()),
1927 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1928 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1929 precise_parent_cache: Mutex::new(HashMap::default()),
1930 c_tag_kind_cache: Mutex::new(HashMap::default()),
1931 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1932 macro_event_cells: Mutex::new(HashMap::default()),
1933 macro_event_name_sets: Mutex::new(HashMap::default()),
1934 macro_include_protection_cells: Mutex::new(HashMap::default()),
1935 macro_environment_checkpoints: Mutex::new(HashMap::default()),
1936 macro_replacements: Mutex::new(HashMap::default()),
1937 macro_local_binding_templates: Mutex::new(HashMap::default()),
1938 macro_lexical_templates: Mutex::new(HashMap::default()),
1939 macro_replacement_bodies: Mutex::new(HashMap::default()),
1940 macro_type_parameters: Mutex::new(HashMap::default()),
1941 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1942 macro_replacement_parse_count: AtomicUsize::new(0),
1943 macro_event_application_count: AtomicUsize::new(0),
1944 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1945 macro_environment_copy_count: AtomicUsize::new(0),
1946 macro_environment_request_count: AtomicUsize::new(0),
1947 cpp_template_metadata: HashMap::default(),
1948 cpp_template_families: HashMap::default(),
1949 qualified_candidate_inspections: AtomicUsize::new(0),
1950 target_preserving_type_resolution_count: AtomicUsize::new(0),
1951 visibility_identifier_lookup_count: 0,
1952 visibility_identifier_batch_count: 0,
1953 }
1954 }
1955
1956 fn cpp_source(&self) -> CppGraphSource<'a> {
1963 CppGraphSource::from_source(self.cpp, self.token)
1964 }
1965
1966 pub fn build(
1967 cpp: &'a dyn CppSource,
1968 token: QueryToken<'a>,
1969 analyzer: &CppGraphSource<'_>,
1970 roots: &HashSet<ProjectFile>,
1971 ) -> Self {
1972 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1973 }
1974
1975 pub fn build_with_cancellation(
1976 cpp: &'a dyn CppSource,
1977 token: QueryToken<'a>,
1978 analyzer: &CppGraphSource<'_>,
1979 roots: &HashSet<ProjectFile>,
1980 cancellation: Option<&CancellationToken>,
1981 ) -> Self {
1982 let visibility_started = Instant::now();
1983 let include_targets = cpp.include_target_index();
1984 let includes_started = Instant::now();
1985 let mut include_graph = IncludeGraph::default();
1986 for root in roots {
1987 include_graph.extend_with(root, cancellation, &mut |file| {
1988 cpp_include_paths(&cpp.visibility_import_statements(token, file))
1989 .into_iter()
1990 .flat_map(|include| {
1991 resolve_include_targets_with_index(file, &include, include_targets)
1992 })
1993 .collect()
1994 });
1995 }
1996 let include_elapsed = includes_started.elapsed();
1997 let include_file_count = include_graph.files().count();
1998 let visible_source_files_by_root = roots
1999 .iter()
2000 .map(|root| {
2001 (
2002 root.clone(),
2003 include_graph.reachable_files(root, cancellation),
2004 )
2005 })
2006 .collect::<HashMap<_, _>>();
2007 let mut visibility_stats = BoundedVisibilityStats::default();
2008 let mut visible_by_file = build_bounded_visible_declarations(
2009 cpp,
2010 token,
2011 analyzer,
2012 roots,
2013 &visible_source_files_by_root,
2014 cancellation,
2015 &mut visibility_stats,
2016 );
2017 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2018 eprintln!(
2019 "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={}",
2020 visibility_started.elapsed().as_millis(),
2021 include_elapsed.as_millis(),
2022 include_file_count,
2023 visibility_stats.rounds,
2024 visibility_stats.root_names,
2025 visibility_stats.identifier_lookups,
2026 visibility_stats.identifier_batches,
2027 visibility_stats.candidate_units,
2028 visibility_stats.candidate_sources,
2029 visibility_stats.declaration_reads,
2030 visibility_stats.declaration_units,
2031 visibility_stats.selected_units,
2032 visibility_stats.dependency_ast_nodes,
2033 visibility_stats.dependency_names,
2034 visibility_stats.lookup_elapsed.as_millis(),
2035 visibility_stats.declaration_elapsed.as_millis(),
2036 visibility_stats.dependency_ast_elapsed.as_millis(),
2037 );
2038 }
2039 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2040 let finalize_started = Instant::now();
2041 if report_stats {
2042 eprintln!(
2043 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2044 visible_by_file.len(),
2045 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2046 );
2047 }
2048 let owner_started = Instant::now();
2049 if report_stats {
2050 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2051 }
2052 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2053 if report_stats {
2054 eprintln!(
2055 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2056 owner_stats.unseen_owners,
2057 owner_stats.definition_lookups,
2058 owner_stats.admitted,
2059 owner_started.elapsed().as_millis(),
2060 );
2061 }
2062 let mut global_field_internal_linkage = HashMap::default();
2063 let identifier_started = Instant::now();
2064 if report_stats {
2065 eprintln!(
2066 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2067 );
2068 }
2069 let visible_by_identifier = build_visible_identifier_index(
2070 analyzer,
2071 &visible_by_file,
2072 &visible_source_files_by_root,
2073 &mut global_field_internal_linkage,
2074 );
2075 if report_stats {
2076 eprintln!(
2077 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2078 visible_by_identifier.len(),
2079 visible_by_identifier
2080 .values()
2081 .map(HashMap::len)
2082 .sum::<usize>(),
2083 visible_by_identifier
2084 .values()
2085 .flat_map(HashMap::values)
2086 .map(Vec::len)
2087 .sum::<usize>(),
2088 identifier_started.elapsed().as_millis(),
2089 );
2090 }
2091 let mut cpp_template_metadata = HashMap::default();
2092 let metadata_started = Instant::now();
2093 let mut template_classes = 0usize;
2094 if report_stats {
2095 eprintln!(
2096 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2097 );
2098 }
2099 for unit in visible_by_file
2100 .values()
2101 .flatten()
2102 .filter(|unit| unit.is_class())
2103 {
2104 template_classes += 1;
2105 if cpp_template_metadata.contains_key(unit) {
2106 continue;
2107 }
2108 if let Some(metadata) = cpp.template_metadata(unit) {
2109 cpp_template_metadata.insert(unit.clone(), metadata);
2110 }
2111 }
2112 if report_stats {
2113 eprintln!(
2114 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2115 template_classes,
2116 cpp_template_metadata.len(),
2117 metadata_started.elapsed().as_millis(),
2118 );
2119 }
2120 let families_started = Instant::now();
2121 if report_stats {
2122 eprintln!(
2123 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2124 );
2125 }
2126 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2127 for (unit, metadata) in &cpp_template_metadata {
2128 cpp_template_families
2129 .entry(metadata.primary_fq_name.clone())
2130 .or_default()
2131 .push(unit.clone());
2132 }
2133 for family in cpp_template_families.values_mut() {
2142 sort_lookup_units(family);
2143 }
2144 if report_stats {
2145 eprintln!(
2146 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2147 cpp_template_families.len(),
2148 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2149 families_started.elapsed().as_millis(),
2150 );
2151 eprintln!(
2152 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2153 visible_by_file.len(),
2154 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2155 finalize_started.elapsed().as_millis(),
2156 visibility_started.elapsed().as_millis(),
2157 );
2158 }
2159 Self {
2160 cpp,
2161 token,
2162 visible_by_file,
2163 visible_by_identifier,
2164 global_field_internal_linkage,
2165 visible_source_files_by_root,
2166 alias_cells: Mutex::new(HashMap::default()),
2167 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2168 parser_alias_target_matches: RwLock::new(HashMap::default()),
2169 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2170 project_using_index: OnceLock::new(),
2171 callable_reference_specs: Mutex::new(HashMap::default()),
2172 structured_include_fact_cells: Mutex::new(HashMap::default()),
2173 include_activation_cells: Mutex::new(HashMap::default()),
2174 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2175 include_path_admission_cells: Mutex::new(HashMap::default()),
2176 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2177 #[cfg(any(test, feature = "test-support"))]
2178 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2179 #[cfg(any(test, feature = "test-support"))]
2180 conditional_include_projection_state_count: AtomicUsize::new(0),
2181 #[cfg(any(test, feature = "test-support"))]
2182 conditional_include_target_state_count: AtomicUsize::new(0),
2183 #[cfg(any(test, feature = "test-support"))]
2184 include_activation_build_count: AtomicUsize::new(0),
2185 #[cfg(any(test, feature = "test-support"))]
2186 using_donor_activation_count: AtomicUsize::new(0),
2187 #[cfg(any(test, feature = "test-support"))]
2188 using_namespace_lookup_count: AtomicUsize::new(0),
2189 #[cfg(any(test, feature = "test-support"))]
2190 using_name_candidate_inspection_count: AtomicUsize::new(0),
2191 #[cfg(any(test, feature = "test-support"))]
2192 callable_reference_spec_build_count: AtomicUsize::new(0),
2193 #[cfg(any(test, feature = "test-support"))]
2194 alias_source_parse_counts: Mutex::new(HashMap::default()),
2195 #[cfg(any(test, feature = "test-support"))]
2196 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2197 parser_alias_fallback_calls: AtomicUsize::new(0),
2198 parser_alias_fallback_files: AtomicUsize::new(0),
2199 parser_alias_source_parses: AtomicUsize::new(0),
2200 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2201 field_type_facts: Mutex::new(HashMap::default()),
2202 structured_alias_targets: Mutex::new(HashMap::default()),
2203 callable_comparables: Mutex::new(HashMap::default()),
2204 comparable_name_declarations: Mutex::new(HashMap::default()),
2205 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2206 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2207 precise_parent_cache: Mutex::new(HashMap::default()),
2208 c_tag_kind_cache: Mutex::new(HashMap::default()),
2209 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2210 macro_event_cells: Mutex::new(HashMap::default()),
2211 macro_event_name_sets: Mutex::new(HashMap::default()),
2212 macro_include_protection_cells: Mutex::new(HashMap::default()),
2213 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2214 macro_replacements: Mutex::new(HashMap::default()),
2215 macro_local_binding_templates: Mutex::new(HashMap::default()),
2216 macro_lexical_templates: Mutex::new(HashMap::default()),
2217 macro_replacement_bodies: Mutex::new(HashMap::default()),
2218 macro_type_parameters: Mutex::new(HashMap::default()),
2219 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2220 #[cfg(any(test, feature = "test-support"))]
2221 macro_replacement_parse_count: AtomicUsize::new(0),
2222 #[cfg(any(test, feature = "test-support"))]
2223 macro_event_application_count: AtomicUsize::new(0),
2224 #[cfg(any(test, feature = "test-support"))]
2225 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2226 #[cfg(any(test, feature = "test-support"))]
2227 macro_environment_copy_count: AtomicUsize::new(0),
2228 #[cfg(any(test, feature = "test-support"))]
2229 macro_environment_request_count: AtomicUsize::new(0),
2230 cpp_template_metadata,
2231 cpp_template_families,
2232 #[cfg(any(test, feature = "test-support"))]
2233 qualified_candidate_inspections: AtomicUsize::new(0),
2234 #[cfg(any(test, feature = "test-support"))]
2235 target_preserving_type_resolution_count: AtomicUsize::new(0),
2236 #[cfg(any(test, feature = "test-support"))]
2237 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2238 #[cfg(any(test, feature = "test-support"))]
2239 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2240 }
2241 }
2242
2243 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2244 if file == target.source() {
2245 return true;
2246 }
2247 if self.global_field_has_internal_linkage(target) {
2248 return self
2249 .visible_source_files_by_root
2250 .get(file)
2251 .is_some_and(|sources| sources.contains(target.source()));
2252 }
2253 self.visible_by_file
2254 .get(file)
2255 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2256 }
2257
2258 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2259 self.global_field_internal_linkage
2260 .get(unit)
2261 .copied()
2262 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2263 }
2264
2265 pub fn call_arity_evidence(
2266 &self,
2267 file: &ProjectFile,
2268 call: Node<'_>,
2269 source: &str,
2270 ) -> CallArityEvidence {
2271 self.call_arity_evidence_at(file, call, source, call.start_byte())
2272 }
2273
2274 pub fn call_arity_evidence_at(
2282 &self,
2283 file: &ProjectFile,
2284 call: Node<'_>,
2285 source: &str,
2286 environment_byte: usize,
2287 ) -> CallArityEvidence {
2288 let Some(arguments) = call
2289 .child_by_field_name("arguments")
2290 .or_else(|| call.child_by_field_name("parameters"))
2291 .or_else(|| call.child_by_field_name("value"))
2292 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2293 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2294 else {
2295 return CallArityEvidence::Exact(0);
2296 };
2297 let recovered_c_keyword_arguments =
2298 recovered_c_keyword_argument_count(file, call, arguments, source);
2299 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2300 let arguments = argument_children(arguments)
2301 .flat_map(|argument| {
2302 recovered_c_new_expression_arguments(argument, c_semantics)
2303 .map(Vec::from)
2304 .unwrap_or_else(|| vec![argument])
2305 })
2306 .collect::<Vec<_>>();
2307 if arguments
2308 .iter()
2309 .all(|argument| !argument_shape_may_change_arity(*argument))
2310 {
2311 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2312 }
2313 let environment = self.macro_environment(file, environment_byte);
2314 let mut stack = Vec::new();
2315 let mut total = recovered_c_keyword_arguments;
2316 for argument in arguments {
2317 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2318 return CallArityEvidence::Unknown;
2319 }
2320 let CallArityEvidence::Exact(spread) =
2321 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2322 else {
2323 return CallArityEvidence::Unknown;
2324 };
2325 total += spread;
2326 }
2327 CallArityEvidence::Exact(total)
2328 }
2329
2330 fn argument_arity_evidence(
2331 &self,
2332 argument: Node<'_>,
2333 source: &str,
2334 environment: &MacroEnvironment,
2335 stack: &mut Vec<(ProjectFile, usize)>,
2336 ) -> CallArityEvidence {
2337 let (name, invocation_arguments, function_like) = match argument.kind() {
2338 "identifier" => (node_text(argument, source), None, false),
2339 "call_expression" => {
2340 let Some(function) = argument.child_by_field_name("function") else {
2341 return CallArityEvidence::Exact(1);
2342 };
2343 if function.kind() != "identifier" {
2344 return CallArityEvidence::Exact(1);
2345 }
2346 let Some(arguments) = argument.child_by_field_name("arguments") else {
2347 return CallArityEvidence::Exact(1);
2348 };
2349 (node_text(function, source), Some(arguments), true)
2350 }
2351 _ => return CallArityEvidence::Exact(1),
2352 };
2353 let Some(binding) = environment.binding(name) else {
2354 return if environment.unknown_names {
2355 CallArityEvidence::Unknown
2356 } else {
2357 CallArityEvidence::Exact(1)
2358 };
2359 };
2360 if !binding.is_exact() {
2361 return CallArityEvidence::Unknown;
2362 }
2363 match (&binding.definition, invocation_arguments, function_like) {
2364 (MacroDefinition::Object { replacement }, None, false) => self
2365 .replacement_arity_evidence(
2366 replacement,
2367 &[],
2368 &[],
2369 source,
2370 environment,
2371 stack,
2372 binding,
2373 ),
2374 (
2375 MacroDefinition::Function {
2376 parameters,
2377 replacement,
2378 },
2379 Some(arguments),
2380 true,
2381 ) => {
2382 let actuals = argument_children(arguments).collect::<Vec<_>>();
2383 if actuals.len() != parameters.len() {
2384 CallArityEvidence::Unknown
2385 } else {
2386 self.replacement_arity_evidence(
2387 replacement,
2388 parameters,
2389 &actuals,
2390 source,
2391 environment,
2392 stack,
2393 binding,
2394 )
2395 }
2396 }
2397 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2398 _ => CallArityEvidence::Unknown,
2399 }
2400 }
2401
2402 #[allow(clippy::too_many_arguments)]
2403 fn replacement_arity_evidence(
2404 &self,
2405 replacement: &str,
2406 parameters: &[String],
2407 actuals: &[Node<'_>],
2408 actual_source: &str,
2409 environment: &MacroEnvironment,
2410 stack: &mut Vec<(ProjectFile, usize)>,
2411 binding: &MacroBinding,
2412 ) -> CallArityEvidence {
2413 let identity = (binding.source.clone(), binding.declaration_byte);
2414 if stack.contains(&identity) || replacement.trim().is_empty() {
2415 return CallArityEvidence::Unknown;
2416 }
2417 stack.push(identity);
2418 let parsed = self.parsed_macro_replacement(binding, replacement);
2419 let evidence = (|| {
2420 let ParsedMacroReplacement::Parsed {
2421 source: sentinel,
2422 tree,
2423 } = parsed.as_ref()
2424 else {
2425 return None;
2426 };
2427 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2428 let arguments = call.child_by_field_name("arguments")?;
2429 let mut total = 0usize;
2430 for argument in argument_children(arguments) {
2431 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2432 return None;
2433 }
2434 if argument.kind() == "identifier"
2435 && let Some(parameter_index) = parameters
2436 .iter()
2437 .position(|parameter| parameter == node_text(argument, sentinel))
2438 {
2439 if !macro_expansion_shape_is_safe(
2440 actuals[parameter_index],
2441 actual_source,
2442 &[],
2443 environment,
2444 ) {
2445 return None;
2446 }
2447 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2448 actuals[parameter_index],
2449 actual_source,
2450 environment,
2451 stack,
2452 ) else {
2453 return None;
2454 };
2455 total += spread;
2456 continue;
2457 }
2458 let CallArityEvidence::Exact(spread) =
2459 self.argument_arity_evidence(argument, sentinel, environment, stack)
2460 else {
2461 return None;
2462 };
2463 total += spread;
2464 }
2465 Some(CallArityEvidence::Exact(total))
2466 })()
2467 .unwrap_or(CallArityEvidence::Unknown);
2468 stack.pop();
2469 evidence
2470 }
2471
2472 fn parsed_macro_replacement(
2473 &self,
2474 binding: &MacroBinding,
2475 replacement: &str,
2476 ) -> Arc<ParsedMacroReplacement> {
2477 let key = (binding.source.clone(), binding.declaration_byte);
2478 let mut cache = self
2479 .macro_replacements
2480 .lock()
2481 .expect("C++ macro replacement cache poisoned");
2482 if let Some(parsed) = cache.get(&key) {
2483 return Arc::clone(parsed);
2484 }
2485 #[cfg(any(test, feature = "test-support"))]
2486 self.macro_replacement_parse_count
2487 .fetch_add(1, Ordering::Relaxed);
2488 let source =
2489 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2490 let mut parser = Parser::new();
2491 let parsed = parser
2492 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2493 .ok()
2494 .and_then(|()| parser.parse(&source, None))
2495 .filter(|tree| !tree.root_node().has_error())
2496 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2497 ParsedMacroReplacement::Parsed { source, tree }
2498 });
2499 let parsed = Arc::new(parsed);
2500 cache.insert(key, Arc::clone(&parsed));
2501 parsed
2502 }
2503
2504 pub fn function_macro_local_binding<'tree>(
2514 &self,
2515 file: &ProjectFile,
2516 statement: Node<'tree>,
2517 source: &str,
2518 ) -> Option<MacroLocalBinding<'tree>> {
2519 if !is_c_source_file(file) {
2520 return None;
2521 }
2522 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2523 return Some(binding);
2524 }
2525 let call = match statement.kind() {
2526 "call_expression" => statement,
2527 "expression_statement" if statement.named_child_count() == 1 => {
2528 statement.named_child(0)?
2529 }
2530 _ => return None,
2531 };
2532 if call.kind() != "call_expression" {
2533 return None;
2534 }
2535 let function = call.child_by_field_name("function")?;
2536 if function.kind() != "identifier" {
2537 return None;
2538 }
2539 let arguments = call.child_by_field_name("arguments")?;
2540 let actuals = argument_children(arguments).collect::<Vec<_>>();
2541 let environment = self.macro_environment(file, call.start_byte());
2542 let function_name = node_text(function, source);
2543 let binding = environment.binding(function_name)?;
2544 let MacroDefinition::Function {
2545 parameters,
2546 replacement,
2547 } = &binding.definition
2548 else {
2549 return None;
2550 };
2551 if actuals.len() != parameters.len() {
2552 return None;
2553 }
2554 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2555 let (type_name, type_node) = match &template.declared_type {
2556 MacroLocalBindingTypeTemplate::Parameter(index) => {
2557 let actual = *actuals.get(*index)?;
2558 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2559 return None;
2560 }
2561 (node_text(actual, source).trim().to_string(), Some(actual))
2562 }
2563 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2564 };
2565 if type_name.is_empty() {
2566 return None;
2567 }
2568 Some(MacroLocalBinding {
2569 name: template.name.clone(),
2570 type_name,
2571 type_node,
2572 pointer_depth: template.pointer_depth,
2573 proven_unit: None,
2574 })
2575 }
2576
2577 pub fn function_macro_container_binding<'tree>(
2586 &self,
2587 analyzer: &CppGraphSource<'_>,
2588 file: &ProjectFile,
2589 assignment: Node<'tree>,
2590 source: &str,
2591 ) -> Option<MacroLocalBinding<'tree>> {
2592 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2593 return None;
2594 }
2595 let name_node = assignment.child_by_field_name("left")?;
2596 if name_node.kind() != "identifier" {
2597 return None;
2598 }
2599 let call = assignment.child_by_field_name("right")?;
2600 if call.kind() != "call_expression" {
2601 return None;
2602 }
2603 let function = call.child_by_field_name("function")?;
2604 if function.kind() != "identifier" {
2605 return None;
2606 }
2607 let arguments = call.child_by_field_name("arguments")?;
2608 let actuals = argument_children(arguments).collect::<Vec<_>>();
2609 let function_name = node_text(function, source);
2610 let environment = self.macro_environment(file, call.start_byte());
2611 let binding = environment.binding(function_name)?;
2612 if !binding.is_exact() {
2613 return None;
2614 }
2615 let MacroDefinition::Function {
2616 parameters,
2617 replacement,
2618 } = &binding.definition
2619 else {
2620 return None;
2621 };
2622 if actuals.len() != parameters.len() {
2623 return None;
2624 }
2625 let body = self.parsed_macro_replacement_body(
2626 &(binding.source.clone(), binding.declaration_byte),
2627 parameters,
2628 replacement,
2629 )?;
2630 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2631 let type_argument = *actuals.get(type_parameter)?;
2632 let type_node = macro_type_argument_node(type_argument, source)?;
2633 let type_name = node_text(type_node, source).trim().to_string();
2634 if type_name.is_empty() {
2635 return None;
2636 }
2637 let explicit_tag = match node_text(type_argument, source) {
2638 "struct" => Some(CppCTagKind::Struct),
2639 "union" => Some(CppCTagKind::Union),
2640 _ => None,
2641 };
2642 let resolved_type = if let Some(tag) = explicit_tag {
2643 let candidates = self
2644 .visible_identifier_candidates(file, &type_name)
2645 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2646 .collect::<Vec<_>>();
2647 self.resolve_type_candidates(
2648 analyzer,
2649 file,
2650 &candidates,
2651 TypeCandidateResolution::Canonical,
2652 )
2653 .ok()
2654 } else {
2655 self.resolve_type_node_result(file, type_node, source)
2656 .ok()
2657 .flatten()
2658 };
2659 let proven_unit = resolved_type?;
2660 Some(MacroLocalBinding {
2661 name: node_text(name_node, source).to_string(),
2662 type_name,
2663 type_node: Some(type_node),
2664 pointer_depth: 1,
2665 proven_unit: Some(proven_unit),
2666 })
2667 }
2668
2669 pub fn macro_local_binding_at<'tree>(
2672 &self,
2673 file: &ProjectFile,
2674 root: Node<'tree>,
2675 source: &str,
2676 start_byte: usize,
2677 end_byte: usize,
2678 ) -> Option<MacroLocalBinding<'tree>> {
2679 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2680 }
2681
2682 pub fn macro_lexical_binding(
2686 &self,
2687 file: &ProjectFile,
2688 root: Node<'_>,
2689 source: &str,
2690 start_byte: usize,
2691 end_byte: usize,
2692 ) -> Option<MacroLexicalBinding> {
2693 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2694 }
2695
2696 pub fn macro_lexical_references(
2701 &self,
2702 file: &ProjectFile,
2703 root: Node<'_>,
2704 source: &str,
2705 max_references: usize,
2706 cancelled: impl FnMut() -> bool,
2707 ) -> MacroLexicalReferences {
2708 crate::graph::macro_lexical::all_references(
2709 || self,
2710 file,
2711 root,
2712 source,
2713 max_references,
2714 cancelled,
2715 )
2716 }
2717
2718 pub(crate) fn function_macro_binding_at(
2723 &self,
2724 file: &ProjectFile,
2725 name: &str,
2726 before_byte: usize,
2727 ) -> Option<(ProjectFile, usize)> {
2728 let environment = self.macro_environment(file, before_byte);
2729 let binding = environment.binding(name)?;
2730 if binding.is_exact()
2731 && matches!(
2732 binding.definition,
2733 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2734 )
2735 {
2736 return Some((binding.source.clone(), binding.declaration_byte));
2737 }
2738 if binding.source != *file {
2744 return None;
2745 }
2746 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2747 let root = prepared.tree().root_node();
2748 let source = prepared.source();
2749 let reference = root.descendant_for_byte_range(
2750 before_byte,
2751 before_byte.saturating_add(1).min(source.len()),
2752 )?;
2753 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2754 let cell = self.macro_event_cell(file);
2755 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2756 let event = events
2757 .iter()
2758 .find(|event| event.byte() == binding.declaration_byte)?;
2759 let MacroEvent::Define {
2760 name: defined_name,
2761 binding: definition,
2762 conditionals,
2763 ..
2764 } = event
2765 else {
2766 return None;
2767 };
2768 if defined_name != name
2769 || conditionals.is_empty()
2770 || !conditionals
2771 .iter()
2772 .all(|condition| reference_conditions.contains(condition))
2773 || !matches!(
2774 definition.definition,
2775 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2776 )
2777 {
2778 return None;
2779 }
2780 Some((definition.source.clone(), definition.declaration_byte))
2781 }
2782
2783 pub fn function_macro_type_argument<'tree>(
2790 &self,
2791 file: &ProjectFile,
2792 node: Node<'tree>,
2793 source: &str,
2794 ) -> Option<Node<'tree>> {
2795 if !is_c_source_file(file) {
2796 return None;
2797 }
2798 let (call, argument_index, argument) = c_function_macro_argument(node)?;
2799 let function = call.child_by_field_name("function")?;
2800 let function_name = node_text(function, source);
2801 if function_name.is_empty() {
2802 return None;
2803 }
2804 if !self.file_defines_macro_name(file, function_name)
2805 && !self
2806 .visible_identifier_candidates(file, function_name)
2807 .any(|candidate| candidate.is_macro())
2808 {
2809 return None;
2810 }
2811 let environment = self.macro_environment(file, call.start_byte());
2812 let binding = environment.binding(function_name)?;
2813 if !binding.is_exact() {
2814 return None;
2815 }
2816 let (parameters, replacement, variadic) = match &binding.definition {
2817 MacroDefinition::Function {
2818 parameters,
2819 replacement,
2820 } => (parameters, replacement, false),
2821 MacroDefinition::VariadicFunction {
2822 parameters,
2823 replacement,
2824 } => (parameters, replacement, true),
2825 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2826 };
2827 let actuals = call
2828 .child_by_field_name("arguments")
2829 .map(argument_children)
2830 .into_iter()
2831 .flatten()
2832 .collect::<Vec<_>>();
2833 let arity_matches = if variadic {
2834 actuals.len() >= parameters.len()
2835 } else {
2836 actuals.len() == parameters.len()
2837 };
2838 let type_parameters = self.macro_type_parameter_indices(
2839 &(binding.source.clone(), binding.declaration_byte),
2840 parameters,
2841 replacement,
2842 )?;
2843 if !arity_matches || !type_parameters.contains(&argument_index) {
2844 return None;
2845 }
2846 let type_node = macro_type_argument_node(argument, source)?;
2847 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2848 return None;
2849 }
2850 Some(type_node)
2851 }
2852
2853 fn macro_local_binding_template(
2854 &self,
2855 binding: &MacroBinding,
2856 parameters: &[String],
2857 replacement: &str,
2858 ) -> Option<Arc<MacroLocalBindingTemplate>> {
2859 let key = (binding.source.clone(), binding.declaration_byte);
2860 if let Some(template) = self
2861 .macro_local_binding_templates
2862 .lock()
2863 .expect("C++ macro local-binding cache poisoned")
2864 .get(&key)
2865 {
2866 return template.clone();
2867 }
2868 let template = (|| {
2869 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2870 let sentinel = body.source.as_str();
2871 let statements = body.statements()?;
2872 if statements.named_child_count() != 1 {
2873 return None;
2874 }
2875 let declaration = statements.named_child(0)?;
2876 if declaration.kind() != "declaration" {
2877 return None;
2878 }
2879 let type_node = declaration
2880 .child_by_field_name("type")
2881 .or_else(|| first_type_child(declaration))?;
2882 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2883 let mut cursor = declaration.walk();
2884 declaration.named_children(&mut cursor).find_map(|child| {
2885 if child.kind() == "init_declarator" {
2886 child.child_by_field_name("declarator")
2887 } else {
2888 is_declarator_node(child).then_some(child)
2889 }
2890 })
2891 })?;
2892 let name = extract_variable_name(declarator, sentinel)?;
2893 let pointer_depth = declared_name_indirection(declaration, type_node, &name, sentinel)?;
2894 let type_text = node_text(type_node, sentinel).trim();
2895 let declared_type = parameters
2896 .iter()
2897 .position(|parameter| parameter == type_text)
2898 .map(MacroLocalBindingTypeTemplate::Parameter)
2899 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2900 Some(Arc::new(MacroLocalBindingTemplate {
2901 name,
2902 declared_type,
2903 pointer_depth,
2904 }))
2905 })();
2906 self.macro_local_binding_templates
2907 .lock()
2908 .expect("C++ macro local-binding cache poisoned")
2909 .insert(key, template.clone());
2910 template
2911 }
2912
2913 pub fn function_macro_replacement_body(
2920 &self,
2921 file: &ProjectFile,
2922 definition: Node<'_>,
2923 source: &str,
2924 ) -> Option<Arc<ParsedReplacementBody>> {
2925 debug_assert_eq!(definition.kind(), "preproc_function_def");
2926 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
2927 MacroDefinition::Function {
2928 parameters,
2929 replacement,
2930 }
2931 | MacroDefinition::VariadicFunction {
2932 parameters,
2933 replacement,
2934 } => (parameters, replacement),
2935 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2936 };
2937 self.parsed_macro_replacement_body(
2938 &(file.clone(), definition.start_byte()),
2939 ¶meters,
2940 &replacement,
2941 )
2942 }
2943
2944 fn parsed_macro_replacement_body(
2952 &self,
2953 key: &(ProjectFile, usize),
2954 parameters: &[String],
2955 replacement: &str,
2956 ) -> Option<Arc<ParsedReplacementBody>> {
2957 if let Some(body) = self
2958 .macro_replacement_bodies
2959 .lock()
2960 .expect("C++ macro replacement body cache poisoned")
2961 .get(key)
2962 {
2963 return body.clone();
2964 }
2965 let body = (|| {
2966 if replacement.trim().is_empty() {
2967 return None;
2968 }
2969 let (source, tree, original_offsets) =
2970 Self::parse_macro_replacement_body(replacement, parameters)?;
2971 let body = ParsedReplacementBody {
2972 source,
2973 tree,
2974 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2975 parameters: parameters.to_vec(),
2976 original_offsets,
2977 };
2978 body.statements()?;
2979 if body.expands_variadic_arguments() {
2980 return None;
2981 }
2982 Some(Arc::new(body))
2983 })();
2984 self.macro_replacement_bodies
2985 .lock()
2986 .expect("C++ macro replacement body cache poisoned")
2987 .insert(key.clone(), body.clone());
2988 body
2989 }
2990
2991 fn parse_macro_replacement_body(
2999 replacement: &str,
3000 parameters: &[String],
3001 ) -> Option<(String, Tree, Box<[usize]>)> {
3002 let normalized = normalize_macro_continuations(replacement);
3003 let parse = |replacement: &str| {
3004 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3005 let mut parser = Parser::new();
3006 parser
3007 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3008 .ok()?;
3009 let tree = parser.parse(&source, None)?;
3010 Some((source, tree))
3011 };
3012 let (source, tree) = parse(&normalized)?;
3013 if !tree.root_node().has_error() {
3014 let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3015 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3016 return Some((source, tree, original_offsets.into_boxed_slice()));
3017 }
3018
3019 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3020 let mut insertion_points = Vec::new();
3021 let mut stack = vec![tree.root_node()];
3022 while let Some(node) = stack.pop() {
3023 if matches!(
3024 node.kind(),
3025 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3026 ) && parameters
3027 .iter()
3028 .any(|parameter| parameter.as_str() == node_text(node, &source))
3029 && Self::macro_formal_needs_statement_separator(node)
3030 {
3031 let point = node.end_byte().saturating_sub(body_offset);
3032 if point <= normalized.len() && !insertion_points.contains(&point) {
3033 insertion_points.push(point);
3034 }
3035 }
3036 push_named_children_reversed(node, &mut stack);
3037 }
3038 if insertion_points.is_empty() {
3039 return None;
3040 }
3041 insertion_points.sort_unstable();
3042 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3043 let mut original_offsets =
3044 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3045 let mut next_insertion = 0;
3046 for (index, byte) in normalized.bytes().enumerate() {
3047 recovered.push(byte);
3048 original_offsets.push(index);
3049 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3050 recovered.push(b';');
3051 original_offsets.push(index + 1);
3052 next_insertion += 1;
3053 }
3054 }
3055 original_offsets.push(normalized.len());
3056 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3057 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3058 let (source, tree) = parse(&recovered)?;
3059 if tree.root_node().has_error() {
3060 return None;
3061 }
3062 Some((source, tree, original_offsets.into_boxed_slice()))
3063 }
3064
3065 fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3070 offsets.extend([replacement_end; 3]);
3071 }
3072
3073 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3078 let mut current = node;
3079 let mut crossed_recovery = false;
3080 while let Some(parent) = current.parent() {
3081 if parent.is_error() {
3082 crossed_recovery = true;
3083 current = parent;
3084 continue;
3085 }
3086 if matches!(
3087 parent.kind(),
3088 "call_expression"
3089 | "argument_list"
3090 | "field_expression"
3091 | "binary_expression"
3092 | "unary_expression"
3093 | "assignment_expression"
3094 | "conditional_expression"
3095 | "parenthesized_expression"
3096 | "subscript_expression"
3097 ) {
3098 return false;
3099 }
3100 if parent.kind() == "expression_statement" {
3101 return parent.named_child_count() == 1;
3102 }
3103 if parent.kind() == "compound_statement" && current == node {
3104 return true;
3105 }
3106 if matches!(
3107 parent.kind(),
3108 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3109 ) {
3110 return crossed_recovery
3111 || parent.child_by_field_name("consequence") == Some(current);
3112 }
3113 if matches!(parent.kind(), "declaration" | "init_declarator") {
3114 return false;
3115 }
3116 current = parent;
3117 }
3118 false
3119 }
3120
3121 fn macro_type_parameter_indices(
3129 &self,
3130 key: &(ProjectFile, usize),
3131 parameters: &[String],
3132 replacement: &str,
3133 ) -> Option<Arc<[usize]>> {
3134 if let Some(indices) = self
3135 .macro_type_parameters
3136 .lock()
3137 .expect("C++ macro type-parameter cache poisoned")
3138 .get(key)
3139 {
3140 return indices.clone();
3141 }
3142 #[cfg(any(test, feature = "test-support"))]
3143 self.macro_replacement_parse_count
3144 .fetch_add(1, Ordering::Relaxed);
3145 let indices = (|| {
3146 if replacement.trim().is_empty() {
3147 return None;
3148 }
3149 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3153 let mut parser = Parser::new();
3154 parser
3155 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3156 .ok()?;
3157 let tree = parser.parse(&source, None)?;
3158 let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3159 Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3160 let body = ParsedReplacementBody {
3161 source,
3162 tree,
3163 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3164 parameters: parameters.to_vec(),
3165 original_offsets: original_offsets.into_boxed_slice(),
3166 };
3167 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3168 })();
3169 self.macro_type_parameters
3170 .lock()
3171 .expect("C++ macro type-parameter cache poisoned")
3172 .insert(key.clone(), indices.clone());
3173 indices
3174 }
3175
3176 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3177 let replacement = if node.kind() == "preproc_function_def" {
3178 function_macro_replacement_span(node, source)
3179 .and_then(|span| source.get(span))
3180 .map(str::to_owned)
3181 .or_else(|| {
3182 node.child_by_field_name("value")
3183 .map(|value| node_text(value, source).to_string())
3184 })
3185 .unwrap_or_default()
3186 } else {
3187 node.child_by_field_name("value")
3188 .map(|value| node_text(value, source).to_string())
3189 .unwrap_or_default()
3190 };
3191 if node.kind() == "preproc_def" {
3192 return MacroDefinition::Object { replacement };
3193 }
3194 let Some(parameters) = node.child_by_field_name("parameters") else {
3195 return MacroDefinition::Unsupported;
3196 };
3197 let variadic = (0..parameters.child_count()).any(|index| {
3198 parameters
3199 .child(index)
3200 .is_some_and(|child| child.kind() == "...")
3201 });
3202 let parameters = (0..parameters.named_child_count())
3203 .filter_map(|index| parameters.named_child(index))
3204 .map(|parameter| node_text(parameter, source).to_string())
3205 .collect::<Vec<_>>();
3206 if variadic {
3207 MacroDefinition::VariadicFunction {
3208 parameters,
3209 replacement,
3210 }
3211 } else {
3212 MacroDefinition::Function {
3213 parameters,
3214 replacement,
3215 }
3216 }
3217 }
3218
3219 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3220 self.macro_event_cells
3221 .lock()
3222 .expect("C++ macro event cache poisoned")
3223 .entry(file.clone())
3224 .or_default()
3225 .clone()
3226 }
3227
3228 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3229 if let Some(names) = self
3230 .macro_event_name_sets
3231 .lock()
3232 .expect("C++ macro event-name cache poisoned")
3233 .get(file)
3234 .cloned()
3235 {
3236 return names.contains(name);
3237 }
3238 let cell = self.macro_event_cell(file);
3239 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3240 let names = Arc::new(
3241 events
3242 .iter()
3243 .filter_map(|event| match event {
3244 MacroEvent::Define { name, .. } => Some(name.clone()),
3245 MacroEvent::Undef { .. }
3246 | MacroEvent::Include { .. }
3247 | MacroEvent::Invalidate { .. } => None,
3248 })
3249 .collect(),
3250 );
3251 self.macro_event_name_sets
3252 .lock()
3253 .expect("C++ macro event-name cache poisoned")
3254 .insert(file.clone(), Arc::clone(&names));
3255 names.contains(name)
3256 }
3257
3258 fn macro_environment_checkpoint_cell(
3259 &self,
3260 file: &ProjectFile,
3261 ) -> MacroEnvironmentCheckpointCell {
3262 self.macro_environment_checkpoints
3263 .lock()
3264 .expect("C++ macro environment checkpoint cache poisoned")
3265 .entry(file.clone())
3266 .or_default()
3267 .clone()
3268 }
3269
3270 pub fn macro_environment(
3272 &self,
3273 file: &ProjectFile,
3274 before_byte: usize,
3275 ) -> Arc<MacroEnvironment> {
3276 #[cfg(any(test, feature = "test-support"))]
3277 self.macro_environment_request_count
3278 .fetch_add(1, Ordering::Relaxed);
3279 let cell = self.macro_event_cell(file);
3280 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3281 let frontier = events.partition_point(|event| event.byte() < before_byte);
3282 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3283 let checkpoints =
3284 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3285 let checkpoint = checkpoints.at_or_before(frontier);
3286 if checkpoint.frontier == frontier {
3287 return Arc::clone(&checkpoint.environment);
3288 }
3289 #[cfg(any(test, feature = "test-support"))]
3290 self.macro_environment_copy_count
3291 .fetch_add(1, Ordering::Relaxed);
3292 let mut environment = checkpoint.environment.as_ref().clone();
3293 let mut include_stack = HashSet::from_iter([file.clone()]);
3294 for event in &events[checkpoint.frontier..frontier] {
3295 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3296 }
3297 Arc::new(environment)
3298 }
3299
3300 fn build_macro_environment_checkpoints(
3303 &self,
3304 file: &ProjectFile,
3305 events: &[MacroEvent],
3306 ) -> MacroEnvironmentCheckpoints {
3307 #[cfg(any(test, feature = "test-support"))]
3308 self.macro_environment_checkpoint_build_count
3309 .fetch_add(1, Ordering::Relaxed);
3310 let mut environment = MacroEnvironment {
3315 build_proven_defines: self
3316 .compile_proven_guards(file)
3317 .iter()
3318 .filter_map(|guard| match guard {
3319 PreprocessorGuard::Defined(name) => Some(name.clone()),
3320 _ => None,
3321 })
3322 .collect(),
3323 ..MacroEnvironment::default()
3324 };
3325 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3326 frontier: 0,
3327 environment: Arc::new(environment.clone()),
3328 }];
3329 let checkpoint_stride = events
3336 .len()
3337 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3338 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3339 let mut include_stack = HashSet::from_iter([file.clone()]);
3340 for (index, event) in events.iter().enumerate() {
3341 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3342 let frontier = index + 1;
3343 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3344 checkpoints.push(MacroEnvironmentCheckpoint {
3345 frontier,
3346 environment: Arc::new(environment.clone()),
3347 });
3348 }
3349 }
3350 MacroEnvironmentCheckpoints { checkpoints }
3351 }
3352
3353 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3362 self.macro_environment(file, before_byte)
3363 .binding(name)
3364 .is_some()
3365 }
3366
3367 pub fn macro_name_may_be_bound_at(
3368 &self,
3369 file: &ProjectFile,
3370 name: &str,
3371 before_byte: usize,
3372 ) -> bool {
3373 self.macro_environment(file, before_byte).may_bind(name)
3374 }
3375
3376 pub fn macro_binding_matches_target_at(
3380 &self,
3381 analyzer: &CppGraphSource<'_>,
3382 file: &ProjectFile,
3383 name: &str,
3384 before_byte: usize,
3385 target: &CodeUnit,
3386 ) -> bool {
3387 let ranges = analyzer.ranges(target);
3388 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3389 self.macro_binding_matches_target_declaration_at(
3390 file,
3391 name,
3392 before_byte,
3393 target.source(),
3394 &declaration_bytes,
3395 )
3396 }
3397
3398 pub(crate) fn macro_declaration_bytes(
3403 &self,
3404 target: &CodeUnit,
3405 ranges: &[Range],
3406 ) -> Vec<usize> {
3407 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3408 return Vec::new();
3409 };
3410 ranges
3411 .iter()
3412 .filter_map(|range| {
3413 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3414 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3415 node = node.parent()?;
3416 }
3417 Some(node.start_byte())
3418 })
3419 .collect()
3420 }
3421
3422 pub(crate) fn macro_binding_matches_target_declaration_at(
3423 &self,
3424 file: &ProjectFile,
3425 name: &str,
3426 before_byte: usize,
3427 target_source: &ProjectFile,
3428 target_declaration_bytes: &[usize],
3429 ) -> bool {
3430 let environment = self.macro_environment(file, before_byte);
3431 let Some(binding) = environment.binding(name) else {
3432 return false;
3433 };
3434 if binding.definition == MacroDefinition::Unsupported {
3435 return false;
3436 }
3437 if binding.source != *target_source {
3441 return false;
3442 }
3443 target_declaration_bytes.contains(&binding.declaration_byte)
3444 }
3445
3446 pub fn resolve_ordinary_macro_reference(
3453 &self,
3454 analyzer: &CppGraphSource<'_>,
3455 file: &ProjectFile,
3456 node: Node<'_>,
3457 source: &str,
3458 ) -> OrdinaryMacroReferenceResolution {
3459 if !is_ordinary_macro_reference_node(node) {
3460 return OrdinaryMacroReferenceResolution::Missing;
3461 }
3462 let name = node_text(node, source);
3463 if name.is_empty() {
3464 return OrdinaryMacroReferenceResolution::Missing;
3465 }
3466 let visible = self
3467 .visible_identifier_candidates(file, name)
3468 .filter(|candidate| candidate.is_macro())
3469 .cloned()
3470 .collect::<Vec<_>>();
3471 let mut exact = Vec::new();
3472 for candidate in &visible {
3473 if self.macro_binding_matches_target_at(
3474 analyzer,
3475 file,
3476 name,
3477 node.start_byte(),
3478 candidate,
3479 ) && !exact
3480 .iter()
3481 .any(|existing| same_visible_symbol(existing, candidate))
3482 {
3483 exact.push(candidate.clone());
3484 }
3485 }
3486 match exact.len() {
3487 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3488 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3489 0 if !visible.is_empty()
3490 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3491 {
3492 OrdinaryMacroReferenceResolution::Ambiguous
3493 }
3494 0 => OrdinaryMacroReferenceResolution::Missing,
3495 }
3496 }
3497
3498 pub fn recovered_c_reference_ranges(
3506 &self,
3507 file: &ProjectFile,
3508 root: Node<'_>,
3509 source: &str,
3510 limit: usize,
3511 ) -> RecoveredCReferenceRanges {
3512 if !is_c_source_file(file) {
3513 return RecoveredCReferenceRanges::Complete(Vec::new());
3514 }
3515 let mut ranges = Vec::new();
3516 let mut seen = HashSet::default();
3517 let mut stack = vec![(root, root.is_error())];
3518 while let Some((node, inside_error)) = stack.pop() {
3519 let inside_error = inside_error || node.is_error();
3520 if node.kind() == "preproc_arg" {
3521 let macro_value_kind = node.parent().and_then(|parent| {
3526 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3527 });
3528 if matches!(
3529 macro_value_kind,
3530 Some("preproc_def" | "preproc_function_def")
3531 ) {
3532 let name = node_text(node, source);
3533 if !name.is_empty()
3534 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3535 && !push_recovered_c_range(
3536 &mut ranges,
3537 &mut seen,
3538 node.start_byte(),
3539 node.end_byte(),
3540 node,
3541 limit,
3542 )
3543 {
3544 return RecoveredCReferenceRanges::LimitExceeded;
3545 }
3546 }
3547 if macro_value_kind == Some("preproc_def") {
3548 for reference in object_macro_replacement_type_references(node, source) {
3549 for range in reference.component_ranges {
3550 let visible = self
3551 .visible_identifier_candidates(file, &source[range.clone()])
3552 .any(|candidate| {
3553 candidate.is_class()
3554 || candidate.is_module()
3555 || is_type_alias(candidate)
3556 });
3557 if visible
3558 && !push_recovered_c_range(
3559 &mut ranges,
3560 &mut seen,
3561 range.start,
3562 range.end,
3563 node,
3564 limit,
3565 )
3566 {
3567 return RecoveredCReferenceRanges::LimitExceeded;
3568 }
3569 }
3570 }
3571 }
3572 }
3573 if inside_error
3574 && recovered_c_reference_node(self, file, node, source)
3575 && !push_recovered_c_range(
3576 &mut ranges,
3577 &mut seen,
3578 node.start_byte(),
3579 node.end_byte(),
3580 node,
3581 limit,
3582 )
3583 {
3584 return RecoveredCReferenceRanges::LimitExceeded;
3585 }
3586 let mut cursor = node.walk();
3587 for child in node.named_children(&mut cursor) {
3588 stack.push((child, inside_error));
3589 }
3590 }
3591 ranges.sort_unstable();
3592 RecoveredCReferenceRanges::Complete(ranges)
3593 }
3594
3595 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3601 self.visible_identifier_candidates(file, target.identifier())
3602 .filter(|candidate| candidate.is_macro())
3603 .any(|candidate| {
3604 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3605 })
3606 }
3607
3608 pub fn object_macro_replacement_at(
3609 &self,
3610 file: &ProjectFile,
3611 name: &str,
3612 before_byte: usize,
3613 ) -> Option<String> {
3614 let environment = self.macro_environment(file, before_byte);
3615 let binding = environment.binding(name)?;
3616 if !binding.exact {
3617 return None;
3618 }
3619 match &binding.definition {
3620 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3621 MacroDefinition::Function { .. }
3622 | MacroDefinition::VariadicFunction { .. }
3623 | MacroDefinition::Unsupported => None,
3624 }
3625 }
3626
3627 fn apply_macro_events(
3628 &self,
3629 file: &ProjectFile,
3630 before_byte: Option<usize>,
3631 environment: &mut MacroEnvironment,
3632 include_stack: &mut HashSet<ProjectFile>,
3633 ) {
3634 if !include_stack.insert(file.clone()) {
3635 return;
3636 }
3637 if self.cpp.prepared_syntax(self.token, file).is_none() {
3638 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3639 include_stack.remove(file);
3640 return;
3641 }
3642 match self.macro_include_protection(file) {
3643 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3644 Some(binding) if binding.is_exact() => {
3645 include_stack.remove(file);
3646 return;
3647 }
3648 Some(_) | None if environment.unknown_names => {
3649 let mut ambiguous_seen = HashSet::default();
3650 self.mark_macro_events_ambiguous(
3651 file,
3652 environment,
3653 &mut ambiguous_seen,
3654 file,
3655 before_byte.unwrap_or_default(),
3656 );
3657 include_stack.remove(file);
3658 return;
3659 }
3660 Some(_) => {
3661 let mut ambiguous_seen = HashSet::default();
3662 self.mark_macro_events_ambiguous(
3663 file,
3664 environment,
3665 &mut ambiguous_seen,
3666 file,
3667 before_byte.unwrap_or_default(),
3668 );
3669 include_stack.remove(file);
3670 return;
3671 }
3672 None => {}
3673 },
3674 MacroIncludeProtection::PragmaOnce => {
3675 if !environment.applied_pragma_once_files.insert(file.clone()) {
3676 include_stack.remove(file);
3677 return;
3678 }
3679 if environment.maybe_applied_pragma_once_files.remove(file) {
3680 let mut ambiguous_seen = HashSet::default();
3685 environment.applied_pragma_once_files.remove(file);
3686 self.mark_macro_events_ambiguous(
3687 file,
3688 environment,
3689 &mut ambiguous_seen,
3690 file,
3691 before_byte.unwrap_or_default(),
3692 );
3693 environment.maybe_applied_pragma_once_files.remove(file);
3694 environment.applied_pragma_once_files.insert(file.clone());
3695 include_stack.remove(file);
3696 return;
3697 }
3698 }
3699 MacroIncludeProtection::None => {}
3700 }
3701 let cell = self.macro_event_cell(file);
3702 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3703 for event in events {
3704 if before_byte.is_some_and(|limit| event.byte() >= limit) {
3705 break;
3706 }
3707 self.apply_macro_event(file, event, environment, include_stack);
3708 }
3709 include_stack.remove(file);
3710 }
3711
3712 fn apply_macro_event(
3713 &self,
3714 file: &ProjectFile,
3715 event: &MacroEvent,
3716 environment: &mut MacroEnvironment,
3717 include_stack: &mut HashSet<ProjectFile>,
3718 ) {
3719 #[cfg(any(test, feature = "test-support"))]
3720 self.macro_event_application_count
3721 .fetch_add(1, Ordering::Relaxed);
3722 match event {
3723 MacroEvent::Define {
3724 name,
3725 binding,
3726 conditionals,
3727 byte,
3728 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3729 Some(true) => environment.insert(name.clone(), binding.clone()),
3730 Some(false) => {}
3731 None => Self::merge_conditional_macro_definition(
3732 environment,
3733 name,
3734 binding,
3735 file,
3736 *byte,
3737 ),
3738 },
3739 MacroEvent::Undef {
3740 name,
3741 conditionals,
3742 byte,
3743 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3744 Some(true) => environment.remove(name),
3745 Some(false) => {}
3746 None => {
3747 if environment.binding(name).is_some() {
3748 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3749 }
3750 }
3751 },
3752 MacroEvent::Include {
3753 targets,
3754 conditionals,
3755 byte,
3756 } => {
3757 let condition =
3758 self.macro_event_condition_value(file, *byte, environment, conditionals);
3759 if condition == Some(false) {
3760 return;
3761 }
3762 if targets.is_empty() {
3763 environment.mark_unknown_names(file, *byte);
3764 return;
3765 }
3766 if condition.is_none() || targets.len() > 1 {
3767 let mut ambiguous_seen = HashSet::default();
3768 for target in targets {
3769 self.mark_macro_events_ambiguous(
3770 target,
3771 environment,
3772 &mut ambiguous_seen,
3773 file,
3774 *byte,
3775 );
3776 }
3777 } else if let Some(target) = targets.first() {
3778 self.apply_macro_events(target, None, environment, include_stack);
3779 }
3780 }
3781 MacroEvent::Invalidate { byte } => {
3782 for binding in environment.bindings.values_mut() {
3783 *binding = MacroBinding::uncertain_from(binding, file, *byte);
3784 }
3785 }
3786 }
3787 }
3788
3789 fn macro_event_condition_value(
3800 &self,
3801 file: &ProjectFile,
3802 event_byte: usize,
3803 environment: &MacroEnvironment,
3804 conditionals: &OwningPreprocessorConditionals,
3805 ) -> Option<bool> {
3806 if conditionals.is_empty() {
3807 return Some(true);
3808 }
3809 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3810 let source = prepared.source();
3811 let root = prepared.tree().root_node();
3812 let descendant = root.descendant_for_byte_range(
3813 event_byte,
3814 event_byte.saturating_add(1).min(source.len()),
3815 )?;
3816 let mut unknown = false;
3817 let mut current = descendant.parent();
3818 while let Some(conditional) = current {
3819 if matches!(
3820 conditional.kind(),
3821 "preproc_if" | "preproc_ifdef" | "preproc_elif"
3822 ) && conditionals.contains(&conditional.start_byte())
3823 {
3824 let mut value = match conditional.kind() {
3825 "preproc_ifdef" => {
3826 let name = conditional.child_by_field_name("name")?;
3827 let defined =
3828 self.macro_name_defined_value(environment, node_text(name, source));
3829 match conditional.child(0)?.kind() {
3830 "#ifdef" => defined,
3831 "#ifndef" => defined.map(|defined| !defined),
3832 _ => None,
3833 }
3834 }
3835 "preproc_if" | "preproc_elif" => conditional
3836 .child_by_field_name("condition")
3837 .and_then(|condition| {
3838 self.preprocessor_integer_value(
3839 condition,
3840 source,
3841 environment,
3842 &mut Vec::new(),
3843 0,
3844 )
3845 })
3846 .map(|value| value != 0),
3847 _ => unreachable!(),
3848 };
3849 if conditional
3850 .child_by_field_name("alternative")
3851 .is_some_and(|alternative| {
3852 alternative.start_byte() <= descendant.start_byte()
3853 && descendant.end_byte() <= alternative.end_byte()
3854 })
3855 {
3856 value = value.map(|value| !value);
3857 }
3858 match value {
3859 Some(true) => {}
3860 Some(false) => return Some(false),
3861 None => unknown = true,
3862 }
3863 }
3864 current = conditional.parent();
3865 }
3866 (!unknown).then_some(true)
3867 }
3868
3869 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3870 if environment.known_undefined_names.contains(name) {
3871 return Some(false);
3872 }
3873 if let Some(binding) = environment.binding(name) {
3874 return binding.is_exact().then_some(true);
3875 }
3876 environment
3877 .build_proven_defines
3878 .contains(name)
3879 .then_some(true)
3880 }
3881
3882 fn preprocessor_integer_value(
3883 &self,
3884 expression: Node<'_>,
3885 source: &str,
3886 environment: &MacroEnvironment,
3887 expansion_stack: &mut Vec<(ProjectFile, usize)>,
3888 depth: usize,
3889 ) -> Option<i128> {
3890 if depth >= 64 {
3893 return None;
3894 }
3895 match expression.kind() {
3896 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3897 "identifier" | "type_identifier" => {
3898 let binding = environment.binding(node_text(expression, source))?;
3899 if !binding.is_exact() {
3900 return None;
3901 }
3902 let MacroDefinition::Object { replacement } = &binding.definition else {
3903 return None;
3904 };
3905 let identity = (binding.source.clone(), binding.declaration_byte);
3906 if expansion_stack.contains(&identity) {
3907 return None;
3908 }
3909 expansion_stack.push(identity);
3910 let parsed = self.parsed_macro_replacement(binding, replacement);
3911 let value = match parsed.as_ref() {
3912 ParsedMacroReplacement::Parsed {
3913 source: replacement_source,
3914 tree,
3915 } => first_descendant_of_kind(tree.root_node(), "call_expression")
3916 .and_then(|call| call.child_by_field_name("arguments"))
3917 .and_then(|arguments| argument_children(arguments).next())
3918 .and_then(|argument| {
3919 self.preprocessor_integer_value(
3920 argument,
3921 replacement_source,
3922 environment,
3923 expansion_stack,
3924 depth + 1,
3925 )
3926 }),
3927 ParsedMacroReplacement::Unsupported => None,
3928 };
3929 expansion_stack.pop();
3930 value
3931 }
3932 "preproc_defined" => {
3933 let mut cursor = expression.walk();
3934 let name = expression
3935 .named_children(&mut cursor)
3936 .find(|child| child.kind() == "identifier")?;
3937 self.macro_name_defined_value(environment, node_text(name, source))
3938 .map(i128::from)
3939 }
3940 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3941 self.preprocessor_integer_value(
3942 child,
3943 source,
3944 environment,
3945 expansion_stack,
3946 depth + 1,
3947 )
3948 }),
3949 "unary_expression" => {
3950 let operator = expression.child_by_field_name("operator")?.kind();
3951 let argument = expression.child_by_field_name("argument")?;
3952 let value = self.preprocessor_integer_value(
3953 argument,
3954 source,
3955 environment,
3956 expansion_stack,
3957 depth + 1,
3958 )?;
3959 match operator {
3960 "+" => Some(value),
3961 "-" => value.checked_neg(),
3962 "!" => Some(i128::from(value == 0)),
3963 "~" => Some(!value),
3964 _ => None,
3965 }
3966 }
3967 "binary_expression" => {
3968 let left = self.preprocessor_integer_value(
3969 expression.child_by_field_name("left")?,
3970 source,
3971 environment,
3972 expansion_stack,
3973 depth + 1,
3974 )?;
3975 let right = self.preprocessor_integer_value(
3976 expression.child_by_field_name("right")?,
3977 source,
3978 environment,
3979 expansion_stack,
3980 depth + 1,
3981 )?;
3982 match expression.child_by_field_name("operator")?.kind() {
3983 "+" => left.checked_add(right),
3984 "-" => left.checked_sub(right),
3985 "*" => left.checked_mul(right),
3986 "/" => left.checked_div(right),
3987 "%" => left.checked_rem(right),
3988 "<<" => u32::try_from(right)
3989 .ok()
3990 .and_then(|shift| left.checked_shl(shift)),
3991 ">>" => u32::try_from(right)
3992 .ok()
3993 .and_then(|shift| left.checked_shr(shift)),
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(i128::from(left != right)),
4000 "&" => Some(left & right),
4001 "|" => Some(left | right),
4002 "^" => Some(left ^ right),
4003 "&&" => Some(i128::from(left != 0 && right != 0)),
4004 "||" => Some(i128::from(left != 0 || right != 0)),
4005 _ => None,
4006 }
4007 }
4008 _ => None,
4009 }
4010 }
4011
4012 fn mark_macro_events_ambiguous(
4013 &self,
4014 file: &ProjectFile,
4015 environment: &mut MacroEnvironment,
4016 include_stack: &mut HashSet<ProjectFile>,
4017 conditional_file: &ProjectFile,
4018 conditional_byte: usize,
4019 ) {
4020 if !include_stack.insert(file.clone()) {
4021 return;
4022 }
4023 if self.cpp.prepared_syntax(self.token, file).is_none() {
4024 environment.mark_unknown_names(conditional_file, conditional_byte);
4025 return;
4026 }
4027 match self.macro_include_protection(file) {
4028 MacroIncludeProtection::MacroGuard(guard) => {
4029 if environment
4030 .binding(&guard)
4031 .is_some_and(MacroBinding::is_exact)
4032 {
4033 return;
4034 }
4035 }
4036 MacroIncludeProtection::PragmaOnce => {
4037 if environment.applied_pragma_once_files.contains(file) {
4038 return;
4039 }
4040 environment
4041 .maybe_applied_pragma_once_files
4042 .insert(file.clone());
4043 }
4044 MacroIncludeProtection::None => {}
4045 }
4046 let cell = self.macro_event_cell(file);
4047 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4048 for event in events {
4049 #[cfg(any(test, feature = "test-support"))]
4050 self.macro_event_application_count
4051 .fetch_add(1, Ordering::Relaxed);
4052 match event {
4053 MacroEvent::Define { name, binding, .. } => {
4054 Self::merge_conditional_macro_definition(
4055 environment,
4056 name,
4057 binding,
4058 conditional_file,
4059 conditional_byte,
4060 );
4061 }
4062 MacroEvent::Undef { name, .. } => {
4063 if environment.binding(name).is_some() {
4064 environment.insert(
4065 name.clone(),
4066 MacroBinding::ambiguous(conditional_file, conditional_byte),
4067 );
4068 } else {
4069 environment.remove_known_undefined(name);
4070 }
4071 }
4072 MacroEvent::Include { targets, .. } => {
4073 if targets.is_empty() {
4074 environment.mark_unknown_names(conditional_file, conditional_byte);
4075 continue;
4076 }
4077 for target in targets {
4078 self.mark_macro_events_ambiguous(
4079 target,
4080 environment,
4081 include_stack,
4082 conditional_file,
4083 conditional_byte,
4084 );
4085 }
4086 }
4087 MacroEvent::Invalidate { .. } => {
4088 for binding in environment.bindings.values_mut() {
4089 *binding = MacroBinding::uncertain_from(
4090 binding,
4091 conditional_file,
4092 conditional_byte,
4093 );
4094 }
4095 }
4096 }
4097 }
4098 }
4099
4100 fn merge_conditional_macro_definition(
4101 environment: &mut MacroEnvironment,
4102 name: &str,
4103 possible_binding: &MacroBinding,
4104 conditional_file: &ProjectFile,
4105 conditional_byte: usize,
4106 ) {
4107 if environment.binding(name).is_some_and(|current| {
4112 current.definition != MacroDefinition::Unsupported
4113 && current.definition == possible_binding.definition
4114 }) {
4115 return;
4116 }
4117 environment.insert(
4118 name.to_string(),
4119 MacroBinding::ambiguous(conditional_file, conditional_byte),
4120 );
4121 }
4122
4123 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4124 let cell = self
4125 .macro_include_protection_cells
4126 .lock()
4127 .expect("C++ include protection cache poisoned")
4128 .entry(file.clone())
4129 .or_default()
4130 .clone();
4131 cell.get_or_init(|| {
4132 self.cpp.prepared_syntax(self.token, file).map_or(
4133 MacroIncludeProtection::None,
4134 |prepared| {
4135 top_level_macro_include_protection(
4136 prepared.tree().root_node(),
4137 prepared.source(),
4138 )
4139 },
4140 )
4141 })
4142 .clone()
4143 }
4144
4145 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4146 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4147 return Vec::new();
4148 };
4149 let source = prepared.source();
4150 let mut events = Vec::new();
4151 let root = prepared.tree().root_node();
4152 let mut stack = vec![root];
4153 while let Some(node) = stack.pop() {
4154 match node.kind() {
4155 "preproc_def" | "preproc_function_def" => {
4156 let Some(name) = node.child_by_field_name("name") else {
4157 continue;
4158 };
4159 let name = node_text(name, source).to_string();
4160 events.push(MacroEvent::Define {
4161 name,
4162 binding: MacroBinding {
4163 source: file.clone(),
4164 declaration_byte: node.start_byte(),
4165 definition: Self::decode_macro_definition(node, source),
4166 exact: true,
4167 },
4168 byte: node.start_byte(),
4169 conditionals: owning_preprocessor_conditionals(root, node, source),
4170 });
4171 continue;
4172 }
4173 "preproc_include" => {
4174 let Some(path) = node.child_by_field_name("path") else {
4175 events.push(MacroEvent::Include {
4176 targets: Vec::new(),
4177 byte: node.start_byte(),
4178 conditionals: owning_preprocessor_conditionals(root, node, source),
4179 });
4180 continue;
4181 };
4182 let include = structured_include_path(path, source);
4183 let targets = include.map_or_else(Vec::new, |include| {
4184 resolve_include_targets_with_index(
4185 file,
4186 include,
4187 self.cpp.include_target_index(),
4188 )
4189 });
4190 if targets.is_empty()
4201 && include.is_some_and(|include| {
4202 !self.cpp.include_target_index().names_indexed_file(include)
4203 })
4204 {
4205 continue;
4206 }
4207 events.push(MacroEvent::Include {
4208 targets,
4209 byte: node.start_byte(),
4210 conditionals: owning_preprocessor_conditionals(root, node, source),
4211 });
4212 continue;
4213 }
4214 "preproc_call" => {
4215 let Some(directive) = node.child_by_field_name("directive") else {
4216 continue;
4217 };
4218 if node_text(directive, source) != "#undef" {
4219 continue;
4220 }
4221 let name = node
4222 .child_by_field_name("argument")
4223 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
4224 if let Some(name) = name {
4225 events.push(MacroEvent::Undef {
4226 name,
4227 byte: node.start_byte(),
4228 conditionals: owning_preprocessor_conditionals(root, node, source),
4229 });
4230 } else {
4231 events.push(MacroEvent::Invalidate {
4232 byte: node.start_byte(),
4233 });
4234 }
4235 continue;
4236 }
4237 _ => {}
4238 }
4239 push_named_children_reversed(node, &mut stack);
4240 }
4241 events.sort_by_key(MacroEvent::byte);
4242 events
4243 }
4244
4245 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4246 self.ordinary_type_import_cells
4247 .lock()
4248 .expect("C++ ordinary type import cache poisoned")
4249 .entry(file.clone())
4250 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4251 .clone()
4252 }
4253
4254 pub fn project_using_index(
4255 &self,
4256 build: impl FnOnce() -> ProjectUsingIndex,
4257 ) -> &ProjectUsingIndex {
4258 self.project_using_index.get_or_init(build)
4259 }
4260
4261 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4262 let mut files = self
4263 .visible_source_files_by_root
4264 .values()
4265 .flatten()
4266 .cloned()
4267 .collect::<HashSet<_>>()
4268 .into_iter()
4269 .collect::<Vec<_>>();
4270 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4271 files
4272 }
4273
4274 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4275 self.visible_source_files_by_root
4276 .get(root)
4277 .is_some_and(|files| files.contains(source))
4278 }
4279
4280 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4281 let cached = self
4282 .visible_parser_alias_name_sets
4283 .read()
4284 .expect("visible parser alias-name cache poisoned")
4285 .get(file)
4286 .cloned();
4287 let cell = if let Some(cached) = cached {
4288 cached
4289 } else {
4290 let mut cells = self
4291 .visible_parser_alias_name_sets
4292 .write()
4293 .expect("visible parser alias-name cache poisoned");
4294 Arc::clone(
4295 cells
4296 .entry(file.clone())
4297 .or_insert_with(|| Arc::new(OnceLock::new())),
4298 )
4299 };
4300 cell.get_or_init(|| {
4301 #[cfg(any(test, feature = "test-support"))]
4302 self.visible_parser_alias_name_set_build_count
4303 .fetch_add(1, Ordering::Relaxed);
4304 let mut names = HashSet::default();
4305 let visible_files = self
4306 .visible_source_files_by_root
4307 .get(file)
4308 .cloned()
4309 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4310 for visible_file in visible_files {
4311 let aliases = {
4312 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4313 Arc::clone(
4314 cells
4315 .entry(visible_file.clone())
4316 .or_insert_with(|| Arc::new(OnceLock::new())),
4317 )
4318 };
4319 for alias in aliases
4320 .get_or_init(|| {
4321 self.parser_alias_source_parses
4322 .fetch_add(1, Ordering::Relaxed);
4323 #[cfg(any(test, feature = "test-support"))]
4324 {
4325 *self
4326 .alias_source_parse_counts
4327 .lock()
4328 .expect("alias source parse count lock")
4329 .entry(visible_file.clone())
4330 .or_default() += 1;
4331 }
4332 aliases_from_prepared_source(self.cpp, self.token, &visible_file)
4333 .into_boxed_slice()
4334 })
4335 .iter()
4336 {
4337 names.insert(alias.name.clone());
4338 }
4339 }
4340 names
4341 })
4342 .contains(name)
4343 }
4344
4345 pub fn parser_alias_name_may_resolve_to_target(
4346 &self,
4347 file: &ProjectFile,
4348 alias_name: &str,
4349 target: &CodeUnit,
4350 ) -> bool {
4351 let started = std::time::Instant::now();
4352 self.parser_alias_fallback_calls
4353 .fetch_add(1, Ordering::Relaxed);
4354 let key = (
4355 file.clone(),
4356 alias_name.to_string(),
4357 logical_symbol_key(target),
4358 );
4359 let cached = self
4360 .parser_alias_target_matches
4361 .read()
4362 .expect("parser alias target-match cache poisoned")
4363 .get(&key)
4364 .cloned();
4365 let cell = if let Some(cached) = cached {
4366 cached
4367 } else {
4368 let mut cells = self
4369 .parser_alias_target_matches
4370 .write()
4371 .expect("parser alias target-match cache poisoned");
4372 Arc::clone(
4373 cells
4374 .entry(key)
4375 .or_insert_with(|| Arc::new(OnceLock::new())),
4376 )
4377 };
4378 let matched = *cell.get_or_init(|| match self.visible_source_files_by_root.get(file) {
4379 None => {
4380 self.parser_alias_fallback_files
4381 .fetch_add(1, Ordering::Relaxed);
4382 self.file_alias_matches(self.cpp, file, alias_name, target)
4383 }
4384 Some(visible_files) => visible_files.iter().any(|visible_file| {
4385 self.parser_alias_fallback_files
4386 .fetch_add(1, Ordering::Relaxed);
4387 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4388 }),
4389 });
4390 self.parser_alias_fallback_elapsed_micros.fetch_add(
4391 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4392 Ordering::Relaxed,
4393 );
4394 matched
4395 }
4396
4397 fn file_alias_matches(
4398 &self,
4399 cpp: &dyn CppSource,
4400 file: &ProjectFile,
4401 alias_name: &str,
4402 target: &CodeUnit,
4403 ) -> bool {
4404 let cell = {
4405 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4406 Arc::clone(
4407 cells
4408 .entry(file.clone())
4409 .or_insert_with(|| Arc::new(OnceLock::new())),
4410 )
4411 };
4412 cell.get_or_init(|| {
4413 self.parser_alias_source_parses
4414 .fetch_add(1, Ordering::Relaxed);
4415 #[cfg(any(test, feature = "test-support"))]
4416 {
4417 *self
4418 .alias_source_parse_counts
4419 .lock()
4420 .expect("alias source parse count lock")
4421 .entry(file.clone())
4422 .or_default() += 1;
4423 }
4424 aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
4425 })
4426 .iter()
4427 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
4428 }
4429
4430 fn callable_arities_for_target(
4431 &self,
4432 analyzer: &CppGraphSource<'_>,
4433 cpp: &dyn CppSource,
4434 file: &ProjectFile,
4435 prepared: &PreparedSyntaxTree,
4436 spec: &TargetSpec,
4437 ) -> Vec<ActivatedCallableArity> {
4438 let Some(signature) = spec.target.signature() else {
4439 return Vec::new();
4440 };
4441 let Some(candidates) = self
4442 .visible_by_identifier
4443 .get(file)
4444 .and_then(|by_name| by_name.get(&spec.member_name))
4445 else {
4446 return Vec::new();
4447 };
4448 let differing_candidates = candidates
4449 .iter()
4450 .filter(|candidate| {
4451 candidate.is_function()
4452 && candidate.fq_name() == spec.target.fq_name()
4453 && candidate.signature() == Some(signature)
4454 })
4455 .filter_map(|candidate| {
4456 analyzer
4457 .signature_metadata(candidate)
4458 .into_iter()
4459 .find_map(|metadata| metadata.callable_arity())
4460 .filter(|arity| Some(*arity) != spec.callable_arity)
4461 .map(|arity| (candidate, arity))
4462 })
4463 .collect::<Vec<_>>();
4464 if differing_candidates.is_empty() {
4465 return Vec::new();
4466 }
4467 let mut arities = Vec::with_capacity(differing_candidates.len());
4468 let reference = CallableReferenceContext {
4471 file,
4472 position: None,
4473 };
4474 for (candidate, candidate_arity) in differing_candidates {
4475 let declaration_activation = if candidate.source() == file {
4476 callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
4477 } else {
4478 cpp.prepared_syntax(self.token, candidate.source())
4479 .and_then(|syntax| {
4480 callable_declaration_activation_in_file(
4481 analyzer,
4482 syntax.as_ref(),
4483 candidate,
4484 &reference,
4485 )
4486 })
4487 };
4488 let Some(declaration_activation) = declaration_activation else {
4489 continue;
4490 };
4491 let activation_byte = if candidate.source() == file {
4492 Some(declaration_activation)
4493 } else {
4494 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4495 };
4496 if let Some(activation_byte) = activation_byte {
4497 arities.push(ActivatedCallableArity {
4498 activation_byte,
4499 arity: candidate_arity,
4500 });
4501 }
4502 }
4503 arities
4504 }
4505
4506 fn callable_parameter_macro_arity(
4507 &self,
4508 target: &CodeUnit,
4509 signature: Option<&str>,
4510 ) -> Option<CallableArity> {
4511 let parameter_types = cpp_signature_param_types(signature?)?;
4512 let [macro_name] = parameter_types.as_slice() else {
4513 return None;
4514 };
4515 if macro_name.is_empty()
4516 || !macro_name
4517 .chars()
4518 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4519 {
4520 return None;
4521 }
4522 let cache_key = (target.source().clone(), macro_name.clone());
4523 if let Some(cached) = self
4524 .callable_parameter_macro_arities
4525 .lock()
4526 .expect("C++ callable parameter-macro arity cache poisoned")
4527 .get(&cache_key)
4528 .copied()
4529 {
4530 return cached;
4531 }
4532 let mut visible_files = HashSet::default();
4533 collect_include_closure(
4534 &self.cpp_source(),
4535 self.cpp.include_target_index(),
4536 target.source(),
4537 &mut visible_files,
4538 None,
4539 );
4540 let mut arities = Vec::new();
4541 for visible_file in visible_files {
4542 let cell = self.macro_event_cell(&visible_file);
4543 for event in
4544 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4545 {
4546 let MacroEvent::Define { name, binding, .. } = event else {
4547 continue;
4548 };
4549 if name != macro_name {
4550 continue;
4551 }
4552 let MacroDefinition::Object { replacement } = &binding.definition else {
4553 continue;
4554 };
4555 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4556 continue;
4557 };
4558 if !arities.contains(&arity) {
4559 arities.push(arity);
4560 }
4561 }
4562 }
4563 let resolved = (|| {
4564 let required = arities
4565 .iter()
4566 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4567 .min()?;
4568 let total = arities.iter().map(|arity| arity.total()).max()?;
4569 let repeated = arities
4570 .iter()
4571 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4572 Some(CallableArity::new(required, total, repeated))
4577 })();
4578 self.callable_parameter_macro_arities
4579 .lock()
4580 .expect("C++ callable parameter-macro arity cache poisoned")
4581 .insert(cache_key, resolved);
4582 resolved
4583 }
4584
4585 pub fn include_activation_for_source(
4586 &self,
4587 cpp: &dyn CppSource,
4588 file: &ProjectFile,
4589 prepared: &PreparedSyntaxTree,
4590 donor_source: &ProjectFile,
4591 ) -> Option<usize> {
4592 let key = (file.clone(), donor_source.clone());
4593 if let Some(cached) = self
4594 .include_activation_cells
4595 .lock()
4596 .expect("C++ include activation cache poisoned")
4597 .get(&key)
4598 .copied()
4599 {
4600 return cached;
4601 }
4602 #[cfg(any(test, feature = "test-support"))]
4603 self.include_activation_build_count
4604 .fetch_add(1, Ordering::Relaxed);
4605 let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
4606 let mut cells = self
4607 .include_activation_cells
4608 .lock()
4609 .expect("C++ include activation cache poisoned");
4610 *cells.entry(key).or_insert(activation)
4611 }
4612
4613 pub fn conditional_include_projections_for_source(
4614 &self,
4615 file: &ProjectFile,
4616 prepared: &PreparedSyntaxTree,
4617 donor_source: &ProjectFile,
4618 ) -> Arc<[ConditionalIncludeProjection]> {
4619 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4620 let cell = self
4621 .conditional_include_projection_cells
4622 .lock()
4623 .expect("C++ conditional include projection cache poisoned")
4624 .entry(file.clone())
4625 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4626 .clone();
4627 let index = cell.get_or_build_pool_independent(|| {
4628 #[cfg(any(test, feature = "test-support"))]
4629 self.conditional_include_projection_index_build_count
4630 .fetch_add(1, Ordering::Relaxed);
4631 find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
4632 #[cfg(any(test, feature = "test-support"))]
4633 self.conditional_include_projection_state_count
4634 .fetch_add(1, Ordering::Relaxed);
4635 })
4636 });
4637 index
4638 .get(donor_source)
4639 .cloned()
4640 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4641 }
4642
4643 #[cfg(any(test, feature = "test-support"))]
4644 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4645 (
4646 self.conditional_include_projection_index_build_count
4647 .load(Ordering::Relaxed),
4648 self.conditional_include_projection_state_count
4649 .load(Ordering::Relaxed),
4650 )
4651 }
4652
4653 #[cfg(any(test, feature = "test-support"))]
4654 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4655 self.conditional_include_target_state_count
4656 .load(Ordering::Relaxed)
4657 }
4658
4659 #[cfg(any(test, feature = "test-support"))]
4660 pub fn include_activation_build_count_for_test(&self) -> usize {
4661 self.include_activation_build_count.load(Ordering::Relaxed)
4662 }
4663
4664 #[cfg(any(test, feature = "test-support"))]
4665 pub fn note_using_donor_activation_for_test(&self) {
4666 self.using_donor_activation_count
4667 .fetch_add(1, Ordering::Relaxed);
4668 }
4669
4670 #[cfg(not(any(test, feature = "test-support")))]
4671 pub fn note_using_donor_activation_for_test(&self) {}
4672
4673 #[cfg(any(test, feature = "test-support"))]
4674 pub fn note_using_namespace_lookup_for_test(&self) {
4675 self.using_namespace_lookup_count
4676 .fetch_add(1, Ordering::Relaxed);
4677 }
4678
4679 #[cfg(not(any(test, feature = "test-support")))]
4680 pub fn note_using_namespace_lookup_for_test(&self) {}
4681
4682 #[cfg(any(test, feature = "test-support"))]
4683 pub fn note_using_name_candidate_inspection_for_test(&self) {
4684 self.using_name_candidate_inspection_count
4685 .fetch_add(1, Ordering::Relaxed);
4686 }
4687
4688 #[cfg(not(any(test, feature = "test-support")))]
4689 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4690
4691 #[cfg(any(test, feature = "test-support"))]
4692 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4693 (
4694 self.using_donor_activation_count.load(Ordering::Relaxed),
4695 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4696 self.callable_reference_spec_build_count
4697 .load(Ordering::Relaxed),
4698 self.using_name_candidate_inspection_count
4699 .load(Ordering::Relaxed),
4700 )
4701 }
4702
4703 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4704 file == target.source()
4705 || self
4706 .visible_by_file
4707 .get(file)
4708 .is_some_and(|visible| visible.contains(target))
4709 }
4710
4711 pub fn declaration_visible_at(
4723 &self,
4724 analyzer: &CppGraphSource<'_>,
4725 file: &ProjectFile,
4726 declaration: &CodeUnit,
4727 reference_byte: usize,
4728 ) -> bool {
4729 let reference_guards = OnceCell::new();
4730 self.visible_identifier_candidates(file, declaration.identifier())
4731 .filter(|candidate| {
4732 self.same_logical_callable(analyzer, candidate, declaration)
4733 || flattened_macro_namespace_declaration_matches(
4734 analyzer,
4735 self.cpp,
4736 file,
4737 candidate,
4738 declaration,
4739 reference_byte,
4740 )
4741 })
4742 .any(|candidate| {
4743 self.physical_declaration_visible_at(
4744 analyzer,
4745 file,
4746 candidate,
4747 reference_byte,
4748 &reference_guards,
4749 )
4750 })
4751 }
4752
4753 pub fn declaration_visible_at_reference(
4760 &self,
4761 analyzer: &CppGraphSource<'_>,
4762 file: &ProjectFile,
4763 declaration: &CodeUnit,
4764 reference: Node<'_>,
4765 ) -> bool {
4766 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4767 return false;
4768 };
4769 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4770 let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4771 if !declaration_guards.iter().any(|(_, required)| {
4772 guards_compatible_at_reference(required, reference_guards.as_ref())
4773 }) {
4774 return false;
4775 }
4776 if declaration.source() == file
4777 && !analyzer.reference_uses_c_semantics(file)
4778 && (declaration.is_field() || declaration.is_callable())
4779 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4780 self.indexed_enclosing_owner_scope(analyzer, file, reference)
4781 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4782 })
4783 {
4784 return true;
4788 }
4789 if declaration.is_field() && declaration.source() == file {
4790 let reference_byte = reference.start_byte();
4791 let reference_function =
4792 real_function_definition_ancestor(reference, prepared.source());
4793 let guards = OnceCell::new();
4794 let field_reference = CallableReferenceContext {
4795 file,
4796 position: Some(CallableReferencePosition {
4797 prepared: prepared.as_ref(),
4798 byte: reference_byte,
4799 guards: &guards,
4800 }),
4801 };
4802 let mut has_local_declaration = false;
4803 let mut local_declaration_visible = false;
4804 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4805 {
4806 let Some(declaration_function) =
4807 real_function_definition_ancestor(declaration, prepared.source())
4808 else {
4809 continue;
4810 };
4811 has_local_declaration = true;
4812 if reference_function.is_some_and(|reference_function| {
4813 reference_function.start_byte() == declaration_function.start_byte()
4814 && reference_function.end_byte() == declaration_function.end_byte()
4815 }) && callable_preprocessor_context_is_visible_for_reference(
4816 declaration,
4817 prepared.source(),
4818 &field_reference,
4819 ) && callable_declaration_activation_byte(declaration) < reference_byte
4820 {
4821 local_declaration_visible = true;
4822 break;
4823 }
4824 }
4825 if has_local_declaration {
4826 return local_declaration_visible;
4827 }
4828 }
4829 let guards = OnceCell::new();
4830 self.physical_declaration_visible_at(
4831 analyzer,
4832 file,
4833 declaration,
4834 reference.start_byte(),
4835 &guards,
4836 )
4837 }
4838
4839 pub fn declaration_visible_for_c_forward_call(
4845 &self,
4846 analyzer: &CppGraphSource<'_>,
4847 file: &ProjectFile,
4848 declaration: &CodeUnit,
4849 reference_byte: usize,
4850 ) -> bool {
4851 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4852 return true;
4853 }
4854 if declaration.source() != file {
4855 return false;
4856 }
4857 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4858 return false;
4859 };
4860 let reference_guards = prepared
4861 .tree()
4862 .root_node()
4863 .descendant_for_byte_range(reference_byte, reference_byte)
4864 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4865 declaration_guard_requirements(analyzer, self.cpp, declaration)
4866 .into_iter()
4867 .any(|(_, required)| {
4868 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4869 })
4870 }
4871
4872 pub fn callable_arity_at_reference(
4873 &self,
4874 analyzer: &CppGraphSource<'_>,
4875 file: &ProjectFile,
4876 candidate: &CodeUnit,
4877 reference_byte: usize,
4878 ) -> Option<CallableArity> {
4879 let key = (file.clone(), logical_symbol_key(candidate));
4880 let cell = self
4881 .callable_reference_specs
4882 .lock()
4883 .expect("C++ callable reference-spec cache poisoned")
4884 .entry(key)
4885 .or_default()
4886 .clone();
4887 let spec = cell.get_or_init(|| {
4888 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4889 let spec = TargetSpec::from_target(analyzer, candidate)?;
4890 let spec = spec
4891 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4892 .into_owned();
4893 #[cfg(any(test, feature = "test-support"))]
4894 self.callable_reference_spec_build_count
4895 .fetch_add(1, Ordering::Relaxed);
4896 Some(spec)
4897 });
4898 spec.as_ref()?.callable_arity_at(reference_byte)
4899 }
4900
4901 fn physical_declaration_visible_at(
4902 &self,
4903 analyzer: &CppGraphSource<'_>,
4904 file: &ProjectFile,
4905 declaration: &CodeUnit,
4906 reference_byte: usize,
4907 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4908 ) -> bool {
4909 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4910 return false;
4911 };
4912 let reference = CallableReferenceContext {
4913 file,
4914 position: Some(CallableReferencePosition {
4915 prepared: prepared.as_ref(),
4916 byte: reference_byte,
4917 guards: reference_guards,
4918 }),
4919 };
4920 if declaration.source() == file {
4921 return callable_declaration_activation_in_file(
4922 analyzer,
4923 prepared.as_ref(),
4924 declaration,
4925 &reference,
4926 )
4927 .or_else(|| {
4928 self.exhaustive_guard_family_activation(
4929 analyzer,
4930 prepared.as_ref(),
4931 declaration,
4932 &reference,
4933 )
4934 })
4935 .is_some_and(|activation| activation < reference_byte);
4936 }
4937 let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
4938 return false;
4939 };
4940 if self
4941 .foreign_callable_declaration_activation(
4942 analyzer,
4943 donor_syntax.as_ref(),
4944 declaration,
4945 &reference,
4946 )
4947 .or_else(|| {
4948 self.exhaustive_guard_family_activation(
4949 analyzer,
4950 donor_syntax.as_ref(),
4951 declaration,
4952 &reference,
4953 )
4954 })
4955 .is_none()
4956 {
4957 return false;
4958 }
4959 declaration_guard_requirements(analyzer, self.cpp, declaration)
4960 .into_iter()
4961 .any(|(_, declaration_guards)| {
4962 self.foreign_declaration_reachable_at_reference(
4963 file,
4964 prepared.as_ref(),
4965 declaration.source(),
4966 &declaration_guards,
4967 reference.guards(),
4968 reference_byte,
4969 )
4970 })
4971 }
4972
4973 fn foreign_callable_declaration_activation(
4988 &self,
4989 analyzer: &CppGraphSource<'_>,
4990 donor_syntax: &PreparedSyntaxTree,
4991 declaration: &CodeUnit,
4992 reference: &CallableReferenceContext<'_>,
4993 ) -> Option<usize> {
4994 let build_decides = !self.compile_context_is_absent(reference.file);
4995 let proven = self.compile_proven_guards(reference.file);
4996 let augmented;
4997 let active = match reference.guards() {
4998 Some(active) if !proven.is_empty() => {
4999 augmented = active.union(&proven).cloned().collect();
5000 Some(&augmented)
5001 }
5002 other => other,
5003 };
5004 nameable_callable_declaration_nodes(analyzer, donor_syntax, declaration)
5005 .into_iter()
5006 .filter(|node| {
5007 let Some(required) = callable_declaration_guard_requirements(
5008 *node,
5009 donor_syntax.source(),
5010 reference,
5011 ) else {
5012 return false;
5013 };
5014 if required.is_empty() {
5015 return true;
5016 }
5017 if build_decides {
5018 guard_requirements_hold_at_reference(&required, active)
5019 } else {
5020 guards_compatible_at_reference(&required, reference.guards())
5021 }
5022 })
5023 .map(callable_declaration_activation_byte)
5024 .min()
5025 }
5026
5027 pub fn external_type_candidate_visible_at(
5028 &self,
5029 file: &ProjectFile,
5030 candidate: &CodeUnit,
5031 reference_byte: usize,
5032 ) -> bool {
5033 if candidate.source() == file {
5034 return true;
5035 }
5036 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5037 return false;
5038 };
5039 self.visible_identifier_candidates(file, candidate.identifier())
5040 .filter(|peer| same_logical_symbol(candidate, peer))
5041 .any(|peer| {
5042 peer.source() == file
5043 || self
5044 .include_activation_for_source(
5045 self.cpp,
5046 file,
5047 prepared.as_ref(),
5048 peer.source(),
5049 )
5050 .is_some_and(|activation| activation <= reference_byte)
5051 })
5052 }
5053
5054 pub fn external_type_declaration_visible_at(
5055 &self,
5056 file: &ProjectFile,
5057 candidate: &CodeUnit,
5058 reference_byte: usize,
5059 ) -> bool {
5060 if candidate.source() == file {
5061 return true;
5062 }
5063 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5064 return false;
5065 };
5066 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5067 .is_some_and(|activation| activation <= reference_byte)
5068 }
5069
5070 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5089 if let Some(cached) = self
5090 .compile_proven_guard_cells
5091 .lock()
5092 .expect("C++ compile-proven guard cache poisoned")
5093 .get(file)
5094 {
5095 return Arc::clone(cached);
5096 }
5097 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5098 Some(names) => names,
5099 None => {
5100 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5101 let seed = translation_units.next().and_then(|translation_unit| {
5102 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5103 });
5104 match seed {
5105 None => HashSet::default(),
5106 Some(mut names) => {
5107 for translation_unit in translation_units {
5108 let Some(reached) = context_fact_names(
5109 self.cpp.compile_contexts_for(&translation_unit),
5110 ) else {
5111 names.clear();
5112 break;
5113 };
5114 names.retain(|name| reached.contains(name));
5115 if names.is_empty() {
5116 break;
5117 }
5118 }
5119 names
5120 }
5121 }
5122 }
5123 };
5124 let proven = Arc::new(
5125 names
5126 .into_iter()
5127 .map(PreprocessorGuard::Defined)
5128 .collect::<HashSet<_>>(),
5129 );
5130 self.compile_proven_guard_cells
5131 .lock()
5132 .expect("C++ compile-proven guard cache poisoned")
5133 .insert(file.clone(), Arc::clone(&proven));
5134 proven
5135 }
5136
5137 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5147 if let Some(cached) = self
5148 .include_path_admission_cells
5149 .lock()
5150 .expect("C++ include-path admission cache poisoned")
5151 .get(file)
5152 .copied()
5153 {
5154 return cached;
5155 }
5156 let admission = if self.compile_context_is_absent(file) {
5157 IncludePathAdmission::Compatible
5158 } else {
5159 IncludePathAdmission::Proven
5160 };
5161 self.include_path_admission_cells
5162 .lock()
5163 .expect("C++ include-path admission cache poisoned")
5164 .insert(file.clone(), admission);
5165 admission
5166 }
5167
5168 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5175 if !self.cpp.compile_contexts_for(file).is_empty() {
5176 return false;
5177 }
5178 let translation_units = self.cpp.reaching_translation_units(file);
5179 translation_units.is_empty()
5180 || translation_units
5181 .iter()
5182 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5183 }
5184
5185 pub fn miss_requires_compile_context(
5197 &self,
5198 file: &ProjectFile,
5199 identifier: &str,
5200 reference: Node<'_>,
5201 ) -> bool {
5202 if !self.compile_context_is_absent(file) {
5203 return false;
5204 }
5205 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5206 return false;
5207 };
5208 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5209 let reference_byte = reference.start_byte();
5210 let mut sources = self
5211 .visible_identifier_candidates(file, identifier)
5212 .map(CodeUnit::source)
5213 .filter(|source| *source != file)
5214 .collect::<Vec<_>>();
5215 sources.sort();
5216 sources.dedup();
5217 sources.into_iter().any(|declaration_source| {
5218 self.conditional_include_projections_for_source(
5219 file,
5220 prepared.as_ref(),
5221 declaration_source,
5222 )
5223 .iter()
5224 .any(|projection| {
5225 projection.activation_byte <= reference_byte
5226 && !guard_requirements_hold_at_reference(
5227 &projection.required_guards,
5228 reference_guards.as_ref(),
5229 )
5230 && guards_compatible_at_reference(
5231 &projection.required_guards,
5232 reference_guards.as_ref(),
5233 )
5234 })
5235 })
5236 }
5237
5238 fn foreign_declaration_reachable_at_reference(
5250 &self,
5251 file: &ProjectFile,
5252 prepared: &PreparedSyntaxTree,
5253 declaration_source: &ProjectFile,
5254 declaration_guards: &HashSet<PreprocessorGuard>,
5255 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5256 reference_byte: usize,
5257 ) -> bool {
5258 let proven = self.compile_proven_guards(file);
5264 let augmented;
5265 let reference_guards = match reference_guards {
5266 Some(active) if !proven.is_empty() => {
5267 augmented = active.union(&proven).cloned().collect();
5268 Some(&augmented)
5269 }
5270 other => other,
5271 };
5272 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5273 eprintln!(
5274 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5275 declaration_source.rel_path().display(),
5276 );
5277 }
5278 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5279 return false;
5280 }
5281 if self
5282 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5283 .is_some_and(|activation| activation <= reference_byte)
5284 {
5285 return true;
5286 }
5287 let projections =
5288 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5289 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5290 eprintln!(
5291 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5292 declaration_source.rel_path().display(),
5293 declaration_guards.len(),
5294 proven.len(),
5295 projections.len(),
5296 );
5297 }
5298 let admission = self.include_path_admission(file);
5299 projections.iter().any(|projection| {
5300 projection.activation_byte <= reference_byte
5301 && admission.admits(
5302 &projection.required_guards,
5303 &projection.partial_guards,
5304 reference_guards,
5305 )
5306 && self.preprocessor_guards_stable_between(
5307 file,
5308 projection.activation_byte,
5309 reference_byte,
5310 &projection.required_guards,
5311 )
5312 })
5313 }
5314
5315 fn foreign_declaration_may_be_reachable_from_raw_guards(
5316 &self,
5317 file: &ProjectFile,
5318 prepared: &PreparedSyntaxTree,
5319 declaration_source: &ProjectFile,
5320 declaration_guards: &HashSet<PreprocessorGuard>,
5321 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5322 reference_byte: usize,
5323 ) -> bool {
5324 let proven = self.compile_proven_guards(file);
5325 let augmented;
5326 let reference_guards = match reference_guards {
5327 Some(active) if !proven.is_empty() => {
5328 augmented = active.union(&proven).cloned().collect();
5329 Some(&augmented)
5330 }
5331 other => other,
5332 };
5333 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5334 return false;
5335 }
5336 if self
5337 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5338 .is_some_and(|activation| activation <= reference_byte)
5339 {
5340 return true;
5341 }
5342 let reachable = find_conditional_include_projection_for_source(
5343 self.cpp,
5344 self.token,
5345 file,
5346 prepared,
5347 declaration_source,
5348 self.include_path_admission(file),
5349 reference_guards,
5350 reference_byte,
5351 &|| {
5352 #[cfg(any(test, feature = "test-support"))]
5353 self.conditional_include_target_state_count
5354 .fetch_add(1, Ordering::Relaxed);
5355 },
5356 );
5357 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5358 eprintln!(
5359 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5360 declaration_source.rel_path().display(),
5361 declaration_guards.len(),
5362 proven.len(),
5363 reference_guards.map_or(0, HashSet::len),
5364 );
5365 }
5366 reachable
5367 }
5368
5369 fn foreign_declaration_reachable_from_compile_proven_guards(
5370 &self,
5371 file: &ProjectFile,
5372 prepared: &PreparedSyntaxTree,
5373 declaration_source: &ProjectFile,
5374 declaration_guards: &HashSet<PreprocessorGuard>,
5375 reference_byte: usize,
5376 ) -> bool {
5377 let proven = self.compile_proven_guards(file);
5378 if proven.is_empty()
5379 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5380 {
5381 return false;
5382 }
5383 let projections =
5384 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5385 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5386 eprintln!(
5387 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5388 declaration_source.rel_path().display(),
5389 declaration_guards.len(),
5390 proven.len(),
5391 projections.len(),
5392 );
5393 }
5394 projections.iter().any(|projection| {
5395 projection.activation_byte <= reference_byte
5396 && guard_requirements_hold_at_reference(
5397 &projection.required_guards,
5398 Some(proven.as_ref()),
5399 )
5400 && self.preprocessor_guards_stable_between(
5405 file,
5406 0,
5407 projection.activation_byte,
5408 &projection.required_guards,
5409 )
5410 })
5411 }
5412
5413 pub fn external_type_candidate_visible_in_context(
5414 &self,
5415 analyzer: &CppGraphSource<'_>,
5416 file: &ProjectFile,
5417 candidate: &CodeUnit,
5418 reference: Node<'_>,
5419 ) -> bool {
5420 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5421 if report_stats {
5422 eprintln!(
5423 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5424 candidate.fq_name(),
5425 candidate.source().rel_path().display(),
5426 file.rel_path().display(),
5427 reference.start_byte(),
5428 );
5429 }
5430 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5431 return false;
5432 };
5433 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5434 let reference_guards = OnceCell::new();
5435 let reference_guards_at_site = || {
5436 reference_guards.get_or_init(|| {
5437 let started = Instant::now();
5438 if report_stats {
5439 eprintln!(
5440 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5441 file.rel_path().display(),
5442 reference.start_byte(),
5443 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5444 );
5445 }
5446 let macro_environment = self.macro_environment(file, reference.start_byte());
5447 let filtered = raw_reference_guards
5448 .clone()
5449 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5450 if report_stats {
5451 eprintln!(
5452 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5453 filtered.is_some(),
5454 started.elapsed().as_millis(),
5455 );
5456 }
5457 filtered
5458 })
5459 };
5460
5461 let peers = self
5462 .visible_identifier_candidates(file, candidate.identifier())
5463 .filter(|peer| same_logical_symbol(candidate, peer))
5464 .collect::<Vec<_>>();
5465 if report_stats {
5466 let peer_sources = peers
5467 .iter()
5468 .map(|peer| peer.source().rel_path().display().to_string())
5469 .collect::<Vec<_>>();
5470 eprintln!(
5471 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5472 candidate.fq_name(),
5473 );
5474 }
5475 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5476 declaration_guard_requirements(analyzer, self.cpp, peer)
5477 .into_iter()
5478 .any(|(declaration_byte, declaration_guards)| {
5479 if peer.source() == file {
5480 let visible = declaration_byte < reference.start_byte()
5481 && declaration_guards.is_empty();
5482 if report_stats {
5483 eprintln!(
5484 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5485 peer.source().rel_path().display(),
5486 declaration_guards.len(),
5487 );
5488 }
5489 return visible;
5490 }
5491 let direct = declaration_guards.is_empty()
5492 && self
5493 .include_activation_for_source(
5494 self.cpp,
5495 file,
5496 prepared.as_ref(),
5497 peer.source(),
5498 )
5499 .is_some_and(|activation| activation <= reference.start_byte());
5500 let compile_proven = !direct
5501 && self.foreign_declaration_reachable_from_compile_proven_guards(
5502 file,
5503 prepared.as_ref(),
5504 peer.source(),
5505 &declaration_guards,
5506 reference.start_byte(),
5507 );
5508 if report_stats {
5509 eprintln!(
5510 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5511 peer.source().rel_path().display(),
5512 declaration_guards.len(),
5513 );
5514 }
5515 direct || compile_proven
5516 })
5517 });
5518 if directly_visible_without_reference_environment {
5519 if report_stats {
5520 eprintln!(
5521 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5522 candidate.fq_name(),
5523 );
5524 }
5525 return true;
5526 }
5527 let directly_visible = peers.iter().any(|peer| {
5528 declaration_guard_requirements(analyzer, self.cpp, peer)
5529 .into_iter()
5530 .any(|(declaration_byte, declaration_guards)| {
5531 if peer.source() == file {
5532 if declaration_byte >= reference.start_byte() {
5533 return false;
5534 }
5535 if !guard_requirements_hold_at_reference(
5536 &declaration_guards,
5537 raw_reference_guards.as_ref(),
5538 ) {
5539 return false;
5540 }
5541 return guard_requirements_hold_at_reference(
5542 &declaration_guards,
5543 reference_guards_at_site().as_ref(),
5544 ) && self.preprocessor_guards_stable_between(
5545 file,
5546 declaration_byte,
5547 reference.start_byte(),
5548 &declaration_guards,
5549 );
5550 }
5551 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5552 file,
5553 prepared.as_ref(),
5554 peer.source(),
5555 &declaration_guards,
5556 raw_reference_guards.as_ref(),
5557 reference.start_byte(),
5558 );
5559 if report_stats {
5560 eprintln!(
5561 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5562 peer.source().rel_path().display(),
5563 declaration_guards.len(),
5564 );
5565 }
5566 if !raw_feasible {
5567 return false;
5568 }
5569 self.foreign_declaration_reachable_at_reference(
5570 file,
5571 prepared.as_ref(),
5572 peer.source(),
5573 &declaration_guards,
5574 reference_guards_at_site().as_ref(),
5575 reference.start_byte(),
5576 )
5577 })
5578 });
5579 if directly_visible {
5580 if report_stats {
5581 eprintln!(
5582 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5583 candidate.fq_name(),
5584 );
5585 }
5586 return true;
5587 }
5588 let complementary = self
5589 .visible_identifier_candidates(file, candidate.identifier())
5590 .filter(|peer| {
5591 peer.kind() == candidate.kind()
5592 && peer.fq_name() == candidate.fq_name()
5593 && peer.source() == candidate.source()
5594 })
5595 .collect::<Vec<_>>();
5596 let complementary_family =
5601 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5602 let raw_candidate_branch_compatible = complementary_family
5603 && raw_reference_guards.as_ref().is_some_and(|active| {
5604 declaration_guard_requirements(analyzer, self.cpp, candidate)
5605 .iter()
5606 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5607 });
5608 if report_stats {
5609 eprintln!(
5610 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5611 candidate.fq_name(),
5612 complementary.len(),
5613 complementary_family,
5614 raw_candidate_branch_compatible,
5615 );
5616 }
5617 let candidate_branch_compatible = raw_candidate_branch_compatible
5618 && reference_guards_at_site().as_ref().is_some_and(|active| {
5619 declaration_guard_requirements(analyzer, self.cpp, candidate)
5620 .iter()
5621 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5622 });
5623 let complementary_visible = candidate_branch_compatible
5624 && if candidate.source() == file {
5625 declaration_guard_requirements(analyzer, self.cpp, candidate)
5626 .iter()
5627 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5628 } else {
5629 self.include_activation_for_source(
5630 self.cpp,
5631 file,
5632 prepared.as_ref(),
5633 candidate.source(),
5634 )
5635 .is_some_and(|activation| activation <= reference.start_byte())
5636 };
5637 if report_stats {
5638 eprintln!(
5639 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5640 if complementary_visible {
5641 "complementary"
5642 } else {
5643 "missing"
5644 },
5645 candidate.fq_name(),
5646 );
5647 }
5648 complementary_visible
5649 }
5650
5651 pub fn is_exhaustive_same_fqn_type_declaration_family(
5652 &self,
5653 analyzer: &CppGraphSource<'_>,
5654 file: &ProjectFile,
5655 candidate: &CodeUnit,
5656 ) -> bool {
5657 let candidates = self
5658 .visible_identifier_candidates(file, candidate.identifier())
5659 .filter(|peer| {
5660 peer.kind() == candidate.kind()
5661 && peer.fq_name() == candidate.fq_name()
5662 && peer.source() == candidate.source()
5663 })
5664 .collect::<Vec<_>>();
5665 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5666 }
5667
5668 pub fn dependent_member_pointer_alias_visible_in_context(
5683 &self,
5684 analyzer: &CppGraphSource<'_>,
5685 file: &ProjectFile,
5686 candidate: &CodeUnit,
5687 owner_components: &[String],
5688 reference: Node<'_>,
5689 ) -> bool {
5690 if !analyzer
5691 .type_alias_provider()
5692 .is_some_and(|provider| provider.is_type_alias(candidate))
5693 {
5694 return false;
5695 }
5696 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5697 return false;
5698 };
5699 if terminal != candidate.identifier()
5700 || canonical_cpp_scope_components(candidate) != owner_components
5701 {
5702 return false;
5703 }
5704 let Some(expected_parent_fq_name) =
5705 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5706 else {
5707 return false;
5708 };
5709 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5710 return false;
5711 };
5712 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5713 || parent_anchor.source() != candidate.source()
5714 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5715 {
5716 return false;
5717 }
5718
5719 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5725 || candidate.source() == file
5726 && !analyzer
5727 .ranges(candidate)
5728 .iter()
5729 .any(|range| range.start_byte < reference.start_byte())
5730 {
5731 return false;
5732 }
5733
5734 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5735 if candidate_guards.is_empty() {
5736 return false;
5737 }
5738 let same_guard_sets =
5739 |left: &[(usize, HashSet<PreprocessorGuard>)],
5740 right: &[(usize, HashSet<PreprocessorGuard>)]| {
5741 left.iter().all(|(_, left_guards)| {
5742 right
5743 .iter()
5744 .any(|(_, right_guards)| left_guards == right_guards)
5745 })
5746 };
5747 let parent_candidates = self
5748 .visible_identifier_candidates(file, parent_anchor.identifier())
5749 .filter(|peer| {
5750 peer.kind() == parent_anchor.kind()
5751 && peer.fq_name() == expected_parent_fq_name.as_str()
5752 && peer.source() == parent_anchor.source()
5753 && canonical_cpp_scope_components(peer) == owner_prefix
5754 })
5755 .filter_map(|peer| {
5756 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5757 (candidate_guards.len() == parent_guards.len()
5758 && same_guard_sets(&candidate_guards, &parent_guards)
5759 && same_guard_sets(&parent_guards, &candidate_guards))
5760 .then(|| (peer.clone(), parent_guards))
5761 })
5762 .collect::<Vec<_>>();
5763 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5764 return false;
5765 };
5766
5767 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5768 return false;
5769 };
5770 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5771 else {
5772 return false;
5773 };
5774 if !candidate_guards.iter().any(|(_, target_guards)| {
5779 guards_compatible_at_reference(target_guards, Some(&reference_guards))
5780 && (candidate.source() != file
5781 || self.preprocessor_guards_stable_between(
5782 file,
5783 0,
5784 reference.start_byte(),
5785 target_guards,
5786 ))
5787 }) {
5788 return false;
5789 }
5790
5791 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5792 }
5793
5794 pub fn external_type_candidate_guard_compatible_in_context(
5804 &self,
5805 analyzer: &CppGraphSource<'_>,
5806 file: &ProjectFile,
5807 candidate: &CodeUnit,
5808 reference: Node<'_>,
5809 ) -> bool {
5810 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5811 return false;
5812 };
5813 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5814
5815 self.visible_identifier_candidates(file, candidate.identifier())
5816 .filter(|peer| same_logical_symbol(candidate, peer))
5817 .any(|peer| {
5818 declaration_guard_requirements(analyzer, self.cpp, peer)
5819 .into_iter()
5820 .any(|(declaration_byte, declaration_guards)| {
5821 if peer.source() == file {
5822 let (start, end) = if declaration_byte <= reference.start_byte() {
5823 (declaration_byte, reference.start_byte())
5824 } else {
5825 (reference.start_byte(), declaration_byte)
5826 };
5827 return guard_requirements_hold_at_reference(
5828 &declaration_guards,
5829 reference_guards.as_ref(),
5830 ) && self.preprocessor_guards_stable_between(
5831 file,
5832 start,
5833 end,
5834 &declaration_guards,
5835 );
5836 }
5837 self.foreign_declaration_reachable_at_reference(
5838 file,
5839 prepared.as_ref(),
5840 peer.source(),
5841 &declaration_guards,
5842 reference_guards.as_ref(),
5843 reference.start_byte(),
5844 )
5845 })
5846 })
5847 }
5848
5849 pub fn same_file_callable_guard_compatible_ignoring_order(
5857 &self,
5858 analyzer: &CppGraphSource<'_>,
5859 file: &ProjectFile,
5860 candidate: &CodeUnit,
5861 reference: Node<'_>,
5862 ) -> bool {
5863 if candidate.source() != file || !candidate.is_callable() {
5864 return false;
5865 }
5866 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5867 return false;
5868 };
5869 let guards = OnceCell::new();
5870 let context = CallableReferenceContext {
5871 file,
5872 position: Some(CallableReferencePosition {
5873 prepared: prepared.as_ref(),
5874 byte: reference.start_byte(),
5875 guards: &guards,
5876 }),
5877 };
5878 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5879 .into_iter()
5880 .any(|declaration| {
5881 callable_preprocessor_context_is_visible_for_reference(
5882 declaration,
5883 prepared.source(),
5884 &context,
5885 )
5886 })
5887 }
5888
5889 pub fn type_candidate_may_be_visible_before_reference(
5890 &self,
5891 analyzer: &CppGraphSource<'_>,
5892 file: &ProjectFile,
5893 candidate: &CodeUnit,
5894 reference_byte: usize,
5895 ) -> bool {
5896 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5897 return false;
5898 };
5899 let root = prepared.tree().root_node();
5900 let end_byte = reference_byte
5901 .saturating_add(1)
5902 .min(prepared.source().len());
5903 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5904 return false;
5905 };
5906 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5907 }
5908
5909 pub fn preprocessor_guards_stable_between(
5910 &self,
5911 file: &ProjectFile,
5912 start_byte: usize,
5913 end_byte: usize,
5914 guards: &HashSet<PreprocessorGuard>,
5915 ) -> bool {
5916 if guards.is_empty() || start_byte >= end_byte {
5917 return true;
5918 }
5919 let cell = self.macro_event_cell(file);
5920 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5921 let mut visited = HashSet::from_iter([file.clone()]);
5922 !events.iter().any(|event| {
5923 event.byte() >= start_byte
5924 && event.byte() < end_byte
5925 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5926 })
5927 }
5928
5929 fn macro_event_may_mutate_guards(
5930 &self,
5931 event: &MacroEvent,
5932 guards: &HashSet<PreprocessorGuard>,
5933 visited: &mut HashSet<ProjectFile>,
5934 ) -> bool {
5935 match event {
5936 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5937 guards.iter().any(|guard| guard.may_depend_on_macro(name))
5938 }
5939 MacroEvent::Include { targets, .. } => {
5940 targets.is_empty()
5941 || targets
5942 .iter()
5943 .any(|target| self.source_may_mutate_guards(target, guards, visited))
5944 }
5945 MacroEvent::Invalidate { .. } => true,
5946 }
5947 }
5948
5949 fn source_may_mutate_guards(
5950 &self,
5951 file: &ProjectFile,
5952 guards: &HashSet<PreprocessorGuard>,
5953 visited: &mut HashSet<ProjectFile>,
5954 ) -> bool {
5955 if !visited.insert(file.clone()) {
5956 return false;
5957 }
5958 let cell = self.macro_event_cell(file);
5959 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5960 events
5961 .iter()
5962 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5963 }
5964
5965 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5966 let normalized = normalize_reference_name(raw_name)?;
5967 self.type_candidates(file, &normalized)
5968 .into_iter()
5969 .next()
5970 .cloned()
5971 }
5972
5973 pub fn unique_visible_parameter_type_fallback(
5982 &self,
5983 analyzer: &CppGraphSource<'_>,
5984 file: &ProjectFile,
5985 node: Node<'_>,
5986 source: &str,
5987 ) -> Option<CodeUnit> {
5988 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5989 return None;
5990 }
5991 let name = node_text(node, source);
5992 let candidates = self
5993 .visible_identifier_candidates(file, name)
5994 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5995 .filter(|candidate| {
5996 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5997 })
5998 .collect::<Vec<_>>();
5999 self.unique_canonical_type_candidate(analyzer, file, &candidates)
6000 }
6001
6002 pub fn resolve_type_node_result(
6003 &self,
6004 file: &ProjectFile,
6005 node: Node<'_>,
6006 source: &str,
6007 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6008 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6009 return Ok(None);
6010 };
6011 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6012 return Ok(Some(primary));
6013 };
6014 self.resolve_template_arguments(file, primary, &arguments)
6015 .map(Some)
6016 }
6017
6018 pub fn resolve_type_node_primary(
6019 &self,
6020 file: &ProjectFile,
6021 node: Node<'_>,
6022 source: &str,
6023 ) -> Option<CodeUnit> {
6024 let components = cpp_type_name_components(node, source)?;
6025 self.resolve_type(file, &components.join("::"))
6026 }
6027
6028 pub fn resolve_template_arguments(
6029 &self,
6030 file: &ProjectFile,
6031 primary: CodeUnit,
6032 arguments: &[CppTemplateExpression],
6033 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6034 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6035 }
6036
6037 fn resolve_template_arguments_inner(
6038 &self,
6039 file: &ProjectFile,
6040 primary: CodeUnit,
6041 arguments: &[CppTemplateExpression],
6042 seen_aliases: &mut HashSet<CodeUnit>,
6043 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6044 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6045 && let Some(alias_target) = &metadata.alias_target
6046 {
6047 if !seen_aliases.insert(primary.clone()) {
6048 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6049 }
6050 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6051 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6052 let target_name = alias_target.components.join("::");
6053 let target_primary = if alias_target.global {
6054 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6055 } else {
6056 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6057 };
6058 let Some(target_primary) = target_primary else {
6059 return Ok(primary);
6063 };
6064 let Some(target_arguments) = &alias_target.arguments else {
6065 return Ok(target_primary);
6066 };
6067 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6068 .ok_or(CppTemplateResolutionError::Substitution)?;
6069 return self.resolve_template_arguments_inner(
6070 file,
6071 target_primary,
6072 &target_arguments,
6073 seen_aliases,
6074 );
6075 }
6076
6077 let primary_fq_name = self
6078 .cpp_template_metadata
6079 .get(&primary)
6080 .map(|metadata| metadata.primary_fq_name.clone())
6081 .unwrap_or_else(|| primary.fq_name());
6082 let has_specialization_metadata = self
6083 .cpp_template_families
6084 .get(&primary_fq_name)
6085 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6086 if !has_specialization_metadata {
6087 return Ok(primary);
6088 }
6089 self.select_template_specialization(file, &primary, arguments)
6090 }
6091
6092 fn select_template_specialization(
6093 &self,
6094 file: &ProjectFile,
6095 resolved: &CodeUnit,
6096 explicit_arguments: &[CppTemplateExpression],
6097 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6098 let primary_fq_name = self
6099 .cpp_template_metadata
6100 .get(resolved)
6101 .map(|metadata| metadata.primary_fq_name.clone())
6102 .unwrap_or_else(|| resolved.fq_name());
6103 let family = self
6104 .cpp_template_families
6105 .get(&primary_fq_name)
6106 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6107 let primary_candidates = family
6108 .iter()
6109 .filter_map(|unit| {
6110 let metadata = self.cpp_template_metadata.get(unit)?;
6111 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6112 })
6113 .collect::<Vec<_>>();
6114 let primary_unit = primary_candidates
6115 .iter()
6116 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6117 .or_else(|| {
6118 primary_candidates
6119 .iter()
6120 .map(|(unit, _)| *unit)
6121 .min_by_key(|unit| {
6122 (
6123 unit.source().to_string(),
6124 unit.signature().unwrap_or_default(),
6125 )
6126 })
6127 })
6128 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6129 let primary_parameters =
6130 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6131 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6132 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6133 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6134
6135 let mut applicable = Vec::new();
6136 for unit in family {
6137 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6138 continue;
6139 };
6140 if metadata.is_primary() || !self.is_visible(file, unit) {
6141 continue;
6142 }
6143 if !cpp_specialization_matches(metadata, &expanded) {
6144 continue;
6145 }
6146 applicable.push((unit, metadata));
6147 }
6148 if applicable.is_empty() {
6149 return Ok(primary_unit.clone());
6150 }
6151
6152 let winners = applicable
6157 .iter()
6158 .filter(|(candidate, candidate_metadata)| {
6159 applicable.iter().all(|(other, other_metadata)| {
6160 same_visible_symbol(candidate, other)
6161 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6162 })
6163 })
6164 .copied()
6165 .collect::<Vec<_>>();
6166 let Some((selected, _)) = winners.first() else {
6167 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6170 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6171 });
6172 };
6173 if winners
6174 .iter()
6175 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6176 {
6177 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6178 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6179 });
6180 }
6181 Ok((*selected).clone())
6182 }
6183
6184 pub fn resolve_type_components_lexically(
6185 &self,
6186 analyzer: &CppGraphSource<'_>,
6187 file: &ProjectFile,
6188 components: &[String],
6189 global: bool,
6190 lexical_scope: &[String],
6191 ) -> LexicalTypeResolution {
6192 self.resolve_type_components_lexically_inner(
6193 analyzer,
6194 file,
6195 components,
6196 global,
6197 lexical_scope,
6198 TypeCandidateResolution::Canonical,
6199 )
6200 }
6201
6202 pub fn resolve_type_components_lexically_for_forward(
6203 &self,
6204 analyzer: &CppGraphSource<'_>,
6205 file: &ProjectFile,
6206 components: &[String],
6207 global: bool,
6208 lexical_scope: &[String],
6209 ) -> LexicalTypeResolution {
6210 self.resolve_type_components_lexically_inner(
6211 analyzer,
6212 file,
6213 components,
6214 global,
6215 lexical_scope,
6216 TypeCandidateResolution::PreserveAlias,
6217 )
6218 }
6219
6220 pub fn resolve_type_components_lexically_for_target(
6221 &self,
6222 analyzer: &CppGraphSource<'_>,
6223 file: &ProjectFile,
6224 components: &[String],
6225 global: bool,
6226 lexical_scope: &[String],
6227 target: &CodeUnit,
6228 ) -> LexicalTypeResolution {
6229 #[cfg(any(test, feature = "test-support"))]
6230 self.target_preserving_type_resolution_count
6231 .fetch_add(1, Ordering::Relaxed);
6232 self.resolve_type_components_lexically_inner(
6233 analyzer,
6234 file,
6235 components,
6236 global,
6237 lexical_scope,
6238 TypeCandidateResolution::PreserveTarget(target),
6239 )
6240 }
6241
6242 pub fn coarse_unqualified_type_reference_may_resolve(
6243 &self,
6244 file: &ProjectFile,
6245 name: &str,
6246 ) -> bool {
6247 if name.is_empty() {
6248 return true;
6249 }
6250 self.visible_identifier_candidates(file, name)
6251 .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
6252 || self.visible_parser_alias_name_is_visible(file, name)
6253 }
6254
6255 #[allow(clippy::too_many_arguments)]
6256 pub fn structured_type_reference_may_resolve_to_target(
6257 &self,
6258 analyzer: &CppGraphSource<'_>,
6259 file: &ProjectFile,
6260 components: &[String],
6261 global: bool,
6262 lexical_scope: &[String],
6263 target: &CodeUnit,
6264 ) -> bool {
6265 if components.is_empty() {
6266 return true;
6267 }
6268 let Some(terminal) = components.last() else {
6269 return true;
6270 };
6271 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6272 .map(|qualified| qualified.join("::"))
6273 .collect::<Vec<_>>();
6274 let target_name = cpp_name_for(target);
6275 if qualified_tiers
6276 .iter()
6277 .any(|qualified| qualified == &target_name)
6278 {
6279 return true;
6280 }
6281
6282 let mut saw_shape_candidate = false;
6283 for candidate in self.visible_identifier_candidates(file, terminal) {
6284 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6285 {
6286 continue;
6287 }
6288 let candidate_name = cpp_name_for(candidate);
6289 let shape_matches = if global || components.len() > 1 {
6290 qualified_tiers
6291 .iter()
6292 .any(|qualified| qualified == &candidate_name)
6293 } else {
6294 true
6295 };
6296 if !shape_matches {
6297 continue;
6298 }
6299 saw_shape_candidate = true;
6300 if same_visible_symbol(candidate, target)
6301 || self.c_tag_declaration_family_matches_target(
6302 analyzer,
6303 file,
6304 std::slice::from_ref(&candidate),
6305 target,
6306 )
6307 || self.compatible_primary_template_redeclarations(candidate, target)
6308 || (declared_type_alias(analyzer, candidate)
6309 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6310 {
6311 return true;
6312 }
6313 }
6314
6315 !saw_shape_candidate
6316 }
6317
6318 fn cached_c_tag_kind(
6325 &self,
6326 analyzer: &CppGraphSource<'_>,
6327 candidate: &CodeUnit,
6328 ) -> Option<CppCTagKind> {
6329 if let Some(kind) = self
6330 .c_tag_kind_cache
6331 .lock()
6332 .expect("C tag kind cache poisoned")
6333 .get(candidate)
6334 {
6335 return *kind;
6336 }
6337 let kind = indexed_c_tag_kind(analyzer, candidate);
6338 self.c_tag_kind_cache
6339 .lock()
6340 .expect("C tag kind cache poisoned")
6341 .insert(candidate.clone(), kind);
6342 kind
6343 }
6344
6345 fn cached_unique_c_tag_complete_definition(
6346 &self,
6347 analyzer: &CppGraphSource<'_>,
6348 target: &CodeUnit,
6349 target_tag: CppCTagKind,
6350 ) -> Option<CodeUnit> {
6351 if let Some(definition) = self
6352 .c_tag_complete_definition_cache
6353 .lock()
6354 .expect("C tag complete-definition cache poisoned")
6355 .get(target)
6356 {
6357 return definition.clone();
6358 }
6359 let complete_definitions = analyzer
6360 .definitions(&target.fq_name())
6361 .filter(|candidate| {
6362 candidate.is_class()
6363 && !declared_type_alias(analyzer, candidate)
6364 && is_c_source_file(candidate.source())
6365 && analyzer.parent_of(candidate).is_none()
6366 && cpp_class_declaration_strength(analyzer, candidate)
6367 == CppClassDeclarationStrength::Full
6368 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6369 })
6370 .collect::<HashSet<_>>();
6371 let definition = (complete_definitions.len() == 1)
6372 .then(|| complete_definitions.into_iter().next())
6373 .flatten()
6374 .filter(|candidate| same_visible_symbol(candidate, target));
6375 self.c_tag_complete_definition_cache
6376 .lock()
6377 .expect("C tag complete-definition cache poisoned")
6378 .insert(target.clone(), definition.clone());
6379 definition
6380 }
6381
6382 pub fn c_tag_declaration_family_matches_target(
6383 &self,
6384 analyzer: &CppGraphSource<'_>,
6385 visible_from: &ProjectFile,
6386 candidates: &[&CodeUnit],
6387 target: &CodeUnit,
6388 ) -> bool {
6389 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6390 let candidate_evidence = candidates
6391 .iter()
6392 .map(|candidate| {
6393 (
6394 candidate.fq_name(),
6395 candidate.source().rel_path().to_path_buf(),
6396 cpp_class_declaration_strength(analyzer, candidate),
6397 indexed_c_tag_kind(analyzer, candidate),
6398 self.is_physically_visible(visible_from, candidate),
6399 )
6400 })
6401 .collect::<Vec<_>>();
6402 eprintln!(
6403 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6404 visible_from.rel_path().display(),
6405 target.fq_name(),
6406 target.source().rel_path().display(),
6407 cpp_class_declaration_strength(analyzer, target),
6408 indexed_c_tag_kind(analyzer, target),
6409 );
6410 }
6411 if candidates.is_empty()
6412 || !target.is_class()
6413 || declared_type_alias(analyzer, target)
6414 || !is_c_source_file(target.source())
6415 || analyzer.parent_of(target).is_some()
6416 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6417 {
6418 return false;
6419 }
6420 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6421 return false;
6422 };
6423 if self
6424 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6425 .is_none()
6426 {
6427 return false;
6428 }
6429 let mut saw_visible_forward = false;
6430 for candidate in candidates.iter().copied() {
6431 if candidate == target {
6432 continue;
6433 }
6434 if !candidate.is_class()
6435 || declared_type_alias(analyzer, candidate)
6436 || candidate.fq_name() != target.fq_name()
6437 || analyzer.parent_of(candidate).is_some()
6438 || cpp_class_declaration_strength(analyzer, candidate)
6439 != CppClassDeclarationStrength::Forward
6440 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6441 || !self.is_physically_visible(visible_from, candidate)
6442 {
6443 return false;
6444 }
6445 saw_visible_forward = true;
6446 }
6447 saw_visible_forward
6448 }
6449
6450 fn unique_c_tag_declaration_family(
6455 &self,
6456 analyzer: &CppGraphSource<'_>,
6457 visible_from: &ProjectFile,
6458 candidates: &[&CodeUnit],
6459 ) -> Option<CodeUnit> {
6460 let first = candidates.first()?;
6461 let target_fq_name = first.fq_name();
6462 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6463 let mut full = None;
6464 let mut saw_forward = false;
6465 for candidate in candidates.iter().copied() {
6466 if !candidate.is_class()
6467 || declared_type_alias(analyzer, candidate)
6468 || candidate.fq_name() != target_fq_name
6469 || analyzer.parent_of(candidate).is_some()
6470 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6471 {
6472 return None;
6473 }
6474 match cpp_class_declaration_strength(analyzer, candidate) {
6475 CppClassDeclarationStrength::Full
6476 if is_c_source_file(candidate.source())
6477 && full.replace(candidate.clone()).is_none() => {}
6478 CppClassDeclarationStrength::Forward
6479 if self.is_physically_visible(visible_from, candidate) =>
6480 {
6481 saw_forward = true
6482 }
6483 _ => return None,
6484 }
6485 }
6486 if saw_forward { full } else { None }
6487 }
6488
6489 pub fn target_preserving_reference_namespace(
6490 &self,
6491 analyzer: &CppGraphSource<'_>,
6492 file: &ProjectFile,
6493 identifier: &str,
6494 target: &CodeUnit,
6495 ) -> Option<Vec<String>> {
6496 let mut namespace = None;
6497 for candidate in self.visible_identifier_candidates(file, identifier) {
6498 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6499 {
6500 continue;
6501 }
6502 if !(same_visible_symbol(candidate, target)
6503 || self.compatible_primary_template_redeclarations(candidate, target)
6504 || declared_type_alias(analyzer, candidate)
6505 && self.structured_alias_primary_preserves_target(
6506 analyzer, file, candidate, target,
6507 ))
6508 {
6509 continue;
6510 }
6511 if namespace
6512 .as_ref()
6513 .is_some_and(|existing| existing != candidate.package_name())
6514 {
6515 return None;
6516 }
6517 namespace = Some(candidate.package_name().to_string());
6518 }
6519 let namespace = namespace?;
6520 Some(
6521 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6522 brokk_bifrost_core::analyzer::Language::Cpp,
6523 &namespace,
6524 ),
6525 )
6526 }
6527
6528 pub fn resolve_imported_type_candidate(
6529 &self,
6530 analyzer: &CppGraphSource<'_>,
6531 file: &ProjectFile,
6532 target: &CodeUnit,
6533 target_components: &[String],
6534 direct_target: Option<&CodeUnit>,
6535 preserve_alias: bool,
6536 ) -> LexicalTypeResolution {
6537 let candidates = [target];
6538 let resolution = if preserve_alias {
6539 TypeCandidateResolution::PreserveAlias
6540 } else {
6541 direct_target.map_or(
6542 TypeCandidateResolution::Canonical,
6543 TypeCandidateResolution::PreserveTarget,
6544 )
6545 };
6546 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6550 Ok(unit) => LexicalTypeResolution::Resolved {
6551 unit,
6552 components: target_components.to_vec(),
6553 candidates: vec![target.clone()],
6554 },
6555 Err(failure) => failure.lexical_resolution(),
6556 }
6557 }
6558
6559 fn resolve_type_components_lexically_inner(
6560 &self,
6561 analyzer: &CppGraphSource<'_>,
6562 file: &ProjectFile,
6563 components: &[String],
6564 global: bool,
6565 lexical_scope: &[String],
6566 resolution: TypeCandidateResolution<'_>,
6567 ) -> LexicalTypeResolution {
6568 if components.is_empty() {
6569 return LexicalTypeResolution::Missing;
6570 }
6571 let mut injected = self.resolve_injected_class_name(
6581 analyzer,
6582 file,
6583 components,
6584 global,
6585 lexical_scope,
6586 resolution,
6587 );
6588 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6589 let prefix_len = qualified.len().saturating_sub(components.len());
6590 if injected
6591 .as_ref()
6592 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6593 {
6594 return injected
6595 .take()
6596 .expect("injected class resolution was just present")
6597 .1;
6598 }
6599 let qualified_name = qualified.join("::");
6600 let candidates = self
6601 .type_candidates(file, &qualified_name)
6602 .into_iter()
6603 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6604 .collect::<Vec<_>>();
6605 if candidates.is_empty() {
6606 if !global && components.len() == 1 {
6607 match self.resolve_inherited_type_for_lexical_scope(
6608 analyzer,
6609 file,
6610 &qualified[..prefix_len],
6611 &components[0],
6612 resolution,
6613 ) {
6614 LexicalTypeResolution::Missing => {}
6615 inherited => return inherited,
6616 }
6617 }
6618 continue;
6619 }
6620 let candidates =
6621 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6622 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6623 Ok(unit) => unit,
6624 Err(failure) => return failure.lexical_resolution(),
6625 };
6626 return LexicalTypeResolution::Resolved {
6627 unit,
6628 components: qualified,
6629 candidates: candidates.into_iter().cloned().collect(),
6630 };
6631 }
6632 LexicalTypeResolution::Missing
6633 }
6634
6635 fn resolve_injected_class_name(
6636 &self,
6637 analyzer: &CppGraphSource<'_>,
6638 file: &ProjectFile,
6639 components: &[String],
6640 global: bool,
6641 lexical_scope: &[String],
6642 resolution: TypeCandidateResolution<'_>,
6643 ) -> Option<(usize, LexicalTypeResolution)> {
6644 if global
6645 || components.len() != 1
6646 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6647 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6648 {
6649 return None;
6650 }
6651 let name = components.first()?;
6652 let mut matches: Vec<&CodeUnit> = Vec::new();
6653 let mut owner_len = 0;
6654 for candidate in self.visible_identifier_candidates(file, name) {
6655 if !candidate.is_class()
6656 || declared_type_alias(analyzer, candidate)
6657 || candidate.identifier() != name
6658 {
6659 continue;
6660 }
6661 let candidate_scope = canonical_cpp_scope_components(candidate);
6662 if candidate_scope.len() > lexical_scope.len()
6663 || !lexical_scope.starts_with(&candidate_scope)
6664 || candidate_scope.last().is_none_or(|last| last != name)
6665 {
6666 continue;
6667 }
6668 if candidate_scope.len() > owner_len {
6669 owner_len = candidate_scope.len();
6670 matches.clear();
6671 }
6672 if candidate_scope.len() == owner_len
6673 && !matches
6674 .iter()
6675 .any(|existing| same_logical_symbol(existing, candidate))
6676 {
6677 matches.push(candidate);
6678 }
6679 }
6680 if matches.is_empty() {
6681 return None;
6682 }
6683 let owner_components = lexical_scope[..owner_len].to_vec();
6690 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6691 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6692 Ok(unit) => LexicalTypeResolution::Resolved {
6693 unit,
6694 components: owner_components,
6695 candidates: matches.into_iter().cloned().collect(),
6696 },
6697 Err(failure) => failure.lexical_resolution(),
6698 };
6699 Some((owner_len, resolution))
6700 }
6701
6702 fn resolve_inherited_type_for_lexical_scope(
6703 &self,
6704 analyzer: &CppGraphSource<'_>,
6705 file: &ProjectFile,
6706 lexical_scope: &[String],
6707 name: &str,
6708 resolution: TypeCandidateResolution<'_>,
6709 ) -> LexicalTypeResolution {
6710 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6711 return LexicalTypeResolution::Missing;
6712 };
6713 let lexical_owner_name = lexical_scope.join("::");
6714 if lexical_owner_name.is_empty() {
6715 return LexicalTypeResolution::Missing;
6716 }
6717 let owner_candidates = self
6718 .type_candidates(file, &lexical_owner_name)
6719 .into_iter()
6720 .filter(|candidate| {
6721 canonical_cpp_name_matches(candidate, &lexical_owner_name)
6722 && !declared_type_alias(analyzer, candidate)
6723 })
6724 .collect::<Vec<_>>();
6725 if owner_candidates.is_empty() {
6726 return LexicalTypeResolution::Missing;
6727 }
6728 let physical_owner_candidates = owner_candidates
6733 .iter()
6734 .copied()
6735 .filter(|candidate| candidate.source() == file)
6736 .collect::<Vec<_>>();
6737 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6738 owner_candidates
6739 } else {
6740 physical_owner_candidates
6741 };
6742 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6743 return LexicalTypeResolution::Ambiguous;
6744 };
6745
6746 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6747 let mut visited_owners = HashSet::default();
6748 while !frontier.is_empty() {
6749 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6750 let mut next_frontier = Vec::new();
6751 for owner in frontier {
6752 if !visited_owners.insert(owner.fq_name()) {
6753 continue;
6754 }
6755 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6756 let candidates = self
6757 .type_candidates(file, &qualified_name)
6758 .into_iter()
6759 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6760 .collect::<Vec<_>>();
6761 if candidates.is_empty() {
6762 for ancestor in hierarchy.get_direct_ancestors(&owner) {
6763 if !next_frontier
6764 .iter()
6765 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6766 {
6767 next_frontier.push(ancestor);
6768 }
6769 }
6770 continue;
6771 }
6772 let candidates =
6773 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6774 let unit =
6775 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6776 Ok(unit) => unit,
6777 Err(failure) => return failure.lexical_resolution(),
6778 };
6779 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6780 }
6781 if let Some((unit, candidates)) = level_matches.first().cloned() {
6782 let Some(first_declaration) = candidates.first() else {
6783 return LexicalTypeResolution::Ambiguous;
6784 };
6785 if !level_matches.iter().all(|(_, declarations)| {
6786 declarations
6787 .iter()
6788 .all(|declaration| same_logical_symbol(first_declaration, declaration))
6789 }) {
6790 return LexicalTypeResolution::Ambiguous;
6791 }
6792 let mut components = lexical_scope.to_vec();
6793 components.push(name.to_string());
6794 return LexicalTypeResolution::Resolved {
6795 unit,
6796 components,
6797 candidates,
6798 };
6799 }
6800 frontier = next_frontier;
6801 }
6802 LexicalTypeResolution::Missing
6803 }
6804
6805 pub fn inherited_injected_class_owner(
6822 &self,
6823 analyzer: &CppGraphSource<'_>,
6824 file: &ProjectFile,
6825 enclosing_owner: &CodeUnit,
6826 injected_name: &str,
6827 ) -> Option<CodeUnit> {
6828 let hierarchy = analyzer.type_hierarchy_provider()?;
6829 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6830 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6831 while !frontier.is_empty() {
6832 let mut level_matches = Vec::new();
6833 let mut next_frontier = Vec::new();
6834 for raw_owner in frontier {
6835 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6836 else {
6837 if raw_owner.identifier() == injected_name {
6838 return None;
6839 }
6840 continue;
6841 };
6842 let propagated = propagated_counts.entry(owner.clone()).or_default();
6843 if *propagated == 2 {
6844 continue;
6845 }
6846 *propagated += 1;
6847 if owner.identifier() == injected_name {
6848 level_matches.push(owner.clone());
6849 }
6850 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6851 }
6852 match level_matches.as_slice() {
6853 [owner] => return Some(owner.clone()),
6854 [_, ..] => return None,
6855 [] => {}
6856 }
6857 frontier = next_frontier;
6858 }
6859 None
6860 }
6861
6862 fn resolve_type_candidates(
6867 &self,
6868 analyzer: &CppGraphSource<'_>,
6869 file: &ProjectFile,
6870 candidates: &[&CodeUnit],
6871 resolution: TypeCandidateResolution<'_>,
6872 ) -> Result<CodeUnit, TypeCandidateFailure> {
6873 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6874 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6875 {
6876 return Ok(unit);
6877 }
6878 match resolution {
6879 TypeCandidateResolution::Canonical => {
6880 self.canonical_type_candidate_resolution(analyzer, file, candidates)
6881 }
6882 TypeCandidateResolution::PreserveAlias => {
6883 let same_fqn_alias_family = candidates.len() > 1
6890 && candidates.iter().all(|candidate| {
6891 declared_type_alias(analyzer, candidate)
6892 && same_logical_symbol(candidates[0], candidate)
6893 })
6894 && candidates
6895 .iter()
6896 .any(|candidate| candidate.source() != candidates[0].source());
6897 if same_fqn_alias_family {
6898 let physically_visible = candidates
6899 .iter()
6900 .copied()
6901 .filter(|candidate| self.is_physically_visible(file, candidate))
6902 .collect::<Vec<_>>();
6903 let one_structured_target = physically_visible.len() > 1
6913 && physically_visible.iter().skip(1).all(|candidate| {
6914 let target = self.structured_alias_target(analyzer, candidate);
6915 target.is_some()
6916 && target
6917 == self.structured_alias_target(analyzer, physically_visible[0])
6918 });
6919 if physically_visible.len() == 1 || one_structured_target {
6920 return Ok(physically_visible[0].clone());
6921 }
6922 }
6923 unique_type_candidate_preserving_alias(analyzer, file, candidates)
6924 .ok_or(TypeCandidateFailure::Ambiguous)
6925 }
6926 TypeCandidateResolution::PreserveTarget(target) => self
6927 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6928 .ok_or(TypeCandidateFailure::Ambiguous),
6929 }
6930 }
6931
6932 fn candidates_for_type_resolution<'b>(
6933 &self,
6934 analyzer: &CppGraphSource<'_>,
6935 file: &ProjectFile,
6936 candidates: &[&'b CodeUnit],
6937 resolution: TypeCandidateResolution<'_>,
6938 ) -> Vec<&'b CodeUnit> {
6939 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6940 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6941 if compile_proven.len() == 1 {
6942 return compile_proven;
6943 }
6944 }
6945 candidates.to_vec()
6946 }
6947
6948 fn compile_proven_type_candidates<'b>(
6955 &self,
6956 analyzer: &CppGraphSource<'_>,
6957 file: &ProjectFile,
6958 candidates: &[&'b CodeUnit],
6959 ) -> Vec<&'b CodeUnit> {
6960 let proven = self.compile_proven_guards(file);
6961 if proven.is_empty() {
6962 return Vec::new();
6963 }
6964 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6965 return Vec::new();
6966 };
6967 candidates
6968 .iter()
6969 .copied()
6970 .filter(|candidate| {
6971 let declaration_guards =
6972 declaration_guard_requirements(analyzer, self.cpp, candidate);
6973 if declaration_guards.is_empty() {
6974 return false;
6975 }
6976 if candidate.source() == file {
6977 return declaration_guards.iter().any(|(_, required)| {
6978 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6979 });
6980 }
6981 declaration_guards.iter().any(|(_, required)| {
6982 self.foreign_declaration_reachable_from_compile_proven_guards(
6983 file,
6984 prepared.as_ref(),
6985 candidate.source(),
6986 required,
6987 usize::MAX,
6988 )
6989 })
6990 })
6991 .collect()
6992 }
6993
6994 pub fn resolve_callable_value_components_lexically(
6995 &self,
6996 analyzer: &CppGraphSource<'_>,
6997 file: &ProjectFile,
6998 owner_components: &[String],
6999 member_name: &str,
7000 global: bool,
7001 lexical_scope: &[String],
7002 ) -> LexicalCallableValueResolution {
7003 if owner_components.is_empty() || member_name.is_empty() {
7004 return LexicalCallableValueResolution::Missing;
7005 }
7006 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7007 let owner_name = qualified_owner.join("::");
7008 let type_candidates = self
7009 .type_candidates(file, &owner_name)
7010 .into_iter()
7011 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7012 .collect::<Vec<_>>();
7013 let resolved_type = if type_candidates.is_empty() {
7014 None
7015 } else {
7016 let Some(unit) =
7017 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7018 else {
7019 return LexicalCallableValueResolution::Ambiguous;
7020 };
7021 Some(unit)
7022 };
7023
7024 let mut qualified_callable = qualified_owner;
7025 qualified_callable.push(member_name.to_string());
7026 let callable_name = qualified_callable.join("::");
7027 let free_function = self
7028 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7029 .into_iter()
7030 .find(|candidate| {
7031 canonical_cpp_name_matches(candidate, &callable_name)
7032 && type_owner_of(analyzer, candidate).is_none()
7033 })
7034 .cloned();
7035
7036 match (resolved_type, free_function) {
7037 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7038 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7039 (None, Some(function)) => {
7040 return LexicalCallableValueResolution::FreeFunction(function);
7041 }
7042 (None, None) => {}
7043 }
7044 }
7045 LexicalCallableValueResolution::Missing
7046 }
7047
7048 fn resolve_type_for_declaration(
7049 &self,
7050 visible_from: &ProjectFile,
7051 declaration: &CodeUnit,
7052 raw_name: &str,
7053 ) -> Option<CodeUnit> {
7054 let normalized = normalize_reference_name(raw_name)?;
7055 if !normalized.contains("::")
7056 && let Some(namespace) = cpp_namespace_for(declaration)
7057 {
7058 for prefix in namespace_prefixes(&namespace) {
7059 let qualified = format!("{prefix}::{normalized}");
7060 if let Some(unit) = self
7061 .type_candidates(visible_from, &qualified)
7062 .into_iter()
7063 .next()
7064 {
7065 return Some(unit.clone());
7066 }
7067 }
7068 }
7069 self.resolve_type(visible_from, raw_name)
7070 }
7071
7072 fn resolve_unique_canonical_type_for_declaration(
7073 &self,
7074 analyzer: &CppGraphSource<'_>,
7075 visible_from: &ProjectFile,
7076 declaration: &CodeUnit,
7077 raw_name: &str,
7078 ) -> Option<CodeUnit> {
7079 let mut current = self.resolve_defining_type_for_declaration(
7080 analyzer,
7081 visible_from,
7082 declaration,
7083 raw_name,
7084 )?;
7085 let mut seen_aliases = HashSet::default();
7086 loop {
7087 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7088 return current.is_class().then_some(current);
7089 };
7090 if matches!(target, StructuredAliasTarget::Builtin) {
7091 return current.is_class().then_some(current);
7092 }
7093 if !seen_aliases.insert(current.clone()) {
7094 return None;
7095 }
7096 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
7097 }
7098 }
7099
7100 pub fn canonical_type_unit(
7101 &self,
7102 analyzer: &CppGraphSource<'_>,
7103 visible_from: &ProjectFile,
7104 unit: &CodeUnit,
7105 ) -> Option<CodeUnit> {
7106 self.canonical_type_resolution(analyzer, visible_from, unit)
7107 .ok()
7108 }
7109
7110 pub fn canonical_type_unit_in_context(
7118 &self,
7119 analyzer: &CppGraphSource<'_>,
7120 visible_from: &ProjectFile,
7121 reference: Node<'_>,
7122 unit: &CodeUnit,
7123 ) -> Option<CodeUnit> {
7124 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7125 {
7126 return None;
7127 }
7128 self.canonical_type_resolution(analyzer, visible_from, unit)
7129 .ok()
7130 }
7131
7132 fn canonical_type_resolution(
7140 &self,
7141 analyzer: &CppGraphSource<'_>,
7142 visible_from: &ProjectFile,
7143 unit: &CodeUnit,
7144 ) -> Result<CodeUnit, TypeCandidateFailure> {
7145 let mut current = unit.clone();
7146 let mut seen_aliases = HashSet::default();
7147 loop {
7148 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7149 return current
7150 .is_class()
7151 .then_some(current)
7152 .ok_or(TypeCandidateFailure::Unresolvable);
7153 };
7154 if matches!(target, StructuredAliasTarget::Builtin) {
7155 return current
7156 .is_class()
7157 .then_some(current)
7158 .ok_or(TypeCandidateFailure::Unresolvable);
7159 }
7160 if !seen_aliases.insert(current.clone()) {
7161 return Err(TypeCandidateFailure::Unresolvable);
7162 }
7163 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7164 }
7165 }
7166
7167 pub fn canonical_visible_full_type_unit(
7168 &self,
7169 analyzer: &CppGraphSource<'_>,
7170 visible_from: &ProjectFile,
7171 unit: &CodeUnit,
7172 ) -> Option<CodeUnit> {
7173 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7174 if cpp_class_declaration_strength(analyzer, &canonical)
7175 != CppClassDeclarationStrength::Forward
7176 {
7177 return Some(canonical);
7178 }
7179 let mut full = Vec::new();
7180 for candidate in self
7181 .visible_identifier_candidates(visible_from, canonical.identifier())
7182 .filter(|candidate| {
7183 candidate.is_class()
7184 && candidate.fq_name() == canonical.fq_name()
7185 && cpp_class_declaration_strength(analyzer, candidate)
7186 == CppClassDeclarationStrength::Full
7187 })
7188 {
7189 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7190 full.push(candidate.clone());
7191 }
7192 }
7193 match full.len() {
7194 0 => Some(canonical),
7195 1 => full.pop(),
7196 _ => None,
7197 }
7198 }
7199
7200 fn resolve_structured_alias_target(
7201 &self,
7202 visible_from: &ProjectFile,
7203 declaration: &CodeUnit,
7204 target: &StructuredAliasTarget,
7205 ) -> Option<CodeUnit> {
7206 self.structured_alias_target_resolution(visible_from, declaration, target)
7207 .ok()
7208 }
7209
7210 fn structured_alias_target_resolution(
7211 &self,
7212 visible_from: &ProjectFile,
7213 declaration: &CodeUnit,
7214 target: &StructuredAliasTarget,
7215 ) -> Result<CodeUnit, TypeCandidateFailure> {
7216 let primary =
7217 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7218 let StructuredAliasTarget::Named { arguments, .. } = target else {
7219 return Err(TypeCandidateFailure::Unresolvable);
7220 };
7221 match arguments {
7222 Some(arguments) => self
7223 .resolve_template_arguments(visible_from, primary, arguments)
7224 .map_err(|error| match error {
7225 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7226 TypeCandidateFailure::Ambiguous
7227 }
7228 _ => TypeCandidateFailure::Unresolvable,
7229 }),
7230 None => Ok(primary),
7231 }
7232 }
7233
7234 fn resolve_structured_alias_primary(
7235 &self,
7236 visible_from: &ProjectFile,
7237 declaration: &CodeUnit,
7238 target: &StructuredAliasTarget,
7239 ) -> Option<CodeUnit> {
7240 self.structured_alias_primary_resolution(visible_from, declaration, target)
7241 .ok()
7242 }
7243
7244 fn structured_alias_primary_resolution(
7245 &self,
7246 visible_from: &ProjectFile,
7247 declaration: &CodeUnit,
7248 target: &StructuredAliasTarget,
7249 ) -> Result<CodeUnit, TypeCandidateFailure> {
7250 let StructuredAliasTarget::Named {
7251 components, global, ..
7252 } = target
7253 else {
7254 return Err(TypeCandidateFailure::Unresolvable);
7255 };
7256 let qualified = components.join("::");
7257 let candidates = if *global {
7258 let mut candidates = self.type_candidates(visible_from, &qualified);
7265 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7266 candidates
7267 } else {
7268 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7269 };
7270 logical_type_candidate(candidates)
7271 }
7272
7273 pub fn structured_alias_primary_preserves_target(
7274 &self,
7275 analyzer: &CppGraphSource<'_>,
7276 visible_from: &ProjectFile,
7277 candidate: &CodeUnit,
7278 target: &CodeUnit,
7279 ) -> bool {
7280 let mut current = candidate.clone();
7281 let mut seen = HashSet::default();
7282 let mut matched_target = false;
7283 loop {
7284 if same_visible_symbol(¤t, target)
7285 || self.compatible_primary_template_redeclarations(¤t, target)
7286 {
7287 matched_target = true;
7288 }
7289 if !seen.insert(current.clone()) {
7290 return false;
7291 }
7292 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7293 return matched_target;
7294 };
7295 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7296 return matched_target;
7297 };
7298 let Some(primary) =
7299 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7300 else {
7301 return matched_target;
7307 };
7308 current = primary;
7309 }
7310 }
7311
7312 pub fn structured_class_alias_resolves_to_target(
7313 &self,
7314 analyzer: &CppGraphSource<'_>,
7315 visible_from: &ProjectFile,
7316 alias: &CodeUnit,
7317 target: &CodeUnit,
7318 ) -> bool {
7319 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7320 return false;
7321 };
7322 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7323 return false;
7324 };
7325 let StructuredAliasTarget::Named {
7326 components, global, ..
7327 } = &alias_target
7328 else {
7329 return false;
7330 };
7331 let lexical_scope = canonical_cpp_scope_components(&owner);
7332 match self.resolve_type_components_lexically_for_target(
7333 analyzer,
7334 visible_from,
7335 components,
7336 *global,
7337 &lexical_scope,
7338 target,
7339 ) {
7340 LexicalTypeResolution::Resolved {
7341 unit, candidates, ..
7342 } => {
7343 same_visible_symbol(&unit, target)
7344 || self.same_template_member_identity(analyzer, &unit, target)
7345 || candidates.iter().any(|candidate| {
7346 same_visible_symbol(candidate, target)
7347 || self.same_template_member_identity(analyzer, candidate, target)
7348 })
7349 }
7350 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7351 self.structured_alias_primary_preserves_target(
7352 analyzer,
7353 visible_from,
7354 alias,
7355 target,
7356 ) || self.flattened_macro_namespace_alias_target_matches(
7357 analyzer,
7358 visible_from,
7359 alias,
7360 &alias_target,
7361 target,
7362 )
7363 }
7364 }
7365 }
7366
7367 pub fn structured_class_alias_path_preserves_target(
7375 &self,
7376 analyzer: &CppGraphSource<'_>,
7377 visible_from: &ProjectFile,
7378 alias: &CodeUnit,
7379 target: &CodeUnit,
7380 ) -> bool {
7381 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7382 return false;
7383 };
7384 let Some(StructuredAliasTarget::Named {
7385 components, global, ..
7386 }) = self.structured_alias_target(analyzer, alias)
7387 else {
7388 return false;
7389 };
7390 let lexical_scope = canonical_cpp_scope_components(&owner);
7391 (1..components.len()).rev().any(|component_count| {
7392 matches!(
7393 self.resolve_type_components_lexically_for_target(
7394 analyzer,
7395 visible_from,
7396 &components[..component_count],
7397 global,
7398 &lexical_scope,
7399 target,
7400 ),
7401 LexicalTypeResolution::Resolved {
7402 ref unit,
7403 ref candidates,
7404 ..
7405 } if same_visible_symbol(unit, target)
7406 || self.same_template_member_identity(analyzer, unit, target)
7407 || candidates.iter().any(|candidate| {
7408 same_visible_symbol(candidate, target)
7409 || self.same_template_member_identity(analyzer, candidate, target)
7410 })
7411 )
7412 })
7413 }
7414
7415 fn flattened_macro_namespace_alias_target_matches(
7416 &self,
7417 analyzer: &CppGraphSource<'_>,
7418 visible_from: &ProjectFile,
7419 alias: &CodeUnit,
7420 alias_target: &StructuredAliasTarget,
7421 target: &CodeUnit,
7422 ) -> bool {
7423 let StructuredAliasTarget::Named {
7424 components,
7425 global: false,
7426 arguments: None,
7427 } = alias_target
7428 else {
7429 return false;
7430 };
7431 let Some((target_name, namespace_components)) = components.split_last() else {
7432 return false;
7433 };
7434 if namespace_components.is_empty()
7435 || target_name != target.identifier()
7436 || alias.source() != target.source()
7437 || alias.source() != visible_from
7438 || !target.is_class()
7439 || declared_type_alias(analyzer, target)
7440 {
7441 return false;
7442 }
7443 if self
7444 .resolve_structured_alias_target(visible_from, alias, alias_target)
7445 .is_some()
7446 {
7447 return false;
7448 }
7449
7450 let alias_ranges = analyzer.ranges(alias);
7451 let target_ranges = analyzer.ranges(target);
7452 if alias_ranges.is_empty() || target_ranges.is_empty() {
7453 return false;
7454 }
7455 let alias_start = alias_ranges
7456 .iter()
7457 .map(|range| range.start_byte)
7458 .min()
7459 .expect("non-empty alias ranges have a minimum");
7460 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
7461 return false;
7462 };
7463 let root = prepared.tree().root_node();
7464 let has_matching_declaration = target_ranges
7465 .iter()
7466 .filter(|range| range.end_byte <= alias_start)
7467 .filter_map(|range| node_for_exact_range(root, range))
7468 .any(|node| {
7469 flattened_macro_namespace_components(node, prepared.source())
7470 .is_some_and(|recovered| recovered == namespace_components)
7471 });
7472 if !has_matching_declaration {
7473 return false;
7474 }
7475
7476 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7477 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7478 guard_requirement_sets_match(&alias_guards, &target_guards)
7479 }
7480
7481 pub fn template_alias_arguments_preserve_target(
7482 &self,
7483 analyzer: &CppGraphSource<'_>,
7484 visible_from: &ProjectFile,
7485 alias: &CodeUnit,
7486 arguments: &[CppTemplateExpression],
7487 target: &CodeUnit,
7488 ) -> bool {
7489 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7490 return false;
7491 };
7492 if metadata.alias_target.is_none()
7493 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7494 {
7495 return false;
7496 }
7497 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7498 }
7499
7500 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7501 self.cpp_template_metadata
7502 .get(unit)
7503 .is_some_and(CppTemplateMetadata::is_primary)
7504 }
7505
7506 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7507 self.cpp_template_metadata
7508 .get(unit)
7509 .is_some_and(CppTemplateMetadata::is_specialization)
7510 }
7511
7512 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7513 same_visible_symbol(left, right)
7514 || self.compatible_primary_template_redeclarations(left, right)
7515 }
7516
7517 pub fn same_template_member_identity(
7518 &self,
7519 analyzer: &CppGraphSource<'_>,
7520 left: &CodeUnit,
7521 right: &CodeUnit,
7522 ) -> bool {
7523 if same_visible_symbol(left, right) {
7524 return true;
7525 }
7526 if left.kind() != right.kind()
7527 || left.identifier() != right.identifier()
7528 || left.signature() != right.signature()
7529 {
7530 return false;
7531 }
7532 let (Some(left_owner), Some(right_owner)) =
7533 (analyzer.parent_of(left), analyzer.parent_of(right))
7534 else {
7535 return false;
7536 };
7537 left_owner.is_class()
7538 && right_owner.is_class()
7539 && self.same_template_owner_identity(&left_owner, &right_owner)
7540 }
7541
7542 fn unique_canonical_type_candidate(
7543 &self,
7544 analyzer: &CppGraphSource<'_>,
7545 visible_from: &ProjectFile,
7546 candidates: &[&CodeUnit],
7547 ) -> Option<CodeUnit> {
7548 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7549 .ok()
7550 }
7551
7552 fn canonical_type_candidate_resolution(
7553 &self,
7554 analyzer: &CppGraphSource<'_>,
7555 visible_from: &ProjectFile,
7556 candidates: &[&CodeUnit],
7557 ) -> Result<CodeUnit, TypeCandidateFailure> {
7558 let mut canonical = Vec::new();
7559 for candidate in candidates {
7560 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7561 if canonical
7562 .iter()
7563 .any(|existing| same_visible_symbol(existing, &resolved))
7564 {
7565 continue;
7566 }
7567 if let Some(existing) = canonical.iter_mut().find(|existing| {
7568 self.compatible_primary_template_redeclarations(existing, &resolved)
7569 }) {
7570 if matches!(
7579 (
7580 cpp_class_declaration_strength(analyzer, existing),
7581 cpp_class_declaration_strength(analyzer, &resolved),
7582 ),
7583 (
7584 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7585 CppClassDeclarationStrength::Full,
7586 ) | (
7587 CppClassDeclarationStrength::Unknown,
7588 CppClassDeclarationStrength::Forward,
7589 )
7590 ) {
7591 *existing = resolved;
7592 }
7593 continue;
7594 }
7595 canonical.push(resolved);
7596 if canonical.len() > 1 {
7597 return Err(TypeCandidateFailure::Ambiguous);
7598 }
7599 }
7600 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7601 }
7602
7603 pub fn unique_type_candidate_preserving_target(
7604 &self,
7605 analyzer: &CppGraphSource<'_>,
7606 visible_from: &ProjectFile,
7607 candidates: &[&CodeUnit],
7608 target: &CodeUnit,
7609 ) -> Option<CodeUnit> {
7610 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7621 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7622 {
7623 return Some(target.clone());
7624 }
7625 let mut resolved_candidates = Vec::new();
7626 for candidate in candidates {
7627 let Some(resolved) =
7633 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7634 else {
7635 continue;
7636 };
7637 if resolved_candidates
7638 .iter()
7639 .any(|existing| same_visible_symbol(existing, &resolved))
7640 {
7641 continue;
7642 }
7643 resolved_candidates.push(resolved);
7644 }
7645 match resolved_candidates.as_slice() {
7646 [] => None,
7647 [single] => Some(single.clone()),
7648 _ => self
7653 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7654 .map(|_| target.clone()),
7655 }
7656 }
7657
7658 pub fn same_fqn_type_spelling_for_target<'b>(
7675 &self,
7676 analyzer: &CppGraphSource<'_>,
7677 visible_from: &ProjectFile,
7678 candidates: &[&'b CodeUnit],
7679 target: &CodeUnit,
7680 ) -> Option<&'b CodeUnit> {
7681 let [first, rest @ ..] = candidates else {
7682 return None;
7683 };
7684 if rest.is_empty()
7685 || !rest.iter().all(|candidate| {
7686 candidate.kind() == first.kind()
7687 && candidate.fq_name() == first.fq_name()
7688 && candidate.source() == first.source()
7689 })
7690 {
7691 return None;
7692 }
7693 candidates
7694 .iter()
7695 .copied()
7696 .find(|candidate| same_symbol(candidate, target))
7697 .or_else(|| {
7698 candidates.iter().copied().find(|candidate| {
7699 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7700 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7701 })
7702 })
7703 }
7704
7705 pub fn alternate_same_fqn_type_declarations(
7706 &self,
7707 analyzer: &CppGraphSource<'_>,
7708 candidates: &[&CodeUnit],
7709 target: &CodeUnit,
7710 ) -> bool {
7711 let Some(first) = candidates.first() else {
7712 return false;
7713 };
7714 let same_api = first.kind() == target.kind()
7715 && first.fq_name() == target.fq_name()
7716 && first.source() == target.source()
7717 && candidates.iter().all(|candidate| {
7718 candidate.kind() == target.kind()
7719 && candidate.fq_name() == target.fq_name()
7720 && candidate.source() == target.source()
7721 })
7722 && candidates
7723 .iter()
7724 .any(|candidate| same_symbol(candidate, target))
7725 && candidates
7726 .iter()
7727 .any(|candidate| !same_logical_symbol(candidate, target));
7728 if !same_api {
7729 return false;
7730 }
7731
7732 let requirements = candidates
7733 .iter()
7734 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7735 .collect::<Vec<_>>();
7736 requirements.len() > 1
7737 && requirements
7738 .iter()
7739 .all(|requirement| !requirement.is_empty())
7740 && requirements.iter().enumerate().all(|(index, left)| {
7741 requirements[index + 1..].iter().all(|right| {
7742 left.iter().all(|(_, left_guards)| {
7743 right.iter().all(|(_, right_guards)| {
7744 merge_preprocessor_guards(left_guards, right_guards).is_none()
7745 })
7746 })
7747 })
7748 })
7749 }
7750
7751 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7752 let mut pending = vec![terms.to_vec()];
7753 while let Some(branch_terms) = pending.pop() {
7754 let mut normalized = Vec::new();
7755 let mut covers_branch = false;
7756 for term in branch_terms {
7757 if term.iter().any(|guard| term.contains(&guard.negated())) {
7758 continue;
7759 }
7760 if term.is_empty() {
7761 covers_branch = true;
7762 break;
7763 }
7764 if !normalized.iter().any(|existing| existing == &term) {
7765 normalized.push(term);
7766 }
7767 }
7768 if covers_branch {
7769 continue;
7770 }
7771 let Some(split_guard) = normalized
7772 .iter()
7773 .flat_map(|term| term.iter())
7774 .next()
7775 .cloned()
7776 else {
7777 return false;
7778 };
7779 let negated_guard = split_guard.negated();
7780 let mut when_defined = Vec::new();
7781 let mut when_undefined = Vec::new();
7782 for term in normalized {
7783 if term.contains(&negated_guard) {
7784 } else if term.contains(&split_guard) {
7786 let mut reduced = term.clone();
7787 reduced.remove(&split_guard);
7788 when_defined.push(reduced);
7789 } else {
7790 when_defined.push(term.clone());
7791 }
7792 if term.contains(&split_guard) {
7793 } else if term.contains(&negated_guard) {
7795 let mut reduced = term;
7796 reduced.remove(&negated_guard);
7797 when_undefined.push(reduced);
7798 } else {
7799 when_undefined.push(term);
7800 }
7801 }
7802 pending.push(when_defined);
7803 pending.push(when_undefined);
7804 }
7805 true
7806 }
7807
7808 fn declarations_share_exhaustive_conditional_family(
7817 &self,
7818 analyzer: &CppGraphSource<'_>,
7819 candidates: &[&CodeUnit],
7820 ) -> Option<(usize, usize)> {
7821 let mut family_range = None;
7822 for candidate in candidates {
7823 let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
7824 let root = prepared.tree().root_node();
7825 let mut candidate_family = None;
7826 for range in analyzer.ranges(candidate) {
7827 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7828 let family = preprocessor_conditional_family_for_declaration(node)?;
7829 let key = (family.start_byte(), family.end_byte());
7830 if candidate_family.is_some_and(|existing| existing != key) {
7831 return None;
7832 }
7833 candidate_family = Some(key);
7834 }
7835 let candidate_family = candidate_family?;
7836 if family_range.is_some_and(|existing| existing != candidate_family) {
7837 return None;
7838 }
7839 family_range = Some(candidate_family);
7840 }
7841 family_range
7842 }
7843
7844 pub fn complementary_same_fqn_type_declarations(
7845 &self,
7846 analyzer: &CppGraphSource<'_>,
7847 candidates: &[&CodeUnit],
7848 target: &CodeUnit,
7849 ) -> bool {
7850 if candidates.len() < 2
7851 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7852 || self
7853 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7854 .is_none()
7855 {
7856 return false;
7857 }
7858 Self::preprocessor_guard_terms_cover_all_paths(
7859 &self.declaration_family_guard_terms(analyzer, candidates),
7860 )
7861 }
7862
7863 fn declaration_family_guard_terms(
7864 &self,
7865 analyzer: &CppGraphSource<'_>,
7866 candidates: &[&CodeUnit],
7867 ) -> Vec<HashSet<PreprocessorGuard>> {
7868 candidates
7869 .iter()
7870 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7871 .map(|(_, guards)| guards)
7872 .collect()
7873 }
7874
7875 fn exhaustive_guard_family_activation(
7891 &self,
7892 analyzer: &CppGraphSource<'_>,
7893 prepared: &PreparedSyntaxTree,
7894 candidate: &CodeUnit,
7895 reference: &CallableReferenceContext<'_>,
7896 ) -> Option<usize> {
7897 if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
7900 return None;
7901 }
7902 let family = self
7903 .visible_identifier_candidates(candidate.source(), candidate.identifier())
7904 .filter(|peer| {
7905 peer.kind() == candidate.kind()
7906 && peer.fq_name() == candidate.fq_name()
7907 && peer.source() == candidate.source()
7908 })
7909 .collect::<Vec<_>>();
7910 let (_, family_end) =
7911 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7912 if !Self::preprocessor_guard_terms_cover_all_paths(
7913 &self.declaration_family_guard_terms(analyzer, &family),
7914 ) {
7915 return None;
7916 }
7917 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7921 .iter()
7922 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7923 {
7924 return None;
7925 }
7926 (first_declaration_byte(analyzer, candidate)?
7927 == family
7928 .iter()
7929 .filter_map(|peer| first_declaration_byte(analyzer, peer))
7930 .min()?)
7931 .then_some(family_end)
7932 }
7933
7934 fn type_candidate_preserving_target(
7935 &self,
7936 analyzer: &CppGraphSource<'_>,
7937 visible_from: &ProjectFile,
7938 candidate: &CodeUnit,
7939 target: &CodeUnit,
7940 ) -> Option<CodeUnit> {
7941 let mut current = candidate.clone();
7942 let mut matched_target = same_visible_symbol(¤t, target)
7943 || self.compatible_primary_template_redeclarations(¤t, target);
7944 let mut seen = HashSet::default();
7945 loop {
7946 if !seen.insert(current.clone()) {
7947 return None;
7948 }
7949 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7950 return matched_target
7951 .then(|| target.clone())
7952 .or_else(|| current.is_class().then_some(current));
7953 };
7954 if self.flattened_macro_namespace_alias_target_matches(
7955 analyzer,
7956 visible_from,
7957 ¤t,
7958 &alias_target,
7959 target,
7960 ) {
7961 return Some(target.clone());
7962 }
7963 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7964 return matched_target
7965 .then(|| target.clone())
7966 .or_else(|| current.is_class().then_some(current));
7967 }
7968 if !self.cpp_template_metadata.contains_key(¤t)
7976 && let Some(primary) =
7977 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7978 && (same_visible_symbol(&primary, target)
7979 || self.compatible_primary_template_redeclarations(&primary, target))
7980 {
7981 return Some(target.clone());
7982 }
7983 if same_visible_symbol(¤t, target) {
7984 return Some(target.clone());
7985 }
7986 if self.cpp_template_metadata.contains_key(¤t) {
7987 return None;
7988 }
7989 let Some(next) =
7990 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
7991 else {
7992 return matched_target.then(|| target.clone());
7993 };
7994 current = next;
7995 matched_target |= same_visible_symbol(¤t, target)
7996 || self.compatible_primary_template_redeclarations(¤t, target);
7997 }
7998 }
7999
8000 fn compatible_primary_template_redeclarations(
8001 &self,
8002 left: &CodeUnit,
8003 right: &CodeUnit,
8004 ) -> bool {
8005 let (Some(left_metadata), Some(right_metadata)) = (
8006 self.cpp_template_metadata.get(left),
8007 self.cpp_template_metadata.get(right),
8008 ) else {
8009 return false;
8010 };
8011 left_metadata.primary_fq_name == right_metadata.primary_fq_name
8012 && left_metadata.is_primary()
8013 && right_metadata.is_primary()
8014 && cpp_reconcile_primary_template_parameters(
8015 &[(left, left_metadata), (right, right_metadata)],
8016 right,
8017 )
8018 .is_some()
8019 }
8020
8021 fn alias_candidate_may_preserve_target(
8022 &self,
8023 analyzer: &CppGraphSource<'_>,
8024 visible_from: &ProjectFile,
8025 candidate: &CodeUnit,
8026 target: &CodeUnit,
8027 ) -> bool {
8028 let mut current = candidate.clone();
8029 let mut seen = HashSet::default();
8030 loop {
8031 if same_visible_symbol(¤t, target)
8032 || self.compatible_primary_template_redeclarations(¤t, target)
8033 {
8034 return true;
8035 }
8036 if self.cpp_template_metadata.contains_key(¤t) {
8037 return true;
8038 }
8039 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8040 return false;
8041 };
8042 let StructuredAliasTarget::Named {
8043 components,
8044 global,
8045 arguments,
8046 } = alias_target
8047 else {
8048 return false;
8049 };
8050 if arguments.is_some() || !seen.insert(current.clone()) {
8051 return true;
8052 }
8053 let qualified = components.join("::");
8054 let next = if global {
8055 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8056 } else {
8057 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8058 };
8059 let Some(next) = next else {
8060 return true;
8061 };
8062 current = next;
8063 }
8064 }
8065
8066 fn alias_candidate_structurally_reaches_target(
8067 &self,
8068 analyzer: &CppGraphSource<'_>,
8069 visible_from: &ProjectFile,
8070 candidate: &CodeUnit,
8071 target: &CodeUnit,
8072 ) -> bool {
8073 let mut current = candidate.clone();
8074 let mut seen = HashSet::default();
8075 loop {
8076 if same_visible_symbol(¤t, target)
8077 || self.compatible_primary_template_redeclarations(¤t, target)
8078 || self
8079 .cpp_template_metadata
8080 .get(¤t)
8081 .zip(self.cpp_template_metadata.get(target))
8082 .is_some_and(|(current, target)| {
8083 current.primary_fq_name == target.primary_fq_name
8084 })
8085 {
8086 return true;
8087 }
8088 if !seen.insert(current.clone()) {
8089 return false;
8090 }
8091 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8092 return false;
8093 };
8094 if let StructuredAliasTarget::Named { components, .. } = &alias_target
8095 && components[..components.len().saturating_sub(1)]
8096 .iter()
8097 .any(|component| {
8098 self.type_reference_component_directly_names_target(component, target)
8099 })
8100 && self.structured_class_alias_path_preserves_target(
8101 analyzer,
8102 visible_from,
8103 ¤t,
8104 target,
8105 )
8106 {
8107 return true;
8108 }
8109 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8110 return false;
8111 }
8112 let Some(primary) =
8113 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8114 else {
8115 return false;
8116 };
8117 current = primary;
8118 }
8119 }
8120
8121 pub fn qualified_alias_reference_may_reach_target(
8133 &self,
8134 analyzer: &CppGraphSource<'_>,
8135 file: &ProjectFile,
8136 components: &[String],
8137 global: bool,
8138 target: &CodeUnit,
8139 ) -> bool {
8140 for end in 0..components.len() {
8141 let spelled = &components[..=end];
8142 for candidate in self
8143 .visible_identifier_candidates(file, &components[end])
8144 .filter(|candidate| declared_type_alias(analyzer, candidate))
8145 {
8146 let candidate_components = canonical_cpp_scope_components(candidate);
8147 let shape_matches = if global {
8148 candidate_components == spelled
8149 } else {
8150 candidate_components.ends_with(spelled)
8151 };
8152 if shape_matches
8153 && self.alias_candidate_structurally_reaches_target(
8154 analyzer, file, candidate, target,
8155 )
8156 {
8157 return true;
8158 }
8159 }
8160 }
8161 false
8162 }
8163
8164 fn type_candidates_for_declaration<'b>(
8168 &'b self,
8169 visible_from: &ProjectFile,
8170 declaration: &CodeUnit,
8171 raw_name: &str,
8172 ) -> Vec<&'b CodeUnit> {
8173 let Some(normalized) = normalize_reference_name(raw_name) else {
8174 return Vec::new();
8175 };
8176 if let Some(namespace) = cpp_namespace_for(declaration) {
8177 for prefix in namespace_prefixes(&namespace) {
8178 let qualified = format!("{prefix}::{normalized}");
8179 let candidates = self.type_candidates(visible_from, &qualified);
8180 if !candidates.is_empty() {
8181 return candidates;
8182 }
8183 }
8184 }
8185 self.type_candidates(visible_from, &normalized)
8186 }
8187
8188 fn resolve_unique_type_for_declaration(
8189 &self,
8190 visible_from: &ProjectFile,
8191 declaration: &CodeUnit,
8192 raw_name: &str,
8193 ) -> Option<CodeUnit> {
8194 unique_logical_type_candidate(self.type_candidates_for_declaration(
8195 visible_from,
8196 declaration,
8197 raw_name,
8198 ))
8199 }
8200
8201 fn resolve_defining_type_for_declaration(
8219 &self,
8220 analyzer: &CppGraphSource<'_>,
8221 visible_from: &ProjectFile,
8222 declaration: &CodeUnit,
8223 raw_name: &str,
8224 ) -> Option<CodeUnit> {
8225 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8226 let logical = unique_logical_type_candidate(candidates.clone())?;
8227 let mut defining = candidates.into_iter().filter(|candidate| {
8228 candidate.is_class()
8229 && cpp_class_declaration_strength(analyzer, candidate)
8230 == CppClassDeclarationStrength::Full
8231 });
8232 match (defining.next(), defining.next()) {
8233 (Some(unique_definition), None) => Some(unique_definition.clone()),
8234 _ => Some(logical),
8235 }
8236 }
8237
8238 pub fn resolves_to_type(
8239 &self,
8240 analyzer: &CppGraphSource<'_>,
8241 file: &ProjectFile,
8242 raw_name: &str,
8243 target: &CodeUnit,
8244 ) -> bool {
8245 let Some(normalized) = normalize_reference_name(raw_name) else {
8246 return false;
8247 };
8248 let candidates = self.type_candidates(file, &normalized);
8249 if candidates.is_empty() {
8250 return self.parser_alias_resolves_to_type(file, raw_name, target);
8251 }
8252 let Some(resolved) =
8253 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8254 else {
8255 return false;
8256 };
8257 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8258 }
8259
8260 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8261 let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
8262 let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
8263 match resolved.kind() {
8264 CodeUnitType::Class => Some(resolved),
8265 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
8266 _ => None,
8267 }
8268 }
8269
8270 pub fn same_logical_callable(
8285 &self,
8286 analyzer: &CppGraphSource<'_>,
8287 left: &CodeUnit,
8288 right: &CodeUnit,
8289 ) -> bool {
8290 if same_logical_symbol(left, right) {
8291 return true;
8292 }
8293 if left.kind() != right.kind()
8294 || !left.is_callable()
8295 || !right.is_callable()
8296 || left.fq_name() != right.fq_name()
8297 {
8298 return false;
8299 }
8300 if self.callable_is_template_declaration(analyzer, left)
8306 || self.callable_is_template_declaration(analyzer, right)
8307 {
8308 return false;
8309 }
8310 let (Some(left_comparable), Some(right_comparable)) = (
8311 self.callable_comparable(analyzer, left),
8312 self.callable_comparable(analyzer, right),
8313 ) else {
8314 return false;
8315 };
8316 if left_comparable.suffix != right_comparable.suffix
8321 || left_comparable.shapes.len() != right_comparable.shapes.len()
8322 {
8323 return false;
8324 }
8325 left_comparable
8326 .shapes
8327 .iter()
8328 .zip(right_comparable.shapes.iter())
8329 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8330 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8331 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8332 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8333 }
8334 _ => false,
8338 })
8339 }
8340
8341 fn comparable_shapes_agree(
8347 &self,
8348 analyzer: &CppGraphSource<'_>,
8349 left: &CppComparableParameter,
8350 right: &CppComparableParameter,
8351 ) -> bool {
8352 let mut stack = vec![(left.root(), right.root())];
8353 while let Some((left_index, right_index)) = stack.pop() {
8354 match (left.node(left_index), right.node(right_index)) {
8355 (
8356 CppComparableNode::Named {
8357 name: left_name,
8358 primitive: left_primitive,
8359 konst: left_konst,
8360 volatil: left_volatil,
8361 },
8362 CppComparableNode::Named {
8363 name: right_name,
8364 primitive: right_primitive,
8365 konst: right_konst,
8366 volatil: right_volatil,
8367 },
8368 ) => {
8369 if left_konst != right_konst
8370 || left_volatil != right_volatil
8371 || left_primitive != right_primitive
8372 || !self.comparable_names_agree(
8373 analyzer,
8374 left_name,
8375 right_name,
8376 *left_primitive,
8377 )
8378 {
8379 return false;
8380 }
8381 }
8382 (
8383 CppComparableNode::Pointer {
8384 inner: left_inner,
8385 konst: left_konst,
8386 volatil: left_volatil,
8387 },
8388 CppComparableNode::Pointer {
8389 inner: right_inner,
8390 konst: right_konst,
8391 volatil: right_volatil,
8392 },
8393 ) => {
8394 if left_konst != right_konst || left_volatil != right_volatil {
8395 return false;
8396 }
8397 stack.push((*left_inner, *right_inner));
8398 }
8399 (
8400 CppComparableNode::Reference { inner: left_inner },
8401 CppComparableNode::Reference { inner: right_inner },
8402 )
8403 | (
8404 CppComparableNode::Array { inner: left_inner },
8405 CppComparableNode::Array { inner: right_inner },
8406 ) => stack.push((*left_inner, *right_inner)),
8407 (
8408 CppComparableNode::Generic {
8409 base: left_base,
8410 arguments: left_arguments,
8411 },
8412 CppComparableNode::Generic {
8413 base: right_base,
8414 arguments: right_arguments,
8415 },
8416 ) => {
8417 if left_arguments.len() != right_arguments.len() {
8418 return false;
8419 }
8420 stack.push((*left_base, *right_base));
8421 stack.extend(
8422 left_arguments.iter().zip(right_arguments.iter()).map(
8423 |(left_argument, right_argument)| (*left_argument, *right_argument),
8424 ),
8425 );
8426 }
8427 _ => return false,
8428 }
8429 }
8430 true
8431 }
8432
8433 fn comparable_names_agree(
8443 &self,
8444 analyzer: &CppGraphSource<'_>,
8445 left: &StructuredTypeName,
8446 right: &StructuredTypeName,
8447 primitive: bool,
8448 ) -> bool {
8449 if primitive {
8450 return left.path() == right.path();
8451 }
8452 match (
8453 self.comparable_name_terminal(analyzer, left),
8454 self.comparable_name_terminal(analyzer, right),
8455 ) {
8456 (Some(left_terminal), Some(right_terminal)) => {
8457 same_logical_symbol(&left_terminal, &right_terminal)
8458 }
8459 (None, None) => {
8460 left.path() == right.path() && left.is_absolute() == right.is_absolute()
8461 }
8462 _ => false,
8463 }
8464 }
8465
8466 fn comparable_name_terminal(
8476 &self,
8477 analyzer: &CppGraphSource<'_>,
8478 name: &StructuredTypeName,
8479 ) -> Option<CodeUnit> {
8480 let mut current = self.comparable_name_declaration(analyzer, name)?;
8481 let mut visited = HashSet::default();
8482 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8483 if !declared_type_alias(analyzer, ¤t) {
8490 return current.is_class().then_some(current);
8491 }
8492 if !visited.insert(current.clone()) {
8493 return None;
8494 }
8495 let signature = current.signature()?;
8496 if cpp_alias_declaration_adds_indirection(signature) {
8501 return None;
8502 }
8503 let raw_target = cpp_alias_declaration_target_text(signature)?;
8504 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
8505 }
8506 None
8507 }
8508
8509 fn comparable_alias_target(
8520 &self,
8521 analyzer: &CppGraphSource<'_>,
8522 alias: &CodeUnit,
8523 raw_target: &str,
8524 ) -> Option<CodeUnit> {
8525 let absolute = raw_target.trim_start().starts_with("::");
8530 let normalized = normalize_reference_name(raw_target)?;
8531 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8532 brokk_bifrost_core::analyzer::Language::Cpp,
8533 &normalized,
8534 );
8535 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8536 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8537 brokk_bifrost_core::analyzer::Language::Cpp,
8538 &namespace,
8539 )
8540 });
8541 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8542 self.comparable_name_declaration(analyzer, &name)
8543 }
8544
8545 fn comparable_name_declaration(
8553 &self,
8554 analyzer: &CppGraphSource<'_>,
8555 name: &StructuredTypeName,
8556 ) -> Option<CodeUnit> {
8557 let mut cache = self
8564 .comparable_name_declarations
8565 .lock()
8566 .expect("C++ comparable name declaration cache poisoned");
8567 if let Some(cached) = cache.get(name) {
8568 return cached.clone();
8569 }
8570 let interner = segment_interner();
8571 let identifier = name.path().last()?;
8572 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8573 let first_depth = if name.is_absolute() {
8574 0
8575 } else {
8576 name.lexical_scope().len()
8577 };
8578 let mut resolved = None;
8579 for depth in (0..=first_depth).rev() {
8580 let mut structured = FqName::new();
8581 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8582 structured.push(interner.intern(component, SegmentKind::Unknown));
8583 }
8584 let mut candidates = candidates_by_identifier
8585 .iter()
8586 .filter(|unit| unit.fq().same_segment_texts(&structured))
8587 .filter(|unit| {
8588 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8589 })
8590 .cloned();
8591 let Some(first) = candidates.next() else {
8592 continue;
8593 };
8594 resolved = candidates
8595 .all(|unit| same_logical_symbol(&unit, &first))
8596 .then_some(first);
8597 break;
8598 }
8599 cache.insert(name.clone(), resolved.clone());
8600 resolved
8601 }
8602
8603 fn callable_comparable(
8609 &self,
8610 analyzer: &CppGraphSource<'_>,
8611 unit: &CodeUnit,
8612 ) -> Option<Arc<ExtractedComparable>> {
8613 if let Some(cached) = self
8614 .callable_comparables
8615 .lock()
8616 .expect("C++ callable comparable cache poisoned")
8617 .get(unit)
8618 .cloned()
8619 {
8620 return cached;
8621 }
8622 let extracted = self
8623 .extract_callable_comparable(analyzer, unit)
8624 .map(Arc::new);
8625 self.callable_comparables
8626 .lock()
8627 .expect("C++ callable comparable cache poisoned")
8628 .insert(unit.clone(), extracted.clone());
8629 extracted
8630 }
8631
8632 fn extract_callable_comparable(
8633 &self,
8634 analyzer: &CppGraphSource<'_>,
8635 unit: &CodeUnit,
8636 ) -> Option<ExtractedComparable> {
8637 let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
8638 let root = prepared.tree().root_node();
8639 let declarator = analyzer
8640 .ranges(unit)
8641 .into_iter()
8642 .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
8643 Some(ExtractedComparable {
8644 shapes: cpp_comparable_parameter_shapes(
8647 declarator,
8648 prepared.source(),
8649 &ParentIndex::unindexed(),
8650 ),
8651 suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
8652 })
8653 }
8654
8655 pub fn canonical_type_for_reference(
8656 &self,
8657 file: &ProjectFile,
8658 raw_name: &str,
8659 ) -> Option<CodeUnit> {
8660 let resolved = self.resolve_type(file, raw_name)?;
8661 self.alias_target(&resolved).or(Some(resolved))
8662 }
8663
8664 pub fn parser_alias_resolves_to_type(
8665 &self,
8666 file: &ProjectFile,
8667 raw_name: &str,
8668 target: &CodeUnit,
8669 ) -> bool {
8670 let Some(alias_name) = normalize_reference_name(raw_name) else {
8671 return false;
8672 };
8673 self.parser_alias_name_may_resolve_to_target(file, &alias_name, target)
8674 }
8675
8676 #[cfg(any(test, feature = "test-support"))]
8677 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8678 self.visible_source_files_by_root
8679 .get(file)
8680 .cloned()
8681 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8682 }
8683
8684 #[cfg(any(test, feature = "test-support"))]
8685 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8686 self.alias_source_parse_counts
8687 .lock()
8688 .expect("alias source parse count lock")
8689 .get(file)
8690 .copied()
8691 .unwrap_or(0)
8692 }
8693
8694 #[cfg(any(test, feature = "test-support"))]
8695 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8696 self.parser_alias_fallback_files.load(Ordering::Relaxed)
8697 }
8698
8699 pub fn resolve_named(
8700 &self,
8701 file: &ProjectFile,
8702 raw_name: &str,
8703 kind: TargetKind,
8704 ) -> Option<CodeUnit> {
8705 let normalized = normalize_reference_name(raw_name)?;
8706 self.named_candidates_for_normalized(file, &normalized, kind)
8707 .into_iter()
8708 .next()
8709 .cloned()
8710 }
8711
8712 pub fn contains_named_symbol(
8713 &self,
8714 file: &ProjectFile,
8715 raw_name: &str,
8716 kind: TargetKind,
8717 target: &CodeUnit,
8718 ) -> bool {
8719 let Some(normalized) = normalize_reference_name(raw_name) else {
8720 return false;
8721 };
8722 self.named_candidates_for_normalized(file, &normalized, kind)
8723 .into_iter()
8724 .any(|unit| {
8725 matches_kind_for_lookup(unit, kind)
8726 && reference_matches_unit(&normalized, unit)
8727 && same_visible_symbol(unit, target)
8728 })
8729 }
8730
8731 pub fn named_candidates(
8732 &self,
8733 file: &ProjectFile,
8734 raw_name: &str,
8735 kind: TargetKind,
8736 ) -> Vec<CodeUnit> {
8737 let Some(normalized) = normalize_reference_name(raw_name) else {
8738 return Vec::new();
8739 };
8740 self.named_candidates_for_normalized(file, &normalized, kind)
8741 .into_iter()
8742 .cloned()
8743 .collect()
8744 }
8745
8746 pub fn resolve_known_non_target(
8747 &self,
8748 file: &ProjectFile,
8749 raw_name: &str,
8750 kind: TargetKind,
8751 target: &CodeUnit,
8752 ) -> bool {
8753 let Some(normalized) = normalize_reference_name(raw_name) else {
8754 return false;
8755 };
8756 normalized.contains("::")
8757 && self
8758 .named_candidates_for_normalized(file, &normalized, kind)
8759 .into_iter()
8760 .any(|unit| {
8761 matches_kind_for_lookup(unit, kind)
8762 && reference_matches_unit(&normalized, unit)
8763 && !same_visible_symbol(unit, target)
8764 })
8765 }
8766
8767 pub fn resolve_call_return_binding(
8768 &self,
8769 analyzer: &CppGraphSource<'_>,
8770 file: &ProjectFile,
8771 raw_name: &str,
8772 arity: usize,
8773 lexical_namespace: Option<&str>,
8774 direct_type: Option<&CodeUnit>,
8775 ) -> Option<CppScanBinding> {
8776 let normalized = normalize_reference_name(raw_name)?;
8777 let mut candidates = Vec::new();
8778 for function in
8779 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8780 {
8781 if cpp_callable_arity(analyzer, function).accepts(arity)
8782 && !direct_type.is_some_and(|direct_type| {
8783 self.callable_is_constructor_declaration(analyzer, function)
8784 && type_owner_of(analyzer, function)
8785 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8786 })
8787 {
8788 candidates.push(function.clone());
8789 }
8790 }
8791 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8792 unanimous_return_binding(analyzer, self, file, &candidates)
8793 }
8794
8795 pub fn resolve_call_return_binding_without_arity(
8796 &self,
8797 analyzer: &CppGraphSource<'_>,
8798 file: &ProjectFile,
8799 raw_name: &str,
8800 lexical_namespace: Option<&str>,
8801 direct_type: Option<&CodeUnit>,
8802 ) -> (bool, Option<CppScanBinding>) {
8803 let Some(normalized) = normalize_reference_name(raw_name) else {
8804 return (false, None);
8805 };
8806 let mut candidates = self
8807 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8808 .into_iter()
8809 .filter(|function| {
8810 function.is_function()
8811 && !direct_type.is_some_and(|direct_type| {
8812 self.callable_is_constructor_declaration(analyzer, function)
8813 && type_owner_of(analyzer, function)
8814 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8815 })
8816 })
8817 .cloned()
8818 .collect::<Vec<_>>();
8819 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8820 let has_candidates = !candidates.is_empty();
8821 (
8822 has_candidates,
8823 unanimous_return_binding(analyzer, self, file, &candidates),
8824 )
8825 }
8826
8827 pub fn visible_identifier_candidates<'b>(
8828 &'b self,
8829 file: &ProjectFile,
8830 identifier: &str,
8831 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8832 self.visible_by_identifier
8833 .get(file)
8834 .and_then(|by_name| by_name.get(identifier))
8835 .into_iter()
8836 .flatten()
8837 }
8838
8839 pub fn type_reference_component_directly_names_target(
8845 &self,
8846 component: &str,
8847 target: &CodeUnit,
8848 ) -> bool {
8849 component == target.identifier()
8850 || self
8851 .cpp_template_metadata
8852 .get(target)
8853 .is_some_and(|metadata| component == metadata.primary_name)
8854 }
8855
8856 pub fn visible_type_reference_component_names_for_target(
8864 &self,
8865 analyzer: &CppGraphSource<'_>,
8866 file: &ProjectFile,
8867 target: &CodeUnit,
8868 ) -> HashSet<String> {
8869 let mut names = HashSet::from_iter([target.identifier().to_string()]);
8870 if let Some(metadata) = self.cpp_template_metadata.get(target) {
8871 names.insert(metadata.primary_name.clone());
8872 }
8873
8874 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8875 for (identifier, candidates) in by_identifier {
8876 if candidates.iter().any(|candidate| {
8877 (candidate.is_class()
8878 && (same_visible_symbol(candidate, target)
8879 || self.compatible_primary_template_redeclarations(candidate, target)))
8880 || (declared_type_alias(analyzer, candidate)
8881 && self.alias_candidate_structurally_reaches_target(
8882 analyzer, file, candidate, target,
8883 ))
8884 }) {
8885 names.insert(identifier.clone());
8886 }
8887 }
8888 }
8889
8890 names
8891 }
8892
8893 pub fn indexed_structural_class_scope(
8894 &self,
8895 file: &ProjectFile,
8896 class: Node<'_>,
8897 source: &str,
8898 ) -> Option<Vec<String>> {
8899 let key = (file.clone(), class.start_byte(), class.end_byte());
8900 if let Some(cached) = self
8901 .indexed_structural_class_scopes
8902 .lock()
8903 .expect("C++ indexed structural-class scope cache poisoned")
8904 .get(&key)
8905 .cloned()
8906 {
8907 return cached;
8908 }
8909 let resolved = (|| {
8910 let name = class.child_by_field_name("name")?;
8911 let identifier = if name.kind() == "template_type" {
8912 node_text(name.child_by_field_name("name")?, source).to_string()
8913 } else {
8914 let mut components = Vec::new();
8915 append_cpp_name_components(name, source, &mut components)?;
8916 components.last()?.clone()
8917 };
8918 let visible = self
8919 .visible_identifier_candidates(file, &identifier)
8920 .cloned()
8921 .collect::<Vec<_>>();
8922 let mut visible = visible;
8923 for candidate in
8924 self.visible_by_file
8925 .get(file)
8926 .into_iter()
8927 .flatten()
8928 .filter(|candidate| {
8929 self.cpp_template_metadata
8930 .get(candidate)
8931 .is_some_and(|metadata| metadata.primary_name == identifier)
8932 })
8933 {
8934 if !visible
8935 .iter()
8936 .any(|existing| same_logical_symbol(existing, candidate))
8937 {
8938 visible.push(candidate.clone());
8939 }
8940 }
8941 let cpp_source = self.cpp_source();
8944 let candidates = visible
8945 .iter()
8946 .filter(|candidate| {
8947 candidate.source() == file
8948 && candidate.is_class()
8949 && !declared_type_alias(&cpp_source, candidate)
8950 && self.cpp.ranges(candidate).iter().any(|range| {
8951 range.start_byte <= class.start_byte()
8952 && class.end_byte() <= range.end_byte
8953 })
8954 })
8955 .collect::<Vec<_>>();
8956 let owner = if name.kind() == "template_type" {
8957 let expected = normalize_cpp_whitespace(node_text(name, source));
8958 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8959 let exact = candidates
8960 .iter()
8961 .copied()
8962 .filter(|candidate| {
8963 candidate
8964 .fq()
8965 .segments()
8966 .iter()
8967 .rev()
8968 .find_map(|&segment| {
8969 let (text, kind) = interner.resolve(segment);
8970 matches!(
8971 kind,
8972 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8973 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8974 )
8975 .then_some(text)
8976 })
8977 .is_some_and(|text| text == expected)
8978 })
8979 .collect::<Vec<_>>();
8980 unique_logical_type_candidate(exact)
8981 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
8982 } else {
8983 unique_logical_type_candidate(candidates)?
8984 };
8985 Some(canonical_cpp_scope_components(&owner))
8986 })();
8987 self.indexed_structural_class_scopes
8988 .lock()
8989 .expect("C++ indexed structural-class scope cache poisoned")
8990 .insert(key, resolved.clone());
8991 resolved
8992 }
8993
8994 pub fn indexed_enclosing_owner_scope(
8995 &self,
8996 analyzer: &CppGraphSource<'_>,
8997 file: &ProjectFile,
8998 node: Node<'_>,
8999 ) -> Option<Vec<String>> {
9000 let anchor = std::iter::successors(Some(node), |current| current.parent())
9001 .find(|current| {
9002 matches!(
9003 current.kind(),
9004 "function_definition"
9005 | "class_specifier"
9006 | "struct_specifier"
9007 | "union_specifier"
9008 )
9009 })
9010 .unwrap_or(node);
9011 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9012 if let Some(cached) = self
9013 .indexed_enclosing_owner_scopes
9014 .lock()
9015 .expect("C++ indexed enclosing-owner scope cache poisoned")
9016 .get(&key)
9017 .cloned()
9018 {
9019 return cached;
9020 }
9021 let resolved = (|| {
9022 let range = Range {
9023 start_byte: node.start_byte(),
9024 end_byte: node.end_byte(),
9025 start_line: node.start_position().row,
9026 end_line: node.end_position().row,
9027 };
9028 let start = analyzer.enclosing_code_unit(file, &range)?;
9029 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9030 start,
9031 |unit| self.cached_precise_parent_of(analyzer, unit),
9032 )
9033 .find(|unit| {
9034 unit.is_class()
9035 && !analyzer
9036 .type_alias_provider()
9037 .is_some_and(|provider| provider.is_type_alias(unit))
9038 })?;
9039 Some(canonical_cpp_scope_components(&owner))
9040 })();
9041 self.indexed_enclosing_owner_scopes
9042 .lock()
9043 .expect("C++ indexed enclosing-owner scope cache poisoned")
9044 .insert(key, resolved.clone());
9045 resolved
9046 }
9047
9048 fn cached_precise_parent_of(
9049 &self,
9050 analyzer: &CppGraphSource<'_>,
9051 code_unit: &CodeUnit,
9052 ) -> Option<CodeUnit> {
9053 if let Some(cached) = self
9054 .precise_parent_cache
9055 .lock()
9056 .expect("C++ precise-parent cache poisoned")
9057 .get(code_unit)
9058 .cloned()
9059 {
9060 return cached;
9061 }
9062 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9063 self.precise_parent_cache
9064 .lock()
9065 .expect("C++ precise-parent cache poisoned")
9066 .insert(code_unit.clone(), resolved.clone());
9067 resolved
9068 }
9069
9070 pub fn callable_is_constructor_declaration(
9071 &self,
9072 analyzer: &CppGraphSource<'_>,
9073 candidate: &CodeUnit,
9074 ) -> bool {
9075 if !candidate.is_function() {
9076 return false;
9077 }
9078 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9079 return false;
9080 };
9081 let root = prepared.tree().root_node();
9082 let candidate_ranges = analyzer.ranges(candidate);
9083 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
9084 let mut current = root
9085 .descendant_for_byte_range(range.start_byte, range.end_byte)
9086 .and_then(|node| node.parent());
9087 while let Some(node) = current {
9088 if matches!(
9089 node.kind(),
9090 "class_specifier" | "struct_specifier" | "union_specifier"
9091 ) {
9092 return node
9093 .child_by_field_name("name")
9094 .map(|name| terminal_name(node_text(name, prepared.source())))
9095 .is_some_and(|name| name == candidate.identifier());
9096 }
9097 current = node.parent();
9098 }
9099 false
9100 });
9101 if enclosed_by_matching_type {
9102 return true;
9103 }
9104 let indexed_containment = analyzer
9105 .declarations(candidate.source())
9106 .into_iter()
9107 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
9108 .any(|owner| {
9109 analyzer.ranges(&owner).iter().any(|owner_range| {
9110 candidate_ranges.iter().any(|candidate_range| {
9111 owner_range.start_byte <= candidate_range.start_byte
9112 && candidate_range.end_byte <= owner_range.end_byte
9113 })
9114 })
9115 });
9116 if indexed_containment {
9117 return true;
9118 }
9119 let metadata = analyzer.signature_metadata(candidate);
9120 !metadata.is_empty()
9121 && metadata
9122 .iter()
9123 .all(|signature| signature.return_type_text().is_none())
9124 }
9125
9126 pub fn callable_is_deduction_guide_declaration(
9134 &self,
9135 analyzer: &CppGraphSource<'_>,
9136 candidate: &CodeUnit,
9137 ) -> bool {
9138 if !candidate.is_function() {
9139 return false;
9140 }
9141 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9142 return false;
9143 };
9144 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
9145 .into_iter()
9146 .any(|declaration| {
9147 if declaration.kind() != "declaration"
9148 || declaration.child_by_field_name("type").is_some()
9149 {
9150 return false;
9151 }
9152 let Some(declarator) = declaration.child_by_field_name("declarator") else {
9153 return false;
9154 };
9155 if declarator.kind() != "function_declarator" {
9156 return false;
9157 }
9158 let mut cursor = declarator.walk();
9159 let has_trailing_return = declarator
9160 .named_children(&mut cursor)
9161 .any(|child| child.kind() == "trailing_return_type");
9162 has_trailing_return
9163 && declarator_name_node(declarator).is_some_and(|name| {
9164 node_text(name, prepared.source()) == candidate.identifier()
9165 })
9166 })
9167 }
9168
9169 pub fn callable_is_template_declaration(
9173 &self,
9174 analyzer: &CppGraphSource<'_>,
9175 candidate: &CodeUnit,
9176 ) -> bool {
9177 if !candidate.is_function() {
9178 return false;
9179 }
9180 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9181 return false;
9182 };
9183 let root = prepared.tree().root_node();
9184 analyzer.ranges(candidate).iter().any(|range| {
9185 let Some(node) = node_for_exact_range(root, range)
9186 .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
9187 else {
9188 return false;
9189 };
9190 node.parent().is_some_and(|parent| {
9191 parent.kind() == "template_declaration"
9192 && parent
9193 .named_child(parent.named_child_count().saturating_sub(1))
9194 .is_some_and(|declaration| same_node(declaration, node))
9195 })
9196 })
9197 }
9198
9199 pub fn type_name_candidates<'b>(
9200 &'b self,
9201 file: &ProjectFile,
9202 normalized: &str,
9203 ) -> Vec<&'b CodeUnit> {
9204 self.candidate_units(file, normalized, TargetKind::Type)
9205 }
9206
9207 pub fn visible_members_for_owner_name<'b>(
9208 &'b self,
9209 file: &ProjectFile,
9210 owner: &CodeUnit,
9211 name: &str,
9212 ) -> Vec<&'b CodeUnit> {
9213 self.visible_identifier_candidates(file, name)
9214 .filter(|unit| {
9215 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9219 .is_some_and(|parent| parent == owner.fq_name())
9220 })
9221 .collect()
9222 }
9223
9224 pub fn visible_member_for_owner_name(
9225 &self,
9226 file: &ProjectFile,
9227 owner: &CodeUnit,
9228 name: &str,
9229 ) -> VisibleMemberResolution {
9230 let candidates = self.visible_members_for_owner_name(file, owner, name);
9231 let mut callables = Vec::new();
9232 let mut non_callable = None;
9233 for candidate in candidates {
9234 if candidate.is_function() {
9235 callables.push(candidate.clone());
9236 } else if non_callable.is_none() {
9237 non_callable = Some(candidate.clone());
9238 }
9239 }
9240 match (callables.is_empty(), non_callable) {
9241 (false, None) => VisibleMemberResolution::Callable(callables),
9242 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9243 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9244 (true, None) => VisibleMemberResolution::Missing,
9245 }
9246 }
9247
9248 fn field_declared_type_fact(
9249 &self,
9250 analyzer: &CppGraphSource<'_>,
9251 field: &CodeUnit,
9252 ) -> Option<DeclaredFieldTypeFact> {
9253 if let Some(cached) = self
9254 .field_type_facts
9255 .lock()
9256 .expect("C++ field type fact cache poisoned")
9257 .get(field)
9258 .cloned()
9259 {
9260 return cached;
9261 }
9262 let decoded = decode_field_declared_type_fact(analyzer, field);
9263 self.field_type_facts
9264 .lock()
9265 .expect("C++ field type fact cache poisoned")
9266 .insert(field.clone(), decoded.clone());
9267 decoded
9268 }
9269
9270 fn structured_alias_target(
9271 &self,
9272 analyzer: &CppGraphSource<'_>,
9273 unit: &CodeUnit,
9274 ) -> Option<StructuredAliasTarget> {
9275 if let Some(cached) = self
9276 .structured_alias_targets
9277 .lock()
9278 .expect("C++ structured alias target cache poisoned")
9279 .get(unit)
9280 .cloned()
9281 {
9282 return cached;
9283 }
9284 let decoded = decode_structured_alias_target(analyzer, unit);
9285 self.structured_alias_targets
9286 .lock()
9287 .expect("C++ structured alias target cache poisoned")
9288 .insert(unit.clone(), decoded.clone());
9289 decoded
9290 }
9291
9292 pub fn type_candidates<'b>(
9293 &'b self,
9294 file: &ProjectFile,
9295 normalized: &str,
9296 ) -> Vec<&'b CodeUnit> {
9297 let mut candidates = self
9298 .candidate_units(file, normalized, TargetKind::Type)
9299 .into_iter()
9300 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
9301 .collect::<Vec<_>>();
9302 dedup_unit_refs(&mut candidates);
9303 candidates
9304 }
9305
9306 pub fn named_candidates_for_normalized<'b>(
9307 &'b self,
9308 file: &ProjectFile,
9309 normalized: &str,
9310 kind: TargetKind,
9311 ) -> Vec<&'b CodeUnit> {
9312 let mut candidates = self
9313 .candidate_units(file, normalized, kind)
9314 .into_iter()
9315 .filter(|unit| {
9316 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9317 })
9318 .collect::<Vec<_>>();
9319 dedup_unit_refs(&mut candidates);
9320 candidates
9321 }
9322
9323 pub fn candidate_units<'b>(
9324 &'b self,
9325 file: &ProjectFile,
9326 normalized: &str,
9327 kind: TargetKind,
9328 ) -> Vec<&'b CodeUnit> {
9329 if normalized.contains("::") {
9330 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9339 brokk_bifrost_core::analyzer::Language::Cpp,
9340 normalized,
9341 )
9342 .pop() else {
9343 return Vec::new();
9344 };
9345 let fqns = cpp_reference_fqn_candidates(normalized, kind);
9346 return self
9347 .visible_identifier_candidates(file, &identifier)
9348 .filter(|unit| {
9349 #[cfg(any(test, feature = "test-support"))]
9350 self.qualified_candidate_inspections
9351 .fetch_add(1, Ordering::Relaxed);
9352 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9353 || canonical_cpp_name_matches(unit, normalized)
9354 })
9355 .collect();
9356 }
9357 self.visible_identifier_candidates(file, normalized)
9358 .collect()
9359 }
9360
9361 #[cfg(any(test, feature = "test-support"))]
9362 pub fn reset_qualified_candidate_inspections(&self) {
9363 self.qualified_candidate_inspections
9364 .store(0, Ordering::Relaxed);
9365 }
9366
9367 #[cfg(any(test, feature = "test-support"))]
9368 pub fn qualified_candidate_inspections(&self) -> usize {
9369 self.qualified_candidate_inspections.load(Ordering::Relaxed)
9370 }
9371
9372 #[cfg(any(test, feature = "test-support"))]
9373 pub fn visibility_identifier_lookup_count(&self) -> usize {
9374 self.visibility_identifier_lookup_count
9375 }
9376
9377 #[cfg(any(test, feature = "test-support"))]
9378 pub fn visibility_identifier_batch_count(&self) -> usize {
9379 self.visibility_identifier_batch_count
9380 }
9381
9382 #[cfg(any(test, feature = "test-support"))]
9383 pub fn reset_target_preserving_type_resolution_count(&self) {
9384 self.target_preserving_type_resolution_count
9385 .store(0, Ordering::Relaxed);
9386 }
9387
9388 #[cfg(any(test, feature = "test-support"))]
9389 pub fn target_preserving_type_resolution_count(&self) -> usize {
9390 self.target_preserving_type_resolution_count
9391 .load(Ordering::Relaxed)
9392 }
9393
9394 #[cfg(any(test, feature = "test-support"))]
9395 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9396 self.visible_parser_alias_name_set_build_count
9397 .load(Ordering::Relaxed)
9398 }
9399}
9400
9401#[derive(Default)]
9402struct IncludeGraph {
9403 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9404}
9405
9406impl IncludeGraph {
9407 fn extend_with<F>(
9408 &mut self,
9409 root: &ProjectFile,
9410 cancellation: Option<&CancellationToken>,
9411 targets_for: &mut F,
9412 ) where
9413 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9414 {
9415 let mut stack = vec![root.clone()];
9416 while let Some(file) = stack.pop() {
9417 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9418 break;
9419 }
9420 if self.targets_by_file.contains_key(&file) {
9421 continue;
9422 }
9423 let targets = targets_for(&file);
9424 stack.extend(targets.iter().cloned());
9425 self.targets_by_file.insert(file, targets);
9426 }
9427 }
9428
9429 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9430 self.targets_by_file.keys()
9431 }
9432
9433 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9434 self.targets_by_file
9435 .get(file)
9436 .map(Vec::as_slice)
9437 .unwrap_or_default()
9438 }
9439
9440 fn reachable_files(
9441 &self,
9442 root: &ProjectFile,
9443 cancellation: Option<&CancellationToken>,
9444 ) -> HashSet<ProjectFile> {
9445 let mut pending = vec![root.clone()];
9446 let mut visited = HashSet::default();
9447 while let Some(file) = pending.pop() {
9448 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9449 break;
9450 }
9451 if visited.insert(file.clone()) {
9452 pending.extend(self.targets(&file).iter().cloned());
9453 }
9454 }
9455 visited
9456 }
9457}
9458
9459fn build_bounded_visible_declarations(
9460 cpp: &dyn CppSource,
9461 token: QueryToken<'_>,
9462 analyzer: &CppGraphSource<'_>,
9463 roots: &HashSet<ProjectFile>,
9464 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9465 cancellation: Option<&CancellationToken>,
9466 stats: &mut BoundedVisibilityStats,
9467) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9468 let mut candidates_by_identifier = HashMap::default();
9469 let mut declarations_by_source_and_reading: HashMap<
9470 (ProjectFile, bool),
9471 BoundedVisibilityDeclarations,
9472 > = HashMap::default();
9473 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9474 roots
9475 .iter()
9476 .map(|root| {
9477 let reading_is_c = analyzer.reference_uses_c_semantics(root);
9478 let root_declarations = declarations_by_source_and_reading
9479 .entry((root.clone(), reading_is_c))
9480 .or_insert_with(|| {
9481 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9482 });
9483 let mut visible = root_declarations
9484 .all
9485 .iter()
9486 .cloned()
9487 .collect::<HashSet<_>>();
9488 let mut pending_names = HashSet::default();
9489 if let Some(prepared) = cpp.prepared_syntax(token, root) {
9490 let mut cursor = prepared.tree().walk();
9495 let mut pending_nodes = vec![prepared.tree().root_node()];
9496 while let Some(node) = pending_nodes.pop() {
9497 if matches!(
9498 node.kind(),
9499 "identifier"
9500 | "type_identifier"
9501 | "field_identifier"
9502 | "namespace_identifier"
9503 ) {
9504 pending_names.insert(node_text(node, prepared.source()).to_string());
9505 }
9506 if node.kind() == "preproc_arg" {
9507 for reference in
9508 object_macro_replacement_type_references(node, prepared.source())
9509 {
9510 pending_names.extend(reference.components);
9511 }
9512 }
9513 pending_nodes.extend(node.named_children(&mut cursor));
9514 }
9515 }
9516 stats.root_names += pending_names.len();
9517 let mut completed_names = HashSet::default();
9518 while !pending_names.is_empty() {
9519 stats.rounds += 1;
9520 let round_names = std::mem::take(&mut pending_names);
9521 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9522 HashMap::default();
9523 let mut identifiers = Vec::new();
9524 for identifier in round_names {
9525 if !completed_names.insert(identifier.clone())
9526 || cancellation.is_some_and(CancellationToken::is_cancelled)
9527 {
9528 continue;
9529 }
9530 identifiers.push(identifier);
9531 }
9532 let missing_identifiers = identifiers
9533 .iter()
9534 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9535 .cloned()
9536 .collect::<HashSet<_>>();
9537 if !missing_identifiers.is_empty() {
9538 let lookup_started = Instant::now();
9539 let mut candidates = cpp
9540 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9541 stats.lookup_elapsed += lookup_started.elapsed();
9542 stats.identifier_lookups += missing_identifiers.len();
9543 stats.identifier_batches += 1;
9544 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9545 break;
9546 }
9547 for identifier in missing_identifiers {
9548 let units = candidates.remove(&identifier).unwrap_or_default();
9549 let candidate_count = units.len();
9550 let candidate_sources = units
9551 .into_iter()
9552 .map(|unit| unit.source().clone())
9553 .collect::<HashSet<_>>();
9554 candidates_by_identifier
9555 .insert(identifier, (candidate_sources, candidate_count));
9556 }
9557 }
9558 for identifier in identifiers {
9559 let (candidate_sources, candidate_count) = candidates_by_identifier
9560 .get(&identifier)
9561 .expect("every missing identifier was inserted after the batch lookup");
9562 stats.candidate_units += *candidate_count;
9563 for source in candidate_sources.iter().cloned() {
9564 if source != *root
9565 && visible_sources
9566 .get(root)
9567 .is_some_and(|files| files.contains(&source))
9568 {
9569 requested_names_by_source
9570 .entry(source)
9571 .or_default()
9572 .insert(identifier.clone());
9573 }
9574 }
9575 }
9576 stats.candidate_sources += requested_names_by_source.len();
9577 for (source, requested_names) in requested_names_by_source {
9578 let declarations = declarations_by_source_and_reading
9579 .entry((source.clone(), reading_is_c))
9580 .or_insert_with(|| {
9581 bounded_visibility_declarations(
9582 cpp,
9583 analyzer,
9584 &source,
9585 reading_is_c,
9586 stats,
9587 )
9588 });
9589 let mut selected = HashSet::default();
9590 for name in requested_names {
9591 if let Some(units) = declarations.by_identifier.get(&name) {
9592 selected.extend(units.iter().cloned());
9593 }
9594 }
9595 for unit in selected {
9596 stats.selected_units += 1;
9597 let dependency_names = dependency_names_by_unit
9598 .entry(unit.clone())
9599 .or_insert_with(|| {
9600 let mut dependency_names = HashSet::default();
9601 if let Some(prepared) = cpp.prepared_syntax(token, &source) {
9602 let ast_started = Instant::now();
9603 let mut cursor = prepared.tree().walk();
9604 for range in analyzer.ranges(&unit) {
9605 let Some(declaration) = node_for_exact_range(
9606 prepared.tree().root_node(),
9607 &range,
9608 ) else {
9609 continue;
9610 };
9611 let mut pending_nodes = vec![declaration];
9612 while let Some(node) = pending_nodes.pop() {
9613 stats.dependency_ast_nodes += 1;
9614 #[cfg(any(test, feature = "test-support"))]
9615 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9616 .with(|count| count.set(count.get() + 1));
9617 if matches!(
9618 node.kind(),
9619 "type_identifier" | "namespace_identifier"
9620 ) {
9621 dependency_names.insert(
9622 node_text(node, prepared.source()).into(),
9623 );
9624 }
9625 pending_nodes.extend(node.named_children(&mut cursor));
9626 }
9627 }
9628 stats.dependency_ast_elapsed += ast_started.elapsed();
9629 }
9630 dependency_names
9631 });
9632 for name in dependency_names.iter() {
9633 if !completed_names.contains(name) && pending_names.insert(name.clone())
9634 {
9635 stats.dependency_names += 1;
9636 }
9637 }
9638 visible.insert(unit);
9639 }
9640 }
9641 }
9642 (root.clone(), visible)
9643 })
9644 .collect()
9645}
9646
9647struct BoundedVisibilityDeclarations {
9648 all: Vec<CodeUnit>,
9649 by_identifier: HashMap<String, Vec<CodeUnit>>,
9650}
9651
9652fn bounded_visibility_declarations(
9653 cpp: &dyn CppSource,
9654 analyzer: &CppGraphSource<'_>,
9655 file: &ProjectFile,
9656 c_semantics: bool,
9657 stats: &mut BoundedVisibilityStats,
9658) -> BoundedVisibilityDeclarations {
9659 let declarations_started = Instant::now();
9660 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9661 .into_iter()
9662 .collect::<Vec<_>>();
9663 stats.declaration_elapsed += declarations_started.elapsed();
9664 stats.declaration_reads += 1;
9665 stats.declaration_units += all.len();
9666
9667 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9668 for unit in &all {
9669 by_identifier
9670 .entry(unit.identifier().to_string())
9671 .or_default()
9672 .push(unit.clone());
9673 if unit.is_class()
9674 && let Some(metadata) = cpp.template_metadata(unit)
9675 && metadata.primary_name != unit.identifier()
9676 {
9677 by_identifier
9678 .entry(metadata.primary_name)
9679 .or_default()
9680 .push(unit.clone());
9681 }
9682 }
9683 BoundedVisibilityDeclarations { all, by_identifier }
9684}
9685
9686#[derive(Default)]
9687struct BoundedVisibilityStats {
9688 rounds: usize,
9689 root_names: usize,
9690 identifier_lookups: usize,
9691 identifier_batches: usize,
9692 candidate_units: usize,
9693 candidate_sources: usize,
9694 declaration_reads: usize,
9695 declaration_units: usize,
9696 selected_units: usize,
9697 dependency_ast_nodes: usize,
9698 dependency_names: usize,
9699 lookup_elapsed: Duration,
9700 declaration_elapsed: Duration,
9701 dependency_ast_elapsed: Duration,
9702}
9703
9704fn bounded_visibility_declarations_in_reading(
9705 analyzer: &CppGraphSource<'_>,
9706 file: &ProjectFile,
9707 c_semantics: bool,
9708) -> BTreeSet<CodeUnit> {
9709 #[cfg(any(test, feature = "test-support"))]
9710 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9711 analyzer.declarations_in_reading(file, c_semantics)
9712}
9713
9714#[cfg(any(test, feature = "test-support"))]
9715pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9716 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9717}
9718
9719#[cfg(any(test, feature = "test-support"))]
9720pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9721 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9722}
9723
9724#[cfg(any(test, feature = "test-support"))]
9725pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9726 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9727}
9728
9729#[cfg(any(test, feature = "test-support"))]
9730pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9731 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9732}
9733
9734pub struct VisibilityData {
9735 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9736 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9737}
9738
9739pub fn build_visibility_data<F, R, D>(
9749 roots: &HashSet<ProjectFile>,
9750 cancellation: Option<&CancellationToken>,
9751 mut targets_for: F,
9752 mut reading_is_c_for: R,
9753 mut declarations_for: D,
9754) -> VisibilityData
9755where
9756 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9757 R: FnMut(&ProjectFile) -> bool,
9758 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9759{
9760 let mut include_graph = IncludeGraph::default();
9761 for file in roots {
9762 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9763 break;
9764 }
9765 include_graph.extend_with(file, cancellation, &mut targets_for);
9766 }
9767 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9768 .files()
9769 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9770 .map(|file| (file.clone(), declarations_for(file, false)))
9771 .collect();
9772 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9773 let mut visible_by_file = HashMap::default();
9774 let mut visible_source_files_by_root = HashMap::default();
9775 for file in roots {
9776 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9777 break;
9778 }
9779 let mut visited = HashSet::default();
9780 let mut visible = HashSet::default();
9781 let declarations_by_file = if reading_is_c_for(file) {
9782 for reached in cpp_declarations_by_file.keys() {
9783 if !c_declarations_by_file.contains_key(reached) {
9784 let declarations = declarations_for(reached, true);
9785 c_declarations_by_file.insert(reached.clone(), declarations);
9786 }
9787 }
9788 &c_declarations_by_file
9789 } else {
9790 &cpp_declarations_by_file
9791 };
9792 collect_visible_declarations(
9793 &include_graph,
9794 declarations_by_file,
9795 file,
9796 &mut visited,
9797 &mut visible,
9798 cancellation,
9799 );
9800 visible_by_file.insert(file.clone(), visible);
9801 visible_source_files_by_root.insert(file.clone(), visited);
9802 }
9803 VisibilityData {
9804 visible_by_file,
9805 visible_source_files_by_root,
9806 }
9807}
9808
9809#[derive(Default)]
9828struct OutOfLineOwnerBindingStats {
9829 unseen_owners: usize,
9830 definition_lookups: usize,
9831 admitted: usize,
9832}
9833
9834fn extend_with_out_of_line_owner_bindings(
9835 cpp: &dyn CppSource,
9836 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9837) -> OutOfLineOwnerBindingStats {
9838 let mut stats = OutOfLineOwnerBindingStats::default();
9839 for (file, visible) in visible_by_file.iter_mut() {
9840 let mut unseen_owners: HashSet<String> = visible
9844 .iter()
9845 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9846 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9847 .collect();
9848 if unseen_owners.is_empty() {
9849 continue;
9850 }
9851 for unit in visible.iter().filter(|unit| unit.is_class()) {
9852 unseen_owners.remove(&unit.fq_name());
9853 }
9854 stats.unseen_owners += unseen_owners.len();
9855 stats.definition_lookups += unseen_owners.len();
9856 let admitted = unseen_owners
9857 .iter()
9858 .flat_map(|owner| cpp.definitions(owner))
9859 .filter(CodeUnit::is_class)
9860 .collect::<Vec<_>>();
9861 stats.admitted += admitted.len();
9862 visible.extend(admitted);
9863 }
9864 stats
9865}
9866
9867pub enum VisibleMemberResolution {
9868 Callable(Vec<CodeUnit>),
9869 NonCallable,
9870 AmbiguousKind,
9871 Missing,
9872}
9873
9874#[derive(Clone)]
9875pub enum EnclosingMemberOwnerResolution {
9876 Owner(CodeUnit),
9877 Ambiguous,
9878 Missing,
9879}
9880
9881pub fn resolve_declaring_member_owner(
9882 analyzer: &CppGraphSource<'_>,
9883 visibility: &VisibilityIndex<'_>,
9884 file: &ProjectFile,
9885 receiver_owner: &CodeUnit,
9886 member_name: &str,
9887) -> EnclosingMemberOwnerResolution {
9888 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9889 return EnclosingMemberOwnerResolution::Missing;
9890 };
9891 let Some(receiver_owner) =
9892 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9893 else {
9894 return EnclosingMemberOwnerResolution::Ambiguous;
9895 };
9896 let resolve_level = |frontier: &[CodeUnit]| {
9897 let mut member_owners = Vec::new();
9898 for raw_owner in frontier {
9899 let Some(owner) =
9900 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9901 else {
9902 return EnclosingMemberOwnerResolution::Ambiguous;
9903 };
9904 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9905 if !member.is_field() && !member.is_function() {
9910 continue;
9911 }
9912 let Some(member_owner) = type_owner_of(analyzer, member) else {
9913 return EnclosingMemberOwnerResolution::Ambiguous;
9914 };
9915 if !member_owners
9916 .iter()
9917 .any(|existing| same_visible_symbol(existing, &member_owner))
9918 {
9919 member_owners.push(member_owner);
9920 }
9921 }
9922 }
9923 match member_owners.len() {
9924 0 => EnclosingMemberOwnerResolution::Missing,
9925 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9926 _ => EnclosingMemberOwnerResolution::Ambiguous,
9927 }
9928 };
9929 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9933 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9934 return direct;
9935 }
9936 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9937 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9938 let mut path_matches = Vec::new();
9939 while let Some(raw_owner) = stack.pop() {
9940 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9941 else {
9942 return EnclosingMemberOwnerResolution::Ambiguous;
9943 };
9944 let propagated = propagated_counts.entry(owner.clone()).or_default();
9948 if *propagated == 2 {
9949 continue;
9950 }
9951 *propagated += 1;
9952 match resolve_level(std::slice::from_ref(&owner)) {
9953 EnclosingMemberOwnerResolution::Owner(owner) => {
9954 path_matches.push(owner);
9955 if path_matches.len() == 2 {
9956 return EnclosingMemberOwnerResolution::Ambiguous;
9957 }
9958 }
9959 EnclosingMemberOwnerResolution::Ambiguous => {
9960 return EnclosingMemberOwnerResolution::Ambiguous;
9961 }
9962 EnclosingMemberOwnerResolution::Missing => {
9963 stack.extend(hierarchy.get_direct_ancestors(&owner));
9964 }
9965 }
9966 }
9967 match path_matches.len() {
9968 0 => EnclosingMemberOwnerResolution::Missing,
9969 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9970 _ => unreachable!("base-path matches are capped at one before returning"),
9971 }
9972}
9973
9974pub fn resolve_declaring_callable_owner(
9989 analyzer: &CppGraphSource<'_>,
9990 visibility: &VisibilityIndex<'_>,
9991 file: &ProjectFile,
9992 ordinary: EnclosingMemberOwnerResolution,
9993 member_name: &str,
9994 call_arity: usize,
9995) -> EnclosingMemberOwnerResolution {
9996 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9997 return ordinary;
9998 };
9999 if visibility
10000 .visible_members_for_owner_name(file, ordinary_owner, member_name)
10001 .into_iter()
10002 .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
10003 {
10004 return ordinary;
10005 }
10006
10007 let mut pending = match member_using_declaration_bases(
10008 analyzer,
10009 visibility,
10010 file,
10011 ordinary_owner,
10012 member_name,
10013 ) {
10014 Ok(bases) => bases,
10015 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10016 };
10017 let mut visited = HashSet::default();
10018 let mut introduced_owners = Vec::new();
10019 while let Some(owner) = pending.pop() {
10020 if !visited.insert(owner.clone()) {
10021 continue;
10022 }
10023 let accepts_arity = visibility
10024 .visible_members_for_owner_name(file, &owner, member_name)
10025 .into_iter()
10026 .any(|unit| {
10027 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
10028 });
10029 if accepts_arity {
10030 if !introduced_owners
10031 .iter()
10032 .any(|existing| same_visible_symbol(existing, &owner))
10033 {
10034 introduced_owners.push(owner);
10035 }
10036 continue;
10037 }
10038 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10039 Ok(bases) => pending.extend(bases),
10040 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10041 }
10042 }
10043 match introduced_owners.as_slice() {
10044 [] => ordinary,
10045 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10046 _ => EnclosingMemberOwnerResolution::Ambiguous,
10047 }
10048}
10049
10050fn member_using_declaration_bases(
10051 analyzer: &CppGraphSource<'_>,
10052 visibility: &VisibilityIndex<'_>,
10053 file: &ProjectFile,
10054 owner: &CodeUnit,
10055 member_name: &str,
10056) -> Result<Vec<CodeUnit>, ()> {
10057 let Some(source) = analyzer.get_source(owner, false) else {
10058 return Ok(Vec::new());
10059 };
10060 let scopes = cpp_member_using_declaration_scopes(&source, member_name);
10061 if scopes.is_empty() {
10062 return Ok(Vec::new());
10063 }
10064 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10065 return Ok(Vec::new());
10066 };
10067 let mut bases = Vec::new();
10068 for raw_ancestor in hierarchy.get_ancestors(owner) {
10069 let Some(ancestor) =
10070 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10071 else {
10072 return Err(());
10073 };
10074 let qualified = cpp_name_for(&ancestor);
10075 if scopes
10076 .iter()
10077 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10078 && !bases
10079 .iter()
10080 .any(|existing| same_visible_symbol(existing, &ancestor))
10081 {
10082 bases.push(ancestor);
10083 }
10084 }
10085 Ok(bases)
10086}
10087
10088pub fn lexical_component_tiers<'a>(
10089 components: &'a [String],
10090 global: bool,
10091 lexical_scope: &'a [String],
10092) -> impl Iterator<Item = Vec<String>> + 'a {
10093 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10094 (0..=first_prefix_len).rev().map(move |prefix_len| {
10095 let mut qualified = Vec::with_capacity(prefix_len + components.len());
10096 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10097 qualified.extend_from_slice(components);
10098 qualified
10099 })
10100}
10101
10102pub fn build_visible_identifier_index(
10103 analyzer: &CppGraphSource<'_>,
10104 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10105 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10106 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10107) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10108 let mut out = HashMap::default();
10109 for (file, visible) in visible_by_file {
10110 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10111 for unit in visible {
10112 if unit.is_field()
10113 && !visible_source_files_by_root
10114 .get(file)
10115 .is_some_and(|sources| sources.contains(unit.source()))
10116 && cpp_global_field_has_internal_linkage_cached(
10117 analyzer,
10118 global_field_internal_linkage,
10119 unit,
10120 )
10121 {
10122 continue;
10123 }
10124 by_identifier
10125 .entry(unit.identifier().to_string())
10126 .or_default()
10127 .push(unit.clone());
10128 }
10129 for units in by_identifier.values_mut() {
10130 sort_lookup_units(units);
10131 units.dedup();
10132 }
10133 out.insert(file.clone(), by_identifier);
10134 }
10135 out
10136}
10137
10138fn sort_lookup_units(units: &mut [CodeUnit]) {
10139 units.sort_by(|left, right| {
10140 left.fq_name()
10141 .cmp(&right.fq_name())
10142 .then_with(|| left.signature().cmp(&right.signature()))
10143 .then_with(|| left.source().cmp(right.source()))
10144 .then_with(|| left.kind().cmp(&right.kind()))
10145 .then_with(|| {
10146 left.package_segment_count()
10147 .cmp(&right.package_segment_count())
10148 })
10149 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10150 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10151 });
10152}
10153
10154fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10155 let interner = segment_interner();
10156 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10157 let (left_text, left_kind) = interner.resolve(left_id);
10158 let (right_text, right_kind) = interner.resolve(right_id);
10159 let order = left_text
10160 .cmp(right_text)
10161 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10162 if order != CmpOrdering::Equal {
10163 return order;
10164 }
10165 }
10166 left.len().cmp(&right.len())
10167}
10168
10169const fn segment_kind_order(kind: SegmentKind) -> u8 {
10170 match kind {
10171 SegmentKind::Path => 0,
10172 SegmentKind::Package => 1,
10173 SegmentKind::Type => 2,
10174 SegmentKind::Companion => 3,
10175 SegmentKind::Nested => 4,
10176 SegmentKind::Member => 5,
10177 SegmentKind::Unknown => 6,
10178 }
10179}
10180
10181fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10182 let mut deduped = Vec::with_capacity(units.len());
10183 for unit in units.drain(..) {
10184 if !deduped.contains(&unit) {
10185 deduped.push(unit);
10186 }
10187 }
10188 *units = deduped;
10189}
10190
10191pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10192 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10196 brokk_bifrost_core::analyzer::Language::Cpp,
10197 reference,
10198 );
10199 if parts.is_empty() {
10200 return Vec::new();
10201 }
10202
10203 let mut candidates = Vec::new();
10204 for package_len in 0..parts.len() {
10205 let package = parts[..package_len].join("::");
10206 let rest = &parts[package_len..];
10207 if rest.is_empty() {
10208 continue;
10209 }
10210 match kind {
10211 TargetKind::Type | TargetKind::Constructor => {
10212 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10213 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10214 }
10215 TargetKind::FreeFunction
10216 | TargetKind::Method
10217 | TargetKind::GlobalField
10218 | TargetKind::MemberField
10219 | TargetKind::Macro => {
10220 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10221 if rest.len() > 1 {
10222 let owner = rest[..rest.len() - 1].join("$");
10223 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10224 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10225 }
10226 }
10227 }
10228 }
10229 candidates
10230}
10231
10232fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10233 let fqn = if package.is_empty() {
10234 short.to_string()
10235 } else {
10236 format!("{package}.{short}")
10237 };
10238 if !out.contains(&fqn) {
10239 out.push(fqn);
10240 }
10241}
10242
10243pub fn infer_cpp_initializer_type(
10244 analyzer: &CppGraphSource<'_>,
10245 visibility: &VisibilityIndex<'_>,
10246 file: &ProjectFile,
10247 source: &str,
10248 node: Node<'_>,
10249) -> Option<CodeUnit> {
10250 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10251 .and_then(|binding| binding.unit)
10252}
10253
10254pub fn infer_cpp_initializer_binding(
10255 analyzer: &CppGraphSource<'_>,
10256 visibility: &VisibilityIndex<'_>,
10257 file: &ProjectFile,
10258 source: &str,
10259 node: Node<'_>,
10260 receiver_resolver: Option<&ReceiverResolver<'_>>,
10261) -> Option<CppScanBinding> {
10262 match node.kind() {
10263 "new_expression" => {
10264 let text = normalize_cpp_whitespace(node_text(node, source));
10265 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
10266 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
10267 let name = normalize_cpp_type_name(type_text);
10268 Some(CppScanBinding::from_type_name(
10269 name.clone(),
10270 visibility.resolve_type(file, &name),
10271 1,
10272 ))
10273 }
10274 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10275 if function.kind() == "field_expression" {
10283 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10284 return resolve_field_method_call_return_binding(
10285 analyzer,
10286 visibility,
10287 file,
10288 source,
10289 function,
10290 arity,
10291 receiver_resolver,
10292 );
10293 }
10294 let function_text = node_text(function, source);
10295 let direct_type_binding = visibility
10296 .resolve_type(file, function_text)
10297 .map(|unit| CppScanBinding::from_unit(unit, 0));
10298 if function.kind() == "template_function" && direct_type_binding.is_some() {
10299 let lexical_namespace = enclosing_namespace_context(node, source);
10300 let arity = visibility.call_arity_evidence(file, node, source).exact();
10301 if let Some(arity) = arity
10302 && let Some(binding) = visibility.resolve_call_return_binding(
10303 analyzer,
10304 file,
10305 function_text,
10306 arity,
10307 lexical_namespace.as_deref(),
10308 direct_type_binding
10309 .as_ref()
10310 .and_then(|binding| binding.unit.as_ref()),
10311 )
10312 {
10313 return Some(binding);
10314 }
10315 let (has_callable, callable_binding) = visibility
10316 .resolve_call_return_binding_without_arity(
10317 analyzer,
10318 file,
10319 function_text,
10320 lexical_namespace.as_deref(),
10321 direct_type_binding
10322 .as_ref()
10323 .and_then(|binding| binding.unit.as_ref()),
10324 );
10325 if let Some(binding) = callable_binding {
10326 return Some(binding);
10327 }
10328 if has_callable {
10329 return None;
10330 }
10331 return direct_type_binding;
10332 }
10333 let arity = visibility.call_arity_evidence(file, node, source).exact();
10338 if let Some(arity) = arity {
10339 let direct_type_binding_for_call = direct_type_binding.clone();
10340 if let Some(binding) = resolve_static_method_call_return_binding(
10341 analyzer, visibility, file, source, function, arity,
10342 )
10343 .or_else(|| {
10344 visibility.resolve_call_return_binding(
10349 analyzer,
10350 file,
10351 function_text,
10352 arity,
10353 enclosing_namespace_context(node, source).as_deref(),
10354 direct_type_binding_for_call
10355 .as_ref()
10356 .and_then(|binding| binding.unit.as_ref()),
10357 )
10358 }) {
10359 return Some(binding);
10360 }
10361 }
10362 direct_type_binding
10363 }),
10364 _ => None,
10365 }
10366}
10367
10368fn resolve_static_method_call_return_binding(
10369 analyzer: &CppGraphSource<'_>,
10370 visibility: &VisibilityIndex<'_>,
10371 file: &ProjectFile,
10372 source: &str,
10373 function: Node<'_>,
10374 arity: usize,
10375) -> Option<CppScanBinding> {
10376 if function.kind() != "qualified_identifier" {
10377 return None;
10378 }
10379 let qualified = normalize_cpp_reference_text(node_text(function, source));
10380 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10387 brokk_bifrost_core::analyzer::Language::Cpp,
10388 &qualified,
10389 );
10390 let (owner_text, member_name) = match parts.split_last() {
10391 Some((member, owner_parts)) if !owner_parts.is_empty() => {
10392 (owner_parts.join("::"), member.clone())
10393 }
10394 _ => {
10395 let scope = function.child_by_field_name("scope")?;
10396 let name = function.child_by_field_name("name")?;
10397 (
10398 node_text(scope, source).to_string(),
10399 node_text(name, source).to_string(),
10400 )
10401 }
10402 };
10403 let owner = visibility.resolve_type(file, &owner_text)?;
10404 let candidates = visibility
10405 .visible_members_for_owner_name(file, &owner, &member_name)
10406 .into_iter()
10407 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
10408 .cloned()
10409 .collect::<Vec<_>>();
10410 unanimous_return_binding(analyzer, visibility, file, &candidates)
10411}
10412
10413fn resolve_field_method_call_return_binding(
10414 analyzer: &CppGraphSource<'_>,
10415 visibility: &VisibilityIndex<'_>,
10416 file: &ProjectFile,
10417 source: &str,
10418 function: Node<'_>,
10419 arity: usize,
10420 receiver_resolver: Option<&ReceiverResolver<'_>>,
10421) -> Option<CppScanBinding> {
10422 debug_assert_eq!(
10423 function.kind(),
10424 "field_expression",
10425 "the member-call return binding answers only for a field-expression callee"
10426 );
10427 let receiver_resolver = receiver_resolver?;
10428 let field = function.child_by_field_name("field")?;
10429 let member_name = node_text(function_terminal_node(field), source);
10430 let receiver = function
10431 .child_by_field_name("argument")
10432 .or_else(|| function.named_child(0))?;
10433 let owners = receiver_resolver(receiver, source);
10434 let mut candidates = Vec::new();
10435 for owner in owners {
10436 let declaring_owner =
10437 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10438 EnclosingMemberOwnerResolution::Owner(owner) => owner,
10439 EnclosingMemberOwnerResolution::Missing => continue,
10440 EnclosingMemberOwnerResolution::Ambiguous => return None,
10441 };
10442 candidates.extend(
10443 visibility
10444 .visible_members_for_owner_name(file, &declaring_owner, member_name)
10445 .into_iter()
10446 .filter(|unit| {
10447 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
10448 })
10449 .cloned(),
10450 );
10451 }
10452 unanimous_return_binding(analyzer, visibility, file, &candidates)
10453}
10454
10455fn unanimous_return_binding(
10456 analyzer: &CppGraphSource<'_>,
10457 visibility: &VisibilityIndex<'_>,
10458 file: &ProjectFile,
10459 candidates: &[CodeUnit],
10460) -> Option<CppScanBinding> {
10461 let mut resolved_return: Option<CppScanBinding> = None;
10462 for function in candidates {
10463 let metadata = analyzer.signature_metadata(function);
10464 let return_types = if metadata.is_empty() {
10465 vec![cpp_function_return_type_text(analyzer, function)?]
10466 } else {
10467 metadata
10468 .iter()
10469 .map(|metadata| metadata.return_type_text().map(str::to_string))
10470 .collect::<Option<Vec<_>>>()?
10471 };
10472 for return_text in return_types {
10473 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10474 let name = normalize_cpp_type_name(&return_text);
10475 let binding = CppScanBinding::from_type_name(
10476 name.clone(),
10477 visibility
10478 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10479 indirection,
10480 );
10481 if let Some(existing) = resolved_return.as_ref()
10482 && (existing.indirection != binding.indirection
10483 || match (&existing.unit, &binding.unit) {
10484 (Some(left), Some(right)) => !same_visible_symbol(left, right),
10485 (None, None) => existing.type_name != binding.type_name,
10486 (Some(_), None) | (None, Some(_)) => true,
10487 })
10488 {
10489 return None;
10490 }
10491 resolved_return = Some(binding);
10492 }
10493 }
10494 resolved_return
10495}
10496
10497fn aliases_from_prepared_source(
10498 cpp: &dyn CppSource,
10499 token: QueryToken<'_>,
10500 file: &ProjectFile,
10501) -> Vec<CppAlias> {
10502 let Some(prepared) = cpp.prepared_syntax(token, file) else {
10503 return Vec::new();
10504 };
10505 let mut aliases = Vec::new();
10506 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
10507 aliases
10508}
10509
10510fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10511 walk_named_tree_preorder(root, true, |node| {
10512 match node.kind() {
10513 "alias_declaration" if alias_has_visible_file_scope(node) => {
10514 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
10515 out.push(alias);
10516 }
10517 }
10518 "type_definition" if alias_has_visible_file_scope(node) => {
10519 collect_typedef_aliases(node, source, out)
10520 }
10521 _ => {}
10522 }
10523 WalkControl::Continue
10524 });
10525}
10526
10527fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10528 let mut current = node.parent();
10529 while let Some(parent) = current {
10530 match parent.kind() {
10531 "translation_unit"
10532 | "namespace_definition"
10533 | "declaration_list"
10534 | "linkage_specification" => current = parent.parent(),
10535 "template_declaration" => current = parent.parent(),
10536 _ => return false,
10537 }
10538 }
10539 true
10540}
10541
10542fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
10543 let name = node
10544 .child_by_field_name("name")
10545 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10546 let target = node
10547 .child_by_field_name("type")
10548 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10549 Some(CppAlias {
10550 name,
10551 target,
10552 namespace: enclosing_namespace_context(node, source),
10553 })
10554}
10555
10556fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10557 let Some(type_node) = node.child_by_field_name("type") else {
10558 return;
10559 };
10560 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
10561 return;
10562 };
10563
10564 let mut cursor = node.walk();
10565 for child in node.named_children(&mut cursor) {
10566 if same_node(child, type_node) {
10567 continue;
10568 }
10569 if let Some(name) = extract_typedef_declarator_name(child, source) {
10570 out.push(CppAlias {
10571 name,
10572 target: target.clone(),
10573 namespace: enclosing_namespace_context(node, source),
10574 });
10575 }
10576 }
10577}
10578
10579fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
10580 match node.kind() {
10581 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
10582 normalize_reference_name(node_text(node, source))
10583 }
10584 _ => node
10585 .child_by_field_name("declarator")
10586 .or_else(|| node.child_by_field_name("name"))
10587 .or_else(|| last_named_child(node))
10588 .and_then(|child| extract_typedef_declarator_name(child, source)),
10589 }
10590}
10591
10592fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
10593 let count = node.named_child_count();
10594 if count == 0 {
10595 None
10596 } else {
10597 node.named_child(count - 1)
10598 }
10599}
10600
10601pub fn collect_include_closure(
10602 analyzer: &CppGraphSource<'_>,
10603 include_targets: &IncludeTargetIndex,
10604 file: &ProjectFile,
10605 out: &mut HashSet<ProjectFile>,
10606 cancellation: Option<&CancellationToken>,
10607) {
10608 let mut stack = vec![file.clone()];
10609 while let Some(file) = stack.pop() {
10610 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10611 break;
10612 }
10613 if !out.insert(file.clone()) {
10614 continue;
10615 }
10616 let imports = analyzer.import_statements(&file);
10617 for include in cpp_include_paths(&imports) {
10618 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10619 stack.push(target);
10620 }
10621 }
10622 }
10623}
10624
10625fn collect_visible_declarations(
10626 include_graph: &IncludeGraph,
10627 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10628 file: &ProjectFile,
10629 visited: &mut HashSet<ProjectFile>,
10630 out: &mut HashSet<CodeUnit>,
10631 cancellation: Option<&CancellationToken>,
10632) {
10633 let mut stack = vec![file.clone()];
10634 while let Some(file) = stack.pop() {
10635 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10636 break;
10637 }
10638 if !visited.insert(file.clone()) {
10639 continue;
10640 }
10641 if let Some(declarations) = declarations_by_file.get(&file) {
10642 out.extend(declarations.iter().cloned());
10643 }
10644 stack.extend(include_graph.targets(&file).iter().cloned());
10645 }
10646}
10647
10648pub fn signature_arity(signature: Option<&str>) -> usize {
10649 let Some(signature) = signature else {
10650 return 0;
10651 };
10652 let inner = signature
10653 .find('(')
10654 .and_then(|open| {
10655 signature[open + 1..]
10656 .find(')')
10657 .map(|close| &signature[open + 1..open + 1 + close])
10658 })
10659 .unwrap_or(signature)
10660 .trim();
10661 if inner.is_empty() || inner == "void" {
10662 return 0;
10663 }
10664 cpp_split_top_level_commas(inner).count()
10665}
10666
10667fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10668 let source = format!("void __bifrost_macro_parameters({replacement});");
10669 let mut parser = Parser::new();
10670 parser
10671 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10672 .ok()?;
10673 let tree = parser.parse(&source, None)?;
10674 let root = tree.root_node();
10675 if root.has_error() {
10676 return None;
10677 }
10678 let declaration = root.named_child(0)?;
10679 let declarator = declaration.child_by_field_name("declarator")?;
10680 let parameters = declarator.child_by_field_name("parameters")?;
10681 let mut required = 0;
10682 let mut total = 0;
10683 let mut repeated = false;
10684 let mut cursor = parameters.walk();
10685 for parameter in parameters.children(&mut cursor) {
10686 match parameter.kind() {
10687 "parameter_declaration" => {
10688 if parameter.child_by_field_name("declarator").is_none()
10689 && parameter
10690 .child_by_field_name("type")
10691 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
10692 {
10693 continue;
10694 }
10695 required += 1;
10696 total += 1;
10697 }
10698 "optional_parameter_declaration" => total += 1,
10699 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
10700 repeated = true;
10701 }
10702 _ => {}
10703 }
10704 }
10705 Some(CallableArity::new(required, total, repeated))
10706}
10707
10708pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
10709 analyzer
10710 .signature_metadata(unit)
10711 .into_iter()
10712 .find_map(|metadata| metadata.callable_arity())
10713 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
10714}
10715
10716pub fn cpp_callable_parameter_types(
10717 analyzer: &CppGraphSource<'_>,
10718 unit: &CodeUnit,
10719) -> Option<Vec<String>> {
10720 analyzer
10721 .signature_metadata(unit)
10722 .into_iter()
10723 .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
10724 .or_else(|| unit.signature().and_then(cpp_signature_param_types))
10725}
10726
10727fn merge_compatible_callable_arities(
10728 left: CallableArity,
10729 right: CallableArity,
10730) -> Option<CallableArity> {
10731 let total = left.total();
10732 let left_repeated = left.accepts(total.saturating_add(1));
10733 let right_repeated = right.accepts(right.total().saturating_add(1));
10734 if total != right.total() || left_repeated != right_repeated {
10735 return None;
10736 }
10737 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10738 Some(CallableArity::new(required, total, left_repeated))
10739}
10740
10741fn find_include_activation(
10742 cpp: &dyn CppSource,
10743 token: QueryToken<'_>,
10744 file: &ProjectFile,
10745 prepared: &PreparedSyntaxTree,
10746 donor_source: &ProjectFile,
10747) -> Option<usize> {
10748 let include_targets = cpp.include_target_index();
10749 let mut direct_includes = Vec::new();
10750 let mut nodes = vec![prepared.tree().root_node()];
10751 let reference = CallableReferenceContext {
10754 file,
10755 position: None,
10756 };
10757 while let Some(node) = nodes.pop() {
10758 if node.kind() == "preproc_include" {
10759 if callable_preprocessor_context_is_visible_for_reference(
10760 node,
10761 prepared.source(),
10762 &reference,
10763 ) {
10764 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10765 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10766 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10767 file,
10768 &include,
10769 include_targets,
10770 )) {
10771 direct_includes.push((node.end_byte(), target));
10772 }
10773 }
10774 }
10775 continue;
10776 }
10777 push_named_children_reversed(node, &mut nodes);
10778 }
10779 direct_includes.sort_by_key(|(activation, _)| *activation);
10780 let mut known_missing = HashSet::default();
10781 direct_includes
10782 .into_iter()
10783 .find(|(_, direct)| {
10784 unconditional_include_reaches(
10785 cpp,
10786 token,
10787 include_targets,
10788 direct,
10789 donor_source,
10790 file,
10791 &mut known_missing,
10792 )
10793 })
10794 .map(|(activation, _)| activation)
10795}
10796
10797fn find_conditional_include_projection_index(
10798 cpp: &dyn CppSource,
10799 token: QueryToken<'_>,
10800 file: &ProjectFile,
10801 prepared: &PreparedSyntaxTree,
10802 on_state: &dyn Fn(),
10803) -> ConditionalIncludeProjectionIndex {
10804 let reference_is_c = reference_uses_c_semantics(cpp, file);
10805 let include_targets = cpp.include_target_index();
10806 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10807 HashMap::default();
10808 let mut pending = Vec::new();
10809 let mut nodes = vec![prepared.tree().root_node()];
10810 while let Some(node) = nodes.pop() {
10811 if node.kind() == "preproc_include" {
10812 let Some(required_guards) =
10813 include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10814 else {
10815 continue;
10816 };
10817 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10818 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10819 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10820 file,
10821 &include,
10822 include_targets,
10823 )) else {
10824 continue;
10825 };
10826 pending.push((target, node.end_byte(), required_guards.clone()));
10827 }
10828 continue;
10829 }
10830 push_named_children_reversed(node, &mut nodes);
10831 }
10832
10833 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10845 HashMap::default();
10846 while let Some((current_file, activation_byte, path)) = pending.pop() {
10847 let guard_sets = expanded
10848 .entry((current_file.clone(), activation_byte))
10849 .or_default();
10850 if guard_sets
10854 .iter()
10855 .any(|existing| existing.is_subset(&path.all))
10856 {
10857 continue;
10858 }
10859 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10860 .drain(..)
10861 .partition(|existing| path.all.is_subset(existing));
10862 *guard_sets = kept;
10863 guard_sets.push(path.all.clone());
10864 if !evicted.is_empty()
10865 && let Some(projections) = projections_by_source.get_mut(¤t_file)
10866 {
10867 projections.retain(|projection| {
10868 projection.activation_byte != activation_byte
10869 || !evicted.contains(&projection.required_guards)
10870 });
10871 }
10872 on_state();
10873
10874 projections_by_source
10877 .entry(current_file.clone())
10878 .or_default()
10879 .push(ConditionalIncludeProjection {
10880 activation_byte,
10881 required_guards: path.all.clone(),
10882 partial_guards: path.partial.clone(),
10883 });
10884
10885 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10886 continue;
10887 };
10888 let mut nodes = vec![current_prepared.tree().root_node()];
10889 while let Some(node) = nodes.pop() {
10890 if node.kind() == "preproc_include" {
10891 let Some(include_guards) = include_directive_guard_requirements(
10892 node,
10893 current_prepared.source(),
10894 reference_is_c,
10895 ) else {
10896 continue;
10897 };
10898 let Some(reached) = path.merged(&include_guards) else {
10899 continue;
10900 };
10901 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
10902 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10903 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10904 ¤t_file,
10905 &include,
10906 include_targets,
10907 )) else {
10908 continue;
10909 };
10910 pending.push((target, activation_byte, reached.clone()));
10911 }
10912 continue;
10913 }
10914 push_named_children_reversed(node, &mut nodes);
10915 }
10916 }
10917
10918 projections_by_source
10919 .into_iter()
10920 .map(|(source, mut projections)| {
10921 projections.sort_by_key(|projection| projection.activation_byte);
10922 (source, Arc::from(projections))
10923 })
10924 .collect()
10925}
10926
10927#[allow(clippy::too_many_arguments)]
10933fn find_conditional_include_projection_for_source(
10934 cpp: &dyn CppSource,
10935 token: QueryToken<'_>,
10936 file: &ProjectFile,
10937 prepared: &PreparedSyntaxTree,
10938 donor_source: &ProjectFile,
10939 admission: IncludePathAdmission,
10940 reference_guards: Option<&HashSet<PreprocessorGuard>>,
10941 reference_byte: usize,
10942 on_state: &dyn Fn(),
10943) -> bool {
10944 let Some(reference_guards) = reference_guards else {
10945 return false;
10946 };
10947 let reference_is_c = reference_uses_c_semantics(cpp, file);
10948 let include_targets = cpp.include_target_index();
10949 let mut pending = Vec::new();
10950 let mut nodes = vec![prepared.tree().root_node()];
10951 while let Some(node) = nodes.pop() {
10952 if node.kind() == "preproc_include" {
10953 let Some(required_guards) =
10954 include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10955 else {
10956 continue;
10957 };
10958 if node.end_byte() > reference_byte
10959 || !admission.admits(
10960 &required_guards.all,
10961 &required_guards.partial,
10962 Some(reference_guards),
10963 )
10964 {
10965 continue;
10966 }
10967 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10968 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10969 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10970 file,
10971 &include,
10972 include_targets,
10973 )) else {
10974 continue;
10975 };
10976 if &target == donor_source {
10977 return true;
10978 }
10979 pending.push((target, required_guards.clone()));
10980 }
10981 continue;
10982 }
10983 push_named_children_reversed(node, &mut nodes);
10984 }
10985
10986 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
10987 while let Some((current_file, path)) = pending.pop() {
10988 let guard_sets = expanded.entry(current_file.clone()).or_default();
10989 if guard_sets.contains(&path.all) {
10990 continue;
10991 }
10992 guard_sets.push(path.all.clone());
10993 on_state();
10994
10995 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10996 continue;
10997 };
10998 let mut nodes = vec![current_prepared.tree().root_node()];
10999 while let Some(node) = nodes.pop() {
11000 if node.kind() == "preproc_include" {
11001 let Some(include_guards) = include_directive_guard_requirements(
11002 node,
11003 current_prepared.source(),
11004 reference_is_c,
11005 ) else {
11006 continue;
11007 };
11008 let Some(reached) = path.merged(&include_guards) else {
11009 continue;
11010 };
11011 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11012 continue;
11013 }
11014 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
11015 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
11016 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11017 ¤t_file,
11018 &include,
11019 include_targets,
11020 )) else {
11021 continue;
11022 };
11023 if &target == donor_source {
11024 return true;
11025 }
11026 pending.push((target, reached.clone()));
11027 }
11028 continue;
11029 }
11030 push_named_children_reversed(node, &mut nodes);
11031 }
11032 }
11033 false
11034}
11035
11036pub fn cpp_include_closure_reaches(
11049 cpp: &dyn CppSource,
11050 token: QueryToken<'_>,
11051 translation_unit: &ProjectFile,
11052 header: &ProjectFile,
11053) -> bool {
11054 unconditional_include_reaches(
11055 cpp,
11056 token,
11057 cpp.include_target_index(),
11058 translation_unit,
11059 header,
11060 translation_unit,
11061 &mut HashSet::default(),
11062 )
11063}
11064
11065fn unconditional_include_reaches(
11066 cpp: &dyn CppSource,
11067 token: QueryToken<'_>,
11068 include_targets: &IncludeTargetIndex,
11069 first: &ProjectFile,
11070 donor_source: &ProjectFile,
11071 reference_file: &ProjectFile,
11072 known_missing: &mut HashSet<ProjectFile>,
11073) -> bool {
11074 if first == donor_source {
11075 return true;
11076 }
11077 if known_missing.contains(first) {
11078 return false;
11079 }
11080 let reference_is_c = reference_file
11081 .rel_path()
11082 .extension()
11083 .and_then(|extension| extension.to_str())
11084 == Some("c");
11085 if let Some(reaches) =
11086 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
11087 {
11088 return reaches;
11089 }
11090 let mut visited = HashSet::default();
11091 let mut files = vec![first.clone()];
11092 let reference = CallableReferenceContext {
11095 file: reference_file,
11096 position: None,
11097 };
11098 while let Some(file) = files.pop() {
11099 if file == *donor_source {
11100 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
11101 return true;
11102 }
11103 if known_missing.contains(&file) || !visited.insert(file.clone()) {
11104 continue;
11105 }
11106 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11107 continue;
11108 };
11109 let mut cursor = prepared.tree().walk();
11111 let mut nodes = vec![prepared.tree().root_node()];
11112 while let Some(node) = nodes.pop() {
11113 if node.kind() == "preproc_include" {
11114 if callable_preprocessor_context_is_visible_for_reference(
11115 node,
11116 prepared.source(),
11117 &reference,
11118 ) {
11119 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
11120 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
11121 if let Some(target) = unique_include_target(
11122 resolve_include_targets_with_index(&file, &include, include_targets),
11123 ) {
11124 files.push(target);
11125 }
11126 }
11127 }
11128 continue;
11129 }
11130 let first_pushed = nodes.len();
11131 nodes.extend(node.named_children(&mut cursor));
11132 nodes[first_pushed..].reverse();
11133 }
11134 }
11135 known_missing.extend(visited);
11136 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
11137 false
11138}
11139
11140fn declaration_guard_requirements(
11141 analyzer: &CppGraphSource<'_>,
11142 cpp: &dyn CppSource,
11143 candidate: &CodeUnit,
11144) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11145 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11146 return Vec::new();
11147 };
11148 let root = prepared.tree().root_node();
11149 analyzer
11150 .ranges(candidate)
11151 .into_iter()
11152 .filter_map(|range| {
11153 root.descendant_for_byte_range(range.start_byte, range.end_byte)
11154 .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
11155 .map(|required| (range.start_byte, required))
11159 })
11160 .collect()
11161}
11162
11163fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11164 analyzer
11165 .ranges(candidate)
11166 .into_iter()
11167 .map(|range| range.start_byte)
11168 .min()
11169}
11170
11171fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11177 let (first, rest) = contexts.split_first()?;
11178 Some(
11179 first
11180 .defined_macros
11181 .iter()
11182 .filter(|name| {
11183 rest.iter()
11184 .all(|context| context.defined_macros.contains(*name))
11185 })
11186 .cloned()
11187 .collect(),
11188 )
11189}
11190
11191pub fn guard_requirements_hold_at_reference(
11192 required: &HashSet<PreprocessorGuard>,
11193 reference: Option<&HashSet<PreprocessorGuard>>,
11194) -> bool {
11195 reference.is_some_and(|active| {
11196 required
11197 .iter()
11198 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11199 })
11200}
11201
11202fn preprocessor_guard_holds_at_reference(
11203 required: &PreprocessorGuard,
11204 active: &HashSet<PreprocessorGuard>,
11205) -> bool {
11206 if active.contains(required) {
11207 return true;
11208 }
11209 let active_expression = BooleanGuardExpression::all(
11210 active
11211 .iter()
11212 .filter_map(PreprocessorGuard::as_boolean_expression),
11213 );
11214 required
11215 .as_boolean_expression()
11216 .is_some_and(|required| active_expression.implies(&required))
11217}
11218
11219fn guards_compatible_at_reference(
11224 declaration: &HashSet<PreprocessorGuard>,
11225 reference: Option<&HashSet<PreprocessorGuard>>,
11226) -> bool {
11227 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11228}
11229
11230pub fn preprocessor_conditional_family_range(
11239 root: Node<'_>,
11240 start_byte: usize,
11241 end_byte: usize,
11242) -> Option<(usize, usize)> {
11243 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11244 let mut ancestor = Some(node);
11245 while let Some(current) = ancestor {
11246 if is_preprocessor_conditional(current)
11247 && preprocessor_conditional_contains_descendant(current, node)
11248 {
11249 let family = preprocessor_conditional_family_root(current);
11250 return Some((family.start_byte(), family.end_byte()));
11251 }
11252 ancestor = current.parent();
11253 }
11254 None
11255}
11256
11257fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
11258 let mut ancestor = node.parent();
11259 while let Some(current) = ancestor {
11260 if is_preprocessor_conditional(current)
11261 && preprocessor_conditional_contains_descendant(current, node)
11262 {
11263 let family = preprocessor_conditional_family_root(current);
11264 if preprocessor_conditional_family_has_terminal_else(family) {
11265 return Some(family);
11266 }
11267 }
11268 ancestor = current.parent();
11269 }
11270 None
11271}
11272
11273fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11274 while let Some(parent) = conditional.parent() {
11275 let is_alternative = parent
11276 .child_by_field_name("alternative")
11277 .is_some_and(|alternative| {
11278 alternative.start_byte() == conditional.start_byte()
11279 && alternative.end_byte() == conditional.end_byte()
11280 });
11281 if !is_alternative {
11282 break;
11283 }
11284 conditional = parent;
11285 }
11286 conditional
11287}
11288
11289fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11290 loop {
11291 let Some(alternative) = conditional.child_by_field_name("alternative") else {
11292 return false;
11293 };
11294 match alternative.kind() {
11295 "preproc_else" => return true,
11296 "preproc_elif" => conditional = alternative,
11297 _ => return false,
11298 }
11299 }
11300}
11301
11302fn include_directive_guard_requirements(
11315 node: Node<'_>,
11316 source: &str,
11317 reference_is_c: bool,
11318) -> Option<PreprocessorGuardEnvironment> {
11319 let required = preprocessor_guard_environment_by_family(node, source)?;
11320 let excluded_by_language = required.all.iter().any(|guard| match guard {
11321 PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11322 PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11323 _ => false,
11324 });
11325 (!excluded_by_language).then_some(required)
11326}
11327
11328pub fn preprocessor_guard_environment(
11329 node: Node<'_>,
11330 source: &str,
11331) -> Option<HashSet<PreprocessorGuard>> {
11332 preprocessor_guard_environment_by_family(node, source).map(|environment| environment.all)
11333}
11334
11335#[derive(Clone)]
11346struct PreprocessorGuardEnvironment {
11347 all: HashSet<PreprocessorGuard>,
11348 partial: HashSet<PreprocessorGuard>,
11349}
11350
11351impl PreprocessorGuardEnvironment {
11352 fn merged(&self, other: &Self) -> Option<Self> {
11356 Some(Self {
11357 all: merge_preprocessor_guards(&self.all, &other.all)?,
11358 partial: self.partial.union(&other.partial).cloned().collect(),
11359 })
11360 }
11361}
11362
11363fn preprocessor_guard_environment_by_family(
11364 node: Node<'_>,
11365 source: &str,
11366) -> Option<PreprocessorGuardEnvironment> {
11367 let mut all = HashSet::default();
11368 let mut partial = HashSet::default();
11369 let mut ancestor = node.parent();
11370 while let Some(conditional) = ancestor {
11371 if matches!(
11372 conditional.kind(),
11373 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11374 ) && !is_file_covering_include_guard(conditional, source)
11375 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11376 && preprocessor_conditional_contains_descendant(conditional, node)
11377 {
11378 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11379 match guard {
11380 PreprocessorGuard::Constant(true) => {
11381 ancestor = conditional.parent();
11382 continue;
11383 }
11384 PreprocessorGuard::Constant(false) => return None,
11385 _ => {}
11386 }
11387 if all.contains(&guard.negated()) {
11388 return None;
11389 }
11390 if !preprocessor_conditional_family_has_terminal_else(
11391 preprocessor_conditional_family_root(conditional),
11392 ) {
11393 partial.insert(guard.clone());
11394 }
11395 all.insert(guard);
11396 }
11397 ancestor = conditional.parent();
11398 }
11399 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11400 match guard {
11401 PreprocessorGuard::Constant(true) => {}
11402 PreprocessorGuard::Constant(false) => return None,
11403 _ => {
11404 if all.contains(&guard.negated()) {
11405 return None;
11406 }
11407 partial.insert(guard.clone());
11411 all.insert(guard);
11412 }
11413 }
11414 }
11415 Some(PreprocessorGuardEnvironment { all, partial })
11416}
11417
11418fn fragmented_statement_preprocessor_guard(
11419 descendant: Node<'_>,
11420 source: &str,
11421) -> Option<PreprocessorGuard> {
11422 let mut ancestor = descendant.parent();
11428 while let Some(statement) = ancestor {
11429 if statement.kind() == "if_statement"
11430 && let (Some(consequence), Some(alternative)) = (
11431 statement.child_by_field_name("consequence"),
11432 statement.child_by_field_name("alternative"),
11433 )
11434 && alternative.start_byte() <= descendant.start_byte()
11435 && descendant.end_byte() <= alternative.end_byte()
11436 {
11437 let mut cursor = consequence.walk();
11438 let openers = consequence
11439 .named_children(&mut cursor)
11440 .filter(|child| {
11441 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11442 && child
11443 .child(child.child_count().saturating_sub(1))
11444 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11445 })
11446 .collect::<Vec<_>>();
11447 if openers.len() != 1 {
11448 ancestor = statement.parent();
11449 continue;
11450 }
11451
11452 let mut terminators = Vec::new();
11453 let mut stack = vec![alternative];
11454 while let Some(node) = stack.pop() {
11455 if node.kind() == "preproc_call"
11456 && node.start_byte() >= descendant.end_byte()
11457 && node
11458 .child_by_field_name("directive")
11459 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11460 {
11461 terminators.push(node);
11462 continue;
11463 }
11464 push_named_children_reversed(node, &mut stack);
11465 }
11466 if terminators.len() == 1 {
11467 return simple_preprocessor_guard(openers[0], source);
11468 }
11469 }
11470 ancestor = statement.parent();
11471 }
11472 None
11473}
11474
11475fn preprocessor_guard_for_descendant(
11476 conditional: Node<'_>,
11477 descendant: Node<'_>,
11478 source: &str,
11479) -> Option<PreprocessorGuard> {
11480 let mut guard = simple_preprocessor_guard(conditional, source)?;
11481 if conditional
11482 .child_by_field_name("alternative")
11483 .is_some_and(|alternative| {
11484 alternative.start_byte() <= descendant.start_byte()
11485 && descendant.end_byte() <= alternative.end_byte()
11486 })
11487 {
11488 let alternative = conditional.child_by_field_name("alternative")?;
11489 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11493 return None;
11494 }
11495 guard = guard.negated();
11496 }
11497 Some(guard)
11498}
11499
11500fn preprocessor_conditional_contains_descendant(
11501 conditional: Node<'_>,
11502 descendant: Node<'_>,
11503) -> bool {
11504 cpp_displaced_preprocessor_boundary(conditional)
11505 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11506}
11507
11508pub fn merge_preprocessor_guards(
11509 left: &HashSet<PreprocessorGuard>,
11510 right: &HashSet<PreprocessorGuard>,
11511) -> Option<HashSet<PreprocessorGuard>> {
11512 let mut merged = left.clone();
11513 for guard in right {
11514 let boolean_negation = guard
11515 .as_boolean_expression()
11516 .map(|expression| expression.negated());
11517 if merged.contains(&guard.negated())
11518 || boolean_negation.is_some_and(|negated| {
11519 merged
11520 .iter()
11521 .filter_map(PreprocessorGuard::as_boolean_expression)
11522 .any(|existing| existing == negated)
11523 })
11524 {
11525 return None;
11526 }
11527 merged.insert(guard.clone());
11528 }
11529 Some(merged)
11530}
11531
11532fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11533 if conditional.kind() == "preproc_ifdef" {
11534 let name = conditional.child_by_field_name("name")?;
11535 let name = node_text(name, source).to_string();
11536 return match conditional.child(0)?.kind() {
11537 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11538 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11539 _ => None,
11540 };
11541 }
11542 let condition = conditional.child_by_field_name("condition")?;
11543 simple_preprocessor_expression_guard(condition, source).or_else(|| {
11544 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11545 node_text(condition, source),
11546 )))
11547 })
11548}
11549
11550fn simple_preprocessor_expression_guard(
11551 expression: Node<'_>,
11552 source: &str,
11553) -> Option<PreprocessorGuard> {
11554 match expression.kind() {
11555 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11556 node_text(expression, source).to_string(),
11557 ))),
11558 "number_literal" => match node_text(expression, source).trim() {
11559 "0" => Some(PreprocessorGuard::Constant(false)),
11560 "1" => Some(PreprocessorGuard::Constant(true)),
11561 _ => None,
11562 },
11563 "preproc_defined" => {
11564 let identifier = (0..expression.named_child_count())
11565 .filter_map(|index| expression.named_child(index))
11566 .find(|child| child.kind() == "identifier")?;
11567 Some(PreprocessorGuard::Defined(
11568 node_text(identifier, source).to_string(),
11569 ))
11570 }
11571 "unary_expression"
11572 if expression
11573 .child_by_field_name("operator")
11574 .is_some_and(|operator| operator.kind() == "!") =>
11575 {
11576 simple_preprocessor_expression_guard(
11577 expression.child_by_field_name("argument")?,
11578 source,
11579 )
11580 .map(|guard| guard.negated())
11581 }
11582 "parenthesized_expression" => (0..expression.named_child_count())
11583 .filter_map(|index| expression.named_child(index))
11584 .next()
11585 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11586 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11587 expression, source,
11588 ))),
11589 _ => None,
11590 }
11591}
11592
11593fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11594 match expression.kind() {
11595 "number_literal" => match node_text(expression, source).trim() {
11596 "0" => BooleanGuardExpression::Constant(false),
11597 "1" => BooleanGuardExpression::Constant(true),
11598 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11599 expression, source,
11600 ))),
11601 },
11602 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11603 "preproc_defined" => {
11604 let identifier = (0..expression.named_child_count())
11605 .filter_map(|index| expression.named_child(index))
11606 .find(|child| child.kind() == "identifier");
11607 identifier.map_or_else(
11608 || {
11609 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11610 expression, source,
11611 )))
11612 },
11613 |identifier| {
11614 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11615 },
11616 )
11617 }
11618 "unary_expression"
11619 if expression
11620 .child_by_field_name("operator")
11621 .is_some_and(|operator| operator.kind() == "!") =>
11622 {
11623 expression.child_by_field_name("argument").map_or_else(
11624 || {
11625 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11626 expression, source,
11627 )))
11628 },
11629 |argument| boolean_preprocessor_expression(argument, source).negated(),
11630 )
11631 }
11632 "parenthesized_expression" => (0..expression.named_child_count())
11633 .filter_map(|index| expression.named_child(index))
11634 .next()
11635 .map_or_else(
11636 || {
11637 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11638 expression, source,
11639 )))
11640 },
11641 |child| boolean_preprocessor_expression(child, source),
11642 ),
11643 "binary_expression" => {
11644 let operands = || {
11645 Some((
11646 boolean_preprocessor_expression(
11647 expression.child_by_field_name("left")?,
11648 source,
11649 ),
11650 boolean_preprocessor_expression(
11651 expression.child_by_field_name("right")?,
11652 source,
11653 ),
11654 ))
11655 };
11656 match expression
11657 .child_by_field_name("operator")
11658 .map(|operator| operator.kind())
11659 {
11660 Some("&&") => operands().map_or_else(
11661 || {
11662 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11663 expression, source,
11664 )))
11665 },
11666 |(left, right)| BooleanGuardExpression::all([left, right]),
11667 ),
11668 Some("||") => operands().map_or_else(
11669 || {
11670 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11671 expression, source,
11672 )))
11673 },
11674 |(left, right)| BooleanGuardExpression::any([left, right]),
11675 ),
11676 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11677 expression, source,
11678 ))),
11679 }
11680 }
11681 _ => {
11682 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
11683 }
11684 }
11685}
11686
11687fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
11688 if targets.len() == 1 {
11689 targets.pop()
11690 } else {
11691 None
11692 }
11693}
11694
11695fn nameable_callable_declaration_nodes<'tree>(
11704 analyzer: &CppGraphSource<'_>,
11705 prepared: &'tree PreparedSyntaxTree,
11706 candidate: &CodeUnit,
11707) -> Vec<Node<'tree>> {
11708 callable_declaration_nodes(analyzer, prepared, candidate)
11709 .into_iter()
11710 .filter(|declaration| {
11711 let mut ancestor = declaration.parent();
11712 while let Some(node) = ancestor {
11713 if node.kind() == "function_definition"
11714 && is_recovered_declaration_scope_container(node, prepared.source())
11715 {
11716 ancestor = node.parent();
11717 continue;
11718 }
11719 if node.kind() == "compound_statement"
11720 && node.parent().is_some_and(|parent| {
11721 is_recovered_declaration_scope_container(parent, prepared.source())
11722 })
11723 {
11724 ancestor = node.parent().and_then(|parent| parent.parent());
11725 continue;
11726 }
11727 if matches!(
11728 node.kind(),
11729 "compound_statement" | "function_definition" | "lambda_expression"
11730 ) {
11731 return false;
11732 }
11733 ancestor = node.parent();
11734 }
11735 true
11736 })
11737 .collect()
11738}
11739
11740fn callable_declaration_nodes<'tree>(
11741 analyzer: &CppGraphSource<'_>,
11742 prepared: &'tree PreparedSyntaxTree,
11743 candidate: &CodeUnit,
11744) -> Vec<Node<'tree>> {
11745 let root = prepared.tree().root_node();
11746 analyzer
11747 .ranges(candidate)
11748 .into_iter()
11749 .filter_map(|range| {
11750 let mut declaration =
11751 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11752 while !matches!(
11765 declaration.kind(),
11766 "declaration" | "field_declaration" | "function_definition"
11767 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
11768 declaration,
11769 prepared.source(),
11770 ) {
11771 let Some(parent) = declaration.parent() else {
11772 break;
11773 };
11774 declaration = parent;
11775 }
11776 Some(declaration)
11777 })
11778 .collect()
11779}
11780
11781fn real_function_definition_ancestor<'tree>(
11782 node: Node<'tree>,
11783 source: &str,
11784) -> Option<Node<'tree>> {
11785 let mut ancestor = node.parent();
11786 while let Some(node) = ancestor {
11787 if node.kind() == "function_definition"
11788 && !is_recovered_declaration_scope_container(node, source)
11789 {
11790 return Some(node);
11791 }
11792 ancestor = node.parent();
11793 }
11794 None
11795}
11796
11797fn callable_declaration_activation_in_file(
11798 analyzer: &CppGraphSource<'_>,
11799 prepared: &PreparedSyntaxTree,
11800 candidate: &CodeUnit,
11801 reference: &CallableReferenceContext<'_>,
11802) -> Option<usize> {
11803 nameable_callable_declaration_nodes(analyzer, prepared, candidate)
11804 .into_iter()
11805 .filter(|declaration| {
11806 callable_preprocessor_context_is_visible_for_reference(
11807 *declaration,
11808 prepared.source(),
11809 reference,
11810 )
11811 })
11812 .map(callable_declaration_activation_byte)
11813 .min()
11814}
11815
11816fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
11821 if declaration.kind() != "function_definition" {
11822 return declaration.end_byte();
11823 }
11824 declaration
11825 .child_by_field_name("declarator")
11826 .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
11827}
11828
11829struct CallableReferenceContext<'a> {
11835 file: &'a ProjectFile,
11836 position: Option<CallableReferencePosition<'a>>,
11837}
11838
11839struct CallableReferencePosition<'a> {
11843 prepared: &'a PreparedSyntaxTree,
11844 byte: usize,
11845 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
11846}
11847
11848impl CallableReferenceContext<'_> {
11849 fn is_c(&self) -> bool {
11850 self.file
11851 .rel_path()
11852 .extension()
11853 .and_then(|extension| extension.to_str())
11854 == Some("c")
11855 }
11856
11857 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
11858 let position = self.position.as_ref()?;
11859 position
11860 .guards
11861 .get_or_init(|| {
11862 position
11863 .prepared
11864 .tree()
11865 .root_node()
11866 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
11867 .and_then(|node| {
11868 preprocessor_guard_environment(node, position.prepared.source())
11869 })
11870 })
11871 .as_ref()
11872 }
11873}
11874
11875fn callable_declaration_guard_requirements(
11887 node: Node<'_>,
11888 source: &str,
11889 reference: &CallableReferenceContext<'_>,
11890) -> Option<HashSet<PreprocessorGuard>> {
11891 let reference_is_c = reference.is_c();
11892 let mut required = HashSet::default();
11893 let mut ancestor = node.parent();
11894 while let Some(conditional) = ancestor {
11895 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11896 && !is_file_covering_include_guard(conditional, source)
11897 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11898 && preprocessor_conditional_contains_descendant(conditional, node)
11899 {
11900 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11901 match guard {
11902 PreprocessorGuard::Constant(true) => {}
11903 PreprocessorGuard::Constant(false) => return None,
11904 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
11905 if reference_is_c {
11906 return None;
11907 }
11908 }
11909 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
11910 if !reference_is_c {
11911 return None;
11912 }
11913 }
11914 guard => {
11915 required.insert(guard);
11916 }
11917 }
11918 }
11919 ancestor = conditional.parent();
11920 }
11921 Some(required)
11922}
11923
11924fn callable_preprocessor_context_is_visible_for_reference(
11928 node: Node<'_>,
11929 source: &str,
11930 reference: &CallableReferenceContext<'_>,
11931) -> bool {
11932 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
11933 return false;
11934 };
11935 required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards())
11936}
11937
11938fn flattened_macro_namespace_declaration_matches(
11939 analyzer: &CppGraphSource<'_>,
11940 cpp: &dyn CppSource,
11941 reference_file: &ProjectFile,
11942 visible_declaration: &CodeUnit,
11943 qualified_candidate: &CodeUnit,
11944 reference_byte: usize,
11945) -> bool {
11946 if visible_declaration.kind() != qualified_candidate.kind()
11952 || visible_declaration.identifier() != qualified_candidate.identifier()
11953 || visible_declaration.signature() != qualified_candidate.signature()
11954 || !visible_declaration.package_name().is_empty()
11955 || qualified_candidate.package_name().is_empty()
11956 {
11957 return false;
11958 }
11959
11960 let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
11961 return false;
11962 };
11963 let root = prepared.tree().root_node();
11964 let closing_brace_limit = if visible_declaration.source() == reference_file {
11965 reference_byte
11966 } else {
11967 usize::MAX
11968 };
11969
11970 analyzer
11971 .ranges(visible_declaration)
11972 .into_iter()
11973 .any(|range| {
11974 let Some(mut declaration) =
11975 root.descendant_for_byte_range(range.start_byte, range.end_byte)
11976 else {
11977 return false;
11978 };
11979 while !matches!(
11980 declaration.kind(),
11981 "declaration" | "field_declaration" | "function_definition"
11982 ) {
11983 let Some(parent) = declaration.parent() else {
11984 return false;
11985 };
11986 declaration = parent;
11987 }
11988 if declaration
11989 .parent()
11990 .is_none_or(|parent| parent.kind() != "translation_unit")
11991 || !macro_displaced_cpp_return_type(declaration, prepared.source())
11992 {
11993 return false;
11994 }
11995
11996 let mut cursor = root.walk();
11997 root.named_children(&mut cursor).any(|sibling| {
11998 sibling.start_byte() >= declaration.end_byte()
11999 && sibling.start_byte() < closing_brace_limit
12000 && direct_unmatched_closing_brace(sibling)
12001 })
12002 })
12003}
12004
12005fn flattened_macro_namespace_components(
12006 declaration: Node<'_>,
12007 source: &str,
12008) -> Option<Vec<String>> {
12009 flattened_macro_function_namespace_components(declaration, source)
12010 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12011}
12012
12013fn flattened_macro_function_namespace_components(
12014 declaration: Node<'_>,
12015 source: &str,
12016) -> Option<Vec<String>> {
12017 let body = declaration
12018 .parent()
12019 .filter(|parent| parent.kind() == "compound_statement")?;
12020 let function = body.parent()?;
12021 if function.child_by_field_name("body") != Some(body) {
12022 return None;
12023 }
12024 let namespace_name = recovered_macro_namespace_name(function, source)?;
12025 let mut components = enclosing_namespace_components(declaration, source)?;
12026 components.push(namespace_name);
12027 Some(components)
12028}
12029
12030fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12041 if function.kind() != "function_definition" || !function.has_error() {
12042 return None;
12043 }
12044 let body = function
12045 .child_by_field_name("body")
12046 .filter(|body| body.kind() == "compound_statement")?;
12047 let mut cursor = function.walk();
12048 let prefix = function
12049 .named_children(&mut cursor)
12050 .take_while(|child| child.start_byte() < body.start_byte())
12051 .filter(|child| child.kind() != "comment")
12052 .collect::<Vec<_>>();
12053 let begin_index = prefix.iter().rposition(|child| {
12054 flattened_macro_sentinel_name(*child, source)
12055 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12056 })?;
12057 let mut identifiers = Vec::new();
12058 let mut stack = prefix[begin_index + 1..]
12059 .iter()
12060 .rev()
12061 .copied()
12062 .collect::<Vec<_>>();
12063 while let Some(current) = stack.pop() {
12064 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12065 identifiers.push(identifier);
12066 continue;
12067 }
12068 let mut cursor = current.walk();
12069 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12070 stack.extend(children.into_iter().rev());
12071 }
12072 let [keyword, namespace_name] = identifiers.as_slice() else {
12073 return None;
12074 };
12075 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12076 {
12077 return None;
12078 }
12079 let mut next = function.next_named_sibling();
12080 let next = loop {
12081 let candidate = next?;
12082 next = candidate.next_named_sibling();
12083 if candidate.kind() != "comment" {
12084 break candidate;
12085 }
12086 };
12087 flattened_macro_sentinel_name(next, source)
12088 .is_some_and(|name| is_namespace_end_sentinel(&name))
12089 .then(|| namespace_name.clone())
12090}
12091
12092fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12097 crate::declarations::is_recovered_exported_class_container(node, source)
12098 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12099 node, source,
12100 )
12101 || recovered_macro_namespace_name(node, source).is_some()
12102}
12103
12104fn flattened_macro_error_namespace_components(
12105 declaration: Node<'_>,
12106 source: &str,
12107) -> Option<Vec<String>> {
12108 let parent = declaration
12109 .parent()
12110 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12111 let mut cursor = parent.walk();
12112 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12113 let declaration_index = siblings
12114 .iter()
12115 .position(|candidate| same_node(*candidate, declaration))?;
12116 let begin_index = (0..declaration_index).rev().find(|index| {
12117 flattened_macro_sentinel_name(siblings[*index], source)
12118 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12119 })?;
12120
12121 let significant = siblings[begin_index + 1..declaration_index]
12122 .iter()
12123 .copied()
12124 .filter(|node| node.kind() != "comment")
12125 .collect::<Vec<_>>();
12126 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12127 return None;
12128 };
12129 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12130 return None;
12131 }
12132 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12133 if significant[2..].iter().any(|node| {
12134 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12135 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12136 })
12137 }) {
12138 return None;
12139 }
12140
12141 let mut saw_namespace_close = false;
12142 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12143 if sibling.kind() == "comment" {
12144 continue;
12145 }
12146 if !saw_namespace_close {
12147 if direct_unmatched_closing_brace(sibling) {
12148 saw_namespace_close = true;
12149 continue;
12150 }
12151 if flattened_macro_sentinel_name(sibling, source).is_some() {
12152 return None;
12153 }
12154 continue;
12155 }
12156 if !flattened_macro_sentinel_name(sibling, source)
12157 .is_some_and(|name| is_namespace_end_sentinel(&name))
12158 {
12159 return None;
12160 }
12161 let mut components = enclosing_namespace_components(declaration, source)?;
12162 components.push(namespace_name);
12163 return Some(components);
12164 }
12165 None
12166}
12167
12168fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12169 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12173 node.named_child(0)?
12174 } else {
12175 node
12176 };
12177 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12178 node.child_by_field_name("type")
12179 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12180 })?;
12181 (cpp_export_macro_token(&candidate)
12182 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12183 .then_some(candidate)
12184}
12185
12186fn is_namespace_begin_sentinel(name: &str) -> bool {
12189 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
12190}
12191
12192fn is_namespace_end_sentinel(name: &str) -> bool {
12193 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
12194}
12195
12196fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12197 if node.kind() != "ERROR" || node.named_child_count() != 1 {
12198 return None;
12199 }
12200 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12201 (!cpp_export_macro_token(&name)).then_some(name)
12202}
12203
12204fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12205 if !matches!(
12206 node.kind(),
12207 "identifier" | "namespace_identifier" | "type_identifier"
12208 ) {
12209 return None;
12210 }
12211 let name = normalize_cpp_whitespace(node_text(node, source));
12212 (!name.is_empty()).then_some(name)
12213}
12214
12215fn guard_requirement_sets_match(
12216 left: &[(usize, HashSet<PreprocessorGuard>)],
12217 right: &[(usize, HashSet<PreprocessorGuard>)],
12218) -> bool {
12219 left.len() == right.len()
12220 && left.iter().all(|(_, left_guards)| {
12221 right
12222 .iter()
12223 .any(|(_, right_guards)| left_guards == right_guards)
12224 })
12225 && right.iter().all(|(_, right_guards)| {
12226 left.iter()
12227 .any(|(_, left_guards)| right_guards == left_guards)
12228 })
12229}
12230
12231fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
12232 let Some(type_node) = declaration.child_by_field_name("type") else {
12233 return false;
12234 };
12235 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
12236 !type_name.is_empty()
12237 && type_name
12238 .chars()
12239 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
12240 && (0..declaration.named_child_count()).any(|index| {
12241 declaration
12242 .named_child(index)
12243 .is_some_and(|child| child.kind() == "ERROR")
12244 })
12245}
12246
12247fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12248 node.kind() == "ERROR"
12249 && (0..node.child_count())
12250 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12251}
12252
12253pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12254 let mut ancestor = node.parent();
12255 while let Some(parent) = ancestor {
12256 if is_preprocessor_conditional(parent)
12257 && !is_file_covering_include_guard(parent, source)
12258 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12259 {
12260 return false;
12261 }
12262 ancestor = parent.parent();
12263 }
12264 true
12265}
12266
12267fn is_split_cpp_language_linkage_wrapper(
12268 conditional: Node<'_>,
12269 descendant: Node<'_>,
12270 source: &str,
12271) -> bool {
12272 if conditional.child_by_field_name("alternative").is_some()
12273 || !matches!(
12274 simple_preprocessor_guard(conditional, source),
12275 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12276 )
12277 {
12278 return false;
12279 }
12280 let mut current = descendant.parent();
12281 let linkage = loop {
12282 let Some(node) = current else {
12283 return false;
12284 };
12285 if node == conditional {
12286 return false;
12287 }
12288 if node.kind() == "linkage_specification" {
12289 break node;
12290 }
12291 current = node.parent();
12292 };
12293 if linkage
12294 .child_by_field_name("value")
12295 .is_none_or(|value| node_text(value, source) != "\"C\"")
12296 {
12297 return false;
12298 }
12299 let Some(body) = linkage.child_by_field_name("body") else {
12300 return false;
12301 };
12302 let closes_opening_branch = (0..body.named_child_count())
12303 .filter_map(|index| body.named_child(index))
12304 .take_while(|child| child.end_byte() <= descendant.start_byte())
12305 .any(|child| {
12306 child.kind() == "preproc_call"
12307 && child
12308 .child_by_field_name("directive")
12309 .is_some_and(|directive| node_text(directive, source) == "#endif")
12310 });
12311 let reopens_for_closing_brace = (0..body.named_child_count())
12312 .filter_map(|index| body.named_child(index))
12313 .skip_while(|child| child.start_byte() < descendant.end_byte())
12314 .any(|child| {
12315 matches!(
12316 simple_preprocessor_guard(child, source),
12317 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12318 ) && (0..child.child_count()).any(|index| {
12319 child
12320 .child(index)
12321 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
12322 })
12323 });
12324 closes_opening_branch && reopens_for_closing_brace
12325}
12326
12327pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12331 node.child_by_field_name("arguments")
12332 .or_else(|| node.child_by_field_name("parameters"))
12333 .or_else(|| node.child_by_field_name("value"))
12334 .or_else(|| first_named_child_of_kind(node, "argument_list"))
12335 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12336}
12337
12338pub fn call_arity(node: Node<'_>) -> usize {
12339 call_arguments_node(node)
12340 .map(|args| argument_children(args).count())
12341 .unwrap_or(0)
12342}
12343
12344pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12345 let recovered_block_arguments = recovered_block_literal_arguments(node);
12346 (0..node.child_count())
12347 .filter_map(move |index| node.child(index))
12348 .filter(|child| child.is_named() && !child.is_extra())
12349 .flat_map(move |child| {
12350 if let Some((raw, left, right)) = recovered_block_arguments
12351 && child == raw
12352 {
12353 [Some(left), Some(right)]
12354 } else {
12355 [Some(child), None]
12356 }
12357 })
12358 .flatten()
12359}
12360
12361pub fn recovered_c_new_expression_arguments(
12370 node: Node<'_>,
12371 uses_c_semantics: bool,
12372) -> Option<[Node<'_>; 2]> {
12373 if !uses_c_semantics || node.kind() != "new_expression" {
12374 return None;
12375 }
12376 let parent = node.parent()?;
12377 if parent.kind() != "argument_list" {
12378 return None;
12379 }
12380 let keyword = node.child(0)?;
12381 let error = node.child(1)?;
12382 let trailing = node.child(2)?;
12383 if node.child(3).is_some()
12384 || keyword.kind() != "new"
12385 || keyword.is_named()
12386 || keyword.child_count() != 0
12387 || error.kind() != "ERROR"
12388 || !error.is_extra()
12389 || error.child_count() != 1
12390 || error.child(0).is_none_or(|comma| comma.kind() != ",")
12391 || node.child_by_field_name("type") != Some(trailing)
12392 || trailing.kind() != "type_identifier"
12393 {
12394 return None;
12395 }
12396 Some([keyword, trailing])
12397}
12398
12399pub fn recovered_c_new_expression_argument_at(
12402 mut node: Node<'_>,
12403 start_byte: usize,
12404 end_byte: usize,
12405 uses_c_semantics: bool,
12406) -> Option<Node<'_>> {
12407 if !uses_c_semantics {
12413 return None;
12414 }
12415 loop {
12416 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12417 return arguments.into_iter().find(|argument| {
12418 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12419 });
12420 }
12421 node = node.parent()?;
12422 }
12423}
12424
12425fn recovered_c_keyword_argument_count(
12426 file: &ProjectFile,
12427 call: Node<'_>,
12428 arguments: Node<'_>,
12429 source: &str,
12430) -> usize {
12431 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12436 return 0;
12437 }
12438 let mut ancestor = Some(call);
12439 let function = loop {
12440 let Some(current) = ancestor else {
12441 return 0;
12442 };
12443 if current.kind() == "function_definition" {
12444 break current;
12445 }
12446 ancestor = current.parent();
12447 };
12448 let Some(parameters) = function
12449 .child_by_field_name("declarator")
12450 .and_then(|declarator| declarator.child_by_field_name("parameters"))
12451 else {
12452 return 0;
12453 };
12454 let displaced_parameter_keywords = (0..parameters.child_count())
12455 .filter_map(|index| parameters.child(index))
12456 .filter(|error| error.kind() == "ERROR")
12457 .filter_map(|error| {
12458 let parameter = error.prev_named_sibling()?;
12459 if parameter.kind() != "parameter_declaration"
12460 || parameter.end_byte() != error.start_byte()
12461 || extract_variable_name(parameter, source).is_some()
12462 {
12463 return None;
12464 }
12465 let mut children = (0..error.child_count())
12466 .filter_map(|index| error.child(index))
12467 .filter(|child| !child.is_extra() && !child.is_missing());
12468 let keyword = children.next()?;
12469 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12470 .then_some(keyword)
12471 })
12472 .collect::<Vec<_>>();
12473 if displaced_parameter_keywords.is_empty() {
12474 return 0;
12475 }
12476
12477 (0..arguments.child_count())
12478 .filter_map(|index| arguments.child(index))
12479 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12480 .filter(|error| {
12481 let mut children = (0..error.child_count())
12482 .filter_map(|index| error.child(index))
12483 .filter(|child| !child.is_extra() && !child.is_missing());
12484 let Some(comma) = children.next() else {
12485 return false;
12486 };
12487 let Some(keyword) = children.next() else {
12488 return false;
12489 };
12490 children.next().is_none()
12491 && comma.kind() == ","
12492 && !keyword.is_named()
12493 && keyword.child_count() == 0
12494 && displaced_parameter_keywords
12495 .iter()
12496 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12497 })
12498 .count()
12499}
12500
12501fn recovered_block_literal_arguments<'tree>(
12502 arguments: Node<'tree>,
12503) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12504 if arguments.kind() != "argument_list" {
12505 return None;
12506 }
12507 let mut raw_arguments = (0..arguments.child_count())
12508 .filter_map(|index| arguments.child(index))
12509 .filter(|child| child.is_named() && !child.is_extra());
12510 let raw = raw_arguments.next()?;
12511 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12512 return None;
12513 }
12514
12515 let left = raw.child_by_field_name("left")?;
12516 if left.is_missing() || left.start_byte() == left.end_byte() {
12517 return None;
12518 }
12519 let right = raw.child_by_field_name("right")?;
12520 if right.kind() != "compound_literal_expression"
12521 || right.is_missing()
12522 || right
12523 .child_by_field_name("type")
12524 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12525 || right
12526 .child_by_field_name("value")
12527 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12528 {
12529 return None;
12530 }
12531 let has_intervening_error = (0..raw.child_count())
12532 .filter_map(|index| raw.child(index))
12533 .any(|child| {
12534 child.kind() == "ERROR"
12535 && !child.is_missing()
12536 && child.start_byte() >= left.end_byte()
12537 && child.end_byte() <= right.start_byte()
12538 });
12539 has_intervening_error.then_some((raw, left, right))
12540}
12541
12542pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12543 match node.kind() {
12544 "new_expression" => node
12545 .child_by_field_name("type")
12546 .or_else(|| node.named_child(0)),
12547 "compound_literal_expression" => node.child_by_field_name("type"),
12548 "call_expression" => node.child_by_field_name("function"),
12549 _ => None,
12550 }
12551}
12552
12553pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12559 if node.kind() != "cast_expression" {
12560 return None;
12561 }
12562 let descriptor = node.child_by_field_name("type")?;
12563 if descriptor.kind() == "type_descriptor" {
12564 descriptor.child_by_field_name("type")
12565 } else {
12566 Some(descriptor)
12567 }
12568}
12569
12570pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
12585 debug_assert_eq!(field.kind(), "field_expression");
12586 let operator = field.child_by_field_name("operator")?;
12587 let mut cursor = field.walk();
12588 let receiver = field
12589 .named_children(&mut cursor)
12590 .filter(|child| child.end_byte() <= operator.start_byte())
12591 .last()?;
12592 if receiver.kind() != "ERROR" {
12593 return Some(receiver);
12594 }
12595 (receiver.named_child_count() == 1)
12596 .then(|| receiver.named_child(0))
12597 .flatten()
12598}
12599
12600pub fn field_initializer_constructs_target(
12601 node: Node<'_>,
12602 ctx: &ScanCtx<'_>,
12603 owner: &CodeUnit,
12604) -> bool {
12605 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
12614 return qualified_base_initializer_constructs_target(node, ctx, owner);
12615 }
12616 let Some(name) = node
12617 .child_by_field_name("name")
12618 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
12619 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
12620 else {
12621 return false;
12622 };
12623 let field_name = node_text(name, ctx.source);
12624 ctx.visibility
12625 .visible_identifier_candidates(ctx.file, field_name)
12626 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
12627 .any(|unit| field_declares_type(unit, ctx, owner))
12628}
12629
12630fn qualified_base_initializer_constructs_target(
12631 node: Node<'_>,
12632 ctx: &ScanCtx<'_>,
12633 owner: &CodeUnit,
12634) -> bool {
12635 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
12636 return false;
12637 };
12638 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
12639 return false;
12640 };
12641 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
12642 return false;
12643 };
12644 let resolves_target = |components: &[String]| {
12645 matches!(
12646 ctx.visibility.resolve_type_components_lexically_for_target(
12647 &ctx.analyzer,
12648 ctx.file,
12649 components,
12650 is_globally_qualified_cpp_name(qualified),
12651 &lexical_scope,
12652 owner,
12653 ),
12654 LexicalTypeResolution::Resolved { unit, .. }
12655 if same_visible_symbol(&unit, owner)
12656 )
12657 };
12658 if resolves_target(&components) {
12659 return true;
12660 }
12661
12662 components
12668 .last()
12669 .is_some_and(|terminal| terminal == owner.identifier())
12670 && resolves_target(&components[..components.len() - 1])
12671}
12672
12673fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12674 unit.signature()
12675 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
12676 || ctx
12677 .analyzer
12678 .get_source(unit, false)
12679 .is_some_and(|declaration| {
12680 field_declaration_type_matches(&declaration, unit, ctx, owner)
12681 })
12682}
12683
12684pub fn field_declared_binding(
12685 analyzer: &CppGraphSource<'_>,
12686 visibility: &VisibilityIndex<'_>,
12687 visible_from: &ProjectFile,
12688 field: &CodeUnit,
12689) -> Option<CppScanBinding> {
12690 let fact = visibility.field_declared_type_fact(analyzer, field)?;
12691 let normalized = normalize_field_type_text(&fact.type_text);
12692 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
12693 analyzer,
12694 visible_from,
12695 field,
12696 &normalized,
12697 );
12698 let resolved = match (resolved, fact.template_arguments.as_deref()) {
12699 (Some(primary), Some(arguments)) => visibility
12700 .resolve_template_arguments(visible_from, primary, arguments)
12701 .ok(),
12702 (resolved, None) => resolved,
12703 (None, Some(_)) => None,
12704 }
12705 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
12706 Some(CppScanBinding::from_type_name(
12707 normalized,
12708 resolved,
12709 fact.indirection,
12710 ))
12711}
12712
12713fn anonymous_aggregate_field_owner(
12720 analyzer: &CppGraphSource<'_>,
12721 visibility: &VisibilityIndex<'_>,
12722 visible_from: &ProjectFile,
12723 field: &CodeUnit,
12724) -> Option<CodeUnit> {
12725 let owner = type_owner_of(analyzer, field)?;
12726 if !owner.is_class() {
12727 return None;
12728 }
12729 let declaration = analyzer.get_source(field, false)?;
12730 let mut parser = Parser::new();
12731 parser
12732 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12733 .ok()?;
12734 let tree = parser.parse(&declaration, None)?;
12735 let mut stack = vec![tree.root_node()];
12736 while let Some(node) = stack.pop() {
12737 if matches!(node.kind(), "declaration" | "field_declaration")
12738 && let Some(type_node) = node
12739 .child_by_field_name("type")
12740 .or_else(|| first_type_child(node))
12741 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12742 && type_node.child_by_field_name("name").is_none()
12743 && declared_name_indirection(node, type_node, field.identifier(), &declaration)
12744 .is_some()
12745 {
12746 let matches = visibility
12747 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12748 .into_iter()
12749 .filter(|child| child.is_class() && child.identifier() == field.identifier())
12750 .collect::<Vec<_>>();
12751 return match matches.as_slice() {
12752 [child] => Some((*child).clone()),
12753 _ => None,
12754 };
12755 }
12756 let mut cursor = node.walk();
12757 stack.extend(node.named_children(&mut cursor));
12758 }
12759 None
12760}
12761
12762pub fn anonymous_aggregate_owner(
12769 analyzer: &CppGraphSource<'_>,
12770 file: &ProjectFile,
12771 node: Node<'_>,
12772) -> Option<CodeUnit> {
12773 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12774 || node.child_by_field_name("name").is_some()
12775 {
12776 return None;
12777 }
12778 let mut candidates = analyzer
12779 .declarations(file)
12780 .into_iter()
12781 .filter(|candidate| {
12782 candidate.is_class()
12783 && analyzer.ranges(candidate).into_iter().any(|range| {
12784 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12785 })
12786 })
12787 .collect::<Vec<_>>();
12788 candidates.sort_by_key(|candidate| candidate.fq_name());
12789 candidates.dedup();
12790 match candidates.as_slice() {
12791 [candidate] => Some(candidate.clone()),
12792 _ => None,
12793 }
12794}
12795
12796fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12798 let Some(first) = candidates.first() else {
12799 return Err(TypeCandidateFailure::Unresolvable);
12800 };
12801 if candidates
12802 .iter()
12803 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12804 {
12805 Ok((*first).clone())
12806 } else {
12807 Err(TypeCandidateFailure::Ambiguous)
12808 }
12809}
12810
12811fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12812 logical_type_candidate(candidates).ok()
12813}
12814
12815fn unique_type_candidate_preserving_alias(
12816 analyzer: &CppGraphSource<'_>,
12817 file: &ProjectFile,
12818 candidates: &[&CodeUnit],
12819) -> Option<CodeUnit> {
12820 let first = *candidates.first()?;
12821 if declared_type_alias(analyzer, first) {
12822 return candidates
12823 .iter()
12824 .all(|candidate| {
12825 declared_type_alias(analyzer, candidate)
12826 && candidate.kind() == first.kind()
12827 && candidate.fq_name() == first.fq_name()
12828 && candidate.source() == first.source()
12829 })
12830 .then(|| first.clone());
12831 }
12832 if analyzer.reference_uses_c_semantics(file)
12833 && first.is_class()
12834 && indexed_c_tag_kind(analyzer, first).is_some()
12835 {
12836 let mut full_source = None;
12837 let mut tag_kind = None;
12838 for candidate in candidates.iter().copied() {
12839 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12840 if tag_kind
12841 .replace(candidate_tag_kind)
12842 .is_some_and(|existing| existing != candidate_tag_kind)
12843 {
12844 return None;
12845 }
12846 if cpp_class_declaration_strength(analyzer, candidate)
12847 == CppClassDeclarationStrength::Full
12848 && full_source
12849 .replace(candidate.source())
12850 .is_some_and(|existing| existing != candidate.source())
12851 {
12852 return None;
12853 }
12854 }
12855 }
12856 candidates
12857 .iter()
12858 .all(|candidate| {
12859 !declared_type_alias(analyzer, candidate)
12860 && candidate.kind() == first.kind()
12861 && candidate.fq_name() == first.fq_name()
12862 })
12863 .then(|| first.clone())
12864}
12865
12866fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
12867 is_type_alias(unit)
12868 || analyzer
12869 .type_alias_provider()
12870 .is_some_and(|provider| provider.is_type_alias(unit))
12871}
12872
12873pub fn field_declared_type_binding(
12874 analyzer: &CppGraphSource<'_>,
12875 visibility: &VisibilityIndex<'_>,
12876 visible_from: &ProjectFile,
12877 field: &CodeUnit,
12878) -> Option<(String, Option<CodeUnit>, i32)> {
12879 let fact = visibility.field_declared_type_fact(analyzer, field)?;
12880 let normalized = normalize_field_type_text(&fact.type_text);
12881 let primary = visibility.resolve_unique_canonical_type_for_declaration(
12882 analyzer,
12883 visible_from,
12884 field,
12885 &normalized,
12886 );
12887 let resolved = match (primary, fact.template_arguments.as_deref()) {
12888 (Some(primary), Some(arguments)) => visibility
12889 .resolve_template_arguments(visible_from, primary, arguments)
12890 .ok(),
12891 (resolved, None) => resolved,
12892 (None, Some(_)) => None,
12893 };
12894 Some((normalized, resolved, fact.indirection))
12895}
12896
12897fn decode_field_declared_type_fact(
12898 analyzer: &CppGraphSource<'_>,
12899 field: &CodeUnit,
12900) -> Option<DeclaredFieldTypeFact> {
12901 let Some(declaration) = analyzer.get_source(field, false) else {
12902 return decode_indexed_field_declared_type_fact(analyzer, field);
12903 };
12904 let mut parser = Parser::new();
12905 parser
12906 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12907 .ok()?;
12908 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
12912 let contextual_tree = parser.parse(&contextual_declaration, None)?;
12913 let mut stack = vec![contextual_tree.root_node()];
12914 while let Some(node) = stack.pop() {
12915 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
12916 && node_text(recovered.name, &contextual_declaration) == field.identifier()
12917 {
12918 return Some(DeclaredFieldTypeFact {
12919 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
12920 indirection: recovered.pointer_depth(),
12921 template_arguments: None,
12922 });
12923 }
12924 if let Some(recovered) =
12925 recovered_function_like_field_declarator(node, &contextual_declaration)
12926 && node_text(recovered.name, &contextual_declaration) == field.identifier()
12927 {
12928 let type_node = node
12929 .child_by_field_name("type")
12930 .or_else(|| first_type_child(node))?;
12931 return Some(DeclaredFieldTypeFact {
12932 type_text: node_text(type_node, &contextual_declaration).to_string(),
12933 indirection: recovered.pointer_depth(),
12934 template_arguments: cpp_template_reference_arguments(
12935 type_node,
12936 &contextual_declaration,
12937 ),
12938 });
12939 }
12940 if let Some(fact) =
12941 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
12942 {
12943 return Some(fact);
12944 }
12945 let mut cursor = node.walk();
12946 stack.extend(node.named_children(&mut cursor));
12947 }
12948 let tree = parser.parse(&declaration, None)?;
12949 let mut stack = vec![tree.root_node()];
12950 while let Some(node) = stack.pop() {
12951 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
12952 {
12953 return Some(fact);
12954 }
12955 let mut cursor = node.walk();
12956 stack.extend(node.named_children(&mut cursor));
12957 }
12958 None
12959}
12960
12961fn decode_indexed_field_declared_type_fact(
12966 analyzer: &CppGraphSource<'_>,
12967 field: &CodeUnit,
12968) -> Option<DeclaredFieldTypeFact> {
12969 let cpp = analyzer.cpp?;
12970 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
12971 let source = prepared.source();
12972 let root = prepared.tree().root_node();
12973 for range in analyzer.ranges(field) {
12974 let end = range.start_byte.saturating_add(1).min(source.len());
12975 let mut current = root.descendant_for_byte_range(range.start_byte, end);
12976 while let Some(node) = current {
12977 if matches!(node.kind(), "declaration" | "field_declaration")
12978 && let Some(fact) =
12979 decode_declared_field_type_node(node, field.identifier(), source)
12980 {
12981 return Some(fact);
12982 }
12983 current = node.parent();
12984 }
12985 }
12986 None
12987}
12988
12989fn decode_declared_field_type_node(
12990 node: Node<'_>,
12991 field_name: &str,
12992 source: &str,
12993) -> Option<DeclaredFieldTypeFact> {
12994 if !matches!(node.kind(), "declaration" | "field_declaration") {
12995 return None;
12996 }
12997 let type_node = node
12998 .child_by_field_name("type")
12999 .or_else(|| first_type_child(node))?;
13000 let indirection = declared_name_indirection(node, type_node, field_name, source)?;
13001 let declared_type = if matches!(
13002 type_node.kind(),
13003 "class_specifier" | "struct_specifier" | "union_specifier"
13004 ) {
13005 type_node.child_by_field_name("name")
13006 } else {
13007 Some(type_node)
13008 };
13009 Some(DeclaredFieldTypeFact {
13010 type_text: declared_type.map_or_else(
13011 || field_name.to_string(),
13012 |declared_type| node_text(declared_type, source).to_string(),
13013 ),
13014 indirection,
13015 template_arguments: declared_type
13016 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13017 })
13018}
13019
13020pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13034 let mut parser = Parser::new();
13035 parser
13036 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13037 .ok()?;
13038 let tree = parser.parse(declaration, None)?;
13039 let mut stack = vec![tree.root_node()];
13040 while let Some(node) = stack.pop() {
13041 let type_node = match node.kind() {
13042 "type_definition" => {
13043 let mut cursor = node.walk();
13044 if node
13045 .children_by_field_name("declarator", &mut cursor)
13046 .any(declarator_names_function_type)
13047 {
13048 return None;
13049 }
13050 node.child_by_field_name("type")?
13051 }
13052 "alias_declaration" => {
13053 let type_node = node.child_by_field_name("type")?;
13054 if type_node
13055 .child_by_field_name("declarator")
13056 .is_some_and(declarator_names_function_type)
13057 {
13058 return None;
13059 }
13060 type_node
13061 }
13062 _ => {
13063 let mut cursor = node.walk();
13064 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13065 stack.extend(children.into_iter().rev());
13066 continue;
13067 }
13068 };
13069 return Some(node_text(type_node, declaration).to_string());
13070 }
13071 None
13072}
13073
13074fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
13083 let mut parser = Parser::new();
13084 if parser
13085 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13086 .is_err()
13087 {
13088 return true;
13089 }
13090 let Some(tree) = parser.parse(declaration, None) else {
13091 return true;
13092 };
13093 let mut stack = vec![tree.root_node()];
13094 while let Some(node) = stack.pop() {
13095 let declarators = match node.kind() {
13096 "type_definition" => {
13097 let mut cursor = node.walk();
13098 node.children_by_field_name("declarator", &mut cursor)
13099 .collect::<Vec<_>>()
13100 }
13101 "alias_declaration" => node
13102 .child_by_field_name("type")
13103 .and_then(|type_node| type_node.child_by_field_name("declarator"))
13104 .into_iter()
13105 .collect::<Vec<_>>(),
13106 _ => {
13107 let mut cursor = node.walk();
13108 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13109 stack.extend(children.into_iter().rev());
13110 continue;
13111 }
13112 };
13113 return declarators.into_iter().any(cpp_declarator_adds_indirection);
13114 }
13115 true
13116}
13117
13118fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13124 let mut current = Some(declarator);
13125 while let Some(node) = current {
13126 match node.kind() {
13127 "function_declarator" | "abstract_function_declarator" => return true,
13128 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13129 current = node.named_child(0);
13130 }
13131 _ => current = node.child_by_field_name("declarator"),
13132 }
13133 }
13134 false
13135}
13136
13137pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
13141 let mut parser = Parser::new();
13142 if parser
13143 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13144 .is_err()
13145 {
13146 return false;
13147 }
13148 let Some(tree) = parser.parse(declaration, None) else {
13149 return false;
13150 };
13151 let mut stack = vec![tree.root_node()];
13152 while let Some(node) = stack.pop() {
13153 if matches!(node.kind(), "declaration" | "field_declaration") {
13154 let mut cursor = node.walk();
13155 if node
13156 .children_by_field_name("declarator", &mut cursor)
13157 .any(|declarator| {
13158 declarator_name_node(declarator).is_some_and(|name| {
13159 node_text(name, declaration) == field_name
13160 && declarator_names_function_type(declarator)
13161 })
13162 })
13163 {
13164 return true;
13165 }
13166 }
13167 let mut cursor = node.walk();
13168 stack.extend(node.named_children(&mut cursor));
13169 }
13170 false
13171}
13172
13173pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
13177 let mut parser = Parser::new();
13178 if parser
13179 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13180 .is_err()
13181 {
13182 return false;
13183 }
13184 let Some(tree) = parser.parse(declaration, None) else {
13185 return false;
13186 };
13187 let mut stack = vec![tree.root_node()];
13188 while let Some(node) = stack.pop() {
13189 match node.kind() {
13190 "type_definition" => {
13191 let mut cursor = node.walk();
13192 if node
13193 .children_by_field_name("declarator", &mut cursor)
13194 .any(|declarator| {
13195 extract_typedef_declarator_name(declarator, declaration)
13196 .is_some_and(|name| name == alias_name)
13197 && declarator_names_function_type(declarator)
13198 })
13199 {
13200 return true;
13201 }
13202 }
13203 "alias_declaration" => {
13204 let names_alias = node
13205 .child_by_field_name("name")
13206 .is_some_and(|name| node_text(name, declaration) == alias_name);
13207 if names_alias
13208 && node
13209 .child_by_field_name("type")
13210 .and_then(|type_node| type_node.child_by_field_name("declarator"))
13211 .is_some_and(declarator_names_function_type)
13212 {
13213 return true;
13214 }
13215 }
13216 _ => {}
13217 }
13218 let mut cursor = node.walk();
13219 stack.extend(node.named_children(&mut cursor));
13220 }
13221 false
13222}
13223
13224fn decode_structured_alias_target(
13225 analyzer: &CppGraphSource<'_>,
13226 unit: &CodeUnit,
13227) -> Option<StructuredAliasTarget> {
13228 analyzer
13229 .get_source(unit, false)
13230 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
13231 .or_else(|| {
13232 let signature = unit.signature()?;
13233 decode_structured_alias_target_source(unit, signature, false)
13234 })
13235}
13236
13237fn decode_structured_alias_target_source(
13238 unit: &CodeUnit,
13239 declaration: &str,
13240 require_top_level: bool,
13241) -> Option<StructuredAliasTarget> {
13242 let mut parser = Parser::new();
13243 parser
13244 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13245 .ok()?;
13246 let tree = parser.parse(declaration, None)?;
13247 let mut stack = vec![tree.root_node()];
13248 while let Some(node) = stack.pop() {
13249 let type_node = match node.kind() {
13250 "type_definition" => {
13251 if require_top_level
13252 && node
13253 .parent()
13254 .is_none_or(|parent| parent.kind() != "translation_unit")
13255 {
13256 let mut cursor = node.walk();
13257 stack.extend(node.named_children(&mut cursor));
13258 continue;
13259 }
13260 let mut declarator_cursor = node.walk();
13261 let declarator = node
13262 .children_by_field_name("declarator", &mut declarator_cursor)
13263 .find(|declarator| {
13264 extract_typedef_declarator_name(*declarator, declaration)
13265 .is_some_and(|name| name == unit.identifier())
13266 })?;
13267 if declarator_names_function_type(declarator) {
13268 return None;
13269 }
13270 node.child_by_field_name("type")?
13271 }
13272 "alias_declaration" => {
13273 if require_top_level
13274 && node
13275 .parent()
13276 .is_none_or(|parent| parent.kind() != "translation_unit")
13277 {
13278 let mut cursor = node.walk();
13279 stack.extend(node.named_children(&mut cursor));
13280 continue;
13281 }
13282 let name = node.child_by_field_name("name")?;
13283 if node_text(name, declaration) != unit.identifier() {
13284 return None;
13285 }
13286 let type_node = node.child_by_field_name("type")?;
13287 if type_node
13288 .child_by_field_name("declarator")
13289 .is_some_and(declarator_names_function_type)
13290 {
13291 return None;
13292 }
13293 type_node
13294 }
13295 _ => {
13296 let mut cursor = node.walk();
13297 stack.extend(node.named_children(&mut cursor));
13298 continue;
13299 }
13300 };
13301 return structured_alias_type_target(type_node, declaration);
13302 }
13303 None
13304}
13305
13306fn structured_alias_type_target(
13307 mut type_node: Node<'_>,
13308 source: &str,
13309) -> Option<StructuredAliasTarget> {
13310 while type_node.kind() == "type_descriptor" {
13311 type_node = type_node.child_by_field_name("type")?;
13312 }
13313 if type_node.kind() == "primitive_type" {
13314 return Some(StructuredAliasTarget::Builtin);
13315 }
13316 if matches!(
13317 type_node.kind(),
13318 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13319 ) {
13320 type_node = type_node.child_by_field_name("name")?;
13321 }
13322 let global = type_node.child_by_field_name("scope").is_none()
13323 && type_node.child(0).is_some_and(|child| child.kind() == "::");
13324 let mut components = Vec::new();
13325 append_structured_type_components(type_node, source, &mut components)?;
13326 let arguments = cpp_template_reference_arguments(type_node, source);
13327 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13328 components,
13329 global,
13330 arguments,
13331 })
13332}
13333
13334fn append_structured_type_components(
13335 node: Node<'_>,
13336 source: &str,
13337 out: &mut Vec<String>,
13338) -> Option<()> {
13339 match node.kind() {
13340 "identifier" | "namespace_identifier" | "type_identifier" => {
13341 out.push(node_text(node, source).to_string());
13342 Some(())
13343 }
13344 "template_type" => {
13345 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13346 }
13347 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13348 if let Some(scope) = node.child_by_field_name("scope") {
13349 append_structured_type_components(scope, source, out)?;
13350 }
13351 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13352 }
13353 _ => None,
13354 }
13355}
13356
13357pub(crate) fn declared_name_indirection(
13358 declaration: Node<'_>,
13359 type_node: Node<'_>,
13360 field_name: &str,
13361 source: &str,
13362) -> Option<i32> {
13363 let mut stack = Vec::new();
13364 let mut cursor = declaration.walk();
13365 stack.extend(
13366 declaration
13367 .named_children(&mut cursor)
13368 .filter(|child| !same_node(*child, type_node)),
13369 );
13370 while let Some(node) = stack.pop() {
13371 if matches!(node.kind(), "identifier" | "field_identifier")
13372 && node_text(node, source) == field_name
13373 {
13374 let mut indirection = 0;
13375 let mut current = node.parent();
13376 while let Some(parent) = current {
13377 if same_node(parent, declaration) {
13378 return Some(indirection);
13379 }
13380 if parent.kind() == "pointer_declarator" {
13381 indirection += 1;
13382 }
13383 current = parent.parent();
13384 }
13385 return None;
13386 }
13387 let mut cursor = node.walk();
13388 stack.extend(node.named_children(&mut cursor));
13389 }
13390 None
13391}
13392
13393fn field_declaration_type_matches(
13394 declaration: &str,
13395 unit: &CodeUnit,
13396 ctx: &ScanCtx<'_>,
13397 owner: &CodeUnit,
13398) -> bool {
13399 ctx.visibility
13400 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
13401 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
13402 let normalized = normalize_field_type_text(type_text);
13403 ctx.visibility
13404 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
13405 || ctx.visibility.resolves_to_type(
13406 &ctx.analyzer,
13407 ctx.file,
13408 normalized.as_str(),
13409 owner,
13410 )
13411 })
13412}
13413
13414fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
13415 let declaration = declaration
13416 .split(['=', ';'])
13417 .next()
13418 .unwrap_or(declaration)
13419 .trim();
13420 let index = declaration.rfind(field_name)?;
13421 let before = &declaration[..index];
13422 let after = &declaration[index + field_name.len()..];
13423 if before.chars().next_back().is_some_and(is_identifier_char)
13424 || after.chars().next().is_some_and(is_identifier_char)
13425 {
13426 return None;
13427 }
13428 Some(before.trim())
13429}
13430
13431fn normalize_field_type_text(type_text: &str) -> String {
13432 const FIELD_SPECIFIERS: [&str; 8] = [
13433 "extern ",
13434 "static ",
13435 "mutable ",
13436 "constexpr ",
13437 "constinit ",
13438 "inline ",
13439 "volatile ",
13440 "const ",
13441 ];
13442
13443 let mut normalized = normalize_type_text(type_text);
13444 loop {
13445 let Some(stripped) = FIELD_SPECIFIERS
13446 .iter()
13447 .find_map(|specifier| normalized.strip_prefix(specifier))
13448 else {
13449 return normalized;
13450 };
13451 normalized = normalize_type_text(stripped);
13452 }
13453}
13454
13455fn is_identifier_char(ch: char) -> bool {
13456 ch == '_' || ch.is_ascii_alphanumeric()
13457}
13458
13459pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13460 let Some(type_node) = node.child_by_field_name("type") else {
13461 return false;
13462 };
13463 ctx.visibility.resolves_to_type(
13464 &ctx.analyzer,
13465 ctx.file,
13466 node_text(type_node, ctx.source),
13467 owner,
13468 )
13469}
13470
13471pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13472 !ctx.analyzer
13473 .declarations(ctx.file)
13474 .into_iter()
13475 .filter(|unit| unit.is_function())
13476 .any(|unit| {
13477 ctx.analyzer.ranges(&unit).iter().any(|range| {
13478 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13479 })
13480 })
13481}
13482
13483pub enum DeclarationConstructorInitializer<'tree> {
13485 Arguments(Node<'tree>),
13488 Expression(Node<'tree>),
13491 Empty,
13493}
13494
13495pub fn declaration_constructor_initializer(
13496 node: Node<'_>,
13497) -> DeclarationConstructorInitializer<'_> {
13498 let mut cursor = node.walk();
13499 for child in node.named_children(&mut cursor) {
13500 if child.kind() == "init_declarator" {
13501 let Some(value) = child
13502 .child_by_field_name("value")
13503 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13504 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13505 else {
13506 return DeclarationConstructorInitializer::Empty;
13507 };
13508 return match value.kind() {
13509 "argument_list" | "initializer_list" => {
13510 DeclarationConstructorInitializer::Arguments(value)
13511 }
13512 "compound_literal_expression" => call_arguments_node(value)
13513 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13514 DeclarationConstructorInitializer::Arguments(arguments)
13515 }),
13516 _ => DeclarationConstructorInitializer::Expression(value),
13517 };
13518 }
13519 if let Some(declarator) = declaration_declarator(node, child) {
13520 return declarator_parameters(declarator)
13521 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13522 DeclarationConstructorInitializer::Arguments(parameters)
13523 });
13524 }
13525 }
13526 DeclarationConstructorInitializer::Empty
13527}
13528
13529pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13530 match declaration_constructor_initializer(node) {
13531 DeclarationConstructorInitializer::Arguments(arguments) => {
13532 argument_children(arguments).count()
13533 }
13534 DeclarationConstructorInitializer::Expression(_) => 1,
13535 DeclarationConstructorInitializer::Empty => 0,
13536 }
13537}
13538
13539fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13543 let mut current = node;
13544 loop {
13545 if let Some(parameters) = current.child_by_field_name("parameters") {
13546 return Some(parameters);
13547 }
13548 current = current.child_by_field_name("declarator")?;
13549 }
13550}
13551
13552pub(super) fn first_named_child_of_kind<'tree>(
13553 node: Node<'tree>,
13554 kind: &str,
13555) -> Option<Node<'tree>> {
13556 let mut cursor = node.walk();
13557 node.named_children(&mut cursor)
13558 .find(|child| child.kind() == kind)
13559}
13560
13561fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13562 let mut stack = vec![root];
13563 while let Some(node) = stack.pop() {
13564 if node.kind() == kind {
13565 return Some(node);
13566 }
13567 push_named_children_reversed(node, &mut stack);
13568 }
13569 None
13570}
13571
13572fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13573 if node.kind() == "identifier" {
13574 return true;
13575 }
13576 if node.kind() == "parenthesized_expression" {
13577 return false;
13578 }
13579 if node.kind() == "call_expression" {
13580 return node
13581 .child_by_field_name("function")
13582 .is_some_and(|function| function.kind() == "identifier");
13583 }
13584 let mut stack = vec![node];
13585 while let Some(descendant) = stack.pop() {
13586 if descendant != node && descendant.kind() == "parenthesized_expression" {
13587 continue;
13588 }
13589 if descendant.kind() == "identifier" {
13590 return true;
13591 }
13592 if descendant.kind() == "call_expression" {
13593 if descendant
13594 .child_by_field_name("function")
13595 .is_some_and(|function| function.kind() == "identifier")
13596 {
13597 return true;
13598 }
13599 continue;
13600 }
13601 push_named_children_reversed(descendant, &mut stack);
13602 }
13603 false
13604}
13605
13606fn macro_expansion_shape_is_safe(
13607 node: Node<'_>,
13608 source: &str,
13609 parameters: &[String],
13610 environment: &MacroEnvironment,
13611) -> bool {
13612 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13613 return true;
13614 }
13615 if node.kind() == "call_expression" {
13616 let Some(function) = node.child_by_field_name("function") else {
13617 return true;
13618 };
13619 if function.kind() != "identifier" {
13620 return true;
13621 }
13622 let function_name = node_text(function, source);
13623 if parameters
13624 .iter()
13625 .any(|parameter| parameter == function_name)
13626 {
13627 return false;
13628 }
13629 if !environment.may_bind(function_name) {
13630 return true;
13631 }
13632 let Some(arguments) = node.child_by_field_name("arguments") else {
13633 return false;
13634 };
13635 return argument_children(arguments).all(|argument| {
13636 if argument.kind() == "identifier"
13637 && parameters
13638 .iter()
13639 .any(|parameter| parameter == node_text(argument, source))
13640 {
13641 return false;
13642 }
13643 macro_expansion_shape_is_safe(argument, source, parameters, environment)
13644 });
13645 }
13646 let mut stack = vec![node];
13647 while let Some(descendant) = stack.pop() {
13648 if descendant != node {
13649 if descendant.kind() == "parenthesized_expression" {
13650 continue;
13651 }
13652 if descendant.kind() == "call_expression" {
13653 let expands = descendant
13654 .child_by_field_name("function")
13655 .filter(|function| function.kind() == "identifier")
13656 .is_some_and(|function| environment.may_bind(node_text(function, source)));
13657 if expands {
13658 return false;
13659 }
13660 continue;
13661 }
13662 }
13663 if descendant.kind() == "identifier" {
13664 let identifier = node_text(descendant, source);
13665 if parameters.iter().any(|parameter| parameter == identifier)
13666 || environment.may_bind(identifier)
13667 {
13668 return false;
13669 }
13670 }
13671 push_named_children_reversed(descendant, &mut stack);
13672 }
13673 true
13674}
13675
13676fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13677 let text = node_text(path, source);
13678 match path.kind() {
13679 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13680 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13681 _ => None,
13682 }
13683}
13684
13685fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13686 let source = prepared.source();
13687 let mut facts = Vec::new();
13688 let mut nodes = vec![prepared.tree().root_node()];
13689 while let Some(node) = nodes.pop() {
13690 if node.kind() == "preproc_include" {
13691 let Some(path) = node
13692 .child_by_field_name("path")
13693 .and_then(|path| structured_include_path(path, source))
13694 .map(str::to_owned)
13695 else {
13696 continue;
13697 };
13698 facts.push(StructuredIncludeFact {
13699 start_byte: node.start_byte(),
13700 end_byte: node.end_byte(),
13701 path,
13702 });
13703 continue;
13704 }
13705 push_named_children_reversed(node, &mut nodes);
13706 }
13707 Arc::from(facts.into_boxed_slice())
13708}
13709
13710fn has_unresolved_include_visible_before_in_prepared(
13711 file: &ProjectFile,
13712 prepared: &PreparedSyntaxTree,
13713 include_targets: &IncludeTargetIndex,
13714 facts: &[StructuredIncludeFact],
13715 before_byte: usize,
13716) -> bool {
13717 let guards = OnceCell::new();
13718 let reference = CallableReferenceContext {
13719 file,
13720 position: Some(CallableReferencePosition {
13721 prepared,
13722 byte: before_byte,
13723 guards: &guards,
13724 }),
13725 };
13726 let root = prepared.tree().root_node();
13727 facts
13728 .iter()
13729 .filter(|fact| fact.end_byte <= before_byte)
13730 .any(|fact| {
13731 let node = root
13732 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13733 .expect("structured include fact range must be in prepared tree");
13734 assert_eq!(
13735 node.kind(),
13736 "preproc_include",
13737 "structured include fact range must identify its include node"
13738 );
13739 callable_preprocessor_context_is_visible_for_reference(
13740 node,
13741 prepared.source(),
13742 &reference,
13743 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13744 })
13745}
13746
13747fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13748 let descendant = node;
13749 while let Some(parent) = node.parent() {
13750 if is_preprocessor_conditional(parent)
13751 && !is_file_covering_include_guard(parent, source)
13752 && preprocessor_conditional_contains_descendant(parent, descendant)
13753 {
13754 return true;
13755 }
13756 node = parent;
13757 }
13758 false
13759}
13760
13761fn owning_preprocessor_conditionals(
13773 root: Node<'_>,
13774 event: Node<'_>,
13775 source: &str,
13776) -> OwningPreprocessorConditionals {
13777 if !has_preprocessor_conditional_ancestor(event, source) {
13778 return OwningPreprocessorConditionals::default();
13779 }
13780 let start = event.start_byte();
13781 let descendant = root
13782 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13783 .expect("a byte inside the parsed tree names a descendant");
13784 let mut owners = Vec::new();
13785 let mut current = descendant.parent();
13786 while let Some(conditional) = current {
13787 if is_preprocessor_conditional(conditional)
13788 && !is_file_covering_include_guard(conditional, source)
13789 && preprocessor_conditional_contains_descendant(conditional, descendant)
13790 {
13791 owners.push(conditional.start_byte());
13792 }
13793 current = conditional.parent();
13794 }
13795 owners.into_boxed_slice()
13796}
13797
13798fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13799 matches!(
13800 node.kind(),
13801 "preproc_if"
13802 | "preproc_ifdef"
13803 | "preproc_ifndef"
13804 | "preproc_elif"
13805 | "preproc_elifdef"
13806 | "preproc_else"
13807 )
13808}
13809
13810fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13811 node.parent()
13812 .filter(|parent| parent.kind() == "translation_unit")
13813 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13814 && is_canonical_include_guard(node, source)
13815}
13816
13817fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13818 if node.kind() != "preproc_ifdef"
13819 || node
13820 .child(0)
13821 .is_none_or(|directive| directive.kind() != "#ifndef")
13822 || node.child_by_field_name("alternative").is_some()
13823 {
13824 return false;
13825 }
13826 let Some(guard_name) = node.child_by_field_name("name") else {
13827 return false;
13828 };
13829 let mut cursor = node.walk();
13830 node.named_children(&mut cursor)
13831 .find(|child| *child != guard_name && child.kind() != "comment")
13832 .filter(|child| child.kind() == "preproc_def")
13833 .and_then(|definition| definition.child_by_field_name("name"))
13834 .is_some_and(|defined_name| {
13835 node_text(defined_name, source) == node_text(guard_name, source)
13836 })
13837}
13838
13839fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
13840 let mut guard = None;
13841 for child in named_children_iter(root) {
13842 if child.kind() == "comment" || is_pragma_once(child, source) {
13843 continue;
13844 }
13845 if guard.is_none() && is_canonical_include_guard(child, source) {
13846 guard = Some(child);
13847 } else {
13848 return None;
13849 }
13850 }
13851 guard
13852 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
13853 .map(|name| node_text(name, source).to_string())
13854}
13855
13856fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
13857 if (0..root.named_child_count())
13858 .filter_map(|index| root.named_child(index))
13859 .any(|child| is_pragma_once(child, source))
13860 {
13861 return MacroIncludeProtection::PragmaOnce;
13862 }
13863 top_level_canonical_include_guard_name(root, source)
13864 .map(MacroIncludeProtection::MacroGuard)
13865 .unwrap_or(MacroIncludeProtection::None)
13866}
13867
13868fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
13869 node.kind() == "preproc_call"
13870 && node
13871 .child_by_field_name("directive")
13872 .is_some_and(|directive| node_text(directive, source) == "#pragma")
13873 && node
13874 .child_by_field_name("argument")
13875 .is_some_and(|argument| node_text(argument, source).trim() == "once")
13876}
13877
13878fn parse_preproc_identifier(argument: &str) -> Option<String> {
13879 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
13880 let mut parser = Parser::new();
13881 parser
13882 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13883 .ok()?;
13884 let tree = parser.parse(&sentinel, None)?;
13885 if tree.root_node().has_error() {
13886 return None;
13887 }
13888 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
13889 let identifier = statement.named_child(0)?;
13890 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
13891 .then(|| node_text(identifier, &sentinel).to_string())
13892}
13893
13894pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
13895 match node.kind() {
13896 "identifier" | "field_identifier" => {
13897 let name = node_text(node, source).trim();
13898 (!name.is_empty()).then(|| name.to_string())
13899 }
13900 "abstract_array_declarator"
13901 | "abstract_function_declarator"
13902 | "abstract_parenthesized_declarator"
13903 | "abstract_pointer_declarator"
13904 | "abstract_reference_declarator" => None,
13905 "function_declarator" => node
13906 .child_by_field_name("declarator")
13907 .or_else(|| node.child_by_field_name("name"))
13908 .and_then(|child| extract_variable_name(child, source)),
13909 _ => node
13910 .child_by_field_name("declarator")
13911 .or_else(|| node.child_by_field_name("name"))
13912 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
13913 .and_then(|child| extract_variable_name(child, source)),
13914 }
13915}
13916
13917pub fn is_c_source_file(file: &ProjectFile) -> bool {
13928 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
13929}
13930
13931pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
13938 if !is_c_source_file(file) || node.kind() != "identifier" {
13939 return false;
13940 }
13941 let mut operand = node;
13942 while let Some(parent) = operand.parent().filter(|parent| {
13943 parent.kind() == "parenthesized_expression"
13944 && parent.named_child_count() == 1
13945 && parent.named_child(0) == Some(operand)
13946 }) {
13947 operand = parent;
13948 }
13949 operand.parent().is_some_and(|parent| {
13950 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
13951 })
13952}
13953
13954pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
13963 if node.kind() != "field_identifier" {
13964 return None;
13965 }
13966 let expression = node.parent().filter(|parent| {
13967 parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
13968 })?;
13969 if expression.has_error() {
13970 return None;
13971 }
13972 let closing = expression.child(expression.child_count().saturating_sub(1))?;
13973 if closing.kind() != ")" || closing.is_missing() {
13974 return None;
13975 }
13976 let type_descriptor = expression.child_by_field_name("type")?;
13977 if type_descriptor.kind() != "type_descriptor"
13978 || type_descriptor.is_missing()
13979 || type_descriptor.has_error()
13980 {
13981 return None;
13982 }
13983 let type_specifier = type_descriptor.child_by_field_name("type")?;
13984 if type_specifier.is_missing() || type_specifier.has_error() {
13985 return None;
13986 }
13987 let type_reference = match type_specifier.kind() {
13988 "class_specifier" | "struct_specifier" | "union_specifier" => {
13989 type_specifier.child_by_field_name("name")?
13990 }
13991 _ => type_specifier,
13992 };
13993 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
13994}
13995
13996pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14001 node.kind() == "field_identifier"
14002 && node.parent().is_some_and(|parent| {
14003 parent.kind() == "offsetof_expression"
14004 && parent.child_by_field_name("member") == Some(node)
14005 })
14006}
14007
14008pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14024 if node.kind() != "type_identifier" {
14025 return false;
14026 }
14027 let Some(descriptor) = node
14028 .parent()
14029 .filter(|parent| parent.kind() == "type_descriptor")
14030 else {
14031 return false;
14032 };
14033 if descriptor.child_by_field_name("type") != Some(node) {
14034 return false;
14035 }
14036 let Some(arguments) = descriptor
14037 .parent()
14038 .filter(|parent| parent.kind() == "template_argument_list")
14039 else {
14040 return false;
14041 };
14042 arguments.parent().is_some_and(|owner| {
14043 matches!(
14044 owner.kind(),
14045 "template_type" | "template_function" | "template_method"
14046 ) && owner.child_by_field_name("arguments") == Some(arguments)
14047 })
14048}
14049
14050pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14063 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14064}
14065
14066pub fn is_declarator_node(node: Node<'_>) -> bool {
14067 matches!(
14068 node.kind(),
14069 "identifier"
14070 | "field_identifier"
14071 | "qualified_identifier"
14072 | "scoped_identifier"
14073 | "pointer_declarator"
14074 | "reference_declarator"
14075 | "array_declarator"
14076 | "parenthesized_declarator"
14077 | "function_declarator"
14078 )
14079}
14080
14081pub fn declaration_declarator<'tree>(
14090 declaration: Node<'tree>,
14091 child: Node<'tree>,
14092) -> Option<Node<'tree>> {
14093 if !matches!(
14094 declaration.kind(),
14095 "declaration"
14096 | "field_declaration"
14097 | "parameter_declaration"
14098 | "optional_parameter_declaration"
14099 | "function_definition"
14100 | "type_definition"
14101 | "alias_declaration"
14102 | "template_instantiation"
14103 ) {
14104 return None;
14105 }
14106 if declaration
14107 .child_by_field_name("type")
14108 .is_some_and(|type_node| same_node(type_node, child))
14109 {
14110 return None;
14111 }
14112 if child.kind() == "init_declarator" {
14113 return child.child_by_field_name("declarator");
14114 }
14115 let field = field_name_in_parent(declaration, child);
14116 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14117 || (declaration.kind() == "type_definition"
14118 && child.kind() == "type_identifier"
14119 && matches!(field, Some("declarator") | None))
14120 {
14121 Some(child)
14122 } else {
14123 None
14124 }
14125}
14126
14127#[derive(Clone, Debug, PartialEq, Eq)]
14130pub struct RecoveredNamespaceRegion {
14131 pub start: usize,
14133 pub end: usize,
14135 pub components: Vec<String>,
14138}
14139
14140#[derive(Clone, Debug, Default)]
14161pub struct OrphanedNamespaceScopeIndex {
14162 regions: Vec<RecoveredNamespaceRegion>,
14163 brace_closes: HashMap<usize, Range>,
14164}
14165
14166impl OrphanedNamespaceScopeIndex {
14167 pub fn build(root: Node<'_>, source: &str) -> Self {
14168 if !root.has_error() {
14169 return Self::default();
14170 }
14171 struct Frame<'tree> {
14172 node: Node<'tree>,
14173 children: Vec<Node<'tree>>,
14174 next: usize,
14175 parsed_scope: Vec<String>,
14176 run: Option<RecoveredNamespaceRegion>,
14177 }
14178 fn frame<'tree>(
14179 node: Node<'tree>,
14180 mut parsed_scope: Vec<String>,
14181 source: &str,
14182 ) -> Frame<'tree> {
14183 if node.kind() == "namespace_definition"
14184 && let Some(name) = node.child_by_field_name("name")
14185 {
14186 let mut components = Vec::new();
14187 if append_cpp_name_components(name, source, &mut components).is_some() {
14188 parsed_scope.extend(components);
14189 }
14190 }
14191 let mut cursor = node.walk();
14192 Frame {
14193 node,
14194 children: node.children(&mut cursor).collect(),
14195 next: 0,
14196 parsed_scope,
14197 run: None,
14198 }
14199 }
14200 let mut regions = Vec::new();
14201 let mut brace_closes = HashMap::default();
14202 let mut open = Vec::new();
14206 let mut lexical_scope = Vec::new();
14207 let mut frames = vec![frame(root, Vec::new(), source)];
14208 while !frames.is_empty() {
14213 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14214 let current = frames.last_mut().expect("frames is non-empty");
14215 if current.next == current.children.len() {
14216 regions.extend(frames.pop().expect("the frame just borrowed").run);
14217 continue;
14218 }
14219 let child = current.children[current.next];
14220 current.next += 1;
14221 match child.kind() {
14222 "{" if !child.is_missing() => {
14223 regions.extend(current.run.take());
14224 let mut components = parent_node
14225 .map(|parent| namespace_body_name_components(parent, current.node, source))
14226 .unwrap_or_default();
14227 if components.is_empty() {
14228 components = recovered_namespace_open_components(
14229 ¤t.children[..current.next - 1],
14230 source,
14231 );
14232 }
14233 open.push((child.start_byte(), lexical_scope.len()));
14234 lexical_scope.extend(components);
14235 continue;
14236 }
14237 "}" if !child.is_missing() => {
14238 regions.extend(current.run.take());
14239 if let Some((start, namespace_len)) = open.pop() {
14240 lexical_scope.truncate(namespace_len);
14241 brace_closes.insert(
14242 start,
14243 Range {
14244 start_byte: child.start_byte(),
14245 end_byte: child.end_byte(),
14246 start_line: child.start_position().row + 1,
14247 end_line: child.end_position().row + 1,
14248 },
14249 );
14250 }
14251 continue;
14252 }
14253 _ => {}
14254 }
14255 if current.node.kind() != "namespace_definition"
14259 && lexical_scope != current.parsed_scope
14260 {
14261 match &mut current.run {
14262 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14263 run => {
14264 regions.extend(run.take());
14265 *run = Some(RecoveredNamespaceRegion {
14266 start: child.start_byte(),
14267 end: child.end_byte(),
14268 components: lexical_scope.clone(),
14269 });
14270 }
14271 }
14272 } else {
14273 regions.extend(current.run.take());
14274 }
14275 if child.has_error() {
14279 let parsed_scope = current.parsed_scope.clone();
14280 frames.push(frame(child, parsed_scope, source));
14281 }
14282 }
14283 Self {
14284 regions,
14285 brace_closes,
14286 }
14287 }
14288
14289 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14292 self.brace_closes.get(&open).copied()
14293 }
14294
14295 pub fn is_empty(&self) -> bool {
14296 self.regions.is_empty()
14297 }
14298
14299 pub fn approximate_size(&self) -> usize {
14301 self.regions.iter().fold(
14302 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14303 |total, region| {
14304 total
14305 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14306 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14307 },
14308 )
14309 }
14310
14311 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14313 self.regions
14314 .iter()
14315 .filter(|region| region.start <= byte && byte < region.end)
14316 .min_by_key(|region| region.end - region.start)
14317 }
14318
14319 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14323 let mut parsed = Vec::new();
14324 let mut current = node.parent();
14325 while let Some(parent) = current {
14326 if parent.kind() == "namespace_definition"
14327 && let Some(name) = parent.child_by_field_name("name")
14328 {
14329 let mut components = Vec::new();
14330 if append_cpp_name_components(name, source, &mut components).is_some() {
14331 parsed.push((parent.start_byte(), components));
14332 }
14333 }
14334 current = parent.parent();
14335 }
14336 parsed.reverse();
14337 self.restore_enclosing_namespaces(parsed, node.start_byte())
14338 }
14339
14340 pub fn restore_enclosing_namespaces(
14346 &self,
14347 parsed: Vec<(usize, Vec<String>)>,
14348 node_start: usize,
14349 ) -> Vec<String> {
14350 let Some(region) = self.region_at(node_start) else {
14351 return parsed
14352 .into_iter()
14353 .flat_map(|(_, components)| components)
14354 .collect();
14355 };
14356 region
14357 .components
14358 .iter()
14359 .cloned()
14360 .chain(
14361 parsed
14362 .into_iter()
14363 .filter(|(start, _)| *start >= region.start)
14364 .flat_map(|(_, components)| components),
14365 )
14366 .collect()
14367 }
14368}
14369
14370fn recovered_namespace_open_components(preceding: &[Node<'_>], source: &str) -> Vec<String> {
14386 let mut head = Vec::new();
14387 for &sibling in preceding.iter().rev() {
14388 if sibling.kind() != "comment" {
14389 head.push(sibling);
14390 if head.len() == 2 {
14391 break;
14392 }
14393 }
14394 }
14395 let [name, keyword] = head[..] else {
14396 return Vec::new();
14397 };
14398 if keyword.kind() != "namespace" {
14399 return Vec::new();
14400 }
14401 let mut components = Vec::new();
14402 if append_cpp_name_components(name, source, &mut components).is_none() {
14403 components.clear();
14404 }
14405 components
14406}
14407
14408fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14411 let mut components = Vec::new();
14412 if body.kind() == "declaration_list"
14413 && parent.kind() == "namespace_definition"
14414 && parent.child_by_field_name("body") == Some(body)
14415 && let Some(name) = parent.child_by_field_name("name")
14416 && append_cpp_name_components(name, source, &mut components).is_none()
14417 {
14418 components.clear();
14419 }
14420 components
14421}
14422
14423#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14424pub enum RecoveredDeclaratorTypeContext {
14425 Declaration,
14426 FunctionDefinition,
14427 Parameter,
14428}
14429
14430pub fn recovered_macro_decorated_declarator_type(
14445 node: Node<'_>,
14446) -> Option<RecoveredDeclaratorTypeContext> {
14447 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14448}
14449
14450pub fn recovered_macro_decorated_type_node(
14455 node: Node<'_>,
14456) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14457 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14458 return None;
14459 }
14460 let qualified = node.parent()?;
14461 if qualified.kind() != "qualified_identifier"
14462 || qualified.child_by_field_name("scope") != Some(node)
14463 || !(0..qualified.child_count())
14464 .filter_map(|index| qualified.child(index))
14465 .any(|child| child.kind() == "::" && child.is_missing())
14466 {
14467 return None;
14468 }
14469 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14470 return None;
14471 }
14472
14473 let (declaration, context) = recovered_declarator_container(qualified)?;
14474 let type_node = declaration
14475 .child_by_field_name("type")
14476 .filter(|type_node| {
14477 *type_node != qualified
14478 && !type_node.is_missing()
14479 && type_node.start_byte() != type_node.end_byte()
14480 })?;
14481 Some((type_node, context))
14482}
14483
14484fn recovered_declarator_container(
14485 mut declarator: Node<'_>,
14486) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14487 loop {
14488 let parent = declarator.parent()?;
14489 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14490 return Some((
14491 parent
14492 .parent()
14493 .filter(|declaration| declaration.kind() == "declaration")?,
14494 RecoveredDeclaratorTypeContext::Declaration,
14495 ));
14496 }
14497 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14498 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14499 }
14500 if parent.kind() == "function_definition"
14501 && has_field_child(parent, "declarator", declarator)
14502 {
14503 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14504 }
14505 if matches!(
14511 parent.kind(),
14512 "parameter_declaration" | "optional_parameter_declaration"
14513 ) && has_field_child(parent, "declarator", declarator)
14514 {
14515 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14516 }
14517 if !matches!(
14518 parent.kind(),
14519 "array_declarator"
14520 | "function_declarator"
14521 | "parenthesized_declarator"
14522 | "pointer_declarator"
14523 | "pointer_type_declarator"
14524 | "reference_declarator"
14525 ) || !has_field_child(parent, "declarator", declarator)
14526 {
14527 return None;
14528 }
14529 declarator = parent;
14530 }
14531}
14532
14533fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14534 let mut cursor = parent.walk();
14535 parent
14536 .children_by_field_name(field, &mut cursor)
14537 .any(|child| child == target)
14538}
14539
14540fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14541 loop {
14542 if node.is_missing() || node.start_byte() == node.end_byte() {
14543 return false;
14544 }
14545 match node.kind() {
14546 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14547 return true;
14548 }
14549 "array_declarator"
14550 | "function_declarator"
14551 | "parenthesized_declarator"
14552 | "pointer_declarator"
14553 | "pointer_type_declarator"
14554 | "reference_declarator" => {
14555 let Some(declarator) = node.child_by_field_name("declarator") else {
14556 return false;
14557 };
14558 node = declarator;
14559 }
14560 _ => return false,
14561 }
14562 }
14563}
14564
14565pub enum DesignatedInitializerOwner {
14567 Resolved(CodeUnit),
14568 Unresolved,
14569}
14570
14571enum InitializerOwnerStep {
14572 Field(String),
14573 AggregateWrapper,
14574}
14575
14576pub fn designated_initializer_owner(
14586 analyzer: &CppGraphSource<'_>,
14587 visibility: &VisibilityIndex<'_>,
14588 file: &ProjectFile,
14589 source: &str,
14590 node: Node<'_>,
14591) -> Option<DesignatedInitializerOwner> {
14592 if let Some(designator) = node
14593 .parent()
14594 .filter(|parent| parent.kind() == "field_designator")
14595 {
14596 let pair = designator.parent()?;
14597 if pair.kind() != "initializer_pair" {
14598 return None;
14599 }
14600 let mut cursor = pair.walk();
14601 let designators = pair
14602 .children_by_field_name("designator", &mut cursor)
14603 .collect::<Vec<_>>();
14604 let position = designators
14605 .iter()
14606 .position(|candidate| same_node(*candidate, designator))?;
14607 let initializer = pair.parent()?;
14608 if initializer.kind() != "initializer_list" {
14609 return None;
14610 }
14611 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14612 for prior in &designators[..position] {
14613 let field = prior
14614 .child_by_field_name("field")
14615 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14616 owner = owner.and_then(|owner| {
14617 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14618 });
14619 }
14620 return Some(classified_designated_owner(owner));
14621 }
14622
14623 let init_declarator = node.parent()?;
14624 if init_declarator.child_by_field_name("declarator") != Some(node)
14625 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14626 {
14627 return None;
14628 }
14629 Some(classified_designated_owner(declaration_owner(
14630 analyzer,
14631 visibility,
14632 file,
14633 source,
14634 init_declarator.parent()?,
14635 )))
14636}
14637
14638fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14639 owner.map_or(
14640 DesignatedInitializerOwner::Unresolved,
14641 DesignatedInitializerOwner::Resolved,
14642 )
14643}
14644
14645fn initializer_list_owner(
14646 analyzer: &CppGraphSource<'_>,
14647 visibility: &VisibilityIndex<'_>,
14648 file: &ProjectFile,
14649 source: &str,
14650 initializer: Node<'_>,
14651) -> Option<CodeUnit> {
14652 let mut current = initializer;
14653 let mut steps = Vec::new();
14654 loop {
14655 let parent = current.parent()?;
14656 match parent.kind() {
14657 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14658 let designator = parent.child_by_field_name("designator")?;
14659 let step = designator
14660 .child_by_field_name("field")
14661 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14662 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14663 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14664 steps.push(step);
14665 current = parent.parent()?;
14666 }
14667 "initializer_list" => {
14668 current = parent;
14669 }
14670 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14671 let declaration = parent.parent()?;
14672 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14673 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14674 }
14675 "compound_literal_expression"
14676 if parent.child_by_field_name("value") == Some(current) =>
14677 {
14678 let type_node = parent.child_by_field_name("type")?;
14679 let owner =
14680 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
14681 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14682 }
14683 "ERROR" => current = parent,
14684 _ => return None,
14685 }
14686 }
14687}
14688
14689fn apply_initializer_owner_steps(
14690 analyzer: &CppGraphSource<'_>,
14691 visibility: &VisibilityIndex<'_>,
14692 file: &ProjectFile,
14693 mut owner: CodeUnit,
14694 steps: Vec<InitializerOwnerStep>,
14695) -> Option<CodeUnit> {
14696 for step in steps.into_iter().rev() {
14697 if let InitializerOwnerStep::Field(field_name) = step {
14698 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
14699 }
14700 }
14701 Some(owner)
14702}
14703
14704fn initializer_field_owner(
14705 analyzer: &CppGraphSource<'_>,
14706 visibility: &VisibilityIndex<'_>,
14707 file: &ProjectFile,
14708 owner: CodeUnit,
14709 field_name: &str,
14710) -> Option<CodeUnit> {
14711 let fields = visibility
14712 .visible_members_for_owner_name(file, &owner, field_name)
14713 .into_iter()
14714 .filter(|field| field.is_field())
14715 .collect::<Vec<_>>();
14716 let field = match fields.as_slice() {
14717 [field] => *field,
14718 _ => return None,
14719 };
14720 field_declared_binding(analyzer, visibility, file, field)?.unit
14721}
14722
14723fn declaration_owner(
14724 analyzer: &CppGraphSource<'_>,
14725 visibility: &VisibilityIndex<'_>,
14726 file: &ProjectFile,
14727 source: &str,
14728 declaration: Node<'_>,
14729) -> Option<CodeUnit> {
14730 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
14731 return None;
14732 }
14733 let type_node = declaration
14734 .child_by_field_name("type")
14735 .or_else(|| first_type_child(declaration))?;
14736 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
14737}
14738
14739fn resolve_designated_owner_type(
14740 analyzer: &CppGraphSource<'_>,
14741 visibility: &VisibilityIndex<'_>,
14742 file: &ProjectFile,
14743 source: &str,
14744 type_node: Node<'_>,
14745) -> Option<CodeUnit> {
14746 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
14747 return Some(owner);
14748 }
14749 let type_name = normalize_type_text(node_text(type_node, source));
14750 visibility
14751 .resolve_type(file, &type_name)
14752 .filter(CodeUnit::is_class)
14753}
14754
14755pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
14756 let mut cursor = node.walk();
14757 node.named_children(&mut cursor).find(|child| {
14758 matches!(
14759 child.kind(),
14760 "type_identifier"
14761 | "primitive_type"
14762 | "qualified_identifier"
14763 | "scoped_type_identifier"
14764 | "struct_specifier"
14765 | "union_specifier"
14766 | "enum_specifier"
14767 )
14768 })
14769}
14770
14771pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
14772 visibility: &VisibilityIndex<'_>,
14773 file: &ProjectFile,
14774 source: &str,
14775 declarator: Node<'_>,
14776 type_text: Option<&str>,
14777 bindings: &LocalInferenceEngine<T>,
14778) -> bool {
14779 if !has_ancestor_kind(declarator, "compound_statement") {
14780 return false;
14781 }
14782 if declarator
14783 .child_by_field_name("declarator")
14784 .is_none_or(|declarator| declarator.kind() != "identifier")
14785 {
14786 return false;
14787 }
14788 if !type_text
14789 .and_then(|text| visibility.resolve_type(file, text))
14790 .is_some_and(|unit| unit.is_class())
14791 {
14792 return false;
14793 }
14794 declarator
14795 .child_by_field_name("parameters")
14796 .is_some_and(|parameters| {
14797 constructor_parameters_look_like_expressions(parameters, source, bindings)
14798 })
14799}
14800
14801fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
14802 parameters: Node<'_>,
14803 source: &str,
14804 bindings: &LocalInferenceEngine<T>,
14805) -> bool {
14806 let mut cursor = parameters.walk();
14807 parameters.named_children(&mut cursor).any(|parameter| {
14808 !matches!(
14809 parameter.kind(),
14810 "parameter_declaration" | "optional_parameter_declaration"
14811 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
14812 })
14813}
14814
14815fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
14816 parameter: Node<'_>,
14817 source: &str,
14818 bindings: &LocalInferenceEngine<T>,
14819) -> bool {
14820 let text = node_text(parameter, source).trim();
14821 if text
14822 .chars()
14823 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
14824 && bindings.is_shadowed(text)
14825 {
14826 return true;
14827 }
14828
14829 let Some(base) = parameter
14830 .child_by_field_name("type")
14831 .filter(|base| base.kind() == "type_identifier")
14832 else {
14833 return false;
14834 };
14835 let Some(subscript) = parameter
14836 .child_by_field_name("declarator")
14837 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
14838 else {
14839 return false;
14840 };
14841 subscript.child_by_field_name("size").is_some()
14842 && bindings.is_shadowed(node_text(base, source).trim())
14843}
14844
14845pub fn is_declaration_name(node: Node<'_>) -> bool {
14846 let Some(parent) = node.parent() else {
14847 return false;
14848 };
14849 if parent
14850 .child_by_field_name("name")
14851 .is_some_and(|name| same_node(name, node))
14852 {
14853 if matches!(
14854 parent.kind(),
14855 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14856 ) {
14857 return cpp_tag_specifier_declares_name(parent);
14858 }
14859 if matches!(
14860 parent.kind(),
14861 "namespace_definition"
14862 | "namespace_alias_definition"
14863 | "alias_declaration"
14864 | "enumerator"
14865 ) {
14866 return true;
14867 }
14868 }
14869
14870 let mut current = Some(parent);
14871 while let Some(ancestor) = current {
14872 let type_definition = ancestor.kind() == "type_definition";
14873 let mut child_cursor = ancestor.walk();
14874 if ancestor.named_children(&mut child_cursor).any(|child| {
14875 declaration_declarator(ancestor, child).is_some_and(|declarator| {
14876 declarator_name_path_contains(declarator, node, type_definition)
14877 })
14878 }) {
14879 return true;
14880 }
14881 if matches!(
14882 ancestor.kind(),
14883 "declaration"
14884 | "field_declaration"
14885 | "parameter_declaration"
14886 | "optional_parameter_declaration"
14887 | "function_definition"
14888 | "type_definition"
14889 | "alias_declaration"
14890 | "template_instantiation"
14891 | "class_specifier"
14892 | "struct_specifier"
14893 | "union_specifier"
14894 | "enum_specifier"
14895 ) {
14896 return false;
14897 }
14898 current = ancestor.parent();
14899 }
14900 false
14901}
14902
14903pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
14912 if !matches!(
14913 node.kind(),
14914 "qualified_identifier" | "scoped_type_identifier"
14915 ) {
14916 return false;
14917 }
14918 let Some(declaration) = node
14919 .parent()
14920 .filter(|parent| parent.kind() == "declaration")
14921 else {
14922 return false;
14923 };
14924 if declaration.child_by_field_name("declarator") != Some(node)
14925 || !declaration
14926 .child_by_field_name("type")
14927 .is_some_and(|friend| {
14928 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
14929 })
14930 {
14931 return false;
14932 }
14933 let mut cursor = declaration.walk();
14934 let mut errors = declaration
14935 .named_children(&mut cursor)
14936 .filter(|child| child.kind() == "ERROR");
14937 let Some(error) = errors.next() else {
14938 return false;
14939 };
14940 errors.next().is_none()
14941 && error.named_child_count() == 1
14942 && error.named_child(0).is_some_and(|class| {
14943 class.kind() == "identifier" && node_text(class, source) == "class"
14944 })
14945}
14946
14947pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
14948 if !matches!(node.kind(), "identifier" | "field_identifier") {
14949 return false;
14950 }
14951 if let Some(parent) = node.parent() {
14952 if parent.kind() == "call_expression"
14953 && parent.child_by_field_name("function") == Some(node)
14954 {
14955 return false;
14956 }
14957 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
14958 && parent.child_by_field_name("label") == Some(node)
14959 {
14960 return false;
14961 }
14962 }
14963 if is_declaration_name(node) {
14964 return false;
14965 }
14966 let mut current = node.parent();
14967 while let Some(ancestor) = current {
14968 match ancestor.kind() {
14969 "preproc_ifdef" | "preproc_ifndef" => {
14970 if ancestor
14971 .child_by_field_name("name")
14972 .is_some_and(|name| node_range_contains(name, node))
14973 {
14974 return false;
14975 }
14976 }
14977 "preproc_if" | "preproc_elif" => {
14978 if ancestor
14979 .child_by_field_name("condition")
14980 .is_some_and(|condition| node_range_contains(condition, node))
14981 {
14982 return false;
14983 }
14984 }
14985 "preproc_else" => {}
14986 kind if kind.starts_with("preproc_") => return false,
14987 _ => {}
14988 }
14989 if matches!(
14990 ancestor.kind(),
14991 "translation_unit" | "function_definition" | "compound_statement"
14992 ) {
14993 break;
14994 }
14995 current = ancestor.parent();
14996 }
14997 true
14998}
14999
15000fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
15001 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
15002}
15003
15004fn recovered_c_reference_node(
15005 visibility: &VisibilityIndex<'_>,
15006 file: &ProjectFile,
15007 node: Node<'_>,
15008 source: &str,
15009) -> bool {
15010 if node.start_byte() >= node.end_byte()
15011 || node.is_error()
15012 || node.is_missing()
15013 || !matches!(
15014 node.kind(),
15015 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15016 )
15017 || recovered_c_macro_binding_role(node)
15018 || recovered_c_label_role(node)
15019 {
15020 return false;
15021 }
15022 let name = node_text(node, source);
15030 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15031 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15032 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15033 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15034 if is_declaration_name(node)
15035 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15036 && !recovered_function_call
15037 && !recovered_macro_call
15038 && !recovered_parenthesized_reference
15039 {
15040 return false;
15041 }
15042
15043 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15044 return true;
15045 }
15046 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15047 return true;
15048 }
15049 if recovered_parenthesized_reference {
15050 return true;
15051 }
15052 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15053 if recovered_function_call {
15054 return true;
15055 }
15056 return visibility
15057 .visible_identifier_candidates(file, name)
15058 .any(|candidate| {
15059 candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
15060 });
15061 }
15062 let visible = visibility
15063 .visible_identifier_candidates(file, name)
15064 .next()
15065 .is_some();
15066 visible
15067 && (recovered_c_reference_anchor(node)
15068 || recovered_c_error_expression_leaf(node)
15069 || recovered_function_call)
15070}
15071
15072fn push_recovered_c_range(
15073 ranges: &mut Vec<Range>,
15074 seen: &mut HashSet<(usize, usize)>,
15075 start_byte: usize,
15076 end_byte: usize,
15077 node: Node<'_>,
15078 limit: usize,
15079) -> bool {
15080 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15081 return true;
15082 }
15083 if ranges.len() >= limit {
15084 return false;
15085 }
15086 ranges.push(Range {
15087 start_byte,
15088 end_byte,
15089 start_line: node.start_position().row,
15090 end_line: node.end_position().row,
15091 });
15092 true
15093}
15094
15095fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15100 let mut current = node.parent();
15101 while let Some(parent) = current {
15102 if parent.is_error() {
15103 let Some(anchor) = parent.parent() else {
15104 return false;
15105 };
15106 return anchor.kind().ends_with("_expression")
15107 || matches!(
15108 anchor.kind(),
15109 "argument_list"
15110 | "return_statement"
15111 | "expression_statement"
15112 | "case_statement"
15113 | "initializer_list"
15114 | "field_designator"
15115 | "enumerator"
15116 );
15117 }
15118 if matches!(
15119 parent.kind(),
15120 "translation_unit" | "function_definition" | "compound_statement"
15121 ) {
15122 return false;
15123 }
15124 current = parent.parent();
15125 }
15126 false
15127}
15128
15129fn recovered_c_function_call(
15136 visibility: &VisibilityIndex<'_>,
15137 file: &ProjectFile,
15138 node: Node<'_>,
15139 name: &str,
15140) -> bool {
15141 if !matches!(
15142 node.kind(),
15143 "identifier" | "field_identifier" | "type_identifier"
15144 ) {
15145 return false;
15146 }
15147 let error_call_prefix = node.parent().is_some_and(|error| {
15152 error.is_error()
15153 && error
15154 .parent()
15155 .is_some_and(|parent| parent.kind() == "compound_statement")
15156 }) && node
15157 .prev_sibling()
15158 .is_none_or(|previous| previous.kind() == ";")
15159 && node.next_sibling().is_some_and(|open| {
15160 open.kind() == "("
15161 && open.next_named_sibling().is_some_and(|argument| {
15162 argument.kind() == "parameter_declaration" && !argument.has_error()
15163 })
15164 });
15165 (error_call_prefix || recovered_c_function_declarator_invocation(node))
15166 && visibility
15167 .visible_identifier_candidates(file, name)
15168 .any(CodeUnit::is_function)
15169}
15170
15171fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15181 let mut function_declarator = if node.parent().is_some_and(|parent| {
15182 parent.kind() == "function_declarator"
15183 && parent.child_by_field_name("declarator") == Some(node)
15184 }) {
15185 node.parent().expect("checked function declarator parent")
15186 } else {
15187 let Some(parameter) = node.parent().filter(|parent| {
15188 parent.kind() == "parameter_declaration"
15189 && parent.child_by_field_name("type") == Some(node)
15190 }) else {
15191 return false;
15192 };
15193 if !parameter
15194 .child_by_field_name("declarator")
15195 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15196 {
15197 return false;
15198 }
15199 let Some(parameters) = parameter
15200 .parent()
15201 .filter(|parent| parent.kind() == "parameter_list")
15202 else {
15203 return false;
15204 };
15205 let Some(function_declarator) = parameters
15206 .parent()
15207 .filter(|parent| parent.kind() == "function_declarator")
15208 else {
15209 return false;
15210 };
15211 function_declarator
15212 };
15213
15214 while let Some(parent) = function_declarator.parent().filter(|parent| {
15217 parent.kind() == "function_declarator"
15218 && parent.child_by_field_name("declarator") == Some(function_declarator)
15219 }) {
15220 function_declarator = parent;
15221 }
15222 let Some(mut current) = function_declarator
15223 .parent()
15224 .filter(|parent| parent.is_error())
15225 else {
15226 return false;
15227 };
15228 loop {
15229 let Some(parent) = current.parent() else {
15230 return false;
15231 };
15232 if matches!(
15233 parent.kind(),
15234 "translation_unit"
15235 | "compound_statement"
15236 | "preproc_if"
15237 | "preproc_ifdef"
15238 | "preproc_ifndef"
15239 | "preproc_else"
15240 | "preproc_elif"
15241 ) {
15242 return true;
15243 }
15244 if parent.kind() == "function_definition"
15245 && parent.child_by_field_name("declarator") == Some(current)
15246 && parent.named_child(0) == Some(current)
15247 && parent.child_by_field_name("body").is_some()
15248 {
15249 return true;
15250 }
15251 if parent.is_error()
15252 || matches!(
15253 parent.kind(),
15254 "parameter_declaration"
15255 | "parameter_list"
15256 | "function_declarator"
15257 | "abstract_function_declarator"
15258 | "parenthesized_declarator"
15259 )
15260 {
15261 current = parent;
15262 continue;
15263 }
15264 return false;
15265 }
15266}
15267
15268fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15274 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15275 return false;
15276 };
15277 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15278 return false;
15279 }
15280 let Some(declarator) = error
15281 .parent()
15282 .filter(|parent| parent.kind() == "parenthesized_declarator")
15283 else {
15284 return false;
15285 };
15286 let Some(declaration) = declarator
15287 .parent()
15288 .filter(|parent| parent.kind() == "declaration")
15289 else {
15290 return false;
15291 };
15292 if declaration.child_by_field_name("declarator") != Some(declarator) {
15293 return false;
15294 }
15295 let Some(type_node) = declaration.child_by_field_name("type") else {
15296 return false;
15297 };
15298 type_node.kind() == "dependent_type"
15299 && type_node
15300 .child(0)
15301 .is_some_and(|keyword| keyword.kind() == "typename")
15302}
15303
15304fn recovered_c_explicit_assignment_callee(
15305 visibility: &VisibilityIndex<'_>,
15306 file: &ProjectFile,
15307 node: Node<'_>,
15308 name: &str,
15309) -> bool {
15310 let mut current = node;
15311 let error = loop {
15312 let Some(parent) = current.parent() else {
15313 return false;
15314 };
15315 if parent.is_error() {
15316 break parent;
15317 }
15318 current = parent;
15319 };
15320 let mut cursor = error.walk();
15321 let explicit_recovery_precedes_callee = error
15322 .named_children(&mut cursor)
15323 .take_while(|child| child.start_byte() < node.start_byte())
15324 .any(|child| child.kind() == "explicit_function_specifier");
15325 if !explicit_recovery_precedes_callee {
15326 return false;
15327 }
15328 visibility
15329 .visible_identifier_candidates(file, name)
15330 .any(CodeUnit::is_function)
15331}
15332
15333fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15334 while let Some(parent) = node.parent() {
15335 if matches!(
15336 parent.kind(),
15337 "preproc_def" | "preproc_function_def" | "preproc_params"
15338 ) {
15339 return true;
15340 }
15341 if parent.is_error()
15342 || matches!(
15343 parent.kind(),
15344 "translation_unit" | "function_definition" | "compound_statement"
15345 )
15346 {
15347 return false;
15348 }
15349 node = parent;
15350 }
15351 false
15352}
15353
15354fn recovered_c_label_role(node: Node<'_>) -> bool {
15355 node.parent().is_some_and(|parent| {
15356 matches!(parent.kind(), "labeled_statement" | "goto_statement")
15357 && parent.child_by_field_name("label") == Some(node)
15358 })
15359}
15360
15361fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15362 while let Some(parent) = node.parent() {
15363 if parent.is_error() {
15364 return false;
15365 }
15366 if parent.kind() == "optional_parameter_declaration"
15371 && parent
15372 .child_by_field_name("default_value")
15373 .is_some_and(|value| node_range_contains(value, node))
15374 {
15375 return true;
15376 }
15377 if parent.kind().ends_with("_expression")
15378 || matches!(
15379 parent.kind(),
15380 "argument_list"
15381 | "return_statement"
15382 | "expression_statement"
15383 | "case_statement"
15384 | "initializer_list"
15385 | "init_declarator"
15386 | "array_declarator"
15387 | "field_designator"
15388 | "enumerator"
15389 )
15390 {
15391 return true;
15392 }
15393 if matches!(
15394 parent.kind(),
15395 "translation_unit"
15396 | "function_definition"
15397 | "compound_statement"
15398 | "declaration"
15399 | "field_declaration"
15400 | "parameter_declaration"
15401 ) {
15402 return false;
15403 }
15404 node = parent;
15405 }
15406 false
15407}
15408
15409pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15417 let mut current = parameter.parent();
15418 while let Some(ancestor) = current {
15419 if ancestor.kind() == "lambda_expression" {
15420 return ancestor
15421 .child_by_field_name("declarator")
15422 .is_some_and(|declarator| {
15423 declarator.start_byte() <= parameter.start_byte()
15424 && parameter.end_byte() <= declarator.end_byte()
15425 });
15426 }
15427 if ancestor.kind() == "function_definition" {
15428 return ancestor
15429 .child_by_field_name("declarator")
15430 .is_some_and(|declarator| {
15431 declarator.start_byte() <= parameter.start_byte()
15432 && parameter.end_byte() <= declarator.end_byte()
15433 });
15434 }
15435 current = ancestor.parent();
15436 }
15437 false
15438}
15439
15440pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15441 let mut current = node.parent();
15442 while let Some(ancestor) = current {
15443 if matches!(
15444 ancestor.kind(),
15445 "parameter_declaration" | "optional_parameter_declaration"
15446 ) {
15447 return ancestor
15448 .child_by_field_name("type")
15449 .is_some_and(|type_node| {
15450 type_node.start_byte() <= node.start_byte()
15451 && node.end_byte() <= type_node.end_byte()
15452 });
15453 }
15454 if matches!(
15455 ancestor.kind(),
15456 "function_definition" | "lambda_expression" | "compound_statement"
15457 ) {
15458 return false;
15459 }
15460 current = ancestor.parent();
15461 }
15462 false
15463}
15464
15465fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15466 if specifier.child_by_field_name("body").is_some() {
15467 return true;
15468 }
15469 let mut current = specifier.parent();
15470 while let Some(ancestor) = current {
15471 match ancestor.kind() {
15472 "type_descriptor"
15473 | "parameter_declaration"
15474 | "optional_parameter_declaration"
15475 | "template_argument_list"
15476 | "cast_expression" => return false,
15477 "declaration" | "field_declaration" => {
15478 let mut cursor = ancestor.walk();
15479 return ancestor
15480 .children_by_field_name("declarator", &mut cursor)
15481 .next()
15482 .is_none();
15483 }
15484 "translation_unit" => return true,
15485 _ => current = ancestor.parent(),
15486 }
15487 }
15488 false
15489}
15490
15491pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15492 match node.kind() {
15493 "identifier"
15494 | "field_identifier"
15495 | "qualified_identifier"
15496 | "scoped_identifier"
15497 | "operator_name"
15498 | "destructor_name"
15499 | "literal_operator_name" => Some(node),
15500 "reference_declarator" | "parenthesized_declarator" => {
15501 node.named_child(0).and_then(declarator_name_node)
15502 }
15503 _ => node
15504 .child_by_field_name("declarator")
15505 .or_else(|| node.child_by_field_name("name"))
15506 .or_else(|| node.child_by_field_name("field"))
15507 .and_then(declarator_name_node),
15508 }
15509}
15510
15511fn declarator_name_path_contains(
15512 declarator: Node<'_>,
15513 candidate: Node<'_>,
15514 allow_type_identifier: bool,
15515) -> bool {
15516 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15517 return false;
15518 };
15519 let mut current = Some(declarator);
15520 while let Some(node) = current {
15521 if same_node(node, candidate) {
15522 return true;
15523 }
15524 if same_node(node, name) {
15525 return false;
15526 }
15527 current = node
15528 .child_by_field_name("declarator")
15529 .or_else(|| node.child_by_field_name("name"))
15530 .or_else(|| node.child_by_field_name("field"));
15531 }
15532 false
15533}
15534
15535fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15536 match node.kind() {
15537 "identifier"
15538 | "field_identifier"
15539 | "operator_name"
15540 | "destructor_name"
15541 | "literal_operator_name" => Some(node),
15542 "type_identifier" if allow_type_identifier => Some(node),
15543 _ => node
15544 .child_by_field_name("declarator")
15545 .or_else(|| node.child_by_field_name("name"))
15546 .or_else(|| node.child_by_field_name("field"))
15547 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15548 }
15549}
15550
15551pub fn is_nested_type_node(node: Node<'_>) -> bool {
15554 node.parent().is_some_and(|parent| {
15555 matches!(
15556 parent.kind(),
15557 "qualified_identifier" | "scoped_type_identifier" | "template_type"
15558 )
15559 })
15560}
15561
15562pub struct OutOfLineMemberDefinitionOwners<'tree> {
15563 pub owners: Vec<(Node<'tree>, CodeUnit)>,
15564 innermost: Option<(Node<'tree>, CodeUnit)>,
15565}
15566
15567impl OutOfLineMemberDefinitionOwners<'_> {
15568 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15569 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15570 }
15571}
15572
15573pub struct QualifiedOwnerComponents<'tree> {
15574 pub nodes: Vec<Node<'tree>>,
15575 pub names: Vec<String>,
15576 pub global: bool,
15577}
15578
15579pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15584 let mut stack = vec![node];
15585 let mut found_separator = false;
15586 while let Some(current) = stack.pop() {
15587 if !matches!(
15588 current.kind(),
15589 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15590 ) {
15591 continue;
15592 }
15593 let mut current_has_separator = false;
15594 for child in children_iter(current) {
15595 if child.kind() == "::" {
15596 if child.is_missing() {
15597 return false;
15598 }
15599 current_has_separator = true;
15600 found_separator = true;
15601 }
15602 }
15603 if !current_has_separator {
15604 return false;
15605 }
15606 for field in ["scope", "name"] {
15607 if let Some(child) = current.child_by_field_name(field)
15608 && matches!(
15609 child.kind(),
15610 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15611 )
15612 {
15613 stack.push(child);
15614 }
15615 }
15616 }
15617 found_separator
15618}
15619
15620pub fn qualified_owner_components<'tree>(
15621 node: Node<'tree>,
15622 source: &str,
15623) -> Option<QualifiedOwnerComponents<'tree>> {
15624 if !qualified_name_has_concrete_scope_separators(node) {
15625 return None;
15626 }
15627 let mut nodes = cpp_name_component_nodes(node)?;
15628 nodes.pop()?;
15629 if nodes.is_empty() {
15630 return None;
15631 }
15632 let names = nodes
15633 .iter()
15634 .map(|component| node_text(*component, source).to_string())
15635 .collect();
15636 Some(QualifiedOwnerComponents {
15637 nodes,
15638 names,
15639 global: is_globally_qualified_cpp_name(node),
15640 })
15641}
15642
15643pub fn out_of_line_member_definition_owner<'tree>(
15644 analyzer: &CppGraphSource<'_>,
15645 visibility: &VisibilityIndex<'_>,
15646 file: &ProjectFile,
15647 source: &str,
15648 node: Node<'tree>,
15649) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15650 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15651 || !has_ancestor_kind(node, "function_definition")
15652 || !is_function_declarator_name_root(node)
15653 {
15654 return None;
15655 }
15656 let qualified = qualified_owner_components(node, source)?;
15657 let lexical_scope = enclosing_namespace_components(node, source)?;
15658 let mut owners = Vec::new();
15659 let mut innermost = None;
15660
15661 for component_count in 1..=qualified.names.len() {
15662 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15663 .resolve_type_components_lexically(
15664 analyzer,
15665 file,
15666 &qualified.names[..component_count],
15667 qualified.global,
15668 &lexical_scope,
15669 )
15670 && !owners
15671 .iter()
15672 .any(|(_, existing)| same_visible_symbol(existing, &unit))
15673 {
15674 if component_count == qualified.names.len() {
15675 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
15676 }
15677 owners.push((qualified.nodes[component_count - 1], unit));
15678 }
15679 }
15680
15681 if innermost.is_none() {
15691 let indexed_owner_components = visibility
15692 .indexed_enclosing_owner_scope(analyzer, file, node)
15693 .or_else(|| {
15694 if qualified.names.len() <= 1 {
15699 return None;
15700 }
15701 let range = Range {
15702 start_byte: node.start_byte(),
15703 end_byte: node.end_byte(),
15704 start_line: node.start_position().row,
15705 end_line: node.end_position().row,
15706 };
15707 let start = analyzer.enclosing_code_unit(file, &range)?;
15708 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15709 brokk_bifrost_core::analyzer::Language::Cpp,
15710 &cpp_name_for(&start),
15711 );
15712 components.pop();
15713 Some(components)
15714 });
15715 if let Some(indexed_owner_components) = indexed_owner_components
15716 && indexed_owner_components.len() > qualified.names.len()
15717 && indexed_owner_components.ends_with(&qualified.names)
15718 && indexed_namespace_path_is_recoverable(
15719 &lexical_scope,
15720 &indexed_owner_components,
15721 qualified.names.len(),
15722 )
15723 && (qualified.names.len() > 1 || !qualified.global)
15728 {
15729 let namespace_count = indexed_owner_components.len() - qualified.names.len();
15730 for component_count in 1..=qualified.names.len() {
15731 let expected = &indexed_owner_components[..namespace_count + component_count];
15732 let owner_node = qualified.nodes[component_count - 1];
15733 for owner in visibility
15734 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
15735 .filter(|candidate| candidate.is_class())
15736 .filter(|candidate| {
15737 canonical_cpp_scope_components(candidate) == expected
15738 && visibility.external_type_candidate_visible_in_context(
15739 analyzer, file, candidate, node,
15740 )
15741 })
15742 {
15743 if component_count == qualified.names.len() && innermost.is_none() {
15744 innermost = Some((owner_node, owner.clone()));
15745 }
15746 if !owners
15747 .iter()
15748 .any(|(_, existing)| same_symbol(existing, owner))
15749 {
15750 owners.push((owner_node, owner.clone()));
15751 }
15752 }
15753 }
15754 }
15755 }
15756 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
15757}
15758
15759fn is_function_declarator_name_root(node: Node<'_>) -> bool {
15760 let mut current = node;
15761 while let Some(parent) = current.parent() {
15762 if parent.kind() == "function_declarator" {
15763 return parent.child_by_field_name("declarator") == Some(current);
15764 }
15765 if matches!(
15766 parent.kind(),
15767 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
15768 ) && parent.child_by_field_name("declarator") == Some(current)
15769 {
15770 current = parent;
15771 continue;
15772 }
15773 return false;
15774 }
15775 false
15776}
15777
15778pub fn append_cpp_name_components(
15779 node: Node<'_>,
15780 source: &str,
15781 out: &mut Vec<String>,
15782) -> Option<()> {
15783 out.extend(
15784 cpp_name_component_nodes(node)?
15785 .into_iter()
15786 .map(|component| node_text(component, source).to_string()),
15787 );
15788 Some(())
15789}
15790
15791pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
15792 let mut components = Vec::new();
15793 append_cpp_name_components(node, source, &mut components)?;
15794 Some(components)
15795}
15796
15797pub fn unique_macro_replacement_type_candidate(
15806 analyzer: &CppGraphSource<'_>,
15807 visibility: &VisibilityIndex<'_>,
15808 file: &ProjectFile,
15809 components: &[String],
15810) -> Option<CodeUnit> {
15811 let terminal = components.last()?;
15812 let mut candidates = Vec::new();
15813 for candidate in visibility
15814 .visible_identifier_candidates(file, terminal)
15815 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
15816 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
15817 {
15818 if !candidates
15819 .iter()
15820 .any(|existing| same_logical_symbol(existing, candidate))
15821 {
15822 candidates.push(candidate.clone());
15823 }
15824 }
15825 (candidates.len() == 1).then(|| candidates.remove(0))
15826}
15827
15828pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
15836 let mut parser = Parser::new();
15837 if parser
15838 .set_language(&tree_sitter_cpp::LANGUAGE.into())
15839 .is_err()
15840 {
15841 return Vec::new();
15842 }
15843 let Some(tree) = parser.parse(source, None) else {
15844 return Vec::new();
15845 };
15846 let mut scopes = Vec::new();
15847 let mut pending = vec![tree.root_node()];
15848 while let Some(node) = pending.pop() {
15849 if node.kind() == "using_declaration" {
15850 let Some(imported) = node.named_child(0) else {
15851 continue;
15852 };
15853 let Some(mut components) = cpp_type_name_components(imported, source) else {
15854 continue;
15855 };
15856 if components.pop().as_deref() == Some(member) && !components.is_empty() {
15857 scopes.push(components.join("::"));
15858 }
15859 continue;
15860 }
15861 push_named_children_reversed(node, &mut pending);
15862 }
15863 scopes
15864}
15865
15866pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
15870 qualified == scope
15871 || qualified
15872 .strip_suffix(scope)
15873 .is_some_and(|prefix| prefix.ends_with("::"))
15874}
15875
15876pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
15881 let Some(type_descriptor) = node.parent() else {
15882 return false;
15883 };
15884 if type_descriptor.kind() != "type_descriptor"
15885 || type_descriptor.child_by_field_name("type") != Some(node)
15886 {
15887 return false;
15888 }
15889 let Some(arguments) = type_descriptor.parent() else {
15890 return false;
15891 };
15892 if arguments.kind() != "template_argument_list" {
15893 return false;
15894 }
15895 arguments.parent().is_some_and(|parent| {
15896 matches!(parent.kind(), "template_type" | "template_function")
15897 && parent.child_by_field_name("arguments") == Some(arguments)
15898 })
15899}
15900
15901pub fn cpp_template_reference_arguments(
15902 mut node: Node<'_>,
15903 source: &str,
15904) -> Option<Vec<CppTemplateExpression>> {
15905 loop {
15906 match node.kind() {
15907 "template_type" | "template_function" => {
15908 let arguments = node.child_by_field_name("arguments")?;
15909 let mut cursor = arguments.walk();
15910 return Some(
15911 arguments
15912 .named_children(&mut cursor)
15913 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
15914 .map(|argument| CppTemplateExpression {
15915 text: normalize_cpp_whitespace(node_text(argument, source)),
15916 term: cpp_template_term(
15918 argument,
15919 source,
15920 &[],
15921 &ParentIndex::unindexed(),
15922 ),
15923 })
15924 .collect(),
15925 );
15926 }
15927 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
15928 node = node
15929 .child_by_field_name("name")
15930 .or_else(|| node.child_by_field_name("type"))?;
15931 }
15932 _ => return None,
15933 }
15934 }
15935}
15936
15937fn cpp_reconcile_primary_template_parameters(
15938 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
15939 preferred: &CodeUnit,
15940) -> Option<Vec<CppTemplateParameterMetadata>> {
15941 let canonical = candidates
15942 .iter()
15943 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
15944 let mut merged = canonical
15945 .parameters
15946 .iter()
15947 .map(|parameter| CppTemplateParameterMetadata {
15948 name: parameter.name.clone(),
15949 kind: parameter.kind,
15950 variadic: parameter.variadic,
15951 default: None,
15952 })
15953 .collect::<Vec<_>>();
15954
15955 for (_, metadata) in candidates {
15956 if metadata.parameters.len() != merged.len() {
15957 return None;
15958 }
15959 let rename_bindings = metadata
15960 .parameters
15961 .iter()
15962 .zip(&merged)
15963 .map(|(parameter, canonical)| {
15964 (
15965 parameter.name.clone(),
15966 CppTemplateTerm::Parameter(canonical.name.clone()),
15967 )
15968 })
15969 .collect::<HashMap<_, _>>();
15970 for ((parameter, canonical), merged_parameter) in metadata
15971 .parameters
15972 .iter()
15973 .zip(&canonical.parameters)
15974 .zip(&mut merged)
15975 {
15976 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
15977 return None;
15978 }
15979 let Some(default) = ¶meter.default else {
15980 continue;
15981 };
15982 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
15983 if let Some(existing) = &merged_parameter.default {
15984 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
15985 return None;
15986 }
15987 } else {
15988 merged_parameter.default = Some(CppTemplateExpression {
15989 text: default.text.clone(),
15990 term: normalized_term,
15991 });
15992 }
15993 }
15994 }
15995 Some(merged)
15996}
15997
15998pub fn cpp_bind_template_arguments(
15999 parameters: &[CppTemplateParameterMetadata],
16000 explicit_arguments: &[CppTemplateExpression],
16001) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16002 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16003 if variadic_index.is_some_and(|index| {
16004 index + 1 != parameters.len()
16005 || parameters[index + 1..]
16006 .iter()
16007 .any(|parameter| parameter.variadic)
16008 }) {
16009 return None;
16010 }
16011 let fixed_count = variadic_index.unwrap_or(parameters.len());
16012 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16013 return None;
16014 }
16015 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16016 let mut expanded = explicit_arguments[..explicit_fixed_count]
16017 .iter()
16018 .map(cpp_clone_template_expression_iterative)
16019 .collect::<Vec<_>>();
16020 let mut bindings = HashMap::default();
16021 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16022 bindings.insert(
16023 parameter.name.clone(),
16024 cpp_clone_template_term_iterative(&argument.term),
16025 );
16026 }
16027 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
16028 let default = parameter.default.as_ref()?;
16029 let term = cpp_substitute_template_term(&default.term, &bindings)?;
16030 bindings.insert(parameter.name.clone(), term.clone());
16031 expanded.push(CppTemplateExpression {
16032 text: default.text.clone(),
16033 term,
16034 });
16035 }
16036 if let Some(index) = variadic_index {
16037 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16038 expanded.extend(
16039 packed_arguments
16040 .iter()
16041 .map(cpp_clone_template_expression_iterative),
16042 );
16043 bindings.insert(
16044 parameters[index].name.clone(),
16045 CppTemplateTerm::Node {
16046 kind: "parameter_pack".to_string(),
16047 children: packed_arguments
16048 .iter()
16049 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16050 .collect(),
16051 },
16052 );
16053 }
16054 Some((expanded, bindings))
16055}
16056
16057fn cpp_specialization_matches(
16058 metadata: &CppTemplateMetadata,
16059 arguments: &[CppTemplateExpression],
16060) -> bool {
16061 if metadata.specialization_arguments.len() != arguments.len() {
16062 return false;
16063 }
16064 let parameter_names = metadata
16065 .parameters
16066 .iter()
16067 .map(|parameter| parameter.name.as_str())
16068 .collect::<HashSet<_>>();
16069 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16070 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
16071 if !cpp_unify_template_term(
16072 &pattern.term,
16073 &argument.term,
16074 ¶meter_names,
16075 &mut bindings,
16076 ) {
16077 return false;
16078 }
16079 }
16080 true
16081}
16082
16083fn cpp_specialization_more_specialized(
16084 candidate: &CppTemplateMetadata,
16085 other: &CppTemplateMetadata,
16086) -> bool {
16087 cpp_specialization_pattern_accepts(other, candidate)
16088 && !cpp_specialization_pattern_accepts(candidate, other)
16089}
16090
16091fn cpp_specialization_pattern_accepts(
16092 broader: &CppTemplateMetadata,
16093 narrower: &CppTemplateMetadata,
16094) -> bool {
16095 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
16096 return false;
16097 }
16098 let parameter_names = broader
16099 .parameters
16100 .iter()
16101 .map(|parameter| parameter.name.as_str())
16102 .collect::<HashSet<_>>();
16103 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16104 broader
16105 .specialization_arguments
16106 .iter()
16107 .zip(&narrower.specialization_arguments)
16108 .all(|(pattern, argument)| {
16109 cpp_unify_template_term(
16110 &pattern.term,
16111 &argument.term,
16112 ¶meter_names,
16113 &mut bindings,
16114 )
16115 })
16116}
16117
16118pub fn cpp_substitute_template_term(
16119 term: &CppTemplateTerm,
16120 bindings: &HashMap<String, CppTemplateTerm>,
16121) -> Option<CppTemplateTerm> {
16122 enum Work<'a> {
16123 Visit(&'a CppTemplateTerm),
16124 Build { kind: String, child_count: usize },
16125 }
16126
16127 let mut work = vec![Work::Visit(term)];
16128 let mut substituted = Vec::new();
16129 while let Some(next) = work.pop() {
16130 match next {
16131 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16132 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16133 }
16134 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16135 substituted.push(CppTemplateTerm::Atom {
16136 kind: kind.clone(),
16137 text: text.clone(),
16138 });
16139 }
16140 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16141 work.push(Work::Build {
16142 kind: kind.clone(),
16143 child_count: children.len(),
16144 });
16145 work.extend(children.iter().rev().map(Work::Visit));
16146 }
16147 Work::Build { kind, child_count } => {
16148 let children = substituted.split_off(substituted.len() - child_count);
16149 substituted.push(CppTemplateTerm::Node { kind, children });
16150 }
16151 }
16152 }
16153 substituted.pop()
16154}
16155
16156pub fn cpp_substitute_template_arguments(
16157 arguments: &[CppTemplateExpression],
16158 bindings: &HashMap<String, CppTemplateTerm>,
16159) -> Option<Vec<CppTemplateExpression>> {
16160 let mut substituted = Vec::new();
16161 for argument in arguments {
16162 let CppTemplateTerm::Node { kind, children } = &argument.term else {
16163 substituted.push(CppTemplateExpression {
16164 text: argument.text.clone(),
16165 term: cpp_substitute_template_term(&argument.term, bindings)?,
16166 });
16167 continue;
16168 };
16169 if kind != "parameter_pack_expansion" {
16170 substituted.push(CppTemplateExpression {
16171 text: argument.text.clone(),
16172 term: cpp_substitute_template_term(&argument.term, bindings)?,
16173 });
16174 continue;
16175 }
16176 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16177 return None;
16178 };
16179 if ellipsis != "..." {
16180 return None;
16181 }
16182
16183 let mut pack_names = Vec::new();
16184 let mut work = vec![pattern];
16185 while let Some(term) = work.pop() {
16186 match term {
16187 CppTemplateTerm::Parameter(name)
16188 if matches!(
16189 bindings.get(name),
16190 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16191 ) =>
16192 {
16193 if !pack_names.contains(name) {
16194 pack_names.push(name.clone());
16195 }
16196 }
16197 CppTemplateTerm::Node { children, .. } => work.extend(children),
16198 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16199 }
16200 }
16201 let first_pack = pack_names.first()?;
16202 let CppTemplateTerm::Node {
16203 children: first_elements,
16204 ..
16205 } = bindings.get(first_pack)?
16206 else {
16207 return None;
16208 };
16209 let pack_len = first_elements.len();
16210 for pack_name in &pack_names {
16211 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16212 return None;
16213 };
16214 if children.len() != pack_len {
16215 return None;
16216 }
16217 }
16218 for index in 0..pack_len {
16219 let mut element_bindings = bindings.clone();
16220 for pack_name in &pack_names {
16221 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16222 return None;
16223 };
16224 element_bindings.insert(
16225 pack_name.clone(),
16226 cpp_clone_template_term_iterative(&children[index]),
16227 );
16228 }
16229 substituted.push(CppTemplateExpression {
16230 text: argument.text.clone(),
16231 term: cpp_substitute_template_term(pattern, &element_bindings)?,
16232 });
16233 }
16234 }
16235 Some(substituted)
16236}
16237
16238fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16239 enum Work<'a> {
16240 Visit(&'a CppTemplateTerm),
16241 Build { kind: String, child_count: usize },
16242 }
16243
16244 let mut work = vec![Work::Visit(term)];
16245 let mut cloned = Vec::new();
16246 while let Some(next) = work.pop() {
16247 match next {
16248 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16249 cloned.push(CppTemplateTerm::Parameter(name.clone()));
16250 }
16251 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16252 cloned.push(CppTemplateTerm::Atom {
16253 kind: kind.clone(),
16254 text: text.clone(),
16255 });
16256 }
16257 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16258 work.push(Work::Build {
16259 kind: kind.clone(),
16260 child_count: children.len(),
16261 });
16262 work.extend(children.iter().rev().map(Work::Visit));
16263 }
16264 Work::Build { kind, child_count } => {
16265 let children = cloned.split_off(cloned.len() - child_count);
16266 cloned.push(CppTemplateTerm::Node { kind, children });
16267 }
16268 }
16269 }
16270 cloned
16271 .pop()
16272 .expect("template term traversal emits one root")
16273}
16274
16275fn cpp_clone_template_expression_iterative(
16276 expression: &CppTemplateExpression,
16277) -> CppTemplateExpression {
16278 CppTemplateExpression {
16279 text: expression.text.clone(),
16280 term: cpp_clone_template_term_iterative(&expression.term),
16281 }
16282}
16283
16284pub fn cpp_unify_template_term(
16285 pattern: &CppTemplateTerm,
16286 argument: &CppTemplateTerm,
16287 parameters: &HashSet<&str>,
16288 bindings: &mut HashMap<String, CppTemplateTerm>,
16289) -> bool {
16290 let mut work = vec![(pattern, argument)];
16291 while let Some((pattern, argument)) = work.pop() {
16292 match pattern {
16293 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16294 if let Some(bound) = bindings.get(name) {
16295 if !cpp_template_terms_equal(bound, argument) {
16296 return false;
16297 }
16298 } else {
16299 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16300 }
16301 }
16302 CppTemplateTerm::Atom {
16303 kind: pattern_kind,
16304 text: pattern_text,
16305 } => {
16306 if !matches!(
16307 argument,
16308 CppTemplateTerm::Atom { kind, text }
16309 if kind == pattern_kind && text == pattern_text
16310 ) {
16311 return false;
16312 }
16313 }
16314 CppTemplateTerm::Node {
16315 kind: pattern_kind,
16316 children: pattern_children,
16317 } => {
16318 let CppTemplateTerm::Node { kind, children } = argument else {
16319 return false;
16320 };
16321 if kind != pattern_kind || children.len() != pattern_children.len() {
16322 return false;
16323 }
16324 work.extend(pattern_children.iter().zip(children).rev());
16325 }
16326 CppTemplateTerm::Parameter(_) => return false,
16327 }
16328 }
16329 true
16330}
16331
16332fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16333 let mut work = vec![(left, right)];
16334 while let Some((left, right)) = work.pop() {
16335 match (left, right) {
16336 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16337 if left != right {
16338 return false;
16339 }
16340 }
16341 (
16342 CppTemplateTerm::Atom {
16343 kind: left_kind,
16344 text: left_text,
16345 },
16346 CppTemplateTerm::Atom {
16347 kind: right_kind,
16348 text: right_text,
16349 },
16350 ) => {
16351 if left_kind != right_kind || left_text != right_text {
16352 return false;
16353 }
16354 }
16355 (
16356 CppTemplateTerm::Node {
16357 kind: left_kind,
16358 children: left_children,
16359 },
16360 CppTemplateTerm::Node {
16361 kind: right_kind,
16362 children: right_children,
16363 },
16364 ) => {
16365 if left_kind != right_kind || left_children.len() != right_children.len() {
16366 return false;
16367 }
16368 work.extend(left_children.iter().zip(right_children).rev());
16369 }
16370 _ => return false,
16371 }
16372 }
16373 true
16374}
16375
16376pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16377 let mut components = Vec::new();
16378 let mut stack = vec![node];
16379 while let Some(current) = stack.pop() {
16380 match current.kind() {
16381 "identifier"
16382 | "field_identifier"
16383 | "namespace_identifier"
16384 | "type_identifier"
16385 | "operator_name"
16386 | "destructor_name" => components.push(current),
16387 "template_type" | "template_function" => {
16388 stack.push(current.child_by_field_name("name")?);
16389 }
16390 "dependent_name" => stack.push(current.named_child(0)?),
16391 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16392 stack.push(current.child_by_field_name("name")?);
16393 if let Some(scope) = current.child_by_field_name("scope") {
16394 stack.push(scope);
16395 }
16396 }
16397 "nested_namespace_specifier" => {
16398 for index in (0..current.named_child_count()).rev() {
16399 stack.push(current.named_child(index)?);
16400 }
16401 }
16402 _ => return None,
16403 }
16404 }
16405 Some(components)
16406}
16407
16408pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16409 node.child_by_field_name("scope").is_none()
16410 && node.child(0).is_some_and(|child| child.kind() == "::")
16411}
16412
16413fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16414 let mut namespaces = Vec::new();
16415 let mut current = node.parent();
16416 while let Some(parent) = current {
16417 if parent.kind() == "namespace_definition"
16418 && let Some(name) = parent.child_by_field_name("name")
16419 {
16420 let mut components = Vec::new();
16421 append_cpp_name_components(name, source, &mut components)?;
16422 namespaces.push(components);
16423 }
16424 current = parent.parent();
16425 }
16426 namespaces.reverse();
16427 Some(namespaces.into_iter().flatten().collect())
16428}
16429
16430fn indexed_namespace_path_is_recoverable(
16441 lexical_scope: &[String],
16442 indexed_owner_scope: &[String],
16443 explicit_owner_component_count: usize,
16444) -> bool {
16445 if lexical_scope.is_empty() {
16446 return explicit_owner_component_count > 1;
16447 }
16448 if lexical_scope.len() >= indexed_owner_scope.len() {
16449 return false;
16450 }
16451 let mut indexed = indexed_owner_scope.iter();
16452 lexical_scope
16453 .iter()
16454 .all(|component| indexed.any(|candidate| candidate == component))
16455}
16456
16457pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16458 let mut current = node.parent();
16459 while let Some(parent) = current {
16460 if parent.kind() == kind {
16461 return true;
16462 }
16463 current = parent.parent();
16464 }
16465 false
16466}
16467
16468pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16474 let mut current = Some(node);
16475 while let Some(candidate) = current {
16476 if candidate.kind() == "declaration" {
16477 let Some(type_node) = candidate.child_by_field_name("type") else {
16478 return false;
16479 };
16480 if !(type_node.start_byte() <= node.start_byte()
16481 && node.end_byte() <= type_node.end_byte())
16482 {
16483 return false;
16484 }
16485 let mut cursor = candidate.walk();
16486 return candidate.named_children(&mut cursor).any(|child| {
16487 child.kind() == "init_declarator"
16488 && child
16489 .child_by_field_name("value")
16490 .is_some_and(|value| value.kind() == "cast_expression")
16491 });
16492 }
16493 current = candidate.parent();
16494 }
16495 false
16496}
16497
16498#[derive(Clone, Copy, PartialEq, Eq)]
16499pub(crate) enum QualifiedAliasReferenceKind {
16500 Ordinary,
16501 ConstructorWithExpressionArgument,
16502 ExhaustiveTemplate,
16503}
16504
16505pub(crate) fn qualified_alias_reference_preserves_target(
16512 node: Node<'_>,
16513 target: &CodeUnit,
16514 analyzer: &CppGraphSource<'_>,
16515 visibility: &VisibilityIndex<'_>,
16516 file: &ProjectFile,
16517 source: &str,
16518) -> Option<QualifiedAliasReferenceKind> {
16519 if !matches!(
16520 node.kind(),
16521 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16522 ) {
16523 return None;
16524 }
16525 let components = cpp_type_name_components(node, source)?;
16526 let name = components.last()?;
16527 analyzer.type_alias_provider().and_then(|provider| {
16528 visibility
16529 .visible_identifier_candidates(file, name)
16530 .find_map(|candidate| {
16531 let proof = provider.is_type_alias(candidate)
16532 && canonical_cpp_scope_components(candidate) == components
16533 && visibility.external_type_candidate_visible_in_context(
16534 analyzer, file, candidate, node,
16535 )
16536 && match cpp_template_reference_arguments(node, source) {
16537 Some(arguments) => visibility.template_alias_arguments_preserve_target(
16538 analyzer, file, candidate, &arguments, target,
16539 ),
16540 None => visibility.structured_alias_primary_preserves_target(
16541 analyzer, file, candidate, target,
16542 ),
16543 };
16544 proof.then(|| {
16545 if cpp_template_reference_arguments(node, source).is_some()
16546 && visibility.is_exhaustive_same_fqn_type_declaration_family(
16547 analyzer, file, candidate,
16548 )
16549 {
16550 QualifiedAliasReferenceKind::ExhaustiveTemplate
16551 } else if qualified_alias_constructor_has_expression_argument(node)
16552 || qualified_alias_local_constructor_declaration(node)
16553 {
16554 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16555 } else {
16556 QualifiedAliasReferenceKind::Ordinary
16557 }
16558 })
16559 })
16560 })
16561}
16562
16563pub(crate) fn qualified_alias_reference_requires_terminal(
16564 reference: Option<QualifiedAliasReferenceKind>,
16565) -> bool {
16566 matches!(
16567 reference,
16568 Some(
16569 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16570 | QualifiedAliasReferenceKind::ExhaustiveTemplate
16571 )
16572 )
16573}
16574
16575fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16576 let Some(declaration) = node.parent().filter(|parent| {
16577 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16578 }) else {
16579 return false;
16580 };
16581 let mut cursor = declaration.walk();
16582 declaration.named_children(&mut cursor).any(|child| {
16583 child.kind() == "init_declarator"
16584 && child
16585 .child_by_field_name("value")
16586 .filter(|value| value.kind() == "argument_list")
16587 .is_some_and(|arguments| {
16588 let mut cursor = arguments.walk();
16589 arguments.named_children(&mut cursor).any(|argument| {
16590 let is_parameter = matches!(
16591 argument.kind(),
16592 "parameter_declaration" | "optional_parameter_declaration"
16593 );
16594 if is_parameter {
16595 argument
16596 .child_by_field_name("type")
16597 .is_some_and(|type_node| {
16598 type_node.kind() == "type_identifier"
16599 && argument.child_by_field_name("declarator").is_none()
16600 })
16601 } else {
16602 !argument.kind().ends_with("_literal")
16603 && !matches!(argument.kind(), "true" | "false" | "nullptr")
16604 }
16605 })
16606 })
16607 })
16608}
16609
16610fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16615 let Some(declaration) = node.parent().filter(|parent| {
16616 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16617 }) else {
16618 return false;
16619 };
16620 if declaration
16621 .parent()
16622 .is_none_or(|parent| parent.kind() != "compound_statement")
16623 {
16624 return false;
16625 }
16626 let mut cursor = declaration.walk();
16627 declaration
16628 .named_children(&mut cursor)
16629 .any(|child| child.kind() == "function_declarator")
16630}
16631
16632pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16638 loop {
16639 let next = match node.kind() {
16640 "qualified_identifier"
16641 | "scoped_identifier"
16642 | "template_method"
16643 | "template_function"
16644 | "template_type" => node.child_by_field_name("name"),
16645 "field_expression" => node.child_by_field_name("field"),
16646 _ => None,
16647 };
16648 let Some(next) = next else {
16649 return node;
16650 };
16651 node = next;
16652 }
16653}
16654
16655#[derive(Clone, Copy)]
16656pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16657 pub receiver: Node<'tree>,
16658 pub member: Node<'tree>,
16659 pub arity: usize,
16660}
16661
16662pub fn recovered_relational_template_member_call(
16670 field: Node<'_>,
16671) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16672 if field.kind() != "field_expression" {
16673 return None;
16674 }
16675 let receiver = field
16676 .child_by_field_name("argument")
16677 .or_else(|| field.child_by_field_name("object"))?;
16678 let member = field.child_by_field_name("field")?;
16679 let less = field.parent()?;
16680 if less.kind() != "binary_expression"
16681 || less.child_by_field_name("left") != Some(field)
16682 || less
16683 .child_by_field_name("operator")
16684 .is_none_or(|operator| operator.kind() != "<")
16685 || less.child_by_field_name("right").is_none()
16686 {
16687 return None;
16688 }
16689 let greater = less.parent()?;
16690 if greater.kind() != "binary_expression"
16691 || greater.child_by_field_name("left") != Some(less)
16692 || greater
16693 .child_by_field_name("operator")
16694 .is_none_or(|operator| operator.kind() != ">")
16695 {
16696 return None;
16697 }
16698 let arguments = greater.child_by_field_name("right")?;
16699 if arguments.kind() != "parenthesized_expression" {
16700 return None;
16701 }
16702 let arity = parenthesized_call_argument_arity(arguments)?;
16703 Some(RecoveredRelationalTemplateMemberCall {
16704 receiver,
16705 member,
16706 arity,
16707 })
16708}
16709
16710fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
16711 let expression = arguments.named_child(0)?;
16712 if expression.kind() != "comma_expression" {
16713 return Some(1);
16714 }
16715 let mut arity = 0usize;
16716 let mut stack = vec![expression];
16717 while let Some(node) = stack.pop() {
16718 if node.kind() == "comma_expression" {
16719 stack.push(node.child_by_field_name("right")?);
16720 stack.push(node.child_by_field_name("left")?);
16721 } else {
16722 arity += 1;
16723 }
16724 }
16725 Some(arity)
16726}
16727
16728pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
16731 while let Some(parent) = node.parent() {
16732 match parent.kind() {
16733 "call_expression" => {
16734 return parent
16735 .child_by_field_name("function")
16736 .or_else(|| parent.named_child(0))
16737 == Some(node);
16738 }
16739 "qualified_identifier"
16740 | "scoped_identifier"
16741 | "template_function"
16742 | "template_type"
16743 | "field_expression" => node = parent,
16744 _ => return false,
16745 }
16746 }
16747 false
16748}
16749
16750pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
16751 if is_call_callee_node(node) {
16752 function_terminal_node(node)
16753 } else {
16754 node
16755 }
16756}
16757
16758pub fn normalize_type_text(value: &str) -> String {
16759 strip_tag_type_prefix(
16760 normalize_cpp_whitespace(value)
16761 .trim_start_matches("const ")
16762 .trim_end_matches('*')
16763 .trim_end_matches('&')
16764 .trim(),
16765 )
16766 .to_string()
16767}
16768
16769fn strip_tag_type_prefix(value: &str) -> &str {
16770 let value = value.trim_start_matches("const ");
16771 value
16772 .strip_prefix("struct ")
16773 .or_else(|| value.strip_prefix("class "))
16774 .or_else(|| value.strip_prefix("enum "))
16775 .unwrap_or(value)
16776 .trim()
16777}
16778
16779pub fn normalize_reference_name(value: &str) -> Option<String> {
16780 let normalized = normalize_cpp_reference_text(value);
16781 (!normalized.is_empty()).then_some(normalized)
16782}
16783
16784pub fn normalize_cpp_reference_text(value: &str) -> String {
16785 let mut text = normalize_cpp_whitespace(value)
16786 .trim_start_matches("new ")
16787 .trim()
16788 .to_string();
16789 if let Some(index) = text.find(['(', '{']) {
16790 text.truncate(index);
16791 }
16792 if let Some(index) = text.find('<') {
16793 text.truncate(index);
16794 }
16795 let normalized = text
16796 .trim()
16797 .trim_start_matches("const ")
16798 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
16799 .trim_matches(':')
16800 .trim();
16801 strip_tag_type_prefix(normalized).to_string()
16802}
16803
16804pub fn cpp_name_for(unit: &CodeUnit) -> String {
16805 let short = unit.short_name().replace(['.', '$'], "::");
16806 if unit.package_name().is_empty() {
16807 short
16808 } else {
16809 format!("{}::{}", unit.package_name(), short)
16810 }
16811}
16812
16813fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
16817 let fq = unit.fq();
16818 if fq.is_empty() {
16819 return None;
16820 }
16821 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16822 Some(
16823 fq.segments()
16824 .iter()
16825 .map(|&segment| interner.resolve(segment).0)
16826 .collect::<Vec<_>>()
16827 .join("::"),
16828 )
16829}
16830
16831fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
16832 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
16833 || unit.fq().is_empty() && cpp_name_for(unit) == expected
16834}
16835
16836pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
16845 let fq = unit.fq();
16846 if !fq.is_empty() {
16847 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16848 let scope = fq
16849 .segments()
16850 .iter()
16851 .filter_map(|&segment| {
16852 let (text, kind) = interner.resolve(segment);
16853 matches!(
16854 kind,
16855 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
16856 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
16857 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
16858 )
16859 .then(|| text.to_string())
16860 })
16861 .collect();
16862 return scope;
16863 }
16864 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16865 brokk_bifrost_core::analyzer::Language::Cpp,
16866 &cpp_name_for(unit),
16867 )
16868}
16869
16870pub fn terminal_name(value: &str) -> &str {
16881 value
16882 .rsplit("::")
16883 .next()
16884 .unwrap_or(value)
16885 .rsplit(['.', '-', '>'])
16886 .next()
16887 .unwrap_or(value)
16888 .trim()
16889}
16890
16891pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
16892 terminal_name(&normalize_cpp_reference_text(value)) == expected
16893}
16894
16895pub fn name_matches_callable(value: &str, expected: &str) -> bool {
16896 name_matches_terminal(value, expected)
16897 || expected.starts_with("operator")
16898 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
16899}
16900
16901pub fn name_mentions(value: &str, expected: &str) -> bool {
16902 normalize_cpp_reference_text(value)
16903 .split("::")
16904 .any(|part| part == expected)
16905}
16906
16907pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
16908 let cpp_name = cpp_name_for(unit);
16909 if reference.contains("::") {
16910 return reference == cpp_name;
16911 }
16912 reference == cpp_name
16913 || terminal_name(reference) == unit.identifier()
16914 && (unit.package_name().is_empty() || reference == unit.identifier())
16915}
16916
16917pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
16918 match kind {
16919 TargetKind::Type
16920 | TargetKind::Constructor
16921 | TargetKind::Method
16922 | TargetKind::MemberField => true,
16923 TargetKind::FreeFunction => unit.is_function(),
16924 TargetKind::GlobalField => unit.is_field(),
16925 TargetKind::Macro => unit.is_macro(),
16926 }
16927}
16928
16929pub fn is_type_alias(unit: &CodeUnit) -> bool {
16930 unit.kind() == CodeUnitType::Field
16931 && unit.signature().is_some_and(|signature| {
16932 signature.starts_with("typedef ") || signature.starts_with("using ")
16933 })
16934}
16935
16936fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
16937 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
16938 let target_name = cpp_name_for(target);
16939 if normalized.contains("::") {
16940 return normalized == target_name;
16941 }
16942 if let Some(namespace) = alias.namespace.as_deref() {
16943 return namespace_prefixes(namespace)
16944 .into_iter()
16945 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
16946 }
16947 target.package_name().is_empty() && normalized == target.identifier()
16948}
16949
16950pub fn cpp_function_return_type_text(
16953 analyzer: &CppGraphSource<'_>,
16954 function: &CodeUnit,
16955) -> Option<String> {
16956 let metadata = analyzer.signature_metadata(function);
16957 if !metadata.is_empty() {
16958 let first = metadata.first()?.return_type_text()?;
16959 return metadata
16960 .iter()
16961 .all(|metadata| metadata.return_type_text() == Some(first))
16962 .then(|| first.to_string());
16963 }
16964 let signature = cpp_function_signature_text(analyzer, function)?;
16965 cpp_function_return_type_text_from_signature(&signature)
16966}
16967
16968fn cpp_function_signature_text(
16969 analyzer: &CppGraphSource<'_>,
16970 function: &CodeUnit,
16971) -> Option<String> {
16972 function
16973 .signature()
16974 .filter(|signature| signature.contains(function.identifier()))
16975 .map(str::to_string)
16976 .or_else(|| analyzer.signatures(function).first().cloned())
16977 .or_else(|| analyzer.get_source(function, false))
16978}
16979
16980fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
16981 let open = signature.find('(')?;
16982 let name_at = cpp_function_name_start(signature, open)?;
16983 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
16984 return Some(return_type);
16985 }
16986 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
16987 .split_whitespace()
16988 .filter(|token| {
16989 !matches!(
16990 *token,
16991 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
16992 )
16993 })
16994 .collect::<Vec<_>>()
16995 .join(" ");
16996 let type_text = type_text.trim();
16997 (!type_text.is_empty()).then(|| type_text.to_string())
16998}
16999
17000fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
17001 let before_parameters = &signature[..open];
17002 if let Some(operator_at) = before_parameters.rfind("operator") {
17003 let boundary = operator_at == 0
17004 || before_parameters[..operator_at]
17005 .chars()
17006 .next_back()
17007 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
17008 if boundary {
17009 return Some(operator_at);
17010 }
17011 }
17012 before_parameters
17013 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
17014 .map(|index| index + 1)
17015}
17016
17017fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
17018 let open = signature_from_name.find('(')?;
17019 let mut depth = 0i32;
17020 for (offset, ch) in signature_from_name[open..].char_indices() {
17021 match ch {
17022 '(' => depth += 1,
17023 ')' => {
17024 depth -= 1;
17025 if depth == 0 {
17026 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
17027 let arrow = rest.find("->")?;
17028 let return_type = rest[arrow + 2..].trim_start();
17029 let return_type = return_type
17030 .split(['{', ';'])
17031 .next()
17032 .unwrap_or(return_type)
17033 .trim();
17034 return (!return_type.is_empty()).then(|| return_type.to_string());
17035 }
17036 }
17037 _ => {}
17038 }
17039 }
17040 None
17041}
17042
17043fn cpp_strip_leading_template_clause(text: &str) -> &str {
17046 let trimmed = text.trim_start();
17047 let Some(rest) = trimmed.strip_prefix("template") else {
17048 return text;
17049 };
17050 let rest = rest.trim_start();
17051 if !rest.starts_with('<') {
17052 return text;
17053 }
17054 let mut depth = 0i32;
17055 for (offset, ch) in rest.char_indices() {
17056 match ch {
17057 '<' => depth += 1,
17058 '>' => {
17059 depth -= 1;
17060 if depth == 0 {
17061 return rest[offset + ch.len_utf8()..].trim_start();
17062 }
17063 }
17064 _ => {}
17065 }
17066 }
17067 text
17068}
17069
17070pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17071 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17081 namespace
17082 .strip_prefix("anonymous_namespace::")
17083 .unwrap_or(namespace)
17084 .to_string()
17085 })
17086}
17087
17088fn namespace_prefixes(namespace: &str) -> Vec<String> {
17089 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17095 brokk_bifrost_core::analyzer::Language::Cpp,
17096 namespace,
17097 );
17098 let mut prefixes = Vec::new();
17099 while !parts.is_empty() {
17100 prefixes.push(parts.join("::"));
17101 parts.pop();
17102 }
17103 prefixes
17104}
17105
17106fn nearest_namespace_candidates(
17107 candidates: Vec<CodeUnit>,
17108 normalized: &str,
17109 lexical_namespace: Option<&str>,
17110) -> Vec<CodeUnit> {
17111 if normalized.contains("::") {
17112 return candidates;
17113 }
17114 if let Some(namespace) = lexical_namespace {
17115 for prefix in namespace_prefixes(namespace) {
17116 let scoped = candidates
17117 .iter()
17118 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17119 .cloned()
17120 .collect::<Vec<_>>();
17121 if !scoped.is_empty() {
17122 return scoped;
17123 }
17124 }
17125 }
17126 candidates
17127 .into_iter()
17128 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17129 .collect()
17130}
17131
17132pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17133 let mut namespaces = Vec::new();
17134 let mut current = node.parent();
17135 while let Some(parent) = current {
17136 if parent.kind() == "namespace_definition"
17137 && let Some(name) = parent.child_by_field_name("name")
17138 {
17139 let namespace = normalize_cpp_reference_text(node_text(name, source));
17140 if !namespace.is_empty() {
17141 namespaces.push(namespace);
17142 }
17143 }
17144 current = parent.parent();
17145 }
17146 if namespaces.is_empty() {
17147 None
17148 } else {
17149 namespaces.reverse();
17150 Some(namespaces.join("::"))
17151 }
17152}
17153
17154pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17158 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17159}
17160
17161fn type_owner_resolution(
17162 analyzer: &CppGraphSource<'_>,
17163 code_unit: &CodeUnit,
17164) -> Option<ResolvedTypeOwner> {
17165 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17166}
17167
17168fn target_type_owner_resolution(
17169 analyzer: &CppGraphSource<'_>,
17170 code_unit: &CodeUnit,
17171) -> Option<ResolvedTypeOwner> {
17172 match type_owner_resolution(analyzer, code_unit) {
17173 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17174 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17175 }
17176}
17177
17178fn target_forward_owner_resolution(
17190 analyzer: &CppGraphSource<'_>,
17191 code_unit: &CodeUnit,
17192) -> Option<ResolvedTypeOwner> {
17193 if !code_unit.is_function() {
17194 return None;
17195 }
17196 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17202 let cpp = analyzer.cpp?;
17203 let mut visible_files = HashSet::default();
17204 collect_include_closure(
17205 analyzer,
17206 cpp.include_target_index(),
17207 code_unit.source(),
17208 &mut visible_files,
17209 None,
17210 );
17211 let candidates = analyzer.workspace_definitions().exact(&owner_name);
17212 let visible_candidates = candidates
17213 .iter()
17214 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17215 .cloned()
17216 .collect::<Vec<_>>();
17217 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17218 DirectOwnerResolution::UniqueFull(unit) => {
17219 return Some(ResolvedTypeOwner {
17220 unit,
17221 is_forward_declaration: false,
17222 });
17223 }
17224 DirectOwnerResolution::ForwardsOnly(forwards) => {
17225 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17226 unit: forwards.into_iter().next().unwrap(),
17227 is_forward_declaration: true,
17228 });
17229 }
17230 DirectOwnerResolution::Ambiguous => return None,
17231 DirectOwnerResolution::None => {}
17232 }
17233
17234 let candidates = candidates
17235 .into_iter()
17236 .filter(|candidate| candidate.is_class())
17237 .collect::<Vec<_>>();
17238 let (unit, is_forward_declaration) =
17239 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17240 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17241 DirectOwnerResolution::ForwardsOnly(forwards) => {
17242 (unique_logical_forward_owner(forwards)?, true)
17243 }
17244 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17245 };
17246 Some(ResolvedTypeOwner {
17247 unit,
17248 is_forward_declaration,
17249 })
17250}
17251
17252pub fn precise_parent_of(
17253 analyzer: &CppGraphSource<'_>,
17254 visibility: &VisibilityIndex<'_>,
17255 code_unit: &CodeUnit,
17256) -> Option<CodeUnit> {
17257 visibility.cached_precise_parent_of(analyzer, code_unit)
17258}
17259
17260fn precise_parent_resolution(
17261 analyzer: &CppGraphSource<'_>,
17262 code_unit: &CodeUnit,
17263) -> Option<ResolvedTypeOwner> {
17264 #[cfg(any(test, feature = "test-support"))]
17265 if let Some(cpp) = analyzer.cpp {
17266 cpp.record_cpp_parent_resolution_for_test();
17267 }
17268 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17269 return Some(ResolvedTypeOwner {
17270 unit,
17271 is_forward_declaration: false,
17272 });
17273 }
17274 let fallback = analyzer.parent_of(code_unit);
17275 if !code_unit.owner_is_type_scope() {
17276 return fallback.map(|unit| ResolvedTypeOwner {
17277 unit,
17278 is_forward_declaration: false,
17279 });
17280 }
17281 let owner_fq = code_unit
17282 .fq()
17283 .parent()
17284 .expect("a unit with an owner identifier has a structured parent");
17285 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17286 match same_source_owner(analyzer, code_unit, &owner_candidates) {
17287 DirectOwnerResolution::UniqueFull(owner) => {
17288 return Some(ResolvedTypeOwner {
17289 unit: owner,
17290 is_forward_declaration: false,
17291 });
17292 }
17293 DirectOwnerResolution::Ambiguous => return None,
17294 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17295 }
17296 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17297 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17298 unit: owner,
17299 is_forward_declaration: false,
17300 }),
17301 DirectOwnerResolution::Ambiguous => None,
17302 DirectOwnerResolution::ForwardsOnly(forwards) => {
17303 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17304 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17305 unit: owner,
17306 is_forward_declaration: false,
17307 }),
17308 FullOwnerResolution::None => {
17309 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17310 unit,
17311 is_forward_declaration: true,
17312 })
17313 }
17314 FullOwnerResolution::Ambiguous => None,
17315 }
17316 }
17317 DirectOwnerResolution::None => {
17318 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17319 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17320 unit: owner,
17321 is_forward_declaration: false,
17322 }),
17323 FullOwnerResolution::Ambiguous => None,
17324 FullOwnerResolution::None => fallback
17325 .filter(|parent| {
17326 parent.source() == code_unit.source()
17327 && parent.fq() == &owner_fq
17328 && (!parent.is_class()
17329 || cpp_class_declaration_strength(analyzer, parent)
17330 == CppClassDeclarationStrength::Full)
17331 })
17332 .map(|unit| ResolvedTypeOwner {
17333 unit,
17334 is_forward_declaration: false,
17335 }),
17336 }
17337 }
17338 }
17339}
17340
17341fn exact_structural_type_parent(
17342 analyzer: &CppGraphSource<'_>,
17343 code_unit: &CodeUnit,
17344) -> Option<CodeUnit> {
17345 if !code_unit.is_function() && !code_unit.is_field() {
17346 return None;
17347 }
17348 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
17350 let parent = cpp.structural_parent_of(code_unit)?;
17351 (!parent.is_module()
17352 && parent.source() == code_unit.source()
17353 && parent.package_name() == code_unit.package_name()
17354 && parent.short_name() == encoded_owner)
17355 .then_some(parent)
17356}
17357
17358fn same_source_owner(
17359 analyzer: &CppGraphSource<'_>,
17360 code_unit: &CodeUnit,
17361 owner_candidates: &[CodeUnit],
17362) -> DirectOwnerResolution {
17363 let candidates = owner_candidates
17364 .iter()
17365 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17366 .cloned()
17367 .collect::<Vec<_>>();
17368 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17369 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17370}
17371
17372fn visible_full_cpp_owner(
17373 analyzer: &CppGraphSource<'_>,
17374 code_unit: &CodeUnit,
17375 owner_candidates: &[CodeUnit],
17376) -> FullOwnerResolution {
17377 let Some(cpp) = analyzer.cpp else {
17378 return FullOwnerResolution::None;
17379 };
17380 let mut visible_files = HashSet::default();
17381 collect_include_closure(
17382 analyzer,
17383 cpp.include_target_index(),
17384 code_unit.source(),
17385 &mut visible_files,
17386 None,
17387 );
17388 let candidates = owner_candidates
17389 .iter()
17390 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17391 .cloned()
17392 .collect::<Vec<_>>();
17393 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17394 let mut full_definition = None;
17395 for candidate in candidates {
17396 match cpp_class_declaration_strength(analyzer, &candidate) {
17397 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17398 return FullOwnerResolution::Ambiguous;
17399 }
17400 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17401 CppClassDeclarationStrength::Forward => {}
17402 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17403 }
17404 }
17405 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17406}
17407
17408pub enum DirectOwnerResolution {
17409 None,
17410 ForwardsOnly(Vec<CodeUnit>),
17411 UniqueFull(CodeUnit),
17412 Ambiguous,
17413}
17414
17415enum FullOwnerResolution {
17416 None,
17417 Unique(CodeUnit),
17418 Ambiguous,
17419}
17420
17421#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17422pub enum CppClassDeclarationStrength {
17423 Full,
17424 Forward,
17425 Unknown,
17426}
17427
17428fn directly_included_owner(
17429 analyzer: &CppGraphSource<'_>,
17430 code_unit: &CodeUnit,
17431 owner_candidates: &[CodeUnit],
17432) -> DirectOwnerResolution {
17433 let Some(cpp) = analyzer.cpp else {
17434 return DirectOwnerResolution::None;
17435 };
17436 let imports = analyzer.import_statements(code_unit.source());
17437 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
17438 .into_iter()
17439 .flat_map(|include| {
17440 resolve_include_targets_with_index(
17441 code_unit.source(),
17442 &include,
17443 cpp.include_target_index(),
17444 )
17445 })
17446 .collect();
17447 let candidates = owner_candidates
17448 .iter()
17449 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17450 .cloned()
17451 .collect::<Vec<_>>();
17452 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17453 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17454}
17455
17456fn prefer_member_declaring_owners(
17457 analyzer: &CppGraphSource<'_>,
17458 member: &CodeUnit,
17459 candidates: Vec<CodeUnit>,
17460) -> Vec<CodeUnit> {
17461 let matching = candidates
17462 .iter()
17463 .filter(|owner| owner_declares_member(analyzer, owner, member))
17464 .cloned()
17465 .collect::<Vec<_>>();
17466 if matching.is_empty() {
17467 candidates
17468 } else {
17469 matching
17470 }
17471}
17472
17473fn owner_declares_member(
17474 analyzer: &CppGraphSource<'_>,
17475 owner: &CodeUnit,
17476 member: &CodeUnit,
17477) -> bool {
17478 analyzer.direct_children(owner).into_iter().any(|child| {
17479 child.kind() == member.kind()
17480 && child.identifier() == member.identifier()
17481 && child.signature() == member.signature()
17482 })
17483}
17484
17485fn classify_direct_owner_candidates(
17486 analyzer: &CppGraphSource<'_>,
17487 candidates: impl Iterator<Item = CodeUnit>,
17488) -> DirectOwnerResolution {
17489 collapse_owner_candidates(candidates.map(|candidate| {
17490 let strength = cpp_class_declaration_strength(analyzer, &candidate);
17491 (candidate, strength)
17492 }))
17493}
17494
17495pub fn collapse_owner_candidates(
17496 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17497) -> DirectOwnerResolution {
17498 let mut full_definition = None;
17499 let mut forwards = Vec::new();
17500 for (candidate, strength) in candidates {
17501 match strength {
17502 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17503 return DirectOwnerResolution::Ambiguous;
17504 }
17505 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17506 CppClassDeclarationStrength::Forward => forwards.push(candidate),
17507 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17508 }
17509 }
17510 if let Some(owner) = full_definition {
17511 DirectOwnerResolution::UniqueFull(owner)
17512 } else if !forwards.is_empty() {
17513 DirectOwnerResolution::ForwardsOnly(forwards)
17514 } else {
17515 DirectOwnerResolution::None
17516 }
17517}
17518
17519#[cfg(any(test, feature = "test-support"))]
17520pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17521 unique_logical_forward_owner(forwards)
17522}
17523
17524fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17525 let first = forwards.pop()?;
17526 forwards
17527 .iter()
17528 .all(|forward| same_logical_symbol(forward, &first))
17529 .then_some(first)
17530}
17531
17532pub fn cpp_class_declaration_strength(
17533 analyzer: &CppGraphSource<'_>,
17534 candidate: &CodeUnit,
17535) -> CppClassDeclarationStrength {
17536 let Some(cpp) = analyzer.cpp else {
17544 return uncached_cpp_class_declaration_strength(analyzer, candidate);
17545 };
17546 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17547 return strength;
17548 }
17549 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17550 cpp.cache_class_declaration_strength(candidate, strength);
17551 strength
17552}
17553
17554fn uncached_cpp_class_declaration_strength(
17555 analyzer: &CppGraphSource<'_>,
17556 candidate: &CodeUnit,
17557) -> CppClassDeclarationStrength {
17558 if let Some(cpp) = analyzer.cpp
17559 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17560 {
17561 return cpp_class_declaration_strength_in_tree(
17562 analyzer,
17563 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17564 candidate,
17565 prepared.source(),
17566 prepared.tree().root_node(),
17567 );
17568 }
17569 let Some(source) = analyzer.indexed_source(candidate.source()) else {
17570 return CppClassDeclarationStrength::Unknown;
17571 };
17572 #[cfg(any(test, feature = "test-support"))]
17573 if let Some(cpp) = analyzer.cpp {
17574 cpp.record_cpp_class_strength_parse_for_test();
17575 }
17576 let mut parser = Parser::new();
17577 if parser
17578 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17579 .is_err()
17580 {
17581 return CppClassDeclarationStrength::Unknown;
17582 }
17583 let Some(tree) = parser.parse(&source, None) else {
17584 return CppClassDeclarationStrength::Unknown;
17585 };
17586 let recovered_export_classes =
17589 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17590 cpp_class_declaration_strength_in_tree(
17591 analyzer,
17592 &recovered_export_classes,
17593 candidate,
17594 &source,
17595 tree.root_node(),
17596 )
17597}
17598
17599fn cpp_class_declaration_strength_in_tree(
17600 analyzer: &CppGraphSource<'_>,
17601 recovered_export_classes: &CppRecoveredExportClassIndex,
17602 candidate: &CodeUnit,
17603 source: &str,
17604 root: Node<'_>,
17605) -> CppClassDeclarationStrength {
17606 let ranges = analyzer.ranges(candidate);
17607 let mut saw_forward = false;
17608 for range in ranges {
17609 match recovered_class_body_at(
17612 recovered_export_classes,
17613 root,
17614 source,
17615 candidate.identifier(),
17616 &range,
17617 ) {
17618 Some(true) => return CppClassDeclarationStrength::Full,
17619 Some(false) => {
17620 saw_forward = true;
17621 continue;
17622 }
17623 None => {}
17624 }
17625 let covers_range_start = |node: &Node<'_>| {
17632 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17633 };
17634 let mut stack = Vec::new();
17635 if covers_range_start(&root) {
17636 stack.push(root);
17637 }
17638 while let Some(node) = stack.pop() {
17639 if node.start_byte() == range.start_byte
17640 && node.end_byte() == range.end_byte
17641 && matches!(
17642 node.kind(),
17643 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17644 )
17645 {
17646 if cpp_class_node_has_body(node) {
17647 return CppClassDeclarationStrength::Full;
17648 }
17649 saw_forward = true;
17650 }
17651 let mut cursor = node.walk();
17652 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17653 }
17654 }
17655 if saw_forward {
17656 CppClassDeclarationStrength::Forward
17657 } else {
17658 CppClassDeclarationStrength::Unknown
17659 }
17660}
17661
17662fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17663 node.child_by_field_name("body").is_some() || {
17664 let mut cursor = node.walk();
17665 node.named_children(&mut cursor).any(|child| {
17666 matches!(
17667 child.kind(),
17668 "declaration_list" | "field_declaration_list" | "enumerator_list"
17669 )
17670 })
17671 }
17672}
17673
17674#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17675enum CppCTagKind {
17676 Struct,
17677 Union,
17678}
17679
17680fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
17681 let declaration = analyzer.get_source(code_unit, false)?;
17682 let mut parser = Parser::new();
17683 parser
17684 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17685 .ok()?;
17686 let tree = parser.parse(&declaration, None)?;
17687 let mut stack = vec![tree.root_node()];
17688 while let Some(node) = stack.pop() {
17689 let kind = match node.kind() {
17690 "struct_specifier" => CppCTagKind::Struct,
17691 "union_specifier" => CppCTagKind::Union,
17692 _ => {
17693 let mut cursor = node.walk();
17694 stack.extend(node.named_children(&mut cursor));
17695 continue;
17696 }
17697 };
17698 if node
17699 .child_by_field_name("name")
17700 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
17701 {
17702 return Some(kind);
17703 }
17704 let mut cursor = node.walk();
17705 stack.extend(node.named_children(&mut cursor));
17706 }
17707 None
17708}
17709
17710pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17711 if !code_unit.owner_is_type_scope() {
17712 return None;
17713 }
17714 let owner_fq = code_unit.fq().parent()?;
17715 ctx.analyzer
17716 .workspace_definitions()
17717 .exact(&owner_fq)
17718 .into_iter()
17719 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
17720}
17721
17722pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17723 left.kind() == right.kind()
17724 && left.fq_name() == right.fq_name()
17725 && left.signature() == right.signature()
17726 && left.source() == right.source()
17727}
17728
17729pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17730 same_symbol(left, right) || same_logical_symbol(left, right)
17731}
17732
17733pub fn same_visible_global_field_symbol(
17734 analyzer: &CppGraphSource<'_>,
17735 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
17736 left: &CodeUnit,
17737 right: &CodeUnit,
17738) -> bool {
17739 if same_symbol(left, right) {
17740 return true;
17741 }
17742 if !same_logical_symbol(left, right) {
17743 return false;
17744 }
17745 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
17746 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
17747 {
17748 left.source() == right.source()
17749 } else {
17750 true
17751 }
17752}
17753
17754fn cpp_global_field_has_internal_linkage_cached(
17755 analyzer: &CppGraphSource<'_>,
17756 cache: &mut HashMap<CodeUnit, bool>,
17757 candidate: &CodeUnit,
17758) -> bool {
17759 if let Some(internal) = cache.get(candidate) {
17760 return *internal;
17761 }
17762 #[cfg(any(test, feature = "test-support"))]
17763 note_cpp_global_field_internal_linkage_classification_for_test();
17764 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
17765 cache.insert(candidate.clone(), internal);
17766 internal
17767}
17768
17769#[cfg(any(test, feature = "test-support"))]
17770thread_local! {
17771 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17772}
17773
17774#[cfg(any(test, feature = "test-support"))]
17775fn note_cpp_global_field_internal_linkage_classification_for_test() {
17776 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17777 count.set(count.get() + 1);
17778 });
17779}
17780
17781#[cfg(any(test, feature = "test-support"))]
17782pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
17783 body: impl FnOnce() -> T,
17784) -> (T, usize) {
17785 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17786 count.set(0);
17787 let result = body();
17788 let observed = count.get();
17789 count.set(0);
17790 (result, observed)
17791 })
17792}
17793
17794pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17795 left.kind() == right.kind()
17796 && left.fq_name() == right.fq_name()
17797 && left.signature() == right.signature()
17798}
17799
17800pub fn cpp_global_field_has_internal_linkage(
17801 analyzer: &CppGraphSource<'_>,
17802 candidate: &CodeUnit,
17803) -> bool {
17804 if !candidate.is_field() || candidate.short_name().contains('.') {
17805 return false;
17806 }
17807 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
17808 return false;
17809 };
17810 match local_linkage {
17811 CppFieldLinkage::Internal => true,
17812 CppFieldLinkage::External => false,
17813 CppFieldLinkage::InternalUnlessExternalPeer => {
17814 !cpp_global_field_linkage_peers(analyzer, candidate)
17815 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
17816 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
17817 }
17818 }
17819}
17820
17821fn cpp_global_field_linkage_peers<'a>(
17822 analyzer: &CppGraphSource<'a>,
17823 candidate: &'a CodeUnit,
17824) -> impl Iterator<Item = CodeUnit> + 'a {
17825 let name = candidate.fq().clone();
17826 analyzer
17827 .workspace_definitions()
17828 .exact(&name)
17829 .into_iter()
17830 .filter(move |peer| {
17831 if peer == candidate {
17832 return false;
17833 }
17834 #[cfg(any(test, feature = "test-support"))]
17835 note_cpp_global_field_linkage_peer_inspection_for_test();
17836 same_logical_symbol(peer, candidate)
17837 })
17838}
17839
17840#[cfg(any(test, feature = "test-support"))]
17841thread_local! {
17842 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17843}
17844
17845#[cfg(any(test, feature = "test-support"))]
17846fn note_cpp_global_field_linkage_peer_inspection_for_test() {
17847 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17848 count.set(count.get() + 1);
17849 });
17850}
17851
17852#[cfg(any(test, feature = "test-support"))]
17853pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
17854 body: impl FnOnce() -> T,
17855) -> (T, usize) {
17856 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17857 count.set(0);
17858 let result = body();
17859 let observed = count.get();
17860 count.set(0);
17861 (result, observed)
17862 })
17863}
17864
17865fn cpp_global_field_declaration_linkage(
17866 analyzer: &CppGraphSource<'_>,
17867 candidate: &CodeUnit,
17868) -> Option<CppFieldLinkage> {
17869 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
17870 return Some(linkage);
17871 }
17872 let cpp = analyzer.cpp?;
17873 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
17874 return cpp_global_field_declaration_linkage_in_tree(
17875 analyzer,
17876 candidate,
17877 prepared.source(),
17878 prepared.tree().root_node(),
17879 );
17880 }
17881 let source = analyzer.indexed_source(candidate.source())?;
17882 let mut parser = Parser::new();
17883 if parser
17884 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17885 .is_err()
17886 {
17887 return None;
17888 }
17889 let tree = parser.parse(&source, None)?;
17890 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
17891}
17892
17893fn cpp_global_field_declaration_linkage_in_tree(
17894 analyzer: &CppGraphSource<'_>,
17895 candidate: &CodeUnit,
17896 source: &str,
17897 root: Node<'_>,
17898) -> Option<CppFieldLinkage> {
17899 analyzer.ranges(candidate).iter().find_map(|range| {
17900 node_for_exact_range(root, range)
17901 .and_then(enclosing_cpp_field_declaration)
17902 .map(|declaration| {
17903 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
17905 })
17906 })
17907}
17908
17909fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
17910 loop {
17911 if matches!(node.kind(), "declaration" | "field_declaration") {
17912 return Some(node);
17913 }
17914 node = node.parent()?;
17915 }
17916}
17917
17918#[cfg(test)]
17919mod tests {
17920 #[test]
17921 fn issue_3089_statement_formal_at_end_of_replacement() {
17922 let parameters = vec!["handle".to_owned(), "block".to_owned()];
17923 for replacement in [
17924 "do { header_event_t* event; if ((handle)->active) block } while (0)",
17925 "do { header_event_t* event; block } while (0)",
17926 ] {
17927 assert!(
17928 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
17929 .is_some(),
17930 "{replacement}"
17931 );
17932 }
17933 }
17934 use super::*;
17935
17936 #[test]
17937 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
17938 let source = "int size(void) { return sizeof(((Payload))); }\n";
17939 let mut parser = Parser::new();
17940 parser
17941 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17942 .expect("C++ grammar");
17943 let tree = parser.parse(source, None).expect("fixture tree");
17944 let start = source.find("Payload").expect("sizeof operand");
17945 let node = tree
17946 .root_node()
17947 .named_descendant_for_byte_range(start, start + "Payload".len())
17948 .expect("focused operand");
17949 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
17950 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
17951
17952 assert_eq!(node.kind(), "identifier");
17953 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
17954 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
17955 }
17956
17957 fn parse_cpp(source: &str) -> Tree {
17958 let mut parser = Parser::new();
17959 parser
17960 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17961 .expect("C++ grammar");
17962 parser.parse(source, None).expect("fixture tree")
17963 }
17964
17965 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
17966 let start = source.find(needle).expect("fixture needle");
17967 tree.root_node()
17968 .named_descendant_for_byte_range(start, start + needle.len())
17969 .expect("node at needle")
17970 }
17971
17972 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
17973 let mut parser = Parser::new();
17974 parser
17975 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17976 .expect("C++ grammar");
17977 let tree = parser.parse(source, None).expect("fixture tree");
17978 PreparedSyntaxTree::new(
17979 PreparedSyntaxSource::Exact(Arc::from(source)),
17980 tree,
17981 compute_line_starts(source),
17982 LanguageDialect::Standard(Language::Cpp),
17983 PreparedSourceOrigin::Disk,
17984 None,
17985 )
17986 }
17987
17988 fn unresolved_include_before(source: &str, reference: &str) -> bool {
17989 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
17990 let prepared = prepared_cpp(source);
17991 let facts = collect_structured_include_facts(&prepared);
17992 let include_targets = IncludeTargetIndex::build([&file]);
17993 has_unresolved_include_visible_before_in_prepared(
17994 &file,
17995 &prepared,
17996 &include_targets,
17997 &facts,
17998 source.find(reference).expect("reference fixture"),
17999 )
18000 }
18001
18002 #[test]
18003 fn unresolved_include_before_reference_is_visible() {
18004 let source = "#include \"missing.h\"\nint use = Missing;\n";
18005 assert!(unresolved_include_before(source, "Missing"));
18006 }
18007
18008 #[test]
18009 fn unresolved_include_after_reference_is_not_visible() {
18010 let source = "int use = Missing;\n#include \"missing.h\"\n";
18011 assert!(!unresolved_include_before(source, "Missing"));
18012 }
18013
18014 #[test]
18015 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18016 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18017 assert!(!unresolved_include_before(source, "Missing"));
18018 }
18019
18020 #[test]
18021 fn unresolved_include_in_current_branch_is_visible() {
18022 let source =
18023 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18024 assert!(unresolved_include_before(source, "Missing"));
18025 }
18026
18027 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18032namespace matchers {
18033 namespace detail {
18034 class API [[nodiscard]] First {
18035 public:
18036 int value() const { return count_ + 1; }
18037 private:
18038 int count_;
18039 };
18040 class API [[nodiscard]] Second {
18041 public:
18042 int value() const { return count_ + 2; }
18043 private:
18044 int count_;
18045 };
18046 } // namespace detail
18047
18048 template <typename T>
18049 void tail_function(MatcherBase<T> const& value);
18050
18051 class TailClass {};
18052} // namespace matchers
18053} // namespace app
18054
18055struct AfterAll {};
18056"#;
18057
18058 #[test]
18059 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18060 let source = STOLEN_BRACE_CASCADE;
18061 let tree = parse_cpp(source);
18062 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18063
18064 let tail_class = named_node_at(&tree, source, "TailClass");
18065 assert!(
18066 !has_ancestor_kind(tail_class, "namespace_definition"),
18067 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18068 );
18069 let displaced = named_node_at(&tree, source, "Second");
18070 assert_eq!(
18071 enclosing_namespace_components(displaced, source),
18072 Some(vec!["app".to_string(), "matchers".to_string()]),
18073 "the fixture must displace the second class out of detail"
18074 );
18075
18076 let components = |needle: &str| {
18077 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18078 };
18079 assert_eq!(components("First"), ["app", "matchers", "detail"]);
18080 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18081 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18082 assert_eq!(components("tail_function"), ["app", "matchers"]);
18083 assert_eq!(components("TailClass"), ["app", "matchers"]);
18084 assert!(components("AfterAll").is_empty());
18085 }
18086
18087 #[test]
18088 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18089 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18090 let tree = parse_cpp(clean);
18091 assert!(!tree.root_node().has_error());
18092 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18093
18094 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18097 let tree = parse_cpp(damaged);
18098 assert!(tree.root_node().has_error());
18099 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18100 assert_eq!(
18101 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18102 ["a"]
18103 );
18104 }
18105
18106 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18113
18114 class Target {
18115 int value_;
18116 };
18117
18118 template <typename T>
18119 class Holder {
18120 public:
18121 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18122
18123#define HOLDER_DEFINE_OP( id, op ) \
18124 template <typename U> \
18125 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
18126 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
18127 return Target{}; \
18128 }
18129
18130 HOLDER_DEFINE_OP( equal, == )
18131#undef HOLDER_DEFINE_OP
18132 T m_lhs;
18133 };
18134
18135 class Tail {};
18136}
18137"#;
18138
18139 #[test]
18140 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18141 let source = COLLAPSED_NAMESPACE_HEAD;
18142 let tree = parse_cpp(source);
18143 let target = named_node_at(&tree, source, "class Target");
18144
18145 assert!(
18146 !has_ancestor_kind(target, "namespace_definition"),
18147 "the fixture must reproduce the collapse: the class has no namespace ancestor"
18148 );
18149 let head = target.parent().expect("the collapsed namespace envelope");
18150 assert_eq!(
18151 head.kind(),
18152 "ERROR",
18153 "the fixture must keep the namespace head in an ERROR node"
18154 );
18155
18156 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18157 assert_eq!(
18158 index.enclosing_namespace_components(target, source),
18159 ["app"]
18160 );
18161 }
18162
18163 #[test]
18164 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18165 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18166 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18167 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18168 assert!(indexed_namespace_path_is_recoverable(
18169 &["cache".to_string()],
18170 &indexed,
18171 1,
18172 ));
18173 }
18174
18175 #[test]
18176 fn sort_lookup_units_totally_orders_every_identity_field() {
18177 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18178 let base = CodeUnit::with_signature(
18179 file.clone(),
18180 CodeUnitType::Function,
18181 "scope",
18182 "value",
18183 Some("()".to_string()),
18184 false,
18185 );
18186 let different_kind = CodeUnit::with_signature(
18187 file.clone(),
18188 CodeUnitType::Field,
18189 "scope",
18190 "value",
18191 Some("()".to_string()),
18192 false,
18193 );
18194 let synthetic = base.with_synthetic(true);
18195
18196 let interner = segment_interner();
18197 let mut member_fq = FqName::new();
18198 member_fq.push(interner.intern("scope", SegmentKind::Package));
18199 member_fq.push(interner.intern("value", SegmentKind::Member));
18200 let different_package_boundary = CodeUnit::from_fq(
18201 file.clone(),
18202 CodeUnitType::Function,
18203 member_fq,
18204 0,
18205 Some("()".to_string()),
18206 false,
18207 );
18208
18209 let mut unknown_fq = FqName::new();
18210 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18211 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18212 let different_segment_kind = CodeUnit::from_fq(
18213 file,
18214 CodeUnitType::Function,
18215 unknown_fq,
18216 1,
18217 Some("()".to_string()),
18218 false,
18219 );
18220
18221 let input = vec![
18222 base,
18223 different_kind,
18224 synthetic,
18225 different_package_boundary,
18226 different_segment_kind,
18227 ];
18228 let mut expected = input.clone();
18229 sort_lookup_units(&mut expected);
18230 assert!(expected.windows(2).all(|pair| {
18231 let mut ordered = pair.to_vec();
18232 sort_lookup_units(&mut ordered);
18233 ordered == pair && pair[0] != pair[1]
18234 }));
18235
18236 let mut reversed = input.clone();
18237 reversed.reverse();
18238 sort_lookup_units(&mut reversed);
18239 assert_eq!(reversed, expected);
18240
18241 let mut rotated = input;
18242 rotated.rotate_left(2);
18243 sort_lookup_units(&mut rotated);
18244 assert_eq!(rotated, expected);
18245 }
18246
18247 #[test]
18248 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18249 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";
18250 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18251 let parse = |source: &str| {
18252 let mut parser = Parser::new();
18253 parser
18254 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18255 .expect("C++ grammar");
18256 parser.parse(source, None).expect("fixture tree")
18257 };
18258
18259 let tree = parse(damaged);
18260 let root = tree.root_node();
18261 let target = damaged.find("target").expect("target byte");
18262 let declaration = root
18263 .descendant_for_byte_range(target, target + "target".len())
18264 .and_then(|mut node| {
18265 loop {
18266 if node.kind() == "declaration" {
18267 break Some(node);
18268 }
18269 node = node.parent()?;
18270 }
18271 })
18272 .expect("declaration after the displaced terminator");
18273 let conditional = declaration
18274 .parent()
18275 .filter(|node| node.kind() == "preproc_ifdef")
18276 .expect("damaged inner conditional");
18277 let outer = conditional
18278 .parent()
18279 .filter(|node| node.kind() == "preproc_ifdef")
18280 .expect("ordinary outer include guard");
18281 let terminator = cpp_displaced_preprocessor_terminator(conditional)
18282 .expect("structured displaced #endif");
18283 assert_eq!(node_text(terminator, damaged), "#endif");
18284 assert!(terminator.end_byte() <= declaration.start_byte());
18285 assert!(!preprocessor_conditional_contains_descendant(
18286 conditional,
18287 declaration
18288 ));
18289 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18290 assert!(preprocessor_conditional_contains_descendant(
18291 outer,
18292 declaration
18293 ));
18294
18295 let tree = parse(guarded);
18296 let conditional = tree
18297 .root_node()
18298 .named_child(0)
18299 .filter(|node| node.kind() == "preproc_ifdef")
18300 .expect("ordinary conditional");
18301 let declaration = conditional
18302 .named_children(&mut conditional.walk())
18303 .find(|node| node.kind() == "declaration")
18304 .expect("guarded declaration");
18305 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18306 assert!(preprocessor_conditional_contains_descendant(
18307 conditional,
18308 declaration
18309 ));
18310
18311 let damaged_alternative = format!(
18312 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18313 "UNUSED(value)\n".repeat(64)
18314 );
18315 let tree = parse(&damaged_alternative);
18316 let conditional = tree
18317 .root_node()
18318 .named_child(0)
18319 .filter(|node| node.kind() == "preproc_ifdef")
18320 .expect("outer conditional with an alternative");
18321 assert!(conditional.has_error());
18322 assert!(conditional.child_by_field_name("alternative").is_some());
18323 assert!(
18324 conditional
18325 .child(conditional.child_count() - 1)
18326 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18327 );
18328 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18329
18330 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";
18331 let tree = parse(split_declaration);
18332 let root = tree.root_node();
18333 let conditional = root
18334 .named_children(&mut root.walk())
18335 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18336 .expect("split declaration conditional");
18337 let target = split_declaration
18338 .find("static int target")
18339 .expect("target byte");
18340 let boundary =
18341 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18342 assert!(boundary.end_byte <= target, "{boundary:?}");
18343 assert_eq!(boundary.end_line, 9, "{boundary:?}");
18344 let target_node = root
18345 .descendant_for_byte_range(target, target + "static".len())
18346 .expect("target node");
18347 assert!(!preprocessor_conditional_contains_descendant(
18348 conditional,
18349 target_node
18350 ));
18351 }
18352
18353 #[test]
18354 fn fragmented_reference_guard_is_recovered() {
18355 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";
18356 let mut parser = Parser::new();
18357 parser
18358 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18359 .expect("C++ grammar");
18360 let tree = parser.parse(source, None).expect("fixture tree");
18361 let start = source.rfind("helper").expect("reference byte");
18362 let node = tree
18363 .root_node()
18364 .descendant_for_byte_range(start, start + "helper".len())
18365 .expect("reference node");
18366 let mut expected = HashSet::default();
18367 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18368 vec![
18369 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18370 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18371 ],
18372 )));
18373 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18374 }
18375
18376 #[test]
18377 fn expression_defined_and_ifndef_guards_are_incompatible() {
18378 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18379 let mut parser = Parser::new();
18380 parser
18381 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18382 .expect("C++ grammar");
18383 let tree = parser.parse(source, None).expect("fixture tree");
18384 let root = tree.root_node();
18385 let selected_start = source.find("selected").expect("selected declaration");
18386 let rejected_start = source.find("rejected").expect("rejected declaration");
18387 let selected = root
18388 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18389 .expect("selected node");
18390 let rejected = root
18391 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18392 .expect("rejected node");
18393 let selected_guards =
18394 preprocessor_guard_environment(selected, source).expect("selected guards");
18395 let rejected_guards =
18396 preprocessor_guard_environment(rejected, source).expect("rejected guards");
18397
18398 assert!(
18399 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18400 "opposite spellings of one macro guard must contradict"
18401 );
18402 }
18403
18404 #[test]
18405 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18406 let source = r#"#ifdef _WIN32
18407#if defined(__cplusplus)
18408extern "C"
18409#endif
18410int platform_api(void);
18411#endif
18412
18413#ifdef _WIN32
18414static int entropy_target(void) { return 0; }
18415#else
18416#ifdef HAVE_COMMON_RANDOM
18417static int other_target(void) { return 0; }
18418#elif defined(HAVE_GETENTROPY)
18419static int entropy_target(void) { return 1; }
18420static int use_entropy(void) { return entropy_target(); }
18421#endif
18422#endif
18423"#;
18424 let mut parser = Parser::new();
18425 parser
18426 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18427 .expect("C++ grammar");
18428 let tree = parser.parse(source, None).expect("fixture tree");
18429 let start = source.rfind("entropy_target()").expect("reference");
18430 let node = tree
18431 .root_node()
18432 .descendant_for_byte_range(start, start + "entropy_target".len())
18433 .expect("reference node");
18434 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18435 assert!(
18436 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18437 "{guards:#?}"
18438 );
18439 assert!(
18440 guards.contains(&PreprocessorGuard::Undefined(
18441 "HAVE_COMMON_RANDOM".to_string()
18442 )),
18443 "{guards:#?}"
18444 );
18445 assert!(
18446 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18447 "{guards:#?}"
18448 );
18449 assert!(
18450 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18451 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18452 );
18453 }
18454
18455 #[test]
18456 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18457 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";
18458 let mut parser = Parser::new();
18459 parser
18460 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18461 .expect("C++ grammar");
18462 let tree = parser.parse(source, None).expect("fixture tree");
18463 let root = tree.root_node();
18464 let node_at = |text: &str, start: usize| {
18465 root.descendant_for_byte_range(start, start + text.len())
18466 .expect("token node")
18467 };
18468
18469 let key_start = source.find("case KEY").expect("case label") + "case ".len();
18470 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18471 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18472 assert!(!is_ordinary_macro_reference_node(node_at(
18473 "ENABLE_KEYS",
18474 guard_start,
18475 )));
18476 }
18477
18478 #[test]
18479 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18480 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";
18481 let mut parser = Parser::new();
18482 parser
18483 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18484 .expect("C++ grammar");
18485 let tree = parser.parse(source, None).expect("fixture tree");
18486 let root = tree.root_node();
18487 let definition_start = source.find("target(void)").expect("definition");
18488 let reference_start = source.rfind("target()").expect("reference");
18489 let definition = root
18490 .descendant_for_byte_range(definition_start, definition_start + "target".len())
18491 .expect("definition node");
18492 let reference = root
18493 .descendant_for_byte_range(reference_start, reference_start + "target".len())
18494 .expect("reference node");
18495 let required =
18496 preprocessor_guard_environment(definition, source).expect("definition guard");
18497 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
18498 assert!(guard_requirements_hold_at_reference(
18499 &required,
18500 Some(&active)
18501 ));
18502 }
18503
18504 #[test]
18505 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
18506 let source = "g_autoptr(FuChunkArray) self = make_array();";
18507 let mut parser = Parser::new();
18508 parser
18509 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18510 .expect("C++ grammar");
18511 let tree = parser.parse(source, None).expect("fixture tree");
18512 let statement = tree.root_node().named_child(0).expect("statement");
18513 let binding =
18514 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
18515 assert_eq!(binding.name, "self");
18516 assert_eq!(binding.type_name, "FuChunkArray");
18517 assert_eq!(binding.pointer_depth, 1);
18518
18519 let near_miss = "holder(FuChunkArray) self = make_array();";
18520 let tree = parser.parse(near_miss, None).expect("near-miss tree");
18521 let statement = tree.root_node().named_child(0).expect("statement");
18522 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
18523 }
18524
18525 #[test]
18526 fn boolean_guard_normalization_proves_equivalence_and_implication() {
18527 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
18528 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
18529 let negated_windows_branch =
18530 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
18531 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
18532 assert_eq!(negated_windows_branch, portable);
18533
18534 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
18535 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
18536 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
18537 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
18538 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
18539 assert!(fallback_branch.implies(&fallback_declaration));
18540 assert!(
18541 BooleanGuardExpression::Truthy("FEATURE".to_string())
18542 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
18543 );
18544 assert!(
18545 BooleanGuardExpression::Undefined("FEATURE".to_string())
18546 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
18547 );
18548 assert!(
18549 !BooleanGuardExpression::Defined("FEATURE".to_string())
18550 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
18551 );
18552 assert!(!fallback_declaration.implies(&fallback_branch));
18553 }
18554
18555 #[test]
18556 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
18557 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";
18558 let mut parser = Parser::new();
18559 parser
18560 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18561 .expect("C++ grammar");
18562 let tree = parser.parse(source, None).expect("fixture tree");
18563 let root = tree.root_node();
18564 let call = |marker: &str| {
18565 let start = source.find(marker).expect("call marker");
18566 let mut node = root
18567 .descendant_for_byte_range(start, start + "helper".len())
18568 .expect("call name node");
18569 loop {
18570 if node.kind() == "call_expression" {
18571 break node;
18572 }
18573 node = node.parent().expect("call expression ancestor");
18574 }
18575 };
18576 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
18577 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
18578 let keyword_call = call("helper(NULL, template); /* bound */");
18579 let keyword_arguments = keyword_call
18580 .child_by_field_name("arguments")
18581 .expect("keyword argument list");
18582 assert_eq!(
18583 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
18584 1
18585 );
18586 assert_eq!(
18587 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
18588 0
18589 );
18590
18591 let unbound_call = call("helper(NULL, template); /* unbound */");
18592 let unbound_arguments = unbound_call
18593 .child_by_field_name("arguments")
18594 .expect("unbound argument list");
18595 assert_eq!(
18596 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
18597 0
18598 );
18599 }
18600
18601 #[test]
18602 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
18603 let source = r#"#define MAKE(type) type *value
18604MAKE(int *);
18605typedef struct Item Item;
18606struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
18607void gen_op_movl(void *s, int first, int second) { }
18608const char *strZ(const char *value) { return value; }
18609int body(void *s) {
18610 MAKE(int *);
18611 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
18612 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
18613 execvp(strZ(value), UNCONSTIFY(char **, args));
18614}
18615 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
18616 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
18617int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
18618 if (d->units) {
18619 switch (target->nodetype) {
18620 case 1:
18621 case 2:
18622 break;
18623 default:
18624 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
18625 }
18626 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
18627 lysdict_remove(ctx, ((struct Item *)target)->units);
18628 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
18629 }
18630 return 0;
18631 }
18632STATIC EFI_STATUS Encode () { return 0; }
18633"#;
18634 let tree = parse_cpp(source);
18635 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
18636 let top_macro = tree
18637 .root_node()
18638 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
18639 .expect("top macro node");
18640 let body_macro_start = source
18641 .match_indices("MAKE(int *);")
18642 .nth(1)
18643 .expect("body macro")
18644 .0;
18645 let body_macro = tree
18646 .root_node()
18647 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
18648 .expect("body macro node");
18649 let function_call_start = source
18650 .find("gen_op_movl(s, offsetof(CPUX86State")
18651 .expect("function call");
18652 let function_call = tree
18653 .root_node()
18654 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
18655 .expect("function call node");
18656 let strz_start = source.find("strZ(value)").expect("nested function call");
18657 let strz = tree
18658 .root_node()
18659 .named_descendant_for_byte_range(strz_start, strz_start + 4)
18660 .expect("nested function call node");
18661 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
18662 let recovered_call = tree
18663 .root_node()
18664 .named_descendant_for_byte_range(
18665 recovered_call_start,
18666 recovered_call_start + "lysdict_remove".len(),
18667 )
18668 .expect("recovered call node");
18669 let binder_start = source.find("Encode").expect("binder");
18670 let binder = tree
18671 .root_node()
18672 .named_descendant_for_byte_range(binder_start, binder_start + 6)
18673 .expect("binder node");
18674
18675 assert!(recovered_c_function_declarator_invocation(top_macro));
18676 assert!(recovered_c_function_declarator_invocation(body_macro));
18677 assert!(recovered_c_function_declarator_invocation(function_call));
18678 assert!(recovered_c_function_declarator_invocation(strz));
18679 assert!(recovered_c_function_declarator_invocation(recovered_call));
18680 assert!(!recovered_c_function_declarator_invocation(binder));
18681 }
18682
18683 #[test]
18684 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
18685 let source = r#"typedef int krb5_context;
18686int helper(int first, int second) { return first + second; }
18687static krb5_context ctx;
18688int main(int argc, char **argv) {
18689 int ccinitial;
18690 const char *collection_name, *typename;
18691 typename = helper(ctx, ccinitial);
18692 return 0;
18693}
18694"#;
18695 let tree = parse_cpp(source);
18696 let ctx = tree
18697 .root_node()
18698 .descendant_for_byte_range(
18699 source.find("ctx, ccinitial").expect("ctx argument"),
18700 source.find("ctx, ccinitial").expect("ctx argument") + 3,
18701 )
18702 .expect("ctx node");
18703 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
18704 let ccinitial = tree
18705 .root_node()
18706 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
18707 .expect("sibling node");
18708 let typename = named_node_at(&tree, source, "typename = helper");
18709 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
18710
18711 assert_eq!(ctx.kind(), "identifier");
18712 assert!(recovered_c_parenthesized_declarator_reference(ctx));
18713 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
18714 assert!(!recovered_c_parenthesized_declarator_reference(typename));
18715 assert!(!recovered_c_parenthesized_declarator_reference(helper));
18716 }
18717
18718 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
18719 let mut parser = Parser::new();
18720 parser
18721 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18722 .expect("C++ grammar");
18723 let tree = parser.parse(source, None).expect("C++ fixture tree");
18724 let mut stack = vec![tree.root_node()];
18725 while let Some(node) = stack.pop() {
18726 if node.kind() == "enum_specifier" {
18727 return flattened_macro_namespace_components(node, source);
18728 }
18729 let mut cursor = node.walk();
18730 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
18731 stack.extend(children.into_iter().rev());
18732 }
18733 None
18734 }
18735
18736 #[test]
18737 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
18738 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18739namespace detail
18740{
18741enum class value_t { null };
18742}
18743NLOHMANN_JSON_NAMESPACE_END
18744NLOHMANN_JSON_NAMESPACE_BEGIN
18745namespace next
18746{
18747struct next_type {};
18748}
18749NLOHMANN_JSON_NAMESPACE_END
18750"#;
18751 assert_eq!(
18752 first_enum_flattened_namespace(complete),
18753 Some(vec!["detail".to_string()])
18754 );
18755
18756 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
18757 assert_eq!(
18758 first_enum_flattened_namespace(&stale_end),
18759 Some(vec!["detail".to_string()]),
18760 "a stale end marker before the begin marker must not replace the intended namespace"
18761 );
18762
18763 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18764namespace detail
18765{
18766enum class value_t { null };
18767}
18768struct next_type {};
18769"#;
18770 assert_eq!(first_enum_flattened_namespace(incomplete), None);
18771 }
18772}
18773
18774#[cfg(test)]
18790mod lookup_order_properties {
18791 use super::*;
18792 use proptest::prelude::*;
18793
18794 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
18798 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
18799 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
18803 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
18804 const KINDS: [CodeUnitType; 6] = [
18805 CodeUnitType::Class,
18806 CodeUnitType::Function,
18807 CodeUnitType::Field,
18808 CodeUnitType::Module,
18809 CodeUnitType::Macro,
18810 CodeUnitType::FileScope,
18811 ];
18812
18813 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18816 enum ProbedOrder {
18817 Before,
18818 Tied,
18819 After,
18820 Contradictory,
18823 }
18824
18825 impl ProbedOrder {
18826 fn mirror(self) -> Self {
18827 match self {
18828 ProbedOrder::Before => ProbedOrder::After,
18829 ProbedOrder::After => ProbedOrder::Before,
18830 other => other,
18831 }
18832 }
18833
18834 fn signum(self) -> i8 {
18836 match self {
18837 ProbedOrder::Before => -1,
18838 ProbedOrder::Tied => 0,
18839 ProbedOrder::After => 1,
18840 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
18841 }
18842 }
18843 }
18844
18845 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
18853 if left == right {
18854 return ProbedOrder::Tied;
18857 }
18858 let mut forward = vec![left.clone(), right.clone()];
18859 sort_lookup_units(&mut forward);
18860 let mut backward = vec![right.clone(), left.clone()];
18861 sort_lookup_units(&mut backward);
18862 let left_first = backward[0] == *left;
18863 let right_first = forward[0] == *right;
18864 match (left_first, right_first) {
18865 (true, true) => ProbedOrder::Contradictory,
18866 (true, false) => ProbedOrder::Before,
18867 (false, true) => ProbedOrder::After,
18868 (false, false) => ProbedOrder::Tied,
18869 }
18870 }
18871
18872 fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
18875 let interner = segment_interner();
18876 unit.fq()
18877 .segments()
18878 .iter()
18879 .map(|&id| {
18880 let (text, kind) = interner.resolve(id);
18881 (kind.name(), text)
18882 })
18883 .collect()
18884 }
18885
18886 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
18887 (
18888 0..ROOT_NAMES.len(),
18889 0..REL_PATHS.len(),
18890 0..KINDS.len(),
18891 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
18892 0..3usize,
18893 0..SIGNATURES.len(),
18894 any::<bool>(),
18895 )
18896 .prop_map(
18897 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
18898 let source = ProjectFile::new(
18899 std::env::temp_dir().join(ROOT_NAMES[root]),
18900 REL_PATHS[rel_path],
18901 );
18902 let interner = segment_interner();
18903 let mut fq = FqName::new();
18904 for (atom, segment_kind) in &segments {
18905 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
18906 }
18907 let package_segment_count = package_prefix % fq.len();
18909 CodeUnit::from_fq(
18910 source,
18911 KINDS[kind],
18912 fq,
18913 package_segment_count,
18914 SIGNATURES[signature].map(str::to_string),
18915 synthetic,
18916 )
18917 },
18918 )
18919 }
18920
18921 proptest! {
18922 #![proptest_config(ProptestConfig::with_cases(256))]
18923
18924 #[test]
18927 fn lookup_order_is_reflexive_and_dual(
18928 left in code_unit_strategy(),
18929 right in code_unit_strategy(),
18930 ) {
18931 prop_assert_eq!(
18932 probe_order(&left, &left),
18933 ProbedOrder::Tied,
18934 "a unit must tie with itself: {:?}",
18935 left
18936 );
18937 let forward = probe_order(&left, &right);
18938 prop_assert_ne!(
18939 forward,
18940 ProbedOrder::Contradictory,
18941 "comparator put each of these strictly first: left={:?} right={:?}",
18942 left,
18943 right
18944 );
18945 prop_assert_eq!(
18946 probe_order(&right, &left),
18947 forward.mirror(),
18948 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
18949 left,
18950 right
18951 );
18952 }
18953
18954 #[test]
18956 fn lookup_order_is_transitive(
18957 a in code_unit_strategy(),
18958 b in code_unit_strategy(),
18959 c in code_unit_strategy(),
18960 ) {
18961 let ab = probe_order(&a, &b);
18962 let bc = probe_order(&b, &c);
18963 let ac = probe_order(&a, &c);
18964 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
18965 prop_assert_ne!(
18966 probed,
18967 ProbedOrder::Contradictory,
18968 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
18969 pair,
18970 a,
18971 b,
18972 c
18973 );
18974 }
18975 if ab.signum() <= 0 && bc.signum() <= 0 {
18976 prop_assert!(
18977 ac.signum() <= 0,
18978 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
18979 a={:?} b={:?} c={:?}",
18980 ab,
18981 bc,
18982 ac,
18983 a,
18984 b,
18985 c
18986 );
18987 }
18988 }
18989
18990 #[test]
18993 fn lookup_order_separates_distinct_identities(
18994 left in code_unit_strategy(),
18995 right in code_unit_strategy(),
18996 ) {
18997 if probe_order(&left, &right) == ProbedOrder::Tied {
18998 prop_assert_eq!(
18999 &left,
19000 &right,
19001 "distinct identities tied, so their order is whatever order they \
19002 arrived in: left_segments={:?} right_segments={:?}",
19003 fq_segments(&left),
19004 fq_segments(&right)
19005 );
19006 }
19007 }
19008
19009 #[test]
19012 fn lookup_sort_is_permutation_invariant(
19013 units in prop::collection::vec(code_unit_strategy(), 1..=8),
19014 ) {
19015 let mut sorted = units.clone();
19016 sort_lookup_units(&mut sorted);
19017 for rotation in 0..units.len() {
19018 for reversed in [false, true] {
19019 let mut permuted = units.clone();
19020 permuted.rotate_left(rotation);
19021 if reversed {
19022 permuted.reverse();
19023 }
19024 sort_lookup_units(&mut permuted);
19025 prop_assert_eq!(
19026 &permuted,
19027 &sorted,
19028 "sorting a permutation gave a different list \
19029 (rotation={}, reversed={}): input={:?}",
19030 rotation,
19031 reversed,
19032 units
19033 );
19034 }
19035 }
19036 }
19037 }
19038}