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, node_range};
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 binds_indirectly: bool,
1742 template_arguments: Option<Vec<CppTemplateExpression>>,
1743}
1744
1745#[derive(Clone, PartialEq, Eq)]
1746enum StructuredAliasTarget {
1747 Builtin,
1748 Named {
1749 components: Vec<String>,
1750 global: bool,
1751 arguments: Option<Vec<CppTemplateExpression>>,
1752 },
1753}
1754
1755struct CppAlias {
1756 name: String,
1757 target: String,
1758 namespace: Option<String>,
1759}
1760
1761type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1762
1763#[derive(Debug, Clone, PartialEq, Eq)]
1767pub enum CppTemplateResolutionError {
1768 AliasCycle { alias: CodeUnit },
1770 ArgumentBinding,
1772 Substitution,
1774 PrimarySelection,
1777 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1780}
1781
1782fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1785 let mut distinct: Vec<CodeUnit> = Vec::new();
1786 for unit in units {
1787 if !distinct
1788 .iter()
1789 .any(|existing| same_visible_symbol(existing, unit))
1790 {
1791 distinct.push(unit.clone());
1792 }
1793 }
1794 distinct
1795}
1796
1797pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1800 visibility: Factory,
1801 file: &ProjectFile,
1802 root: Node<'_>,
1803 source: &str,
1804 max_references: usize,
1805 cancelled: impl FnMut() -> bool,
1806) -> MacroLexicalReferences
1807where
1808 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1809{
1810 crate::graph::macro_lexical::all_references(
1811 visibility,
1812 file,
1813 root,
1814 source,
1815 max_references,
1816 cancelled,
1817 )
1818}
1819
1820impl<'a> VisibilityIndex<'a> {
1821 pub fn cpp(&self) -> &'a dyn CppSource {
1822 self.cpp
1823 }
1824
1825 pub fn token(&self) -> QueryToken<'a> {
1827 self.token
1828 }
1829
1830 pub fn has_unresolved_include_visible_before(
1838 &self,
1839 file: &ProjectFile,
1840 before_byte: usize,
1841 ) -> bool {
1842 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1843 return false;
1844 };
1845 let cell = self
1846 .structured_include_fact_cells
1847 .lock()
1848 .expect("C++ structured include-fact cache poisoned")
1849 .entry(file.clone())
1850 .or_default()
1851 .clone();
1852 let facts = cell.get_or_init(|| collect_structured_include_facts(prepared.as_ref()));
1853 has_unresolved_include_visible_before_in_prepared(
1854 file,
1855 prepared.as_ref(),
1856 self.cpp.include_target_index(),
1857 facts,
1858 before_byte,
1859 )
1860 }
1861
1862 #[cfg(any(test, feature = "test-support"))]
1870 pub fn from_visible_files_for_test(
1871 cpp: &'a dyn CppSource,
1872 token: QueryToken<'a>,
1873 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1874 ) -> Self {
1875 let visible_source_files_by_root = visible_by_file
1876 .iter()
1877 .map(|(file, visible)| {
1878 (
1879 file.clone(),
1880 visible
1881 .iter()
1882 .map(|unit| unit.source().clone())
1883 .chain(std::iter::once(file.clone()))
1884 .collect(),
1885 )
1886 })
1887 .collect();
1888 let mut global_field_internal_linkage = HashMap::default();
1889 Self {
1890 cpp,
1891 token,
1892 visible_by_identifier: build_visible_identifier_index(
1893 &CppGraphSource::from_source(cpp, token),
1894 &visible_by_file,
1895 &visible_source_files_by_root,
1896 &mut global_field_internal_linkage,
1897 ),
1898 global_field_internal_linkage,
1899 visible_by_file,
1900 visible_source_files_by_root,
1901 alias_cells: Mutex::new(HashMap::default()),
1902 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1903 parser_alias_target_matches: RwLock::new(HashMap::default()),
1904 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1905 project_using_index: OnceLock::new(),
1906 callable_reference_specs: Mutex::new(HashMap::default()),
1907 structured_include_fact_cells: Mutex::new(HashMap::default()),
1908 include_activation_cells: Mutex::new(HashMap::default()),
1909 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1910 include_path_admission_cells: Mutex::new(HashMap::default()),
1911 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1912 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1913 conditional_include_projection_state_count: AtomicUsize::new(0),
1914 conditional_include_target_state_count: AtomicUsize::new(0),
1915 include_activation_build_count: AtomicUsize::new(0),
1916 using_donor_activation_count: AtomicUsize::new(0),
1917 using_namespace_lookup_count: AtomicUsize::new(0),
1918 using_name_candidate_inspection_count: AtomicUsize::new(0),
1919 callable_reference_spec_build_count: AtomicUsize::new(0),
1920 alias_source_parse_counts: Mutex::new(HashMap::default()),
1921 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1922 parser_alias_fallback_calls: AtomicUsize::new(0),
1923 parser_alias_fallback_files: AtomicUsize::new(0),
1924 parser_alias_source_parses: AtomicUsize::new(0),
1925 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1926 field_type_facts: Mutex::new(HashMap::default()),
1927 structured_alias_targets: Mutex::new(HashMap::default()),
1928 callable_comparables: Mutex::new(HashMap::default()),
1929 comparable_name_declarations: Mutex::new(HashMap::default()),
1930 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1931 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1932 precise_parent_cache: Mutex::new(HashMap::default()),
1933 c_tag_kind_cache: Mutex::new(HashMap::default()),
1934 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1935 macro_event_cells: Mutex::new(HashMap::default()),
1936 macro_event_name_sets: Mutex::new(HashMap::default()),
1937 macro_include_protection_cells: Mutex::new(HashMap::default()),
1938 macro_environment_checkpoints: Mutex::new(HashMap::default()),
1939 macro_replacements: Mutex::new(HashMap::default()),
1940 macro_local_binding_templates: Mutex::new(HashMap::default()),
1941 macro_lexical_templates: Mutex::new(HashMap::default()),
1942 macro_replacement_bodies: Mutex::new(HashMap::default()),
1943 macro_type_parameters: Mutex::new(HashMap::default()),
1944 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1945 macro_replacement_parse_count: AtomicUsize::new(0),
1946 macro_event_application_count: AtomicUsize::new(0),
1947 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1948 macro_environment_copy_count: AtomicUsize::new(0),
1949 macro_environment_request_count: AtomicUsize::new(0),
1950 cpp_template_metadata: HashMap::default(),
1951 cpp_template_families: HashMap::default(),
1952 qualified_candidate_inspections: AtomicUsize::new(0),
1953 target_preserving_type_resolution_count: AtomicUsize::new(0),
1954 visibility_identifier_lookup_count: 0,
1955 visibility_identifier_batch_count: 0,
1956 }
1957 }
1958
1959 fn cpp_source(&self) -> CppGraphSource<'a> {
1966 CppGraphSource::from_source(self.cpp, self.token)
1967 }
1968
1969 pub fn build(
1970 cpp: &'a dyn CppSource,
1971 token: QueryToken<'a>,
1972 analyzer: &CppGraphSource<'_>,
1973 roots: &HashSet<ProjectFile>,
1974 ) -> Self {
1975 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1976 }
1977
1978 pub fn build_with_cancellation(
1979 cpp: &'a dyn CppSource,
1980 token: QueryToken<'a>,
1981 analyzer: &CppGraphSource<'_>,
1982 roots: &HashSet<ProjectFile>,
1983 cancellation: Option<&CancellationToken>,
1984 ) -> Self {
1985 let visibility_started = Instant::now();
1986 let include_targets = cpp.include_target_index();
1987 let includes_started = Instant::now();
1988 let mut include_graph = IncludeGraph::default();
1989 for root in roots {
1990 include_graph.extend_with(root, cancellation, &mut |file| {
1991 cpp_include_paths(&cpp.visibility_import_statements(token, file))
1992 .into_iter()
1993 .flat_map(|include| {
1994 resolve_include_targets_with_index(file, &include, include_targets)
1995 })
1996 .collect()
1997 });
1998 }
1999 let include_elapsed = includes_started.elapsed();
2000 let include_file_count = include_graph.files().count();
2001 let visible_source_files_by_root = roots
2002 .iter()
2003 .map(|root| {
2004 (
2005 root.clone(),
2006 include_graph.reachable_files(root, cancellation),
2007 )
2008 })
2009 .collect::<HashMap<_, _>>();
2010 let mut visibility_stats = BoundedVisibilityStats::default();
2011 let mut visible_by_file = build_bounded_visible_declarations(
2012 cpp,
2013 token,
2014 analyzer,
2015 roots,
2016 &visible_source_files_by_root,
2017 cancellation,
2018 &mut visibility_stats,
2019 );
2020 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2021 eprintln!(
2022 "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={}",
2023 visibility_started.elapsed().as_millis(),
2024 include_elapsed.as_millis(),
2025 include_file_count,
2026 visibility_stats.rounds,
2027 visibility_stats.root_names,
2028 visibility_stats.identifier_lookups,
2029 visibility_stats.identifier_batches,
2030 visibility_stats.candidate_units,
2031 visibility_stats.candidate_sources,
2032 visibility_stats.declaration_reads,
2033 visibility_stats.declaration_units,
2034 visibility_stats.selected_units,
2035 visibility_stats.dependency_ast_nodes,
2036 visibility_stats.dependency_names,
2037 visibility_stats.lookup_elapsed.as_millis(),
2038 visibility_stats.declaration_elapsed.as_millis(),
2039 visibility_stats.dependency_ast_elapsed.as_millis(),
2040 );
2041 }
2042 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2043 let finalize_started = Instant::now();
2044 if report_stats {
2045 eprintln!(
2046 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2047 visible_by_file.len(),
2048 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2049 );
2050 }
2051 let owner_started = Instant::now();
2052 if report_stats {
2053 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2054 }
2055 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2056 if report_stats {
2057 eprintln!(
2058 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2059 owner_stats.unseen_owners,
2060 owner_stats.definition_lookups,
2061 owner_stats.admitted,
2062 owner_started.elapsed().as_millis(),
2063 );
2064 }
2065 let mut global_field_internal_linkage = HashMap::default();
2066 let identifier_started = Instant::now();
2067 if report_stats {
2068 eprintln!(
2069 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2070 );
2071 }
2072 let visible_by_identifier = build_visible_identifier_index(
2073 analyzer,
2074 &visible_by_file,
2075 &visible_source_files_by_root,
2076 &mut global_field_internal_linkage,
2077 );
2078 if report_stats {
2079 eprintln!(
2080 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2081 visible_by_identifier.len(),
2082 visible_by_identifier
2083 .values()
2084 .map(HashMap::len)
2085 .sum::<usize>(),
2086 visible_by_identifier
2087 .values()
2088 .flat_map(HashMap::values)
2089 .map(Vec::len)
2090 .sum::<usize>(),
2091 identifier_started.elapsed().as_millis(),
2092 );
2093 }
2094 let mut cpp_template_metadata = HashMap::default();
2095 let metadata_started = Instant::now();
2096 let mut template_classes = 0usize;
2097 if report_stats {
2098 eprintln!(
2099 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2100 );
2101 }
2102 for unit in visible_by_file
2103 .values()
2104 .flatten()
2105 .filter(|unit| unit.is_class())
2106 {
2107 template_classes += 1;
2108 if cpp_template_metadata.contains_key(unit) {
2109 continue;
2110 }
2111 if let Some(metadata) = cpp.template_metadata(unit) {
2112 cpp_template_metadata.insert(unit.clone(), metadata);
2113 }
2114 }
2115 if report_stats {
2116 eprintln!(
2117 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2118 template_classes,
2119 cpp_template_metadata.len(),
2120 metadata_started.elapsed().as_millis(),
2121 );
2122 }
2123 let families_started = Instant::now();
2124 if report_stats {
2125 eprintln!(
2126 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2127 );
2128 }
2129 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2130 for (unit, metadata) in &cpp_template_metadata {
2131 cpp_template_families
2132 .entry(metadata.primary_fq_name.clone())
2133 .or_default()
2134 .push(unit.clone());
2135 }
2136 for family in cpp_template_families.values_mut() {
2145 sort_lookup_units(family);
2146 }
2147 if report_stats {
2148 eprintln!(
2149 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2150 cpp_template_families.len(),
2151 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2152 families_started.elapsed().as_millis(),
2153 );
2154 eprintln!(
2155 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2156 visible_by_file.len(),
2157 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2158 finalize_started.elapsed().as_millis(),
2159 visibility_started.elapsed().as_millis(),
2160 );
2161 }
2162 Self {
2163 cpp,
2164 token,
2165 visible_by_file,
2166 visible_by_identifier,
2167 global_field_internal_linkage,
2168 visible_source_files_by_root,
2169 alias_cells: Mutex::new(HashMap::default()),
2170 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2171 parser_alias_target_matches: RwLock::new(HashMap::default()),
2172 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2173 project_using_index: OnceLock::new(),
2174 callable_reference_specs: Mutex::new(HashMap::default()),
2175 structured_include_fact_cells: Mutex::new(HashMap::default()),
2176 include_activation_cells: Mutex::new(HashMap::default()),
2177 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2178 include_path_admission_cells: Mutex::new(HashMap::default()),
2179 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2180 #[cfg(any(test, feature = "test-support"))]
2181 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2182 #[cfg(any(test, feature = "test-support"))]
2183 conditional_include_projection_state_count: AtomicUsize::new(0),
2184 #[cfg(any(test, feature = "test-support"))]
2185 conditional_include_target_state_count: AtomicUsize::new(0),
2186 #[cfg(any(test, feature = "test-support"))]
2187 include_activation_build_count: AtomicUsize::new(0),
2188 #[cfg(any(test, feature = "test-support"))]
2189 using_donor_activation_count: AtomicUsize::new(0),
2190 #[cfg(any(test, feature = "test-support"))]
2191 using_namespace_lookup_count: AtomicUsize::new(0),
2192 #[cfg(any(test, feature = "test-support"))]
2193 using_name_candidate_inspection_count: AtomicUsize::new(0),
2194 #[cfg(any(test, feature = "test-support"))]
2195 callable_reference_spec_build_count: AtomicUsize::new(0),
2196 #[cfg(any(test, feature = "test-support"))]
2197 alias_source_parse_counts: Mutex::new(HashMap::default()),
2198 #[cfg(any(test, feature = "test-support"))]
2199 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2200 parser_alias_fallback_calls: AtomicUsize::new(0),
2201 parser_alias_fallback_files: AtomicUsize::new(0),
2202 parser_alias_source_parses: AtomicUsize::new(0),
2203 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2204 field_type_facts: Mutex::new(HashMap::default()),
2205 structured_alias_targets: Mutex::new(HashMap::default()),
2206 callable_comparables: Mutex::new(HashMap::default()),
2207 comparable_name_declarations: Mutex::new(HashMap::default()),
2208 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2209 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2210 precise_parent_cache: Mutex::new(HashMap::default()),
2211 c_tag_kind_cache: Mutex::new(HashMap::default()),
2212 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2213 macro_event_cells: Mutex::new(HashMap::default()),
2214 macro_event_name_sets: Mutex::new(HashMap::default()),
2215 macro_include_protection_cells: Mutex::new(HashMap::default()),
2216 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2217 macro_replacements: Mutex::new(HashMap::default()),
2218 macro_local_binding_templates: Mutex::new(HashMap::default()),
2219 macro_lexical_templates: Mutex::new(HashMap::default()),
2220 macro_replacement_bodies: Mutex::new(HashMap::default()),
2221 macro_type_parameters: Mutex::new(HashMap::default()),
2222 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2223 #[cfg(any(test, feature = "test-support"))]
2224 macro_replacement_parse_count: AtomicUsize::new(0),
2225 #[cfg(any(test, feature = "test-support"))]
2226 macro_event_application_count: AtomicUsize::new(0),
2227 #[cfg(any(test, feature = "test-support"))]
2228 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2229 #[cfg(any(test, feature = "test-support"))]
2230 macro_environment_copy_count: AtomicUsize::new(0),
2231 #[cfg(any(test, feature = "test-support"))]
2232 macro_environment_request_count: AtomicUsize::new(0),
2233 cpp_template_metadata,
2234 cpp_template_families,
2235 #[cfg(any(test, feature = "test-support"))]
2236 qualified_candidate_inspections: AtomicUsize::new(0),
2237 #[cfg(any(test, feature = "test-support"))]
2238 target_preserving_type_resolution_count: AtomicUsize::new(0),
2239 #[cfg(any(test, feature = "test-support"))]
2240 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2241 #[cfg(any(test, feature = "test-support"))]
2242 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2243 }
2244 }
2245
2246 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2247 if file == target.source() {
2248 return true;
2249 }
2250 if self.global_field_has_internal_linkage(target) {
2251 return self
2252 .visible_source_files_by_root
2253 .get(file)
2254 .is_some_and(|sources| sources.contains(target.source()));
2255 }
2256 self.visible_by_file
2257 .get(file)
2258 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2259 }
2260
2261 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2262 self.global_field_internal_linkage
2263 .get(unit)
2264 .copied()
2265 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2266 }
2267
2268 pub fn call_arity_evidence(
2269 &self,
2270 file: &ProjectFile,
2271 call: Node<'_>,
2272 source: &str,
2273 ) -> CallArityEvidence {
2274 self.call_arity_evidence_at(file, call, source, call.start_byte())
2275 }
2276
2277 pub fn call_arity_evidence_at(
2285 &self,
2286 file: &ProjectFile,
2287 call: Node<'_>,
2288 source: &str,
2289 environment_byte: usize,
2290 ) -> CallArityEvidence {
2291 let Some(arguments) = call
2292 .child_by_field_name("arguments")
2293 .or_else(|| call.child_by_field_name("parameters"))
2294 .or_else(|| call.child_by_field_name("value"))
2295 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2296 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2297 else {
2298 return CallArityEvidence::Exact(0);
2299 };
2300 let recovered_c_keyword_arguments =
2301 recovered_c_keyword_argument_count(file, call, arguments, source);
2302 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2303 let arguments = argument_children(arguments)
2304 .flat_map(|argument| {
2305 recovered_c_new_expression_arguments(argument, c_semantics)
2306 .map(Vec::from)
2307 .unwrap_or_else(|| vec![argument])
2308 })
2309 .collect::<Vec<_>>();
2310 if arguments
2311 .iter()
2312 .all(|argument| !argument_shape_may_change_arity(*argument))
2313 {
2314 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2315 }
2316 let environment = self.macro_environment(file, environment_byte);
2317 let mut stack = Vec::new();
2318 let mut total = recovered_c_keyword_arguments;
2319 for argument in arguments {
2320 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2321 return CallArityEvidence::Unknown;
2322 }
2323 let CallArityEvidence::Exact(spread) =
2324 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2325 else {
2326 return CallArityEvidence::Unknown;
2327 };
2328 total += spread;
2329 }
2330 CallArityEvidence::Exact(total)
2331 }
2332
2333 fn argument_arity_evidence(
2334 &self,
2335 argument: Node<'_>,
2336 source: &str,
2337 environment: &MacroEnvironment,
2338 stack: &mut Vec<(ProjectFile, usize)>,
2339 ) -> CallArityEvidence {
2340 let (name, invocation_arguments, function_like) = match argument.kind() {
2341 "identifier" => (node_text(argument, source), None, false),
2342 "call_expression" => {
2343 let Some(function) = argument.child_by_field_name("function") else {
2344 return CallArityEvidence::Exact(1);
2345 };
2346 if function.kind() != "identifier" {
2347 return CallArityEvidence::Exact(1);
2348 }
2349 let Some(arguments) = argument.child_by_field_name("arguments") else {
2350 return CallArityEvidence::Exact(1);
2351 };
2352 (node_text(function, source), Some(arguments), true)
2353 }
2354 _ => return CallArityEvidence::Exact(1),
2355 };
2356 let Some(binding) = environment.binding(name) else {
2357 return if environment.unknown_names {
2358 CallArityEvidence::Unknown
2359 } else {
2360 CallArityEvidence::Exact(1)
2361 };
2362 };
2363 if !binding.is_exact() {
2364 return CallArityEvidence::Unknown;
2365 }
2366 match (&binding.definition, invocation_arguments, function_like) {
2367 (MacroDefinition::Object { replacement }, None, false) => self
2368 .replacement_arity_evidence(
2369 replacement,
2370 &[],
2371 &[],
2372 source,
2373 environment,
2374 stack,
2375 binding,
2376 ),
2377 (
2378 MacroDefinition::Function {
2379 parameters,
2380 replacement,
2381 },
2382 Some(arguments),
2383 true,
2384 ) => {
2385 let actuals = argument_children(arguments).collect::<Vec<_>>();
2386 if actuals.len() != parameters.len() {
2387 CallArityEvidence::Unknown
2388 } else {
2389 self.replacement_arity_evidence(
2390 replacement,
2391 parameters,
2392 &actuals,
2393 source,
2394 environment,
2395 stack,
2396 binding,
2397 )
2398 }
2399 }
2400 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2401 _ => CallArityEvidence::Unknown,
2402 }
2403 }
2404
2405 #[allow(clippy::too_many_arguments)]
2406 fn replacement_arity_evidence(
2407 &self,
2408 replacement: &str,
2409 parameters: &[String],
2410 actuals: &[Node<'_>],
2411 actual_source: &str,
2412 environment: &MacroEnvironment,
2413 stack: &mut Vec<(ProjectFile, usize)>,
2414 binding: &MacroBinding,
2415 ) -> CallArityEvidence {
2416 let identity = (binding.source.clone(), binding.declaration_byte);
2417 if stack.contains(&identity) || replacement.trim().is_empty() {
2418 return CallArityEvidence::Unknown;
2419 }
2420 stack.push(identity);
2421 let parsed = self.parsed_macro_replacement(binding, replacement);
2422 let evidence = (|| {
2423 let ParsedMacroReplacement::Parsed {
2424 source: sentinel,
2425 tree,
2426 } = parsed.as_ref()
2427 else {
2428 return None;
2429 };
2430 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2431 let arguments = call.child_by_field_name("arguments")?;
2432 let mut total = 0usize;
2433 for argument in argument_children(arguments) {
2434 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2435 return None;
2436 }
2437 if argument.kind() == "identifier"
2438 && let Some(parameter_index) = parameters
2439 .iter()
2440 .position(|parameter| parameter == node_text(argument, sentinel))
2441 {
2442 if !macro_expansion_shape_is_safe(
2443 actuals[parameter_index],
2444 actual_source,
2445 &[],
2446 environment,
2447 ) {
2448 return None;
2449 }
2450 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2451 actuals[parameter_index],
2452 actual_source,
2453 environment,
2454 stack,
2455 ) else {
2456 return None;
2457 };
2458 total += spread;
2459 continue;
2460 }
2461 let CallArityEvidence::Exact(spread) =
2462 self.argument_arity_evidence(argument, sentinel, environment, stack)
2463 else {
2464 return None;
2465 };
2466 total += spread;
2467 }
2468 Some(CallArityEvidence::Exact(total))
2469 })()
2470 .unwrap_or(CallArityEvidence::Unknown);
2471 stack.pop();
2472 evidence
2473 }
2474
2475 fn parsed_macro_replacement(
2476 &self,
2477 binding: &MacroBinding,
2478 replacement: &str,
2479 ) -> Arc<ParsedMacroReplacement> {
2480 let key = (binding.source.clone(), binding.declaration_byte);
2481 let mut cache = self
2482 .macro_replacements
2483 .lock()
2484 .expect("C++ macro replacement cache poisoned");
2485 if let Some(parsed) = cache.get(&key) {
2486 return Arc::clone(parsed);
2487 }
2488 #[cfg(any(test, feature = "test-support"))]
2489 self.macro_replacement_parse_count
2490 .fetch_add(1, Ordering::Relaxed);
2491 let source =
2492 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2493 let mut parser = Parser::new();
2494 let parsed = parser
2495 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2496 .ok()
2497 .and_then(|()| parser.parse(&source, None))
2498 .filter(|tree| !tree.root_node().has_error())
2499 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2500 ParsedMacroReplacement::Parsed { source, tree }
2501 });
2502 let parsed = Arc::new(parsed);
2503 cache.insert(key, Arc::clone(&parsed));
2504 parsed
2505 }
2506
2507 pub fn function_macro_local_binding<'tree>(
2517 &self,
2518 file: &ProjectFile,
2519 statement: Node<'tree>,
2520 source: &str,
2521 ) -> Option<MacroLocalBinding<'tree>> {
2522 if !is_c_source_file(file) {
2523 return None;
2524 }
2525 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2526 return Some(binding);
2527 }
2528 let call = match statement.kind() {
2529 "call_expression" => statement,
2530 "expression_statement" if statement.named_child_count() == 1 => {
2531 statement.named_child(0)?
2532 }
2533 _ => return None,
2534 };
2535 if call.kind() != "call_expression" {
2536 return None;
2537 }
2538 let function = call.child_by_field_name("function")?;
2539 if function.kind() != "identifier" {
2540 return None;
2541 }
2542 let arguments = call.child_by_field_name("arguments")?;
2543 let actuals = argument_children(arguments).collect::<Vec<_>>();
2544 let environment = self.macro_environment(file, call.start_byte());
2545 let function_name = node_text(function, source);
2546 let binding = environment.binding(function_name)?;
2547 let MacroDefinition::Function {
2548 parameters,
2549 replacement,
2550 } = &binding.definition
2551 else {
2552 return None;
2553 };
2554 if actuals.len() != parameters.len() {
2555 return None;
2556 }
2557 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2558 let (type_name, type_node) = match &template.declared_type {
2559 MacroLocalBindingTypeTemplate::Parameter(index) => {
2560 let actual = *actuals.get(*index)?;
2561 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2562 return None;
2563 }
2564 (node_text(actual, source).trim().to_string(), Some(actual))
2565 }
2566 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2567 };
2568 if type_name.is_empty() {
2569 return None;
2570 }
2571 Some(MacroLocalBinding {
2572 name: template.name.clone(),
2573 type_name,
2574 type_node,
2575 pointer_depth: template.pointer_depth,
2576 proven_unit: None,
2577 })
2578 }
2579
2580 pub fn function_macro_container_binding<'tree>(
2589 &self,
2590 analyzer: &CppGraphSource<'_>,
2591 file: &ProjectFile,
2592 assignment: Node<'tree>,
2593 source: &str,
2594 ) -> Option<MacroLocalBinding<'tree>> {
2595 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2596 return None;
2597 }
2598 let name_node = assignment.child_by_field_name("left")?;
2599 if name_node.kind() != "identifier" {
2600 return None;
2601 }
2602 let call = assignment.child_by_field_name("right")?;
2603 if call.kind() != "call_expression" {
2604 return None;
2605 }
2606 let function = call.child_by_field_name("function")?;
2607 if function.kind() != "identifier" {
2608 return None;
2609 }
2610 let arguments = call.child_by_field_name("arguments")?;
2611 let actuals = argument_children(arguments).collect::<Vec<_>>();
2612 let function_name = node_text(function, source);
2613 let environment = self.macro_environment(file, call.start_byte());
2614 let binding = environment.binding(function_name)?;
2615 if !binding.is_exact() {
2616 return None;
2617 }
2618 let MacroDefinition::Function {
2619 parameters,
2620 replacement,
2621 } = &binding.definition
2622 else {
2623 return None;
2624 };
2625 if actuals.len() != parameters.len() {
2626 return None;
2627 }
2628 let body = self.parsed_macro_replacement_body(
2629 &(binding.source.clone(), binding.declaration_byte),
2630 parameters,
2631 replacement,
2632 )?;
2633 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2634 let type_argument = *actuals.get(type_parameter)?;
2635 let type_node = macro_type_argument_node(type_argument, source)?;
2636 let type_name = node_text(type_node, source).trim().to_string();
2637 if type_name.is_empty() {
2638 return None;
2639 }
2640 let explicit_tag = match node_text(type_argument, source) {
2641 "struct" => Some(CppCTagKind::Struct),
2642 "union" => Some(CppCTagKind::Union),
2643 _ => None,
2644 };
2645 let resolved_type = if let Some(tag) = explicit_tag {
2646 let candidates = self
2647 .visible_identifier_candidates(file, &type_name)
2648 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2649 .collect::<Vec<_>>();
2650 self.resolve_type_candidates(
2651 analyzer,
2652 file,
2653 &candidates,
2654 TypeCandidateResolution::Canonical,
2655 )
2656 .ok()
2657 } else {
2658 self.resolve_type_node_result(file, type_node, source)
2659 .ok()
2660 .flatten()
2661 };
2662 let proven_unit = resolved_type?;
2663 Some(MacroLocalBinding {
2664 name: node_text(name_node, source).to_string(),
2665 type_name,
2666 type_node: Some(type_node),
2667 pointer_depth: 1,
2668 proven_unit: Some(proven_unit),
2669 })
2670 }
2671
2672 pub fn macro_local_binding_at<'tree>(
2675 &self,
2676 file: &ProjectFile,
2677 root: Node<'tree>,
2678 source: &str,
2679 start_byte: usize,
2680 end_byte: usize,
2681 ) -> Option<MacroLocalBinding<'tree>> {
2682 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2683 }
2684
2685 pub fn macro_lexical_binding(
2689 &self,
2690 file: &ProjectFile,
2691 root: Node<'_>,
2692 source: &str,
2693 start_byte: usize,
2694 end_byte: usize,
2695 ) -> Option<MacroLexicalBinding> {
2696 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2697 }
2698
2699 pub fn macro_lexical_references(
2704 &self,
2705 file: &ProjectFile,
2706 root: Node<'_>,
2707 source: &str,
2708 max_references: usize,
2709 cancelled: impl FnMut() -> bool,
2710 ) -> MacroLexicalReferences {
2711 crate::graph::macro_lexical::all_references(
2712 || self,
2713 file,
2714 root,
2715 source,
2716 max_references,
2717 cancelled,
2718 )
2719 }
2720
2721 pub(crate) fn function_macro_binding_at(
2726 &self,
2727 file: &ProjectFile,
2728 name: &str,
2729 before_byte: usize,
2730 ) -> Option<(ProjectFile, usize)> {
2731 let environment = self.macro_environment(file, before_byte);
2732 let binding = environment.binding(name)?;
2733 if binding.is_exact()
2734 && matches!(
2735 binding.definition,
2736 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2737 )
2738 {
2739 return Some((binding.source.clone(), binding.declaration_byte));
2740 }
2741 if binding.source != *file {
2747 return None;
2748 }
2749 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2750 let root = prepared.tree().root_node();
2751 let source = prepared.source();
2752 let reference = root.descendant_for_byte_range(
2753 before_byte,
2754 before_byte.saturating_add(1).min(source.len()),
2755 )?;
2756 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2757 let cell = self.macro_event_cell(file);
2758 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2759 let event = events
2760 .iter()
2761 .find(|event| event.byte() == binding.declaration_byte)?;
2762 let MacroEvent::Define {
2763 name: defined_name,
2764 binding: definition,
2765 conditionals,
2766 ..
2767 } = event
2768 else {
2769 return None;
2770 };
2771 if defined_name != name
2772 || conditionals.is_empty()
2773 || !conditionals
2774 .iter()
2775 .all(|condition| reference_conditions.contains(condition))
2776 || !matches!(
2777 definition.definition,
2778 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2779 )
2780 {
2781 return None;
2782 }
2783 Some((definition.source.clone(), definition.declaration_byte))
2784 }
2785
2786 pub fn function_macro_type_argument<'tree>(
2793 &self,
2794 file: &ProjectFile,
2795 node: Node<'tree>,
2796 source: &str,
2797 ) -> Option<Node<'tree>> {
2798 if !is_c_source_file(file) {
2799 return None;
2800 }
2801 let (call, argument_index, argument) = c_function_macro_argument(node)?;
2802 let function = call.child_by_field_name("function")?;
2803 let function_name = node_text(function, source);
2804 if function_name.is_empty() {
2805 return None;
2806 }
2807 if !self.file_defines_macro_name(file, function_name)
2808 && !self
2809 .visible_identifier_candidates(file, function_name)
2810 .any(|candidate| candidate.is_macro())
2811 {
2812 return None;
2813 }
2814 let environment = self.macro_environment(file, call.start_byte());
2815 let binding = environment.binding(function_name)?;
2816 if !binding.is_exact() {
2817 return None;
2818 }
2819 let (parameters, replacement, variadic) = match &binding.definition {
2820 MacroDefinition::Function {
2821 parameters,
2822 replacement,
2823 } => (parameters, replacement, false),
2824 MacroDefinition::VariadicFunction {
2825 parameters,
2826 replacement,
2827 } => (parameters, replacement, true),
2828 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2829 };
2830 let actuals = call
2831 .child_by_field_name("arguments")
2832 .map(argument_children)
2833 .into_iter()
2834 .flatten()
2835 .collect::<Vec<_>>();
2836 let arity_matches = if variadic {
2837 actuals.len() >= parameters.len()
2838 } else {
2839 actuals.len() == parameters.len()
2840 };
2841 let type_parameters = self.macro_type_parameter_indices(
2842 &(binding.source.clone(), binding.declaration_byte),
2843 parameters,
2844 replacement,
2845 )?;
2846 if !arity_matches || !type_parameters.contains(&argument_index) {
2847 return None;
2848 }
2849 let type_node = macro_type_argument_node(argument, source)?;
2850 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2851 return None;
2852 }
2853 Some(type_node)
2854 }
2855
2856 fn macro_local_binding_template(
2857 &self,
2858 binding: &MacroBinding,
2859 parameters: &[String],
2860 replacement: &str,
2861 ) -> Option<Arc<MacroLocalBindingTemplate>> {
2862 let key = (binding.source.clone(), binding.declaration_byte);
2863 if let Some(template) = self
2864 .macro_local_binding_templates
2865 .lock()
2866 .expect("C++ macro local-binding cache poisoned")
2867 .get(&key)
2868 {
2869 return template.clone();
2870 }
2871 let template = (|| {
2872 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2873 let sentinel = body.source.as_str();
2874 let statements = body.statements()?;
2875 if statements.named_child_count() != 1 {
2876 return None;
2877 }
2878 let declaration = statements.named_child(0)?;
2879 if declaration.kind() != "declaration" {
2880 return None;
2881 }
2882 let type_node = declaration
2883 .child_by_field_name("type")
2884 .or_else(|| first_type_child(declaration))?;
2885 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2886 let mut cursor = declaration.walk();
2887 declaration.named_children(&mut cursor).find_map(|child| {
2888 if child.kind() == "init_declarator" {
2889 child.child_by_field_name("declarator")
2890 } else {
2891 is_declarator_node(child).then_some(child)
2892 }
2893 })
2894 })?;
2895 let name = extract_variable_name(declarator, sentinel)?;
2896 let pointer_depth =
2897 declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
2898 let type_text = node_text(type_node, sentinel).trim();
2899 let declared_type = parameters
2900 .iter()
2901 .position(|parameter| parameter == type_text)
2902 .map(MacroLocalBindingTypeTemplate::Parameter)
2903 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2904 Some(Arc::new(MacroLocalBindingTemplate {
2905 name,
2906 declared_type,
2907 pointer_depth,
2908 }))
2909 })();
2910 self.macro_local_binding_templates
2911 .lock()
2912 .expect("C++ macro local-binding cache poisoned")
2913 .insert(key, template.clone());
2914 template
2915 }
2916
2917 pub fn function_macro_replacement_body(
2924 &self,
2925 file: &ProjectFile,
2926 definition: Node<'_>,
2927 source: &str,
2928 ) -> Option<Arc<ParsedReplacementBody>> {
2929 debug_assert_eq!(definition.kind(), "preproc_function_def");
2930 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
2931 MacroDefinition::Function {
2932 parameters,
2933 replacement,
2934 }
2935 | MacroDefinition::VariadicFunction {
2936 parameters,
2937 replacement,
2938 } => (parameters, replacement),
2939 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2940 };
2941 self.parsed_macro_replacement_body(
2942 &(file.clone(), definition.start_byte()),
2943 ¶meters,
2944 &replacement,
2945 )
2946 }
2947
2948 fn parsed_macro_replacement_body(
2956 &self,
2957 key: &(ProjectFile, usize),
2958 parameters: &[String],
2959 replacement: &str,
2960 ) -> Option<Arc<ParsedReplacementBody>> {
2961 if let Some(body) = self
2962 .macro_replacement_bodies
2963 .lock()
2964 .expect("C++ macro replacement body cache poisoned")
2965 .get(key)
2966 {
2967 return body.clone();
2968 }
2969 let body = (|| {
2970 if replacement.trim().is_empty() {
2971 return None;
2972 }
2973 let (source, tree, original_offsets) =
2974 Self::parse_macro_replacement_body(replacement, parameters)?;
2975 let body = ParsedReplacementBody {
2976 source,
2977 tree,
2978 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2979 parameters: parameters.to_vec(),
2980 original_offsets,
2981 };
2982 body.statements()?;
2983 if body.expands_variadic_arguments() {
2984 return None;
2985 }
2986 Some(Arc::new(body))
2987 })();
2988 self.macro_replacement_bodies
2989 .lock()
2990 .expect("C++ macro replacement body cache poisoned")
2991 .insert(key.clone(), body.clone());
2992 body
2993 }
2994
2995 fn parse_macro_replacement_body(
3003 replacement: &str,
3004 parameters: &[String],
3005 ) -> Option<(String, Tree, Box<[usize]>)> {
3006 let normalized = normalize_macro_continuations(replacement);
3007 let parse = |replacement: &str| {
3008 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3009 let mut parser = Parser::new();
3010 parser
3011 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3012 .ok()?;
3013 let tree = parser.parse(&source, None)?;
3014 Some((source, tree))
3015 };
3016 let (source, tree) = parse(&normalized)?;
3017 if !tree.root_node().has_error() {
3018 let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3019 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3020 return Some((source, tree, original_offsets.into_boxed_slice()));
3021 }
3022
3023 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3024 let mut insertion_points = Vec::new();
3025 let mut stack = vec![tree.root_node()];
3026 while let Some(node) = stack.pop() {
3027 if matches!(
3028 node.kind(),
3029 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3030 ) && parameters
3031 .iter()
3032 .any(|parameter| parameter.as_str() == node_text(node, &source))
3033 && Self::macro_formal_needs_statement_separator(node)
3034 {
3035 let point = node.end_byte().saturating_sub(body_offset);
3036 if point <= normalized.len() && !insertion_points.contains(&point) {
3037 insertion_points.push(point);
3038 }
3039 }
3040 push_named_children_reversed(node, &mut stack);
3041 }
3042 if insertion_points.is_empty() {
3043 return None;
3044 }
3045 insertion_points.sort_unstable();
3046 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3047 let mut original_offsets =
3048 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3049 let mut next_insertion = 0;
3050 for (index, byte) in normalized.bytes().enumerate() {
3051 recovered.push(byte);
3052 original_offsets.push(index);
3053 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3054 recovered.push(b';');
3055 original_offsets.push(index + 1);
3056 next_insertion += 1;
3057 }
3058 }
3059 original_offsets.push(normalized.len());
3060 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3061 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3062 let (source, tree) = parse(&recovered)?;
3063 if tree.root_node().has_error() {
3064 return None;
3065 }
3066 Some((source, tree, original_offsets.into_boxed_slice()))
3067 }
3068
3069 fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3074 offsets.extend([replacement_end; 3]);
3075 }
3076
3077 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3082 let mut current = node;
3083 let mut crossed_recovery = false;
3084 while let Some(parent) = current.parent() {
3085 if parent.is_error() {
3086 crossed_recovery = true;
3087 current = parent;
3088 continue;
3089 }
3090 if matches!(
3091 parent.kind(),
3092 "call_expression"
3093 | "argument_list"
3094 | "field_expression"
3095 | "binary_expression"
3096 | "unary_expression"
3097 | "assignment_expression"
3098 | "conditional_expression"
3099 | "parenthesized_expression"
3100 | "subscript_expression"
3101 ) {
3102 return false;
3103 }
3104 if parent.kind() == "expression_statement" {
3105 return parent.named_child_count() == 1;
3106 }
3107 if parent.kind() == "compound_statement" && current == node {
3108 return true;
3109 }
3110 if matches!(
3111 parent.kind(),
3112 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3113 ) {
3114 return crossed_recovery
3115 || parent.child_by_field_name("consequence") == Some(current);
3116 }
3117 if matches!(parent.kind(), "declaration" | "init_declarator") {
3118 return false;
3119 }
3120 current = parent;
3121 }
3122 false
3123 }
3124
3125 fn macro_type_parameter_indices(
3133 &self,
3134 key: &(ProjectFile, usize),
3135 parameters: &[String],
3136 replacement: &str,
3137 ) -> Option<Arc<[usize]>> {
3138 if let Some(indices) = self
3139 .macro_type_parameters
3140 .lock()
3141 .expect("C++ macro type-parameter cache poisoned")
3142 .get(key)
3143 {
3144 return indices.clone();
3145 }
3146 #[cfg(any(test, feature = "test-support"))]
3147 self.macro_replacement_parse_count
3148 .fetch_add(1, Ordering::Relaxed);
3149 let indices = (|| {
3150 if replacement.trim().is_empty() {
3151 return None;
3152 }
3153 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3157 let mut parser = Parser::new();
3158 parser
3159 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3160 .ok()?;
3161 let tree = parser.parse(&source, None)?;
3162 let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3163 Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3164 let body = ParsedReplacementBody {
3165 source,
3166 tree,
3167 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3168 parameters: parameters.to_vec(),
3169 original_offsets: original_offsets.into_boxed_slice(),
3170 };
3171 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3172 })();
3173 self.macro_type_parameters
3174 .lock()
3175 .expect("C++ macro type-parameter cache poisoned")
3176 .insert(key.clone(), indices.clone());
3177 indices
3178 }
3179
3180 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3181 let replacement = if node.kind() == "preproc_function_def" {
3182 function_macro_replacement_span(node, source)
3183 .and_then(|span| source.get(span))
3184 .map(str::to_owned)
3185 .or_else(|| {
3186 node.child_by_field_name("value")
3187 .map(|value| node_text(value, source).to_string())
3188 })
3189 .unwrap_or_default()
3190 } else {
3191 node.child_by_field_name("value")
3192 .map(|value| node_text(value, source).to_string())
3193 .unwrap_or_default()
3194 };
3195 if node.kind() == "preproc_def" {
3196 return MacroDefinition::Object { replacement };
3197 }
3198 let Some(parameters) = node.child_by_field_name("parameters") else {
3199 return MacroDefinition::Unsupported;
3200 };
3201 let variadic = (0..parameters.child_count()).any(|index| {
3202 parameters
3203 .child(index)
3204 .is_some_and(|child| child.kind() == "...")
3205 });
3206 let parameters = (0..parameters.named_child_count())
3207 .filter_map(|index| parameters.named_child(index))
3208 .map(|parameter| node_text(parameter, source).to_string())
3209 .collect::<Vec<_>>();
3210 if variadic {
3211 MacroDefinition::VariadicFunction {
3212 parameters,
3213 replacement,
3214 }
3215 } else {
3216 MacroDefinition::Function {
3217 parameters,
3218 replacement,
3219 }
3220 }
3221 }
3222
3223 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3224 self.macro_event_cells
3225 .lock()
3226 .expect("C++ macro event cache poisoned")
3227 .entry(file.clone())
3228 .or_default()
3229 .clone()
3230 }
3231
3232 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3233 if let Some(names) = self
3234 .macro_event_name_sets
3235 .lock()
3236 .expect("C++ macro event-name cache poisoned")
3237 .get(file)
3238 .cloned()
3239 {
3240 return names.contains(name);
3241 }
3242 let cell = self.macro_event_cell(file);
3243 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3244 let names = Arc::new(
3245 events
3246 .iter()
3247 .filter_map(|event| match event {
3248 MacroEvent::Define { name, .. } => Some(name.clone()),
3249 MacroEvent::Undef { .. }
3250 | MacroEvent::Include { .. }
3251 | MacroEvent::Invalidate { .. } => None,
3252 })
3253 .collect(),
3254 );
3255 self.macro_event_name_sets
3256 .lock()
3257 .expect("C++ macro event-name cache poisoned")
3258 .insert(file.clone(), Arc::clone(&names));
3259 names.contains(name)
3260 }
3261
3262 fn macro_environment_checkpoint_cell(
3263 &self,
3264 file: &ProjectFile,
3265 ) -> MacroEnvironmentCheckpointCell {
3266 self.macro_environment_checkpoints
3267 .lock()
3268 .expect("C++ macro environment checkpoint cache poisoned")
3269 .entry(file.clone())
3270 .or_default()
3271 .clone()
3272 }
3273
3274 pub fn macro_environment(
3276 &self,
3277 file: &ProjectFile,
3278 before_byte: usize,
3279 ) -> Arc<MacroEnvironment> {
3280 #[cfg(any(test, feature = "test-support"))]
3281 self.macro_environment_request_count
3282 .fetch_add(1, Ordering::Relaxed);
3283 let cell = self.macro_event_cell(file);
3284 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3285 let frontier = events.partition_point(|event| event.byte() < before_byte);
3286 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3287 let checkpoints =
3288 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3289 let checkpoint = checkpoints.at_or_before(frontier);
3290 if checkpoint.frontier == frontier {
3291 return Arc::clone(&checkpoint.environment);
3292 }
3293 #[cfg(any(test, feature = "test-support"))]
3294 self.macro_environment_copy_count
3295 .fetch_add(1, Ordering::Relaxed);
3296 let mut environment = checkpoint.environment.as_ref().clone();
3297 let mut include_stack = HashSet::from_iter([file.clone()]);
3298 for event in &events[checkpoint.frontier..frontier] {
3299 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3300 }
3301 Arc::new(environment)
3302 }
3303
3304 fn build_macro_environment_checkpoints(
3307 &self,
3308 file: &ProjectFile,
3309 events: &[MacroEvent],
3310 ) -> MacroEnvironmentCheckpoints {
3311 #[cfg(any(test, feature = "test-support"))]
3312 self.macro_environment_checkpoint_build_count
3313 .fetch_add(1, Ordering::Relaxed);
3314 let mut environment = MacroEnvironment {
3319 build_proven_defines: self
3320 .compile_proven_guards(file)
3321 .iter()
3322 .filter_map(|guard| match guard {
3323 PreprocessorGuard::Defined(name) => Some(name.clone()),
3324 _ => None,
3325 })
3326 .collect(),
3327 ..MacroEnvironment::default()
3328 };
3329 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3330 frontier: 0,
3331 environment: Arc::new(environment.clone()),
3332 }];
3333 let checkpoint_stride = events
3340 .len()
3341 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3342 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3343 let mut include_stack = HashSet::from_iter([file.clone()]);
3344 for (index, event) in events.iter().enumerate() {
3345 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3346 let frontier = index + 1;
3347 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3348 checkpoints.push(MacroEnvironmentCheckpoint {
3349 frontier,
3350 environment: Arc::new(environment.clone()),
3351 });
3352 }
3353 }
3354 MacroEnvironmentCheckpoints { checkpoints }
3355 }
3356
3357 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3366 self.macro_environment(file, before_byte)
3367 .binding(name)
3368 .is_some()
3369 }
3370
3371 pub fn macro_name_may_be_bound_at(
3372 &self,
3373 file: &ProjectFile,
3374 name: &str,
3375 before_byte: usize,
3376 ) -> bool {
3377 self.macro_environment(file, before_byte).may_bind(name)
3378 }
3379
3380 pub fn macro_binding_matches_target_at(
3384 &self,
3385 analyzer: &CppGraphSource<'_>,
3386 file: &ProjectFile,
3387 name: &str,
3388 before_byte: usize,
3389 target: &CodeUnit,
3390 ) -> bool {
3391 let ranges = analyzer.ranges(target);
3392 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3393 self.macro_binding_matches_target_declaration_at(
3394 file,
3395 name,
3396 before_byte,
3397 target.source(),
3398 &declaration_bytes,
3399 )
3400 }
3401
3402 pub(crate) fn macro_declaration_bytes(
3407 &self,
3408 target: &CodeUnit,
3409 ranges: &[Range],
3410 ) -> Vec<usize> {
3411 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3412 return Vec::new();
3413 };
3414 ranges
3415 .iter()
3416 .filter_map(|range| {
3417 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3418 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3419 node = node.parent()?;
3420 }
3421 Some(node.start_byte())
3422 })
3423 .collect()
3424 }
3425
3426 pub(crate) fn macro_binding_matches_target_declaration_at(
3427 &self,
3428 file: &ProjectFile,
3429 name: &str,
3430 before_byte: usize,
3431 target_source: &ProjectFile,
3432 target_declaration_bytes: &[usize],
3433 ) -> bool {
3434 let environment = self.macro_environment(file, before_byte);
3435 let Some(binding) = environment.binding(name) else {
3436 return false;
3437 };
3438 if binding.definition == MacroDefinition::Unsupported {
3439 return false;
3440 }
3441 if binding.source != *target_source {
3445 return false;
3446 }
3447 target_declaration_bytes.contains(&binding.declaration_byte)
3448 }
3449
3450 pub fn resolve_ordinary_macro_reference(
3457 &self,
3458 analyzer: &CppGraphSource<'_>,
3459 file: &ProjectFile,
3460 node: Node<'_>,
3461 source: &str,
3462 ) -> OrdinaryMacroReferenceResolution {
3463 if !is_ordinary_macro_reference_node(node) {
3464 return OrdinaryMacroReferenceResolution::Missing;
3465 }
3466 let name = node_text(node, source);
3467 if name.is_empty() {
3468 return OrdinaryMacroReferenceResolution::Missing;
3469 }
3470 let visible = self
3471 .visible_identifier_candidates(file, name)
3472 .filter(|candidate| candidate.is_macro())
3473 .cloned()
3474 .collect::<Vec<_>>();
3475 let mut exact = Vec::new();
3476 for candidate in &visible {
3477 if self.macro_binding_matches_target_at(
3478 analyzer,
3479 file,
3480 name,
3481 node.start_byte(),
3482 candidate,
3483 ) && !exact
3484 .iter()
3485 .any(|existing| same_visible_symbol(existing, candidate))
3486 {
3487 exact.push(candidate.clone());
3488 }
3489 }
3490 match exact.len() {
3491 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3492 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3493 0 if !visible.is_empty()
3494 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3495 {
3496 OrdinaryMacroReferenceResolution::Ambiguous
3497 }
3498 0 => OrdinaryMacroReferenceResolution::Missing,
3499 }
3500 }
3501
3502 pub fn recovered_c_reference_ranges(
3510 &self,
3511 file: &ProjectFile,
3512 root: Node<'_>,
3513 source: &str,
3514 limit: usize,
3515 ) -> RecoveredCReferenceRanges {
3516 if !is_c_source_file(file) {
3517 return RecoveredCReferenceRanges::Complete(Vec::new());
3518 }
3519 let mut ranges = Vec::new();
3520 let mut seen = HashSet::default();
3521 let mut stack = vec![(root, root.is_error())];
3522 while let Some((node, inside_error)) = stack.pop() {
3523 let inside_error = inside_error || node.is_error();
3524 if node.kind() == "preproc_arg" {
3525 let macro_value_kind = node.parent().and_then(|parent| {
3530 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3531 });
3532 if matches!(
3533 macro_value_kind,
3534 Some("preproc_def" | "preproc_function_def")
3535 ) {
3536 let name = node_text(node, source);
3537 if !name.is_empty()
3538 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3539 && !push_recovered_c_range(
3540 &mut ranges,
3541 &mut seen,
3542 node.start_byte(),
3543 node.end_byte(),
3544 node,
3545 limit,
3546 )
3547 {
3548 return RecoveredCReferenceRanges::LimitExceeded;
3549 }
3550 }
3551 if macro_value_kind == Some("preproc_def") {
3552 for reference in object_macro_replacement_type_references(node, source) {
3553 for range in reference.component_ranges {
3554 let visible = self
3555 .visible_identifier_candidates(file, &source[range.clone()])
3556 .any(|candidate| {
3557 candidate.is_class()
3558 || candidate.is_module()
3559 || is_type_alias(candidate)
3560 });
3561 if visible
3562 && !push_recovered_c_range(
3563 &mut ranges,
3564 &mut seen,
3565 range.start,
3566 range.end,
3567 node,
3568 limit,
3569 )
3570 {
3571 return RecoveredCReferenceRanges::LimitExceeded;
3572 }
3573 }
3574 }
3575 }
3576 }
3577 if inside_error
3578 && recovered_c_reference_node(self, file, node, source)
3579 && !push_recovered_c_range(
3580 &mut ranges,
3581 &mut seen,
3582 node.start_byte(),
3583 node.end_byte(),
3584 node,
3585 limit,
3586 )
3587 {
3588 return RecoveredCReferenceRanges::LimitExceeded;
3589 }
3590 let mut cursor = node.walk();
3591 for child in node.named_children(&mut cursor) {
3592 stack.push((child, inside_error));
3593 }
3594 }
3595 ranges.sort_unstable();
3596 RecoveredCReferenceRanges::Complete(ranges)
3597 }
3598
3599 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3605 self.visible_identifier_candidates(file, target.identifier())
3606 .filter(|candidate| candidate.is_macro())
3607 .any(|candidate| {
3608 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3609 })
3610 }
3611
3612 pub fn object_macro_replacement_at(
3613 &self,
3614 file: &ProjectFile,
3615 name: &str,
3616 before_byte: usize,
3617 ) -> Option<String> {
3618 let environment = self.macro_environment(file, before_byte);
3619 let binding = environment.binding(name)?;
3620 if !binding.exact {
3621 return None;
3622 }
3623 match &binding.definition {
3624 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3625 MacroDefinition::Function { .. }
3626 | MacroDefinition::VariadicFunction { .. }
3627 | MacroDefinition::Unsupported => None,
3628 }
3629 }
3630
3631 fn apply_macro_events(
3632 &self,
3633 file: &ProjectFile,
3634 before_byte: Option<usize>,
3635 environment: &mut MacroEnvironment,
3636 include_stack: &mut HashSet<ProjectFile>,
3637 ) {
3638 if !include_stack.insert(file.clone()) {
3639 return;
3640 }
3641 if self.cpp.prepared_syntax(self.token, file).is_none() {
3642 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3643 include_stack.remove(file);
3644 return;
3645 }
3646 match self.macro_include_protection(file) {
3647 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3648 Some(binding) if binding.is_exact() => {
3649 include_stack.remove(file);
3650 return;
3651 }
3652 Some(_) | None if environment.unknown_names => {
3653 let mut ambiguous_seen = HashSet::default();
3654 self.mark_macro_events_ambiguous(
3655 file,
3656 environment,
3657 &mut ambiguous_seen,
3658 file,
3659 before_byte.unwrap_or_default(),
3660 );
3661 include_stack.remove(file);
3662 return;
3663 }
3664 Some(_) => {
3665 let mut ambiguous_seen = HashSet::default();
3666 self.mark_macro_events_ambiguous(
3667 file,
3668 environment,
3669 &mut ambiguous_seen,
3670 file,
3671 before_byte.unwrap_or_default(),
3672 );
3673 include_stack.remove(file);
3674 return;
3675 }
3676 None => {}
3677 },
3678 MacroIncludeProtection::PragmaOnce => {
3679 if !environment.applied_pragma_once_files.insert(file.clone()) {
3680 include_stack.remove(file);
3681 return;
3682 }
3683 if environment.maybe_applied_pragma_once_files.remove(file) {
3684 let mut ambiguous_seen = HashSet::default();
3689 environment.applied_pragma_once_files.remove(file);
3690 self.mark_macro_events_ambiguous(
3691 file,
3692 environment,
3693 &mut ambiguous_seen,
3694 file,
3695 before_byte.unwrap_or_default(),
3696 );
3697 environment.maybe_applied_pragma_once_files.remove(file);
3698 environment.applied_pragma_once_files.insert(file.clone());
3699 include_stack.remove(file);
3700 return;
3701 }
3702 }
3703 MacroIncludeProtection::None => {}
3704 }
3705 let cell = self.macro_event_cell(file);
3706 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3707 for event in events {
3708 if before_byte.is_some_and(|limit| event.byte() >= limit) {
3709 break;
3710 }
3711 self.apply_macro_event(file, event, environment, include_stack);
3712 }
3713 include_stack.remove(file);
3714 }
3715
3716 fn apply_macro_event(
3717 &self,
3718 file: &ProjectFile,
3719 event: &MacroEvent,
3720 environment: &mut MacroEnvironment,
3721 include_stack: &mut HashSet<ProjectFile>,
3722 ) {
3723 #[cfg(any(test, feature = "test-support"))]
3724 self.macro_event_application_count
3725 .fetch_add(1, Ordering::Relaxed);
3726 match event {
3727 MacroEvent::Define {
3728 name,
3729 binding,
3730 conditionals,
3731 byte,
3732 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3733 Some(true) => environment.insert(name.clone(), binding.clone()),
3734 Some(false) => {}
3735 None => Self::merge_conditional_macro_definition(
3736 environment,
3737 name,
3738 binding,
3739 file,
3740 *byte,
3741 ),
3742 },
3743 MacroEvent::Undef {
3744 name,
3745 conditionals,
3746 byte,
3747 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3748 Some(true) => environment.remove(name),
3749 Some(false) => {}
3750 None => {
3751 if environment.binding(name).is_some() {
3752 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3753 }
3754 }
3755 },
3756 MacroEvent::Include {
3757 targets,
3758 conditionals,
3759 byte,
3760 } => {
3761 let condition =
3762 self.macro_event_condition_value(file, *byte, environment, conditionals);
3763 if condition == Some(false) {
3764 return;
3765 }
3766 if targets.is_empty() {
3767 environment.mark_unknown_names(file, *byte);
3768 return;
3769 }
3770 if condition.is_none() || targets.len() > 1 {
3771 let mut ambiguous_seen = HashSet::default();
3772 for target in targets {
3773 self.mark_macro_events_ambiguous(
3774 target,
3775 environment,
3776 &mut ambiguous_seen,
3777 file,
3778 *byte,
3779 );
3780 }
3781 } else if let Some(target) = targets.first() {
3782 self.apply_macro_events(target, None, environment, include_stack);
3783 }
3784 }
3785 MacroEvent::Invalidate { byte } => {
3786 for binding in environment.bindings.values_mut() {
3787 *binding = MacroBinding::uncertain_from(binding, file, *byte);
3788 }
3789 }
3790 }
3791 }
3792
3793 fn macro_event_condition_value(
3804 &self,
3805 file: &ProjectFile,
3806 event_byte: usize,
3807 environment: &MacroEnvironment,
3808 conditionals: &OwningPreprocessorConditionals,
3809 ) -> Option<bool> {
3810 if conditionals.is_empty() {
3811 return Some(true);
3812 }
3813 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3814 let source = prepared.source();
3815 let root = prepared.tree().root_node();
3816 let descendant = root.descendant_for_byte_range(
3817 event_byte,
3818 event_byte.saturating_add(1).min(source.len()),
3819 )?;
3820 let mut unknown = false;
3821 let mut current = descendant.parent();
3822 while let Some(conditional) = current {
3823 if matches!(
3824 conditional.kind(),
3825 "preproc_if" | "preproc_ifdef" | "preproc_elif"
3826 ) && conditionals.contains(&conditional.start_byte())
3827 {
3828 let mut value = match conditional.kind() {
3829 "preproc_ifdef" => {
3830 let name = conditional.child_by_field_name("name")?;
3831 let defined =
3832 self.macro_name_defined_value(environment, node_text(name, source));
3833 match conditional.child(0)?.kind() {
3834 "#ifdef" => defined,
3835 "#ifndef" => defined.map(|defined| !defined),
3836 _ => None,
3837 }
3838 }
3839 "preproc_if" | "preproc_elif" => conditional
3840 .child_by_field_name("condition")
3841 .and_then(|condition| {
3842 self.preprocessor_integer_value(
3843 condition,
3844 source,
3845 environment,
3846 &mut Vec::new(),
3847 0,
3848 )
3849 })
3850 .map(|value| value != 0),
3851 _ => unreachable!(),
3852 };
3853 if conditional
3854 .child_by_field_name("alternative")
3855 .is_some_and(|alternative| {
3856 alternative.start_byte() <= descendant.start_byte()
3857 && descendant.end_byte() <= alternative.end_byte()
3858 })
3859 {
3860 value = value.map(|value| !value);
3861 }
3862 match value {
3863 Some(true) => {}
3864 Some(false) => return Some(false),
3865 None => unknown = true,
3866 }
3867 }
3868 current = conditional.parent();
3869 }
3870 (!unknown).then_some(true)
3871 }
3872
3873 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3874 if environment.known_undefined_names.contains(name) {
3875 return Some(false);
3876 }
3877 if let Some(binding) = environment.binding(name) {
3878 return binding.is_exact().then_some(true);
3879 }
3880 environment
3881 .build_proven_defines
3882 .contains(name)
3883 .then_some(true)
3884 }
3885
3886 fn preprocessor_integer_value(
3887 &self,
3888 expression: Node<'_>,
3889 source: &str,
3890 environment: &MacroEnvironment,
3891 expansion_stack: &mut Vec<(ProjectFile, usize)>,
3892 depth: usize,
3893 ) -> Option<i128> {
3894 if depth >= 64 {
3897 return None;
3898 }
3899 match expression.kind() {
3900 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3901 "identifier" | "type_identifier" => {
3902 let binding = environment.binding(node_text(expression, source))?;
3903 if !binding.is_exact() {
3904 return None;
3905 }
3906 let MacroDefinition::Object { replacement } = &binding.definition else {
3907 return None;
3908 };
3909 let identity = (binding.source.clone(), binding.declaration_byte);
3910 if expansion_stack.contains(&identity) {
3911 return None;
3912 }
3913 expansion_stack.push(identity);
3914 let parsed = self.parsed_macro_replacement(binding, replacement);
3915 let value = match parsed.as_ref() {
3916 ParsedMacroReplacement::Parsed {
3917 source: replacement_source,
3918 tree,
3919 } => first_descendant_of_kind(tree.root_node(), "call_expression")
3920 .and_then(|call| call.child_by_field_name("arguments"))
3921 .and_then(|arguments| argument_children(arguments).next())
3922 .and_then(|argument| {
3923 self.preprocessor_integer_value(
3924 argument,
3925 replacement_source,
3926 environment,
3927 expansion_stack,
3928 depth + 1,
3929 )
3930 }),
3931 ParsedMacroReplacement::Unsupported => None,
3932 };
3933 expansion_stack.pop();
3934 value
3935 }
3936 "preproc_defined" => {
3937 let mut cursor = expression.walk();
3938 let name = expression
3939 .named_children(&mut cursor)
3940 .find(|child| child.kind() == "identifier")?;
3941 self.macro_name_defined_value(environment, node_text(name, source))
3942 .map(i128::from)
3943 }
3944 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3945 self.preprocessor_integer_value(
3946 child,
3947 source,
3948 environment,
3949 expansion_stack,
3950 depth + 1,
3951 )
3952 }),
3953 "unary_expression" => {
3954 let operator = expression.child_by_field_name("operator")?.kind();
3955 let argument = expression.child_by_field_name("argument")?;
3956 let value = self.preprocessor_integer_value(
3957 argument,
3958 source,
3959 environment,
3960 expansion_stack,
3961 depth + 1,
3962 )?;
3963 match operator {
3964 "+" => Some(value),
3965 "-" => value.checked_neg(),
3966 "!" => Some(i128::from(value == 0)),
3967 "~" => Some(!value),
3968 _ => None,
3969 }
3970 }
3971 "binary_expression" => {
3972 let left = self.preprocessor_integer_value(
3973 expression.child_by_field_name("left")?,
3974 source,
3975 environment,
3976 expansion_stack,
3977 depth + 1,
3978 )?;
3979 let right = self.preprocessor_integer_value(
3980 expression.child_by_field_name("right")?,
3981 source,
3982 environment,
3983 expansion_stack,
3984 depth + 1,
3985 )?;
3986 match expression.child_by_field_name("operator")?.kind() {
3987 "+" => left.checked_add(right),
3988 "-" => left.checked_sub(right),
3989 "*" => left.checked_mul(right),
3990 "/" => left.checked_div(right),
3991 "%" => left.checked_rem(right),
3992 "<<" => u32::try_from(right)
3993 .ok()
3994 .and_then(|shift| left.checked_shl(shift)),
3995 ">>" => u32::try_from(right)
3996 .ok()
3997 .and_then(|shift| left.checked_shr(shift)),
3998 "<" => Some(i128::from(left < right)),
3999 "<=" => Some(i128::from(left <= right)),
4000 ">" => Some(i128::from(left > right)),
4001 ">=" => Some(i128::from(left >= right)),
4002 "==" => Some(i128::from(left == right)),
4003 "!=" => Some(i128::from(left != right)),
4004 "&" => Some(left & right),
4005 "|" => Some(left | right),
4006 "^" => Some(left ^ right),
4007 "&&" => Some(i128::from(left != 0 && right != 0)),
4008 "||" => Some(i128::from(left != 0 || right != 0)),
4009 _ => None,
4010 }
4011 }
4012 _ => None,
4013 }
4014 }
4015
4016 fn mark_macro_events_ambiguous(
4017 &self,
4018 file: &ProjectFile,
4019 environment: &mut MacroEnvironment,
4020 include_stack: &mut HashSet<ProjectFile>,
4021 conditional_file: &ProjectFile,
4022 conditional_byte: usize,
4023 ) {
4024 if !include_stack.insert(file.clone()) {
4025 return;
4026 }
4027 if self.cpp.prepared_syntax(self.token, file).is_none() {
4028 environment.mark_unknown_names(conditional_file, conditional_byte);
4029 return;
4030 }
4031 match self.macro_include_protection(file) {
4032 MacroIncludeProtection::MacroGuard(guard) => {
4033 if environment
4034 .binding(&guard)
4035 .is_some_and(MacroBinding::is_exact)
4036 {
4037 return;
4038 }
4039 }
4040 MacroIncludeProtection::PragmaOnce => {
4041 if environment.applied_pragma_once_files.contains(file) {
4042 return;
4043 }
4044 environment
4045 .maybe_applied_pragma_once_files
4046 .insert(file.clone());
4047 }
4048 MacroIncludeProtection::None => {}
4049 }
4050 let cell = self.macro_event_cell(file);
4051 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4052 for event in events {
4053 #[cfg(any(test, feature = "test-support"))]
4054 self.macro_event_application_count
4055 .fetch_add(1, Ordering::Relaxed);
4056 match event {
4057 MacroEvent::Define { name, binding, .. } => {
4058 Self::merge_conditional_macro_definition(
4059 environment,
4060 name,
4061 binding,
4062 conditional_file,
4063 conditional_byte,
4064 );
4065 }
4066 MacroEvent::Undef { name, .. } => {
4067 if environment.binding(name).is_some() {
4068 environment.insert(
4069 name.clone(),
4070 MacroBinding::ambiguous(conditional_file, conditional_byte),
4071 );
4072 } else {
4073 environment.remove_known_undefined(name);
4074 }
4075 }
4076 MacroEvent::Include { targets, .. } => {
4077 if targets.is_empty() {
4078 environment.mark_unknown_names(conditional_file, conditional_byte);
4079 continue;
4080 }
4081 for target in targets {
4082 self.mark_macro_events_ambiguous(
4083 target,
4084 environment,
4085 include_stack,
4086 conditional_file,
4087 conditional_byte,
4088 );
4089 }
4090 }
4091 MacroEvent::Invalidate { .. } => {
4092 for binding in environment.bindings.values_mut() {
4093 *binding = MacroBinding::uncertain_from(
4094 binding,
4095 conditional_file,
4096 conditional_byte,
4097 );
4098 }
4099 }
4100 }
4101 }
4102 }
4103
4104 fn merge_conditional_macro_definition(
4105 environment: &mut MacroEnvironment,
4106 name: &str,
4107 possible_binding: &MacroBinding,
4108 conditional_file: &ProjectFile,
4109 conditional_byte: usize,
4110 ) {
4111 if environment.binding(name).is_some_and(|current| {
4116 current.definition != MacroDefinition::Unsupported
4117 && current.definition == possible_binding.definition
4118 }) {
4119 return;
4120 }
4121 environment.insert(
4122 name.to_string(),
4123 MacroBinding::ambiguous(conditional_file, conditional_byte),
4124 );
4125 }
4126
4127 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4128 let cell = self
4129 .macro_include_protection_cells
4130 .lock()
4131 .expect("C++ include protection cache poisoned")
4132 .entry(file.clone())
4133 .or_default()
4134 .clone();
4135 cell.get_or_init(|| {
4136 self.cpp.prepared_syntax(self.token, file).map_or(
4137 MacroIncludeProtection::None,
4138 |prepared| {
4139 top_level_macro_include_protection(
4140 prepared.tree().root_node(),
4141 prepared.source(),
4142 )
4143 },
4144 )
4145 })
4146 .clone()
4147 }
4148
4149 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4150 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4151 return Vec::new();
4152 };
4153 let source = prepared.source();
4154 let mut events = Vec::new();
4155 let root = prepared.tree().root_node();
4156 let mut stack = vec![root];
4157 while let Some(node) = stack.pop() {
4158 match node.kind() {
4159 "preproc_def" | "preproc_function_def" => {
4160 let Some(name) = node.child_by_field_name("name") else {
4161 continue;
4162 };
4163 let name = node_text(name, source).to_string();
4164 events.push(MacroEvent::Define {
4165 name,
4166 binding: MacroBinding {
4167 source: file.clone(),
4168 declaration_byte: node.start_byte(),
4169 definition: Self::decode_macro_definition(node, source),
4170 exact: true,
4171 },
4172 byte: node.start_byte(),
4173 conditionals: owning_preprocessor_conditionals(root, node, source),
4174 });
4175 continue;
4176 }
4177 "preproc_include" => {
4178 let Some(path) = node.child_by_field_name("path") else {
4179 events.push(MacroEvent::Include {
4180 targets: Vec::new(),
4181 byte: node.start_byte(),
4182 conditionals: owning_preprocessor_conditionals(root, node, source),
4183 });
4184 continue;
4185 };
4186 let include = structured_include_path(path, source);
4187 let targets = include.map_or_else(Vec::new, |include| {
4188 resolve_include_targets_with_index(
4189 file,
4190 include,
4191 self.cpp.include_target_index(),
4192 )
4193 });
4194 if targets.is_empty()
4205 && include.is_some_and(|include| {
4206 !self.cpp.include_target_index().names_indexed_file(include)
4207 })
4208 {
4209 continue;
4210 }
4211 events.push(MacroEvent::Include {
4212 targets,
4213 byte: node.start_byte(),
4214 conditionals: owning_preprocessor_conditionals(root, node, source),
4215 });
4216 continue;
4217 }
4218 "preproc_call" => {
4219 let Some(directive) = node.child_by_field_name("directive") else {
4220 continue;
4221 };
4222 if node_text(directive, source) != "#undef" {
4223 continue;
4224 }
4225 let name = node
4226 .child_by_field_name("argument")
4227 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
4228 if let Some(name) = name {
4229 events.push(MacroEvent::Undef {
4230 name,
4231 byte: node.start_byte(),
4232 conditionals: owning_preprocessor_conditionals(root, node, source),
4233 });
4234 } else {
4235 events.push(MacroEvent::Invalidate {
4236 byte: node.start_byte(),
4237 });
4238 }
4239 continue;
4240 }
4241 _ => {}
4242 }
4243 push_named_children_reversed(node, &mut stack);
4244 }
4245 events.sort_by_key(MacroEvent::byte);
4246 events
4247 }
4248
4249 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4250 self.ordinary_type_import_cells
4251 .lock()
4252 .expect("C++ ordinary type import cache poisoned")
4253 .entry(file.clone())
4254 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4255 .clone()
4256 }
4257
4258 pub fn project_using_index(
4259 &self,
4260 build: impl FnOnce() -> ProjectUsingIndex,
4261 ) -> &ProjectUsingIndex {
4262 self.project_using_index.get_or_init(build)
4263 }
4264
4265 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4266 let mut files = self
4267 .visible_source_files_by_root
4268 .values()
4269 .flatten()
4270 .cloned()
4271 .collect::<HashSet<_>>()
4272 .into_iter()
4273 .collect::<Vec<_>>();
4274 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4275 files
4276 }
4277
4278 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4279 self.visible_source_files_by_root
4280 .get(root)
4281 .is_some_and(|files| files.contains(source))
4282 }
4283
4284 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4285 let cached = self
4286 .visible_parser_alias_name_sets
4287 .read()
4288 .expect("visible parser alias-name cache poisoned")
4289 .get(file)
4290 .cloned();
4291 let cell = if let Some(cached) = cached {
4292 cached
4293 } else {
4294 let mut cells = self
4295 .visible_parser_alias_name_sets
4296 .write()
4297 .expect("visible parser alias-name cache poisoned");
4298 Arc::clone(
4299 cells
4300 .entry(file.clone())
4301 .or_insert_with(|| Arc::new(OnceLock::new())),
4302 )
4303 };
4304 cell.get_or_init(|| {
4305 #[cfg(any(test, feature = "test-support"))]
4306 self.visible_parser_alias_name_set_build_count
4307 .fetch_add(1, Ordering::Relaxed);
4308 let mut names = HashSet::default();
4309 let visible_files = self
4310 .visible_source_files_by_root
4311 .get(file)
4312 .cloned()
4313 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4314 for visible_file in visible_files {
4315 let aliases = {
4316 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4317 Arc::clone(
4318 cells
4319 .entry(visible_file.clone())
4320 .or_insert_with(|| Arc::new(OnceLock::new())),
4321 )
4322 };
4323 for alias in aliases
4324 .get_or_init(|| {
4325 self.parser_alias_source_parses
4326 .fetch_add(1, Ordering::Relaxed);
4327 #[cfg(any(test, feature = "test-support"))]
4328 {
4329 *self
4330 .alias_source_parse_counts
4331 .lock()
4332 .expect("alias source parse count lock")
4333 .entry(visible_file.clone())
4334 .or_default() += 1;
4335 }
4336 aliases_from_prepared_source(self.cpp, self.token, &visible_file)
4337 .into_boxed_slice()
4338 })
4339 .iter()
4340 {
4341 names.insert(alias.name.clone());
4342 }
4343 }
4344 names
4345 })
4346 .contains(name)
4347 }
4348
4349 pub fn parser_alias_name_may_resolve_to_target(
4350 &self,
4351 file: &ProjectFile,
4352 alias_name: &str,
4353 target: &CodeUnit,
4354 ) -> bool {
4355 let started = std::time::Instant::now();
4356 self.parser_alias_fallback_calls
4357 .fetch_add(1, Ordering::Relaxed);
4358 let key = (
4359 file.clone(),
4360 alias_name.to_string(),
4361 logical_symbol_key(target),
4362 );
4363 let cached = self
4364 .parser_alias_target_matches
4365 .read()
4366 .expect("parser alias target-match cache poisoned")
4367 .get(&key)
4368 .cloned();
4369 let cell = if let Some(cached) = cached {
4370 cached
4371 } else {
4372 let mut cells = self
4373 .parser_alias_target_matches
4374 .write()
4375 .expect("parser alias target-match cache poisoned");
4376 Arc::clone(
4377 cells
4378 .entry(key)
4379 .or_insert_with(|| Arc::new(OnceLock::new())),
4380 )
4381 };
4382 let matched = *cell.get_or_init(|| match self.visible_source_files_by_root.get(file) {
4383 None => {
4384 self.parser_alias_fallback_files
4385 .fetch_add(1, Ordering::Relaxed);
4386 self.file_alias_matches(self.cpp, file, alias_name, target)
4387 }
4388 Some(visible_files) => visible_files.iter().any(|visible_file| {
4389 self.parser_alias_fallback_files
4390 .fetch_add(1, Ordering::Relaxed);
4391 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4392 }),
4393 });
4394 self.parser_alias_fallback_elapsed_micros.fetch_add(
4395 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4396 Ordering::Relaxed,
4397 );
4398 matched
4399 }
4400
4401 fn file_alias_matches(
4402 &self,
4403 cpp: &dyn CppSource,
4404 file: &ProjectFile,
4405 alias_name: &str,
4406 target: &CodeUnit,
4407 ) -> bool {
4408 let cell = {
4409 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4410 Arc::clone(
4411 cells
4412 .entry(file.clone())
4413 .or_insert_with(|| Arc::new(OnceLock::new())),
4414 )
4415 };
4416 cell.get_or_init(|| {
4417 self.parser_alias_source_parses
4418 .fetch_add(1, Ordering::Relaxed);
4419 #[cfg(any(test, feature = "test-support"))]
4420 {
4421 *self
4422 .alias_source_parse_counts
4423 .lock()
4424 .expect("alias source parse count lock")
4425 .entry(file.clone())
4426 .or_default() += 1;
4427 }
4428 aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
4429 })
4430 .iter()
4431 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
4432 }
4433
4434 fn callable_arities_for_target(
4435 &self,
4436 analyzer: &CppGraphSource<'_>,
4437 cpp: &dyn CppSource,
4438 file: &ProjectFile,
4439 prepared: &PreparedSyntaxTree,
4440 spec: &TargetSpec,
4441 ) -> Vec<ActivatedCallableArity> {
4442 let Some(signature) = spec.target.signature() else {
4443 return Vec::new();
4444 };
4445 let Some(candidates) = self
4446 .visible_by_identifier
4447 .get(file)
4448 .and_then(|by_name| by_name.get(&spec.member_name))
4449 else {
4450 return Vec::new();
4451 };
4452 let differing_candidates = candidates
4453 .iter()
4454 .filter(|candidate| {
4455 candidate.is_function()
4456 && candidate.fq_name() == spec.target.fq_name()
4457 && candidate.signature() == Some(signature)
4458 })
4459 .filter_map(|candidate| {
4460 analyzer
4461 .signature_metadata(candidate)
4462 .into_iter()
4463 .find_map(|metadata| metadata.callable_arity())
4464 .filter(|arity| Some(*arity) != spec.callable_arity)
4465 .map(|arity| (candidate, arity))
4466 })
4467 .collect::<Vec<_>>();
4468 if differing_candidates.is_empty() {
4469 return Vec::new();
4470 }
4471 let mut arities = Vec::with_capacity(differing_candidates.len());
4472 let reference = CallableReferenceContext {
4475 file,
4476 position: None,
4477 };
4478 for (candidate, candidate_arity) in differing_candidates {
4479 let declaration_activation = if candidate.source() == file {
4480 callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
4481 } else {
4482 cpp.prepared_syntax(self.token, candidate.source())
4483 .and_then(|syntax| {
4484 callable_declaration_activation_in_file(
4485 analyzer,
4486 syntax.as_ref(),
4487 candidate,
4488 &reference,
4489 )
4490 })
4491 };
4492 let Some(declaration_activation) = declaration_activation else {
4493 continue;
4494 };
4495 let activation_byte = if candidate.source() == file {
4496 Some(declaration_activation)
4497 } else {
4498 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4499 };
4500 if let Some(activation_byte) = activation_byte {
4501 arities.push(ActivatedCallableArity {
4502 activation_byte,
4503 arity: candidate_arity,
4504 });
4505 }
4506 }
4507 arities
4508 }
4509
4510 fn callable_parameter_macro_arity(
4511 &self,
4512 target: &CodeUnit,
4513 signature: Option<&str>,
4514 ) -> Option<CallableArity> {
4515 let parameter_types = cpp_signature_param_types(signature?)?;
4516 let [macro_name] = parameter_types.as_slice() else {
4517 return None;
4518 };
4519 if macro_name.is_empty()
4520 || !macro_name
4521 .chars()
4522 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4523 {
4524 return None;
4525 }
4526 let cache_key = (target.source().clone(), macro_name.clone());
4527 if let Some(cached) = self
4528 .callable_parameter_macro_arities
4529 .lock()
4530 .expect("C++ callable parameter-macro arity cache poisoned")
4531 .get(&cache_key)
4532 .copied()
4533 {
4534 return cached;
4535 }
4536 let mut visible_files = HashSet::default();
4537 collect_include_closure(
4538 &self.cpp_source(),
4539 self.cpp.include_target_index(),
4540 target.source(),
4541 &mut visible_files,
4542 None,
4543 );
4544 let mut arities = Vec::new();
4545 for visible_file in visible_files {
4546 let cell = self.macro_event_cell(&visible_file);
4547 for event in
4548 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4549 {
4550 let MacroEvent::Define { name, binding, .. } = event else {
4551 continue;
4552 };
4553 if name != macro_name {
4554 continue;
4555 }
4556 let MacroDefinition::Object { replacement } = &binding.definition else {
4557 continue;
4558 };
4559 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4560 continue;
4561 };
4562 if !arities.contains(&arity) {
4563 arities.push(arity);
4564 }
4565 }
4566 }
4567 let resolved = (|| {
4568 let required = arities
4569 .iter()
4570 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4571 .min()?;
4572 let total = arities.iter().map(|arity| arity.total()).max()?;
4573 let repeated = arities
4574 .iter()
4575 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4576 Some(CallableArity::new(required, total, repeated))
4581 })();
4582 self.callable_parameter_macro_arities
4583 .lock()
4584 .expect("C++ callable parameter-macro arity cache poisoned")
4585 .insert(cache_key, resolved);
4586 resolved
4587 }
4588
4589 pub fn include_activation_for_source(
4590 &self,
4591 cpp: &dyn CppSource,
4592 file: &ProjectFile,
4593 prepared: &PreparedSyntaxTree,
4594 donor_source: &ProjectFile,
4595 ) -> Option<usize> {
4596 let key = (file.clone(), donor_source.clone());
4597 if let Some(cached) = self
4598 .include_activation_cells
4599 .lock()
4600 .expect("C++ include activation cache poisoned")
4601 .get(&key)
4602 .copied()
4603 {
4604 return cached;
4605 }
4606 #[cfg(any(test, feature = "test-support"))]
4607 self.include_activation_build_count
4608 .fetch_add(1, Ordering::Relaxed);
4609 let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
4610 let mut cells = self
4611 .include_activation_cells
4612 .lock()
4613 .expect("C++ include activation cache poisoned");
4614 *cells.entry(key).or_insert(activation)
4615 }
4616
4617 pub fn conditional_include_projections_for_source(
4618 &self,
4619 file: &ProjectFile,
4620 prepared: &PreparedSyntaxTree,
4621 donor_source: &ProjectFile,
4622 ) -> Arc<[ConditionalIncludeProjection]> {
4623 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4624 let cell = self
4625 .conditional_include_projection_cells
4626 .lock()
4627 .expect("C++ conditional include projection cache poisoned")
4628 .entry(file.clone())
4629 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4630 .clone();
4631 let index = cell.get_or_build_pool_independent(|| {
4632 #[cfg(any(test, feature = "test-support"))]
4633 self.conditional_include_projection_index_build_count
4634 .fetch_add(1, Ordering::Relaxed);
4635 find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
4636 #[cfg(any(test, feature = "test-support"))]
4637 self.conditional_include_projection_state_count
4638 .fetch_add(1, Ordering::Relaxed);
4639 })
4640 });
4641 index
4642 .get(donor_source)
4643 .cloned()
4644 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4645 }
4646
4647 #[cfg(any(test, feature = "test-support"))]
4648 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4649 (
4650 self.conditional_include_projection_index_build_count
4651 .load(Ordering::Relaxed),
4652 self.conditional_include_projection_state_count
4653 .load(Ordering::Relaxed),
4654 )
4655 }
4656
4657 #[cfg(any(test, feature = "test-support"))]
4658 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4659 self.conditional_include_target_state_count
4660 .load(Ordering::Relaxed)
4661 }
4662
4663 #[cfg(any(test, feature = "test-support"))]
4664 pub fn include_activation_build_count_for_test(&self) -> usize {
4665 self.include_activation_build_count.load(Ordering::Relaxed)
4666 }
4667
4668 #[cfg(any(test, feature = "test-support"))]
4669 pub fn note_using_donor_activation_for_test(&self) {
4670 self.using_donor_activation_count
4671 .fetch_add(1, Ordering::Relaxed);
4672 }
4673
4674 #[cfg(not(any(test, feature = "test-support")))]
4675 pub fn note_using_donor_activation_for_test(&self) {}
4676
4677 #[cfg(any(test, feature = "test-support"))]
4678 pub fn note_using_namespace_lookup_for_test(&self) {
4679 self.using_namespace_lookup_count
4680 .fetch_add(1, Ordering::Relaxed);
4681 }
4682
4683 #[cfg(not(any(test, feature = "test-support")))]
4684 pub fn note_using_namespace_lookup_for_test(&self) {}
4685
4686 #[cfg(any(test, feature = "test-support"))]
4687 pub fn note_using_name_candidate_inspection_for_test(&self) {
4688 self.using_name_candidate_inspection_count
4689 .fetch_add(1, Ordering::Relaxed);
4690 }
4691
4692 #[cfg(not(any(test, feature = "test-support")))]
4693 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4694
4695 #[cfg(any(test, feature = "test-support"))]
4696 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4697 (
4698 self.using_donor_activation_count.load(Ordering::Relaxed),
4699 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4700 self.callable_reference_spec_build_count
4701 .load(Ordering::Relaxed),
4702 self.using_name_candidate_inspection_count
4703 .load(Ordering::Relaxed),
4704 )
4705 }
4706
4707 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4708 file == target.source()
4709 || self
4710 .visible_by_file
4711 .get(file)
4712 .is_some_and(|visible| visible.contains(target))
4713 }
4714
4715 pub fn declaration_visible_at(
4727 &self,
4728 analyzer: &CppGraphSource<'_>,
4729 file: &ProjectFile,
4730 declaration: &CodeUnit,
4731 reference_byte: usize,
4732 ) -> bool {
4733 let reference_guards = OnceCell::new();
4734 self.visible_identifier_candidates(file, declaration.identifier())
4735 .filter(|candidate| {
4736 self.same_logical_callable(analyzer, candidate, declaration)
4737 || flattened_macro_namespace_declaration_matches(
4738 analyzer,
4739 self.cpp,
4740 file,
4741 candidate,
4742 declaration,
4743 reference_byte,
4744 )
4745 })
4746 .any(|candidate| {
4747 self.physical_declaration_visible_at(
4748 analyzer,
4749 file,
4750 candidate,
4751 reference_byte,
4752 &reference_guards,
4753 )
4754 })
4755 }
4756
4757 pub fn declaration_visible_at_reference(
4764 &self,
4765 analyzer: &CppGraphSource<'_>,
4766 file: &ProjectFile,
4767 declaration: &CodeUnit,
4768 reference: Node<'_>,
4769 ) -> bool {
4770 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4771 return false;
4772 };
4773 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4774 let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4775 if !declaration_guards.iter().any(|(_, required)| {
4776 guards_compatible_at_reference(required, reference_guards.as_ref())
4777 }) {
4778 return false;
4779 }
4780 if declaration.source() == file
4781 && !analyzer.reference_uses_c_semantics(file)
4782 && (declaration.is_field() || declaration.is_callable())
4783 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4784 self.indexed_enclosing_owner_scope(analyzer, file, reference)
4785 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4786 })
4787 {
4788 return true;
4792 }
4793 if declaration.is_field() && declaration.source() == file {
4794 let reference_byte = reference.start_byte();
4795 let reference_function =
4796 real_function_definition_ancestor(reference, prepared.source());
4797 let guards = OnceCell::new();
4798 let field_reference = CallableReferenceContext {
4799 file,
4800 position: Some(CallableReferencePosition {
4801 prepared: prepared.as_ref(),
4802 byte: reference_byte,
4803 guards: &guards,
4804 }),
4805 };
4806 let mut has_local_declaration = false;
4807 let mut local_declaration_visible = false;
4808 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4809 {
4810 let Some(declaration_function) =
4811 real_function_definition_ancestor(declaration, prepared.source())
4812 else {
4813 continue;
4814 };
4815 has_local_declaration = true;
4816 if reference_function.is_some_and(|reference_function| {
4817 reference_function.start_byte() == declaration_function.start_byte()
4818 && reference_function.end_byte() == declaration_function.end_byte()
4819 }) && callable_preprocessor_context_is_visible_for_reference(
4820 declaration,
4821 prepared.source(),
4822 &field_reference,
4823 ) && callable_declaration_activation_byte(declaration) < reference_byte
4824 {
4825 local_declaration_visible = true;
4826 break;
4827 }
4828 }
4829 if has_local_declaration {
4830 return local_declaration_visible;
4831 }
4832 }
4833 let guards = OnceCell::new();
4834 self.physical_declaration_visible_at(
4835 analyzer,
4836 file,
4837 declaration,
4838 reference.start_byte(),
4839 &guards,
4840 )
4841 }
4842
4843 pub fn declaration_visible_for_c_forward_call(
4849 &self,
4850 analyzer: &CppGraphSource<'_>,
4851 file: &ProjectFile,
4852 declaration: &CodeUnit,
4853 reference_byte: usize,
4854 ) -> bool {
4855 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4856 return true;
4857 }
4858 if declaration.source() != file {
4859 return false;
4860 }
4861 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4862 return false;
4863 };
4864 let reference_guards = prepared
4865 .tree()
4866 .root_node()
4867 .descendant_for_byte_range(reference_byte, reference_byte)
4868 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4869 declaration_guard_requirements(analyzer, self.cpp, declaration)
4870 .into_iter()
4871 .any(|(_, required)| {
4872 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4873 })
4874 }
4875
4876 pub fn callable_arity_at_reference(
4877 &self,
4878 analyzer: &CppGraphSource<'_>,
4879 file: &ProjectFile,
4880 candidate: &CodeUnit,
4881 reference_byte: usize,
4882 ) -> Option<CallableArity> {
4883 let key = (file.clone(), logical_symbol_key(candidate));
4884 let cell = self
4885 .callable_reference_specs
4886 .lock()
4887 .expect("C++ callable reference-spec cache poisoned")
4888 .entry(key)
4889 .or_default()
4890 .clone();
4891 let spec = cell.get_or_init(|| {
4892 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4893 let spec = TargetSpec::from_target(analyzer, candidate)?;
4894 let spec = spec
4895 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4896 .into_owned();
4897 #[cfg(any(test, feature = "test-support"))]
4898 self.callable_reference_spec_build_count
4899 .fetch_add(1, Ordering::Relaxed);
4900 Some(spec)
4901 });
4902 spec.as_ref()?.callable_arity_at(reference_byte)
4903 }
4904
4905 fn physical_declaration_visible_at(
4906 &self,
4907 analyzer: &CppGraphSource<'_>,
4908 file: &ProjectFile,
4909 declaration: &CodeUnit,
4910 reference_byte: usize,
4911 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4912 ) -> bool {
4913 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4914 return false;
4915 };
4916 let reference = CallableReferenceContext {
4917 file,
4918 position: Some(CallableReferencePosition {
4919 prepared: prepared.as_ref(),
4920 byte: reference_byte,
4921 guards: reference_guards,
4922 }),
4923 };
4924 if declaration.source() == file {
4925 return callable_declaration_activation_in_file(
4926 analyzer,
4927 prepared.as_ref(),
4928 declaration,
4929 &reference,
4930 )
4931 .or_else(|| {
4932 self.exhaustive_guard_family_activation(
4933 analyzer,
4934 prepared.as_ref(),
4935 declaration,
4936 &reference,
4937 )
4938 })
4939 .is_some_and(|activation| activation < reference_byte);
4940 }
4941 let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
4942 return false;
4943 };
4944 if self
4945 .foreign_callable_declaration_activation(
4946 analyzer,
4947 donor_syntax.as_ref(),
4948 declaration,
4949 &reference,
4950 )
4951 .or_else(|| {
4952 self.exhaustive_guard_family_activation(
4953 analyzer,
4954 donor_syntax.as_ref(),
4955 declaration,
4956 &reference,
4957 )
4958 })
4959 .is_none()
4960 {
4961 return false;
4962 }
4963 declaration_guard_requirements(analyzer, self.cpp, declaration)
4964 .into_iter()
4965 .any(|(_, declaration_guards)| {
4966 self.foreign_declaration_reachable_at_reference(
4967 file,
4968 prepared.as_ref(),
4969 declaration.source(),
4970 &declaration_guards,
4971 reference.guards(),
4972 reference_byte,
4973 )
4974 })
4975 }
4976
4977 fn foreign_callable_declaration_activation(
4992 &self,
4993 analyzer: &CppGraphSource<'_>,
4994 donor_syntax: &PreparedSyntaxTree,
4995 declaration: &CodeUnit,
4996 reference: &CallableReferenceContext<'_>,
4997 ) -> Option<usize> {
4998 let build_decides = !self.compile_context_is_absent(reference.file);
4999 let proven = self.compile_proven_guards(reference.file);
5000 let augmented;
5001 let active = match reference.guards() {
5002 Some(active) if !proven.is_empty() => {
5003 augmented = active.union(&proven).cloned().collect();
5004 Some(&augmented)
5005 }
5006 other => other,
5007 };
5008 nameable_callable_declaration_nodes(analyzer, donor_syntax, declaration)
5009 .into_iter()
5010 .filter(|node| {
5011 let Some(required) = callable_declaration_guard_requirements(
5012 *node,
5013 donor_syntax.source(),
5014 reference,
5015 ) else {
5016 return false;
5017 };
5018 if required.is_empty() {
5019 return true;
5020 }
5021 if build_decides {
5022 guard_requirements_hold_at_reference(&required, active)
5023 } else {
5024 guards_compatible_at_reference(&required, reference.guards())
5025 }
5026 })
5027 .map(callable_declaration_activation_byte)
5028 .min()
5029 }
5030
5031 pub fn external_type_candidate_visible_at(
5032 &self,
5033 file: &ProjectFile,
5034 candidate: &CodeUnit,
5035 reference_byte: usize,
5036 ) -> bool {
5037 if candidate.source() == file {
5038 return true;
5039 }
5040 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5041 return false;
5042 };
5043 self.visible_identifier_candidates(file, candidate.identifier())
5044 .filter(|peer| same_logical_symbol(candidate, peer))
5045 .any(|peer| {
5046 peer.source() == file
5047 || self
5048 .include_activation_for_source(
5049 self.cpp,
5050 file,
5051 prepared.as_ref(),
5052 peer.source(),
5053 )
5054 .is_some_and(|activation| activation <= reference_byte)
5055 })
5056 }
5057
5058 pub fn external_type_declaration_visible_at(
5059 &self,
5060 file: &ProjectFile,
5061 candidate: &CodeUnit,
5062 reference_byte: usize,
5063 ) -> bool {
5064 if candidate.source() == file {
5065 return true;
5066 }
5067 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5068 return false;
5069 };
5070 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5071 .is_some_and(|activation| activation <= reference_byte)
5072 }
5073
5074 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5093 if let Some(cached) = self
5094 .compile_proven_guard_cells
5095 .lock()
5096 .expect("C++ compile-proven guard cache poisoned")
5097 .get(file)
5098 {
5099 return Arc::clone(cached);
5100 }
5101 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5102 Some(names) => names,
5103 None => {
5104 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5105 let seed = translation_units.next().and_then(|translation_unit| {
5106 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5107 });
5108 match seed {
5109 None => HashSet::default(),
5110 Some(mut names) => {
5111 for translation_unit in translation_units {
5112 let Some(reached) = context_fact_names(
5113 self.cpp.compile_contexts_for(&translation_unit),
5114 ) else {
5115 names.clear();
5116 break;
5117 };
5118 names.retain(|name| reached.contains(name));
5119 if names.is_empty() {
5120 break;
5121 }
5122 }
5123 names
5124 }
5125 }
5126 }
5127 };
5128 let proven = Arc::new(
5129 names
5130 .into_iter()
5131 .map(PreprocessorGuard::Defined)
5132 .collect::<HashSet<_>>(),
5133 );
5134 self.compile_proven_guard_cells
5135 .lock()
5136 .expect("C++ compile-proven guard cache poisoned")
5137 .insert(file.clone(), Arc::clone(&proven));
5138 proven
5139 }
5140
5141 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5151 if let Some(cached) = self
5152 .include_path_admission_cells
5153 .lock()
5154 .expect("C++ include-path admission cache poisoned")
5155 .get(file)
5156 .copied()
5157 {
5158 return cached;
5159 }
5160 let admission = if self.compile_context_is_absent(file) {
5161 IncludePathAdmission::Compatible
5162 } else {
5163 IncludePathAdmission::Proven
5164 };
5165 self.include_path_admission_cells
5166 .lock()
5167 .expect("C++ include-path admission cache poisoned")
5168 .insert(file.clone(), admission);
5169 admission
5170 }
5171
5172 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5179 if !self.cpp.compile_contexts_for(file).is_empty() {
5180 return false;
5181 }
5182 let translation_units = self.cpp.reaching_translation_units(file);
5183 translation_units.is_empty()
5184 || translation_units
5185 .iter()
5186 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5187 }
5188
5189 pub fn miss_requires_compile_context(
5201 &self,
5202 file: &ProjectFile,
5203 identifier: &str,
5204 reference: Node<'_>,
5205 ) -> bool {
5206 if !self.compile_context_is_absent(file) {
5207 return false;
5208 }
5209 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5210 return false;
5211 };
5212 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5213 let reference_byte = reference.start_byte();
5214 let mut sources = self
5215 .visible_identifier_candidates(file, identifier)
5216 .map(CodeUnit::source)
5217 .filter(|source| *source != file)
5218 .collect::<Vec<_>>();
5219 sources.sort();
5220 sources.dedup();
5221 sources.into_iter().any(|declaration_source| {
5222 self.conditional_include_projections_for_source(
5223 file,
5224 prepared.as_ref(),
5225 declaration_source,
5226 )
5227 .iter()
5228 .any(|projection| {
5229 projection.activation_byte <= reference_byte
5230 && !guard_requirements_hold_at_reference(
5231 &projection.required_guards,
5232 reference_guards.as_ref(),
5233 )
5234 && guards_compatible_at_reference(
5235 &projection.required_guards,
5236 reference_guards.as_ref(),
5237 )
5238 })
5239 })
5240 }
5241
5242 fn foreign_declaration_reachable_at_reference(
5254 &self,
5255 file: &ProjectFile,
5256 prepared: &PreparedSyntaxTree,
5257 declaration_source: &ProjectFile,
5258 declaration_guards: &HashSet<PreprocessorGuard>,
5259 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5260 reference_byte: usize,
5261 ) -> bool {
5262 let proven = self.compile_proven_guards(file);
5268 let augmented;
5269 let reference_guards = match reference_guards {
5270 Some(active) if !proven.is_empty() => {
5271 augmented = active.union(&proven).cloned().collect();
5272 Some(&augmented)
5273 }
5274 other => other,
5275 };
5276 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5277 eprintln!(
5278 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5279 declaration_source.rel_path().display(),
5280 );
5281 }
5282 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5283 return false;
5284 }
5285 if self
5286 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5287 .is_some_and(|activation| activation <= reference_byte)
5288 {
5289 return true;
5290 }
5291 let projections =
5292 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5293 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5294 eprintln!(
5295 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5296 declaration_source.rel_path().display(),
5297 declaration_guards.len(),
5298 proven.len(),
5299 projections.len(),
5300 );
5301 }
5302 let admission = self.include_path_admission(file);
5303 projections.iter().any(|projection| {
5304 projection.activation_byte <= reference_byte
5305 && admission.admits(
5306 &projection.required_guards,
5307 &projection.partial_guards,
5308 reference_guards,
5309 )
5310 && self.preprocessor_guards_stable_between(
5311 file,
5312 projection.activation_byte,
5313 reference_byte,
5314 &projection.required_guards,
5315 )
5316 })
5317 }
5318
5319 fn foreign_declaration_may_be_reachable_from_raw_guards(
5320 &self,
5321 file: &ProjectFile,
5322 prepared: &PreparedSyntaxTree,
5323 declaration_source: &ProjectFile,
5324 declaration_guards: &HashSet<PreprocessorGuard>,
5325 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5326 reference_byte: usize,
5327 ) -> bool {
5328 let proven = self.compile_proven_guards(file);
5329 let augmented;
5330 let reference_guards = match reference_guards {
5331 Some(active) if !proven.is_empty() => {
5332 augmented = active.union(&proven).cloned().collect();
5333 Some(&augmented)
5334 }
5335 other => other,
5336 };
5337 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5338 return false;
5339 }
5340 if self
5341 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5342 .is_some_and(|activation| activation <= reference_byte)
5343 {
5344 return true;
5345 }
5346 let reachable = find_conditional_include_projection_for_source(
5347 self.cpp,
5348 self.token,
5349 file,
5350 prepared,
5351 declaration_source,
5352 self.include_path_admission(file),
5353 reference_guards,
5354 reference_byte,
5355 &|| {
5356 #[cfg(any(test, feature = "test-support"))]
5357 self.conditional_include_target_state_count
5358 .fetch_add(1, Ordering::Relaxed);
5359 },
5360 );
5361 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5362 eprintln!(
5363 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5364 declaration_source.rel_path().display(),
5365 declaration_guards.len(),
5366 proven.len(),
5367 reference_guards.map_or(0, HashSet::len),
5368 );
5369 }
5370 reachable
5371 }
5372
5373 fn foreign_declaration_reachable_from_compile_proven_guards(
5374 &self,
5375 file: &ProjectFile,
5376 prepared: &PreparedSyntaxTree,
5377 declaration_source: &ProjectFile,
5378 declaration_guards: &HashSet<PreprocessorGuard>,
5379 reference_byte: usize,
5380 ) -> bool {
5381 let proven = self.compile_proven_guards(file);
5382 if proven.is_empty()
5383 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5384 {
5385 return false;
5386 }
5387 let projections =
5388 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5389 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5390 eprintln!(
5391 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5392 declaration_source.rel_path().display(),
5393 declaration_guards.len(),
5394 proven.len(),
5395 projections.len(),
5396 );
5397 }
5398 projections.iter().any(|projection| {
5399 projection.activation_byte <= reference_byte
5400 && guard_requirements_hold_at_reference(
5401 &projection.required_guards,
5402 Some(proven.as_ref()),
5403 )
5404 && self.preprocessor_guards_stable_between(
5409 file,
5410 0,
5411 projection.activation_byte,
5412 &projection.required_guards,
5413 )
5414 })
5415 }
5416
5417 pub fn external_type_candidate_visible_in_context(
5418 &self,
5419 analyzer: &CppGraphSource<'_>,
5420 file: &ProjectFile,
5421 candidate: &CodeUnit,
5422 reference: Node<'_>,
5423 ) -> bool {
5424 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5425 if report_stats {
5426 eprintln!(
5427 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5428 candidate.fq_name(),
5429 candidate.source().rel_path().display(),
5430 file.rel_path().display(),
5431 reference.start_byte(),
5432 );
5433 }
5434 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5435 return false;
5436 };
5437 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5438 let reference_guards = OnceCell::new();
5439 let reference_guards_at_site = || {
5440 reference_guards.get_or_init(|| {
5441 let started = Instant::now();
5442 if report_stats {
5443 eprintln!(
5444 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5445 file.rel_path().display(),
5446 reference.start_byte(),
5447 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5448 );
5449 }
5450 let macro_environment = self.macro_environment(file, reference.start_byte());
5451 let filtered = raw_reference_guards
5452 .clone()
5453 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5454 if report_stats {
5455 eprintln!(
5456 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5457 filtered.is_some(),
5458 started.elapsed().as_millis(),
5459 );
5460 }
5461 filtered
5462 })
5463 };
5464
5465 let peers = self
5466 .visible_identifier_candidates(file, candidate.identifier())
5467 .filter(|peer| same_logical_symbol(candidate, peer))
5468 .collect::<Vec<_>>();
5469 if report_stats {
5470 let peer_sources = peers
5471 .iter()
5472 .map(|peer| peer.source().rel_path().display().to_string())
5473 .collect::<Vec<_>>();
5474 eprintln!(
5475 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5476 candidate.fq_name(),
5477 );
5478 }
5479 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5480 declaration_guard_requirements(analyzer, self.cpp, peer)
5481 .into_iter()
5482 .any(|(declaration_byte, declaration_guards)| {
5483 if peer.source() == file {
5484 let visible = declaration_byte < reference.start_byte()
5485 && declaration_guards.is_empty();
5486 if report_stats {
5487 eprintln!(
5488 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5489 peer.source().rel_path().display(),
5490 declaration_guards.len(),
5491 );
5492 }
5493 return visible;
5494 }
5495 let direct = declaration_guards.is_empty()
5496 && self
5497 .include_activation_for_source(
5498 self.cpp,
5499 file,
5500 prepared.as_ref(),
5501 peer.source(),
5502 )
5503 .is_some_and(|activation| activation <= reference.start_byte());
5504 let compile_proven = !direct
5505 && self.foreign_declaration_reachable_from_compile_proven_guards(
5506 file,
5507 prepared.as_ref(),
5508 peer.source(),
5509 &declaration_guards,
5510 reference.start_byte(),
5511 );
5512 if report_stats {
5513 eprintln!(
5514 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5515 peer.source().rel_path().display(),
5516 declaration_guards.len(),
5517 );
5518 }
5519 direct || compile_proven
5520 })
5521 });
5522 if directly_visible_without_reference_environment {
5523 if report_stats {
5524 eprintln!(
5525 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5526 candidate.fq_name(),
5527 );
5528 }
5529 return true;
5530 }
5531 let directly_visible = peers.iter().any(|peer| {
5532 declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5533 .into_iter()
5534 .any(|site| {
5535 let declaration_byte = site.byte;
5536 let declaration_guards = site.guards;
5537 if peer.source() == file {
5538 if declaration_byte >= reference.start_byte() {
5539 return false;
5540 }
5541 let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5546 guard_requirements_hold_at_reference(&declaration_guards, guards)
5547 || (site.completed_family_before_reference
5548 && guards_compatible_at_reference(&declaration_guards, guards))
5549 };
5550 if !active(raw_reference_guards.as_ref()) {
5551 return false;
5552 }
5553 return active(reference_guards_at_site().as_ref())
5554 && self.preprocessor_guards_stable_between(
5555 file,
5556 declaration_byte,
5557 reference.start_byte(),
5558 &declaration_guards,
5559 );
5560 }
5561 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5562 file,
5563 prepared.as_ref(),
5564 peer.source(),
5565 &declaration_guards,
5566 raw_reference_guards.as_ref(),
5567 reference.start_byte(),
5568 );
5569 if report_stats {
5570 eprintln!(
5571 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5572 peer.source().rel_path().display(),
5573 declaration_guards.len(),
5574 );
5575 }
5576 if !raw_feasible {
5577 return false;
5578 }
5579 self.foreign_declaration_reachable_at_reference(
5580 file,
5581 prepared.as_ref(),
5582 peer.source(),
5583 &declaration_guards,
5584 reference_guards_at_site().as_ref(),
5585 reference.start_byte(),
5586 )
5587 })
5588 });
5589 if directly_visible {
5590 if report_stats {
5591 eprintln!(
5592 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5593 candidate.fq_name(),
5594 );
5595 }
5596 return true;
5597 }
5598 let complementary = self
5599 .visible_identifier_candidates(file, candidate.identifier())
5600 .filter(|peer| {
5601 peer.kind() == candidate.kind()
5602 && peer.fq_name() == candidate.fq_name()
5603 && peer.source() == candidate.source()
5604 })
5605 .collect::<Vec<_>>();
5606 let complementary_family =
5611 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5612 let raw_candidate_branch_compatible = complementary_family
5613 && raw_reference_guards.as_ref().is_some_and(|active| {
5614 declaration_guard_requirements(analyzer, self.cpp, candidate)
5615 .iter()
5616 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5617 });
5618 if report_stats {
5619 eprintln!(
5620 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5621 candidate.fq_name(),
5622 complementary.len(),
5623 complementary_family,
5624 raw_candidate_branch_compatible,
5625 );
5626 }
5627 let candidate_branch_compatible = raw_candidate_branch_compatible
5628 && reference_guards_at_site().as_ref().is_some_and(|active| {
5629 declaration_guard_requirements(analyzer, self.cpp, candidate)
5630 .iter()
5631 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5632 });
5633 let complementary_visible = candidate_branch_compatible
5634 && if candidate.source() == file {
5635 declaration_guard_requirements(analyzer, self.cpp, candidate)
5636 .iter()
5637 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5638 } else {
5639 self.include_activation_for_source(
5640 self.cpp,
5641 file,
5642 prepared.as_ref(),
5643 candidate.source(),
5644 )
5645 .is_some_and(|activation| activation <= reference.start_byte())
5646 };
5647 if report_stats {
5648 eprintln!(
5649 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5650 if complementary_visible {
5651 "complementary"
5652 } else {
5653 "missing"
5654 },
5655 candidate.fq_name(),
5656 );
5657 }
5658 complementary_visible
5659 }
5660
5661 pub fn is_exhaustive_same_fqn_type_declaration_family(
5662 &self,
5663 analyzer: &CppGraphSource<'_>,
5664 file: &ProjectFile,
5665 candidate: &CodeUnit,
5666 ) -> bool {
5667 let candidates = self
5668 .visible_identifier_candidates(file, candidate.identifier())
5669 .filter(|peer| {
5670 peer.kind() == candidate.kind()
5671 && peer.fq_name() == candidate.fq_name()
5672 && peer.source() == candidate.source()
5673 })
5674 .collect::<Vec<_>>();
5675 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5676 }
5677
5678 pub fn dependent_member_pointer_alias_visible_in_context(
5693 &self,
5694 analyzer: &CppGraphSource<'_>,
5695 file: &ProjectFile,
5696 candidate: &CodeUnit,
5697 owner_components: &[String],
5698 reference: Node<'_>,
5699 ) -> bool {
5700 if !analyzer
5701 .type_alias_provider()
5702 .is_some_and(|provider| provider.is_type_alias(candidate))
5703 {
5704 return false;
5705 }
5706 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5707 return false;
5708 };
5709 if terminal != candidate.identifier()
5710 || canonical_cpp_scope_components(candidate) != owner_components
5711 {
5712 return false;
5713 }
5714 let Some(expected_parent_fq_name) =
5715 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5716 else {
5717 return false;
5718 };
5719 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5720 return false;
5721 };
5722 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5723 || parent_anchor.source() != candidate.source()
5724 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5725 {
5726 return false;
5727 }
5728
5729 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5735 || candidate.source() == file
5736 && !analyzer
5737 .ranges(candidate)
5738 .iter()
5739 .any(|range| range.start_byte < reference.start_byte())
5740 {
5741 return false;
5742 }
5743
5744 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5745 if candidate_guards.is_empty() {
5746 return false;
5747 }
5748 let same_guard_sets =
5749 |left: &[(usize, HashSet<PreprocessorGuard>)],
5750 right: &[(usize, HashSet<PreprocessorGuard>)]| {
5751 left.iter().all(|(_, left_guards)| {
5752 right
5753 .iter()
5754 .any(|(_, right_guards)| left_guards == right_guards)
5755 })
5756 };
5757 let parent_candidates = self
5758 .visible_identifier_candidates(file, parent_anchor.identifier())
5759 .filter(|peer| {
5760 peer.kind() == parent_anchor.kind()
5761 && peer.fq_name() == expected_parent_fq_name.as_str()
5762 && peer.source() == parent_anchor.source()
5763 && canonical_cpp_scope_components(peer) == owner_prefix
5764 })
5765 .filter_map(|peer| {
5766 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5767 (candidate_guards.len() == parent_guards.len()
5768 && same_guard_sets(&candidate_guards, &parent_guards)
5769 && same_guard_sets(&parent_guards, &candidate_guards))
5770 .then(|| (peer.clone(), parent_guards))
5771 })
5772 .collect::<Vec<_>>();
5773 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5774 return false;
5775 };
5776
5777 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5778 return false;
5779 };
5780 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5781 else {
5782 return false;
5783 };
5784 if !candidate_guards.iter().any(|(_, target_guards)| {
5789 guards_compatible_at_reference(target_guards, Some(&reference_guards))
5790 && (candidate.source() != file
5791 || self.preprocessor_guards_stable_between(
5792 file,
5793 0,
5794 reference.start_byte(),
5795 target_guards,
5796 ))
5797 }) {
5798 return false;
5799 }
5800
5801 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5802 }
5803
5804 pub fn external_type_candidate_guard_compatible_in_context(
5814 &self,
5815 analyzer: &CppGraphSource<'_>,
5816 file: &ProjectFile,
5817 candidate: &CodeUnit,
5818 reference: Node<'_>,
5819 ) -> bool {
5820 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5821 return false;
5822 };
5823 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5824
5825 self.visible_identifier_candidates(file, candidate.identifier())
5826 .filter(|peer| same_logical_symbol(candidate, peer))
5827 .any(|peer| {
5828 declaration_guard_requirements(analyzer, self.cpp, peer)
5829 .into_iter()
5830 .any(|(declaration_byte, declaration_guards)| {
5831 if peer.source() == file {
5832 let (start, end) = if declaration_byte <= reference.start_byte() {
5833 (declaration_byte, reference.start_byte())
5834 } else {
5835 (reference.start_byte(), declaration_byte)
5836 };
5837 return guard_requirements_hold_at_reference(
5838 &declaration_guards,
5839 reference_guards.as_ref(),
5840 ) && self.preprocessor_guards_stable_between(
5841 file,
5842 start,
5843 end,
5844 &declaration_guards,
5845 );
5846 }
5847 self.foreign_declaration_reachable_at_reference(
5848 file,
5849 prepared.as_ref(),
5850 peer.source(),
5851 &declaration_guards,
5852 reference_guards.as_ref(),
5853 reference.start_byte(),
5854 )
5855 })
5856 })
5857 }
5858
5859 pub fn same_file_callable_guard_compatible_ignoring_order(
5867 &self,
5868 analyzer: &CppGraphSource<'_>,
5869 file: &ProjectFile,
5870 candidate: &CodeUnit,
5871 reference: Node<'_>,
5872 ) -> bool {
5873 if candidate.source() != file || !candidate.is_callable() {
5874 return false;
5875 }
5876 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5877 return false;
5878 };
5879 let guards = OnceCell::new();
5880 let context = CallableReferenceContext {
5881 file,
5882 position: Some(CallableReferencePosition {
5883 prepared: prepared.as_ref(),
5884 byte: reference.start_byte(),
5885 guards: &guards,
5886 }),
5887 };
5888 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5889 .into_iter()
5890 .any(|declaration| {
5891 callable_preprocessor_context_is_visible_for_reference(
5892 declaration,
5893 prepared.source(),
5894 &context,
5895 )
5896 })
5897 }
5898
5899 pub fn type_candidate_may_be_visible_before_reference(
5900 &self,
5901 analyzer: &CppGraphSource<'_>,
5902 file: &ProjectFile,
5903 candidate: &CodeUnit,
5904 reference_byte: usize,
5905 ) -> bool {
5906 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5907 return false;
5908 };
5909 let root = prepared.tree().root_node();
5910 let end_byte = reference_byte
5911 .saturating_add(1)
5912 .min(prepared.source().len());
5913 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5914 return false;
5915 };
5916 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5917 }
5918
5919 pub fn preprocessor_guards_stable_between(
5920 &self,
5921 file: &ProjectFile,
5922 start_byte: usize,
5923 end_byte: usize,
5924 guards: &HashSet<PreprocessorGuard>,
5925 ) -> bool {
5926 if guards.is_empty() || start_byte >= end_byte {
5927 return true;
5928 }
5929 let cell = self.macro_event_cell(file);
5930 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5931 let mut visited = HashSet::from_iter([file.clone()]);
5932 !events.iter().any(|event| {
5933 event.byte() >= start_byte
5934 && event.byte() < end_byte
5935 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5936 })
5937 }
5938
5939 fn macro_event_may_mutate_guards(
5940 &self,
5941 event: &MacroEvent,
5942 guards: &HashSet<PreprocessorGuard>,
5943 visited: &mut HashSet<ProjectFile>,
5944 ) -> bool {
5945 match event {
5946 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5947 guards.iter().any(|guard| guard.may_depend_on_macro(name))
5948 }
5949 MacroEvent::Include { targets, .. } => {
5950 targets.is_empty()
5951 || targets
5952 .iter()
5953 .any(|target| self.source_may_mutate_guards(target, guards, visited))
5954 }
5955 MacroEvent::Invalidate { .. } => true,
5956 }
5957 }
5958
5959 fn source_may_mutate_guards(
5960 &self,
5961 file: &ProjectFile,
5962 guards: &HashSet<PreprocessorGuard>,
5963 visited: &mut HashSet<ProjectFile>,
5964 ) -> bool {
5965 if !visited.insert(file.clone()) {
5966 return false;
5967 }
5968 let cell = self.macro_event_cell(file);
5969 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5970 events
5971 .iter()
5972 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5973 }
5974
5975 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5976 let normalized = normalize_reference_name(raw_name)?;
5977 self.type_candidates(file, &normalized)
5978 .into_iter()
5979 .next()
5980 .cloned()
5981 }
5982
5983 pub fn unique_visible_parameter_type_fallback(
5992 &self,
5993 analyzer: &CppGraphSource<'_>,
5994 file: &ProjectFile,
5995 node: Node<'_>,
5996 source: &str,
5997 ) -> Option<CodeUnit> {
5998 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5999 return None;
6000 }
6001 let name = node_text(node, source);
6002 let candidates = self
6003 .visible_identifier_candidates(file, name)
6004 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6005 .filter(|candidate| {
6006 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6007 })
6008 .collect::<Vec<_>>();
6009 self.unique_canonical_type_candidate(analyzer, file, &candidates)
6010 }
6011
6012 pub fn resolve_type_node_result(
6013 &self,
6014 file: &ProjectFile,
6015 node: Node<'_>,
6016 source: &str,
6017 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6018 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6019 return Ok(None);
6020 };
6021 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6022 return Ok(Some(primary));
6023 };
6024 self.resolve_template_arguments(file, primary, &arguments)
6025 .map(Some)
6026 }
6027
6028 pub fn resolve_type_node_primary(
6029 &self,
6030 file: &ProjectFile,
6031 node: Node<'_>,
6032 source: &str,
6033 ) -> Option<CodeUnit> {
6034 let components = cpp_type_name_components(node, source)?;
6035 self.resolve_type(file, &components.join("::"))
6036 }
6037
6038 pub fn resolve_template_arguments(
6039 &self,
6040 file: &ProjectFile,
6041 primary: CodeUnit,
6042 arguments: &[CppTemplateExpression],
6043 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6044 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6045 }
6046
6047 fn resolve_template_arguments_inner(
6048 &self,
6049 file: &ProjectFile,
6050 primary: CodeUnit,
6051 arguments: &[CppTemplateExpression],
6052 seen_aliases: &mut HashSet<CodeUnit>,
6053 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6054 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6055 && let Some(alias_target) = &metadata.alias_target
6056 {
6057 if !seen_aliases.insert(primary.clone()) {
6058 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6059 }
6060 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6061 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6062 let target_name = alias_target.components.join("::");
6063 let target_primary = if alias_target.global {
6064 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6065 } else {
6066 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6067 };
6068 let Some(target_primary) = target_primary else {
6069 return Ok(primary);
6073 };
6074 let Some(target_arguments) = &alias_target.arguments else {
6075 return Ok(target_primary);
6076 };
6077 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6078 .ok_or(CppTemplateResolutionError::Substitution)?;
6079 return self.resolve_template_arguments_inner(
6080 file,
6081 target_primary,
6082 &target_arguments,
6083 seen_aliases,
6084 );
6085 }
6086
6087 let primary_fq_name = self
6088 .cpp_template_metadata
6089 .get(&primary)
6090 .map(|metadata| metadata.primary_fq_name.clone())
6091 .unwrap_or_else(|| primary.fq_name());
6092 let has_specialization_metadata = self
6093 .cpp_template_families
6094 .get(&primary_fq_name)
6095 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6096 if !has_specialization_metadata {
6097 return Ok(primary);
6098 }
6099 self.select_template_specialization(file, &primary, arguments)
6100 }
6101
6102 fn select_template_specialization(
6103 &self,
6104 file: &ProjectFile,
6105 resolved: &CodeUnit,
6106 explicit_arguments: &[CppTemplateExpression],
6107 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6108 let primary_fq_name = self
6109 .cpp_template_metadata
6110 .get(resolved)
6111 .map(|metadata| metadata.primary_fq_name.clone())
6112 .unwrap_or_else(|| resolved.fq_name());
6113 let family = self
6114 .cpp_template_families
6115 .get(&primary_fq_name)
6116 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6117 let primary_candidates = family
6118 .iter()
6119 .filter_map(|unit| {
6120 let metadata = self.cpp_template_metadata.get(unit)?;
6121 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6122 })
6123 .collect::<Vec<_>>();
6124 let primary_unit = primary_candidates
6125 .iter()
6126 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6127 .or_else(|| {
6128 primary_candidates
6129 .iter()
6130 .map(|(unit, _)| *unit)
6131 .min_by_key(|unit| {
6132 (
6133 unit.source().to_string(),
6134 unit.signature().unwrap_or_default(),
6135 )
6136 })
6137 })
6138 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6139 let primary_parameters =
6140 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6141 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6142 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6143 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6144
6145 let mut applicable = Vec::new();
6146 for unit in family {
6147 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6148 continue;
6149 };
6150 if metadata.is_primary() || !self.is_visible(file, unit) {
6151 continue;
6152 }
6153 if !cpp_specialization_matches(metadata, &expanded) {
6154 continue;
6155 }
6156 applicable.push((unit, metadata));
6157 }
6158 if applicable.is_empty() {
6159 return Ok(primary_unit.clone());
6160 }
6161
6162 let winners = applicable
6167 .iter()
6168 .filter(|(candidate, candidate_metadata)| {
6169 applicable.iter().all(|(other, other_metadata)| {
6170 same_visible_symbol(candidate, other)
6171 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6172 })
6173 })
6174 .copied()
6175 .collect::<Vec<_>>();
6176 let Some((selected, _)) = winners.first() else {
6177 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6180 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6181 });
6182 };
6183 if winners
6184 .iter()
6185 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6186 {
6187 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6188 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6189 });
6190 }
6191 Ok((*selected).clone())
6192 }
6193
6194 pub fn resolve_type_components_lexically(
6195 &self,
6196 analyzer: &CppGraphSource<'_>,
6197 file: &ProjectFile,
6198 components: &[String],
6199 global: bool,
6200 lexical_scope: &[String],
6201 ) -> LexicalTypeResolution {
6202 self.resolve_type_components_lexically_inner(
6203 analyzer,
6204 file,
6205 components,
6206 global,
6207 lexical_scope,
6208 TypeCandidateResolution::Canonical,
6209 )
6210 }
6211
6212 pub fn resolve_type_components_lexically_for_forward(
6213 &self,
6214 analyzer: &CppGraphSource<'_>,
6215 file: &ProjectFile,
6216 components: &[String],
6217 global: bool,
6218 lexical_scope: &[String],
6219 ) -> LexicalTypeResolution {
6220 self.resolve_type_components_lexically_inner(
6221 analyzer,
6222 file,
6223 components,
6224 global,
6225 lexical_scope,
6226 TypeCandidateResolution::PreserveAlias,
6227 )
6228 }
6229
6230 pub fn resolve_type_components_lexically_for_target(
6231 &self,
6232 analyzer: &CppGraphSource<'_>,
6233 file: &ProjectFile,
6234 components: &[String],
6235 global: bool,
6236 lexical_scope: &[String],
6237 target: &CodeUnit,
6238 ) -> LexicalTypeResolution {
6239 #[cfg(any(test, feature = "test-support"))]
6240 self.target_preserving_type_resolution_count
6241 .fetch_add(1, Ordering::Relaxed);
6242 self.resolve_type_components_lexically_inner(
6243 analyzer,
6244 file,
6245 components,
6246 global,
6247 lexical_scope,
6248 TypeCandidateResolution::PreserveTarget(target),
6249 )
6250 }
6251
6252 pub fn coarse_unqualified_type_reference_may_resolve(
6253 &self,
6254 file: &ProjectFile,
6255 name: &str,
6256 ) -> bool {
6257 if name.is_empty() {
6258 return true;
6259 }
6260 self.visible_identifier_candidates(file, name)
6261 .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
6262 || self.visible_parser_alias_name_is_visible(file, name)
6263 }
6264
6265 #[allow(clippy::too_many_arguments)]
6266 pub fn structured_type_reference_may_resolve_to_target(
6267 &self,
6268 analyzer: &CppGraphSource<'_>,
6269 file: &ProjectFile,
6270 components: &[String],
6271 global: bool,
6272 lexical_scope: &[String],
6273 target: &CodeUnit,
6274 ) -> bool {
6275 if components.is_empty() {
6276 return true;
6277 }
6278 let Some(terminal) = components.last() else {
6279 return true;
6280 };
6281 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6282 .map(|qualified| qualified.join("::"))
6283 .collect::<Vec<_>>();
6284 let target_name = cpp_name_for(target);
6285 if qualified_tiers
6286 .iter()
6287 .any(|qualified| qualified == &target_name)
6288 {
6289 return true;
6290 }
6291
6292 let mut saw_shape_candidate = false;
6293 for candidate in self.visible_identifier_candidates(file, terminal) {
6294 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6295 {
6296 continue;
6297 }
6298 let candidate_name = cpp_name_for(candidate);
6299 let shape_matches = if global || components.len() > 1 {
6300 qualified_tiers
6301 .iter()
6302 .any(|qualified| qualified == &candidate_name)
6303 } else {
6304 true
6305 };
6306 if !shape_matches {
6307 continue;
6308 }
6309 saw_shape_candidate = true;
6310 if same_visible_symbol(candidate, target)
6311 || self.c_tag_declaration_family_matches_target(
6312 analyzer,
6313 file,
6314 std::slice::from_ref(&candidate),
6315 target,
6316 )
6317 || self.compatible_primary_template_redeclarations(candidate, target)
6318 || (declared_type_alias(analyzer, candidate)
6319 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6320 {
6321 return true;
6322 }
6323 }
6324
6325 !saw_shape_candidate
6326 }
6327
6328 fn cached_c_tag_kind(
6335 &self,
6336 analyzer: &CppGraphSource<'_>,
6337 candidate: &CodeUnit,
6338 ) -> Option<CppCTagKind> {
6339 if let Some(kind) = self
6340 .c_tag_kind_cache
6341 .lock()
6342 .expect("C tag kind cache poisoned")
6343 .get(candidate)
6344 {
6345 return *kind;
6346 }
6347 let kind = indexed_c_tag_kind(analyzer, candidate);
6348 self.c_tag_kind_cache
6349 .lock()
6350 .expect("C tag kind cache poisoned")
6351 .insert(candidate.clone(), kind);
6352 kind
6353 }
6354
6355 fn cached_unique_c_tag_complete_definition(
6356 &self,
6357 analyzer: &CppGraphSource<'_>,
6358 target: &CodeUnit,
6359 target_tag: CppCTagKind,
6360 ) -> Option<CodeUnit> {
6361 if let Some(definition) = self
6362 .c_tag_complete_definition_cache
6363 .lock()
6364 .expect("C tag complete-definition cache poisoned")
6365 .get(target)
6366 {
6367 return definition.clone();
6368 }
6369 let complete_definitions = analyzer
6370 .definitions(&target.fq_name())
6371 .filter(|candidate| {
6372 candidate.is_class()
6373 && !declared_type_alias(analyzer, candidate)
6374 && is_c_source_file(candidate.source())
6375 && analyzer.parent_of(candidate).is_none()
6376 && cpp_class_declaration_strength(analyzer, candidate)
6377 == CppClassDeclarationStrength::Full
6378 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6379 })
6380 .collect::<HashSet<_>>();
6381 let definition = (complete_definitions.len() == 1)
6382 .then(|| complete_definitions.into_iter().next())
6383 .flatten()
6384 .filter(|candidate| same_visible_symbol(candidate, target));
6385 self.c_tag_complete_definition_cache
6386 .lock()
6387 .expect("C tag complete-definition cache poisoned")
6388 .insert(target.clone(), definition.clone());
6389 definition
6390 }
6391
6392 pub fn c_tag_declaration_family_matches_target(
6393 &self,
6394 analyzer: &CppGraphSource<'_>,
6395 visible_from: &ProjectFile,
6396 candidates: &[&CodeUnit],
6397 target: &CodeUnit,
6398 ) -> bool {
6399 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6400 let candidate_evidence = candidates
6401 .iter()
6402 .map(|candidate| {
6403 (
6404 candidate.fq_name(),
6405 candidate.source().rel_path().to_path_buf(),
6406 cpp_class_declaration_strength(analyzer, candidate),
6407 indexed_c_tag_kind(analyzer, candidate),
6408 self.is_physically_visible(visible_from, candidate),
6409 )
6410 })
6411 .collect::<Vec<_>>();
6412 eprintln!(
6413 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6414 visible_from.rel_path().display(),
6415 target.fq_name(),
6416 target.source().rel_path().display(),
6417 cpp_class_declaration_strength(analyzer, target),
6418 indexed_c_tag_kind(analyzer, target),
6419 );
6420 }
6421 if candidates.is_empty()
6422 || !target.is_class()
6423 || declared_type_alias(analyzer, target)
6424 || !is_c_source_file(target.source())
6425 || analyzer.parent_of(target).is_some()
6426 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6427 {
6428 return false;
6429 }
6430 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6431 return false;
6432 };
6433 if self
6434 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6435 .is_none()
6436 {
6437 return false;
6438 }
6439 let mut saw_visible_forward = false;
6440 for candidate in candidates.iter().copied() {
6441 if candidate == target {
6442 continue;
6443 }
6444 if !candidate.is_class()
6445 || declared_type_alias(analyzer, candidate)
6446 || candidate.fq_name() != target.fq_name()
6447 || analyzer.parent_of(candidate).is_some()
6448 || cpp_class_declaration_strength(analyzer, candidate)
6449 != CppClassDeclarationStrength::Forward
6450 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6451 || !self.is_physically_visible(visible_from, candidate)
6452 {
6453 return false;
6454 }
6455 saw_visible_forward = true;
6456 }
6457 saw_visible_forward
6458 }
6459
6460 fn unique_c_tag_declaration_family(
6465 &self,
6466 analyzer: &CppGraphSource<'_>,
6467 visible_from: &ProjectFile,
6468 candidates: &[&CodeUnit],
6469 ) -> Option<CodeUnit> {
6470 let first = candidates.first()?;
6471 let target_fq_name = first.fq_name();
6472 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6473 let mut full = None;
6474 let mut saw_forward = false;
6475 for candidate in candidates.iter().copied() {
6476 if !candidate.is_class()
6477 || declared_type_alias(analyzer, candidate)
6478 || candidate.fq_name() != target_fq_name
6479 || analyzer.parent_of(candidate).is_some()
6480 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6481 {
6482 return None;
6483 }
6484 match cpp_class_declaration_strength(analyzer, candidate) {
6485 CppClassDeclarationStrength::Full
6486 if is_c_source_file(candidate.source())
6487 && full.replace(candidate.clone()).is_none() => {}
6488 CppClassDeclarationStrength::Forward
6489 if self.is_physically_visible(visible_from, candidate) =>
6490 {
6491 saw_forward = true
6492 }
6493 _ => return None,
6494 }
6495 }
6496 if saw_forward { full } else { None }
6497 }
6498
6499 pub fn target_preserving_reference_namespace(
6500 &self,
6501 analyzer: &CppGraphSource<'_>,
6502 file: &ProjectFile,
6503 identifier: &str,
6504 target: &CodeUnit,
6505 ) -> Option<Vec<String>> {
6506 let mut namespace = None;
6507 for candidate in self.visible_identifier_candidates(file, identifier) {
6508 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6509 {
6510 continue;
6511 }
6512 if !(same_visible_symbol(candidate, target)
6513 || self.compatible_primary_template_redeclarations(candidate, target)
6514 || declared_type_alias(analyzer, candidate)
6515 && self.structured_alias_primary_preserves_target(
6516 analyzer, file, candidate, target,
6517 ))
6518 {
6519 continue;
6520 }
6521 if namespace
6522 .as_ref()
6523 .is_some_and(|existing| existing != candidate.package_name())
6524 {
6525 return None;
6526 }
6527 namespace = Some(candidate.package_name().to_string());
6528 }
6529 let namespace = namespace?;
6530 Some(
6531 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6532 brokk_bifrost_core::analyzer::Language::Cpp,
6533 &namespace,
6534 ),
6535 )
6536 }
6537
6538 pub fn resolve_imported_type_candidate(
6539 &self,
6540 analyzer: &CppGraphSource<'_>,
6541 file: &ProjectFile,
6542 target: &CodeUnit,
6543 target_components: &[String],
6544 direct_target: Option<&CodeUnit>,
6545 preserve_alias: bool,
6546 ) -> LexicalTypeResolution {
6547 let candidates = [target];
6548 let resolution = if preserve_alias {
6549 TypeCandidateResolution::PreserveAlias
6550 } else {
6551 direct_target.map_or(
6552 TypeCandidateResolution::Canonical,
6553 TypeCandidateResolution::PreserveTarget,
6554 )
6555 };
6556 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6560 Ok(unit) => LexicalTypeResolution::Resolved {
6561 unit,
6562 components: target_components.to_vec(),
6563 candidates: vec![target.clone()],
6564 },
6565 Err(failure) => failure.lexical_resolution(),
6566 }
6567 }
6568
6569 fn resolve_type_components_lexically_inner(
6570 &self,
6571 analyzer: &CppGraphSource<'_>,
6572 file: &ProjectFile,
6573 components: &[String],
6574 global: bool,
6575 lexical_scope: &[String],
6576 resolution: TypeCandidateResolution<'_>,
6577 ) -> LexicalTypeResolution {
6578 if components.is_empty() {
6579 return LexicalTypeResolution::Missing;
6580 }
6581 let mut injected = self.resolve_injected_class_name(
6591 analyzer,
6592 file,
6593 components,
6594 global,
6595 lexical_scope,
6596 resolution,
6597 );
6598 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6599 let prefix_len = qualified.len().saturating_sub(components.len());
6600 if injected
6601 .as_ref()
6602 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6603 {
6604 return injected
6605 .take()
6606 .expect("injected class resolution was just present")
6607 .1;
6608 }
6609 let qualified_name = qualified.join("::");
6610 let candidates = self
6611 .type_candidates(file, &qualified_name)
6612 .into_iter()
6613 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6614 .collect::<Vec<_>>();
6615 if candidates.is_empty() {
6616 if !global && components.len() == 1 {
6617 match self.resolve_inherited_type_for_lexical_scope(
6618 analyzer,
6619 file,
6620 &qualified[..prefix_len],
6621 &components[0],
6622 resolution,
6623 ) {
6624 LexicalTypeResolution::Missing => {}
6625 inherited => return inherited,
6626 }
6627 }
6628 continue;
6629 }
6630 let candidates =
6631 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6632 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6633 Ok(unit) => unit,
6634 Err(failure) => return failure.lexical_resolution(),
6635 };
6636 return LexicalTypeResolution::Resolved {
6637 unit,
6638 components: qualified,
6639 candidates: candidates.into_iter().cloned().collect(),
6640 };
6641 }
6642 LexicalTypeResolution::Missing
6643 }
6644
6645 fn resolve_injected_class_name(
6646 &self,
6647 analyzer: &CppGraphSource<'_>,
6648 file: &ProjectFile,
6649 components: &[String],
6650 global: bool,
6651 lexical_scope: &[String],
6652 resolution: TypeCandidateResolution<'_>,
6653 ) -> Option<(usize, LexicalTypeResolution)> {
6654 if global
6655 || components.len() != 1
6656 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6657 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6658 {
6659 return None;
6660 }
6661 let name = components.first()?;
6662 let mut matches: Vec<&CodeUnit> = Vec::new();
6663 let mut owner_len = 0;
6664 for candidate in self.visible_identifier_candidates(file, name) {
6665 if !candidate.is_class()
6666 || declared_type_alias(analyzer, candidate)
6667 || candidate.identifier() != name
6668 {
6669 continue;
6670 }
6671 let candidate_scope = canonical_cpp_scope_components(candidate);
6672 if candidate_scope.len() > lexical_scope.len()
6673 || !lexical_scope.starts_with(&candidate_scope)
6674 || candidate_scope.last().is_none_or(|last| last != name)
6675 {
6676 continue;
6677 }
6678 if candidate_scope.len() > owner_len {
6679 owner_len = candidate_scope.len();
6680 matches.clear();
6681 }
6682 if candidate_scope.len() == owner_len
6683 && !matches
6684 .iter()
6685 .any(|existing| same_logical_symbol(existing, candidate))
6686 {
6687 matches.push(candidate);
6688 }
6689 }
6690 if matches.is_empty() {
6691 return None;
6692 }
6693 let owner_components = lexical_scope[..owner_len].to_vec();
6700 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6701 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6702 Ok(unit) => LexicalTypeResolution::Resolved {
6703 unit,
6704 components: owner_components,
6705 candidates: matches.into_iter().cloned().collect(),
6706 },
6707 Err(failure) => failure.lexical_resolution(),
6708 };
6709 Some((owner_len, resolution))
6710 }
6711
6712 fn resolve_inherited_type_for_lexical_scope(
6713 &self,
6714 analyzer: &CppGraphSource<'_>,
6715 file: &ProjectFile,
6716 lexical_scope: &[String],
6717 name: &str,
6718 resolution: TypeCandidateResolution<'_>,
6719 ) -> LexicalTypeResolution {
6720 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6721 return LexicalTypeResolution::Missing;
6722 };
6723 let lexical_owner_name = lexical_scope.join("::");
6724 if lexical_owner_name.is_empty() {
6725 return LexicalTypeResolution::Missing;
6726 }
6727 let owner_candidates = self
6728 .type_candidates(file, &lexical_owner_name)
6729 .into_iter()
6730 .filter(|candidate| {
6731 canonical_cpp_name_matches(candidate, &lexical_owner_name)
6732 && !declared_type_alias(analyzer, candidate)
6733 })
6734 .collect::<Vec<_>>();
6735 if owner_candidates.is_empty() {
6736 return LexicalTypeResolution::Missing;
6737 }
6738 let physical_owner_candidates = owner_candidates
6743 .iter()
6744 .copied()
6745 .filter(|candidate| candidate.source() == file)
6746 .collect::<Vec<_>>();
6747 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6748 owner_candidates
6749 } else {
6750 physical_owner_candidates
6751 };
6752 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6753 return LexicalTypeResolution::Ambiguous;
6754 };
6755
6756 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6757 let mut visited_owners = HashSet::default();
6758 while !frontier.is_empty() {
6759 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6760 let mut next_frontier = Vec::new();
6761 for owner in frontier {
6762 if !visited_owners.insert(owner.fq_name()) {
6763 continue;
6764 }
6765 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6766 let candidates = self
6767 .type_candidates(file, &qualified_name)
6768 .into_iter()
6769 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6770 .collect::<Vec<_>>();
6771 if candidates.is_empty() {
6772 for ancestor in hierarchy.get_direct_ancestors(&owner) {
6773 if !next_frontier
6774 .iter()
6775 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6776 {
6777 next_frontier.push(ancestor);
6778 }
6779 }
6780 continue;
6781 }
6782 let candidates =
6783 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6784 let unit =
6785 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6786 Ok(unit) => unit,
6787 Err(failure) => return failure.lexical_resolution(),
6788 };
6789 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6790 }
6791 if let Some((unit, candidates)) = level_matches.first().cloned() {
6792 let Some(first_declaration) = candidates.first() else {
6793 return LexicalTypeResolution::Ambiguous;
6794 };
6795 if !level_matches.iter().all(|(_, declarations)| {
6796 declarations
6797 .iter()
6798 .all(|declaration| same_logical_symbol(first_declaration, declaration))
6799 }) {
6800 return LexicalTypeResolution::Ambiguous;
6801 }
6802 let mut components = lexical_scope.to_vec();
6803 components.push(name.to_string());
6804 return LexicalTypeResolution::Resolved {
6805 unit,
6806 components,
6807 candidates,
6808 };
6809 }
6810 frontier = next_frontier;
6811 }
6812 LexicalTypeResolution::Missing
6813 }
6814
6815 pub fn inherited_injected_class_owner(
6832 &self,
6833 analyzer: &CppGraphSource<'_>,
6834 file: &ProjectFile,
6835 enclosing_owner: &CodeUnit,
6836 injected_name: &str,
6837 ) -> Option<CodeUnit> {
6838 let hierarchy = analyzer.type_hierarchy_provider()?;
6839 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6840 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6841 while !frontier.is_empty() {
6842 let mut level_matches = Vec::new();
6843 let mut next_frontier = Vec::new();
6844 for raw_owner in frontier {
6845 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6846 else {
6847 if raw_owner.identifier() == injected_name {
6848 return None;
6849 }
6850 continue;
6851 };
6852 let propagated = propagated_counts.entry(owner.clone()).or_default();
6853 if *propagated == 2 {
6854 continue;
6855 }
6856 *propagated += 1;
6857 if owner.identifier() == injected_name {
6858 level_matches.push(owner.clone());
6859 }
6860 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6861 }
6862 match level_matches.as_slice() {
6863 [owner] => return Some(owner.clone()),
6864 [_, ..] => return None,
6865 [] => {}
6866 }
6867 frontier = next_frontier;
6868 }
6869 None
6870 }
6871
6872 fn resolve_type_candidates(
6877 &self,
6878 analyzer: &CppGraphSource<'_>,
6879 file: &ProjectFile,
6880 candidates: &[&CodeUnit],
6881 resolution: TypeCandidateResolution<'_>,
6882 ) -> Result<CodeUnit, TypeCandidateFailure> {
6883 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6884 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6885 {
6886 return Ok(unit);
6887 }
6888 match resolution {
6889 TypeCandidateResolution::Canonical => {
6890 self.canonical_type_candidate_resolution(analyzer, file, candidates)
6891 }
6892 TypeCandidateResolution::PreserveAlias => {
6893 let same_fqn_alias_family = candidates.len() > 1
6900 && candidates.iter().all(|candidate| {
6901 declared_type_alias(analyzer, candidate)
6902 && same_logical_symbol(candidates[0], candidate)
6903 })
6904 && candidates
6905 .iter()
6906 .any(|candidate| candidate.source() != candidates[0].source());
6907 if same_fqn_alias_family {
6908 let physically_visible = candidates
6909 .iter()
6910 .copied()
6911 .filter(|candidate| self.is_physically_visible(file, candidate))
6912 .collect::<Vec<_>>();
6913 let one_structured_target = physically_visible.len() > 1
6923 && physically_visible.iter().skip(1).all(|candidate| {
6924 let target = self.structured_alias_target(analyzer, candidate);
6925 target.is_some()
6926 && target
6927 == self.structured_alias_target(analyzer, physically_visible[0])
6928 });
6929 if physically_visible.len() == 1 || one_structured_target {
6930 return Ok(physically_visible[0].clone());
6931 }
6932 }
6933 unique_type_candidate_preserving_alias(analyzer, file, candidates)
6934 .ok_or(TypeCandidateFailure::Ambiguous)
6935 }
6936 TypeCandidateResolution::PreserveTarget(target) => self
6937 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6938 .ok_or(TypeCandidateFailure::Ambiguous),
6939 }
6940 }
6941
6942 fn candidates_for_type_resolution<'b>(
6943 &self,
6944 analyzer: &CppGraphSource<'_>,
6945 file: &ProjectFile,
6946 candidates: &[&'b CodeUnit],
6947 resolution: TypeCandidateResolution<'_>,
6948 ) -> Vec<&'b CodeUnit> {
6949 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6950 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6951 if compile_proven.len() == 1 {
6952 return compile_proven;
6953 }
6954 }
6955 candidates.to_vec()
6956 }
6957
6958 fn compile_proven_type_candidates<'b>(
6965 &self,
6966 analyzer: &CppGraphSource<'_>,
6967 file: &ProjectFile,
6968 candidates: &[&'b CodeUnit],
6969 ) -> Vec<&'b CodeUnit> {
6970 let proven = self.compile_proven_guards(file);
6971 if proven.is_empty() {
6972 return Vec::new();
6973 }
6974 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6975 return Vec::new();
6976 };
6977 candidates
6978 .iter()
6979 .copied()
6980 .filter(|candidate| {
6981 let declaration_guards =
6982 declaration_guard_requirements(analyzer, self.cpp, candidate);
6983 if declaration_guards.is_empty() {
6984 return false;
6985 }
6986 if candidate.source() == file {
6987 return declaration_guards.iter().any(|(_, required)| {
6988 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6989 });
6990 }
6991 declaration_guards.iter().any(|(_, required)| {
6992 self.foreign_declaration_reachable_from_compile_proven_guards(
6993 file,
6994 prepared.as_ref(),
6995 candidate.source(),
6996 required,
6997 usize::MAX,
6998 )
6999 })
7000 })
7001 .collect()
7002 }
7003
7004 pub fn resolve_callable_value_components_lexically(
7005 &self,
7006 analyzer: &CppGraphSource<'_>,
7007 file: &ProjectFile,
7008 owner_components: &[String],
7009 member_name: &str,
7010 global: bool,
7011 lexical_scope: &[String],
7012 ) -> LexicalCallableValueResolution {
7013 if owner_components.is_empty() || member_name.is_empty() {
7014 return LexicalCallableValueResolution::Missing;
7015 }
7016 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7017 let owner_name = qualified_owner.join("::");
7018 let type_candidates = self
7019 .type_candidates(file, &owner_name)
7020 .into_iter()
7021 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7022 .collect::<Vec<_>>();
7023 let resolved_type = if type_candidates.is_empty() {
7024 None
7025 } else {
7026 let Some(unit) =
7027 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7028 else {
7029 return LexicalCallableValueResolution::Ambiguous;
7030 };
7031 Some(unit)
7032 };
7033
7034 let mut qualified_callable = qualified_owner;
7035 qualified_callable.push(member_name.to_string());
7036 let callable_name = qualified_callable.join("::");
7037 let free_function = self
7038 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7039 .into_iter()
7040 .find(|candidate| {
7041 canonical_cpp_name_matches(candidate, &callable_name)
7042 && type_owner_of(analyzer, candidate).is_none()
7043 })
7044 .cloned();
7045
7046 match (resolved_type, free_function) {
7047 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7048 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7049 (None, Some(function)) => {
7050 return LexicalCallableValueResolution::FreeFunction(function);
7051 }
7052 (None, None) => {}
7053 }
7054 }
7055 LexicalCallableValueResolution::Missing
7056 }
7057
7058 fn resolve_type_for_declaration(
7059 &self,
7060 visible_from: &ProjectFile,
7061 declaration: &CodeUnit,
7062 raw_name: &str,
7063 ) -> Option<CodeUnit> {
7064 let normalized = normalize_reference_name(raw_name)?;
7065 if !normalized.contains("::")
7066 && let Some(namespace) = cpp_namespace_for(declaration)
7067 {
7068 for prefix in namespace_prefixes(&namespace) {
7069 let qualified = format!("{prefix}::{normalized}");
7070 if let Some(unit) = self
7071 .type_candidates(visible_from, &qualified)
7072 .into_iter()
7073 .next()
7074 {
7075 return Some(unit.clone());
7076 }
7077 }
7078 }
7079 self.resolve_type(visible_from, raw_name)
7080 }
7081
7082 fn resolve_unique_canonical_type_for_declaration(
7083 &self,
7084 analyzer: &CppGraphSource<'_>,
7085 visible_from: &ProjectFile,
7086 declaration: &CodeUnit,
7087 raw_name: &str,
7088 ) -> Option<CodeUnit> {
7089 let mut current = self.resolve_defining_type_for_declaration(
7090 analyzer,
7091 visible_from,
7092 declaration,
7093 raw_name,
7094 )?;
7095 let mut seen_aliases = HashSet::default();
7096 loop {
7097 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7098 return current.is_class().then_some(current);
7099 };
7100 if matches!(target, StructuredAliasTarget::Builtin) {
7101 return current.is_class().then_some(current);
7102 }
7103 if !seen_aliases.insert(current.clone()) {
7104 return None;
7105 }
7106 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
7107 }
7108 }
7109
7110 pub fn canonical_type_unit(
7111 &self,
7112 analyzer: &CppGraphSource<'_>,
7113 visible_from: &ProjectFile,
7114 unit: &CodeUnit,
7115 ) -> Option<CodeUnit> {
7116 self.canonical_type_resolution(analyzer, visible_from, unit)
7117 .ok()
7118 }
7119
7120 pub fn canonical_type_unit_in_context(
7128 &self,
7129 analyzer: &CppGraphSource<'_>,
7130 visible_from: &ProjectFile,
7131 reference: Node<'_>,
7132 unit: &CodeUnit,
7133 ) -> Option<CodeUnit> {
7134 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7135 {
7136 return None;
7137 }
7138 self.canonical_type_resolution(analyzer, visible_from, unit)
7139 .ok()
7140 }
7141
7142 fn canonical_type_resolution(
7150 &self,
7151 analyzer: &CppGraphSource<'_>,
7152 visible_from: &ProjectFile,
7153 unit: &CodeUnit,
7154 ) -> Result<CodeUnit, TypeCandidateFailure> {
7155 let mut current = unit.clone();
7156 let mut seen_aliases = HashSet::default();
7157 loop {
7158 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7159 return current
7160 .is_class()
7161 .then_some(current)
7162 .ok_or(TypeCandidateFailure::Unresolvable);
7163 };
7164 if matches!(target, StructuredAliasTarget::Builtin) {
7165 return current
7166 .is_class()
7167 .then_some(current)
7168 .ok_or(TypeCandidateFailure::Unresolvable);
7169 }
7170 if !seen_aliases.insert(current.clone()) {
7171 return Err(TypeCandidateFailure::Unresolvable);
7172 }
7173 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7174 }
7175 }
7176
7177 pub fn canonical_visible_full_type_unit(
7178 &self,
7179 analyzer: &CppGraphSource<'_>,
7180 visible_from: &ProjectFile,
7181 unit: &CodeUnit,
7182 ) -> Option<CodeUnit> {
7183 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7184 if cpp_class_declaration_strength(analyzer, &canonical)
7185 != CppClassDeclarationStrength::Forward
7186 {
7187 return Some(canonical);
7188 }
7189 let mut full = Vec::new();
7190 for candidate in self
7191 .visible_identifier_candidates(visible_from, canonical.identifier())
7192 .filter(|candidate| {
7193 candidate.is_class()
7194 && candidate.fq_name() == canonical.fq_name()
7195 && cpp_class_declaration_strength(analyzer, candidate)
7196 == CppClassDeclarationStrength::Full
7197 })
7198 {
7199 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7200 full.push(candidate.clone());
7201 }
7202 }
7203 match full.len() {
7204 0 => Some(canonical),
7205 1 => full.pop(),
7206 _ => None,
7207 }
7208 }
7209
7210 fn resolve_structured_alias_target(
7211 &self,
7212 visible_from: &ProjectFile,
7213 declaration: &CodeUnit,
7214 target: &StructuredAliasTarget,
7215 ) -> Option<CodeUnit> {
7216 self.structured_alias_target_resolution(visible_from, declaration, target)
7217 .ok()
7218 }
7219
7220 fn structured_alias_target_resolution(
7221 &self,
7222 visible_from: &ProjectFile,
7223 declaration: &CodeUnit,
7224 target: &StructuredAliasTarget,
7225 ) -> Result<CodeUnit, TypeCandidateFailure> {
7226 let primary =
7227 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7228 let StructuredAliasTarget::Named { arguments, .. } = target else {
7229 return Err(TypeCandidateFailure::Unresolvable);
7230 };
7231 match arguments {
7232 Some(arguments) => self
7233 .resolve_template_arguments(visible_from, primary, arguments)
7234 .map_err(|error| match error {
7235 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7236 TypeCandidateFailure::Ambiguous
7237 }
7238 _ => TypeCandidateFailure::Unresolvable,
7239 }),
7240 None => Ok(primary),
7241 }
7242 }
7243
7244 fn resolve_structured_alias_primary(
7245 &self,
7246 visible_from: &ProjectFile,
7247 declaration: &CodeUnit,
7248 target: &StructuredAliasTarget,
7249 ) -> Option<CodeUnit> {
7250 self.structured_alias_primary_resolution(visible_from, declaration, target)
7251 .ok()
7252 }
7253
7254 fn structured_alias_primary_resolution(
7255 &self,
7256 visible_from: &ProjectFile,
7257 declaration: &CodeUnit,
7258 target: &StructuredAliasTarget,
7259 ) -> Result<CodeUnit, TypeCandidateFailure> {
7260 let StructuredAliasTarget::Named {
7261 components, global, ..
7262 } = target
7263 else {
7264 return Err(TypeCandidateFailure::Unresolvable);
7265 };
7266 let qualified = components.join("::");
7267 let candidates = if *global {
7268 let mut candidates = self.type_candidates(visible_from, &qualified);
7275 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7276 candidates
7277 } else {
7278 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7279 };
7280 logical_type_candidate(candidates)
7281 }
7282
7283 pub fn structured_alias_primary_preserves_target(
7284 &self,
7285 analyzer: &CppGraphSource<'_>,
7286 visible_from: &ProjectFile,
7287 candidate: &CodeUnit,
7288 target: &CodeUnit,
7289 ) -> bool {
7290 let mut current = candidate.clone();
7291 let mut seen = HashSet::default();
7292 let mut matched_target = false;
7293 loop {
7294 if same_visible_symbol(¤t, target)
7295 || self.compatible_primary_template_redeclarations(¤t, target)
7296 {
7297 matched_target = true;
7298 }
7299 if !seen.insert(current.clone()) {
7300 return false;
7301 }
7302 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7303 return matched_target;
7304 };
7305 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7306 return matched_target;
7307 };
7308 let Some(primary) =
7309 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7310 else {
7311 return matched_target;
7317 };
7318 current = primary;
7319 }
7320 }
7321
7322 pub fn structured_class_alias_resolves_to_target(
7323 &self,
7324 analyzer: &CppGraphSource<'_>,
7325 visible_from: &ProjectFile,
7326 alias: &CodeUnit,
7327 target: &CodeUnit,
7328 ) -> bool {
7329 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7330 return false;
7331 };
7332 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7333 return false;
7334 };
7335 let StructuredAliasTarget::Named {
7336 components, global, ..
7337 } = &alias_target
7338 else {
7339 return false;
7340 };
7341 let lexical_scope = canonical_cpp_scope_components(&owner);
7342 match self.resolve_type_components_lexically_for_target(
7343 analyzer,
7344 visible_from,
7345 components,
7346 *global,
7347 &lexical_scope,
7348 target,
7349 ) {
7350 LexicalTypeResolution::Resolved {
7351 unit, candidates, ..
7352 } => {
7353 same_visible_symbol(&unit, target)
7354 || self.same_template_member_identity(analyzer, &unit, target)
7355 || candidates.iter().any(|candidate| {
7356 same_visible_symbol(candidate, target)
7357 || self.same_template_member_identity(analyzer, candidate, target)
7358 })
7359 }
7360 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7361 self.structured_alias_primary_preserves_target(
7362 analyzer,
7363 visible_from,
7364 alias,
7365 target,
7366 ) || self.flattened_macro_namespace_alias_target_matches(
7367 analyzer,
7368 visible_from,
7369 alias,
7370 &alias_target,
7371 target,
7372 )
7373 }
7374 }
7375 }
7376
7377 pub fn structured_class_alias_path_preserves_target(
7385 &self,
7386 analyzer: &CppGraphSource<'_>,
7387 visible_from: &ProjectFile,
7388 alias: &CodeUnit,
7389 target: &CodeUnit,
7390 ) -> bool {
7391 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7392 return false;
7393 };
7394 let Some(StructuredAliasTarget::Named {
7395 components, global, ..
7396 }) = self.structured_alias_target(analyzer, alias)
7397 else {
7398 return false;
7399 };
7400 let lexical_scope = canonical_cpp_scope_components(&owner);
7401 (1..components.len()).rev().any(|component_count| {
7402 matches!(
7403 self.resolve_type_components_lexically_for_target(
7404 analyzer,
7405 visible_from,
7406 &components[..component_count],
7407 global,
7408 &lexical_scope,
7409 target,
7410 ),
7411 LexicalTypeResolution::Resolved {
7412 ref unit,
7413 ref candidates,
7414 ..
7415 } if same_visible_symbol(unit, target)
7416 || self.same_template_member_identity(analyzer, unit, target)
7417 || candidates.iter().any(|candidate| {
7418 same_visible_symbol(candidate, target)
7419 || self.same_template_member_identity(analyzer, candidate, target)
7420 })
7421 )
7422 })
7423 }
7424
7425 fn flattened_macro_namespace_alias_target_matches(
7426 &self,
7427 analyzer: &CppGraphSource<'_>,
7428 visible_from: &ProjectFile,
7429 alias: &CodeUnit,
7430 alias_target: &StructuredAliasTarget,
7431 target: &CodeUnit,
7432 ) -> bool {
7433 let StructuredAliasTarget::Named {
7434 components,
7435 global: false,
7436 arguments: None,
7437 } = alias_target
7438 else {
7439 return false;
7440 };
7441 let Some((target_name, namespace_components)) = components.split_last() else {
7442 return false;
7443 };
7444 if namespace_components.is_empty()
7445 || target_name != target.identifier()
7446 || alias.source() != target.source()
7447 || alias.source() != visible_from
7448 || !target.is_class()
7449 || declared_type_alias(analyzer, target)
7450 {
7451 return false;
7452 }
7453 if self
7454 .resolve_structured_alias_target(visible_from, alias, alias_target)
7455 .is_some()
7456 {
7457 return false;
7458 }
7459
7460 let alias_ranges = analyzer.ranges(alias);
7461 let target_ranges = analyzer.ranges(target);
7462 if alias_ranges.is_empty() || target_ranges.is_empty() {
7463 return false;
7464 }
7465 let alias_start = alias_ranges
7466 .iter()
7467 .map(|range| range.start_byte)
7468 .min()
7469 .expect("non-empty alias ranges have a minimum");
7470 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
7471 return false;
7472 };
7473 let root = prepared.tree().root_node();
7474 let has_matching_declaration = target_ranges
7475 .iter()
7476 .filter(|range| range.end_byte <= alias_start)
7477 .filter_map(|range| node_for_exact_range(root, range))
7478 .any(|node| {
7479 flattened_macro_namespace_components(node, prepared.source())
7480 .is_some_and(|recovered| recovered == namespace_components)
7481 });
7482 if !has_matching_declaration {
7483 return false;
7484 }
7485
7486 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7487 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7488 guard_requirement_sets_match(&alias_guards, &target_guards)
7489 }
7490
7491 pub fn template_alias_arguments_preserve_target(
7492 &self,
7493 analyzer: &CppGraphSource<'_>,
7494 visible_from: &ProjectFile,
7495 alias: &CodeUnit,
7496 arguments: &[CppTemplateExpression],
7497 target: &CodeUnit,
7498 ) -> bool {
7499 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7500 return false;
7501 };
7502 if metadata.alias_target.is_none()
7503 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7504 {
7505 return false;
7506 }
7507 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7508 }
7509
7510 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7511 self.cpp_template_metadata
7512 .get(unit)
7513 .is_some_and(CppTemplateMetadata::is_primary)
7514 }
7515
7516 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7517 self.cpp_template_metadata
7518 .get(unit)
7519 .is_some_and(CppTemplateMetadata::is_specialization)
7520 }
7521
7522 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7523 same_visible_symbol(left, right)
7524 || self.compatible_primary_template_redeclarations(left, right)
7525 }
7526
7527 pub fn same_template_member_identity(
7528 &self,
7529 analyzer: &CppGraphSource<'_>,
7530 left: &CodeUnit,
7531 right: &CodeUnit,
7532 ) -> bool {
7533 if same_visible_symbol(left, right) {
7534 return true;
7535 }
7536 if left.kind() != right.kind()
7537 || left.identifier() != right.identifier()
7538 || left.signature() != right.signature()
7539 {
7540 return false;
7541 }
7542 let (Some(left_owner), Some(right_owner)) =
7543 (analyzer.parent_of(left), analyzer.parent_of(right))
7544 else {
7545 return false;
7546 };
7547 left_owner.is_class()
7548 && right_owner.is_class()
7549 && self.same_template_owner_identity(&left_owner, &right_owner)
7550 }
7551
7552 fn unique_canonical_type_candidate(
7553 &self,
7554 analyzer: &CppGraphSource<'_>,
7555 visible_from: &ProjectFile,
7556 candidates: &[&CodeUnit],
7557 ) -> Option<CodeUnit> {
7558 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7559 .ok()
7560 }
7561
7562 fn canonical_type_candidate_resolution(
7563 &self,
7564 analyzer: &CppGraphSource<'_>,
7565 visible_from: &ProjectFile,
7566 candidates: &[&CodeUnit],
7567 ) -> Result<CodeUnit, TypeCandidateFailure> {
7568 let mut canonical = Vec::new();
7569 for candidate in candidates {
7570 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7571 if canonical
7572 .iter()
7573 .any(|existing| same_visible_symbol(existing, &resolved))
7574 {
7575 continue;
7576 }
7577 if let Some(existing) = canonical.iter_mut().find(|existing| {
7578 self.compatible_primary_template_redeclarations(existing, &resolved)
7579 }) {
7580 if matches!(
7589 (
7590 cpp_class_declaration_strength(analyzer, existing),
7591 cpp_class_declaration_strength(analyzer, &resolved),
7592 ),
7593 (
7594 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7595 CppClassDeclarationStrength::Full,
7596 ) | (
7597 CppClassDeclarationStrength::Unknown,
7598 CppClassDeclarationStrength::Forward,
7599 )
7600 ) {
7601 *existing = resolved;
7602 }
7603 continue;
7604 }
7605 canonical.push(resolved);
7606 if canonical.len() > 1 {
7607 return Err(TypeCandidateFailure::Ambiguous);
7608 }
7609 }
7610 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7611 }
7612
7613 pub fn unique_type_candidate_preserving_target(
7614 &self,
7615 analyzer: &CppGraphSource<'_>,
7616 visible_from: &ProjectFile,
7617 candidates: &[&CodeUnit],
7618 target: &CodeUnit,
7619 ) -> Option<CodeUnit> {
7620 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7631 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7632 {
7633 return Some(target.clone());
7634 }
7635 let mut resolved_candidates = Vec::new();
7636 for candidate in candidates {
7637 let Some(resolved) =
7643 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7644 else {
7645 continue;
7646 };
7647 if resolved_candidates
7648 .iter()
7649 .any(|existing| same_visible_symbol(existing, &resolved))
7650 {
7651 continue;
7652 }
7653 resolved_candidates.push(resolved);
7654 }
7655 match resolved_candidates.as_slice() {
7656 [] => None,
7657 [single] => Some(single.clone()),
7658 _ => self
7663 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7664 .map(|_| target.clone()),
7665 }
7666 }
7667
7668 pub fn same_fqn_type_spelling_for_target<'b>(
7685 &self,
7686 analyzer: &CppGraphSource<'_>,
7687 visible_from: &ProjectFile,
7688 candidates: &[&'b CodeUnit],
7689 target: &CodeUnit,
7690 ) -> Option<&'b CodeUnit> {
7691 let [first, rest @ ..] = candidates else {
7692 return None;
7693 };
7694 if rest.is_empty()
7695 || !rest.iter().all(|candidate| {
7696 candidate.kind() == first.kind()
7697 && candidate.fq_name() == first.fq_name()
7698 && candidate.source() == first.source()
7699 })
7700 {
7701 return None;
7702 }
7703 candidates
7704 .iter()
7705 .copied()
7706 .find(|candidate| same_symbol(candidate, target))
7707 .or_else(|| {
7708 candidates.iter().copied().find(|candidate| {
7709 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7710 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7711 })
7712 })
7713 }
7714
7715 pub fn alternate_same_fqn_type_declarations(
7716 &self,
7717 analyzer: &CppGraphSource<'_>,
7718 candidates: &[&CodeUnit],
7719 target: &CodeUnit,
7720 ) -> bool {
7721 let Some(first) = candidates.first() else {
7722 return false;
7723 };
7724 let same_api = first.kind() == target.kind()
7725 && first.fq_name() == target.fq_name()
7726 && first.source() == target.source()
7727 && candidates.iter().all(|candidate| {
7728 candidate.kind() == target.kind()
7729 && candidate.fq_name() == target.fq_name()
7730 && candidate.source() == target.source()
7731 })
7732 && candidates
7733 .iter()
7734 .any(|candidate| same_symbol(candidate, target))
7735 && candidates
7736 .iter()
7737 .any(|candidate| !same_logical_symbol(candidate, target));
7738 if !same_api {
7739 return false;
7740 }
7741
7742 let requirements = candidates
7743 .iter()
7744 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7745 .collect::<Vec<_>>();
7746 requirements.len() > 1
7747 && requirements
7748 .iter()
7749 .all(|requirement| !requirement.is_empty())
7750 && requirements.iter().enumerate().all(|(index, left)| {
7751 requirements[index + 1..].iter().all(|right| {
7752 left.iter().all(|(_, left_guards)| {
7753 right.iter().all(|(_, right_guards)| {
7754 merge_preprocessor_guards(left_guards, right_guards).is_none()
7755 })
7756 })
7757 })
7758 })
7759 }
7760
7761 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7762 let mut pending = vec![terms.to_vec()];
7763 while let Some(branch_terms) = pending.pop() {
7764 let mut normalized = Vec::new();
7765 let mut covers_branch = false;
7766 for term in branch_terms {
7767 if term.iter().any(|guard| term.contains(&guard.negated())) {
7768 continue;
7769 }
7770 if term.is_empty() {
7771 covers_branch = true;
7772 break;
7773 }
7774 if !normalized.iter().any(|existing| existing == &term) {
7775 normalized.push(term);
7776 }
7777 }
7778 if covers_branch {
7779 continue;
7780 }
7781 let Some(split_guard) = normalized
7782 .iter()
7783 .flat_map(|term| term.iter())
7784 .next()
7785 .cloned()
7786 else {
7787 return false;
7788 };
7789 let negated_guard = split_guard.negated();
7790 let mut when_defined = Vec::new();
7791 let mut when_undefined = Vec::new();
7792 for term in normalized {
7793 if term.contains(&negated_guard) {
7794 } else if term.contains(&split_guard) {
7796 let mut reduced = term.clone();
7797 reduced.remove(&split_guard);
7798 when_defined.push(reduced);
7799 } else {
7800 when_defined.push(term.clone());
7801 }
7802 if term.contains(&split_guard) {
7803 } else if term.contains(&negated_guard) {
7805 let mut reduced = term;
7806 reduced.remove(&negated_guard);
7807 when_undefined.push(reduced);
7808 } else {
7809 when_undefined.push(term);
7810 }
7811 }
7812 pending.push(when_defined);
7813 pending.push(when_undefined);
7814 }
7815 true
7816 }
7817
7818 fn declarations_share_exhaustive_conditional_family(
7827 &self,
7828 analyzer: &CppGraphSource<'_>,
7829 candidates: &[&CodeUnit],
7830 ) -> Option<(usize, usize)> {
7831 let mut family_range = None;
7832 for candidate in candidates {
7833 let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
7834 let root = prepared.tree().root_node();
7835 let mut candidate_family = None;
7836 for range in analyzer.ranges(candidate) {
7837 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7838 let family = preprocessor_conditional_family_for_declaration(node)?;
7839 let key = (family.start_byte(), family.end_byte());
7840 if candidate_family.is_some_and(|existing| existing != key) {
7841 return None;
7842 }
7843 candidate_family = Some(key);
7844 }
7845 let candidate_family = candidate_family?;
7846 if family_range.is_some_and(|existing| existing != candidate_family) {
7847 return None;
7848 }
7849 family_range = Some(candidate_family);
7850 }
7851 family_range
7852 }
7853
7854 pub fn complementary_same_fqn_type_declarations(
7855 &self,
7856 analyzer: &CppGraphSource<'_>,
7857 candidates: &[&CodeUnit],
7858 target: &CodeUnit,
7859 ) -> bool {
7860 if candidates.len() < 2
7861 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7862 || self
7863 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7864 .is_none()
7865 {
7866 return false;
7867 }
7868 Self::preprocessor_guard_terms_cover_all_paths(
7869 &self.declaration_family_guard_terms(analyzer, candidates),
7870 )
7871 }
7872
7873 fn declaration_family_guard_terms(
7874 &self,
7875 analyzer: &CppGraphSource<'_>,
7876 candidates: &[&CodeUnit],
7877 ) -> Vec<HashSet<PreprocessorGuard>> {
7878 candidates
7879 .iter()
7880 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7881 .map(|(_, guards)| guards)
7882 .collect()
7883 }
7884
7885 fn exhaustive_guard_family_activation(
7901 &self,
7902 analyzer: &CppGraphSource<'_>,
7903 prepared: &PreparedSyntaxTree,
7904 candidate: &CodeUnit,
7905 reference: &CallableReferenceContext<'_>,
7906 ) -> Option<usize> {
7907 if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
7910 return None;
7911 }
7912 let family = self
7913 .visible_identifier_candidates(candidate.source(), candidate.identifier())
7914 .filter(|peer| {
7915 peer.kind() == candidate.kind()
7916 && peer.fq_name() == candidate.fq_name()
7917 && peer.source() == candidate.source()
7918 })
7919 .collect::<Vec<_>>();
7920 let (_, family_end) =
7921 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7922 if !Self::preprocessor_guard_terms_cover_all_paths(
7923 &self.declaration_family_guard_terms(analyzer, &family),
7924 ) {
7925 return None;
7926 }
7927 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7931 .iter()
7932 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7933 {
7934 return None;
7935 }
7936 (first_declaration_byte(analyzer, candidate)?
7937 == family
7938 .iter()
7939 .filter_map(|peer| first_declaration_byte(analyzer, peer))
7940 .min()?)
7941 .then_some(family_end)
7942 }
7943
7944 fn type_candidate_preserving_target(
7945 &self,
7946 analyzer: &CppGraphSource<'_>,
7947 visible_from: &ProjectFile,
7948 candidate: &CodeUnit,
7949 target: &CodeUnit,
7950 ) -> Option<CodeUnit> {
7951 let mut current = candidate.clone();
7952 let mut matched_target = same_visible_symbol(¤t, target)
7953 || self.compatible_primary_template_redeclarations(¤t, target);
7954 let mut seen = HashSet::default();
7955 loop {
7956 if !seen.insert(current.clone()) {
7957 return None;
7958 }
7959 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7960 return matched_target
7961 .then(|| target.clone())
7962 .or_else(|| current.is_class().then_some(current));
7963 };
7964 if self.flattened_macro_namespace_alias_target_matches(
7965 analyzer,
7966 visible_from,
7967 ¤t,
7968 &alias_target,
7969 target,
7970 ) {
7971 return Some(target.clone());
7972 }
7973 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7974 return matched_target
7975 .then(|| target.clone())
7976 .or_else(|| current.is_class().then_some(current));
7977 }
7978 if !self.cpp_template_metadata.contains_key(¤t)
7986 && let Some(primary) =
7987 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7988 && (same_visible_symbol(&primary, target)
7989 || self.compatible_primary_template_redeclarations(&primary, target))
7990 {
7991 return Some(target.clone());
7992 }
7993 if same_visible_symbol(¤t, target) {
7994 return Some(target.clone());
7995 }
7996 if self.cpp_template_metadata.contains_key(¤t) {
7997 return None;
7998 }
7999 let Some(next) =
8000 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
8001 else {
8002 return matched_target.then(|| target.clone());
8003 };
8004 current = next;
8005 matched_target |= same_visible_symbol(¤t, target)
8006 || self.compatible_primary_template_redeclarations(¤t, target);
8007 }
8008 }
8009
8010 fn compatible_primary_template_redeclarations(
8011 &self,
8012 left: &CodeUnit,
8013 right: &CodeUnit,
8014 ) -> bool {
8015 let (Some(left_metadata), Some(right_metadata)) = (
8016 self.cpp_template_metadata.get(left),
8017 self.cpp_template_metadata.get(right),
8018 ) else {
8019 return false;
8020 };
8021 left_metadata.primary_fq_name == right_metadata.primary_fq_name
8022 && left_metadata.is_primary()
8023 && right_metadata.is_primary()
8024 && cpp_reconcile_primary_template_parameters(
8025 &[(left, left_metadata), (right, right_metadata)],
8026 right,
8027 )
8028 .is_some()
8029 }
8030
8031 fn alias_candidate_may_preserve_target(
8032 &self,
8033 analyzer: &CppGraphSource<'_>,
8034 visible_from: &ProjectFile,
8035 candidate: &CodeUnit,
8036 target: &CodeUnit,
8037 ) -> bool {
8038 let mut current = candidate.clone();
8039 let mut seen = HashSet::default();
8040 loop {
8041 if same_visible_symbol(¤t, target)
8042 || self.compatible_primary_template_redeclarations(¤t, target)
8043 {
8044 return true;
8045 }
8046 if self.cpp_template_metadata.contains_key(¤t) {
8047 return true;
8048 }
8049 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8050 return false;
8051 };
8052 let StructuredAliasTarget::Named {
8053 components,
8054 global,
8055 arguments,
8056 } = alias_target
8057 else {
8058 return false;
8059 };
8060 if arguments.is_some() || !seen.insert(current.clone()) {
8061 return true;
8062 }
8063 let qualified = components.join("::");
8064 let next = if global {
8065 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8066 } else {
8067 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8068 };
8069 let Some(next) = next else {
8070 return true;
8071 };
8072 current = next;
8073 }
8074 }
8075
8076 fn alias_candidate_structurally_reaches_target(
8077 &self,
8078 analyzer: &CppGraphSource<'_>,
8079 visible_from: &ProjectFile,
8080 candidate: &CodeUnit,
8081 target: &CodeUnit,
8082 ) -> bool {
8083 let mut current = candidate.clone();
8084 let mut seen = HashSet::default();
8085 loop {
8086 if same_visible_symbol(¤t, target)
8087 || self.compatible_primary_template_redeclarations(¤t, target)
8088 || self
8089 .cpp_template_metadata
8090 .get(¤t)
8091 .zip(self.cpp_template_metadata.get(target))
8092 .is_some_and(|(current, target)| {
8093 current.primary_fq_name == target.primary_fq_name
8094 })
8095 {
8096 return true;
8097 }
8098 if !seen.insert(current.clone()) {
8099 return false;
8100 }
8101 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8102 return false;
8103 };
8104 if let StructuredAliasTarget::Named { components, .. } = &alias_target
8105 && components[..components.len().saturating_sub(1)]
8106 .iter()
8107 .any(|component| {
8108 self.type_reference_component_directly_names_target(component, target)
8109 })
8110 && self.structured_class_alias_path_preserves_target(
8111 analyzer,
8112 visible_from,
8113 ¤t,
8114 target,
8115 )
8116 {
8117 return true;
8118 }
8119 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8120 return false;
8121 }
8122 let Some(primary) =
8123 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8124 else {
8125 return false;
8126 };
8127 current = primary;
8128 }
8129 }
8130
8131 pub fn qualified_alias_reference_may_reach_target(
8143 &self,
8144 analyzer: &CppGraphSource<'_>,
8145 file: &ProjectFile,
8146 components: &[String],
8147 global: bool,
8148 target: &CodeUnit,
8149 ) -> bool {
8150 for end in 0..components.len() {
8151 let spelled = &components[..=end];
8152 for candidate in self
8153 .visible_identifier_candidates(file, &components[end])
8154 .filter(|candidate| declared_type_alias(analyzer, candidate))
8155 {
8156 let candidate_components = canonical_cpp_scope_components(candidate);
8157 let shape_matches = if global {
8158 candidate_components == spelled
8159 } else {
8160 candidate_components.ends_with(spelled)
8161 };
8162 if shape_matches
8163 && self.alias_candidate_structurally_reaches_target(
8164 analyzer, file, candidate, target,
8165 )
8166 {
8167 return true;
8168 }
8169 }
8170 }
8171 false
8172 }
8173
8174 fn type_candidates_for_declaration<'b>(
8178 &'b self,
8179 visible_from: &ProjectFile,
8180 declaration: &CodeUnit,
8181 raw_name: &str,
8182 ) -> Vec<&'b CodeUnit> {
8183 let Some(normalized) = normalize_reference_name(raw_name) else {
8184 return Vec::new();
8185 };
8186 if let Some(namespace) = cpp_namespace_for(declaration) {
8187 for prefix in namespace_prefixes(&namespace) {
8188 let qualified = format!("{prefix}::{normalized}");
8189 let candidates = self.type_candidates(visible_from, &qualified);
8190 if !candidates.is_empty() {
8191 return candidates;
8192 }
8193 }
8194 }
8195 self.type_candidates(visible_from, &normalized)
8196 }
8197
8198 fn resolve_unique_type_for_declaration(
8199 &self,
8200 visible_from: &ProjectFile,
8201 declaration: &CodeUnit,
8202 raw_name: &str,
8203 ) -> Option<CodeUnit> {
8204 unique_logical_type_candidate(self.type_candidates_for_declaration(
8205 visible_from,
8206 declaration,
8207 raw_name,
8208 ))
8209 }
8210
8211 fn resolve_defining_type_for_declaration(
8229 &self,
8230 analyzer: &CppGraphSource<'_>,
8231 visible_from: &ProjectFile,
8232 declaration: &CodeUnit,
8233 raw_name: &str,
8234 ) -> Option<CodeUnit> {
8235 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8236 let logical = unique_logical_type_candidate(candidates.clone())?;
8237 let mut defining = candidates.into_iter().filter(|candidate| {
8238 candidate.is_class()
8239 && cpp_class_declaration_strength(analyzer, candidate)
8240 == CppClassDeclarationStrength::Full
8241 });
8242 match (defining.next(), defining.next()) {
8243 (Some(unique_definition), None) => Some(unique_definition.clone()),
8244 _ => Some(logical),
8245 }
8246 }
8247
8248 pub fn resolves_to_type(
8249 &self,
8250 analyzer: &CppGraphSource<'_>,
8251 file: &ProjectFile,
8252 raw_name: &str,
8253 target: &CodeUnit,
8254 ) -> bool {
8255 let Some(normalized) = normalize_reference_name(raw_name) else {
8256 return false;
8257 };
8258 let candidates = self.type_candidates(file, &normalized);
8259 if candidates.is_empty() {
8260 return self.parser_alias_resolves_to_type(file, raw_name, target);
8261 }
8262 let Some(resolved) =
8263 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8264 else {
8265 return false;
8266 };
8267 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8268 }
8269
8270 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8271 let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
8272 let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
8273 match resolved.kind() {
8274 CodeUnitType::Class => Some(resolved),
8275 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
8276 _ => None,
8277 }
8278 }
8279
8280 pub fn same_logical_callable(
8295 &self,
8296 analyzer: &CppGraphSource<'_>,
8297 left: &CodeUnit,
8298 right: &CodeUnit,
8299 ) -> bool {
8300 if same_logical_symbol(left, right) {
8301 return true;
8302 }
8303 if left.kind() != right.kind()
8304 || !left.is_callable()
8305 || !right.is_callable()
8306 || left.fq_name() != right.fq_name()
8307 {
8308 return false;
8309 }
8310 if self.callable_is_template_declaration(analyzer, left)
8316 || self.callable_is_template_declaration(analyzer, right)
8317 {
8318 return false;
8319 }
8320 let (Some(left_comparable), Some(right_comparable)) = (
8321 self.callable_comparable(analyzer, left),
8322 self.callable_comparable(analyzer, right),
8323 ) else {
8324 return false;
8325 };
8326 if left_comparable.suffix != right_comparable.suffix
8331 || left_comparable.shapes.len() != right_comparable.shapes.len()
8332 {
8333 return false;
8334 }
8335 left_comparable
8336 .shapes
8337 .iter()
8338 .zip(right_comparable.shapes.iter())
8339 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8340 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8341 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8342 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8343 }
8344 _ => false,
8348 })
8349 }
8350
8351 fn comparable_shapes_agree(
8357 &self,
8358 analyzer: &CppGraphSource<'_>,
8359 left: &CppComparableParameter,
8360 right: &CppComparableParameter,
8361 ) -> bool {
8362 let mut stack = vec![(left.root(), right.root())];
8363 while let Some((left_index, right_index)) = stack.pop() {
8364 match (left.node(left_index), right.node(right_index)) {
8365 (
8366 CppComparableNode::Named {
8367 name: left_name,
8368 primitive: left_primitive,
8369 konst: left_konst,
8370 volatil: left_volatil,
8371 },
8372 CppComparableNode::Named {
8373 name: right_name,
8374 primitive: right_primitive,
8375 konst: right_konst,
8376 volatil: right_volatil,
8377 },
8378 ) => {
8379 if left_konst != right_konst
8380 || left_volatil != right_volatil
8381 || left_primitive != right_primitive
8382 || !self.comparable_names_agree(
8383 analyzer,
8384 left_name,
8385 right_name,
8386 *left_primitive,
8387 )
8388 {
8389 return false;
8390 }
8391 }
8392 (
8393 CppComparableNode::Pointer {
8394 inner: left_inner,
8395 konst: left_konst,
8396 volatil: left_volatil,
8397 },
8398 CppComparableNode::Pointer {
8399 inner: right_inner,
8400 konst: right_konst,
8401 volatil: right_volatil,
8402 },
8403 ) => {
8404 if left_konst != right_konst || left_volatil != right_volatil {
8405 return false;
8406 }
8407 stack.push((*left_inner, *right_inner));
8408 }
8409 (
8410 CppComparableNode::Reference { inner: left_inner },
8411 CppComparableNode::Reference { inner: right_inner },
8412 )
8413 | (
8414 CppComparableNode::Array { inner: left_inner },
8415 CppComparableNode::Array { inner: right_inner },
8416 ) => stack.push((*left_inner, *right_inner)),
8417 (
8418 CppComparableNode::Generic {
8419 base: left_base,
8420 arguments: left_arguments,
8421 },
8422 CppComparableNode::Generic {
8423 base: right_base,
8424 arguments: right_arguments,
8425 },
8426 ) => {
8427 if left_arguments.len() != right_arguments.len() {
8428 return false;
8429 }
8430 stack.push((*left_base, *right_base));
8431 stack.extend(
8432 left_arguments.iter().zip(right_arguments.iter()).map(
8433 |(left_argument, right_argument)| (*left_argument, *right_argument),
8434 ),
8435 );
8436 }
8437 _ => return false,
8438 }
8439 }
8440 true
8441 }
8442
8443 fn comparable_names_agree(
8453 &self,
8454 analyzer: &CppGraphSource<'_>,
8455 left: &StructuredTypeName,
8456 right: &StructuredTypeName,
8457 primitive: bool,
8458 ) -> bool {
8459 if primitive {
8460 return left.path() == right.path();
8461 }
8462 match (
8463 self.comparable_name_terminal(analyzer, left),
8464 self.comparable_name_terminal(analyzer, right),
8465 ) {
8466 (Some(left_terminal), Some(right_terminal)) => {
8467 same_logical_symbol(&left_terminal, &right_terminal)
8468 }
8469 (None, None) => {
8470 left.path() == right.path() && left.is_absolute() == right.is_absolute()
8471 }
8472 _ => false,
8473 }
8474 }
8475
8476 fn comparable_name_terminal(
8486 &self,
8487 analyzer: &CppGraphSource<'_>,
8488 name: &StructuredTypeName,
8489 ) -> Option<CodeUnit> {
8490 let mut current = self.comparable_name_declaration(analyzer, name)?;
8491 let mut visited = HashSet::default();
8492 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8493 if !declared_type_alias(analyzer, ¤t) {
8500 return current.is_class().then_some(current);
8501 }
8502 if !visited.insert(current.clone()) {
8503 return None;
8504 }
8505 let signature = current.signature()?;
8506 if cpp_alias_declaration_adds_indirection(signature) {
8511 return None;
8512 }
8513 let raw_target = cpp_alias_declaration_target_text(signature)?;
8514 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
8515 }
8516 None
8517 }
8518
8519 fn comparable_alias_target(
8530 &self,
8531 analyzer: &CppGraphSource<'_>,
8532 alias: &CodeUnit,
8533 raw_target: &str,
8534 ) -> Option<CodeUnit> {
8535 let absolute = raw_target.trim_start().starts_with("::");
8540 let normalized = normalize_reference_name(raw_target)?;
8541 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8542 brokk_bifrost_core::analyzer::Language::Cpp,
8543 &normalized,
8544 );
8545 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8546 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8547 brokk_bifrost_core::analyzer::Language::Cpp,
8548 &namespace,
8549 )
8550 });
8551 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8552 self.comparable_name_declaration(analyzer, &name)
8553 }
8554
8555 fn comparable_name_declaration(
8563 &self,
8564 analyzer: &CppGraphSource<'_>,
8565 name: &StructuredTypeName,
8566 ) -> Option<CodeUnit> {
8567 let mut cache = self
8574 .comparable_name_declarations
8575 .lock()
8576 .expect("C++ comparable name declaration cache poisoned");
8577 if let Some(cached) = cache.get(name) {
8578 return cached.clone();
8579 }
8580 let interner = segment_interner();
8581 let identifier = name.path().last()?;
8582 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8583 let first_depth = if name.is_absolute() {
8584 0
8585 } else {
8586 name.lexical_scope().len()
8587 };
8588 let mut resolved = None;
8589 for depth in (0..=first_depth).rev() {
8590 let mut structured = FqName::new();
8591 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8592 structured.push(interner.intern(component, SegmentKind::Unknown));
8593 }
8594 let mut candidates = candidates_by_identifier
8595 .iter()
8596 .filter(|unit| unit.fq().same_segment_texts(&structured))
8597 .filter(|unit| {
8598 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8599 })
8600 .cloned();
8601 let Some(first) = candidates.next() else {
8602 continue;
8603 };
8604 resolved = candidates
8605 .all(|unit| same_logical_symbol(&unit, &first))
8606 .then_some(first);
8607 break;
8608 }
8609 cache.insert(name.clone(), resolved.clone());
8610 resolved
8611 }
8612
8613 fn callable_comparable(
8619 &self,
8620 analyzer: &CppGraphSource<'_>,
8621 unit: &CodeUnit,
8622 ) -> Option<Arc<ExtractedComparable>> {
8623 if let Some(cached) = self
8624 .callable_comparables
8625 .lock()
8626 .expect("C++ callable comparable cache poisoned")
8627 .get(unit)
8628 .cloned()
8629 {
8630 return cached;
8631 }
8632 let extracted = self
8633 .extract_callable_comparable(analyzer, unit)
8634 .map(Arc::new);
8635 self.callable_comparables
8636 .lock()
8637 .expect("C++ callable comparable cache poisoned")
8638 .insert(unit.clone(), extracted.clone());
8639 extracted
8640 }
8641
8642 fn extract_callable_comparable(
8643 &self,
8644 analyzer: &CppGraphSource<'_>,
8645 unit: &CodeUnit,
8646 ) -> Option<ExtractedComparable> {
8647 let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
8648 let root = prepared.tree().root_node();
8649 let declarator = analyzer
8650 .ranges(unit)
8651 .into_iter()
8652 .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
8653 Some(ExtractedComparable {
8654 shapes: cpp_comparable_parameter_shapes(
8657 declarator,
8658 prepared.source(),
8659 &ParentIndex::unindexed(),
8660 ),
8661 suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
8662 })
8663 }
8664
8665 pub fn canonical_type_for_reference(
8666 &self,
8667 file: &ProjectFile,
8668 raw_name: &str,
8669 ) -> Option<CodeUnit> {
8670 let resolved = self.resolve_type(file, raw_name)?;
8671 self.alias_target(&resolved).or(Some(resolved))
8672 }
8673
8674 pub fn parser_alias_resolves_to_type(
8675 &self,
8676 file: &ProjectFile,
8677 raw_name: &str,
8678 target: &CodeUnit,
8679 ) -> bool {
8680 let Some(alias_name) = normalize_reference_name(raw_name) else {
8681 return false;
8682 };
8683 self.parser_alias_name_may_resolve_to_target(file, &alias_name, target)
8684 }
8685
8686 #[cfg(any(test, feature = "test-support"))]
8687 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8688 self.visible_source_files_by_root
8689 .get(file)
8690 .cloned()
8691 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8692 }
8693
8694 #[cfg(any(test, feature = "test-support"))]
8695 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8696 self.alias_source_parse_counts
8697 .lock()
8698 .expect("alias source parse count lock")
8699 .get(file)
8700 .copied()
8701 .unwrap_or(0)
8702 }
8703
8704 #[cfg(any(test, feature = "test-support"))]
8705 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8706 self.parser_alias_fallback_files.load(Ordering::Relaxed)
8707 }
8708
8709 pub fn resolve_named(
8710 &self,
8711 file: &ProjectFile,
8712 raw_name: &str,
8713 kind: TargetKind,
8714 ) -> Option<CodeUnit> {
8715 let normalized = normalize_reference_name(raw_name)?;
8716 self.named_candidates_for_normalized(file, &normalized, kind)
8717 .into_iter()
8718 .next()
8719 .cloned()
8720 }
8721
8722 pub fn contains_named_symbol(
8723 &self,
8724 file: &ProjectFile,
8725 raw_name: &str,
8726 kind: TargetKind,
8727 target: &CodeUnit,
8728 ) -> bool {
8729 let Some(normalized) = normalize_reference_name(raw_name) else {
8730 return false;
8731 };
8732 self.named_candidates_for_normalized(file, &normalized, kind)
8733 .into_iter()
8734 .any(|unit| {
8735 matches_kind_for_lookup(unit, kind)
8736 && reference_matches_unit(&normalized, unit)
8737 && same_visible_symbol(unit, target)
8738 })
8739 }
8740
8741 pub fn named_candidates(
8742 &self,
8743 file: &ProjectFile,
8744 raw_name: &str,
8745 kind: TargetKind,
8746 ) -> Vec<CodeUnit> {
8747 let Some(normalized) = normalize_reference_name(raw_name) else {
8748 return Vec::new();
8749 };
8750 self.named_candidates_for_normalized(file, &normalized, kind)
8751 .into_iter()
8752 .cloned()
8753 .collect()
8754 }
8755
8756 pub fn resolve_known_non_target(
8757 &self,
8758 file: &ProjectFile,
8759 raw_name: &str,
8760 kind: TargetKind,
8761 target: &CodeUnit,
8762 ) -> bool {
8763 let Some(normalized) = normalize_reference_name(raw_name) else {
8764 return false;
8765 };
8766 normalized.contains("::")
8767 && self
8768 .named_candidates_for_normalized(file, &normalized, kind)
8769 .into_iter()
8770 .any(|unit| {
8771 matches_kind_for_lookup(unit, kind)
8772 && reference_matches_unit(&normalized, unit)
8773 && !same_visible_symbol(unit, target)
8774 })
8775 }
8776
8777 pub fn resolve_call_return_binding(
8778 &self,
8779 analyzer: &CppGraphSource<'_>,
8780 file: &ProjectFile,
8781 raw_name: &str,
8782 arity: usize,
8783 lexical_namespace: Option<&str>,
8784 direct_type: Option<&CodeUnit>,
8785 ) -> Option<CppScanBinding> {
8786 let normalized = normalize_reference_name(raw_name)?;
8787 let mut candidates = Vec::new();
8788 for function in
8789 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8790 {
8791 if cpp_callable_arity(analyzer, function).accepts(arity)
8792 && !direct_type.is_some_and(|direct_type| {
8793 self.callable_is_constructor_declaration(analyzer, function)
8794 && type_owner_of(analyzer, function)
8795 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8796 })
8797 {
8798 candidates.push(function.clone());
8799 }
8800 }
8801 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8802 unanimous_return_binding(analyzer, self, file, &candidates)
8803 }
8804
8805 pub fn resolve_call_return_binding_without_arity(
8806 &self,
8807 analyzer: &CppGraphSource<'_>,
8808 file: &ProjectFile,
8809 raw_name: &str,
8810 lexical_namespace: Option<&str>,
8811 direct_type: Option<&CodeUnit>,
8812 ) -> (bool, Option<CppScanBinding>) {
8813 let Some(normalized) = normalize_reference_name(raw_name) else {
8814 return (false, None);
8815 };
8816 let mut candidates = self
8817 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8818 .into_iter()
8819 .filter(|function| {
8820 function.is_function()
8821 && !direct_type.is_some_and(|direct_type| {
8822 self.callable_is_constructor_declaration(analyzer, function)
8823 && type_owner_of(analyzer, function)
8824 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8825 })
8826 })
8827 .cloned()
8828 .collect::<Vec<_>>();
8829 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8830 let has_candidates = !candidates.is_empty();
8831 (
8832 has_candidates,
8833 unanimous_return_binding(analyzer, self, file, &candidates),
8834 )
8835 }
8836
8837 pub fn visible_identifier_candidates<'b>(
8838 &'b self,
8839 file: &ProjectFile,
8840 identifier: &str,
8841 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8842 self.visible_by_identifier
8843 .get(file)
8844 .and_then(|by_name| by_name.get(identifier))
8845 .into_iter()
8846 .flatten()
8847 }
8848
8849 pub fn type_reference_component_directly_names_target(
8855 &self,
8856 component: &str,
8857 target: &CodeUnit,
8858 ) -> bool {
8859 component == target.identifier()
8860 || self
8861 .cpp_template_metadata
8862 .get(target)
8863 .is_some_and(|metadata| component == metadata.primary_name)
8864 }
8865
8866 pub fn visible_type_reference_component_names_for_target(
8874 &self,
8875 analyzer: &CppGraphSource<'_>,
8876 file: &ProjectFile,
8877 target: &CodeUnit,
8878 ) -> HashSet<String> {
8879 let mut names = HashSet::from_iter([target.identifier().to_string()]);
8880 if let Some(metadata) = self.cpp_template_metadata.get(target) {
8881 names.insert(metadata.primary_name.clone());
8882 }
8883
8884 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8885 for (identifier, candidates) in by_identifier {
8886 if candidates.iter().any(|candidate| {
8887 (candidate.is_class()
8888 && (same_visible_symbol(candidate, target)
8889 || self.compatible_primary_template_redeclarations(candidate, target)))
8890 || (declared_type_alias(analyzer, candidate)
8891 && self.alias_candidate_structurally_reaches_target(
8892 analyzer, file, candidate, target,
8893 ))
8894 }) {
8895 names.insert(identifier.clone());
8896 }
8897 }
8898 }
8899
8900 names
8901 }
8902
8903 pub fn indexed_structural_class_scope(
8904 &self,
8905 file: &ProjectFile,
8906 class: Node<'_>,
8907 source: &str,
8908 ) -> Option<Vec<String>> {
8909 let key = (file.clone(), class.start_byte(), class.end_byte());
8910 if let Some(cached) = self
8911 .indexed_structural_class_scopes
8912 .lock()
8913 .expect("C++ indexed structural-class scope cache poisoned")
8914 .get(&key)
8915 .cloned()
8916 {
8917 return cached;
8918 }
8919 let resolved = (|| {
8920 let name = class.child_by_field_name("name")?;
8921 let identifier = if name.kind() == "template_type" {
8922 node_text(name.child_by_field_name("name")?, source).to_string()
8923 } else {
8924 let mut components = Vec::new();
8925 append_cpp_name_components(name, source, &mut components)?;
8926 components.last()?.clone()
8927 };
8928 let visible = self
8929 .visible_identifier_candidates(file, &identifier)
8930 .cloned()
8931 .collect::<Vec<_>>();
8932 let mut visible = visible;
8933 for candidate in
8934 self.visible_by_file
8935 .get(file)
8936 .into_iter()
8937 .flatten()
8938 .filter(|candidate| {
8939 self.cpp_template_metadata
8940 .get(candidate)
8941 .is_some_and(|metadata| metadata.primary_name == identifier)
8942 })
8943 {
8944 if !visible
8945 .iter()
8946 .any(|existing| same_logical_symbol(existing, candidate))
8947 {
8948 visible.push(candidate.clone());
8949 }
8950 }
8951 let cpp_source = self.cpp_source();
8954 let candidates = visible
8955 .iter()
8956 .filter(|candidate| {
8957 candidate.source() == file
8958 && candidate.is_class()
8959 && !declared_type_alias(&cpp_source, candidate)
8960 && self.cpp.ranges(candidate).iter().any(|range| {
8961 range.start_byte <= class.start_byte()
8962 && class.end_byte() <= range.end_byte
8963 })
8964 })
8965 .collect::<Vec<_>>();
8966 let owner = if name.kind() == "template_type" {
8967 let expected = normalize_cpp_whitespace(node_text(name, source));
8968 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8969 let exact = candidates
8970 .iter()
8971 .copied()
8972 .filter(|candidate| {
8973 candidate
8974 .fq()
8975 .segments()
8976 .iter()
8977 .rev()
8978 .find_map(|&segment| {
8979 let (text, kind) = interner.resolve(segment);
8980 matches!(
8981 kind,
8982 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8983 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8984 )
8985 .then_some(text)
8986 })
8987 .is_some_and(|text| text == expected)
8988 })
8989 .collect::<Vec<_>>();
8990 unique_logical_type_candidate(exact)
8991 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
8992 } else {
8993 unique_logical_type_candidate(candidates)?
8994 };
8995 Some(canonical_cpp_scope_components(&owner))
8996 })();
8997 self.indexed_structural_class_scopes
8998 .lock()
8999 .expect("C++ indexed structural-class scope cache poisoned")
9000 .insert(key, resolved.clone());
9001 resolved
9002 }
9003
9004 pub fn indexed_enclosing_owner_scope(
9005 &self,
9006 analyzer: &CppGraphSource<'_>,
9007 file: &ProjectFile,
9008 node: Node<'_>,
9009 ) -> Option<Vec<String>> {
9010 let anchor = std::iter::successors(Some(node), |current| current.parent())
9011 .find(|current| {
9012 matches!(
9013 current.kind(),
9014 "function_definition"
9015 | "class_specifier"
9016 | "struct_specifier"
9017 | "union_specifier"
9018 )
9019 })
9020 .unwrap_or(node);
9021 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9022 if let Some(cached) = self
9023 .indexed_enclosing_owner_scopes
9024 .lock()
9025 .expect("C++ indexed enclosing-owner scope cache poisoned")
9026 .get(&key)
9027 .cloned()
9028 {
9029 return cached;
9030 }
9031 let resolved = (|| {
9032 let range = Range {
9033 start_byte: node.start_byte(),
9034 end_byte: node.end_byte(),
9035 start_line: node.start_position().row,
9036 end_line: node.end_position().row,
9037 };
9038 let start = analyzer.enclosing_code_unit(file, &range)?;
9039 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9040 start,
9041 |unit| self.cached_precise_parent_of(analyzer, unit),
9042 )
9043 .find(|unit| {
9044 unit.is_class()
9045 && !analyzer
9046 .type_alias_provider()
9047 .is_some_and(|provider| provider.is_type_alias(unit))
9048 })?;
9049 Some(canonical_cpp_scope_components(&owner))
9050 })();
9051 self.indexed_enclosing_owner_scopes
9052 .lock()
9053 .expect("C++ indexed enclosing-owner scope cache poisoned")
9054 .insert(key, resolved.clone());
9055 resolved
9056 }
9057
9058 fn cached_precise_parent_of(
9059 &self,
9060 analyzer: &CppGraphSource<'_>,
9061 code_unit: &CodeUnit,
9062 ) -> Option<CodeUnit> {
9063 if let Some(cached) = self
9064 .precise_parent_cache
9065 .lock()
9066 .expect("C++ precise-parent cache poisoned")
9067 .get(code_unit)
9068 .cloned()
9069 {
9070 return cached;
9071 }
9072 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9073 self.precise_parent_cache
9074 .lock()
9075 .expect("C++ precise-parent cache poisoned")
9076 .insert(code_unit.clone(), resolved.clone());
9077 resolved
9078 }
9079
9080 pub fn callable_is_constructor_declaration(
9081 &self,
9082 analyzer: &CppGraphSource<'_>,
9083 candidate: &CodeUnit,
9084 ) -> bool {
9085 if !candidate.is_function() {
9086 return false;
9087 }
9088 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9089 return false;
9090 };
9091 let root = prepared.tree().root_node();
9092 let candidate_ranges = analyzer.ranges(candidate);
9093 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
9094 let mut current = root
9095 .descendant_for_byte_range(range.start_byte, range.end_byte)
9096 .and_then(|node| node.parent());
9097 while let Some(node) = current {
9098 if matches!(
9099 node.kind(),
9100 "class_specifier" | "struct_specifier" | "union_specifier"
9101 ) {
9102 return node
9103 .child_by_field_name("name")
9104 .map(|name| terminal_name(node_text(name, prepared.source())))
9105 .is_some_and(|name| name == candidate.identifier());
9106 }
9107 current = node.parent();
9108 }
9109 false
9110 });
9111 if enclosed_by_matching_type {
9112 return true;
9113 }
9114 let indexed_containment = analyzer
9115 .declarations(candidate.source())
9116 .into_iter()
9117 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
9118 .any(|owner| {
9119 analyzer.ranges(&owner).iter().any(|owner_range| {
9120 candidate_ranges.iter().any(|candidate_range| {
9121 owner_range.start_byte <= candidate_range.start_byte
9122 && candidate_range.end_byte <= owner_range.end_byte
9123 })
9124 })
9125 });
9126 if indexed_containment {
9127 return true;
9128 }
9129 let metadata = analyzer.signature_metadata(candidate);
9130 !metadata.is_empty()
9131 && metadata
9132 .iter()
9133 .all(|signature| signature.return_type_text().is_none())
9134 }
9135
9136 pub fn callable_is_deduction_guide_declaration(
9144 &self,
9145 analyzer: &CppGraphSource<'_>,
9146 candidate: &CodeUnit,
9147 ) -> bool {
9148 if !candidate.is_function() {
9149 return false;
9150 }
9151 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9152 return false;
9153 };
9154 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
9155 .into_iter()
9156 .any(|declaration| {
9157 if declaration.kind() != "declaration"
9158 || declaration.child_by_field_name("type").is_some()
9159 {
9160 return false;
9161 }
9162 let Some(declarator) = declaration.child_by_field_name("declarator") else {
9163 return false;
9164 };
9165 if declarator.kind() != "function_declarator" {
9166 return false;
9167 }
9168 let mut cursor = declarator.walk();
9169 let has_trailing_return = declarator
9170 .named_children(&mut cursor)
9171 .any(|child| child.kind() == "trailing_return_type");
9172 has_trailing_return
9173 && declarator_name_node(declarator).is_some_and(|name| {
9174 node_text(name, prepared.source()) == candidate.identifier()
9175 })
9176 })
9177 }
9178
9179 pub fn callable_is_template_declaration(
9183 &self,
9184 analyzer: &CppGraphSource<'_>,
9185 candidate: &CodeUnit,
9186 ) -> bool {
9187 if !candidate.is_function() {
9188 return false;
9189 }
9190 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9191 return false;
9192 };
9193 let root = prepared.tree().root_node();
9194 analyzer.ranges(candidate).iter().any(|range| {
9195 let Some(node) = node_for_exact_range(root, range)
9196 .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
9197 else {
9198 return false;
9199 };
9200 node.parent().is_some_and(|parent| {
9201 parent.kind() == "template_declaration"
9202 && parent
9203 .named_child(parent.named_child_count().saturating_sub(1))
9204 .is_some_and(|declaration| same_node(declaration, node))
9205 })
9206 })
9207 }
9208
9209 pub fn type_name_candidates<'b>(
9210 &'b self,
9211 file: &ProjectFile,
9212 normalized: &str,
9213 ) -> Vec<&'b CodeUnit> {
9214 self.candidate_units(file, normalized, TargetKind::Type)
9215 }
9216
9217 pub fn visible_members_for_owner_name<'b>(
9218 &'b self,
9219 file: &ProjectFile,
9220 owner: &CodeUnit,
9221 name: &str,
9222 ) -> Vec<&'b CodeUnit> {
9223 self.visible_identifier_candidates(file, name)
9224 .filter(|unit| {
9225 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9229 .is_some_and(|parent| parent == owner.fq_name())
9230 })
9231 .collect()
9232 }
9233
9234 pub fn visible_member_for_owner_name(
9235 &self,
9236 file: &ProjectFile,
9237 owner: &CodeUnit,
9238 name: &str,
9239 ) -> VisibleMemberResolution {
9240 let candidates = self.visible_members_for_owner_name(file, owner, name);
9241 let mut callables = Vec::new();
9242 let mut non_callable = None;
9243 for candidate in candidates {
9244 if candidate.is_function() {
9245 callables.push(candidate.clone());
9246 } else if non_callable.is_none() {
9247 non_callable = Some(candidate.clone());
9248 }
9249 }
9250 match (callables.is_empty(), non_callable) {
9251 (false, None) => VisibleMemberResolution::Callable(callables),
9252 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9253 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9254 (true, None) => VisibleMemberResolution::Missing,
9255 }
9256 }
9257
9258 fn field_declared_type_fact(
9259 &self,
9260 analyzer: &CppGraphSource<'_>,
9261 field: &CodeUnit,
9262 ) -> Option<DeclaredFieldTypeFact> {
9263 if let Some(cached) = self
9264 .field_type_facts
9265 .lock()
9266 .expect("C++ field type fact cache poisoned")
9267 .get(field)
9268 .cloned()
9269 {
9270 return cached;
9271 }
9272 let decoded = decode_field_declared_type_fact(analyzer, field);
9273 self.field_type_facts
9274 .lock()
9275 .expect("C++ field type fact cache poisoned")
9276 .insert(field.clone(), decoded.clone());
9277 decoded
9278 }
9279
9280 fn structured_alias_target(
9281 &self,
9282 analyzer: &CppGraphSource<'_>,
9283 unit: &CodeUnit,
9284 ) -> Option<StructuredAliasTarget> {
9285 if let Some(cached) = self
9286 .structured_alias_targets
9287 .lock()
9288 .expect("C++ structured alias target cache poisoned")
9289 .get(unit)
9290 .cloned()
9291 {
9292 return cached;
9293 }
9294 let decoded = decode_structured_alias_target(analyzer, unit);
9295 self.structured_alias_targets
9296 .lock()
9297 .expect("C++ structured alias target cache poisoned")
9298 .insert(unit.clone(), decoded.clone());
9299 decoded
9300 }
9301
9302 pub fn type_candidates<'b>(
9303 &'b self,
9304 file: &ProjectFile,
9305 normalized: &str,
9306 ) -> Vec<&'b CodeUnit> {
9307 let mut candidates = self
9308 .candidate_units(file, normalized, TargetKind::Type)
9309 .into_iter()
9310 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
9311 .collect::<Vec<_>>();
9312 dedup_unit_refs(&mut candidates);
9313 candidates
9314 }
9315
9316 pub fn named_candidates_for_normalized<'b>(
9317 &'b self,
9318 file: &ProjectFile,
9319 normalized: &str,
9320 kind: TargetKind,
9321 ) -> Vec<&'b CodeUnit> {
9322 let mut candidates = self
9323 .candidate_units(file, normalized, kind)
9324 .into_iter()
9325 .filter(|unit| {
9326 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9327 })
9328 .collect::<Vec<_>>();
9329 dedup_unit_refs(&mut candidates);
9330 candidates
9331 }
9332
9333 pub fn candidate_units<'b>(
9334 &'b self,
9335 file: &ProjectFile,
9336 normalized: &str,
9337 kind: TargetKind,
9338 ) -> Vec<&'b CodeUnit> {
9339 if normalized.contains("::") {
9340 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9349 brokk_bifrost_core::analyzer::Language::Cpp,
9350 normalized,
9351 )
9352 .pop() else {
9353 return Vec::new();
9354 };
9355 let fqns = cpp_reference_fqn_candidates(normalized, kind);
9356 return self
9357 .visible_identifier_candidates(file, &identifier)
9358 .filter(|unit| {
9359 #[cfg(any(test, feature = "test-support"))]
9360 self.qualified_candidate_inspections
9361 .fetch_add(1, Ordering::Relaxed);
9362 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9363 || canonical_cpp_name_matches(unit, normalized)
9364 })
9365 .collect();
9366 }
9367 self.visible_identifier_candidates(file, normalized)
9368 .collect()
9369 }
9370
9371 #[cfg(any(test, feature = "test-support"))]
9372 pub fn reset_qualified_candidate_inspections(&self) {
9373 self.qualified_candidate_inspections
9374 .store(0, Ordering::Relaxed);
9375 }
9376
9377 #[cfg(any(test, feature = "test-support"))]
9378 pub fn qualified_candidate_inspections(&self) -> usize {
9379 self.qualified_candidate_inspections.load(Ordering::Relaxed)
9380 }
9381
9382 #[cfg(any(test, feature = "test-support"))]
9383 pub fn visibility_identifier_lookup_count(&self) -> usize {
9384 self.visibility_identifier_lookup_count
9385 }
9386
9387 #[cfg(any(test, feature = "test-support"))]
9388 pub fn visibility_identifier_batch_count(&self) -> usize {
9389 self.visibility_identifier_batch_count
9390 }
9391
9392 #[cfg(any(test, feature = "test-support"))]
9393 pub fn reset_target_preserving_type_resolution_count(&self) {
9394 self.target_preserving_type_resolution_count
9395 .store(0, Ordering::Relaxed);
9396 }
9397
9398 #[cfg(any(test, feature = "test-support"))]
9399 pub fn target_preserving_type_resolution_count(&self) -> usize {
9400 self.target_preserving_type_resolution_count
9401 .load(Ordering::Relaxed)
9402 }
9403
9404 #[cfg(any(test, feature = "test-support"))]
9405 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9406 self.visible_parser_alias_name_set_build_count
9407 .load(Ordering::Relaxed)
9408 }
9409}
9410
9411#[derive(Default)]
9412struct IncludeGraph {
9413 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9414}
9415
9416impl IncludeGraph {
9417 fn extend_with<F>(
9418 &mut self,
9419 root: &ProjectFile,
9420 cancellation: Option<&CancellationToken>,
9421 targets_for: &mut F,
9422 ) where
9423 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9424 {
9425 let mut stack = vec![root.clone()];
9426 while let Some(file) = stack.pop() {
9427 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9428 break;
9429 }
9430 if self.targets_by_file.contains_key(&file) {
9431 continue;
9432 }
9433 let targets = targets_for(&file);
9434 stack.extend(targets.iter().cloned());
9435 self.targets_by_file.insert(file, targets);
9436 }
9437 }
9438
9439 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9440 self.targets_by_file.keys()
9441 }
9442
9443 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9444 self.targets_by_file
9445 .get(file)
9446 .map(Vec::as_slice)
9447 .unwrap_or_default()
9448 }
9449
9450 fn reachable_files(
9451 &self,
9452 root: &ProjectFile,
9453 cancellation: Option<&CancellationToken>,
9454 ) -> HashSet<ProjectFile> {
9455 let mut pending = vec![root.clone()];
9456 let mut visited = HashSet::default();
9457 while let Some(file) = pending.pop() {
9458 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9459 break;
9460 }
9461 if visited.insert(file.clone()) {
9462 pending.extend(self.targets(&file).iter().cloned());
9463 }
9464 }
9465 visited
9466 }
9467}
9468
9469fn build_bounded_visible_declarations(
9470 cpp: &dyn CppSource,
9471 token: QueryToken<'_>,
9472 analyzer: &CppGraphSource<'_>,
9473 roots: &HashSet<ProjectFile>,
9474 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9475 cancellation: Option<&CancellationToken>,
9476 stats: &mut BoundedVisibilityStats,
9477) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9478 let mut candidates_by_identifier = HashMap::default();
9479 let mut declarations_by_source_and_reading: HashMap<
9480 (ProjectFile, bool),
9481 BoundedVisibilityDeclarations,
9482 > = HashMap::default();
9483 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9484 roots
9485 .iter()
9486 .map(|root| {
9487 let reading_is_c = analyzer.reference_uses_c_semantics(root);
9488 let root_declarations = declarations_by_source_and_reading
9489 .entry((root.clone(), reading_is_c))
9490 .or_insert_with(|| {
9491 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9492 });
9493 let mut visible = root_declarations
9494 .all
9495 .iter()
9496 .cloned()
9497 .collect::<HashSet<_>>();
9498 let mut pending_names = HashSet::default();
9499 if let Some(prepared) = cpp.prepared_syntax(token, root) {
9500 let mut cursor = prepared.tree().walk();
9505 let mut pending_nodes = vec![prepared.tree().root_node()];
9506 while let Some(node) = pending_nodes.pop() {
9507 if matches!(
9508 node.kind(),
9509 "identifier"
9510 | "type_identifier"
9511 | "field_identifier"
9512 | "namespace_identifier"
9513 ) {
9514 pending_names.insert(node_text(node, prepared.source()).to_string());
9515 }
9516 if node.kind() == "preproc_arg" {
9517 for reference in
9518 object_macro_replacement_type_references(node, prepared.source())
9519 {
9520 pending_names.extend(reference.components);
9521 }
9522 }
9523 pending_nodes.extend(node.named_children(&mut cursor));
9524 }
9525 }
9526 stats.root_names += pending_names.len();
9527 let mut completed_names = HashSet::default();
9528 while !pending_names.is_empty() {
9529 stats.rounds += 1;
9530 let round_names = std::mem::take(&mut pending_names);
9531 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9532 HashMap::default();
9533 let mut identifiers = Vec::new();
9534 for identifier in round_names {
9535 if !completed_names.insert(identifier.clone())
9536 || cancellation.is_some_and(CancellationToken::is_cancelled)
9537 {
9538 continue;
9539 }
9540 identifiers.push(identifier);
9541 }
9542 let missing_identifiers = identifiers
9543 .iter()
9544 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9545 .cloned()
9546 .collect::<HashSet<_>>();
9547 if !missing_identifiers.is_empty() {
9548 let lookup_started = Instant::now();
9549 let mut candidates = cpp
9550 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9551 stats.lookup_elapsed += lookup_started.elapsed();
9552 stats.identifier_lookups += missing_identifiers.len();
9553 stats.identifier_batches += 1;
9554 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9555 break;
9556 }
9557 for identifier in missing_identifiers {
9558 let units = candidates.remove(&identifier).unwrap_or_default();
9559 let candidate_count = units.len();
9560 let candidate_sources = units
9561 .into_iter()
9562 .map(|unit| unit.source().clone())
9563 .collect::<HashSet<_>>();
9564 candidates_by_identifier
9565 .insert(identifier, (candidate_sources, candidate_count));
9566 }
9567 }
9568 for identifier in identifiers {
9569 let (candidate_sources, candidate_count) = candidates_by_identifier
9570 .get(&identifier)
9571 .expect("every missing identifier was inserted after the batch lookup");
9572 stats.candidate_units += *candidate_count;
9573 for source in candidate_sources.iter().cloned() {
9574 if source != *root
9575 && visible_sources
9576 .get(root)
9577 .is_some_and(|files| files.contains(&source))
9578 {
9579 requested_names_by_source
9580 .entry(source)
9581 .or_default()
9582 .insert(identifier.clone());
9583 }
9584 }
9585 }
9586 stats.candidate_sources += requested_names_by_source.len();
9587 for (source, requested_names) in requested_names_by_source {
9588 let declarations = declarations_by_source_and_reading
9589 .entry((source.clone(), reading_is_c))
9590 .or_insert_with(|| {
9591 bounded_visibility_declarations(
9592 cpp,
9593 analyzer,
9594 &source,
9595 reading_is_c,
9596 stats,
9597 )
9598 });
9599 let mut selected = HashSet::default();
9600 for name in requested_names {
9601 if let Some(units) = declarations.by_identifier.get(&name) {
9602 selected.extend(units.iter().cloned());
9603 }
9604 }
9605 for unit in selected {
9606 stats.selected_units += 1;
9607 let dependency_names = dependency_names_by_unit
9608 .entry(unit.clone())
9609 .or_insert_with(|| {
9610 let mut dependency_names = HashSet::default();
9611 if let Some(prepared) = cpp.prepared_syntax(token, &source) {
9612 let ast_started = Instant::now();
9613 let mut cursor = prepared.tree().walk();
9614 for range in analyzer.ranges(&unit) {
9615 let Some(declaration) = node_for_exact_range(
9616 prepared.tree().root_node(),
9617 &range,
9618 ) else {
9619 continue;
9620 };
9621 let mut pending_nodes = vec![declaration];
9622 while let Some(node) = pending_nodes.pop() {
9623 stats.dependency_ast_nodes += 1;
9624 #[cfg(any(test, feature = "test-support"))]
9625 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9626 .with(|count| count.set(count.get() + 1));
9627 if matches!(
9628 node.kind(),
9629 "type_identifier" | "namespace_identifier"
9630 ) {
9631 dependency_names.insert(
9632 node_text(node, prepared.source()).into(),
9633 );
9634 }
9635 pending_nodes.extend(node.named_children(&mut cursor));
9636 }
9637 }
9638 stats.dependency_ast_elapsed += ast_started.elapsed();
9639 }
9640 dependency_names
9641 });
9642 for name in dependency_names.iter() {
9643 if !completed_names.contains(name) && pending_names.insert(name.clone())
9644 {
9645 stats.dependency_names += 1;
9646 }
9647 }
9648 visible.insert(unit);
9649 }
9650 }
9651 }
9652 (root.clone(), visible)
9653 })
9654 .collect()
9655}
9656
9657struct BoundedVisibilityDeclarations {
9658 all: Vec<CodeUnit>,
9659 by_identifier: HashMap<String, Vec<CodeUnit>>,
9660}
9661
9662fn bounded_visibility_declarations(
9663 cpp: &dyn CppSource,
9664 analyzer: &CppGraphSource<'_>,
9665 file: &ProjectFile,
9666 c_semantics: bool,
9667 stats: &mut BoundedVisibilityStats,
9668) -> BoundedVisibilityDeclarations {
9669 let declarations_started = Instant::now();
9670 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9671 .into_iter()
9672 .collect::<Vec<_>>();
9673 stats.declaration_elapsed += declarations_started.elapsed();
9674 stats.declaration_reads += 1;
9675 stats.declaration_units += all.len();
9676
9677 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9678 for unit in &all {
9679 by_identifier
9680 .entry(unit.identifier().to_string())
9681 .or_default()
9682 .push(unit.clone());
9683 if unit.is_class()
9684 && let Some(metadata) = cpp.template_metadata(unit)
9685 && metadata.primary_name != unit.identifier()
9686 {
9687 by_identifier
9688 .entry(metadata.primary_name)
9689 .or_default()
9690 .push(unit.clone());
9691 }
9692 }
9693 BoundedVisibilityDeclarations { all, by_identifier }
9694}
9695
9696#[derive(Default)]
9697struct BoundedVisibilityStats {
9698 rounds: usize,
9699 root_names: usize,
9700 identifier_lookups: usize,
9701 identifier_batches: usize,
9702 candidate_units: usize,
9703 candidate_sources: usize,
9704 declaration_reads: usize,
9705 declaration_units: usize,
9706 selected_units: usize,
9707 dependency_ast_nodes: usize,
9708 dependency_names: usize,
9709 lookup_elapsed: Duration,
9710 declaration_elapsed: Duration,
9711 dependency_ast_elapsed: Duration,
9712}
9713
9714fn bounded_visibility_declarations_in_reading(
9715 analyzer: &CppGraphSource<'_>,
9716 file: &ProjectFile,
9717 c_semantics: bool,
9718) -> BTreeSet<CodeUnit> {
9719 #[cfg(any(test, feature = "test-support"))]
9720 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9721 analyzer.declarations_in_reading(file, c_semantics)
9722}
9723
9724#[cfg(any(test, feature = "test-support"))]
9725pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9726 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9727}
9728
9729#[cfg(any(test, feature = "test-support"))]
9730pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9731 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9732}
9733
9734#[cfg(any(test, feature = "test-support"))]
9735pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9736 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9737}
9738
9739#[cfg(any(test, feature = "test-support"))]
9740pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9741 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9742}
9743
9744pub struct VisibilityData {
9745 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9746 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9747}
9748
9749pub fn build_visibility_data<F, R, D>(
9759 roots: &HashSet<ProjectFile>,
9760 cancellation: Option<&CancellationToken>,
9761 mut targets_for: F,
9762 mut reading_is_c_for: R,
9763 mut declarations_for: D,
9764) -> VisibilityData
9765where
9766 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9767 R: FnMut(&ProjectFile) -> bool,
9768 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9769{
9770 let mut include_graph = IncludeGraph::default();
9771 for file in roots {
9772 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9773 break;
9774 }
9775 include_graph.extend_with(file, cancellation, &mut targets_for);
9776 }
9777 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9778 .files()
9779 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9780 .map(|file| (file.clone(), declarations_for(file, false)))
9781 .collect();
9782 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9783 let mut visible_by_file = HashMap::default();
9784 let mut visible_source_files_by_root = HashMap::default();
9785 for file in roots {
9786 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9787 break;
9788 }
9789 let mut visited = HashSet::default();
9790 let mut visible = HashSet::default();
9791 let declarations_by_file = if reading_is_c_for(file) {
9792 for reached in cpp_declarations_by_file.keys() {
9793 if !c_declarations_by_file.contains_key(reached) {
9794 let declarations = declarations_for(reached, true);
9795 c_declarations_by_file.insert(reached.clone(), declarations);
9796 }
9797 }
9798 &c_declarations_by_file
9799 } else {
9800 &cpp_declarations_by_file
9801 };
9802 collect_visible_declarations(
9803 &include_graph,
9804 declarations_by_file,
9805 file,
9806 &mut visited,
9807 &mut visible,
9808 cancellation,
9809 );
9810 visible_by_file.insert(file.clone(), visible);
9811 visible_source_files_by_root.insert(file.clone(), visited);
9812 }
9813 VisibilityData {
9814 visible_by_file,
9815 visible_source_files_by_root,
9816 }
9817}
9818
9819#[derive(Default)]
9838struct OutOfLineOwnerBindingStats {
9839 unseen_owners: usize,
9840 definition_lookups: usize,
9841 admitted: usize,
9842}
9843
9844fn extend_with_out_of_line_owner_bindings(
9845 cpp: &dyn CppSource,
9846 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9847) -> OutOfLineOwnerBindingStats {
9848 let mut stats = OutOfLineOwnerBindingStats::default();
9849 for (file, visible) in visible_by_file.iter_mut() {
9850 let mut unseen_owners: HashSet<String> = visible
9854 .iter()
9855 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9856 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9857 .collect();
9858 if unseen_owners.is_empty() {
9859 continue;
9860 }
9861 for unit in visible.iter().filter(|unit| unit.is_class()) {
9862 unseen_owners.remove(&unit.fq_name());
9863 }
9864 stats.unseen_owners += unseen_owners.len();
9865 stats.definition_lookups += unseen_owners.len();
9866 let admitted = unseen_owners
9867 .iter()
9868 .flat_map(|owner| cpp.definitions(owner))
9869 .filter(CodeUnit::is_class)
9870 .collect::<Vec<_>>();
9871 stats.admitted += admitted.len();
9872 visible.extend(admitted);
9873 }
9874 stats
9875}
9876
9877pub enum VisibleMemberResolution {
9878 Callable(Vec<CodeUnit>),
9879 NonCallable,
9880 AmbiguousKind,
9881 Missing,
9882}
9883
9884#[derive(Clone)]
9885pub enum EnclosingMemberOwnerResolution {
9886 Owner(CodeUnit),
9887 Ambiguous,
9888 Missing,
9889}
9890
9891pub fn resolve_declaring_member_owner(
9892 analyzer: &CppGraphSource<'_>,
9893 visibility: &VisibilityIndex<'_>,
9894 file: &ProjectFile,
9895 receiver_owner: &CodeUnit,
9896 member_name: &str,
9897) -> EnclosingMemberOwnerResolution {
9898 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9899 return EnclosingMemberOwnerResolution::Missing;
9900 };
9901 let Some(receiver_owner) =
9902 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9903 else {
9904 return EnclosingMemberOwnerResolution::Ambiguous;
9905 };
9906 let resolve_level = |frontier: &[CodeUnit]| {
9907 let mut member_owners = Vec::new();
9908 for raw_owner in frontier {
9909 let Some(owner) =
9910 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9911 else {
9912 return EnclosingMemberOwnerResolution::Ambiguous;
9913 };
9914 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9915 if !member.is_field() && !member.is_function() {
9920 continue;
9921 }
9922 let Some(member_owner) = type_owner_of(analyzer, member) else {
9923 return EnclosingMemberOwnerResolution::Ambiguous;
9924 };
9925 if !member_owners
9926 .iter()
9927 .any(|existing| same_visible_symbol(existing, &member_owner))
9928 {
9929 member_owners.push(member_owner);
9930 }
9931 }
9932 }
9933 match member_owners.len() {
9934 0 => EnclosingMemberOwnerResolution::Missing,
9935 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9936 _ => EnclosingMemberOwnerResolution::Ambiguous,
9937 }
9938 };
9939 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9943 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9944 return direct;
9945 }
9946 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9947 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9948 let mut path_matches = Vec::new();
9949 while let Some(raw_owner) = stack.pop() {
9950 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9951 else {
9952 return EnclosingMemberOwnerResolution::Ambiguous;
9953 };
9954 let propagated = propagated_counts.entry(owner.clone()).or_default();
9958 if *propagated == 2 {
9959 continue;
9960 }
9961 *propagated += 1;
9962 match resolve_level(std::slice::from_ref(&owner)) {
9963 EnclosingMemberOwnerResolution::Owner(owner) => {
9964 path_matches.push(owner);
9965 if path_matches.len() == 2 {
9966 return EnclosingMemberOwnerResolution::Ambiguous;
9967 }
9968 }
9969 EnclosingMemberOwnerResolution::Ambiguous => {
9970 return EnclosingMemberOwnerResolution::Ambiguous;
9971 }
9972 EnclosingMemberOwnerResolution::Missing => {
9973 stack.extend(hierarchy.get_direct_ancestors(&owner));
9974 }
9975 }
9976 }
9977 match path_matches.len() {
9978 0 => EnclosingMemberOwnerResolution::Missing,
9979 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9980 _ => unreachable!("base-path matches are capped at one before returning"),
9981 }
9982}
9983
9984pub fn resolve_declaring_callable_owner(
9999 analyzer: &CppGraphSource<'_>,
10000 visibility: &VisibilityIndex<'_>,
10001 file: &ProjectFile,
10002 ordinary: EnclosingMemberOwnerResolution,
10003 member_name: &str,
10004 call_arity: usize,
10005) -> EnclosingMemberOwnerResolution {
10006 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10007 return ordinary;
10008 };
10009 if visibility
10010 .visible_members_for_owner_name(file, ordinary_owner, member_name)
10011 .into_iter()
10012 .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
10013 {
10014 return ordinary;
10015 }
10016
10017 let mut pending = match member_using_declaration_bases(
10018 analyzer,
10019 visibility,
10020 file,
10021 ordinary_owner,
10022 member_name,
10023 ) {
10024 Ok(bases) => bases,
10025 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10026 };
10027 let mut visited = HashSet::default();
10028 let mut introduced_owners = Vec::new();
10029 while let Some(owner) = pending.pop() {
10030 if !visited.insert(owner.clone()) {
10031 continue;
10032 }
10033 let accepts_arity = visibility
10034 .visible_members_for_owner_name(file, &owner, member_name)
10035 .into_iter()
10036 .any(|unit| {
10037 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
10038 });
10039 if accepts_arity {
10040 if !introduced_owners
10041 .iter()
10042 .any(|existing| same_visible_symbol(existing, &owner))
10043 {
10044 introduced_owners.push(owner);
10045 }
10046 continue;
10047 }
10048 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10049 Ok(bases) => pending.extend(bases),
10050 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10051 }
10052 }
10053 match introduced_owners.as_slice() {
10054 [] => ordinary,
10055 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10056 _ => EnclosingMemberOwnerResolution::Ambiguous,
10057 }
10058}
10059
10060fn member_using_declaration_bases(
10061 analyzer: &CppGraphSource<'_>,
10062 visibility: &VisibilityIndex<'_>,
10063 file: &ProjectFile,
10064 owner: &CodeUnit,
10065 member_name: &str,
10066) -> Result<Vec<CodeUnit>, ()> {
10067 let Some(source) = analyzer.get_source(owner, false) else {
10068 return Ok(Vec::new());
10069 };
10070 let scopes = cpp_member_using_declaration_scopes(&source, member_name);
10071 if scopes.is_empty() {
10072 return Ok(Vec::new());
10073 }
10074 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10075 return Ok(Vec::new());
10076 };
10077 let mut bases = Vec::new();
10078 for raw_ancestor in hierarchy.get_ancestors(owner) {
10079 let Some(ancestor) =
10080 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10081 else {
10082 return Err(());
10083 };
10084 let qualified = cpp_name_for(&ancestor);
10085 if scopes
10086 .iter()
10087 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10088 && !bases
10089 .iter()
10090 .any(|existing| same_visible_symbol(existing, &ancestor))
10091 {
10092 bases.push(ancestor);
10093 }
10094 }
10095 Ok(bases)
10096}
10097
10098pub fn lexical_component_tiers<'a>(
10099 components: &'a [String],
10100 global: bool,
10101 lexical_scope: &'a [String],
10102) -> impl Iterator<Item = Vec<String>> + 'a {
10103 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10104 (0..=first_prefix_len).rev().map(move |prefix_len| {
10105 let mut qualified = Vec::with_capacity(prefix_len + components.len());
10106 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10107 qualified.extend_from_slice(components);
10108 qualified
10109 })
10110}
10111
10112pub fn build_visible_identifier_index(
10113 analyzer: &CppGraphSource<'_>,
10114 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10115 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10116 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10117) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10118 let mut out = HashMap::default();
10119 for (file, visible) in visible_by_file {
10120 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10121 for unit in visible {
10122 if unit.is_field()
10123 && !visible_source_files_by_root
10124 .get(file)
10125 .is_some_and(|sources| sources.contains(unit.source()))
10126 && cpp_global_field_has_internal_linkage_cached(
10127 analyzer,
10128 global_field_internal_linkage,
10129 unit,
10130 )
10131 {
10132 continue;
10133 }
10134 by_identifier
10135 .entry(unit.identifier().to_string())
10136 .or_default()
10137 .push(unit.clone());
10138 }
10139 for units in by_identifier.values_mut() {
10140 sort_lookup_units(units);
10141 units.dedup();
10142 }
10143 out.insert(file.clone(), by_identifier);
10144 }
10145 out
10146}
10147
10148fn sort_lookup_units(units: &mut [CodeUnit]) {
10149 units.sort_by(|left, right| {
10150 left.fq_name()
10151 .cmp(&right.fq_name())
10152 .then_with(|| left.signature().cmp(&right.signature()))
10153 .then_with(|| left.source().cmp(right.source()))
10154 .then_with(|| left.kind().cmp(&right.kind()))
10155 .then_with(|| {
10156 left.package_segment_count()
10157 .cmp(&right.package_segment_count())
10158 })
10159 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10160 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10161 });
10162}
10163
10164fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10165 let interner = segment_interner();
10166 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10167 let (left_text, left_kind) = interner.resolve(left_id);
10168 let (right_text, right_kind) = interner.resolve(right_id);
10169 let order = left_text
10170 .cmp(right_text)
10171 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10172 if order != CmpOrdering::Equal {
10173 return order;
10174 }
10175 }
10176 left.len().cmp(&right.len())
10177}
10178
10179const fn segment_kind_order(kind: SegmentKind) -> u8 {
10180 match kind {
10181 SegmentKind::Path => 0,
10182 SegmentKind::Package => 1,
10183 SegmentKind::Type => 2,
10184 SegmentKind::Companion => 3,
10185 SegmentKind::Nested => 4,
10186 SegmentKind::Member => 5,
10187 SegmentKind::Unknown => 6,
10188 }
10189}
10190
10191fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10192 let mut deduped = Vec::with_capacity(units.len());
10193 for unit in units.drain(..) {
10194 if !deduped.contains(&unit) {
10195 deduped.push(unit);
10196 }
10197 }
10198 *units = deduped;
10199}
10200
10201pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10202 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10206 brokk_bifrost_core::analyzer::Language::Cpp,
10207 reference,
10208 );
10209 if parts.is_empty() {
10210 return Vec::new();
10211 }
10212
10213 let mut candidates = Vec::new();
10214 for package_len in 0..parts.len() {
10215 let package = parts[..package_len].join("::");
10216 let rest = &parts[package_len..];
10217 if rest.is_empty() {
10218 continue;
10219 }
10220 match kind {
10221 TargetKind::Type | TargetKind::Constructor => {
10222 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10223 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10224 }
10225 TargetKind::FreeFunction
10226 | TargetKind::Method
10227 | TargetKind::GlobalField
10228 | TargetKind::MemberField
10229 | TargetKind::Macro => {
10230 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10231 if rest.len() > 1 {
10232 let owner = rest[..rest.len() - 1].join("$");
10233 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10234 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10235 }
10236 }
10237 }
10238 }
10239 candidates
10240}
10241
10242fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10243 let fqn = if package.is_empty() {
10244 short.to_string()
10245 } else {
10246 format!("{package}.{short}")
10247 };
10248 if !out.contains(&fqn) {
10249 out.push(fqn);
10250 }
10251}
10252
10253pub fn infer_cpp_initializer_type(
10254 analyzer: &CppGraphSource<'_>,
10255 visibility: &VisibilityIndex<'_>,
10256 file: &ProjectFile,
10257 source: &str,
10258 node: Node<'_>,
10259) -> Option<CodeUnit> {
10260 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10261 .and_then(|binding| binding.unit)
10262}
10263
10264pub fn infer_cpp_initializer_binding(
10265 analyzer: &CppGraphSource<'_>,
10266 visibility: &VisibilityIndex<'_>,
10267 file: &ProjectFile,
10268 source: &str,
10269 node: Node<'_>,
10270 receiver_resolver: Option<&ReceiverResolver<'_>>,
10271) -> Option<CppScanBinding> {
10272 match node.kind() {
10273 "new_expression" => {
10274 let text = normalize_cpp_whitespace(node_text(node, source));
10275 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
10276 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
10277 let name = normalize_cpp_type_name(type_text);
10278 Some(CppScanBinding::from_type_name(
10279 name.clone(),
10280 visibility.resolve_type(file, &name),
10281 1,
10282 ))
10283 }
10284 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10285 if function.kind() == "field_expression" {
10293 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10294 return resolve_field_method_call_return_binding(
10295 analyzer,
10296 visibility,
10297 file,
10298 source,
10299 function,
10300 arity,
10301 receiver_resolver,
10302 );
10303 }
10304 let function_text = node_text(function, source);
10305 let direct_type_binding = visibility
10306 .resolve_type(file, function_text)
10307 .map(|unit| CppScanBinding::from_unit(unit, 0));
10308 if function.kind() == "template_function" && direct_type_binding.is_some() {
10309 let lexical_namespace = enclosing_namespace_context(node, source);
10310 let arity = visibility.call_arity_evidence(file, node, source).exact();
10311 if let Some(arity) = arity
10312 && let Some(binding) = visibility.resolve_call_return_binding(
10313 analyzer,
10314 file,
10315 function_text,
10316 arity,
10317 lexical_namespace.as_deref(),
10318 direct_type_binding
10319 .as_ref()
10320 .and_then(|binding| binding.unit.as_ref()),
10321 )
10322 {
10323 return Some(binding);
10324 }
10325 let (has_callable, callable_binding) = visibility
10326 .resolve_call_return_binding_without_arity(
10327 analyzer,
10328 file,
10329 function_text,
10330 lexical_namespace.as_deref(),
10331 direct_type_binding
10332 .as_ref()
10333 .and_then(|binding| binding.unit.as_ref()),
10334 );
10335 if let Some(binding) = callable_binding {
10336 return Some(binding);
10337 }
10338 if has_callable {
10339 return None;
10340 }
10341 return direct_type_binding;
10342 }
10343 let arity = visibility.call_arity_evidence(file, node, source).exact();
10348 if let Some(arity) = arity {
10349 let direct_type_binding_for_call = direct_type_binding.clone();
10350 if let Some(binding) = resolve_static_method_call_return_binding(
10351 analyzer, visibility, file, source, function, arity,
10352 )
10353 .or_else(|| {
10354 visibility.resolve_call_return_binding(
10359 analyzer,
10360 file,
10361 function_text,
10362 arity,
10363 enclosing_namespace_context(node, source).as_deref(),
10364 direct_type_binding_for_call
10365 .as_ref()
10366 .and_then(|binding| binding.unit.as_ref()),
10367 )
10368 }) {
10369 return Some(binding);
10370 }
10371 }
10372 direct_type_binding
10373 }),
10374 _ => None,
10375 }
10376}
10377
10378fn resolve_static_method_call_return_binding(
10379 analyzer: &CppGraphSource<'_>,
10380 visibility: &VisibilityIndex<'_>,
10381 file: &ProjectFile,
10382 source: &str,
10383 function: Node<'_>,
10384 arity: usize,
10385) -> Option<CppScanBinding> {
10386 if function.kind() != "qualified_identifier" {
10387 return None;
10388 }
10389 let qualified = normalize_cpp_reference_text(node_text(function, source));
10390 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10397 brokk_bifrost_core::analyzer::Language::Cpp,
10398 &qualified,
10399 );
10400 let (owner_text, member_name) = match parts.split_last() {
10401 Some((member, owner_parts)) if !owner_parts.is_empty() => {
10402 (owner_parts.join("::"), member.clone())
10403 }
10404 _ => {
10405 let scope = function.child_by_field_name("scope")?;
10406 let name = function.child_by_field_name("name")?;
10407 (
10408 node_text(scope, source).to_string(),
10409 node_text(name, source).to_string(),
10410 )
10411 }
10412 };
10413 let owner = visibility.resolve_type(file, &owner_text)?;
10414 let candidates = visibility
10415 .visible_members_for_owner_name(file, &owner, &member_name)
10416 .into_iter()
10417 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
10418 .cloned()
10419 .collect::<Vec<_>>();
10420 unanimous_return_binding(analyzer, visibility, file, &candidates)
10421}
10422
10423fn resolve_field_method_call_return_binding(
10424 analyzer: &CppGraphSource<'_>,
10425 visibility: &VisibilityIndex<'_>,
10426 file: &ProjectFile,
10427 source: &str,
10428 function: Node<'_>,
10429 arity: usize,
10430 receiver_resolver: Option<&ReceiverResolver<'_>>,
10431) -> Option<CppScanBinding> {
10432 debug_assert_eq!(
10433 function.kind(),
10434 "field_expression",
10435 "the member-call return binding answers only for a field-expression callee"
10436 );
10437 let receiver_resolver = receiver_resolver?;
10438 let field = function.child_by_field_name("field")?;
10439 let member_name = node_text(function_terminal_node(field), source);
10440 let receiver = function
10441 .child_by_field_name("argument")
10442 .or_else(|| function.named_child(0))?;
10443 let owners = receiver_resolver(receiver, source);
10444 let mut candidates = Vec::new();
10445 for owner in owners {
10446 let declaring_owner =
10447 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10448 EnclosingMemberOwnerResolution::Owner(owner) => owner,
10449 EnclosingMemberOwnerResolution::Missing => continue,
10450 EnclosingMemberOwnerResolution::Ambiguous => return None,
10451 };
10452 candidates.extend(
10453 visibility
10454 .visible_members_for_owner_name(file, &declaring_owner, member_name)
10455 .into_iter()
10456 .filter(|unit| {
10457 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
10458 })
10459 .cloned(),
10460 );
10461 }
10462 unanimous_return_binding(analyzer, visibility, file, &candidates)
10463}
10464
10465fn unanimous_return_binding(
10466 analyzer: &CppGraphSource<'_>,
10467 visibility: &VisibilityIndex<'_>,
10468 file: &ProjectFile,
10469 candidates: &[CodeUnit],
10470) -> Option<CppScanBinding> {
10471 let mut resolved_return: Option<CppScanBinding> = None;
10472 for function in candidates {
10473 let metadata = analyzer.signature_metadata(function);
10474 let return_types = if metadata.is_empty() {
10475 vec![cpp_function_return_type_text(analyzer, function)?]
10476 } else {
10477 metadata
10478 .iter()
10479 .map(|metadata| metadata.return_type_text().map(str::to_string))
10480 .collect::<Option<Vec<_>>>()?
10481 };
10482 for return_text in return_types {
10483 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10484 let name = normalize_cpp_type_name(&return_text);
10485 let binding = CppScanBinding::from_type_name(
10486 name.clone(),
10487 visibility
10488 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10489 indirection,
10490 );
10491 if let Some(existing) = resolved_return.as_ref()
10492 && (existing.indirection != binding.indirection
10493 || match (&existing.unit, &binding.unit) {
10494 (Some(left), Some(right)) => !same_visible_symbol(left, right),
10495 (None, None) => existing.type_name != binding.type_name,
10496 (Some(_), None) | (None, Some(_)) => true,
10497 })
10498 {
10499 return None;
10500 }
10501 resolved_return = Some(binding);
10502 }
10503 }
10504 resolved_return
10505}
10506
10507fn aliases_from_prepared_source(
10508 cpp: &dyn CppSource,
10509 token: QueryToken<'_>,
10510 file: &ProjectFile,
10511) -> Vec<CppAlias> {
10512 let Some(prepared) = cpp.prepared_syntax(token, file) else {
10513 return Vec::new();
10514 };
10515 let mut aliases = Vec::new();
10516 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
10517 aliases
10518}
10519
10520fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10521 walk_named_tree_preorder(root, true, |node| {
10522 match node.kind() {
10523 "alias_declaration" if alias_has_visible_file_scope(node) => {
10524 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
10525 out.push(alias);
10526 }
10527 }
10528 "type_definition" if alias_has_visible_file_scope(node) => {
10529 collect_typedef_aliases(node, source, out)
10530 }
10531 _ => {}
10532 }
10533 WalkControl::Continue
10534 });
10535}
10536
10537fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10538 let mut current = node.parent();
10539 while let Some(parent) = current {
10540 match parent.kind() {
10541 "translation_unit"
10542 | "namespace_definition"
10543 | "declaration_list"
10544 | "linkage_specification" => current = parent.parent(),
10545 "template_declaration" => current = parent.parent(),
10546 _ => return false,
10547 }
10548 }
10549 true
10550}
10551
10552fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
10553 let name = node
10554 .child_by_field_name("name")
10555 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10556 let target = node
10557 .child_by_field_name("type")
10558 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10559 Some(CppAlias {
10560 name,
10561 target,
10562 namespace: enclosing_namespace_context(node, source),
10563 })
10564}
10565
10566fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10567 let Some(type_node) = node.child_by_field_name("type") else {
10568 return;
10569 };
10570 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
10571 return;
10572 };
10573
10574 let mut cursor = node.walk();
10575 for child in node.named_children(&mut cursor) {
10576 if same_node(child, type_node) {
10577 continue;
10578 }
10579 if let Some(name) = extract_typedef_declarator_name(child, source) {
10580 out.push(CppAlias {
10581 name,
10582 target: target.clone(),
10583 namespace: enclosing_namespace_context(node, source),
10584 });
10585 }
10586 }
10587}
10588
10589fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
10590 match node.kind() {
10591 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
10592 normalize_reference_name(node_text(node, source))
10593 }
10594 _ => node
10595 .child_by_field_name("declarator")
10596 .or_else(|| node.child_by_field_name("name"))
10597 .or_else(|| last_named_child(node))
10598 .and_then(|child| extract_typedef_declarator_name(child, source)),
10599 }
10600}
10601
10602fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
10603 let count = node.named_child_count();
10604 if count == 0 {
10605 None
10606 } else {
10607 node.named_child(count - 1)
10608 }
10609}
10610
10611pub fn collect_include_closure(
10612 analyzer: &CppGraphSource<'_>,
10613 include_targets: &IncludeTargetIndex,
10614 file: &ProjectFile,
10615 out: &mut HashSet<ProjectFile>,
10616 cancellation: Option<&CancellationToken>,
10617) {
10618 let mut stack = vec![file.clone()];
10619 while let Some(file) = stack.pop() {
10620 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10621 break;
10622 }
10623 if !out.insert(file.clone()) {
10624 continue;
10625 }
10626 let imports = analyzer.import_statements(&file);
10627 for include in cpp_include_paths(&imports) {
10628 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10629 stack.push(target);
10630 }
10631 }
10632 }
10633}
10634
10635fn collect_visible_declarations(
10636 include_graph: &IncludeGraph,
10637 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10638 file: &ProjectFile,
10639 visited: &mut HashSet<ProjectFile>,
10640 out: &mut HashSet<CodeUnit>,
10641 cancellation: Option<&CancellationToken>,
10642) {
10643 let mut stack = vec![file.clone()];
10644 while let Some(file) = stack.pop() {
10645 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10646 break;
10647 }
10648 if !visited.insert(file.clone()) {
10649 continue;
10650 }
10651 if let Some(declarations) = declarations_by_file.get(&file) {
10652 out.extend(declarations.iter().cloned());
10653 }
10654 stack.extend(include_graph.targets(&file).iter().cloned());
10655 }
10656}
10657
10658pub fn signature_arity(signature: Option<&str>) -> usize {
10659 let Some(signature) = signature else {
10660 return 0;
10661 };
10662 let inner = signature
10663 .find('(')
10664 .and_then(|open| {
10665 signature[open + 1..]
10666 .find(')')
10667 .map(|close| &signature[open + 1..open + 1 + close])
10668 })
10669 .unwrap_or(signature)
10670 .trim();
10671 if inner.is_empty() || inner == "void" {
10672 return 0;
10673 }
10674 cpp_split_top_level_commas(inner).count()
10675}
10676
10677fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10678 let source = format!("void __bifrost_macro_parameters({replacement});");
10679 let mut parser = Parser::new();
10680 parser
10681 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10682 .ok()?;
10683 let tree = parser.parse(&source, None)?;
10684 let root = tree.root_node();
10685 if root.has_error() {
10686 return None;
10687 }
10688 let declaration = root.named_child(0)?;
10689 let declarator = declaration.child_by_field_name("declarator")?;
10690 let parameters = declarator.child_by_field_name("parameters")?;
10691 let mut required = 0;
10692 let mut total = 0;
10693 let mut repeated = false;
10694 let mut cursor = parameters.walk();
10695 for parameter in parameters.children(&mut cursor) {
10696 match parameter.kind() {
10697 "parameter_declaration" => {
10698 if parameter.child_by_field_name("declarator").is_none()
10699 && parameter
10700 .child_by_field_name("type")
10701 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
10702 {
10703 continue;
10704 }
10705 required += 1;
10706 total += 1;
10707 }
10708 "optional_parameter_declaration" => total += 1,
10709 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
10710 repeated = true;
10711 }
10712 _ => {}
10713 }
10714 }
10715 Some(CallableArity::new(required, total, repeated))
10716}
10717
10718pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
10719 analyzer
10720 .signature_metadata(unit)
10721 .into_iter()
10722 .find_map(|metadata| metadata.callable_arity())
10723 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
10724}
10725
10726pub fn cpp_callable_parameter_types(
10727 analyzer: &CppGraphSource<'_>,
10728 unit: &CodeUnit,
10729) -> Option<Vec<String>> {
10730 analyzer
10731 .signature_metadata(unit)
10732 .into_iter()
10733 .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
10734 .or_else(|| unit.signature().and_then(cpp_signature_param_types))
10735}
10736
10737fn merge_compatible_callable_arities(
10738 left: CallableArity,
10739 right: CallableArity,
10740) -> Option<CallableArity> {
10741 let total = left.total();
10742 let left_repeated = left.accepts(total.saturating_add(1));
10743 let right_repeated = right.accepts(right.total().saturating_add(1));
10744 if total != right.total() || left_repeated != right_repeated {
10745 return None;
10746 }
10747 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10748 Some(CallableArity::new(required, total, left_repeated))
10749}
10750
10751fn find_include_activation(
10752 cpp: &dyn CppSource,
10753 token: QueryToken<'_>,
10754 file: &ProjectFile,
10755 prepared: &PreparedSyntaxTree,
10756 donor_source: &ProjectFile,
10757) -> Option<usize> {
10758 let include_targets = cpp.include_target_index();
10759 let mut direct_includes = Vec::new();
10760 let mut nodes = vec![prepared.tree().root_node()];
10761 let reference = CallableReferenceContext {
10764 file,
10765 position: None,
10766 };
10767 while let Some(node) = nodes.pop() {
10768 if node.kind() == "preproc_include" {
10769 if callable_preprocessor_context_is_visible_for_reference(
10770 node,
10771 prepared.source(),
10772 &reference,
10773 ) {
10774 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10775 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10776 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10777 file,
10778 &include,
10779 include_targets,
10780 )) {
10781 direct_includes.push((node.end_byte(), target));
10782 }
10783 }
10784 }
10785 continue;
10786 }
10787 push_named_children_reversed(node, &mut nodes);
10788 }
10789 direct_includes.sort_by_key(|(activation, _)| *activation);
10790 let mut known_missing = HashSet::default();
10791 direct_includes
10792 .into_iter()
10793 .find(|(_, direct)| {
10794 unconditional_include_reaches(
10795 cpp,
10796 token,
10797 include_targets,
10798 direct,
10799 donor_source,
10800 file,
10801 &mut known_missing,
10802 )
10803 })
10804 .map(|(activation, _)| activation)
10805}
10806
10807fn find_conditional_include_projection_index(
10808 cpp: &dyn CppSource,
10809 token: QueryToken<'_>,
10810 file: &ProjectFile,
10811 prepared: &PreparedSyntaxTree,
10812 on_state: &dyn Fn(),
10813) -> ConditionalIncludeProjectionIndex {
10814 let reference_is_c = reference_uses_c_semantics(cpp, file);
10815 let include_targets = cpp.include_target_index();
10816 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10817 HashMap::default();
10818 let mut pending = Vec::new();
10819 let mut nodes = vec![prepared.tree().root_node()];
10820 while let Some(node) = nodes.pop() {
10821 if node.kind() == "preproc_include" {
10822 let Some(required_guards) =
10823 include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10824 else {
10825 continue;
10826 };
10827 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10828 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10829 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10830 file,
10831 &include,
10832 include_targets,
10833 )) else {
10834 continue;
10835 };
10836 pending.push((target, node.end_byte(), required_guards.clone()));
10837 }
10838 continue;
10839 }
10840 push_named_children_reversed(node, &mut nodes);
10841 }
10842
10843 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10855 HashMap::default();
10856 while let Some((current_file, activation_byte, path)) = pending.pop() {
10857 let guard_sets = expanded
10858 .entry((current_file.clone(), activation_byte))
10859 .or_default();
10860 if guard_sets
10864 .iter()
10865 .any(|existing| existing.is_subset(&path.all))
10866 {
10867 continue;
10868 }
10869 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10870 .drain(..)
10871 .partition(|existing| path.all.is_subset(existing));
10872 *guard_sets = kept;
10873 guard_sets.push(path.all.clone());
10874 if !evicted.is_empty()
10875 && let Some(projections) = projections_by_source.get_mut(¤t_file)
10876 {
10877 projections.retain(|projection| {
10878 projection.activation_byte != activation_byte
10879 || !evicted.contains(&projection.required_guards)
10880 });
10881 }
10882 on_state();
10883
10884 projections_by_source
10887 .entry(current_file.clone())
10888 .or_default()
10889 .push(ConditionalIncludeProjection {
10890 activation_byte,
10891 required_guards: path.all.clone(),
10892 partial_guards: path.partial.clone(),
10893 });
10894
10895 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10896 continue;
10897 };
10898 let mut nodes = vec![current_prepared.tree().root_node()];
10899 while let Some(node) = nodes.pop() {
10900 if node.kind() == "preproc_include" {
10901 let Some(include_guards) = include_directive_guard_requirements(
10902 node,
10903 current_prepared.source(),
10904 reference_is_c,
10905 ) else {
10906 continue;
10907 };
10908 let Some(reached) = path.merged(&include_guards) else {
10909 continue;
10910 };
10911 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
10912 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10913 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10914 ¤t_file,
10915 &include,
10916 include_targets,
10917 )) else {
10918 continue;
10919 };
10920 pending.push((target, activation_byte, reached.clone()));
10921 }
10922 continue;
10923 }
10924 push_named_children_reversed(node, &mut nodes);
10925 }
10926 }
10927
10928 projections_by_source
10929 .into_iter()
10930 .map(|(source, mut projections)| {
10931 projections.sort_by_key(|projection| projection.activation_byte);
10932 (source, Arc::from(projections))
10933 })
10934 .collect()
10935}
10936
10937#[allow(clippy::too_many_arguments)]
10943fn find_conditional_include_projection_for_source(
10944 cpp: &dyn CppSource,
10945 token: QueryToken<'_>,
10946 file: &ProjectFile,
10947 prepared: &PreparedSyntaxTree,
10948 donor_source: &ProjectFile,
10949 admission: IncludePathAdmission,
10950 reference_guards: Option<&HashSet<PreprocessorGuard>>,
10951 reference_byte: usize,
10952 on_state: &dyn Fn(),
10953) -> bool {
10954 let Some(reference_guards) = reference_guards else {
10955 return false;
10956 };
10957 let reference_is_c = reference_uses_c_semantics(cpp, file);
10958 let include_targets = cpp.include_target_index();
10959 let mut pending = Vec::new();
10960 let mut nodes = vec![prepared.tree().root_node()];
10961 while let Some(node) = nodes.pop() {
10962 if node.kind() == "preproc_include" {
10963 let Some(required_guards) =
10964 include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10965 else {
10966 continue;
10967 };
10968 if node.end_byte() > reference_byte
10969 || !admission.admits(
10970 &required_guards.all,
10971 &required_guards.partial,
10972 Some(reference_guards),
10973 )
10974 {
10975 continue;
10976 }
10977 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10978 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10979 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10980 file,
10981 &include,
10982 include_targets,
10983 )) else {
10984 continue;
10985 };
10986 if &target == donor_source {
10987 return true;
10988 }
10989 pending.push((target, required_guards.clone()));
10990 }
10991 continue;
10992 }
10993 push_named_children_reversed(node, &mut nodes);
10994 }
10995
10996 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
10997 while let Some((current_file, path)) = pending.pop() {
10998 let guard_sets = expanded.entry(current_file.clone()).or_default();
10999 if guard_sets.contains(&path.all) {
11000 continue;
11001 }
11002 guard_sets.push(path.all.clone());
11003 on_state();
11004
11005 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11006 continue;
11007 };
11008 let mut nodes = vec![current_prepared.tree().root_node()];
11009 while let Some(node) = nodes.pop() {
11010 if node.kind() == "preproc_include" {
11011 let Some(include_guards) = include_directive_guard_requirements(
11012 node,
11013 current_prepared.source(),
11014 reference_is_c,
11015 ) else {
11016 continue;
11017 };
11018 let Some(reached) = path.merged(&include_guards) else {
11019 continue;
11020 };
11021 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11022 continue;
11023 }
11024 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
11025 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
11026 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11027 ¤t_file,
11028 &include,
11029 include_targets,
11030 )) else {
11031 continue;
11032 };
11033 if &target == donor_source {
11034 return true;
11035 }
11036 pending.push((target, reached.clone()));
11037 }
11038 continue;
11039 }
11040 push_named_children_reversed(node, &mut nodes);
11041 }
11042 }
11043 false
11044}
11045
11046pub fn cpp_include_closure_reaches(
11059 cpp: &dyn CppSource,
11060 token: QueryToken<'_>,
11061 translation_unit: &ProjectFile,
11062 header: &ProjectFile,
11063) -> bool {
11064 unconditional_include_reaches(
11065 cpp,
11066 token,
11067 cpp.include_target_index(),
11068 translation_unit,
11069 header,
11070 translation_unit,
11071 &mut HashSet::default(),
11072 )
11073}
11074
11075fn unconditional_include_reaches(
11076 cpp: &dyn CppSource,
11077 token: QueryToken<'_>,
11078 include_targets: &IncludeTargetIndex,
11079 first: &ProjectFile,
11080 donor_source: &ProjectFile,
11081 reference_file: &ProjectFile,
11082 known_missing: &mut HashSet<ProjectFile>,
11083) -> bool {
11084 if first == donor_source {
11085 return true;
11086 }
11087 if known_missing.contains(first) {
11088 return false;
11089 }
11090 let reference_is_c = reference_file
11091 .rel_path()
11092 .extension()
11093 .and_then(|extension| extension.to_str())
11094 == Some("c");
11095 if let Some(reaches) =
11096 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
11097 {
11098 return reaches;
11099 }
11100 let mut visited = HashSet::default();
11101 let mut files = vec![first.clone()];
11102 let reference = CallableReferenceContext {
11105 file: reference_file,
11106 position: None,
11107 };
11108 while let Some(file) = files.pop() {
11109 if file == *donor_source {
11110 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
11111 return true;
11112 }
11113 if known_missing.contains(&file) || !visited.insert(file.clone()) {
11114 continue;
11115 }
11116 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11117 continue;
11118 };
11119 let mut cursor = prepared.tree().walk();
11121 let mut nodes = vec![prepared.tree().root_node()];
11122 while let Some(node) = nodes.pop() {
11123 if node.kind() == "preproc_include" {
11124 if callable_preprocessor_context_is_visible_for_reference(
11125 node,
11126 prepared.source(),
11127 &reference,
11128 ) {
11129 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
11130 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
11131 if let Some(target) = unique_include_target(
11132 resolve_include_targets_with_index(&file, &include, include_targets),
11133 ) {
11134 files.push(target);
11135 }
11136 }
11137 }
11138 continue;
11139 }
11140 let first_pushed = nodes.len();
11141 nodes.extend(node.named_children(&mut cursor));
11142 nodes[first_pushed..].reverse();
11143 }
11144 }
11145 known_missing.extend(visited);
11146 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
11147 false
11148}
11149
11150fn declaration_guard_requirements(
11151 analyzer: &CppGraphSource<'_>,
11152 cpp: &dyn CppSource,
11153 candidate: &CodeUnit,
11154) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11155 declaration_guard_sites(analyzer, cpp, candidate, None)
11156 .into_iter()
11157 .map(|site| (site.byte, site.guards))
11158 .collect()
11159}
11160
11161struct DeclarationGuardSite {
11164 byte: usize,
11166 guards: HashSet<PreprocessorGuard>,
11168 completed_family_before_reference: bool,
11172}
11173
11174fn declaration_guard_sites(
11177 analyzer: &CppGraphSource<'_>,
11178 cpp: &dyn CppSource,
11179 candidate: &CodeUnit,
11180 reference_byte: Option<usize>,
11181) -> Vec<DeclarationGuardSite> {
11182 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11183 return Vec::new();
11184 };
11185 let root = prepared.tree().root_node();
11186 analyzer
11187 .ranges(candidate)
11188 .into_iter()
11189 .filter_map(|range| {
11190 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11191 let guards = preprocessor_guard_environment(node, prepared.source())?;
11192 Some(DeclarationGuardSite {
11196 byte: range.start_byte,
11197 guards,
11198 completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
11199 declaration_follows_completed_family_before_reference(
11200 node,
11201 prepared.source(),
11202 reference_byte,
11203 )
11204 }),
11205 })
11206 })
11207 .collect()
11208}
11209
11210fn declaration_follows_completed_family_before_reference(
11229 node: Node<'_>,
11230 source: &str,
11231 reference_byte: usize,
11232) -> bool {
11233 let mut inside_family = false;
11234 let mut ancestor = node.parent();
11235 while let Some(conditional) = ancestor {
11236 if matches!(
11237 conditional.kind(),
11238 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11239 ) && !is_file_covering_include_guard(conditional, source)
11240 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11241 && preprocessor_conditional_contains_descendant(conditional, node)
11242 {
11243 let family = preprocessor_conditional_family_root(conditional);
11244 if !preprocessor_conditional_family_has_terminal_else(family)
11245 || family.end_byte() > reference_byte
11246 {
11247 return false;
11248 }
11249 inside_family = true;
11250 }
11251 ancestor = conditional.parent();
11252 }
11253 inside_family
11254}
11255
11256fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11257 analyzer
11258 .ranges(candidate)
11259 .into_iter()
11260 .map(|range| range.start_byte)
11261 .min()
11262}
11263
11264fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11270 let (first, rest) = contexts.split_first()?;
11271 Some(
11272 first
11273 .defined_macros
11274 .iter()
11275 .filter(|name| {
11276 rest.iter()
11277 .all(|context| context.defined_macros.contains(*name))
11278 })
11279 .cloned()
11280 .collect(),
11281 )
11282}
11283
11284pub fn guard_requirements_hold_at_reference(
11285 required: &HashSet<PreprocessorGuard>,
11286 reference: Option<&HashSet<PreprocessorGuard>>,
11287) -> bool {
11288 reference.is_some_and(|active| {
11289 required
11290 .iter()
11291 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11292 })
11293}
11294
11295fn preprocessor_guard_holds_at_reference(
11296 required: &PreprocessorGuard,
11297 active: &HashSet<PreprocessorGuard>,
11298) -> bool {
11299 if active.contains(required) {
11300 return true;
11301 }
11302 let active_expression = BooleanGuardExpression::all(
11303 active
11304 .iter()
11305 .filter_map(PreprocessorGuard::as_boolean_expression),
11306 );
11307 required
11308 .as_boolean_expression()
11309 .is_some_and(|required| active_expression.implies(&required))
11310}
11311
11312fn guards_compatible_at_reference(
11317 declaration: &HashSet<PreprocessorGuard>,
11318 reference: Option<&HashSet<PreprocessorGuard>>,
11319) -> bool {
11320 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11321}
11322
11323pub fn preprocessor_conditional_family_range(
11332 root: Node<'_>,
11333 start_byte: usize,
11334 end_byte: usize,
11335) -> Option<(usize, usize)> {
11336 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11337 let mut ancestor = Some(node);
11338 while let Some(current) = ancestor {
11339 if is_preprocessor_conditional(current)
11340 && preprocessor_conditional_contains_descendant(current, node)
11341 {
11342 let family = preprocessor_conditional_family_root(current);
11343 return Some((family.start_byte(), family.end_byte()));
11344 }
11345 ancestor = current.parent();
11346 }
11347 None
11348}
11349
11350fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
11351 let mut ancestor = node.parent();
11352 while let Some(current) = ancestor {
11353 if is_preprocessor_conditional(current)
11354 && preprocessor_conditional_contains_descendant(current, node)
11355 {
11356 let family = preprocessor_conditional_family_root(current);
11357 if preprocessor_conditional_family_has_terminal_else(family) {
11358 return Some(family);
11359 }
11360 }
11361 ancestor = current.parent();
11362 }
11363 None
11364}
11365
11366fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11367 while let Some(parent) = conditional.parent() {
11368 let is_alternative = parent
11369 .child_by_field_name("alternative")
11370 .is_some_and(|alternative| {
11371 alternative.start_byte() == conditional.start_byte()
11372 && alternative.end_byte() == conditional.end_byte()
11373 });
11374 if !is_alternative {
11375 break;
11376 }
11377 conditional = parent;
11378 }
11379 conditional
11380}
11381
11382fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11383 loop {
11384 let Some(alternative) = conditional.child_by_field_name("alternative") else {
11385 return false;
11386 };
11387 match alternative.kind() {
11388 "preproc_else" => return true,
11389 "preproc_elif" => conditional = alternative,
11390 _ => return false,
11391 }
11392 }
11393}
11394
11395fn include_directive_guard_requirements(
11408 node: Node<'_>,
11409 source: &str,
11410 reference_is_c: bool,
11411) -> Option<PreprocessorGuardEnvironment> {
11412 let required = preprocessor_guard_environment_by_family(node, source)?;
11413 let excluded_by_language = required.all.iter().any(|guard| match guard {
11414 PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11415 PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11416 _ => false,
11417 });
11418 (!excluded_by_language).then_some(required)
11419}
11420
11421pub fn preprocessor_guard_environment(
11422 node: Node<'_>,
11423 source: &str,
11424) -> Option<HashSet<PreprocessorGuard>> {
11425 preprocessor_guard_environment_by_family(node, source).map(|environment| environment.all)
11426}
11427
11428#[derive(Clone)]
11439struct PreprocessorGuardEnvironment {
11440 all: HashSet<PreprocessorGuard>,
11441 partial: HashSet<PreprocessorGuard>,
11442}
11443
11444impl PreprocessorGuardEnvironment {
11445 fn merged(&self, other: &Self) -> Option<Self> {
11449 Some(Self {
11450 all: merge_preprocessor_guards(&self.all, &other.all)?,
11451 partial: self.partial.union(&other.partial).cloned().collect(),
11452 })
11453 }
11454}
11455
11456fn preprocessor_guard_environment_by_family(
11457 node: Node<'_>,
11458 source: &str,
11459) -> Option<PreprocessorGuardEnvironment> {
11460 let mut all = HashSet::default();
11461 let mut partial = HashSet::default();
11462 let mut ancestor = node.parent();
11463 while let Some(conditional) = ancestor {
11464 if matches!(
11465 conditional.kind(),
11466 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11467 ) && !is_file_covering_include_guard(conditional, source)
11468 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11469 && preprocessor_conditional_contains_descendant(conditional, node)
11470 {
11471 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11472 match guard {
11473 PreprocessorGuard::Constant(true) => {
11474 ancestor = conditional.parent();
11475 continue;
11476 }
11477 PreprocessorGuard::Constant(false) => return None,
11478 _ => {}
11479 }
11480 if all.contains(&guard.negated()) {
11481 return None;
11482 }
11483 if !preprocessor_conditional_family_has_terminal_else(
11484 preprocessor_conditional_family_root(conditional),
11485 ) {
11486 partial.insert(guard.clone());
11487 }
11488 all.insert(guard);
11489 }
11490 ancestor = conditional.parent();
11491 }
11492 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11493 match guard {
11494 PreprocessorGuard::Constant(true) => {}
11495 PreprocessorGuard::Constant(false) => return None,
11496 _ => {
11497 if all.contains(&guard.negated()) {
11498 return None;
11499 }
11500 partial.insert(guard.clone());
11504 all.insert(guard);
11505 }
11506 }
11507 }
11508 Some(PreprocessorGuardEnvironment { all, partial })
11509}
11510
11511fn fragmented_statement_preprocessor_guard(
11512 descendant: Node<'_>,
11513 source: &str,
11514) -> Option<PreprocessorGuard> {
11515 let mut ancestor = descendant.parent();
11521 while let Some(statement) = ancestor {
11522 if statement.kind() == "if_statement"
11523 && let (Some(consequence), Some(alternative)) = (
11524 statement.child_by_field_name("consequence"),
11525 statement.child_by_field_name("alternative"),
11526 )
11527 && alternative.start_byte() <= descendant.start_byte()
11528 && descendant.end_byte() <= alternative.end_byte()
11529 {
11530 let mut cursor = consequence.walk();
11531 let openers = consequence
11532 .named_children(&mut cursor)
11533 .filter(|child| {
11534 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11535 && child
11536 .child(child.child_count().saturating_sub(1))
11537 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11538 })
11539 .collect::<Vec<_>>();
11540 if openers.len() != 1 {
11541 ancestor = statement.parent();
11542 continue;
11543 }
11544
11545 let mut terminators = Vec::new();
11546 let mut stack = vec![alternative];
11547 while let Some(node) = stack.pop() {
11548 if node.kind() == "preproc_call"
11549 && node.start_byte() >= descendant.end_byte()
11550 && node
11551 .child_by_field_name("directive")
11552 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11553 {
11554 terminators.push(node);
11555 continue;
11556 }
11557 push_named_children_reversed(node, &mut stack);
11558 }
11559 if terminators.len() == 1 {
11560 return simple_preprocessor_guard(openers[0], source);
11561 }
11562 }
11563 ancestor = statement.parent();
11564 }
11565 None
11566}
11567
11568fn preprocessor_guard_for_descendant(
11569 conditional: Node<'_>,
11570 descendant: Node<'_>,
11571 source: &str,
11572) -> Option<PreprocessorGuard> {
11573 let mut guard = simple_preprocessor_guard(conditional, source)?;
11574 if conditional
11575 .child_by_field_name("alternative")
11576 .is_some_and(|alternative| {
11577 alternative.start_byte() <= descendant.start_byte()
11578 && descendant.end_byte() <= alternative.end_byte()
11579 })
11580 {
11581 let alternative = conditional.child_by_field_name("alternative")?;
11582 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11586 return None;
11587 }
11588 guard = guard.negated();
11589 }
11590 Some(guard)
11591}
11592
11593fn preprocessor_conditional_contains_descendant(
11594 conditional: Node<'_>,
11595 descendant: Node<'_>,
11596) -> bool {
11597 cpp_displaced_preprocessor_boundary(conditional)
11598 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11599}
11600
11601pub fn merge_preprocessor_guards(
11602 left: &HashSet<PreprocessorGuard>,
11603 right: &HashSet<PreprocessorGuard>,
11604) -> Option<HashSet<PreprocessorGuard>> {
11605 let mut merged = left.clone();
11606 for guard in right {
11607 let boolean_negation = guard
11608 .as_boolean_expression()
11609 .map(|expression| expression.negated());
11610 if merged.contains(&guard.negated())
11611 || boolean_negation.is_some_and(|negated| {
11612 merged
11613 .iter()
11614 .filter_map(PreprocessorGuard::as_boolean_expression)
11615 .any(|existing| existing == negated)
11616 })
11617 {
11618 return None;
11619 }
11620 merged.insert(guard.clone());
11621 }
11622 Some(merged)
11623}
11624
11625fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11626 if conditional.kind() == "preproc_ifdef" {
11627 let name = conditional.child_by_field_name("name")?;
11628 let name = node_text(name, source).to_string();
11629 return match conditional.child(0)?.kind() {
11630 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11631 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11632 _ => None,
11633 };
11634 }
11635 let condition = conditional.child_by_field_name("condition")?;
11636 simple_preprocessor_expression_guard(condition, source).or_else(|| {
11637 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11638 node_text(condition, source),
11639 )))
11640 })
11641}
11642
11643fn simple_preprocessor_expression_guard(
11644 expression: Node<'_>,
11645 source: &str,
11646) -> Option<PreprocessorGuard> {
11647 match expression.kind() {
11648 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11649 node_text(expression, source).to_string(),
11650 ))),
11651 "number_literal" => match node_text(expression, source).trim() {
11652 "0" => Some(PreprocessorGuard::Constant(false)),
11653 "1" => Some(PreprocessorGuard::Constant(true)),
11654 _ => None,
11655 },
11656 "preproc_defined" => {
11657 let identifier = (0..expression.named_child_count())
11658 .filter_map(|index| expression.named_child(index))
11659 .find(|child| child.kind() == "identifier")?;
11660 Some(PreprocessorGuard::Defined(
11661 node_text(identifier, source).to_string(),
11662 ))
11663 }
11664 "unary_expression"
11665 if expression
11666 .child_by_field_name("operator")
11667 .is_some_and(|operator| operator.kind() == "!") =>
11668 {
11669 simple_preprocessor_expression_guard(
11670 expression.child_by_field_name("argument")?,
11671 source,
11672 )
11673 .map(|guard| guard.negated())
11674 }
11675 "parenthesized_expression" => (0..expression.named_child_count())
11676 .filter_map(|index| expression.named_child(index))
11677 .next()
11678 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11679 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11680 expression, source,
11681 ))),
11682 _ => None,
11683 }
11684}
11685
11686fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11687 match expression.kind() {
11688 "number_literal" => match node_text(expression, source).trim() {
11689 "0" => BooleanGuardExpression::Constant(false),
11690 "1" => BooleanGuardExpression::Constant(true),
11691 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11692 expression, source,
11693 ))),
11694 },
11695 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11696 "preproc_defined" => {
11697 let identifier = (0..expression.named_child_count())
11698 .filter_map(|index| expression.named_child(index))
11699 .find(|child| child.kind() == "identifier");
11700 identifier.map_or_else(
11701 || {
11702 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11703 expression, source,
11704 )))
11705 },
11706 |identifier| {
11707 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11708 },
11709 )
11710 }
11711 "unary_expression"
11712 if expression
11713 .child_by_field_name("operator")
11714 .is_some_and(|operator| operator.kind() == "!") =>
11715 {
11716 expression.child_by_field_name("argument").map_or_else(
11717 || {
11718 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11719 expression, source,
11720 )))
11721 },
11722 |argument| boolean_preprocessor_expression(argument, source).negated(),
11723 )
11724 }
11725 "parenthesized_expression" => (0..expression.named_child_count())
11726 .filter_map(|index| expression.named_child(index))
11727 .next()
11728 .map_or_else(
11729 || {
11730 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11731 expression, source,
11732 )))
11733 },
11734 |child| boolean_preprocessor_expression(child, source),
11735 ),
11736 "binary_expression" => {
11737 let operands = || {
11738 Some((
11739 boolean_preprocessor_expression(
11740 expression.child_by_field_name("left")?,
11741 source,
11742 ),
11743 boolean_preprocessor_expression(
11744 expression.child_by_field_name("right")?,
11745 source,
11746 ),
11747 ))
11748 };
11749 match expression
11750 .child_by_field_name("operator")
11751 .map(|operator| operator.kind())
11752 {
11753 Some("&&") => operands().map_or_else(
11754 || {
11755 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11756 expression, source,
11757 )))
11758 },
11759 |(left, right)| BooleanGuardExpression::all([left, right]),
11760 ),
11761 Some("||") => operands().map_or_else(
11762 || {
11763 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11764 expression, source,
11765 )))
11766 },
11767 |(left, right)| BooleanGuardExpression::any([left, right]),
11768 ),
11769 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11770 expression, source,
11771 ))),
11772 }
11773 }
11774 _ => {
11775 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
11776 }
11777 }
11778}
11779
11780fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
11781 if targets.len() == 1 {
11782 targets.pop()
11783 } else {
11784 None
11785 }
11786}
11787
11788fn nameable_callable_declaration_nodes<'tree>(
11797 analyzer: &CppGraphSource<'_>,
11798 prepared: &'tree PreparedSyntaxTree,
11799 candidate: &CodeUnit,
11800) -> Vec<Node<'tree>> {
11801 callable_declaration_nodes(analyzer, prepared, candidate)
11802 .into_iter()
11803 .filter(|declaration| {
11804 let mut ancestor = declaration.parent();
11805 while let Some(node) = ancestor {
11806 if node.kind() == "function_definition"
11807 && is_recovered_declaration_scope_container(node, prepared.source())
11808 {
11809 ancestor = node.parent();
11810 continue;
11811 }
11812 if node.kind() == "compound_statement"
11813 && node.parent().is_some_and(|parent| {
11814 is_recovered_declaration_scope_container(parent, prepared.source())
11815 })
11816 {
11817 ancestor = node.parent().and_then(|parent| parent.parent());
11818 continue;
11819 }
11820 if matches!(
11821 node.kind(),
11822 "compound_statement" | "function_definition" | "lambda_expression"
11823 ) {
11824 return false;
11825 }
11826 ancestor = node.parent();
11827 }
11828 true
11829 })
11830 .collect()
11831}
11832
11833fn callable_declaration_nodes<'tree>(
11834 analyzer: &CppGraphSource<'_>,
11835 prepared: &'tree PreparedSyntaxTree,
11836 candidate: &CodeUnit,
11837) -> Vec<Node<'tree>> {
11838 let root = prepared.tree().root_node();
11839 analyzer
11840 .ranges(candidate)
11841 .into_iter()
11842 .filter_map(|range| {
11843 let mut declaration =
11844 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11845 while !matches!(
11858 declaration.kind(),
11859 "declaration" | "field_declaration" | "function_definition"
11860 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
11861 declaration,
11862 prepared.source(),
11863 ) {
11864 let Some(parent) = declaration.parent() else {
11865 break;
11866 };
11867 declaration = parent;
11868 }
11869 Some(declaration)
11870 })
11871 .collect()
11872}
11873
11874fn real_function_definition_ancestor<'tree>(
11875 node: Node<'tree>,
11876 source: &str,
11877) -> Option<Node<'tree>> {
11878 let mut ancestor = node.parent();
11879 while let Some(node) = ancestor {
11880 if node.kind() == "function_definition"
11881 && !is_recovered_declaration_scope_container(node, source)
11882 {
11883 return Some(node);
11884 }
11885 ancestor = node.parent();
11886 }
11887 None
11888}
11889
11890fn callable_declaration_activation_in_file(
11891 analyzer: &CppGraphSource<'_>,
11892 prepared: &PreparedSyntaxTree,
11893 candidate: &CodeUnit,
11894 reference: &CallableReferenceContext<'_>,
11895) -> Option<usize> {
11896 nameable_callable_declaration_nodes(analyzer, prepared, candidate)
11897 .into_iter()
11898 .filter(|declaration| {
11899 callable_preprocessor_context_is_visible_for_reference(
11900 *declaration,
11901 prepared.source(),
11902 reference,
11903 )
11904 })
11905 .map(callable_declaration_activation_byte)
11906 .min()
11907}
11908
11909fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
11914 if declaration.kind() != "function_definition" {
11915 return declaration.end_byte();
11916 }
11917 declaration
11918 .child_by_field_name("declarator")
11919 .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
11920}
11921
11922struct CallableReferenceContext<'a> {
11928 file: &'a ProjectFile,
11929 position: Option<CallableReferencePosition<'a>>,
11930}
11931
11932struct CallableReferencePosition<'a> {
11936 prepared: &'a PreparedSyntaxTree,
11937 byte: usize,
11938 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
11939}
11940
11941impl CallableReferenceContext<'_> {
11942 fn is_c(&self) -> bool {
11943 self.file
11944 .rel_path()
11945 .extension()
11946 .and_then(|extension| extension.to_str())
11947 == Some("c")
11948 }
11949
11950 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
11951 let position = self.position.as_ref()?;
11952 position
11953 .guards
11954 .get_or_init(|| {
11955 position
11956 .prepared
11957 .tree()
11958 .root_node()
11959 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
11960 .and_then(|node| {
11961 preprocessor_guard_environment(node, position.prepared.source())
11962 })
11963 })
11964 .as_ref()
11965 }
11966}
11967
11968fn callable_declaration_guard_requirements(
11980 node: Node<'_>,
11981 source: &str,
11982 reference: &CallableReferenceContext<'_>,
11983) -> Option<HashSet<PreprocessorGuard>> {
11984 let reference_is_c = reference.is_c();
11985 let mut required = HashSet::default();
11986 let mut ancestor = node.parent();
11987 while let Some(conditional) = ancestor {
11988 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11989 && !is_file_covering_include_guard(conditional, source)
11990 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11991 && preprocessor_conditional_contains_descendant(conditional, node)
11992 {
11993 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11994 match guard {
11995 PreprocessorGuard::Constant(true) => {}
11996 PreprocessorGuard::Constant(false) => return None,
11997 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
11998 if reference_is_c {
11999 return None;
12000 }
12001 }
12002 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
12003 if !reference_is_c {
12004 return None;
12005 }
12006 }
12007 guard => {
12008 required.insert(guard);
12009 }
12010 }
12011 }
12012 ancestor = conditional.parent();
12013 }
12014 Some(required)
12015}
12016
12017fn callable_preprocessor_context_is_visible_for_reference(
12028 node: Node<'_>,
12029 source: &str,
12030 reference: &CallableReferenceContext<'_>,
12031) -> bool {
12032 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12033 return false;
12034 };
12035 if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12036 return true;
12037 }
12038 reference.position.as_ref().is_some_and(|position| {
12039 guards_compatible_at_reference(&required, reference.guards())
12040 && declaration_follows_completed_family_before_reference(node, source, position.byte)
12041 })
12042}
12043
12044fn flattened_macro_namespace_declaration_matches(
12045 analyzer: &CppGraphSource<'_>,
12046 cpp: &dyn CppSource,
12047 reference_file: &ProjectFile,
12048 visible_declaration: &CodeUnit,
12049 qualified_candidate: &CodeUnit,
12050 reference_byte: usize,
12051) -> bool {
12052 if visible_declaration.kind() != qualified_candidate.kind()
12058 || visible_declaration.identifier() != qualified_candidate.identifier()
12059 || visible_declaration.signature() != qualified_candidate.signature()
12060 || !visible_declaration.package_name().is_empty()
12061 || qualified_candidate.package_name().is_empty()
12062 {
12063 return false;
12064 }
12065
12066 let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
12067 return false;
12068 };
12069 let root = prepared.tree().root_node();
12070 let closing_brace_limit = if visible_declaration.source() == reference_file {
12071 reference_byte
12072 } else {
12073 usize::MAX
12074 };
12075
12076 analyzer
12077 .ranges(visible_declaration)
12078 .into_iter()
12079 .any(|range| {
12080 let Some(mut declaration) =
12081 root.descendant_for_byte_range(range.start_byte, range.end_byte)
12082 else {
12083 return false;
12084 };
12085 while !matches!(
12086 declaration.kind(),
12087 "declaration" | "field_declaration" | "function_definition"
12088 ) {
12089 let Some(parent) = declaration.parent() else {
12090 return false;
12091 };
12092 declaration = parent;
12093 }
12094 if declaration
12095 .parent()
12096 .is_none_or(|parent| parent.kind() != "translation_unit")
12097 || !macro_displaced_cpp_return_type(declaration, prepared.source())
12098 {
12099 return false;
12100 }
12101
12102 let mut cursor = root.walk();
12103 root.named_children(&mut cursor).any(|sibling| {
12104 sibling.start_byte() >= declaration.end_byte()
12105 && sibling.start_byte() < closing_brace_limit
12106 && direct_unmatched_closing_brace(sibling)
12107 })
12108 })
12109}
12110
12111fn flattened_macro_namespace_components(
12112 declaration: Node<'_>,
12113 source: &str,
12114) -> Option<Vec<String>> {
12115 flattened_macro_function_namespace_components(declaration, source)
12116 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12117}
12118
12119fn flattened_macro_function_namespace_components(
12120 declaration: Node<'_>,
12121 source: &str,
12122) -> Option<Vec<String>> {
12123 let body = declaration
12124 .parent()
12125 .filter(|parent| parent.kind() == "compound_statement")?;
12126 let function = body.parent()?;
12127 if function.child_by_field_name("body") != Some(body) {
12128 return None;
12129 }
12130 let namespace_name = recovered_macro_namespace_name(function, source)?;
12131 let mut components = enclosing_namespace_components(declaration, source)?;
12132 components.push(namespace_name);
12133 Some(components)
12134}
12135
12136fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12147 if function.kind() != "function_definition" || !function.has_error() {
12148 return None;
12149 }
12150 let body = function
12151 .child_by_field_name("body")
12152 .filter(|body| body.kind() == "compound_statement")?;
12153 let mut cursor = function.walk();
12154 let prefix = function
12155 .named_children(&mut cursor)
12156 .take_while(|child| child.start_byte() < body.start_byte())
12157 .filter(|child| child.kind() != "comment")
12158 .collect::<Vec<_>>();
12159 let begin_index = prefix.iter().rposition(|child| {
12160 flattened_macro_sentinel_name(*child, source)
12161 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12162 })?;
12163 let mut identifiers = Vec::new();
12164 let mut stack = prefix[begin_index + 1..]
12165 .iter()
12166 .rev()
12167 .copied()
12168 .collect::<Vec<_>>();
12169 while let Some(current) = stack.pop() {
12170 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12171 identifiers.push(identifier);
12172 continue;
12173 }
12174 let mut cursor = current.walk();
12175 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12176 stack.extend(children.into_iter().rev());
12177 }
12178 let [keyword, namespace_name] = identifiers.as_slice() else {
12179 return None;
12180 };
12181 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12182 {
12183 return None;
12184 }
12185 let mut next = function.next_named_sibling();
12186 let next = loop {
12187 let candidate = next?;
12188 next = candidate.next_named_sibling();
12189 if candidate.kind() != "comment" {
12190 break candidate;
12191 }
12192 };
12193 flattened_macro_sentinel_name(next, source)
12194 .is_some_and(|name| is_namespace_end_sentinel(&name))
12195 .then(|| namespace_name.clone())
12196}
12197
12198fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12203 crate::declarations::is_recovered_exported_class_container(node, source)
12204 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12205 node, source,
12206 )
12207 || recovered_macro_namespace_name(node, source).is_some()
12208}
12209
12210fn flattened_macro_error_namespace_components(
12211 declaration: Node<'_>,
12212 source: &str,
12213) -> Option<Vec<String>> {
12214 let parent = declaration
12215 .parent()
12216 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12217 let mut cursor = parent.walk();
12218 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12219 let declaration_index = siblings
12220 .iter()
12221 .position(|candidate| same_node(*candidate, declaration))?;
12222 let begin_index = (0..declaration_index).rev().find(|index| {
12223 flattened_macro_sentinel_name(siblings[*index], source)
12224 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12225 })?;
12226
12227 let significant = siblings[begin_index + 1..declaration_index]
12228 .iter()
12229 .copied()
12230 .filter(|node| node.kind() != "comment")
12231 .collect::<Vec<_>>();
12232 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12233 return None;
12234 };
12235 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12236 return None;
12237 }
12238 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12239 if significant[2..].iter().any(|node| {
12240 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12241 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12242 })
12243 }) {
12244 return None;
12245 }
12246
12247 let mut saw_namespace_close = false;
12248 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12249 if sibling.kind() == "comment" {
12250 continue;
12251 }
12252 if !saw_namespace_close {
12253 if direct_unmatched_closing_brace(sibling) {
12254 saw_namespace_close = true;
12255 continue;
12256 }
12257 if flattened_macro_sentinel_name(sibling, source).is_some() {
12258 return None;
12259 }
12260 continue;
12261 }
12262 if !flattened_macro_sentinel_name(sibling, source)
12263 .is_some_and(|name| is_namespace_end_sentinel(&name))
12264 {
12265 return None;
12266 }
12267 let mut components = enclosing_namespace_components(declaration, source)?;
12268 components.push(namespace_name);
12269 return Some(components);
12270 }
12271 None
12272}
12273
12274fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12275 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12279 node.named_child(0)?
12280 } else {
12281 node
12282 };
12283 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12284 node.child_by_field_name("type")
12285 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12286 })?;
12287 (cpp_export_macro_token(&candidate)
12288 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12289 .then_some(candidate)
12290}
12291
12292fn is_namespace_begin_sentinel(name: &str) -> bool {
12295 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
12296}
12297
12298fn is_namespace_end_sentinel(name: &str) -> bool {
12299 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
12300}
12301
12302fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12303 if node.kind() != "ERROR" || node.named_child_count() != 1 {
12304 return None;
12305 }
12306 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12307 (!cpp_export_macro_token(&name)).then_some(name)
12308}
12309
12310fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12311 if !matches!(
12312 node.kind(),
12313 "identifier" | "namespace_identifier" | "type_identifier"
12314 ) {
12315 return None;
12316 }
12317 let name = normalize_cpp_whitespace(node_text(node, source));
12318 (!name.is_empty()).then_some(name)
12319}
12320
12321fn guard_requirement_sets_match(
12322 left: &[(usize, HashSet<PreprocessorGuard>)],
12323 right: &[(usize, HashSet<PreprocessorGuard>)],
12324) -> bool {
12325 left.len() == right.len()
12326 && left.iter().all(|(_, left_guards)| {
12327 right
12328 .iter()
12329 .any(|(_, right_guards)| left_guards == right_guards)
12330 })
12331 && right.iter().all(|(_, right_guards)| {
12332 left.iter()
12333 .any(|(_, left_guards)| right_guards == left_guards)
12334 })
12335}
12336
12337fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
12338 let Some(type_node) = declaration.child_by_field_name("type") else {
12339 return false;
12340 };
12341 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
12342 !type_name.is_empty()
12343 && type_name
12344 .chars()
12345 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
12346 && (0..declaration.named_child_count()).any(|index| {
12347 declaration
12348 .named_child(index)
12349 .is_some_and(|child| child.kind() == "ERROR")
12350 })
12351}
12352
12353fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12354 node.kind() == "ERROR"
12355 && (0..node.child_count())
12356 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12357}
12358
12359pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12360 let mut ancestor = node.parent();
12361 while let Some(parent) = ancestor {
12362 if is_preprocessor_conditional(parent)
12363 && !is_file_covering_include_guard(parent, source)
12364 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12365 {
12366 return false;
12367 }
12368 ancestor = parent.parent();
12369 }
12370 true
12371}
12372
12373fn is_split_cpp_language_linkage_wrapper(
12374 conditional: Node<'_>,
12375 descendant: Node<'_>,
12376 source: &str,
12377) -> bool {
12378 if conditional.child_by_field_name("alternative").is_some()
12379 || !matches!(
12380 simple_preprocessor_guard(conditional, source),
12381 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12382 )
12383 {
12384 return false;
12385 }
12386 let mut current = descendant.parent();
12387 let linkage = loop {
12388 let Some(node) = current else {
12389 return false;
12390 };
12391 if node == conditional {
12392 return false;
12393 }
12394 if node.kind() == "linkage_specification" {
12395 break node;
12396 }
12397 current = node.parent();
12398 };
12399 if linkage
12400 .child_by_field_name("value")
12401 .is_none_or(|value| node_text(value, source) != "\"C\"")
12402 {
12403 return false;
12404 }
12405 let Some(body) = linkage.child_by_field_name("body") else {
12406 return false;
12407 };
12408 let closes_opening_branch = (0..body.named_child_count())
12409 .filter_map(|index| body.named_child(index))
12410 .take_while(|child| child.end_byte() <= descendant.start_byte())
12411 .any(|child| {
12412 child.kind() == "preproc_call"
12413 && child
12414 .child_by_field_name("directive")
12415 .is_some_and(|directive| node_text(directive, source) == "#endif")
12416 });
12417 let reopens_for_closing_brace = (0..body.named_child_count())
12418 .filter_map(|index| body.named_child(index))
12419 .skip_while(|child| child.start_byte() < descendant.end_byte())
12420 .any(|child| {
12421 matches!(
12422 simple_preprocessor_guard(child, source),
12423 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12424 ) && (0..child.child_count()).any(|index| {
12425 child
12426 .child(index)
12427 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
12428 })
12429 });
12430 closes_opening_branch && reopens_for_closing_brace
12431}
12432
12433pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12437 node.child_by_field_name("arguments")
12438 .or_else(|| node.child_by_field_name("parameters"))
12439 .or_else(|| node.child_by_field_name("value"))
12440 .or_else(|| first_named_child_of_kind(node, "argument_list"))
12441 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12442}
12443
12444pub fn call_arity(node: Node<'_>) -> usize {
12445 call_arguments_node(node)
12446 .map(|args| argument_children(args).count())
12447 .unwrap_or(0)
12448}
12449
12450pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12451 let recovered_block_arguments = recovered_block_literal_arguments(node);
12452 (0..node.child_count())
12453 .filter_map(move |index| node.child(index))
12454 .filter(|child| child.is_named() && !child.is_extra())
12455 .flat_map(move |child| {
12456 if let Some((raw, left, right)) = recovered_block_arguments
12457 && child == raw
12458 {
12459 [Some(left), Some(right)]
12460 } else {
12461 [Some(child), None]
12462 }
12463 })
12464 .flatten()
12465}
12466
12467pub fn recovered_c_new_expression_arguments(
12476 node: Node<'_>,
12477 uses_c_semantics: bool,
12478) -> Option<[Node<'_>; 2]> {
12479 if !uses_c_semantics || node.kind() != "new_expression" {
12480 return None;
12481 }
12482 let parent = node.parent()?;
12483 if parent.kind() != "argument_list" {
12484 return None;
12485 }
12486 let keyword = node.child(0)?;
12487 let error = node.child(1)?;
12488 let trailing = node.child(2)?;
12489 if node.child(3).is_some()
12490 || keyword.kind() != "new"
12491 || keyword.is_named()
12492 || keyword.child_count() != 0
12493 || error.kind() != "ERROR"
12494 || !error.is_extra()
12495 || error.child_count() != 1
12496 || error.child(0).is_none_or(|comma| comma.kind() != ",")
12497 || node.child_by_field_name("type") != Some(trailing)
12498 || trailing.kind() != "type_identifier"
12499 {
12500 return None;
12501 }
12502 Some([keyword, trailing])
12503}
12504
12505pub fn recovered_c_new_expression_argument_at(
12508 mut node: Node<'_>,
12509 start_byte: usize,
12510 end_byte: usize,
12511 uses_c_semantics: bool,
12512) -> Option<Node<'_>> {
12513 if !uses_c_semantics {
12519 return None;
12520 }
12521 loop {
12522 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12523 return arguments.into_iter().find(|argument| {
12524 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12525 });
12526 }
12527 node = node.parent()?;
12528 }
12529}
12530
12531fn recovered_c_keyword_argument_count(
12532 file: &ProjectFile,
12533 call: Node<'_>,
12534 arguments: Node<'_>,
12535 source: &str,
12536) -> usize {
12537 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12542 return 0;
12543 }
12544 let mut ancestor = Some(call);
12545 let function = loop {
12546 let Some(current) = ancestor else {
12547 return 0;
12548 };
12549 if current.kind() == "function_definition" {
12550 break current;
12551 }
12552 ancestor = current.parent();
12553 };
12554 let Some(parameters) = function
12555 .child_by_field_name("declarator")
12556 .and_then(|declarator| declarator.child_by_field_name("parameters"))
12557 else {
12558 return 0;
12559 };
12560 let displaced_parameter_keywords = (0..parameters.child_count())
12561 .filter_map(|index| parameters.child(index))
12562 .filter(|error| error.kind() == "ERROR")
12563 .filter_map(|error| {
12564 let parameter = error.prev_named_sibling()?;
12565 if parameter.kind() != "parameter_declaration"
12566 || parameter.end_byte() != error.start_byte()
12567 || extract_variable_name(parameter, source).is_some()
12568 {
12569 return None;
12570 }
12571 let mut children = (0..error.child_count())
12572 .filter_map(|index| error.child(index))
12573 .filter(|child| !child.is_extra() && !child.is_missing());
12574 let keyword = children.next()?;
12575 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12576 .then_some(keyword)
12577 })
12578 .collect::<Vec<_>>();
12579 if displaced_parameter_keywords.is_empty() {
12580 return 0;
12581 }
12582
12583 (0..arguments.child_count())
12584 .filter_map(|index| arguments.child(index))
12585 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12586 .filter(|error| {
12587 let mut children = (0..error.child_count())
12588 .filter_map(|index| error.child(index))
12589 .filter(|child| !child.is_extra() && !child.is_missing());
12590 let Some(comma) = children.next() else {
12591 return false;
12592 };
12593 let Some(keyword) = children.next() else {
12594 return false;
12595 };
12596 children.next().is_none()
12597 && comma.kind() == ","
12598 && !keyword.is_named()
12599 && keyword.child_count() == 0
12600 && displaced_parameter_keywords
12601 .iter()
12602 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12603 })
12604 .count()
12605}
12606
12607fn recovered_block_literal_arguments<'tree>(
12608 arguments: Node<'tree>,
12609) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12610 if arguments.kind() != "argument_list" {
12611 return None;
12612 }
12613 let mut raw_arguments = (0..arguments.child_count())
12614 .filter_map(|index| arguments.child(index))
12615 .filter(|child| child.is_named() && !child.is_extra());
12616 let raw = raw_arguments.next()?;
12617 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12618 return None;
12619 }
12620
12621 let left = raw.child_by_field_name("left")?;
12622 if left.is_missing() || left.start_byte() == left.end_byte() {
12623 return None;
12624 }
12625 let right = raw.child_by_field_name("right")?;
12626 if right.kind() != "compound_literal_expression"
12627 || right.is_missing()
12628 || right
12629 .child_by_field_name("type")
12630 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12631 || right
12632 .child_by_field_name("value")
12633 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12634 {
12635 return None;
12636 }
12637 let has_intervening_error = (0..raw.child_count())
12638 .filter_map(|index| raw.child(index))
12639 .any(|child| {
12640 child.kind() == "ERROR"
12641 && !child.is_missing()
12642 && child.start_byte() >= left.end_byte()
12643 && child.end_byte() <= right.start_byte()
12644 });
12645 has_intervening_error.then_some((raw, left, right))
12646}
12647
12648pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12649 match node.kind() {
12650 "new_expression" => node
12651 .child_by_field_name("type")
12652 .or_else(|| node.named_child(0)),
12653 "compound_literal_expression" => node.child_by_field_name("type"),
12654 "call_expression" => node.child_by_field_name("function"),
12655 _ => None,
12656 }
12657}
12658
12659pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12665 if node.kind() != "cast_expression" {
12666 return None;
12667 }
12668 let descriptor = node.child_by_field_name("type")?;
12669 if descriptor.kind() == "type_descriptor" {
12670 descriptor.child_by_field_name("type")
12671 } else {
12672 Some(descriptor)
12673 }
12674}
12675
12676pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
12691 debug_assert_eq!(field.kind(), "field_expression");
12692 let operator = field.child_by_field_name("operator")?;
12693 let mut cursor = field.walk();
12694 let receiver = field
12695 .named_children(&mut cursor)
12696 .filter(|child| child.end_byte() <= operator.start_byte())
12697 .last()?;
12698 if receiver.kind() != "ERROR" {
12699 return Some(receiver);
12700 }
12701 (receiver.named_child_count() == 1)
12702 .then(|| receiver.named_child(0))
12703 .flatten()
12704}
12705
12706pub fn field_initializer_constructs_target(
12707 node: Node<'_>,
12708 ctx: &ScanCtx<'_>,
12709 owner: &CodeUnit,
12710) -> bool {
12711 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
12720 return qualified_base_initializer_constructs_target(node, ctx, owner);
12721 }
12722 let Some(name) = node
12723 .child_by_field_name("name")
12724 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
12725 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
12726 else {
12727 return false;
12728 };
12729 let field_name = node_text(name, ctx.source);
12730 ctx.visibility
12731 .visible_identifier_candidates(ctx.file, field_name)
12732 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
12733 .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
12734}
12735
12736fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
12746 ctx.visibility
12747 .field_declared_type_fact(&ctx.analyzer, unit)
12748 .is_none_or(|fact| !fact.binds_indirectly)
12749}
12750
12751fn qualified_base_initializer_constructs_target(
12752 node: Node<'_>,
12753 ctx: &ScanCtx<'_>,
12754 owner: &CodeUnit,
12755) -> bool {
12756 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
12757 return false;
12758 };
12759 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
12760 return false;
12761 };
12762 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
12763 return false;
12764 };
12765 let resolves_target = |components: &[String]| {
12766 matches!(
12767 ctx.visibility.resolve_type_components_lexically_for_target(
12768 &ctx.analyzer,
12769 ctx.file,
12770 components,
12771 is_globally_qualified_cpp_name(qualified),
12772 &lexical_scope,
12773 owner,
12774 ),
12775 LexicalTypeResolution::Resolved { unit, .. }
12776 if same_visible_symbol(&unit, owner)
12777 )
12778 };
12779 if resolves_target(&components) {
12780 return true;
12781 }
12782
12783 components
12789 .last()
12790 .is_some_and(|terminal| terminal == owner.identifier())
12791 && resolves_target(&components[..components.len() - 1])
12792}
12793
12794fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12795 unit.signature()
12796 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
12797 || ctx
12798 .analyzer
12799 .get_source(unit, false)
12800 .is_some_and(|declaration| {
12801 field_declaration_type_matches(&declaration, unit, ctx, owner)
12802 })
12803}
12804
12805pub fn field_declared_binding(
12806 analyzer: &CppGraphSource<'_>,
12807 visibility: &VisibilityIndex<'_>,
12808 visible_from: &ProjectFile,
12809 field: &CodeUnit,
12810) -> Option<CppScanBinding> {
12811 let fact = visibility.field_declared_type_fact(analyzer, field)?;
12812 let normalized = normalize_field_type_text(&fact.type_text);
12813 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
12814 analyzer,
12815 visible_from,
12816 field,
12817 &normalized,
12818 );
12819 let resolved = match (resolved, fact.template_arguments.as_deref()) {
12820 (Some(primary), Some(arguments)) => visibility
12821 .resolve_template_arguments(visible_from, primary, arguments)
12822 .ok(),
12823 (resolved, None) => resolved,
12824 (None, Some(_)) => None,
12825 }
12826 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
12827 Some(CppScanBinding::from_type_name(
12828 normalized,
12829 resolved,
12830 fact.indirection,
12831 ))
12832}
12833
12834fn anonymous_aggregate_field_owner(
12841 analyzer: &CppGraphSource<'_>,
12842 visibility: &VisibilityIndex<'_>,
12843 visible_from: &ProjectFile,
12844 field: &CodeUnit,
12845) -> Option<CodeUnit> {
12846 let owner = type_owner_of(analyzer, field)?;
12847 if !owner.is_class() {
12848 return None;
12849 }
12850 let declaration = analyzer.get_source(field, false)?;
12851 let mut parser = Parser::new();
12852 parser
12853 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12854 .ok()?;
12855 let tree = parser.parse(&declaration, None)?;
12856 let mut stack = vec![tree.root_node()];
12857 while let Some(node) = stack.pop() {
12858 if matches!(node.kind(), "declaration" | "field_declaration")
12859 && let Some(type_node) = node
12860 .child_by_field_name("type")
12861 .or_else(|| first_type_child(node))
12862 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12863 && type_node.child_by_field_name("name").is_none()
12864 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
12865 {
12866 let matches = visibility
12867 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12868 .into_iter()
12869 .filter(|child| child.is_class() && child.identifier() == field.identifier())
12870 .collect::<Vec<_>>();
12871 return match matches.as_slice() {
12872 [child] => Some((*child).clone()),
12873 _ => None,
12874 };
12875 }
12876 let mut cursor = node.walk();
12877 stack.extend(node.named_children(&mut cursor));
12878 }
12879 None
12880}
12881
12882pub fn anonymous_aggregate_owner(
12889 analyzer: &CppGraphSource<'_>,
12890 file: &ProjectFile,
12891 node: Node<'_>,
12892) -> Option<CodeUnit> {
12893 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12894 || node.child_by_field_name("name").is_some()
12895 {
12896 return None;
12897 }
12898 let mut candidates = analyzer
12899 .declarations(file)
12900 .into_iter()
12901 .filter(|candidate| {
12902 candidate.is_class()
12903 && analyzer.ranges(candidate).into_iter().any(|range| {
12904 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12905 })
12906 })
12907 .collect::<Vec<_>>();
12908 candidates.sort_by_key(|candidate| candidate.fq_name());
12909 candidates.dedup();
12910 match candidates.as_slice() {
12911 [candidate] => Some(candidate.clone()),
12912 _ => None,
12913 }
12914}
12915
12916fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12918 let Some(first) = candidates.first() else {
12919 return Err(TypeCandidateFailure::Unresolvable);
12920 };
12921 if candidates
12922 .iter()
12923 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12924 {
12925 Ok((*first).clone())
12926 } else {
12927 Err(TypeCandidateFailure::Ambiguous)
12928 }
12929}
12930
12931fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12932 logical_type_candidate(candidates).ok()
12933}
12934
12935fn unique_type_candidate_preserving_alias(
12936 analyzer: &CppGraphSource<'_>,
12937 file: &ProjectFile,
12938 candidates: &[&CodeUnit],
12939) -> Option<CodeUnit> {
12940 let first = *candidates.first()?;
12941 if declared_type_alias(analyzer, first) {
12942 return candidates
12943 .iter()
12944 .all(|candidate| {
12945 declared_type_alias(analyzer, candidate)
12946 && candidate.kind() == first.kind()
12947 && candidate.fq_name() == first.fq_name()
12948 && candidate.source() == first.source()
12949 })
12950 .then(|| first.clone());
12951 }
12952 if analyzer.reference_uses_c_semantics(file)
12953 && first.is_class()
12954 && indexed_c_tag_kind(analyzer, first).is_some()
12955 {
12956 let mut full_source = None;
12957 let mut tag_kind = None;
12958 for candidate in candidates.iter().copied() {
12959 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12960 if tag_kind
12961 .replace(candidate_tag_kind)
12962 .is_some_and(|existing| existing != candidate_tag_kind)
12963 {
12964 return None;
12965 }
12966 if cpp_class_declaration_strength(analyzer, candidate)
12967 == CppClassDeclarationStrength::Full
12968 && full_source
12969 .replace(candidate.source())
12970 .is_some_and(|existing| existing != candidate.source())
12971 {
12972 return None;
12973 }
12974 }
12975 }
12976 candidates
12977 .iter()
12978 .all(|candidate| {
12979 !declared_type_alias(analyzer, candidate)
12980 && candidate.kind() == first.kind()
12981 && candidate.fq_name() == first.fq_name()
12982 })
12983 .then(|| first.clone())
12984}
12985
12986fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
12987 is_type_alias(unit)
12988 || analyzer
12989 .type_alias_provider()
12990 .is_some_and(|provider| provider.is_type_alias(unit))
12991}
12992
12993pub fn field_declared_type_binding(
12994 analyzer: &CppGraphSource<'_>,
12995 visibility: &VisibilityIndex<'_>,
12996 visible_from: &ProjectFile,
12997 field: &CodeUnit,
12998) -> Option<(String, Option<CodeUnit>, i32)> {
12999 let fact = visibility.field_declared_type_fact(analyzer, field)?;
13000 let normalized = normalize_field_type_text(&fact.type_text);
13001 let primary = visibility.resolve_unique_canonical_type_for_declaration(
13002 analyzer,
13003 visible_from,
13004 field,
13005 &normalized,
13006 );
13007 let resolved = match (primary, fact.template_arguments.as_deref()) {
13008 (Some(primary), Some(arguments)) => visibility
13009 .resolve_template_arguments(visible_from, primary, arguments)
13010 .ok(),
13011 (resolved, None) => resolved,
13012 (None, Some(_)) => None,
13013 };
13014 Some((normalized, resolved, fact.indirection))
13015}
13016
13017fn decode_field_declared_type_fact(
13018 analyzer: &CppGraphSource<'_>,
13019 field: &CodeUnit,
13020) -> Option<DeclaredFieldTypeFact> {
13021 let Some(declaration) = analyzer.get_source(field, false) else {
13022 return decode_indexed_field_declared_type_fact(analyzer, field);
13023 };
13024 let mut parser = Parser::new();
13025 parser
13026 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13027 .ok()?;
13028 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13032 let contextual_tree = parser.parse(&contextual_declaration, None)?;
13033 let mut stack = vec![contextual_tree.root_node()];
13034 while let Some(node) = stack.pop() {
13035 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13036 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13037 {
13038 return Some(DeclaredFieldTypeFact {
13039 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13040 indirection: recovered.pointer_depth(),
13041 binds_indirectly: recovered.pointer_depth() > 0,
13042 template_arguments: None,
13043 });
13044 }
13045 if let Some(recovered) =
13046 recovered_function_like_field_declarator(node, &contextual_declaration)
13047 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13048 {
13049 let type_node = node
13050 .child_by_field_name("type")
13051 .or_else(|| first_type_child(node))?;
13052 return Some(DeclaredFieldTypeFact {
13053 type_text: node_text(type_node, &contextual_declaration).to_string(),
13054 indirection: recovered.pointer_depth(),
13055 binds_indirectly: true,
13058 template_arguments: cpp_template_reference_arguments(
13059 type_node,
13060 &contextual_declaration,
13061 ),
13062 });
13063 }
13064 if let Some(fact) =
13065 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13066 {
13067 return Some(fact);
13068 }
13069 let mut cursor = node.walk();
13070 stack.extend(node.named_children(&mut cursor));
13071 }
13072 let tree = parser.parse(&declaration, None)?;
13073 let mut stack = vec![tree.root_node()];
13074 while let Some(node) = stack.pop() {
13075 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13076 {
13077 return Some(fact);
13078 }
13079 let mut cursor = node.walk();
13080 stack.extend(node.named_children(&mut cursor));
13081 }
13082 None
13083}
13084
13085fn decode_indexed_field_declared_type_fact(
13090 analyzer: &CppGraphSource<'_>,
13091 field: &CodeUnit,
13092) -> Option<DeclaredFieldTypeFact> {
13093 let cpp = analyzer.cpp?;
13094 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13095 let source = prepared.source();
13096 let root = prepared.tree().root_node();
13097 for range in analyzer.ranges(field) {
13098 let end = range.start_byte.saturating_add(1).min(source.len());
13099 let mut current = root.descendant_for_byte_range(range.start_byte, end);
13100 while let Some(node) = current {
13101 if matches!(node.kind(), "declaration" | "field_declaration")
13102 && let Some(fact) =
13103 decode_declared_field_type_node(node, field.identifier(), source)
13104 {
13105 return Some(fact);
13106 }
13107 current = node.parent();
13108 }
13109 }
13110 None
13111}
13112
13113fn decode_declared_field_type_node(
13114 node: Node<'_>,
13115 field_name: &str,
13116 source: &str,
13117) -> Option<DeclaredFieldTypeFact> {
13118 if !matches!(node.kind(), "declaration" | "field_declaration") {
13119 return None;
13120 }
13121 let type_node = node
13122 .child_by_field_name("type")
13123 .or_else(|| first_type_child(node))?;
13124 let binding = declared_name_binding(node, type_node, field_name, source)?;
13125 let declared_type = if matches!(
13126 type_node.kind(),
13127 "class_specifier" | "struct_specifier" | "union_specifier"
13128 ) {
13129 type_node.child_by_field_name("name")
13130 } else {
13131 Some(type_node)
13132 };
13133 Some(DeclaredFieldTypeFact {
13134 type_text: declared_type.map_or_else(
13135 || field_name.to_string(),
13136 |declared_type| node_text(declared_type, source).to_string(),
13137 ),
13138 indirection: binding.pointer_depth,
13139 binds_indirectly: binding.indirect,
13140 template_arguments: declared_type
13141 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13142 })
13143}
13144
13145pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13159 let mut parser = Parser::new();
13160 parser
13161 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13162 .ok()?;
13163 let tree = parser.parse(declaration, None)?;
13164 let mut stack = vec![tree.root_node()];
13165 while let Some(node) = stack.pop() {
13166 let type_node = match node.kind() {
13167 "type_definition" => {
13168 let mut cursor = node.walk();
13169 if node
13170 .children_by_field_name("declarator", &mut cursor)
13171 .any(declarator_names_function_type)
13172 {
13173 return None;
13174 }
13175 node.child_by_field_name("type")?
13176 }
13177 "alias_declaration" => {
13178 let type_node = node.child_by_field_name("type")?;
13179 if type_node
13180 .child_by_field_name("declarator")
13181 .is_some_and(declarator_names_function_type)
13182 {
13183 return None;
13184 }
13185 type_node
13186 }
13187 _ => {
13188 let mut cursor = node.walk();
13189 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13190 stack.extend(children.into_iter().rev());
13191 continue;
13192 }
13193 };
13194 return Some(node_text(type_node, declaration).to_string());
13195 }
13196 None
13197}
13198
13199fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
13208 let mut parser = Parser::new();
13209 if parser
13210 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13211 .is_err()
13212 {
13213 return true;
13214 }
13215 let Some(tree) = parser.parse(declaration, None) else {
13216 return true;
13217 };
13218 let mut stack = vec![tree.root_node()];
13219 while let Some(node) = stack.pop() {
13220 let declarators = match node.kind() {
13221 "type_definition" => {
13222 let mut cursor = node.walk();
13223 node.children_by_field_name("declarator", &mut cursor)
13224 .collect::<Vec<_>>()
13225 }
13226 "alias_declaration" => node
13227 .child_by_field_name("type")
13228 .and_then(|type_node| type_node.child_by_field_name("declarator"))
13229 .into_iter()
13230 .collect::<Vec<_>>(),
13231 _ => {
13232 let mut cursor = node.walk();
13233 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13234 stack.extend(children.into_iter().rev());
13235 continue;
13236 }
13237 };
13238 return declarators.into_iter().any(cpp_declarator_adds_indirection);
13239 }
13240 true
13241}
13242
13243fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13249 let mut current = Some(declarator);
13250 while let Some(node) = current {
13251 match node.kind() {
13252 "function_declarator" | "abstract_function_declarator" => return true,
13253 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13254 current = node.named_child(0);
13255 }
13256 _ => current = node.child_by_field_name("declarator"),
13257 }
13258 }
13259 false
13260}
13261
13262pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
13266 let mut parser = Parser::new();
13267 if parser
13268 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13269 .is_err()
13270 {
13271 return false;
13272 }
13273 let Some(tree) = parser.parse(declaration, None) else {
13274 return false;
13275 };
13276 let mut stack = vec![tree.root_node()];
13277 while let Some(node) = stack.pop() {
13278 if matches!(node.kind(), "declaration" | "field_declaration") {
13279 let mut cursor = node.walk();
13280 if node
13281 .children_by_field_name("declarator", &mut cursor)
13282 .any(|declarator| {
13283 declarator_name_node(declarator).is_some_and(|name| {
13284 node_text(name, declaration) == field_name
13285 && declarator_names_function_type(declarator)
13286 })
13287 })
13288 {
13289 return true;
13290 }
13291 }
13292 let mut cursor = node.walk();
13293 stack.extend(node.named_children(&mut cursor));
13294 }
13295 false
13296}
13297
13298pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
13302 let mut parser = Parser::new();
13303 if parser
13304 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13305 .is_err()
13306 {
13307 return false;
13308 }
13309 let Some(tree) = parser.parse(declaration, None) else {
13310 return false;
13311 };
13312 let mut stack = vec![tree.root_node()];
13313 while let Some(node) = stack.pop() {
13314 match node.kind() {
13315 "type_definition" => {
13316 let mut cursor = node.walk();
13317 if node
13318 .children_by_field_name("declarator", &mut cursor)
13319 .any(|declarator| {
13320 extract_typedef_declarator_name(declarator, declaration)
13321 .is_some_and(|name| name == alias_name)
13322 && declarator_names_function_type(declarator)
13323 })
13324 {
13325 return true;
13326 }
13327 }
13328 "alias_declaration" => {
13329 let names_alias = node
13330 .child_by_field_name("name")
13331 .is_some_and(|name| node_text(name, declaration) == alias_name);
13332 if names_alias
13333 && node
13334 .child_by_field_name("type")
13335 .and_then(|type_node| type_node.child_by_field_name("declarator"))
13336 .is_some_and(declarator_names_function_type)
13337 {
13338 return true;
13339 }
13340 }
13341 _ => {}
13342 }
13343 let mut cursor = node.walk();
13344 stack.extend(node.named_children(&mut cursor));
13345 }
13346 false
13347}
13348
13349fn decode_structured_alias_target(
13350 analyzer: &CppGraphSource<'_>,
13351 unit: &CodeUnit,
13352) -> Option<StructuredAliasTarget> {
13353 analyzer
13354 .get_source(unit, false)
13355 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
13356 .or_else(|| {
13357 let signature = unit.signature()?;
13358 decode_structured_alias_target_source(unit, signature, false)
13359 })
13360}
13361
13362fn decode_structured_alias_target_source(
13363 unit: &CodeUnit,
13364 declaration: &str,
13365 require_top_level: bool,
13366) -> Option<StructuredAliasTarget> {
13367 let mut parser = Parser::new();
13368 parser
13369 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13370 .ok()?;
13371 let tree = parser.parse(declaration, None)?;
13372 let mut stack = vec![tree.root_node()];
13373 while let Some(node) = stack.pop() {
13374 let type_node = match node.kind() {
13375 "type_definition" => {
13376 if require_top_level
13377 && node
13378 .parent()
13379 .is_none_or(|parent| parent.kind() != "translation_unit")
13380 {
13381 let mut cursor = node.walk();
13382 stack.extend(node.named_children(&mut cursor));
13383 continue;
13384 }
13385 let mut declarator_cursor = node.walk();
13386 let declarator = node
13387 .children_by_field_name("declarator", &mut declarator_cursor)
13388 .find(|declarator| {
13389 extract_typedef_declarator_name(*declarator, declaration)
13390 .is_some_and(|name| name == unit.identifier())
13391 })?;
13392 if declarator_names_function_type(declarator) {
13393 return None;
13394 }
13395 node.child_by_field_name("type")?
13396 }
13397 "alias_declaration" => {
13398 if require_top_level
13399 && node
13400 .parent()
13401 .is_none_or(|parent| parent.kind() != "translation_unit")
13402 {
13403 let mut cursor = node.walk();
13404 stack.extend(node.named_children(&mut cursor));
13405 continue;
13406 }
13407 let name = node.child_by_field_name("name")?;
13408 if node_text(name, declaration) != unit.identifier() {
13409 return None;
13410 }
13411 let type_node = node.child_by_field_name("type")?;
13412 if type_node
13413 .child_by_field_name("declarator")
13414 .is_some_and(declarator_names_function_type)
13415 {
13416 return None;
13417 }
13418 type_node
13419 }
13420 _ => {
13421 let mut cursor = node.walk();
13422 stack.extend(node.named_children(&mut cursor));
13423 continue;
13424 }
13425 };
13426 return structured_alias_type_target(type_node, declaration);
13427 }
13428 None
13429}
13430
13431fn structured_alias_type_target(
13432 mut type_node: Node<'_>,
13433 source: &str,
13434) -> Option<StructuredAliasTarget> {
13435 while type_node.kind() == "type_descriptor" {
13436 type_node = type_node.child_by_field_name("type")?;
13437 }
13438 if type_node.kind() == "primitive_type" {
13439 return Some(StructuredAliasTarget::Builtin);
13440 }
13441 if matches!(
13442 type_node.kind(),
13443 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13444 ) {
13445 type_node = type_node.child_by_field_name("name")?;
13446 }
13447 let global = type_node.child_by_field_name("scope").is_none()
13448 && type_node.child(0).is_some_and(|child| child.kind() == "::");
13449 let mut components = Vec::new();
13450 append_structured_type_components(type_node, source, &mut components)?;
13451 let arguments = cpp_template_reference_arguments(type_node, source);
13452 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13453 components,
13454 global,
13455 arguments,
13456 })
13457}
13458
13459fn append_structured_type_components(
13460 node: Node<'_>,
13461 source: &str,
13462 out: &mut Vec<String>,
13463) -> Option<()> {
13464 match node.kind() {
13465 "identifier" | "namespace_identifier" | "type_identifier" => {
13466 out.push(node_text(node, source).to_string());
13467 Some(())
13468 }
13469 "template_type" => {
13470 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13471 }
13472 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13473 if let Some(scope) = node.child_by_field_name("scope") {
13474 append_structured_type_components(scope, source, out)?;
13475 }
13476 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13477 }
13478 _ => None,
13479 }
13480}
13481
13482pub(crate) struct DeclaredNameBinding {
13485 pub pointer_depth: i32,
13487 pub indirect: bool,
13491}
13492
13493pub(crate) fn declared_name_binding(
13494 declaration: Node<'_>,
13495 type_node: Node<'_>,
13496 field_name: &str,
13497 source: &str,
13498) -> Option<DeclaredNameBinding> {
13499 let mut stack = Vec::new();
13500 let mut cursor = declaration.walk();
13501 stack.extend(
13502 declaration
13503 .named_children(&mut cursor)
13504 .filter(|child| !same_node(*child, type_node)),
13505 );
13506 while let Some(node) = stack.pop() {
13507 if matches!(node.kind(), "identifier" | "field_identifier")
13508 && node_text(node, source) == field_name
13509 {
13510 let mut binding = DeclaredNameBinding {
13511 pointer_depth: 0,
13512 indirect: false,
13513 };
13514 let mut current = node.parent();
13515 while let Some(parent) = current {
13516 if same_node(parent, declaration) {
13517 return Some(binding);
13518 }
13519 match parent.kind() {
13520 "pointer_declarator" => {
13521 binding.pointer_depth += 1;
13522 binding.indirect = true;
13523 }
13524 "reference_declarator" => binding.indirect = true,
13525 _ => {}
13526 }
13527 current = parent.parent();
13528 }
13529 return None;
13530 }
13531 let mut cursor = node.walk();
13532 stack.extend(node.named_children(&mut cursor));
13533 }
13534 None
13535}
13536
13537fn field_declaration_type_matches(
13538 declaration: &str,
13539 unit: &CodeUnit,
13540 ctx: &ScanCtx<'_>,
13541 owner: &CodeUnit,
13542) -> bool {
13543 ctx.visibility
13544 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
13545 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
13546 let normalized = normalize_field_type_text(type_text);
13547 ctx.visibility
13548 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
13549 || ctx.visibility.resolves_to_type(
13550 &ctx.analyzer,
13551 ctx.file,
13552 normalized.as_str(),
13553 owner,
13554 )
13555 })
13556}
13557
13558fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
13559 let declaration = declaration
13560 .split(['=', ';'])
13561 .next()
13562 .unwrap_or(declaration)
13563 .trim();
13564 let index = declaration.rfind(field_name)?;
13565 let before = &declaration[..index];
13566 let after = &declaration[index + field_name.len()..];
13567 if before.chars().next_back().is_some_and(is_identifier_char)
13568 || after.chars().next().is_some_and(is_identifier_char)
13569 {
13570 return None;
13571 }
13572 Some(before.trim())
13573}
13574
13575fn normalize_field_type_text(type_text: &str) -> String {
13576 const FIELD_SPECIFIERS: [&str; 8] = [
13577 "extern ",
13578 "static ",
13579 "mutable ",
13580 "constexpr ",
13581 "constinit ",
13582 "inline ",
13583 "volatile ",
13584 "const ",
13585 ];
13586
13587 let mut normalized = normalize_type_text(type_text);
13588 loop {
13589 let Some(stripped) = FIELD_SPECIFIERS
13590 .iter()
13591 .find_map(|specifier| normalized.strip_prefix(specifier))
13592 else {
13593 return normalized;
13594 };
13595 normalized = normalize_type_text(stripped);
13596 }
13597}
13598
13599fn is_identifier_char(ch: char) -> bool {
13600 ch == '_' || ch.is_ascii_alphanumeric()
13601}
13602
13603pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13604 let Some(type_node) = node.child_by_field_name("type") else {
13605 return false;
13606 };
13607 ctx.visibility.resolves_to_type(
13608 &ctx.analyzer,
13609 ctx.file,
13610 node_text(type_node, ctx.source),
13611 owner,
13612 )
13613}
13614
13615pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13616 !ctx.analyzer
13617 .declarations(ctx.file)
13618 .into_iter()
13619 .filter(|unit| unit.is_function())
13620 .any(|unit| {
13621 ctx.analyzer.ranges(&unit).iter().any(|range| {
13622 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13623 })
13624 })
13625}
13626
13627pub enum DeclarationConstructorInitializer<'tree> {
13629 Arguments(Node<'tree>),
13632 Expression(Node<'tree>),
13635 Empty,
13637}
13638
13639pub fn declaration_constructor_initializer(
13640 node: Node<'_>,
13641) -> DeclarationConstructorInitializer<'_> {
13642 let mut cursor = node.walk();
13643 for child in node.named_children(&mut cursor) {
13644 if child.kind() == "init_declarator" {
13645 let Some(value) = child
13646 .child_by_field_name("value")
13647 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13648 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13649 else {
13650 return DeclarationConstructorInitializer::Empty;
13651 };
13652 return match value.kind() {
13653 "argument_list" | "initializer_list" => {
13654 DeclarationConstructorInitializer::Arguments(value)
13655 }
13656 "compound_literal_expression" => call_arguments_node(value)
13657 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13658 DeclarationConstructorInitializer::Arguments(arguments)
13659 }),
13660 _ => DeclarationConstructorInitializer::Expression(value),
13661 };
13662 }
13663 if let Some(declarator) = declaration_declarator(node, child) {
13664 return declarator_parameters(declarator)
13665 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13666 DeclarationConstructorInitializer::Arguments(parameters)
13667 });
13668 }
13669 }
13670 DeclarationConstructorInitializer::Empty
13671}
13672
13673pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13674 match declaration_constructor_initializer(node) {
13675 DeclarationConstructorInitializer::Arguments(arguments) => {
13676 argument_children(arguments).count()
13677 }
13678 DeclarationConstructorInitializer::Expression(_) => 1,
13679 DeclarationConstructorInitializer::Empty => 0,
13680 }
13681}
13682
13683fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13687 let mut current = node;
13688 loop {
13689 if let Some(parameters) = current.child_by_field_name("parameters") {
13690 return Some(parameters);
13691 }
13692 current = current.child_by_field_name("declarator")?;
13693 }
13694}
13695
13696pub(super) fn first_named_child_of_kind<'tree>(
13697 node: Node<'tree>,
13698 kind: &str,
13699) -> Option<Node<'tree>> {
13700 let mut cursor = node.walk();
13701 node.named_children(&mut cursor)
13702 .find(|child| child.kind() == kind)
13703}
13704
13705fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13706 let mut stack = vec![root];
13707 while let Some(node) = stack.pop() {
13708 if node.kind() == kind {
13709 return Some(node);
13710 }
13711 push_named_children_reversed(node, &mut stack);
13712 }
13713 None
13714}
13715
13716fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13717 if node.kind() == "identifier" {
13718 return true;
13719 }
13720 if node.kind() == "parenthesized_expression" {
13721 return false;
13722 }
13723 if node.kind() == "call_expression" {
13724 return node
13725 .child_by_field_name("function")
13726 .is_some_and(|function| function.kind() == "identifier");
13727 }
13728 let mut stack = vec![node];
13729 while let Some(descendant) = stack.pop() {
13730 if descendant != node && descendant.kind() == "parenthesized_expression" {
13731 continue;
13732 }
13733 if descendant.kind() == "identifier" {
13734 return true;
13735 }
13736 if descendant.kind() == "call_expression" {
13737 if descendant
13738 .child_by_field_name("function")
13739 .is_some_and(|function| function.kind() == "identifier")
13740 {
13741 return true;
13742 }
13743 continue;
13744 }
13745 push_named_children_reversed(descendant, &mut stack);
13746 }
13747 false
13748}
13749
13750fn macro_expansion_shape_is_safe(
13751 node: Node<'_>,
13752 source: &str,
13753 parameters: &[String],
13754 environment: &MacroEnvironment,
13755) -> bool {
13756 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13757 return true;
13758 }
13759 if node.kind() == "call_expression" {
13760 let Some(function) = node.child_by_field_name("function") else {
13761 return true;
13762 };
13763 if function.kind() != "identifier" {
13764 return true;
13765 }
13766 let function_name = node_text(function, source);
13767 if parameters
13768 .iter()
13769 .any(|parameter| parameter == function_name)
13770 {
13771 return false;
13772 }
13773 if !environment.may_bind(function_name) {
13774 return true;
13775 }
13776 let Some(arguments) = node.child_by_field_name("arguments") else {
13777 return false;
13778 };
13779 return argument_children(arguments).all(|argument| {
13780 if argument.kind() == "identifier"
13781 && parameters
13782 .iter()
13783 .any(|parameter| parameter == node_text(argument, source))
13784 {
13785 return false;
13786 }
13787 macro_expansion_shape_is_safe(argument, source, parameters, environment)
13788 });
13789 }
13790 let mut stack = vec![node];
13791 while let Some(descendant) = stack.pop() {
13792 if descendant != node {
13793 if descendant.kind() == "parenthesized_expression" {
13794 continue;
13795 }
13796 if descendant.kind() == "call_expression" {
13797 let expands = descendant
13798 .child_by_field_name("function")
13799 .filter(|function| function.kind() == "identifier")
13800 .is_some_and(|function| environment.may_bind(node_text(function, source)));
13801 if expands {
13802 return false;
13803 }
13804 continue;
13805 }
13806 }
13807 if descendant.kind() == "identifier" {
13808 let identifier = node_text(descendant, source);
13809 if parameters.iter().any(|parameter| parameter == identifier)
13810 || environment.may_bind(identifier)
13811 {
13812 return false;
13813 }
13814 }
13815 push_named_children_reversed(descendant, &mut stack);
13816 }
13817 true
13818}
13819
13820fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13821 let text = node_text(path, source);
13822 match path.kind() {
13823 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13824 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13825 _ => None,
13826 }
13827}
13828
13829fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13830 let source = prepared.source();
13831 let mut facts = Vec::new();
13832 let mut nodes = vec![prepared.tree().root_node()];
13833 while let Some(node) = nodes.pop() {
13834 if node.kind() == "preproc_include" {
13835 let Some(path) = node
13836 .child_by_field_name("path")
13837 .and_then(|path| structured_include_path(path, source))
13838 .map(str::to_owned)
13839 else {
13840 continue;
13841 };
13842 facts.push(StructuredIncludeFact {
13843 start_byte: node.start_byte(),
13844 end_byte: node.end_byte(),
13845 path,
13846 });
13847 continue;
13848 }
13849 push_named_children_reversed(node, &mut nodes);
13850 }
13851 Arc::from(facts.into_boxed_slice())
13852}
13853
13854fn has_unresolved_include_visible_before_in_prepared(
13855 file: &ProjectFile,
13856 prepared: &PreparedSyntaxTree,
13857 include_targets: &IncludeTargetIndex,
13858 facts: &[StructuredIncludeFact],
13859 before_byte: usize,
13860) -> bool {
13861 let guards = OnceCell::new();
13862 let reference = CallableReferenceContext {
13863 file,
13864 position: Some(CallableReferencePosition {
13865 prepared,
13866 byte: before_byte,
13867 guards: &guards,
13868 }),
13869 };
13870 let root = prepared.tree().root_node();
13871 facts
13872 .iter()
13873 .filter(|fact| fact.end_byte <= before_byte)
13874 .any(|fact| {
13875 let node = root
13876 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13877 .expect("structured include fact range must be in prepared tree");
13878 assert_eq!(
13879 node.kind(),
13880 "preproc_include",
13881 "structured include fact range must identify its include node"
13882 );
13883 callable_preprocessor_context_is_visible_for_reference(
13884 node,
13885 prepared.source(),
13886 &reference,
13887 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13888 })
13889}
13890
13891fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13892 let descendant = node;
13893 while let Some(parent) = node.parent() {
13894 if is_preprocessor_conditional(parent)
13895 && !is_file_covering_include_guard(parent, source)
13896 && preprocessor_conditional_contains_descendant(parent, descendant)
13897 {
13898 return true;
13899 }
13900 node = parent;
13901 }
13902 false
13903}
13904
13905fn owning_preprocessor_conditionals(
13917 root: Node<'_>,
13918 event: Node<'_>,
13919 source: &str,
13920) -> OwningPreprocessorConditionals {
13921 if !has_preprocessor_conditional_ancestor(event, source) {
13922 return OwningPreprocessorConditionals::default();
13923 }
13924 let start = event.start_byte();
13925 let descendant = root
13926 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13927 .expect("a byte inside the parsed tree names a descendant");
13928 let mut owners = Vec::new();
13929 let mut current = descendant.parent();
13930 while let Some(conditional) = current {
13931 if is_preprocessor_conditional(conditional)
13932 && !is_file_covering_include_guard(conditional, source)
13933 && preprocessor_conditional_contains_descendant(conditional, descendant)
13934 {
13935 owners.push(conditional.start_byte());
13936 }
13937 current = conditional.parent();
13938 }
13939 owners.into_boxed_slice()
13940}
13941
13942fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13943 matches!(
13944 node.kind(),
13945 "preproc_if"
13946 | "preproc_ifdef"
13947 | "preproc_ifndef"
13948 | "preproc_elif"
13949 | "preproc_elifdef"
13950 | "preproc_else"
13951 )
13952}
13953
13954fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13955 node.parent()
13956 .filter(|parent| parent.kind() == "translation_unit")
13957 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13958 && is_canonical_include_guard(node, source)
13959}
13960
13961fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13962 if node.kind() != "preproc_ifdef"
13963 || node
13964 .child(0)
13965 .is_none_or(|directive| directive.kind() != "#ifndef")
13966 || node.child_by_field_name("alternative").is_some()
13967 {
13968 return false;
13969 }
13970 let Some(guard_name) = node.child_by_field_name("name") else {
13971 return false;
13972 };
13973 let mut cursor = node.walk();
13974 node.named_children(&mut cursor)
13975 .find(|child| *child != guard_name && child.kind() != "comment")
13976 .filter(|child| child.kind() == "preproc_def")
13977 .and_then(|definition| definition.child_by_field_name("name"))
13978 .is_some_and(|defined_name| {
13979 node_text(defined_name, source) == node_text(guard_name, source)
13980 })
13981}
13982
13983fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
13984 let mut guard = None;
13985 for child in named_children_iter(root) {
13986 if child.kind() == "comment" || is_pragma_once(child, source) {
13987 continue;
13988 }
13989 if guard.is_none() && is_canonical_include_guard(child, source) {
13990 guard = Some(child);
13991 } else {
13992 return None;
13993 }
13994 }
13995 guard
13996 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
13997 .map(|name| node_text(name, source).to_string())
13998}
13999
14000fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14001 if (0..root.named_child_count())
14002 .filter_map(|index| root.named_child(index))
14003 .any(|child| is_pragma_once(child, source))
14004 {
14005 return MacroIncludeProtection::PragmaOnce;
14006 }
14007 top_level_canonical_include_guard_name(root, source)
14008 .map(MacroIncludeProtection::MacroGuard)
14009 .unwrap_or(MacroIncludeProtection::None)
14010}
14011
14012fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14013 node.kind() == "preproc_call"
14014 && node
14015 .child_by_field_name("directive")
14016 .is_some_and(|directive| node_text(directive, source) == "#pragma")
14017 && node
14018 .child_by_field_name("argument")
14019 .is_some_and(|argument| node_text(argument, source).trim() == "once")
14020}
14021
14022fn parse_preproc_identifier(argument: &str) -> Option<String> {
14023 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14024 let mut parser = Parser::new();
14025 parser
14026 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14027 .ok()?;
14028 let tree = parser.parse(&sentinel, None)?;
14029 if tree.root_node().has_error() {
14030 return None;
14031 }
14032 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14033 let identifier = statement.named_child(0)?;
14034 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14035 .then(|| node_text(identifier, &sentinel).to_string())
14036}
14037
14038pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14039 match node.kind() {
14040 "identifier" | "field_identifier" => {
14041 let name = node_text(node, source).trim();
14042 (!name.is_empty()).then(|| name.to_string())
14043 }
14044 "abstract_array_declarator"
14045 | "abstract_function_declarator"
14046 | "abstract_parenthesized_declarator"
14047 | "abstract_pointer_declarator"
14048 | "abstract_reference_declarator" => None,
14049 "function_declarator" => node
14050 .child_by_field_name("declarator")
14051 .or_else(|| node.child_by_field_name("name"))
14052 .and_then(|child| extract_variable_name(child, source)),
14053 _ => node
14054 .child_by_field_name("declarator")
14055 .or_else(|| node.child_by_field_name("name"))
14056 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14057 .and_then(|child| extract_variable_name(child, source)),
14058 }
14059}
14060
14061pub fn is_c_source_file(file: &ProjectFile) -> bool {
14072 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14073}
14074
14075pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14082 if !is_c_source_file(file) || node.kind() != "identifier" {
14083 return false;
14084 }
14085 let mut operand = node;
14086 while let Some(parent) = operand.parent().filter(|parent| {
14087 parent.kind() == "parenthesized_expression"
14088 && parent.named_child_count() == 1
14089 && parent.named_child(0) == Some(operand)
14090 }) {
14091 operand = parent;
14092 }
14093 operand.parent().is_some_and(|parent| {
14094 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14095 })
14096}
14097
14098pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14107 if node.kind() != "field_identifier" {
14108 return None;
14109 }
14110 let expression = node.parent().filter(|parent| {
14111 parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
14112 })?;
14113 if expression.has_error() {
14114 return None;
14115 }
14116 let closing = expression.child(expression.child_count().saturating_sub(1))?;
14117 if closing.kind() != ")" || closing.is_missing() {
14118 return None;
14119 }
14120 let type_descriptor = expression.child_by_field_name("type")?;
14121 if type_descriptor.kind() != "type_descriptor"
14122 || type_descriptor.is_missing()
14123 || type_descriptor.has_error()
14124 {
14125 return None;
14126 }
14127 let type_specifier = type_descriptor.child_by_field_name("type")?;
14128 if type_specifier.is_missing() || type_specifier.has_error() {
14129 return None;
14130 }
14131 let type_reference = match type_specifier.kind() {
14132 "class_specifier" | "struct_specifier" | "union_specifier" => {
14133 type_specifier.child_by_field_name("name")?
14134 }
14135 _ => type_specifier,
14136 };
14137 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14138}
14139
14140pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14145 node.kind() == "field_identifier"
14146 && node.parent().is_some_and(|parent| {
14147 parent.kind() == "offsetof_expression"
14148 && parent.child_by_field_name("member") == Some(node)
14149 })
14150}
14151
14152pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14168 if node.kind() != "type_identifier" {
14169 return false;
14170 }
14171 let Some(descriptor) = node
14172 .parent()
14173 .filter(|parent| parent.kind() == "type_descriptor")
14174 else {
14175 return false;
14176 };
14177 if descriptor.child_by_field_name("type") != Some(node) {
14178 return false;
14179 }
14180 let Some(arguments) = descriptor
14181 .parent()
14182 .filter(|parent| parent.kind() == "template_argument_list")
14183 else {
14184 return false;
14185 };
14186 arguments.parent().is_some_and(|owner| {
14187 matches!(
14188 owner.kind(),
14189 "template_type" | "template_function" | "template_method"
14190 ) && owner.child_by_field_name("arguments") == Some(arguments)
14191 })
14192}
14193
14194pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14207 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14208}
14209
14210pub fn is_declarator_node(node: Node<'_>) -> bool {
14211 matches!(
14212 node.kind(),
14213 "identifier"
14214 | "field_identifier"
14215 | "qualified_identifier"
14216 | "scoped_identifier"
14217 | "pointer_declarator"
14218 | "reference_declarator"
14219 | "array_declarator"
14220 | "parenthesized_declarator"
14221 | "function_declarator"
14222 )
14223}
14224
14225pub fn declaration_declarator<'tree>(
14234 declaration: Node<'tree>,
14235 child: Node<'tree>,
14236) -> Option<Node<'tree>> {
14237 if !matches!(
14238 declaration.kind(),
14239 "declaration"
14240 | "field_declaration"
14241 | "parameter_declaration"
14242 | "optional_parameter_declaration"
14243 | "function_definition"
14244 | "type_definition"
14245 | "alias_declaration"
14246 | "template_instantiation"
14247 ) {
14248 return None;
14249 }
14250 if declaration
14251 .child_by_field_name("type")
14252 .is_some_and(|type_node| same_node(type_node, child))
14253 {
14254 return None;
14255 }
14256 if child.kind() == "init_declarator" {
14257 return child.child_by_field_name("declarator");
14258 }
14259 let field = field_name_in_parent(declaration, child);
14260 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14261 || (declaration.kind() == "type_definition"
14262 && child.kind() == "type_identifier"
14263 && matches!(field, Some("declarator") | None))
14264 {
14265 Some(child)
14266 } else {
14267 None
14268 }
14269}
14270
14271#[derive(Clone, Debug, PartialEq, Eq)]
14274pub struct RecoveredNamespaceRegion {
14275 pub start: usize,
14277 pub end: usize,
14279 pub components: Vec<String>,
14282 pub component_ranges: Vec<Range>,
14291}
14292
14293#[derive(Clone, Debug, Default)]
14329pub struct OrphanedNamespaceScopeIndex {
14330 regions: Vec<RecoveredNamespaceRegion>,
14331 brace_closes: HashMap<usize, Range>,
14334}
14335
14336impl OrphanedNamespaceScopeIndex {
14337 pub fn build(root: Node<'_>, source: &str) -> Self {
14338 if !root.has_error() {
14339 return Self::default();
14340 }
14341 #[derive(Clone, Copy)]
14347 struct OpenHead {
14348 head: Range,
14349 open: usize,
14350 }
14351 struct PendingRegion {
14354 start: usize,
14355 end: usize,
14356 components: Vec<String>,
14357 heads: Vec<OpenHead>,
14358 }
14359 struct Frame<'tree> {
14360 node: Node<'tree>,
14361 children: Vec<Node<'tree>>,
14362 next: usize,
14363 parsed_scope: Vec<String>,
14364 run: Option<PendingRegion>,
14365 }
14366 fn frame<'tree>(
14367 node: Node<'tree>,
14368 mut parsed_scope: Vec<String>,
14369 source: &str,
14370 ) -> Frame<'tree> {
14371 if node.kind() == "namespace_definition"
14372 && let Some(name) = node.child_by_field_name("name")
14373 {
14374 let mut components = Vec::new();
14375 if append_cpp_name_components(name, source, &mut components).is_some() {
14376 parsed_scope.extend(components);
14377 }
14378 }
14379 let mut cursor = node.walk();
14380 Frame {
14381 node,
14382 children: node.children(&mut cursor).collect(),
14383 next: 0,
14384 parsed_scope,
14385 run: None,
14386 }
14387 }
14388 let mut regions = Vec::new();
14389 let mut brace_closes = HashMap::default();
14390 let mut unpaired_closes: Vec<Range> = Vec::new();
14396 let mut open = Vec::new();
14400 let mut lexical_scope = Vec::new();
14401 let mut lexical_heads: Vec<OpenHead> = Vec::new();
14406 let mut frames = vec![frame(root, Vec::new(), source)];
14407 while !frames.is_empty() {
14412 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14413 let current = frames.last_mut().expect("frames is non-empty");
14414 if current.next == current.children.len() {
14415 regions.extend(frames.pop().expect("the frame just borrowed").run);
14416 continue;
14417 }
14418 let child = current.children[current.next];
14419 current.next += 1;
14420 match child.kind() {
14421 "{" if !child.is_missing() => {
14422 regions.extend(current.run.take());
14423 let mut named = parent_node.and_then(|parent| {
14428 let components =
14429 namespace_body_name_components(parent, current.node, source);
14430 (!components.is_empty()).then_some((parent, components))
14431 });
14432 if named.is_none() {
14433 named = recovered_namespace_open_components(
14434 ¤t.children[..current.next - 1],
14435 source,
14436 );
14437 }
14438 open.push((child.start_byte(), lexical_scope.len()));
14439 if let Some((written_at, components)) = named {
14440 let head = OpenHead {
14441 head: Range {
14442 start_byte: written_at.start_byte(),
14443 end_byte: child.end_byte(),
14444 start_line: written_at.start_position().row + 1,
14445 end_line: child.end_position().row + 1,
14446 },
14447 open: child.start_byte(),
14448 };
14449 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14450 lexical_scope.extend(components);
14451 }
14452 continue;
14453 }
14454 "}" if !child.is_missing() => {
14455 regions.extend(current.run.take());
14456 let close = Range {
14457 start_byte: child.start_byte(),
14458 end_byte: child.end_byte(),
14459 start_line: child.start_position().row + 1,
14460 end_line: child.end_position().row + 1,
14461 };
14462 match open.pop() {
14463 Some((start, namespace_len)) => {
14464 debug_assert!(
14465 start < close.start_byte,
14466 "a close pairs with an open before it: {start} then {close:?}"
14467 );
14468 lexical_scope.truncate(namespace_len);
14469 lexical_heads.truncate(namespace_len);
14470 brace_closes.insert(start, close);
14471 }
14472 None => unpaired_closes.push(close),
14473 }
14474 continue;
14475 }
14476 _ => {}
14477 }
14478 if current.node.kind() != "namespace_definition"
14482 && lexical_scope != current.parsed_scope
14483 {
14484 match &mut current.run {
14485 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14486 run => {
14487 regions.extend(run.take());
14488 *run = Some(PendingRegion {
14489 start: child.start_byte(),
14490 end: child.end_byte(),
14491 components: lexical_scope.clone(),
14492 heads: lexical_heads.clone(),
14493 });
14494 }
14495 }
14496 } else {
14497 regions.extend(current.run.take());
14498 }
14499 if child.has_error() {
14503 let parsed_scope = current.parsed_scope.clone();
14504 frames.push(frame(child, parsed_scope, source));
14505 }
14506 }
14507 if !unpaired_closes.is_empty() {
14516 brace_closes.clear();
14517 }
14518 let regions = regions
14523 .into_iter()
14524 .map(|region| {
14525 debug_assert_eq!(
14526 region.components.len(),
14527 region.heads.len(),
14528 "every recovered namespace level carries the head that writes it: {:?}",
14529 region.components
14530 );
14531 RecoveredNamespaceRegion {
14532 start: region.start,
14533 end: region.end,
14534 component_ranges: region
14535 .heads
14536 .iter()
14537 .map(|level| match brace_closes.get(&level.open) {
14538 Some(close) => Range {
14539 start_byte: level.head.start_byte,
14540 end_byte: close.end_byte,
14541 start_line: level.head.start_line,
14542 end_line: close.end_line,
14543 },
14544 None => level.head,
14545 })
14546 .collect(),
14547 components: region.components,
14548 }
14549 })
14550 .collect();
14551 Self {
14552 regions,
14553 brace_closes,
14554 }
14555 }
14556
14557 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14564 self.brace_closes.get(&open).copied()
14565 }
14566
14567 pub fn is_empty(&self) -> bool {
14568 self.regions.is_empty()
14569 }
14570
14571 pub fn approximate_size(&self) -> usize {
14573 self.regions.iter().fold(
14574 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14575 |total, region| {
14576 total
14577 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14578 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14579 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14580 },
14581 )
14582 }
14583
14584 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14586 self.regions
14587 .iter()
14588 .filter(|region| region.start <= byte && byte < region.end)
14589 .min_by_key(|region| region.end - region.start)
14590 }
14591
14592 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14596 self.enclosing_namespace_levels(node, source)
14597 .into_iter()
14598 .map(|(component, _)| component)
14599 .collect()
14600 }
14601
14602 pub fn enclosing_namespace_levels(&self, node: Node<'_>, source: &str) -> Vec<(String, Range)> {
14609 let mut parsed = Vec::new();
14610 let mut current = node.parent();
14611 while let Some(parent) = current {
14612 if parent.kind() == "namespace_definition"
14613 && let Some(name) = parent.child_by_field_name("name")
14614 {
14615 let mut components = Vec::new();
14616 if append_cpp_name_components(name, source, &mut components).is_some() {
14617 let range = node_range(parent);
14618 parsed.push((
14619 parent.start_byte(),
14620 components
14621 .into_iter()
14622 .map(|component| (component, range))
14623 .collect(),
14624 ));
14625 }
14626 }
14627 current = parent.parent();
14628 }
14629 parsed.reverse();
14630 self.splice_recovered_path(parsed, node.start_byte(), |region| {
14631 region
14632 .components
14633 .iter()
14634 .cloned()
14635 .zip(region.component_ranges.iter().copied())
14636 .collect()
14637 })
14638 }
14639
14640 pub fn restore_enclosing_namespaces(
14646 &self,
14647 parsed: Vec<(usize, Vec<String>)>,
14648 node_start: usize,
14649 ) -> Vec<String> {
14650 self.splice_recovered_path(parsed, node_start, |region| region.components.clone())
14651 }
14652
14653 fn splice_recovered_path<T>(
14659 &self,
14660 parsed: Vec<(usize, Vec<T>)>,
14661 node_start: usize,
14662 recovered: impl FnOnce(&RecoveredNamespaceRegion) -> Vec<T>,
14663 ) -> Vec<T> {
14664 let Some(region) = self.region_at(node_start) else {
14665 return parsed.into_iter().flat_map(|(_, levels)| levels).collect();
14666 };
14667 recovered(region)
14668 .into_iter()
14669 .chain(
14670 parsed
14671 .into_iter()
14672 .filter(|(start, _)| *start >= region.start)
14673 .flat_map(|(_, levels)| levels),
14674 )
14675 .collect()
14676 }
14677}
14678
14679fn recovered_namespace_open_components<'tree>(
14695 preceding: &[Node<'tree>],
14696 source: &str,
14697) -> Option<(Node<'tree>, Vec<String>)> {
14698 let mut head = Vec::new();
14699 for &sibling in preceding.iter().rev() {
14700 if sibling.kind() != "comment" {
14701 head.push(sibling);
14702 if head.len() == 2 {
14703 break;
14704 }
14705 }
14706 }
14707 let [name, keyword] = head[..] else {
14708 return None;
14709 };
14710 if keyword.kind() != "namespace" {
14711 return None;
14712 }
14713 let mut components = Vec::new();
14714 append_cpp_name_components(name, source, &mut components)?;
14715 (!components.is_empty()).then_some((keyword, components))
14716}
14717
14718fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14721 let mut components = Vec::new();
14722 if body.kind() == "declaration_list"
14723 && parent.kind() == "namespace_definition"
14724 && parent.child_by_field_name("body") == Some(body)
14725 && let Some(name) = parent.child_by_field_name("name")
14726 && append_cpp_name_components(name, source, &mut components).is_none()
14727 {
14728 components.clear();
14729 }
14730 components
14731}
14732
14733#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14734pub enum RecoveredDeclaratorTypeContext {
14735 Declaration,
14736 FunctionDefinition,
14737 Parameter,
14738}
14739
14740pub fn recovered_macro_decorated_declarator_type(
14755 node: Node<'_>,
14756) -> Option<RecoveredDeclaratorTypeContext> {
14757 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14758}
14759
14760pub fn recovered_macro_decorated_type_node(
14765 node: Node<'_>,
14766) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14767 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14768 return None;
14769 }
14770 let qualified = node.parent()?;
14771 if qualified.kind() != "qualified_identifier"
14772 || qualified.child_by_field_name("scope") != Some(node)
14773 || !(0..qualified.child_count())
14774 .filter_map(|index| qualified.child(index))
14775 .any(|child| child.kind() == "::" && child.is_missing())
14776 {
14777 return None;
14778 }
14779 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14780 return None;
14781 }
14782
14783 let (declaration, context) = recovered_declarator_container(qualified)?;
14784 let type_node = declaration
14785 .child_by_field_name("type")
14786 .filter(|type_node| {
14787 *type_node != qualified
14788 && !type_node.is_missing()
14789 && type_node.start_byte() != type_node.end_byte()
14790 })?;
14791 Some((type_node, context))
14792}
14793
14794fn recovered_declarator_container(
14795 mut declarator: Node<'_>,
14796) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14797 loop {
14798 let parent = declarator.parent()?;
14799 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14800 return Some((
14801 parent
14802 .parent()
14803 .filter(|declaration| declaration.kind() == "declaration")?,
14804 RecoveredDeclaratorTypeContext::Declaration,
14805 ));
14806 }
14807 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14808 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14809 }
14810 if parent.kind() == "function_definition"
14811 && has_field_child(parent, "declarator", declarator)
14812 {
14813 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14814 }
14815 if matches!(
14821 parent.kind(),
14822 "parameter_declaration" | "optional_parameter_declaration"
14823 ) && has_field_child(parent, "declarator", declarator)
14824 {
14825 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14826 }
14827 if !matches!(
14828 parent.kind(),
14829 "array_declarator"
14830 | "function_declarator"
14831 | "parenthesized_declarator"
14832 | "pointer_declarator"
14833 | "pointer_type_declarator"
14834 | "reference_declarator"
14835 ) || !has_field_child(parent, "declarator", declarator)
14836 {
14837 return None;
14838 }
14839 declarator = parent;
14840 }
14841}
14842
14843fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14844 let mut cursor = parent.walk();
14845 parent
14846 .children_by_field_name(field, &mut cursor)
14847 .any(|child| child == target)
14848}
14849
14850fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14851 loop {
14852 if node.is_missing() || node.start_byte() == node.end_byte() {
14853 return false;
14854 }
14855 match node.kind() {
14856 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14857 return true;
14858 }
14859 "array_declarator"
14860 | "function_declarator"
14861 | "parenthesized_declarator"
14862 | "pointer_declarator"
14863 | "pointer_type_declarator"
14864 | "reference_declarator" => {
14865 let Some(declarator) = node.child_by_field_name("declarator") else {
14866 return false;
14867 };
14868 node = declarator;
14869 }
14870 _ => return false,
14871 }
14872 }
14873}
14874
14875pub enum DesignatedInitializerOwner {
14877 Resolved(CodeUnit),
14878 Unresolved,
14879}
14880
14881enum InitializerOwnerStep {
14882 Field(String),
14883 AggregateWrapper,
14884}
14885
14886pub fn designated_initializer_owner(
14896 analyzer: &CppGraphSource<'_>,
14897 visibility: &VisibilityIndex<'_>,
14898 file: &ProjectFile,
14899 source: &str,
14900 node: Node<'_>,
14901) -> Option<DesignatedInitializerOwner> {
14902 if let Some(designator) = node
14903 .parent()
14904 .filter(|parent| parent.kind() == "field_designator")
14905 {
14906 let pair = designator.parent()?;
14907 if pair.kind() != "initializer_pair" {
14908 return None;
14909 }
14910 let mut cursor = pair.walk();
14911 let designators = pair
14912 .children_by_field_name("designator", &mut cursor)
14913 .collect::<Vec<_>>();
14914 let position = designators
14915 .iter()
14916 .position(|candidate| same_node(*candidate, designator))?;
14917 let initializer = pair.parent()?;
14918 if initializer.kind() != "initializer_list" {
14919 return None;
14920 }
14921 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14922 for prior in &designators[..position] {
14923 let field = prior
14924 .child_by_field_name("field")
14925 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14926 owner = owner.and_then(|owner| {
14927 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14928 });
14929 }
14930 return Some(classified_designated_owner(owner));
14931 }
14932
14933 let init_declarator = node.parent()?;
14934 if init_declarator.child_by_field_name("declarator") != Some(node)
14935 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14936 {
14937 return None;
14938 }
14939 Some(classified_designated_owner(declaration_owner(
14940 analyzer,
14941 visibility,
14942 file,
14943 source,
14944 init_declarator.parent()?,
14945 )))
14946}
14947
14948fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14949 owner.map_or(
14950 DesignatedInitializerOwner::Unresolved,
14951 DesignatedInitializerOwner::Resolved,
14952 )
14953}
14954
14955fn initializer_list_owner(
14956 analyzer: &CppGraphSource<'_>,
14957 visibility: &VisibilityIndex<'_>,
14958 file: &ProjectFile,
14959 source: &str,
14960 initializer: Node<'_>,
14961) -> Option<CodeUnit> {
14962 let mut current = initializer;
14963 let mut steps = Vec::new();
14964 loop {
14965 let parent = current.parent()?;
14966 match parent.kind() {
14967 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14968 let designator = parent.child_by_field_name("designator")?;
14969 let step = designator
14970 .child_by_field_name("field")
14971 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14972 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14973 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14974 steps.push(step);
14975 current = parent.parent()?;
14976 }
14977 "initializer_list" => {
14978 current = parent;
14979 }
14980 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14981 let declaration = parent.parent()?;
14982 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14983 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14984 }
14985 "compound_literal_expression"
14986 if parent.child_by_field_name("value") == Some(current) =>
14987 {
14988 let type_node = parent.child_by_field_name("type")?;
14989 let owner =
14990 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
14991 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14992 }
14993 "ERROR" => current = parent,
14994 _ => return None,
14995 }
14996 }
14997}
14998
14999fn apply_initializer_owner_steps(
15000 analyzer: &CppGraphSource<'_>,
15001 visibility: &VisibilityIndex<'_>,
15002 file: &ProjectFile,
15003 mut owner: CodeUnit,
15004 steps: Vec<InitializerOwnerStep>,
15005) -> Option<CodeUnit> {
15006 for step in steps.into_iter().rev() {
15007 if let InitializerOwnerStep::Field(field_name) = step {
15008 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15009 }
15010 }
15011 Some(owner)
15012}
15013
15014fn initializer_field_owner(
15015 analyzer: &CppGraphSource<'_>,
15016 visibility: &VisibilityIndex<'_>,
15017 file: &ProjectFile,
15018 owner: CodeUnit,
15019 field_name: &str,
15020) -> Option<CodeUnit> {
15021 let fields = visibility
15022 .visible_members_for_owner_name(file, &owner, field_name)
15023 .into_iter()
15024 .filter(|field| field.is_field())
15025 .collect::<Vec<_>>();
15026 let field = match fields.as_slice() {
15027 [field] => *field,
15028 _ => return None,
15029 };
15030 field_declared_binding(analyzer, visibility, file, field)?.unit
15031}
15032
15033fn declaration_owner(
15034 analyzer: &CppGraphSource<'_>,
15035 visibility: &VisibilityIndex<'_>,
15036 file: &ProjectFile,
15037 source: &str,
15038 declaration: Node<'_>,
15039) -> Option<CodeUnit> {
15040 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15041 return None;
15042 }
15043 let type_node = declaration
15044 .child_by_field_name("type")
15045 .or_else(|| first_type_child(declaration))?;
15046 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15047}
15048
15049fn resolve_designated_owner_type(
15050 analyzer: &CppGraphSource<'_>,
15051 visibility: &VisibilityIndex<'_>,
15052 file: &ProjectFile,
15053 source: &str,
15054 type_node: Node<'_>,
15055) -> Option<CodeUnit> {
15056 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15057 return Some(owner);
15058 }
15059 let type_name = normalize_type_text(node_text(type_node, source));
15060 visibility
15061 .resolve_type(file, &type_name)
15062 .filter(CodeUnit::is_class)
15063}
15064
15065pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15066 let mut cursor = node.walk();
15067 node.named_children(&mut cursor).find(|child| {
15068 matches!(
15069 child.kind(),
15070 "type_identifier"
15071 | "primitive_type"
15072 | "qualified_identifier"
15073 | "scoped_type_identifier"
15074 | "struct_specifier"
15075 | "union_specifier"
15076 | "enum_specifier"
15077 )
15078 })
15079}
15080
15081pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15082 visibility: &VisibilityIndex<'_>,
15083 file: &ProjectFile,
15084 source: &str,
15085 declarator: Node<'_>,
15086 type_text: Option<&str>,
15087 bindings: &LocalInferenceEngine<T>,
15088) -> bool {
15089 if !has_ancestor_kind(declarator, "compound_statement") {
15090 return false;
15091 }
15092 if declarator
15093 .child_by_field_name("declarator")
15094 .is_none_or(|declarator| declarator.kind() != "identifier")
15095 {
15096 return false;
15097 }
15098 if !type_text
15099 .and_then(|text| visibility.resolve_type(file, text))
15100 .is_some_and(|unit| unit.is_class())
15101 {
15102 return false;
15103 }
15104 declarator
15105 .child_by_field_name("parameters")
15106 .is_some_and(|parameters| {
15107 constructor_parameters_look_like_expressions(parameters, source, bindings)
15108 })
15109}
15110
15111fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15112 parameters: Node<'_>,
15113 source: &str,
15114 bindings: &LocalInferenceEngine<T>,
15115) -> bool {
15116 let mut cursor = parameters.walk();
15117 parameters.named_children(&mut cursor).any(|parameter| {
15118 !matches!(
15119 parameter.kind(),
15120 "parameter_declaration" | "optional_parameter_declaration"
15121 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15122 })
15123}
15124
15125fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15126 parameter: Node<'_>,
15127 source: &str,
15128 bindings: &LocalInferenceEngine<T>,
15129) -> bool {
15130 let text = node_text(parameter, source).trim();
15131 if text
15132 .chars()
15133 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15134 && bindings.is_shadowed(text)
15135 {
15136 return true;
15137 }
15138
15139 let Some(base) = parameter
15140 .child_by_field_name("type")
15141 .filter(|base| base.kind() == "type_identifier")
15142 else {
15143 return false;
15144 };
15145 let Some(subscript) = parameter
15146 .child_by_field_name("declarator")
15147 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15148 else {
15149 return false;
15150 };
15151 subscript.child_by_field_name("size").is_some()
15152 && bindings.is_shadowed(node_text(base, source).trim())
15153}
15154
15155pub fn is_declaration_name(node: Node<'_>) -> bool {
15156 let Some(parent) = node.parent() else {
15157 return false;
15158 };
15159 if parent
15160 .child_by_field_name("name")
15161 .is_some_and(|name| same_node(name, node))
15162 {
15163 if matches!(
15164 parent.kind(),
15165 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15166 ) {
15167 return cpp_tag_specifier_declares_name(parent);
15168 }
15169 if matches!(
15170 parent.kind(),
15171 "namespace_definition"
15172 | "namespace_alias_definition"
15173 | "alias_declaration"
15174 | "enumerator"
15175 ) {
15176 return true;
15177 }
15178 }
15179
15180 let mut current = Some(parent);
15181 while let Some(ancestor) = current {
15182 let type_definition = ancestor.kind() == "type_definition";
15183 let mut child_cursor = ancestor.walk();
15184 if ancestor.named_children(&mut child_cursor).any(|child| {
15185 declaration_declarator(ancestor, child).is_some_and(|declarator| {
15186 declarator_name_path_contains(declarator, node, type_definition)
15187 })
15188 }) {
15189 return true;
15190 }
15191 if matches!(
15192 ancestor.kind(),
15193 "declaration"
15194 | "field_declaration"
15195 | "parameter_declaration"
15196 | "optional_parameter_declaration"
15197 | "function_definition"
15198 | "type_definition"
15199 | "alias_declaration"
15200 | "template_instantiation"
15201 | "class_specifier"
15202 | "struct_specifier"
15203 | "union_specifier"
15204 | "enum_specifier"
15205 ) {
15206 return false;
15207 }
15208 current = ancestor.parent();
15209 }
15210 false
15211}
15212
15213pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15222 if !matches!(
15223 node.kind(),
15224 "qualified_identifier" | "scoped_type_identifier"
15225 ) {
15226 return false;
15227 }
15228 let Some(declaration) = node
15229 .parent()
15230 .filter(|parent| parent.kind() == "declaration")
15231 else {
15232 return false;
15233 };
15234 if declaration.child_by_field_name("declarator") != Some(node)
15235 || !declaration
15236 .child_by_field_name("type")
15237 .is_some_and(|friend| {
15238 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15239 })
15240 {
15241 return false;
15242 }
15243 let mut cursor = declaration.walk();
15244 let mut errors = declaration
15245 .named_children(&mut cursor)
15246 .filter(|child| child.kind() == "ERROR");
15247 let Some(error) = errors.next() else {
15248 return false;
15249 };
15250 errors.next().is_none()
15251 && error.named_child_count() == 1
15252 && error.named_child(0).is_some_and(|class| {
15253 class.kind() == "identifier" && node_text(class, source) == "class"
15254 })
15255}
15256
15257pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15258 if !matches!(node.kind(), "identifier" | "field_identifier") {
15259 return false;
15260 }
15261 if let Some(parent) = node.parent() {
15262 if parent.kind() == "call_expression"
15263 && parent.child_by_field_name("function") == Some(node)
15264 {
15265 return false;
15266 }
15267 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15268 && parent.child_by_field_name("label") == Some(node)
15269 {
15270 return false;
15271 }
15272 }
15273 if is_declaration_name(node) {
15274 return false;
15275 }
15276 let mut current = node.parent();
15277 while let Some(ancestor) = current {
15278 match ancestor.kind() {
15279 "preproc_ifdef" | "preproc_ifndef" => {
15280 if ancestor
15281 .child_by_field_name("name")
15282 .is_some_and(|name| node_range_contains(name, node))
15283 {
15284 return false;
15285 }
15286 }
15287 "preproc_if" | "preproc_elif" => {
15288 if ancestor
15289 .child_by_field_name("condition")
15290 .is_some_and(|condition| node_range_contains(condition, node))
15291 {
15292 return false;
15293 }
15294 }
15295 "preproc_else" => {}
15296 kind if kind.starts_with("preproc_") => return false,
15297 _ => {}
15298 }
15299 if matches!(
15300 ancestor.kind(),
15301 "translation_unit" | "function_definition" | "compound_statement"
15302 ) {
15303 break;
15304 }
15305 current = ancestor.parent();
15306 }
15307 true
15308}
15309
15310fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
15311 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
15312}
15313
15314fn recovered_c_reference_node(
15315 visibility: &VisibilityIndex<'_>,
15316 file: &ProjectFile,
15317 node: Node<'_>,
15318 source: &str,
15319) -> bool {
15320 if node.start_byte() >= node.end_byte()
15321 || node.is_error()
15322 || node.is_missing()
15323 || !matches!(
15324 node.kind(),
15325 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15326 )
15327 || recovered_c_macro_binding_role(node)
15328 || recovered_c_label_role(node)
15329 {
15330 return false;
15331 }
15332 let name = node_text(node, source);
15340 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15341 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15342 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15343 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15344 if is_declaration_name(node)
15345 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15346 && !recovered_function_call
15347 && !recovered_macro_call
15348 && !recovered_parenthesized_reference
15349 {
15350 return false;
15351 }
15352
15353 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15354 return true;
15355 }
15356 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15357 return true;
15358 }
15359 if recovered_parenthesized_reference {
15360 return true;
15361 }
15362 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15363 if recovered_function_call {
15364 return true;
15365 }
15366 return visibility
15367 .visible_identifier_candidates(file, name)
15368 .any(|candidate| {
15369 candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
15370 });
15371 }
15372 let visible = visibility
15373 .visible_identifier_candidates(file, name)
15374 .next()
15375 .is_some();
15376 visible
15377 && (recovered_c_reference_anchor(node)
15378 || recovered_c_error_expression_leaf(node)
15379 || recovered_function_call)
15380}
15381
15382fn push_recovered_c_range(
15383 ranges: &mut Vec<Range>,
15384 seen: &mut HashSet<(usize, usize)>,
15385 start_byte: usize,
15386 end_byte: usize,
15387 node: Node<'_>,
15388 limit: usize,
15389) -> bool {
15390 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15391 return true;
15392 }
15393 if ranges.len() >= limit {
15394 return false;
15395 }
15396 ranges.push(Range {
15397 start_byte,
15398 end_byte,
15399 start_line: node.start_position().row,
15400 end_line: node.end_position().row,
15401 });
15402 true
15403}
15404
15405fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15410 let mut current = node.parent();
15411 while let Some(parent) = current {
15412 if parent.is_error() {
15413 let Some(anchor) = parent.parent() else {
15414 return false;
15415 };
15416 return anchor.kind().ends_with("_expression")
15417 || matches!(
15418 anchor.kind(),
15419 "argument_list"
15420 | "return_statement"
15421 | "expression_statement"
15422 | "case_statement"
15423 | "initializer_list"
15424 | "field_designator"
15425 | "enumerator"
15426 );
15427 }
15428 if matches!(
15429 parent.kind(),
15430 "translation_unit" | "function_definition" | "compound_statement"
15431 ) {
15432 return false;
15433 }
15434 current = parent.parent();
15435 }
15436 false
15437}
15438
15439fn recovered_c_function_call(
15446 visibility: &VisibilityIndex<'_>,
15447 file: &ProjectFile,
15448 node: Node<'_>,
15449 name: &str,
15450) -> bool {
15451 if !matches!(
15452 node.kind(),
15453 "identifier" | "field_identifier" | "type_identifier"
15454 ) {
15455 return false;
15456 }
15457 let error_call_prefix = node.parent().is_some_and(|error| {
15462 error.is_error()
15463 && error
15464 .parent()
15465 .is_some_and(|parent| parent.kind() == "compound_statement")
15466 }) && node
15467 .prev_sibling()
15468 .is_none_or(|previous| previous.kind() == ";")
15469 && node.next_sibling().is_some_and(|open| {
15470 open.kind() == "("
15471 && open.next_named_sibling().is_some_and(|argument| {
15472 argument.kind() == "parameter_declaration" && !argument.has_error()
15473 })
15474 });
15475 (error_call_prefix || recovered_c_function_declarator_invocation(node))
15476 && visibility
15477 .visible_identifier_candidates(file, name)
15478 .any(CodeUnit::is_function)
15479}
15480
15481fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15491 let mut function_declarator = if node.parent().is_some_and(|parent| {
15492 parent.kind() == "function_declarator"
15493 && parent.child_by_field_name("declarator") == Some(node)
15494 }) {
15495 node.parent().expect("checked function declarator parent")
15496 } else {
15497 let Some(parameter) = node.parent().filter(|parent| {
15498 parent.kind() == "parameter_declaration"
15499 && parent.child_by_field_name("type") == Some(node)
15500 }) else {
15501 return false;
15502 };
15503 if !parameter
15504 .child_by_field_name("declarator")
15505 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15506 {
15507 return false;
15508 }
15509 let Some(parameters) = parameter
15510 .parent()
15511 .filter(|parent| parent.kind() == "parameter_list")
15512 else {
15513 return false;
15514 };
15515 let Some(function_declarator) = parameters
15516 .parent()
15517 .filter(|parent| parent.kind() == "function_declarator")
15518 else {
15519 return false;
15520 };
15521 function_declarator
15522 };
15523
15524 while let Some(parent) = function_declarator.parent().filter(|parent| {
15527 parent.kind() == "function_declarator"
15528 && parent.child_by_field_name("declarator") == Some(function_declarator)
15529 }) {
15530 function_declarator = parent;
15531 }
15532 let Some(mut current) = function_declarator
15533 .parent()
15534 .filter(|parent| parent.is_error())
15535 else {
15536 return false;
15537 };
15538 loop {
15539 let Some(parent) = current.parent() else {
15540 return false;
15541 };
15542 if matches!(
15543 parent.kind(),
15544 "translation_unit"
15545 | "compound_statement"
15546 | "preproc_if"
15547 | "preproc_ifdef"
15548 | "preproc_ifndef"
15549 | "preproc_else"
15550 | "preproc_elif"
15551 ) {
15552 return true;
15553 }
15554 if parent.kind() == "function_definition"
15555 && parent.child_by_field_name("declarator") == Some(current)
15556 && parent.named_child(0) == Some(current)
15557 && parent.child_by_field_name("body").is_some()
15558 {
15559 return true;
15560 }
15561 if parent.is_error()
15562 || matches!(
15563 parent.kind(),
15564 "parameter_declaration"
15565 | "parameter_list"
15566 | "function_declarator"
15567 | "abstract_function_declarator"
15568 | "parenthesized_declarator"
15569 )
15570 {
15571 current = parent;
15572 continue;
15573 }
15574 return false;
15575 }
15576}
15577
15578fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15584 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15585 return false;
15586 };
15587 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15588 return false;
15589 }
15590 let Some(declarator) = error
15591 .parent()
15592 .filter(|parent| parent.kind() == "parenthesized_declarator")
15593 else {
15594 return false;
15595 };
15596 let Some(declaration) = declarator
15597 .parent()
15598 .filter(|parent| parent.kind() == "declaration")
15599 else {
15600 return false;
15601 };
15602 if declaration.child_by_field_name("declarator") != Some(declarator) {
15603 return false;
15604 }
15605 let Some(type_node) = declaration.child_by_field_name("type") else {
15606 return false;
15607 };
15608 type_node.kind() == "dependent_type"
15609 && type_node
15610 .child(0)
15611 .is_some_and(|keyword| keyword.kind() == "typename")
15612}
15613
15614fn recovered_c_explicit_assignment_callee(
15615 visibility: &VisibilityIndex<'_>,
15616 file: &ProjectFile,
15617 node: Node<'_>,
15618 name: &str,
15619) -> bool {
15620 let mut current = node;
15621 let error = loop {
15622 let Some(parent) = current.parent() else {
15623 return false;
15624 };
15625 if parent.is_error() {
15626 break parent;
15627 }
15628 current = parent;
15629 };
15630 let mut cursor = error.walk();
15631 let explicit_recovery_precedes_callee = error
15632 .named_children(&mut cursor)
15633 .take_while(|child| child.start_byte() < node.start_byte())
15634 .any(|child| child.kind() == "explicit_function_specifier");
15635 if !explicit_recovery_precedes_callee {
15636 return false;
15637 }
15638 visibility
15639 .visible_identifier_candidates(file, name)
15640 .any(CodeUnit::is_function)
15641}
15642
15643fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15644 while let Some(parent) = node.parent() {
15645 if matches!(
15646 parent.kind(),
15647 "preproc_def" | "preproc_function_def" | "preproc_params"
15648 ) {
15649 return true;
15650 }
15651 if parent.is_error()
15652 || matches!(
15653 parent.kind(),
15654 "translation_unit" | "function_definition" | "compound_statement"
15655 )
15656 {
15657 return false;
15658 }
15659 node = parent;
15660 }
15661 false
15662}
15663
15664fn recovered_c_label_role(node: Node<'_>) -> bool {
15665 node.parent().is_some_and(|parent| {
15666 matches!(parent.kind(), "labeled_statement" | "goto_statement")
15667 && parent.child_by_field_name("label") == Some(node)
15668 })
15669}
15670
15671fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15672 while let Some(parent) = node.parent() {
15673 if parent.is_error() {
15674 return false;
15675 }
15676 if parent.kind() == "optional_parameter_declaration"
15681 && parent
15682 .child_by_field_name("default_value")
15683 .is_some_and(|value| node_range_contains(value, node))
15684 {
15685 return true;
15686 }
15687 if parent.kind().ends_with("_expression")
15688 || matches!(
15689 parent.kind(),
15690 "argument_list"
15691 | "return_statement"
15692 | "expression_statement"
15693 | "case_statement"
15694 | "initializer_list"
15695 | "init_declarator"
15696 | "array_declarator"
15697 | "field_designator"
15698 | "enumerator"
15699 )
15700 {
15701 return true;
15702 }
15703 if matches!(
15704 parent.kind(),
15705 "translation_unit"
15706 | "function_definition"
15707 | "compound_statement"
15708 | "declaration"
15709 | "field_declaration"
15710 | "parameter_declaration"
15711 ) {
15712 return false;
15713 }
15714 node = parent;
15715 }
15716 false
15717}
15718
15719pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15727 let mut current = parameter.parent();
15728 while let Some(ancestor) = current {
15729 if ancestor.kind() == "lambda_expression" {
15730 return ancestor
15731 .child_by_field_name("declarator")
15732 .is_some_and(|declarator| {
15733 declarator.start_byte() <= parameter.start_byte()
15734 && parameter.end_byte() <= declarator.end_byte()
15735 });
15736 }
15737 if ancestor.kind() == "function_definition" {
15738 return ancestor
15739 .child_by_field_name("declarator")
15740 .is_some_and(|declarator| {
15741 declarator.start_byte() <= parameter.start_byte()
15742 && parameter.end_byte() <= declarator.end_byte()
15743 });
15744 }
15745 current = ancestor.parent();
15746 }
15747 false
15748}
15749
15750pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15751 let mut current = node.parent();
15752 while let Some(ancestor) = current {
15753 if matches!(
15754 ancestor.kind(),
15755 "parameter_declaration" | "optional_parameter_declaration"
15756 ) {
15757 return ancestor
15758 .child_by_field_name("type")
15759 .is_some_and(|type_node| {
15760 type_node.start_byte() <= node.start_byte()
15761 && node.end_byte() <= type_node.end_byte()
15762 });
15763 }
15764 if matches!(
15765 ancestor.kind(),
15766 "function_definition" | "lambda_expression" | "compound_statement"
15767 ) {
15768 return false;
15769 }
15770 current = ancestor.parent();
15771 }
15772 false
15773}
15774
15775fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15776 if specifier.child_by_field_name("body").is_some() {
15777 return true;
15778 }
15779 let mut current = specifier.parent();
15780 while let Some(ancestor) = current {
15781 match ancestor.kind() {
15782 "type_descriptor"
15783 | "parameter_declaration"
15784 | "optional_parameter_declaration"
15785 | "template_argument_list"
15786 | "cast_expression" => return false,
15787 "declaration" | "field_declaration" => {
15788 let mut cursor = ancestor.walk();
15789 return ancestor
15790 .children_by_field_name("declarator", &mut cursor)
15791 .next()
15792 .is_none();
15793 }
15794 "translation_unit" => return true,
15795 _ => current = ancestor.parent(),
15796 }
15797 }
15798 false
15799}
15800
15801pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15802 match node.kind() {
15803 "identifier"
15804 | "field_identifier"
15805 | "qualified_identifier"
15806 | "scoped_identifier"
15807 | "operator_name"
15808 | "destructor_name"
15809 | "literal_operator_name" => Some(node),
15810 "reference_declarator" | "parenthesized_declarator" => {
15811 node.named_child(0).and_then(declarator_name_node)
15812 }
15813 _ => node
15814 .child_by_field_name("declarator")
15815 .or_else(|| node.child_by_field_name("name"))
15816 .or_else(|| node.child_by_field_name("field"))
15817 .and_then(declarator_name_node),
15818 }
15819}
15820
15821fn declarator_name_path_contains(
15822 declarator: Node<'_>,
15823 candidate: Node<'_>,
15824 allow_type_identifier: bool,
15825) -> bool {
15826 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15827 return false;
15828 };
15829 let mut current = Some(declarator);
15830 while let Some(node) = current {
15831 if same_node(node, candidate) {
15832 return true;
15833 }
15834 if same_node(node, name) {
15835 return false;
15836 }
15837 current = node
15838 .child_by_field_name("declarator")
15839 .or_else(|| node.child_by_field_name("name"))
15840 .or_else(|| node.child_by_field_name("field"));
15841 }
15842 false
15843}
15844
15845fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15846 match node.kind() {
15847 "identifier"
15848 | "field_identifier"
15849 | "operator_name"
15850 | "destructor_name"
15851 | "literal_operator_name" => Some(node),
15852 "type_identifier" if allow_type_identifier => Some(node),
15853 _ => node
15854 .child_by_field_name("declarator")
15855 .or_else(|| node.child_by_field_name("name"))
15856 .or_else(|| node.child_by_field_name("field"))
15857 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15858 }
15859}
15860
15861pub fn is_nested_type_node(node: Node<'_>) -> bool {
15864 node.parent().is_some_and(|parent| {
15865 matches!(
15866 parent.kind(),
15867 "qualified_identifier" | "scoped_type_identifier" | "template_type"
15868 )
15869 })
15870}
15871
15872pub struct OutOfLineMemberDefinitionOwners<'tree> {
15873 pub owners: Vec<(Node<'tree>, CodeUnit)>,
15874 innermost: Option<(Node<'tree>, CodeUnit)>,
15875}
15876
15877impl OutOfLineMemberDefinitionOwners<'_> {
15878 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15879 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15880 }
15881}
15882
15883pub struct QualifiedOwnerComponents<'tree> {
15884 pub nodes: Vec<Node<'tree>>,
15885 pub names: Vec<String>,
15886 pub global: bool,
15887}
15888
15889pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15894 let mut stack = vec![node];
15895 let mut found_separator = false;
15896 while let Some(current) = stack.pop() {
15897 if !matches!(
15898 current.kind(),
15899 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15900 ) {
15901 continue;
15902 }
15903 let mut current_has_separator = false;
15904 for child in children_iter(current) {
15905 if child.kind() == "::" {
15906 if child.is_missing() {
15907 return false;
15908 }
15909 current_has_separator = true;
15910 found_separator = true;
15911 }
15912 }
15913 if !current_has_separator {
15914 return false;
15915 }
15916 for field in ["scope", "name"] {
15917 if let Some(child) = current.child_by_field_name(field)
15918 && matches!(
15919 child.kind(),
15920 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15921 )
15922 {
15923 stack.push(child);
15924 }
15925 }
15926 }
15927 found_separator
15928}
15929
15930pub fn qualified_owner_components<'tree>(
15931 node: Node<'tree>,
15932 source: &str,
15933) -> Option<QualifiedOwnerComponents<'tree>> {
15934 if !qualified_name_has_concrete_scope_separators(node) {
15935 return None;
15936 }
15937 let mut nodes = cpp_name_component_nodes(node)?;
15938 nodes.pop()?;
15939 if nodes.is_empty() {
15940 return None;
15941 }
15942 let names = nodes
15943 .iter()
15944 .map(|component| node_text(*component, source).to_string())
15945 .collect();
15946 Some(QualifiedOwnerComponents {
15947 nodes,
15948 names,
15949 global: is_globally_qualified_cpp_name(node),
15950 })
15951}
15952
15953pub fn out_of_line_member_definition_owner<'tree>(
15954 analyzer: &CppGraphSource<'_>,
15955 visibility: &VisibilityIndex<'_>,
15956 file: &ProjectFile,
15957 source: &str,
15958 node: Node<'tree>,
15959) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15960 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15961 || !has_ancestor_kind(node, "function_definition")
15962 || !is_function_declarator_name_root(node)
15963 {
15964 return None;
15965 }
15966 let qualified = qualified_owner_components(node, source)?;
15967 let lexical_scope = enclosing_namespace_components(node, source)?;
15968 let mut owners = Vec::new();
15969 let mut innermost = None;
15970
15971 for component_count in 1..=qualified.names.len() {
15972 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15973 .resolve_type_components_lexically(
15974 analyzer,
15975 file,
15976 &qualified.names[..component_count],
15977 qualified.global,
15978 &lexical_scope,
15979 )
15980 && !owners
15981 .iter()
15982 .any(|(_, existing)| same_visible_symbol(existing, &unit))
15983 {
15984 if component_count == qualified.names.len() {
15985 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
15986 }
15987 owners.push((qualified.nodes[component_count - 1], unit));
15988 }
15989 }
15990
15991 if innermost.is_none() {
16001 let indexed_owner_components = visibility
16002 .indexed_enclosing_owner_scope(analyzer, file, node)
16003 .or_else(|| {
16004 if qualified.names.len() <= 1 {
16009 return None;
16010 }
16011 let range = Range {
16012 start_byte: node.start_byte(),
16013 end_byte: node.end_byte(),
16014 start_line: node.start_position().row,
16015 end_line: node.end_position().row,
16016 };
16017 let start = analyzer.enclosing_code_unit(file, &range)?;
16018 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16019 brokk_bifrost_core::analyzer::Language::Cpp,
16020 &cpp_name_for(&start),
16021 );
16022 components.pop();
16023 Some(components)
16024 });
16025 if let Some(indexed_owner_components) = indexed_owner_components
16026 && indexed_owner_components.len() > qualified.names.len()
16027 && indexed_owner_components.ends_with(&qualified.names)
16028 && indexed_namespace_path_is_recoverable(
16029 &lexical_scope,
16030 &indexed_owner_components,
16031 qualified.names.len(),
16032 )
16033 && (qualified.names.len() > 1 || !qualified.global)
16038 {
16039 let namespace_count = indexed_owner_components.len() - qualified.names.len();
16040 for component_count in 1..=qualified.names.len() {
16041 let expected = &indexed_owner_components[..namespace_count + component_count];
16042 let owner_node = qualified.nodes[component_count - 1];
16043 for owner in visibility
16044 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16045 .filter(|candidate| candidate.is_class())
16046 .filter(|candidate| {
16047 canonical_cpp_scope_components(candidate) == expected
16048 && visibility.external_type_candidate_visible_in_context(
16049 analyzer, file, candidate, node,
16050 )
16051 })
16052 {
16053 if component_count == qualified.names.len() && innermost.is_none() {
16054 innermost = Some((owner_node, owner.clone()));
16055 }
16056 if !owners
16057 .iter()
16058 .any(|(_, existing)| same_symbol(existing, owner))
16059 {
16060 owners.push((owner_node, owner.clone()));
16061 }
16062 }
16063 }
16064 }
16065 }
16066 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16067}
16068
16069fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16070 let mut current = node;
16071 while let Some(parent) = current.parent() {
16072 if parent.kind() == "function_declarator" {
16073 return parent.child_by_field_name("declarator") == Some(current);
16074 }
16075 if matches!(
16076 parent.kind(),
16077 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16078 ) && parent.child_by_field_name("declarator") == Some(current)
16079 {
16080 current = parent;
16081 continue;
16082 }
16083 return false;
16084 }
16085 false
16086}
16087
16088pub fn append_cpp_name_components(
16089 node: Node<'_>,
16090 source: &str,
16091 out: &mut Vec<String>,
16092) -> Option<()> {
16093 out.extend(
16094 cpp_name_component_nodes(node)?
16095 .into_iter()
16096 .map(|component| node_text(component, source).to_string()),
16097 );
16098 Some(())
16099}
16100
16101pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16102 let mut components = Vec::new();
16103 append_cpp_name_components(node, source, &mut components)?;
16104 Some(components)
16105}
16106
16107pub fn unique_macro_replacement_type_candidate(
16116 analyzer: &CppGraphSource<'_>,
16117 visibility: &VisibilityIndex<'_>,
16118 file: &ProjectFile,
16119 components: &[String],
16120) -> Option<CodeUnit> {
16121 let terminal = components.last()?;
16122 let mut candidates = Vec::new();
16123 for candidate in visibility
16124 .visible_identifier_candidates(file, terminal)
16125 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16126 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16127 {
16128 if !candidates
16129 .iter()
16130 .any(|existing| same_logical_symbol(existing, candidate))
16131 {
16132 candidates.push(candidate.clone());
16133 }
16134 }
16135 (candidates.len() == 1).then(|| candidates.remove(0))
16136}
16137
16138pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16146 let mut parser = Parser::new();
16147 if parser
16148 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16149 .is_err()
16150 {
16151 return Vec::new();
16152 }
16153 let Some(tree) = parser.parse(source, None) else {
16154 return Vec::new();
16155 };
16156 let mut scopes = Vec::new();
16157 let mut pending = vec![tree.root_node()];
16158 while let Some(node) = pending.pop() {
16159 if node.kind() == "using_declaration" {
16160 let Some(imported) = node.named_child(0) else {
16161 continue;
16162 };
16163 let Some(mut components) = cpp_type_name_components(imported, source) else {
16164 continue;
16165 };
16166 if components.pop().as_deref() == Some(member) && !components.is_empty() {
16167 scopes.push(components.join("::"));
16168 }
16169 continue;
16170 }
16171 push_named_children_reversed(node, &mut pending);
16172 }
16173 scopes
16174}
16175
16176pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16180 qualified == scope
16181 || qualified
16182 .strip_suffix(scope)
16183 .is_some_and(|prefix| prefix.ends_with("::"))
16184}
16185
16186pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16191 let Some(type_descriptor) = node.parent() else {
16192 return false;
16193 };
16194 if type_descriptor.kind() != "type_descriptor"
16195 || type_descriptor.child_by_field_name("type") != Some(node)
16196 {
16197 return false;
16198 }
16199 let Some(arguments) = type_descriptor.parent() else {
16200 return false;
16201 };
16202 if arguments.kind() != "template_argument_list" {
16203 return false;
16204 }
16205 arguments.parent().is_some_and(|parent| {
16206 matches!(parent.kind(), "template_type" | "template_function")
16207 && parent.child_by_field_name("arguments") == Some(arguments)
16208 })
16209}
16210
16211pub fn cpp_template_reference_arguments(
16212 mut node: Node<'_>,
16213 source: &str,
16214) -> Option<Vec<CppTemplateExpression>> {
16215 loop {
16216 match node.kind() {
16217 "template_type" | "template_function" => {
16218 let arguments = node.child_by_field_name("arguments")?;
16219 let mut cursor = arguments.walk();
16220 return Some(
16221 arguments
16222 .named_children(&mut cursor)
16223 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
16224 .map(|argument| CppTemplateExpression {
16225 text: normalize_cpp_whitespace(node_text(argument, source)),
16226 term: cpp_template_term(
16228 argument,
16229 source,
16230 &[],
16231 &ParentIndex::unindexed(),
16232 ),
16233 })
16234 .collect(),
16235 );
16236 }
16237 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
16238 node = node
16239 .child_by_field_name("name")
16240 .or_else(|| node.child_by_field_name("type"))?;
16241 }
16242 _ => return None,
16243 }
16244 }
16245}
16246
16247fn cpp_reconcile_primary_template_parameters(
16248 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
16249 preferred: &CodeUnit,
16250) -> Option<Vec<CppTemplateParameterMetadata>> {
16251 let canonical = candidates
16252 .iter()
16253 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
16254 let mut merged = canonical
16255 .parameters
16256 .iter()
16257 .map(|parameter| CppTemplateParameterMetadata {
16258 name: parameter.name.clone(),
16259 kind: parameter.kind,
16260 variadic: parameter.variadic,
16261 default: None,
16262 })
16263 .collect::<Vec<_>>();
16264
16265 for (_, metadata) in candidates {
16266 if metadata.parameters.len() != merged.len() {
16267 return None;
16268 }
16269 let rename_bindings = metadata
16270 .parameters
16271 .iter()
16272 .zip(&merged)
16273 .map(|(parameter, canonical)| {
16274 (
16275 parameter.name.clone(),
16276 CppTemplateTerm::Parameter(canonical.name.clone()),
16277 )
16278 })
16279 .collect::<HashMap<_, _>>();
16280 for ((parameter, canonical), merged_parameter) in metadata
16281 .parameters
16282 .iter()
16283 .zip(&canonical.parameters)
16284 .zip(&mut merged)
16285 {
16286 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
16287 return None;
16288 }
16289 let Some(default) = ¶meter.default else {
16290 continue;
16291 };
16292 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
16293 if let Some(existing) = &merged_parameter.default {
16294 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
16295 return None;
16296 }
16297 } else {
16298 merged_parameter.default = Some(CppTemplateExpression {
16299 text: default.text.clone(),
16300 term: normalized_term,
16301 });
16302 }
16303 }
16304 }
16305 Some(merged)
16306}
16307
16308pub fn cpp_bind_template_arguments(
16309 parameters: &[CppTemplateParameterMetadata],
16310 explicit_arguments: &[CppTemplateExpression],
16311) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16312 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16313 if variadic_index.is_some_and(|index| {
16314 index + 1 != parameters.len()
16315 || parameters[index + 1..]
16316 .iter()
16317 .any(|parameter| parameter.variadic)
16318 }) {
16319 return None;
16320 }
16321 let fixed_count = variadic_index.unwrap_or(parameters.len());
16322 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16323 return None;
16324 }
16325 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16326 let mut expanded = explicit_arguments[..explicit_fixed_count]
16327 .iter()
16328 .map(cpp_clone_template_expression_iterative)
16329 .collect::<Vec<_>>();
16330 let mut bindings = HashMap::default();
16331 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16332 bindings.insert(
16333 parameter.name.clone(),
16334 cpp_clone_template_term_iterative(&argument.term),
16335 );
16336 }
16337 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
16338 let default = parameter.default.as_ref()?;
16339 let term = cpp_substitute_template_term(&default.term, &bindings)?;
16340 bindings.insert(parameter.name.clone(), term.clone());
16341 expanded.push(CppTemplateExpression {
16342 text: default.text.clone(),
16343 term,
16344 });
16345 }
16346 if let Some(index) = variadic_index {
16347 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16348 expanded.extend(
16349 packed_arguments
16350 .iter()
16351 .map(cpp_clone_template_expression_iterative),
16352 );
16353 bindings.insert(
16354 parameters[index].name.clone(),
16355 CppTemplateTerm::Node {
16356 kind: "parameter_pack".to_string(),
16357 children: packed_arguments
16358 .iter()
16359 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16360 .collect(),
16361 },
16362 );
16363 }
16364 Some((expanded, bindings))
16365}
16366
16367fn cpp_specialization_matches(
16368 metadata: &CppTemplateMetadata,
16369 arguments: &[CppTemplateExpression],
16370) -> bool {
16371 if metadata.specialization_arguments.len() != arguments.len() {
16372 return false;
16373 }
16374 let parameter_names = metadata
16375 .parameters
16376 .iter()
16377 .map(|parameter| parameter.name.as_str())
16378 .collect::<HashSet<_>>();
16379 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16380 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
16381 if !cpp_unify_template_term(
16382 &pattern.term,
16383 &argument.term,
16384 ¶meter_names,
16385 &mut bindings,
16386 ) {
16387 return false;
16388 }
16389 }
16390 true
16391}
16392
16393fn cpp_specialization_more_specialized(
16394 candidate: &CppTemplateMetadata,
16395 other: &CppTemplateMetadata,
16396) -> bool {
16397 cpp_specialization_pattern_accepts(other, candidate)
16398 && !cpp_specialization_pattern_accepts(candidate, other)
16399}
16400
16401fn cpp_specialization_pattern_accepts(
16402 broader: &CppTemplateMetadata,
16403 narrower: &CppTemplateMetadata,
16404) -> bool {
16405 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
16406 return false;
16407 }
16408 let parameter_names = broader
16409 .parameters
16410 .iter()
16411 .map(|parameter| parameter.name.as_str())
16412 .collect::<HashSet<_>>();
16413 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16414 broader
16415 .specialization_arguments
16416 .iter()
16417 .zip(&narrower.specialization_arguments)
16418 .all(|(pattern, argument)| {
16419 cpp_unify_template_term(
16420 &pattern.term,
16421 &argument.term,
16422 ¶meter_names,
16423 &mut bindings,
16424 )
16425 })
16426}
16427
16428pub fn cpp_substitute_template_term(
16429 term: &CppTemplateTerm,
16430 bindings: &HashMap<String, CppTemplateTerm>,
16431) -> Option<CppTemplateTerm> {
16432 enum Work<'a> {
16433 Visit(&'a CppTemplateTerm),
16434 Build { kind: String, child_count: usize },
16435 }
16436
16437 let mut work = vec![Work::Visit(term)];
16438 let mut substituted = Vec::new();
16439 while let Some(next) = work.pop() {
16440 match next {
16441 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16442 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16443 }
16444 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16445 substituted.push(CppTemplateTerm::Atom {
16446 kind: kind.clone(),
16447 text: text.clone(),
16448 });
16449 }
16450 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16451 work.push(Work::Build {
16452 kind: kind.clone(),
16453 child_count: children.len(),
16454 });
16455 work.extend(children.iter().rev().map(Work::Visit));
16456 }
16457 Work::Build { kind, child_count } => {
16458 let children = substituted.split_off(substituted.len() - child_count);
16459 substituted.push(CppTemplateTerm::Node { kind, children });
16460 }
16461 }
16462 }
16463 substituted.pop()
16464}
16465
16466pub fn cpp_substitute_template_arguments(
16467 arguments: &[CppTemplateExpression],
16468 bindings: &HashMap<String, CppTemplateTerm>,
16469) -> Option<Vec<CppTemplateExpression>> {
16470 let mut substituted = Vec::new();
16471 for argument in arguments {
16472 let CppTemplateTerm::Node { kind, children } = &argument.term else {
16473 substituted.push(CppTemplateExpression {
16474 text: argument.text.clone(),
16475 term: cpp_substitute_template_term(&argument.term, bindings)?,
16476 });
16477 continue;
16478 };
16479 if kind != "parameter_pack_expansion" {
16480 substituted.push(CppTemplateExpression {
16481 text: argument.text.clone(),
16482 term: cpp_substitute_template_term(&argument.term, bindings)?,
16483 });
16484 continue;
16485 }
16486 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16487 return None;
16488 };
16489 if ellipsis != "..." {
16490 return None;
16491 }
16492
16493 let mut pack_names = Vec::new();
16494 let mut work = vec![pattern];
16495 while let Some(term) = work.pop() {
16496 match term {
16497 CppTemplateTerm::Parameter(name)
16498 if matches!(
16499 bindings.get(name),
16500 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16501 ) =>
16502 {
16503 if !pack_names.contains(name) {
16504 pack_names.push(name.clone());
16505 }
16506 }
16507 CppTemplateTerm::Node { children, .. } => work.extend(children),
16508 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16509 }
16510 }
16511 let first_pack = pack_names.first()?;
16512 let CppTemplateTerm::Node {
16513 children: first_elements,
16514 ..
16515 } = bindings.get(first_pack)?
16516 else {
16517 return None;
16518 };
16519 let pack_len = first_elements.len();
16520 for pack_name in &pack_names {
16521 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16522 return None;
16523 };
16524 if children.len() != pack_len {
16525 return None;
16526 }
16527 }
16528 for index in 0..pack_len {
16529 let mut element_bindings = bindings.clone();
16530 for pack_name in &pack_names {
16531 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16532 return None;
16533 };
16534 element_bindings.insert(
16535 pack_name.clone(),
16536 cpp_clone_template_term_iterative(&children[index]),
16537 );
16538 }
16539 substituted.push(CppTemplateExpression {
16540 text: argument.text.clone(),
16541 term: cpp_substitute_template_term(pattern, &element_bindings)?,
16542 });
16543 }
16544 }
16545 Some(substituted)
16546}
16547
16548fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16549 enum Work<'a> {
16550 Visit(&'a CppTemplateTerm),
16551 Build { kind: String, child_count: usize },
16552 }
16553
16554 let mut work = vec![Work::Visit(term)];
16555 let mut cloned = Vec::new();
16556 while let Some(next) = work.pop() {
16557 match next {
16558 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16559 cloned.push(CppTemplateTerm::Parameter(name.clone()));
16560 }
16561 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16562 cloned.push(CppTemplateTerm::Atom {
16563 kind: kind.clone(),
16564 text: text.clone(),
16565 });
16566 }
16567 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16568 work.push(Work::Build {
16569 kind: kind.clone(),
16570 child_count: children.len(),
16571 });
16572 work.extend(children.iter().rev().map(Work::Visit));
16573 }
16574 Work::Build { kind, child_count } => {
16575 let children = cloned.split_off(cloned.len() - child_count);
16576 cloned.push(CppTemplateTerm::Node { kind, children });
16577 }
16578 }
16579 }
16580 cloned
16581 .pop()
16582 .expect("template term traversal emits one root")
16583}
16584
16585fn cpp_clone_template_expression_iterative(
16586 expression: &CppTemplateExpression,
16587) -> CppTemplateExpression {
16588 CppTemplateExpression {
16589 text: expression.text.clone(),
16590 term: cpp_clone_template_term_iterative(&expression.term),
16591 }
16592}
16593
16594pub fn cpp_unify_template_term(
16595 pattern: &CppTemplateTerm,
16596 argument: &CppTemplateTerm,
16597 parameters: &HashSet<&str>,
16598 bindings: &mut HashMap<String, CppTemplateTerm>,
16599) -> bool {
16600 let mut work = vec![(pattern, argument)];
16601 while let Some((pattern, argument)) = work.pop() {
16602 match pattern {
16603 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16604 if let Some(bound) = bindings.get(name) {
16605 if !cpp_template_terms_equal(bound, argument) {
16606 return false;
16607 }
16608 } else {
16609 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16610 }
16611 }
16612 CppTemplateTerm::Atom {
16613 kind: pattern_kind,
16614 text: pattern_text,
16615 } => {
16616 if !matches!(
16617 argument,
16618 CppTemplateTerm::Atom { kind, text }
16619 if kind == pattern_kind && text == pattern_text
16620 ) {
16621 return false;
16622 }
16623 }
16624 CppTemplateTerm::Node {
16625 kind: pattern_kind,
16626 children: pattern_children,
16627 } => {
16628 let CppTemplateTerm::Node { kind, children } = argument else {
16629 return false;
16630 };
16631 if kind != pattern_kind || children.len() != pattern_children.len() {
16632 return false;
16633 }
16634 work.extend(pattern_children.iter().zip(children).rev());
16635 }
16636 CppTemplateTerm::Parameter(_) => return false,
16637 }
16638 }
16639 true
16640}
16641
16642fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16643 let mut work = vec![(left, right)];
16644 while let Some((left, right)) = work.pop() {
16645 match (left, right) {
16646 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16647 if left != right {
16648 return false;
16649 }
16650 }
16651 (
16652 CppTemplateTerm::Atom {
16653 kind: left_kind,
16654 text: left_text,
16655 },
16656 CppTemplateTerm::Atom {
16657 kind: right_kind,
16658 text: right_text,
16659 },
16660 ) => {
16661 if left_kind != right_kind || left_text != right_text {
16662 return false;
16663 }
16664 }
16665 (
16666 CppTemplateTerm::Node {
16667 kind: left_kind,
16668 children: left_children,
16669 },
16670 CppTemplateTerm::Node {
16671 kind: right_kind,
16672 children: right_children,
16673 },
16674 ) => {
16675 if left_kind != right_kind || left_children.len() != right_children.len() {
16676 return false;
16677 }
16678 work.extend(left_children.iter().zip(right_children).rev());
16679 }
16680 _ => return false,
16681 }
16682 }
16683 true
16684}
16685
16686pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16687 let mut components = Vec::new();
16688 let mut stack = vec![node];
16689 while let Some(current) = stack.pop() {
16690 match current.kind() {
16691 "identifier"
16692 | "field_identifier"
16693 | "namespace_identifier"
16694 | "type_identifier"
16695 | "operator_name"
16696 | "destructor_name" => components.push(current),
16697 "template_type" | "template_function" => {
16698 stack.push(current.child_by_field_name("name")?);
16699 }
16700 "dependent_name" => stack.push(current.named_child(0)?),
16701 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16702 stack.push(current.child_by_field_name("name")?);
16703 if let Some(scope) = current.child_by_field_name("scope") {
16704 stack.push(scope);
16705 }
16706 }
16707 "nested_namespace_specifier" => {
16708 for index in (0..current.named_child_count()).rev() {
16709 stack.push(current.named_child(index)?);
16710 }
16711 }
16712 _ => return None,
16713 }
16714 }
16715 Some(components)
16716}
16717
16718pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16719 node.child_by_field_name("scope").is_none()
16720 && node.child(0).is_some_and(|child| child.kind() == "::")
16721}
16722
16723fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16724 let mut namespaces = Vec::new();
16725 let mut current = node.parent();
16726 while let Some(parent) = current {
16727 if parent.kind() == "namespace_definition"
16728 && let Some(name) = parent.child_by_field_name("name")
16729 {
16730 let mut components = Vec::new();
16731 append_cpp_name_components(name, source, &mut components)?;
16732 namespaces.push(components);
16733 }
16734 current = parent.parent();
16735 }
16736 namespaces.reverse();
16737 Some(namespaces.into_iter().flatten().collect())
16738}
16739
16740fn indexed_namespace_path_is_recoverable(
16751 lexical_scope: &[String],
16752 indexed_owner_scope: &[String],
16753 explicit_owner_component_count: usize,
16754) -> bool {
16755 if lexical_scope.is_empty() {
16756 return explicit_owner_component_count > 1;
16757 }
16758 if lexical_scope.len() >= indexed_owner_scope.len() {
16759 return false;
16760 }
16761 let mut indexed = indexed_owner_scope.iter();
16762 lexical_scope
16763 .iter()
16764 .all(|component| indexed.any(|candidate| candidate == component))
16765}
16766
16767pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16768 let mut current = node.parent();
16769 while let Some(parent) = current {
16770 if parent.kind() == kind {
16771 return true;
16772 }
16773 current = parent.parent();
16774 }
16775 false
16776}
16777
16778pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16784 let mut current = Some(node);
16785 while let Some(candidate) = current {
16786 if candidate.kind() == "declaration" {
16787 let Some(type_node) = candidate.child_by_field_name("type") else {
16788 return false;
16789 };
16790 if !(type_node.start_byte() <= node.start_byte()
16791 && node.end_byte() <= type_node.end_byte())
16792 {
16793 return false;
16794 }
16795 let mut cursor = candidate.walk();
16796 return candidate.named_children(&mut cursor).any(|child| {
16797 child.kind() == "init_declarator"
16798 && child
16799 .child_by_field_name("value")
16800 .is_some_and(|value| value.kind() == "cast_expression")
16801 });
16802 }
16803 current = candidate.parent();
16804 }
16805 false
16806}
16807
16808#[derive(Clone, Copy, PartialEq, Eq)]
16809pub(crate) enum QualifiedAliasReferenceKind {
16810 Ordinary,
16811 ConstructorWithExpressionArgument,
16812 ExhaustiveTemplate,
16813}
16814
16815pub(crate) fn qualified_alias_reference_preserves_target(
16822 node: Node<'_>,
16823 target: &CodeUnit,
16824 analyzer: &CppGraphSource<'_>,
16825 visibility: &VisibilityIndex<'_>,
16826 file: &ProjectFile,
16827 source: &str,
16828) -> Option<QualifiedAliasReferenceKind> {
16829 if !matches!(
16830 node.kind(),
16831 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16832 ) {
16833 return None;
16834 }
16835 let components = cpp_type_name_components(node, source)?;
16836 let name = components.last()?;
16837 analyzer.type_alias_provider().and_then(|provider| {
16838 visibility
16839 .visible_identifier_candidates(file, name)
16840 .find_map(|candidate| {
16841 let proof = provider.is_type_alias(candidate)
16842 && canonical_cpp_scope_components(candidate) == components
16843 && visibility.external_type_candidate_visible_in_context(
16844 analyzer, file, candidate, node,
16845 )
16846 && match cpp_template_reference_arguments(node, source) {
16847 Some(arguments) => visibility.template_alias_arguments_preserve_target(
16848 analyzer, file, candidate, &arguments, target,
16849 ),
16850 None => visibility.structured_alias_primary_preserves_target(
16851 analyzer, file, candidate, target,
16852 ),
16853 };
16854 proof.then(|| {
16855 if cpp_template_reference_arguments(node, source).is_some()
16856 && visibility.is_exhaustive_same_fqn_type_declaration_family(
16857 analyzer, file, candidate,
16858 )
16859 {
16860 QualifiedAliasReferenceKind::ExhaustiveTemplate
16861 } else if qualified_alias_constructor_has_expression_argument(node)
16862 || qualified_alias_local_constructor_declaration(node)
16863 {
16864 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16865 } else {
16866 QualifiedAliasReferenceKind::Ordinary
16867 }
16868 })
16869 })
16870 })
16871}
16872
16873pub(crate) fn qualified_alias_reference_requires_terminal(
16874 reference: Option<QualifiedAliasReferenceKind>,
16875) -> bool {
16876 matches!(
16877 reference,
16878 Some(
16879 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16880 | QualifiedAliasReferenceKind::ExhaustiveTemplate
16881 )
16882 )
16883}
16884
16885fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16886 let Some(declaration) = node.parent().filter(|parent| {
16887 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16888 }) else {
16889 return false;
16890 };
16891 let mut cursor = declaration.walk();
16892 declaration.named_children(&mut cursor).any(|child| {
16893 child.kind() == "init_declarator"
16894 && child
16895 .child_by_field_name("value")
16896 .filter(|value| value.kind() == "argument_list")
16897 .is_some_and(|arguments| {
16898 let mut cursor = arguments.walk();
16899 arguments.named_children(&mut cursor).any(|argument| {
16900 let is_parameter = matches!(
16901 argument.kind(),
16902 "parameter_declaration" | "optional_parameter_declaration"
16903 );
16904 if is_parameter {
16905 argument
16906 .child_by_field_name("type")
16907 .is_some_and(|type_node| {
16908 type_node.kind() == "type_identifier"
16909 && argument.child_by_field_name("declarator").is_none()
16910 })
16911 } else {
16912 !argument.kind().ends_with("_literal")
16913 && !matches!(argument.kind(), "true" | "false" | "nullptr")
16914 }
16915 })
16916 })
16917 })
16918}
16919
16920fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16925 let Some(declaration) = node.parent().filter(|parent| {
16926 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16927 }) else {
16928 return false;
16929 };
16930 if declaration
16931 .parent()
16932 .is_none_or(|parent| parent.kind() != "compound_statement")
16933 {
16934 return false;
16935 }
16936 let mut cursor = declaration.walk();
16937 declaration
16938 .named_children(&mut cursor)
16939 .any(|child| child.kind() == "function_declarator")
16940}
16941
16942pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16948 loop {
16949 let next = match node.kind() {
16950 "qualified_identifier"
16951 | "scoped_identifier"
16952 | "template_method"
16953 | "template_function"
16954 | "template_type" => node.child_by_field_name("name"),
16955 "field_expression" => node.child_by_field_name("field"),
16956 _ => None,
16957 };
16958 let Some(next) = next else {
16959 return node;
16960 };
16961 node = next;
16962 }
16963}
16964
16965#[derive(Clone, Copy)]
16966pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16967 pub receiver: Node<'tree>,
16968 pub member: Node<'tree>,
16969 pub arity: usize,
16970}
16971
16972pub fn recovered_relational_template_member_call(
16980 field: Node<'_>,
16981) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16982 if field.kind() != "field_expression" {
16983 return None;
16984 }
16985 let receiver = field
16986 .child_by_field_name("argument")
16987 .or_else(|| field.child_by_field_name("object"))?;
16988 let member = field.child_by_field_name("field")?;
16989 let less = field.parent()?;
16990 if less.kind() != "binary_expression"
16991 || less.child_by_field_name("left") != Some(field)
16992 || less
16993 .child_by_field_name("operator")
16994 .is_none_or(|operator| operator.kind() != "<")
16995 || less.child_by_field_name("right").is_none()
16996 {
16997 return None;
16998 }
16999 let greater = less.parent()?;
17000 if greater.kind() != "binary_expression"
17001 || greater.child_by_field_name("left") != Some(less)
17002 || greater
17003 .child_by_field_name("operator")
17004 .is_none_or(|operator| operator.kind() != ">")
17005 {
17006 return None;
17007 }
17008 let arguments = greater.child_by_field_name("right")?;
17009 if arguments.kind() != "parenthesized_expression" {
17010 return None;
17011 }
17012 let arity = parenthesized_call_argument_arity(arguments)?;
17013 Some(RecoveredRelationalTemplateMemberCall {
17014 receiver,
17015 member,
17016 arity,
17017 })
17018}
17019
17020fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17021 let expression = arguments.named_child(0)?;
17022 if expression.kind() != "comma_expression" {
17023 return Some(1);
17024 }
17025 let mut arity = 0usize;
17026 let mut stack = vec![expression];
17027 while let Some(node) = stack.pop() {
17028 if node.kind() == "comma_expression" {
17029 stack.push(node.child_by_field_name("right")?);
17030 stack.push(node.child_by_field_name("left")?);
17031 } else {
17032 arity += 1;
17033 }
17034 }
17035 Some(arity)
17036}
17037
17038pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17041 while let Some(parent) = node.parent() {
17042 match parent.kind() {
17043 "call_expression" => {
17044 return parent
17045 .child_by_field_name("function")
17046 .or_else(|| parent.named_child(0))
17047 == Some(node);
17048 }
17049 "qualified_identifier"
17050 | "scoped_identifier"
17051 | "template_function"
17052 | "template_type"
17053 | "field_expression" => node = parent,
17054 _ => return false,
17055 }
17056 }
17057 false
17058}
17059
17060pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
17061 if is_call_callee_node(node) {
17062 function_terminal_node(node)
17063 } else {
17064 node
17065 }
17066}
17067
17068pub fn normalize_type_text(value: &str) -> String {
17069 strip_tag_type_prefix(
17070 normalize_cpp_whitespace(value)
17071 .trim_start_matches("const ")
17072 .trim_end_matches('*')
17073 .trim_end_matches('&')
17074 .trim(),
17075 )
17076 .to_string()
17077}
17078
17079fn strip_tag_type_prefix(value: &str) -> &str {
17080 let value = value.trim_start_matches("const ");
17081 value
17082 .strip_prefix("struct ")
17083 .or_else(|| value.strip_prefix("class "))
17084 .or_else(|| value.strip_prefix("enum "))
17085 .unwrap_or(value)
17086 .trim()
17087}
17088
17089pub fn normalize_reference_name(value: &str) -> Option<String> {
17090 let normalized = normalize_cpp_reference_text(value);
17091 (!normalized.is_empty()).then_some(normalized)
17092}
17093
17094pub fn normalize_cpp_reference_text(value: &str) -> String {
17095 let mut text = normalize_cpp_whitespace(value)
17096 .trim_start_matches("new ")
17097 .trim()
17098 .to_string();
17099 if let Some(index) = text.find(['(', '{']) {
17100 text.truncate(index);
17101 }
17102 if let Some(index) = text.find('<') {
17103 text.truncate(index);
17104 }
17105 let normalized = text
17106 .trim()
17107 .trim_start_matches("const ")
17108 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17109 .trim_matches(':')
17110 .trim();
17111 strip_tag_type_prefix(normalized).to_string()
17112}
17113
17114pub fn cpp_name_for(unit: &CodeUnit) -> String {
17115 let short = unit.short_name().replace(['.', '$'], "::");
17116 if unit.package_name().is_empty() {
17117 short
17118 } else {
17119 format!("{}::{}", unit.package_name(), short)
17120 }
17121}
17122
17123fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17127 let fq = unit.fq();
17128 if fq.is_empty() {
17129 return None;
17130 }
17131 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17132 Some(
17133 fq.segments()
17134 .iter()
17135 .map(|&segment| interner.resolve(segment).0)
17136 .collect::<Vec<_>>()
17137 .join("::"),
17138 )
17139}
17140
17141fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17142 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17143 || unit.fq().is_empty() && cpp_name_for(unit) == expected
17144}
17145
17146pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17155 let fq = unit.fq();
17156 if !fq.is_empty() {
17157 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17158 let scope = fq
17159 .segments()
17160 .iter()
17161 .filter_map(|&segment| {
17162 let (text, kind) = interner.resolve(segment);
17163 matches!(
17164 kind,
17165 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17166 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17167 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17168 )
17169 .then(|| text.to_string())
17170 })
17171 .collect();
17172 return scope;
17173 }
17174 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17175 brokk_bifrost_core::analyzer::Language::Cpp,
17176 &cpp_name_for(unit),
17177 )
17178}
17179
17180pub fn terminal_name(value: &str) -> &str {
17191 value
17192 .rsplit("::")
17193 .next()
17194 .unwrap_or(value)
17195 .rsplit(['.', '-', '>'])
17196 .next()
17197 .unwrap_or(value)
17198 .trim()
17199}
17200
17201pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17202 terminal_name(&normalize_cpp_reference_text(value)) == expected
17203}
17204
17205pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17206 name_matches_terminal(value, expected)
17207 || expected.starts_with("operator")
17208 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17209}
17210
17211pub fn name_mentions(value: &str, expected: &str) -> bool {
17212 normalize_cpp_reference_text(value)
17213 .split("::")
17214 .any(|part| part == expected)
17215}
17216
17217pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17218 let cpp_name = cpp_name_for(unit);
17219 if reference.contains("::") {
17220 return reference == cpp_name;
17221 }
17222 reference == cpp_name
17223 || terminal_name(reference) == unit.identifier()
17224 && (unit.package_name().is_empty() || reference == unit.identifier())
17225}
17226
17227pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
17228 match kind {
17229 TargetKind::Type
17230 | TargetKind::Constructor
17231 | TargetKind::Method
17232 | TargetKind::MemberField => true,
17233 TargetKind::FreeFunction => unit.is_function(),
17234 TargetKind::GlobalField => unit.is_field(),
17235 TargetKind::Macro => unit.is_macro(),
17236 }
17237}
17238
17239pub fn is_type_alias(unit: &CodeUnit) -> bool {
17240 unit.kind() == CodeUnitType::Field
17241 && unit.signature().is_some_and(|signature| {
17242 signature.starts_with("typedef ") || signature.starts_with("using ")
17243 })
17244}
17245
17246fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
17247 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
17248 let target_name = cpp_name_for(target);
17249 if normalized.contains("::") {
17250 return normalized == target_name;
17251 }
17252 if let Some(namespace) = alias.namespace.as_deref() {
17253 return namespace_prefixes(namespace)
17254 .into_iter()
17255 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
17256 }
17257 target.package_name().is_empty() && normalized == target.identifier()
17258}
17259
17260pub fn cpp_function_return_type_text(
17263 analyzer: &CppGraphSource<'_>,
17264 function: &CodeUnit,
17265) -> Option<String> {
17266 let metadata = analyzer.signature_metadata(function);
17267 if !metadata.is_empty() {
17268 let first = metadata.first()?.return_type_text()?;
17269 return metadata
17270 .iter()
17271 .all(|metadata| metadata.return_type_text() == Some(first))
17272 .then(|| first.to_string());
17273 }
17274 let signature = cpp_function_signature_text(analyzer, function)?;
17275 cpp_function_return_type_text_from_signature(&signature)
17276}
17277
17278fn cpp_function_signature_text(
17279 analyzer: &CppGraphSource<'_>,
17280 function: &CodeUnit,
17281) -> Option<String> {
17282 function
17283 .signature()
17284 .filter(|signature| signature.contains(function.identifier()))
17285 .map(str::to_string)
17286 .or_else(|| analyzer.signatures(function).first().cloned())
17287 .or_else(|| analyzer.get_source(function, false))
17288}
17289
17290fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
17291 let open = signature.find('(')?;
17292 let name_at = cpp_function_name_start(signature, open)?;
17293 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
17294 return Some(return_type);
17295 }
17296 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
17297 .split_whitespace()
17298 .filter(|token| {
17299 !matches!(
17300 *token,
17301 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
17302 )
17303 })
17304 .collect::<Vec<_>>()
17305 .join(" ");
17306 let type_text = type_text.trim();
17307 (!type_text.is_empty()).then(|| type_text.to_string())
17308}
17309
17310fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
17311 let before_parameters = &signature[..open];
17312 if let Some(operator_at) = before_parameters.rfind("operator") {
17313 let boundary = operator_at == 0
17314 || before_parameters[..operator_at]
17315 .chars()
17316 .next_back()
17317 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
17318 if boundary {
17319 return Some(operator_at);
17320 }
17321 }
17322 before_parameters
17323 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
17324 .map(|index| index + 1)
17325}
17326
17327fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
17328 let open = signature_from_name.find('(')?;
17329 let mut depth = 0i32;
17330 for (offset, ch) in signature_from_name[open..].char_indices() {
17331 match ch {
17332 '(' => depth += 1,
17333 ')' => {
17334 depth -= 1;
17335 if depth == 0 {
17336 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
17337 let arrow = rest.find("->")?;
17338 let return_type = rest[arrow + 2..].trim_start();
17339 let return_type = return_type
17340 .split(['{', ';'])
17341 .next()
17342 .unwrap_or(return_type)
17343 .trim();
17344 return (!return_type.is_empty()).then(|| return_type.to_string());
17345 }
17346 }
17347 _ => {}
17348 }
17349 }
17350 None
17351}
17352
17353fn cpp_strip_leading_template_clause(text: &str) -> &str {
17356 let trimmed = text.trim_start();
17357 let Some(rest) = trimmed.strip_prefix("template") else {
17358 return text;
17359 };
17360 let rest = rest.trim_start();
17361 if !rest.starts_with('<') {
17362 return text;
17363 }
17364 let mut depth = 0i32;
17365 for (offset, ch) in rest.char_indices() {
17366 match ch {
17367 '<' => depth += 1,
17368 '>' => {
17369 depth -= 1;
17370 if depth == 0 {
17371 return rest[offset + ch.len_utf8()..].trim_start();
17372 }
17373 }
17374 _ => {}
17375 }
17376 }
17377 text
17378}
17379
17380pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17381 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17391 namespace
17392 .strip_prefix("anonymous_namespace::")
17393 .unwrap_or(namespace)
17394 .to_string()
17395 })
17396}
17397
17398fn namespace_prefixes(namespace: &str) -> Vec<String> {
17399 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17405 brokk_bifrost_core::analyzer::Language::Cpp,
17406 namespace,
17407 );
17408 let mut prefixes = Vec::new();
17409 while !parts.is_empty() {
17410 prefixes.push(parts.join("::"));
17411 parts.pop();
17412 }
17413 prefixes
17414}
17415
17416fn nearest_namespace_candidates(
17417 candidates: Vec<CodeUnit>,
17418 normalized: &str,
17419 lexical_namespace: Option<&str>,
17420) -> Vec<CodeUnit> {
17421 if normalized.contains("::") {
17422 return candidates;
17423 }
17424 if let Some(namespace) = lexical_namespace {
17425 for prefix in namespace_prefixes(namespace) {
17426 let scoped = candidates
17427 .iter()
17428 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17429 .cloned()
17430 .collect::<Vec<_>>();
17431 if !scoped.is_empty() {
17432 return scoped;
17433 }
17434 }
17435 }
17436 candidates
17437 .into_iter()
17438 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17439 .collect()
17440}
17441
17442pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17443 let mut namespaces = Vec::new();
17444 let mut current = node.parent();
17445 while let Some(parent) = current {
17446 if parent.kind() == "namespace_definition"
17447 && let Some(name) = parent.child_by_field_name("name")
17448 {
17449 let namespace = normalize_cpp_reference_text(node_text(name, source));
17450 if !namespace.is_empty() {
17451 namespaces.push(namespace);
17452 }
17453 }
17454 current = parent.parent();
17455 }
17456 if namespaces.is_empty() {
17457 None
17458 } else {
17459 namespaces.reverse();
17460 Some(namespaces.join("::"))
17461 }
17462}
17463
17464pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17468 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17469}
17470
17471fn type_owner_resolution(
17472 analyzer: &CppGraphSource<'_>,
17473 code_unit: &CodeUnit,
17474) -> Option<ResolvedTypeOwner> {
17475 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17476}
17477
17478fn target_type_owner_resolution(
17479 analyzer: &CppGraphSource<'_>,
17480 code_unit: &CodeUnit,
17481) -> Option<ResolvedTypeOwner> {
17482 match type_owner_resolution(analyzer, code_unit) {
17483 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17484 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17485 }
17486}
17487
17488fn target_forward_owner_resolution(
17500 analyzer: &CppGraphSource<'_>,
17501 code_unit: &CodeUnit,
17502) -> Option<ResolvedTypeOwner> {
17503 if !code_unit.is_function() {
17504 return None;
17505 }
17506 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17512 let cpp = analyzer.cpp?;
17513 let mut visible_files = HashSet::default();
17514 collect_include_closure(
17515 analyzer,
17516 cpp.include_target_index(),
17517 code_unit.source(),
17518 &mut visible_files,
17519 None,
17520 );
17521 let candidates = analyzer.workspace_definitions().exact(&owner_name);
17522 let visible_candidates = candidates
17523 .iter()
17524 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17525 .cloned()
17526 .collect::<Vec<_>>();
17527 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17528 DirectOwnerResolution::UniqueFull(unit) => {
17529 return Some(ResolvedTypeOwner {
17530 unit,
17531 is_forward_declaration: false,
17532 });
17533 }
17534 DirectOwnerResolution::ForwardsOnly(forwards) => {
17535 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17536 unit: forwards.into_iter().next().unwrap(),
17537 is_forward_declaration: true,
17538 });
17539 }
17540 DirectOwnerResolution::Ambiguous => return None,
17541 DirectOwnerResolution::None => {}
17542 }
17543
17544 let candidates = candidates
17545 .into_iter()
17546 .filter(|candidate| candidate.is_class())
17547 .collect::<Vec<_>>();
17548 let (unit, is_forward_declaration) =
17549 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17550 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17551 DirectOwnerResolution::ForwardsOnly(forwards) => {
17552 (unique_logical_forward_owner(forwards)?, true)
17553 }
17554 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17555 };
17556 Some(ResolvedTypeOwner {
17557 unit,
17558 is_forward_declaration,
17559 })
17560}
17561
17562pub fn precise_parent_of(
17563 analyzer: &CppGraphSource<'_>,
17564 visibility: &VisibilityIndex<'_>,
17565 code_unit: &CodeUnit,
17566) -> Option<CodeUnit> {
17567 visibility.cached_precise_parent_of(analyzer, code_unit)
17568}
17569
17570fn precise_parent_resolution(
17571 analyzer: &CppGraphSource<'_>,
17572 code_unit: &CodeUnit,
17573) -> Option<ResolvedTypeOwner> {
17574 #[cfg(any(test, feature = "test-support"))]
17575 if let Some(cpp) = analyzer.cpp {
17576 cpp.record_cpp_parent_resolution_for_test();
17577 }
17578 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17579 return Some(ResolvedTypeOwner {
17580 unit,
17581 is_forward_declaration: false,
17582 });
17583 }
17584 let fallback = analyzer.parent_of(code_unit);
17585 if !code_unit.owner_is_type_scope() {
17586 return fallback.map(|unit| ResolvedTypeOwner {
17587 unit,
17588 is_forward_declaration: false,
17589 });
17590 }
17591 let owner_fq = code_unit
17592 .fq()
17593 .parent()
17594 .expect("a unit with an owner identifier has a structured parent");
17595 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17596 match same_source_owner(analyzer, code_unit, &owner_candidates) {
17597 DirectOwnerResolution::UniqueFull(owner) => {
17598 return Some(ResolvedTypeOwner {
17599 unit: owner,
17600 is_forward_declaration: false,
17601 });
17602 }
17603 DirectOwnerResolution::Ambiguous => return None,
17604 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17605 }
17606 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17607 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17608 unit: owner,
17609 is_forward_declaration: false,
17610 }),
17611 DirectOwnerResolution::Ambiguous => None,
17612 DirectOwnerResolution::ForwardsOnly(forwards) => {
17613 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17614 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17615 unit: owner,
17616 is_forward_declaration: false,
17617 }),
17618 FullOwnerResolution::None => {
17619 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17620 unit,
17621 is_forward_declaration: true,
17622 })
17623 }
17624 FullOwnerResolution::Ambiguous => None,
17625 }
17626 }
17627 DirectOwnerResolution::None => {
17628 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17629 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17630 unit: owner,
17631 is_forward_declaration: false,
17632 }),
17633 FullOwnerResolution::Ambiguous => None,
17634 FullOwnerResolution::None => fallback
17635 .filter(|parent| {
17636 parent.source() == code_unit.source()
17637 && parent.fq() == &owner_fq
17638 && (!parent.is_class()
17639 || cpp_class_declaration_strength(analyzer, parent)
17640 == CppClassDeclarationStrength::Full)
17641 })
17642 .map(|unit| ResolvedTypeOwner {
17643 unit,
17644 is_forward_declaration: false,
17645 }),
17646 }
17647 }
17648 }
17649}
17650
17651fn exact_structural_type_parent(
17652 analyzer: &CppGraphSource<'_>,
17653 code_unit: &CodeUnit,
17654) -> Option<CodeUnit> {
17655 if !code_unit.is_function() && !code_unit.is_field() {
17656 return None;
17657 }
17658 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
17660 let parent = cpp.structural_parent_of(code_unit)?;
17661 (!parent.is_module()
17662 && parent.source() == code_unit.source()
17663 && parent.package_name() == code_unit.package_name()
17664 && parent.short_name() == encoded_owner)
17665 .then_some(parent)
17666}
17667
17668fn same_source_owner(
17669 analyzer: &CppGraphSource<'_>,
17670 code_unit: &CodeUnit,
17671 owner_candidates: &[CodeUnit],
17672) -> DirectOwnerResolution {
17673 let candidates = owner_candidates
17674 .iter()
17675 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17676 .cloned()
17677 .collect::<Vec<_>>();
17678 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17679 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17680}
17681
17682fn visible_full_cpp_owner(
17683 analyzer: &CppGraphSource<'_>,
17684 code_unit: &CodeUnit,
17685 owner_candidates: &[CodeUnit],
17686) -> FullOwnerResolution {
17687 let Some(cpp) = analyzer.cpp else {
17688 return FullOwnerResolution::None;
17689 };
17690 let mut visible_files = HashSet::default();
17691 collect_include_closure(
17692 analyzer,
17693 cpp.include_target_index(),
17694 code_unit.source(),
17695 &mut visible_files,
17696 None,
17697 );
17698 let candidates = owner_candidates
17699 .iter()
17700 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17701 .cloned()
17702 .collect::<Vec<_>>();
17703 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17704 let mut full_definition = None;
17705 for candidate in candidates {
17706 match cpp_class_declaration_strength(analyzer, &candidate) {
17707 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17708 return FullOwnerResolution::Ambiguous;
17709 }
17710 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17711 CppClassDeclarationStrength::Forward => {}
17712 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17713 }
17714 }
17715 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17716}
17717
17718pub enum DirectOwnerResolution {
17719 None,
17720 ForwardsOnly(Vec<CodeUnit>),
17721 UniqueFull(CodeUnit),
17722 Ambiguous,
17723}
17724
17725enum FullOwnerResolution {
17726 None,
17727 Unique(CodeUnit),
17728 Ambiguous,
17729}
17730
17731#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17732pub enum CppClassDeclarationStrength {
17733 Full,
17734 Forward,
17735 Unknown,
17736}
17737
17738fn directly_included_owner(
17739 analyzer: &CppGraphSource<'_>,
17740 code_unit: &CodeUnit,
17741 owner_candidates: &[CodeUnit],
17742) -> DirectOwnerResolution {
17743 let Some(cpp) = analyzer.cpp else {
17744 return DirectOwnerResolution::None;
17745 };
17746 let imports = analyzer.import_statements(code_unit.source());
17747 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
17748 .into_iter()
17749 .flat_map(|include| {
17750 resolve_include_targets_with_index(
17751 code_unit.source(),
17752 &include,
17753 cpp.include_target_index(),
17754 )
17755 })
17756 .collect();
17757 let candidates = owner_candidates
17758 .iter()
17759 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17760 .cloned()
17761 .collect::<Vec<_>>();
17762 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17763 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17764}
17765
17766fn prefer_member_declaring_owners(
17767 analyzer: &CppGraphSource<'_>,
17768 member: &CodeUnit,
17769 candidates: Vec<CodeUnit>,
17770) -> Vec<CodeUnit> {
17771 let matching = candidates
17772 .iter()
17773 .filter(|owner| owner_declares_member(analyzer, owner, member))
17774 .cloned()
17775 .collect::<Vec<_>>();
17776 if matching.is_empty() {
17777 candidates
17778 } else {
17779 matching
17780 }
17781}
17782
17783fn owner_declares_member(
17784 analyzer: &CppGraphSource<'_>,
17785 owner: &CodeUnit,
17786 member: &CodeUnit,
17787) -> bool {
17788 analyzer.direct_children(owner).into_iter().any(|child| {
17789 child.kind() == member.kind()
17790 && child.identifier() == member.identifier()
17791 && child.signature() == member.signature()
17792 })
17793}
17794
17795fn classify_direct_owner_candidates(
17796 analyzer: &CppGraphSource<'_>,
17797 candidates: impl Iterator<Item = CodeUnit>,
17798) -> DirectOwnerResolution {
17799 collapse_owner_candidates(candidates.map(|candidate| {
17800 let strength = cpp_class_declaration_strength(analyzer, &candidate);
17801 (candidate, strength)
17802 }))
17803}
17804
17805pub fn collapse_owner_candidates(
17806 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17807) -> DirectOwnerResolution {
17808 let mut full_definition = None;
17809 let mut forwards = Vec::new();
17810 for (candidate, strength) in candidates {
17811 match strength {
17812 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17813 return DirectOwnerResolution::Ambiguous;
17814 }
17815 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17816 CppClassDeclarationStrength::Forward => forwards.push(candidate),
17817 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17818 }
17819 }
17820 if let Some(owner) = full_definition {
17821 DirectOwnerResolution::UniqueFull(owner)
17822 } else if !forwards.is_empty() {
17823 DirectOwnerResolution::ForwardsOnly(forwards)
17824 } else {
17825 DirectOwnerResolution::None
17826 }
17827}
17828
17829#[cfg(any(test, feature = "test-support"))]
17830pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17831 unique_logical_forward_owner(forwards)
17832}
17833
17834fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17835 let first = forwards.pop()?;
17836 forwards
17837 .iter()
17838 .all(|forward| same_logical_symbol(forward, &first))
17839 .then_some(first)
17840}
17841
17842pub fn cpp_class_declaration_strength(
17843 analyzer: &CppGraphSource<'_>,
17844 candidate: &CodeUnit,
17845) -> CppClassDeclarationStrength {
17846 let Some(cpp) = analyzer.cpp else {
17854 return uncached_cpp_class_declaration_strength(analyzer, candidate);
17855 };
17856 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17857 return strength;
17858 }
17859 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17860 cpp.cache_class_declaration_strength(candidate, strength);
17861 strength
17862}
17863
17864fn uncached_cpp_class_declaration_strength(
17865 analyzer: &CppGraphSource<'_>,
17866 candidate: &CodeUnit,
17867) -> CppClassDeclarationStrength {
17868 if let Some(cpp) = analyzer.cpp
17869 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17870 {
17871 return cpp_class_declaration_strength_in_tree(
17872 analyzer,
17873 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17874 candidate,
17875 prepared.source(),
17876 prepared.tree().root_node(),
17877 );
17878 }
17879 let Some(source) = analyzer.indexed_source(candidate.source()) else {
17880 return CppClassDeclarationStrength::Unknown;
17881 };
17882 #[cfg(any(test, feature = "test-support"))]
17883 if let Some(cpp) = analyzer.cpp {
17884 cpp.record_cpp_class_strength_parse_for_test();
17885 }
17886 let mut parser = Parser::new();
17887 if parser
17888 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17889 .is_err()
17890 {
17891 return CppClassDeclarationStrength::Unknown;
17892 }
17893 let Some(tree) = parser.parse(&source, None) else {
17894 return CppClassDeclarationStrength::Unknown;
17895 };
17896 let recovered_export_classes =
17899 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17900 cpp_class_declaration_strength_in_tree(
17901 analyzer,
17902 &recovered_export_classes,
17903 candidate,
17904 &source,
17905 tree.root_node(),
17906 )
17907}
17908
17909fn cpp_class_declaration_strength_in_tree(
17910 analyzer: &CppGraphSource<'_>,
17911 recovered_export_classes: &CppRecoveredExportClassIndex,
17912 candidate: &CodeUnit,
17913 source: &str,
17914 root: Node<'_>,
17915) -> CppClassDeclarationStrength {
17916 let ranges = analyzer.ranges(candidate);
17917 let mut saw_forward = false;
17918 for range in ranges {
17919 match recovered_class_body_at(
17922 recovered_export_classes,
17923 root,
17924 source,
17925 candidate.identifier(),
17926 &range,
17927 ) {
17928 Some(true) => return CppClassDeclarationStrength::Full,
17929 Some(false) => {
17930 saw_forward = true;
17931 continue;
17932 }
17933 None => {}
17934 }
17935 let covers_range_start = |node: &Node<'_>| {
17942 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17943 };
17944 let mut stack = Vec::new();
17945 if covers_range_start(&root) {
17946 stack.push(root);
17947 }
17948 while let Some(node) = stack.pop() {
17949 if node.start_byte() == range.start_byte
17950 && node.end_byte() == range.end_byte
17951 && matches!(
17952 node.kind(),
17953 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17954 )
17955 {
17956 if cpp_class_node_has_body(node) {
17957 return CppClassDeclarationStrength::Full;
17958 }
17959 saw_forward = true;
17960 }
17961 let mut cursor = node.walk();
17962 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17963 }
17964 }
17965 if saw_forward {
17966 CppClassDeclarationStrength::Forward
17967 } else {
17968 CppClassDeclarationStrength::Unknown
17969 }
17970}
17971
17972fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17973 node.child_by_field_name("body").is_some() || {
17974 let mut cursor = node.walk();
17975 node.named_children(&mut cursor).any(|child| {
17976 matches!(
17977 child.kind(),
17978 "declaration_list" | "field_declaration_list" | "enumerator_list"
17979 )
17980 })
17981 }
17982}
17983
17984#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17985enum CppCTagKind {
17986 Struct,
17987 Union,
17988}
17989
17990fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
17991 let declaration = analyzer.get_source(code_unit, false)?;
17992 let mut parser = Parser::new();
17993 parser
17994 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17995 .ok()?;
17996 let tree = parser.parse(&declaration, None)?;
17997 let mut stack = vec![tree.root_node()];
17998 while let Some(node) = stack.pop() {
17999 let kind = match node.kind() {
18000 "struct_specifier" => CppCTagKind::Struct,
18001 "union_specifier" => CppCTagKind::Union,
18002 _ => {
18003 let mut cursor = node.walk();
18004 stack.extend(node.named_children(&mut cursor));
18005 continue;
18006 }
18007 };
18008 if node
18009 .child_by_field_name("name")
18010 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18011 {
18012 return Some(kind);
18013 }
18014 let mut cursor = node.walk();
18015 stack.extend(node.named_children(&mut cursor));
18016 }
18017 None
18018}
18019
18020pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18021 if !code_unit.owner_is_type_scope() {
18022 return None;
18023 }
18024 let owner_fq = code_unit.fq().parent()?;
18025 ctx.analyzer
18026 .workspace_definitions()
18027 .exact(&owner_fq)
18028 .into_iter()
18029 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18030}
18031
18032pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18033 left.kind() == right.kind()
18034 && left.fq_name() == right.fq_name()
18035 && left.signature() == right.signature()
18036 && left.source() == right.source()
18037}
18038
18039pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18040 same_symbol(left, right) || same_logical_symbol(left, right)
18041}
18042
18043pub fn same_visible_global_field_symbol(
18044 analyzer: &CppGraphSource<'_>,
18045 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18046 left: &CodeUnit,
18047 right: &CodeUnit,
18048) -> bool {
18049 if same_symbol(left, right) {
18050 return true;
18051 }
18052 if !same_logical_symbol(left, right) {
18053 return false;
18054 }
18055 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18056 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18057 {
18058 left.source() == right.source()
18059 } else {
18060 true
18061 }
18062}
18063
18064fn cpp_global_field_has_internal_linkage_cached(
18065 analyzer: &CppGraphSource<'_>,
18066 cache: &mut HashMap<CodeUnit, bool>,
18067 candidate: &CodeUnit,
18068) -> bool {
18069 if let Some(internal) = cache.get(candidate) {
18070 return *internal;
18071 }
18072 #[cfg(any(test, feature = "test-support"))]
18073 note_cpp_global_field_internal_linkage_classification_for_test();
18074 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18075 cache.insert(candidate.clone(), internal);
18076 internal
18077}
18078
18079#[cfg(any(test, feature = "test-support"))]
18080thread_local! {
18081 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18082}
18083
18084#[cfg(any(test, feature = "test-support"))]
18085fn note_cpp_global_field_internal_linkage_classification_for_test() {
18086 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18087 count.set(count.get() + 1);
18088 });
18089}
18090
18091#[cfg(any(test, feature = "test-support"))]
18092pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18093 body: impl FnOnce() -> T,
18094) -> (T, usize) {
18095 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18096 count.set(0);
18097 let result = body();
18098 let observed = count.get();
18099 count.set(0);
18100 (result, observed)
18101 })
18102}
18103
18104pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18105 left.kind() == right.kind()
18106 && left.fq_name() == right.fq_name()
18107 && left.signature() == right.signature()
18108}
18109
18110pub fn cpp_global_field_has_internal_linkage(
18111 analyzer: &CppGraphSource<'_>,
18112 candidate: &CodeUnit,
18113) -> bool {
18114 if !candidate.is_field() || candidate.short_name().contains('.') {
18115 return false;
18116 }
18117 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18118 return false;
18119 };
18120 match local_linkage {
18121 CppFieldLinkage::Internal => true,
18122 CppFieldLinkage::External => false,
18123 CppFieldLinkage::InternalUnlessExternalPeer => {
18124 !cpp_global_field_linkage_peers(analyzer, candidate)
18125 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18126 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18127 }
18128 }
18129}
18130
18131fn cpp_global_field_linkage_peers<'a>(
18132 analyzer: &CppGraphSource<'a>,
18133 candidate: &'a CodeUnit,
18134) -> impl Iterator<Item = CodeUnit> + 'a {
18135 let name = candidate.fq().clone();
18136 analyzer
18137 .workspace_definitions()
18138 .exact(&name)
18139 .into_iter()
18140 .filter(move |peer| {
18141 if peer == candidate {
18142 return false;
18143 }
18144 #[cfg(any(test, feature = "test-support"))]
18145 note_cpp_global_field_linkage_peer_inspection_for_test();
18146 same_logical_symbol(peer, candidate)
18147 })
18148}
18149
18150#[cfg(any(test, feature = "test-support"))]
18151thread_local! {
18152 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18153}
18154
18155#[cfg(any(test, feature = "test-support"))]
18156fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18157 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18158 count.set(count.get() + 1);
18159 });
18160}
18161
18162#[cfg(any(test, feature = "test-support"))]
18163pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18164 body: impl FnOnce() -> T,
18165) -> (T, usize) {
18166 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18167 count.set(0);
18168 let result = body();
18169 let observed = count.get();
18170 count.set(0);
18171 (result, observed)
18172 })
18173}
18174
18175fn cpp_global_field_declaration_linkage(
18176 analyzer: &CppGraphSource<'_>,
18177 candidate: &CodeUnit,
18178) -> Option<CppFieldLinkage> {
18179 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
18180 return Some(linkage);
18181 }
18182 let cpp = analyzer.cpp?;
18183 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
18184 return cpp_global_field_declaration_linkage_in_tree(
18185 analyzer,
18186 candidate,
18187 prepared.source(),
18188 prepared.tree().root_node(),
18189 );
18190 }
18191 let source = analyzer.indexed_source(candidate.source())?;
18192 let mut parser = Parser::new();
18193 if parser
18194 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18195 .is_err()
18196 {
18197 return None;
18198 }
18199 let tree = parser.parse(&source, None)?;
18200 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
18201}
18202
18203fn cpp_global_field_declaration_linkage_in_tree(
18204 analyzer: &CppGraphSource<'_>,
18205 candidate: &CodeUnit,
18206 source: &str,
18207 root: Node<'_>,
18208) -> Option<CppFieldLinkage> {
18209 analyzer.ranges(candidate).iter().find_map(|range| {
18210 node_for_exact_range(root, range)
18211 .and_then(enclosing_cpp_field_declaration)
18212 .map(|declaration| {
18213 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
18215 })
18216 })
18217}
18218
18219fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
18220 loop {
18221 if matches!(node.kind(), "declaration" | "field_declaration") {
18222 return Some(node);
18223 }
18224 node = node.parent()?;
18225 }
18226}
18227
18228#[cfg(test)]
18229mod tests {
18230 #[test]
18231 fn issue_3089_statement_formal_at_end_of_replacement() {
18232 let parameters = vec!["handle".to_owned(), "block".to_owned()];
18233 for replacement in [
18234 "do { header_event_t* event; if ((handle)->active) block } while (0)",
18235 "do { header_event_t* event; block } while (0)",
18236 ] {
18237 assert!(
18238 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
18239 .is_some(),
18240 "{replacement}"
18241 );
18242 }
18243 }
18244 use super::*;
18245
18246 #[test]
18247 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18248 let source = "int size(void) { return sizeof(((Payload))); }\n";
18249 let mut parser = Parser::new();
18250 parser
18251 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18252 .expect("C++ grammar");
18253 let tree = parser.parse(source, None).expect("fixture tree");
18254 let start = source.find("Payload").expect("sizeof operand");
18255 let node = tree
18256 .root_node()
18257 .named_descendant_for_byte_range(start, start + "Payload".len())
18258 .expect("focused operand");
18259 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18260 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18261
18262 assert_eq!(node.kind(), "identifier");
18263 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18264 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18265 }
18266
18267 fn parse_cpp(source: &str) -> Tree {
18268 let mut parser = Parser::new();
18269 parser
18270 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18271 .expect("C++ grammar");
18272 parser.parse(source, None).expect("fixture tree")
18273 }
18274
18275 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18276 let start = source.find(needle).expect("fixture needle");
18277 tree.root_node()
18278 .named_descendant_for_byte_range(start, start + needle.len())
18279 .expect("node at needle")
18280 }
18281
18282 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18283 let mut parser = Parser::new();
18284 parser
18285 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18286 .expect("C++ grammar");
18287 let tree = parser.parse(source, None).expect("fixture tree");
18288 PreparedSyntaxTree::new(
18289 PreparedSyntaxSource::Exact(Arc::from(source)),
18290 tree,
18291 compute_line_starts(source),
18292 LanguageDialect::Standard(Language::Cpp),
18293 PreparedSourceOrigin::Disk,
18294 None,
18295 )
18296 }
18297
18298 fn unresolved_include_before(source: &str, reference: &str) -> bool {
18299 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18300 let prepared = prepared_cpp(source);
18301 let facts = collect_structured_include_facts(&prepared);
18302 let include_targets = IncludeTargetIndex::build([&file]);
18303 has_unresolved_include_visible_before_in_prepared(
18304 &file,
18305 &prepared,
18306 &include_targets,
18307 &facts,
18308 source.find(reference).expect("reference fixture"),
18309 )
18310 }
18311
18312 #[test]
18313 fn unresolved_include_before_reference_is_visible() {
18314 let source = "#include \"missing.h\"\nint use = Missing;\n";
18315 assert!(unresolved_include_before(source, "Missing"));
18316 }
18317
18318 #[test]
18319 fn unresolved_include_after_reference_is_not_visible() {
18320 let source = "int use = Missing;\n#include \"missing.h\"\n";
18321 assert!(!unresolved_include_before(source, "Missing"));
18322 }
18323
18324 #[test]
18325 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18326 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18327 assert!(!unresolved_include_before(source, "Missing"));
18328 }
18329
18330 #[test]
18331 fn unresolved_include_in_current_branch_is_visible() {
18332 let source =
18333 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18334 assert!(unresolved_include_before(source, "Missing"));
18335 }
18336
18337 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18342namespace matchers {
18343 namespace detail {
18344 class API [[nodiscard]] First {
18345 public:
18346 int value() const { return count_ + 1; }
18347 private:
18348 int count_;
18349 };
18350 class API [[nodiscard]] Second {
18351 public:
18352 int value() const { return count_ + 2; }
18353 private:
18354 int count_;
18355 };
18356 } // namespace detail
18357
18358 template <typename T>
18359 void tail_function(MatcherBase<T> const& value);
18360
18361 class TailClass {};
18362} // namespace matchers
18363} // namespace app
18364
18365struct AfterAll {};
18366"#;
18367
18368 #[test]
18369 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18370 let source = STOLEN_BRACE_CASCADE;
18371 let tree = parse_cpp(source);
18372 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18373
18374 let tail_class = named_node_at(&tree, source, "TailClass");
18375 assert!(
18376 !has_ancestor_kind(tail_class, "namespace_definition"),
18377 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18378 );
18379 let displaced = named_node_at(&tree, source, "Second");
18380 assert_eq!(
18381 enclosing_namespace_components(displaced, source),
18382 Some(vec!["app".to_string(), "matchers".to_string()]),
18383 "the fixture must displace the second class out of detail"
18384 );
18385
18386 let components = |needle: &str| {
18387 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18388 };
18389 assert_eq!(components("First"), ["app", "matchers", "detail"]);
18390 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18391 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18392 assert_eq!(components("tail_function"), ["app", "matchers"]);
18393 assert_eq!(components("TailClass"), ["app", "matchers"]);
18394 assert!(components("AfterAll").is_empty());
18395 }
18396
18397 #[test]
18398 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18399 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18400 let tree = parse_cpp(clean);
18401 assert!(!tree.root_node().has_error());
18402 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18403
18404 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18407 let tree = parse_cpp(damaged);
18408 assert!(tree.root_node().has_error());
18409 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18410 assert_eq!(
18411 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18412 ["a"]
18413 );
18414 }
18415
18416 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18423
18424 class Target {
18425 int value_;
18426 };
18427
18428 template <typename T>
18429 class Holder {
18430 public:
18431 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18432
18433#define HOLDER_DEFINE_OP( id, op ) \
18434 template <typename U> \
18435 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
18436 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
18437 return Target{}; \
18438 }
18439
18440 HOLDER_DEFINE_OP( equal, == )
18441#undef HOLDER_DEFINE_OP
18442 T m_lhs;
18443 };
18444
18445 class Tail {};
18446}
18447"#;
18448
18449 #[test]
18450 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18451 let source = COLLAPSED_NAMESPACE_HEAD;
18452 let tree = parse_cpp(source);
18453 let target = named_node_at(&tree, source, "class Target");
18454
18455 assert!(
18456 !has_ancestor_kind(target, "namespace_definition"),
18457 "the fixture must reproduce the collapse: the class has no namespace ancestor"
18458 );
18459 let head = target.parent().expect("the collapsed namespace envelope");
18460 assert_eq!(
18461 head.kind(),
18462 "ERROR",
18463 "the fixture must keep the namespace head in an ERROR node"
18464 );
18465
18466 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18467 assert_eq!(
18468 index.enclosing_namespace_components(target, source),
18469 ["app"]
18470 );
18471 assert_eq!(
18472 index.enclosing_namespace_levels(target, source),
18473 vec![(
18474 "app".to_string(),
18475 Range {
18476 start_byte: 0,
18477 end_byte: source.len() - 1,
18478 start_line: 1,
18479 end_line: 25,
18480 },
18481 )],
18482 "the recovered namespace owns its full construct, not its first member or just its header"
18483 );
18484 }
18485
18486 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
18495
18496namespace app {
18497namespace detail {
18498
18499bool check(bool active) {
18500 APP_IF_ACTIVE
18501 return true;
18502 }
18503 return false;
18504}
18505
18506class Tail {};
18507
18508} // namespace detail
18509} // namespace app
18510"#;
18511
18512 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
18522namespace compile_time {
18523namespace number_parsing {
18524consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
18525 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
18526 auto digit = [](uint8_t c) -> uint32_t {
18527 }
18528 }
18529 if (a) {
18530 while (a) {
18531 }
18532 }
18533 switch (c) {
18534 case '{': {
18535 }
18536 }
18537 }
18538 while (a) {
18539 cursor += field.second;
18540 if (a) {
18541 case '{': {
18542 if (a) {
18543 }
18544 }
18545 case 'n': {
18546 if (a) {
18547 }
18548 }
18549 }
18550 }
18551 if (a) {
18552 simdjson_consteval_error("Expected '}'");
18553 }
18554}
18555}
18556}
18557"#;
18558
18559 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
18561 let mut stack = vec![root];
18562 let mut counts = (0, 0);
18563 while let Some(node) = stack.pop() {
18564 match (node.kind(), node.is_missing()) {
18565 ("{", false) => counts.0 += 1,
18566 ("}", false) => counts.1 += 1,
18567 _ => {}
18568 }
18569 let mut cursor = node.walk();
18570 stack.extend(node.children(&mut cursor));
18571 }
18572 counts
18573 }
18574
18575 fn namespace_open_byte(source: &str, head: &str) -> usize {
18577 let at = source.find(head).expect("fixture namespace head");
18578 at + head.len() - 1
18579 }
18580
18581 #[test]
18582 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
18583 let source = MACRO_OPENED_SCOPE;
18584 let tree = parse_cpp(source);
18585 let root = tree.root_node();
18586 assert_eq!(
18587 real_brace_token_counts(root),
18588 (4, 5),
18589 "the fixture must reproduce the loss: one scope the source opens \
18590 has no `{{` token, so the stream holds an extra real close"
18591 );
18592
18593 let index = OrphanedNamespaceScopeIndex::build(root, source);
18594 for head in ["namespace app {", "namespace detail {"] {
18595 assert_eq!(
18596 index.matching_close_brace(namespace_open_byte(source, head)),
18597 None,
18598 "`{head}` must not claim a close: the stack had to give it one that \
18599 belongs to an inner scope (line 15 for `app`, line 11 for \
18600 `detail`) once the macro's `{{` went missing"
18601 );
18602 }
18603 }
18604
18605 #[test]
18606 fn orphaned_namespace_scope_index_pairs_nothing_on_the_reflection_recovery() {
18607 let source = REFLECTION_CASE_LABEL_RECOVERY;
18608 let tree = parse_cpp(source);
18609 let root = tree.root_node();
18610 let mut stack = vec![root];
18611 let mut mis_lexed_opens = Vec::new();
18612 while let Some(node) = stack.pop() {
18613 if node.kind() == "character"
18614 && &source[node.start_byte()..node.end_byte()] == "{"
18615 && node
18616 .parent()
18617 .is_some_and(|parent| parent.kind() != "char_literal")
18618 {
18619 mis_lexed_opens.push(node.start_position().row + 1);
18620 }
18621 let mut cursor = node.walk();
18622 stack.extend(node.children(&mut cursor));
18623 }
18624 assert!(
18625 !mis_lexed_opens.is_empty(),
18626 "the fixture must reproduce the mis-lex: a scope-opening `{{` \
18627 consumed as a `character` of a char literal recovery never closed"
18628 );
18629 assert_eq!(
18630 real_brace_token_counts(root),
18631 (17, 18),
18632 "which is what leaves the brace token stream short one open: \
18633 mis-lexed at {mis_lexed_opens:?}"
18634 );
18635
18636 let index = OrphanedNamespaceScopeIndex::build(root, source);
18637 for head in [
18638 "namespace simdjson {",
18639 "namespace compile_time {",
18640 "namespace number_parsing {",
18641 ] {
18642 assert_eq!(
18643 index.matching_close_brace(namespace_open_byte(source, head)),
18644 None,
18645 "`{head}` must not claim a close: every close after the lost open \
18646 belongs one level in, so the stack paired the three heads on \
18647 lines 1..3 with the closes on lines 33..35 instead of 34..36"
18648 );
18649 }
18650 }
18651
18652 #[test]
18653 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18654 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18655 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18656 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18657 assert!(indexed_namespace_path_is_recoverable(
18658 &["cache".to_string()],
18659 &indexed,
18660 1,
18661 ));
18662 }
18663
18664 #[test]
18665 fn sort_lookup_units_totally_orders_every_identity_field() {
18666 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18667 let base = CodeUnit::with_signature(
18668 file.clone(),
18669 CodeUnitType::Function,
18670 "scope",
18671 "value",
18672 Some("()".to_string()),
18673 false,
18674 );
18675 let different_kind = CodeUnit::with_signature(
18676 file.clone(),
18677 CodeUnitType::Field,
18678 "scope",
18679 "value",
18680 Some("()".to_string()),
18681 false,
18682 );
18683 let synthetic = base.with_synthetic(true);
18684
18685 let interner = segment_interner();
18686 let mut member_fq = FqName::new();
18687 member_fq.push(interner.intern("scope", SegmentKind::Package));
18688 member_fq.push(interner.intern("value", SegmentKind::Member));
18689 let different_package_boundary = CodeUnit::from_fq(
18690 file.clone(),
18691 CodeUnitType::Function,
18692 member_fq,
18693 0,
18694 Some("()".to_string()),
18695 false,
18696 );
18697
18698 let mut unknown_fq = FqName::new();
18699 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18700 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18701 let different_segment_kind = CodeUnit::from_fq(
18702 file,
18703 CodeUnitType::Function,
18704 unknown_fq,
18705 1,
18706 Some("()".to_string()),
18707 false,
18708 );
18709
18710 let input = vec![
18711 base,
18712 different_kind,
18713 synthetic,
18714 different_package_boundary,
18715 different_segment_kind,
18716 ];
18717 let mut expected = input.clone();
18718 sort_lookup_units(&mut expected);
18719 assert!(expected.windows(2).all(|pair| {
18720 let mut ordered = pair.to_vec();
18721 sort_lookup_units(&mut ordered);
18722 ordered == pair && pair[0] != pair[1]
18723 }));
18724
18725 let mut reversed = input.clone();
18726 reversed.reverse();
18727 sort_lookup_units(&mut reversed);
18728 assert_eq!(reversed, expected);
18729
18730 let mut rotated = input;
18731 rotated.rotate_left(2);
18732 sort_lookup_units(&mut rotated);
18733 assert_eq!(rotated, expected);
18734 }
18735
18736 #[test]
18737 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18738 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";
18739 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18740 let parse = |source: &str| {
18741 let mut parser = Parser::new();
18742 parser
18743 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18744 .expect("C++ grammar");
18745 parser.parse(source, None).expect("fixture tree")
18746 };
18747
18748 let tree = parse(damaged);
18749 let root = tree.root_node();
18750 let target = damaged.find("target").expect("target byte");
18751 let declaration = root
18752 .descendant_for_byte_range(target, target + "target".len())
18753 .and_then(|mut node| {
18754 loop {
18755 if node.kind() == "declaration" {
18756 break Some(node);
18757 }
18758 node = node.parent()?;
18759 }
18760 })
18761 .expect("declaration after the displaced terminator");
18762 let conditional = declaration
18763 .parent()
18764 .filter(|node| node.kind() == "preproc_ifdef")
18765 .expect("damaged inner conditional");
18766 let outer = conditional
18767 .parent()
18768 .filter(|node| node.kind() == "preproc_ifdef")
18769 .expect("ordinary outer include guard");
18770 let terminator = cpp_displaced_preprocessor_terminator(conditional)
18771 .expect("structured displaced #endif");
18772 assert_eq!(node_text(terminator, damaged), "#endif");
18773 assert!(terminator.end_byte() <= declaration.start_byte());
18774 assert!(!preprocessor_conditional_contains_descendant(
18775 conditional,
18776 declaration
18777 ));
18778 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18779 assert!(preprocessor_conditional_contains_descendant(
18780 outer,
18781 declaration
18782 ));
18783
18784 let tree = parse(guarded);
18785 let conditional = tree
18786 .root_node()
18787 .named_child(0)
18788 .filter(|node| node.kind() == "preproc_ifdef")
18789 .expect("ordinary conditional");
18790 let declaration = conditional
18791 .named_children(&mut conditional.walk())
18792 .find(|node| node.kind() == "declaration")
18793 .expect("guarded declaration");
18794 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18795 assert!(preprocessor_conditional_contains_descendant(
18796 conditional,
18797 declaration
18798 ));
18799
18800 let damaged_alternative = format!(
18801 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18802 "UNUSED(value)\n".repeat(64)
18803 );
18804 let tree = parse(&damaged_alternative);
18805 let conditional = tree
18806 .root_node()
18807 .named_child(0)
18808 .filter(|node| node.kind() == "preproc_ifdef")
18809 .expect("outer conditional with an alternative");
18810 assert!(conditional.has_error());
18811 assert!(conditional.child_by_field_name("alternative").is_some());
18812 assert!(
18813 conditional
18814 .child(conditional.child_count() - 1)
18815 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18816 );
18817 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18818
18819 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";
18820 let tree = parse(split_declaration);
18821 let root = tree.root_node();
18822 let conditional = root
18823 .named_children(&mut root.walk())
18824 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18825 .expect("split declaration conditional");
18826 let target = split_declaration
18827 .find("static int target")
18828 .expect("target byte");
18829 let boundary =
18830 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18831 assert!(boundary.end_byte <= target, "{boundary:?}");
18832 assert_eq!(boundary.end_line, 9, "{boundary:?}");
18833 let target_node = root
18834 .descendant_for_byte_range(target, target + "static".len())
18835 .expect("target node");
18836 assert!(!preprocessor_conditional_contains_descendant(
18837 conditional,
18838 target_node
18839 ));
18840 }
18841
18842 #[test]
18843 fn fragmented_reference_guard_is_recovered() {
18844 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";
18845 let mut parser = Parser::new();
18846 parser
18847 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18848 .expect("C++ grammar");
18849 let tree = parser.parse(source, None).expect("fixture tree");
18850 let start = source.rfind("helper").expect("reference byte");
18851 let node = tree
18852 .root_node()
18853 .descendant_for_byte_range(start, start + "helper".len())
18854 .expect("reference node");
18855 let mut expected = HashSet::default();
18856 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18857 vec![
18858 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18859 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18860 ],
18861 )));
18862 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18863 }
18864
18865 #[test]
18866 fn expression_defined_and_ifndef_guards_are_incompatible() {
18867 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18868 let mut parser = Parser::new();
18869 parser
18870 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18871 .expect("C++ grammar");
18872 let tree = parser.parse(source, None).expect("fixture tree");
18873 let root = tree.root_node();
18874 let selected_start = source.find("selected").expect("selected declaration");
18875 let rejected_start = source.find("rejected").expect("rejected declaration");
18876 let selected = root
18877 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18878 .expect("selected node");
18879 let rejected = root
18880 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18881 .expect("rejected node");
18882 let selected_guards =
18883 preprocessor_guard_environment(selected, source).expect("selected guards");
18884 let rejected_guards =
18885 preprocessor_guard_environment(rejected, source).expect("rejected guards");
18886
18887 assert!(
18888 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18889 "opposite spellings of one macro guard must contradict"
18890 );
18891 }
18892
18893 #[test]
18894 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18895 let source = r#"#ifdef _WIN32
18896#if defined(__cplusplus)
18897extern "C"
18898#endif
18899int platform_api(void);
18900#endif
18901
18902#ifdef _WIN32
18903static int entropy_target(void) { return 0; }
18904#else
18905#ifdef HAVE_COMMON_RANDOM
18906static int other_target(void) { return 0; }
18907#elif defined(HAVE_GETENTROPY)
18908static int entropy_target(void) { return 1; }
18909static int use_entropy(void) { return entropy_target(); }
18910#endif
18911#endif
18912"#;
18913 let mut parser = Parser::new();
18914 parser
18915 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18916 .expect("C++ grammar");
18917 let tree = parser.parse(source, None).expect("fixture tree");
18918 let start = source.rfind("entropy_target()").expect("reference");
18919 let node = tree
18920 .root_node()
18921 .descendant_for_byte_range(start, start + "entropy_target".len())
18922 .expect("reference node");
18923 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18924 assert!(
18925 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18926 "{guards:#?}"
18927 );
18928 assert!(
18929 guards.contains(&PreprocessorGuard::Undefined(
18930 "HAVE_COMMON_RANDOM".to_string()
18931 )),
18932 "{guards:#?}"
18933 );
18934 assert!(
18935 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18936 "{guards:#?}"
18937 );
18938 assert!(
18939 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18940 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18941 );
18942 }
18943
18944 #[test]
18945 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18946 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";
18947 let mut parser = Parser::new();
18948 parser
18949 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18950 .expect("C++ grammar");
18951 let tree = parser.parse(source, None).expect("fixture tree");
18952 let root = tree.root_node();
18953 let node_at = |text: &str, start: usize| {
18954 root.descendant_for_byte_range(start, start + text.len())
18955 .expect("token node")
18956 };
18957
18958 let key_start = source.find("case KEY").expect("case label") + "case ".len();
18959 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18960 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18961 assert!(!is_ordinary_macro_reference_node(node_at(
18962 "ENABLE_KEYS",
18963 guard_start,
18964 )));
18965 }
18966
18967 #[test]
18968 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18969 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";
18970 let mut parser = Parser::new();
18971 parser
18972 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18973 .expect("C++ grammar");
18974 let tree = parser.parse(source, None).expect("fixture tree");
18975 let root = tree.root_node();
18976 let definition_start = source.find("target(void)").expect("definition");
18977 let reference_start = source.rfind("target()").expect("reference");
18978 let definition = root
18979 .descendant_for_byte_range(definition_start, definition_start + "target".len())
18980 .expect("definition node");
18981 let reference = root
18982 .descendant_for_byte_range(reference_start, reference_start + "target".len())
18983 .expect("reference node");
18984 let required =
18985 preprocessor_guard_environment(definition, source).expect("definition guard");
18986 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
18987 assert!(guard_requirements_hold_at_reference(
18988 &required,
18989 Some(&active)
18990 ));
18991 }
18992
18993 #[test]
18994 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
18995 let source = "g_autoptr(FuChunkArray) self = make_array();";
18996 let mut parser = Parser::new();
18997 parser
18998 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18999 .expect("C++ grammar");
19000 let tree = parser.parse(source, None).expect("fixture tree");
19001 let statement = tree.root_node().named_child(0).expect("statement");
19002 let binding =
19003 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
19004 assert_eq!(binding.name, "self");
19005 assert_eq!(binding.type_name, "FuChunkArray");
19006 assert_eq!(binding.pointer_depth, 1);
19007
19008 let near_miss = "holder(FuChunkArray) self = make_array();";
19009 let tree = parser.parse(near_miss, None).expect("near-miss tree");
19010 let statement = tree.root_node().named_child(0).expect("statement");
19011 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
19012 }
19013
19014 #[test]
19015 fn boolean_guard_normalization_proves_equivalence_and_implication() {
19016 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
19017 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
19018 let negated_windows_branch =
19019 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
19020 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
19021 assert_eq!(negated_windows_branch, portable);
19022
19023 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
19024 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
19025 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
19026 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
19027 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
19028 assert!(fallback_branch.implies(&fallback_declaration));
19029 assert!(
19030 BooleanGuardExpression::Truthy("FEATURE".to_string())
19031 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
19032 );
19033 assert!(
19034 BooleanGuardExpression::Undefined("FEATURE".to_string())
19035 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
19036 );
19037 assert!(
19038 !BooleanGuardExpression::Defined("FEATURE".to_string())
19039 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
19040 );
19041 assert!(!fallback_declaration.implies(&fallback_branch));
19042 }
19043
19044 #[test]
19045 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
19046 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";
19047 let mut parser = Parser::new();
19048 parser
19049 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19050 .expect("C++ grammar");
19051 let tree = parser.parse(source, None).expect("fixture tree");
19052 let root = tree.root_node();
19053 let call = |marker: &str| {
19054 let start = source.find(marker).expect("call marker");
19055 let mut node = root
19056 .descendant_for_byte_range(start, start + "helper".len())
19057 .expect("call name node");
19058 loop {
19059 if node.kind() == "call_expression" {
19060 break node;
19061 }
19062 node = node.parent().expect("call expression ancestor");
19063 }
19064 };
19065 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
19066 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
19067 let keyword_call = call("helper(NULL, template); /* bound */");
19068 let keyword_arguments = keyword_call
19069 .child_by_field_name("arguments")
19070 .expect("keyword argument list");
19071 assert_eq!(
19072 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
19073 1
19074 );
19075 assert_eq!(
19076 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
19077 0
19078 );
19079
19080 let unbound_call = call("helper(NULL, template); /* unbound */");
19081 let unbound_arguments = unbound_call
19082 .child_by_field_name("arguments")
19083 .expect("unbound argument list");
19084 assert_eq!(
19085 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
19086 0
19087 );
19088 }
19089
19090 #[test]
19091 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
19092 let source = r#"#define MAKE(type) type *value
19093MAKE(int *);
19094typedef struct Item Item;
19095struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
19096void gen_op_movl(void *s, int first, int second) { }
19097const char *strZ(const char *value) { return value; }
19098int body(void *s) {
19099 MAKE(int *);
19100 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
19101 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
19102 execvp(strZ(value), UNCONSTIFY(char **, args));
19103}
19104 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
19105 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
19106int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
19107 if (d->units) {
19108 switch (target->nodetype) {
19109 case 1:
19110 case 2:
19111 break;
19112 default:
19113 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
19114 }
19115 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
19116 lysdict_remove(ctx, ((struct Item *)target)->units);
19117 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
19118 }
19119 return 0;
19120 }
19121STATIC EFI_STATUS Encode () { return 0; }
19122"#;
19123 let tree = parse_cpp(source);
19124 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
19125 let top_macro = tree
19126 .root_node()
19127 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
19128 .expect("top macro node");
19129 let body_macro_start = source
19130 .match_indices("MAKE(int *);")
19131 .nth(1)
19132 .expect("body macro")
19133 .0;
19134 let body_macro = tree
19135 .root_node()
19136 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
19137 .expect("body macro node");
19138 let function_call_start = source
19139 .find("gen_op_movl(s, offsetof(CPUX86State")
19140 .expect("function call");
19141 let function_call = tree
19142 .root_node()
19143 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
19144 .expect("function call node");
19145 let strz_start = source.find("strZ(value)").expect("nested function call");
19146 let strz = tree
19147 .root_node()
19148 .named_descendant_for_byte_range(strz_start, strz_start + 4)
19149 .expect("nested function call node");
19150 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
19151 let recovered_call = tree
19152 .root_node()
19153 .named_descendant_for_byte_range(
19154 recovered_call_start,
19155 recovered_call_start + "lysdict_remove".len(),
19156 )
19157 .expect("recovered call node");
19158 let binder_start = source.find("Encode").expect("binder");
19159 let binder = tree
19160 .root_node()
19161 .named_descendant_for_byte_range(binder_start, binder_start + 6)
19162 .expect("binder node");
19163
19164 assert!(recovered_c_function_declarator_invocation(top_macro));
19165 assert!(recovered_c_function_declarator_invocation(body_macro));
19166 assert!(recovered_c_function_declarator_invocation(function_call));
19167 assert!(recovered_c_function_declarator_invocation(strz));
19168 assert!(recovered_c_function_declarator_invocation(recovered_call));
19169 assert!(!recovered_c_function_declarator_invocation(binder));
19170 }
19171
19172 #[test]
19173 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
19174 let source = r#"typedef int krb5_context;
19175int helper(int first, int second) { return first + second; }
19176static krb5_context ctx;
19177int main(int argc, char **argv) {
19178 int ccinitial;
19179 const char *collection_name, *typename;
19180 typename = helper(ctx, ccinitial);
19181 return 0;
19182}
19183"#;
19184 let tree = parse_cpp(source);
19185 let ctx = tree
19186 .root_node()
19187 .descendant_for_byte_range(
19188 source.find("ctx, ccinitial").expect("ctx argument"),
19189 source.find("ctx, ccinitial").expect("ctx argument") + 3,
19190 )
19191 .expect("ctx node");
19192 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
19193 let ccinitial = tree
19194 .root_node()
19195 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
19196 .expect("sibling node");
19197 let typename = named_node_at(&tree, source, "typename = helper");
19198 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
19199
19200 assert_eq!(ctx.kind(), "identifier");
19201 assert!(recovered_c_parenthesized_declarator_reference(ctx));
19202 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
19203 assert!(!recovered_c_parenthesized_declarator_reference(typename));
19204 assert!(!recovered_c_parenthesized_declarator_reference(helper));
19205 }
19206
19207 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
19208 let mut parser = Parser::new();
19209 parser
19210 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19211 .expect("C++ grammar");
19212 let tree = parser.parse(source, None).expect("C++ fixture tree");
19213 let mut stack = vec![tree.root_node()];
19214 while let Some(node) = stack.pop() {
19215 if node.kind() == "enum_specifier" {
19216 return flattened_macro_namespace_components(node, source);
19217 }
19218 let mut cursor = node.walk();
19219 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
19220 stack.extend(children.into_iter().rev());
19221 }
19222 None
19223 }
19224
19225 #[test]
19226 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
19227 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19228namespace detail
19229{
19230enum class value_t { null };
19231}
19232NLOHMANN_JSON_NAMESPACE_END
19233NLOHMANN_JSON_NAMESPACE_BEGIN
19234namespace next
19235{
19236struct next_type {};
19237}
19238NLOHMANN_JSON_NAMESPACE_END
19239"#;
19240 assert_eq!(
19241 first_enum_flattened_namespace(complete),
19242 Some(vec!["detail".to_string()])
19243 );
19244
19245 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
19246 assert_eq!(
19247 first_enum_flattened_namespace(&stale_end),
19248 Some(vec!["detail".to_string()]),
19249 "a stale end marker before the begin marker must not replace the intended namespace"
19250 );
19251
19252 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19253namespace detail
19254{
19255enum class value_t { null };
19256}
19257struct next_type {};
19258"#;
19259 assert_eq!(first_enum_flattened_namespace(incomplete), None);
19260 }
19261}
19262
19263#[cfg(test)]
19279mod lookup_order_properties {
19280 use super::*;
19281 use proptest::prelude::*;
19282
19283 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
19287 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
19288 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
19292 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
19293 const KINDS: [CodeUnitType; 6] = [
19294 CodeUnitType::Class,
19295 CodeUnitType::Function,
19296 CodeUnitType::Field,
19297 CodeUnitType::Module,
19298 CodeUnitType::Macro,
19299 CodeUnitType::FileScope,
19300 ];
19301
19302 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19305 enum ProbedOrder {
19306 Before,
19307 Tied,
19308 After,
19309 Contradictory,
19312 }
19313
19314 impl ProbedOrder {
19315 fn mirror(self) -> Self {
19316 match self {
19317 ProbedOrder::Before => ProbedOrder::After,
19318 ProbedOrder::After => ProbedOrder::Before,
19319 other => other,
19320 }
19321 }
19322
19323 fn signum(self) -> i8 {
19325 match self {
19326 ProbedOrder::Before => -1,
19327 ProbedOrder::Tied => 0,
19328 ProbedOrder::After => 1,
19329 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
19330 }
19331 }
19332 }
19333
19334 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
19342 if left == right {
19343 return ProbedOrder::Tied;
19346 }
19347 let mut forward = vec![left.clone(), right.clone()];
19348 sort_lookup_units(&mut forward);
19349 let mut backward = vec![right.clone(), left.clone()];
19350 sort_lookup_units(&mut backward);
19351 let left_first = backward[0] == *left;
19352 let right_first = forward[0] == *right;
19353 match (left_first, right_first) {
19354 (true, true) => ProbedOrder::Contradictory,
19355 (true, false) => ProbedOrder::Before,
19356 (false, true) => ProbedOrder::After,
19357 (false, false) => ProbedOrder::Tied,
19358 }
19359 }
19360
19361 fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
19364 let interner = segment_interner();
19365 unit.fq()
19366 .segments()
19367 .iter()
19368 .map(|&id| {
19369 let (text, kind) = interner.resolve(id);
19370 (kind.name(), text)
19371 })
19372 .collect()
19373 }
19374
19375 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
19376 (
19377 0..ROOT_NAMES.len(),
19378 0..REL_PATHS.len(),
19379 0..KINDS.len(),
19380 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
19381 0..3usize,
19382 0..SIGNATURES.len(),
19383 any::<bool>(),
19384 )
19385 .prop_map(
19386 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
19387 let source = ProjectFile::new(
19388 std::env::temp_dir().join(ROOT_NAMES[root]),
19389 REL_PATHS[rel_path],
19390 );
19391 let interner = segment_interner();
19392 let mut fq = FqName::new();
19393 for (atom, segment_kind) in &segments {
19394 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
19395 }
19396 let package_segment_count = package_prefix % fq.len();
19398 CodeUnit::from_fq(
19399 source,
19400 KINDS[kind],
19401 fq,
19402 package_segment_count,
19403 SIGNATURES[signature].map(str::to_string),
19404 synthetic,
19405 )
19406 },
19407 )
19408 }
19409
19410 proptest! {
19411 #![proptest_config(ProptestConfig::with_cases(256))]
19412
19413 #[test]
19416 fn lookup_order_is_reflexive_and_dual(
19417 left in code_unit_strategy(),
19418 right in code_unit_strategy(),
19419 ) {
19420 prop_assert_eq!(
19421 probe_order(&left, &left),
19422 ProbedOrder::Tied,
19423 "a unit must tie with itself: {:?}",
19424 left
19425 );
19426 let forward = probe_order(&left, &right);
19427 prop_assert_ne!(
19428 forward,
19429 ProbedOrder::Contradictory,
19430 "comparator put each of these strictly first: left={:?} right={:?}",
19431 left,
19432 right
19433 );
19434 prop_assert_eq!(
19435 probe_order(&right, &left),
19436 forward.mirror(),
19437 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
19438 left,
19439 right
19440 );
19441 }
19442
19443 #[test]
19445 fn lookup_order_is_transitive(
19446 a in code_unit_strategy(),
19447 b in code_unit_strategy(),
19448 c in code_unit_strategy(),
19449 ) {
19450 let ab = probe_order(&a, &b);
19451 let bc = probe_order(&b, &c);
19452 let ac = probe_order(&a, &c);
19453 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
19454 prop_assert_ne!(
19455 probed,
19456 ProbedOrder::Contradictory,
19457 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
19458 pair,
19459 a,
19460 b,
19461 c
19462 );
19463 }
19464 if ab.signum() <= 0 && bc.signum() <= 0 {
19465 prop_assert!(
19466 ac.signum() <= 0,
19467 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
19468 a={:?} b={:?} c={:?}",
19469 ab,
19470 bc,
19471 ac,
19472 a,
19473 b,
19474 c
19475 );
19476 }
19477 }
19478
19479 #[test]
19482 fn lookup_order_separates_distinct_identities(
19483 left in code_unit_strategy(),
19484 right in code_unit_strategy(),
19485 ) {
19486 if probe_order(&left, &right) == ProbedOrder::Tied {
19487 prop_assert_eq!(
19488 &left,
19489 &right,
19490 "distinct identities tied, so their order is whatever order they \
19491 arrived in: left_segments={:?} right_segments={:?}",
19492 fq_segments(&left),
19493 fq_segments(&right)
19494 );
19495 }
19496 }
19497
19498 #[test]
19501 fn lookup_sort_is_permutation_invariant(
19502 units in prop::collection::vec(code_unit_strategy(), 1..=8),
19503 ) {
19504 let mut sorted = units.clone();
19505 sort_lookup_units(&mut sorted);
19506 for rotation in 0..units.len() {
19507 for reversed in [false, true] {
19508 let mut permuted = units.clone();
19509 permuted.rotate_left(rotation);
19510 if reversed {
19511 permuted.reverse();
19512 }
19513 sort_lookup_units(&mut permuted);
19514 prop_assert_eq!(
19515 &permuted,
19516 &sorted,
19517 "sorting a permutation gave a different list \
19518 (rotation={}, reversed={}): input={:?}",
19519 rotation,
19520 reversed,
19521 units
19522 );
19523 }
19524 }
19525 }
19526 }
19527}