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
12834pub fn field_initializer_holds_subobject(
12841 analyzer: &CppGraphSource<'_>,
12842 visibility: &VisibilityIndex<'_>,
12843 field: &CodeUnit,
12844) -> bool {
12845 visibility
12846 .field_declared_type_fact(analyzer, field)
12847 .is_none_or(|fact| !fact.binds_indirectly)
12848}
12849
12850fn anonymous_aggregate_field_owner(
12857 analyzer: &CppGraphSource<'_>,
12858 visibility: &VisibilityIndex<'_>,
12859 visible_from: &ProjectFile,
12860 field: &CodeUnit,
12861) -> Option<CodeUnit> {
12862 let owner = type_owner_of(analyzer, field)?;
12863 if !owner.is_class() {
12864 return None;
12865 }
12866 let declaration = analyzer.get_source(field, false)?;
12867 let mut parser = Parser::new();
12868 parser
12869 .set_language(&tree_sitter_cpp::LANGUAGE.into())
12870 .ok()?;
12871 let tree = parser.parse(&declaration, None)?;
12872 let mut stack = vec![tree.root_node()];
12873 while let Some(node) = stack.pop() {
12874 if matches!(node.kind(), "declaration" | "field_declaration")
12875 && let Some(type_node) = node
12876 .child_by_field_name("type")
12877 .or_else(|| first_type_child(node))
12878 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12879 && type_node.child_by_field_name("name").is_none()
12880 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
12881 {
12882 let matches = visibility
12883 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12884 .into_iter()
12885 .filter(|child| child.is_class() && child.identifier() == field.identifier())
12886 .collect::<Vec<_>>();
12887 return match matches.as_slice() {
12888 [child] => Some((*child).clone()),
12889 _ => None,
12890 };
12891 }
12892 let mut cursor = node.walk();
12893 stack.extend(node.named_children(&mut cursor));
12894 }
12895 None
12896}
12897
12898pub fn anonymous_aggregate_owner(
12905 analyzer: &CppGraphSource<'_>,
12906 file: &ProjectFile,
12907 node: Node<'_>,
12908) -> Option<CodeUnit> {
12909 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12910 || node.child_by_field_name("name").is_some()
12911 {
12912 return None;
12913 }
12914 let mut candidates = analyzer
12915 .declarations(file)
12916 .into_iter()
12917 .filter(|candidate| {
12918 candidate.is_class()
12919 && analyzer.ranges(candidate).into_iter().any(|range| {
12920 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12921 })
12922 })
12923 .collect::<Vec<_>>();
12924 candidates.sort_by_key(|candidate| candidate.fq_name());
12925 candidates.dedup();
12926 match candidates.as_slice() {
12927 [candidate] => Some(candidate.clone()),
12928 _ => None,
12929 }
12930}
12931
12932fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12934 let Some(first) = candidates.first() else {
12935 return Err(TypeCandidateFailure::Unresolvable);
12936 };
12937 if candidates
12938 .iter()
12939 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12940 {
12941 Ok((*first).clone())
12942 } else {
12943 Err(TypeCandidateFailure::Ambiguous)
12944 }
12945}
12946
12947fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12948 logical_type_candidate(candidates).ok()
12949}
12950
12951fn unique_type_candidate_preserving_alias(
12952 analyzer: &CppGraphSource<'_>,
12953 file: &ProjectFile,
12954 candidates: &[&CodeUnit],
12955) -> Option<CodeUnit> {
12956 let first = *candidates.first()?;
12957 if declared_type_alias(analyzer, first) {
12958 return candidates
12959 .iter()
12960 .all(|candidate| {
12961 declared_type_alias(analyzer, candidate)
12962 && candidate.kind() == first.kind()
12963 && candidate.fq_name() == first.fq_name()
12964 && candidate.source() == first.source()
12965 })
12966 .then(|| first.clone());
12967 }
12968 if analyzer.reference_uses_c_semantics(file)
12969 && first.is_class()
12970 && indexed_c_tag_kind(analyzer, first).is_some()
12971 {
12972 let mut full_source = None;
12973 let mut tag_kind = None;
12974 for candidate in candidates.iter().copied() {
12975 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12976 if tag_kind
12977 .replace(candidate_tag_kind)
12978 .is_some_and(|existing| existing != candidate_tag_kind)
12979 {
12980 return None;
12981 }
12982 if cpp_class_declaration_strength(analyzer, candidate)
12983 == CppClassDeclarationStrength::Full
12984 && full_source
12985 .replace(candidate.source())
12986 .is_some_and(|existing| existing != candidate.source())
12987 {
12988 return None;
12989 }
12990 }
12991 }
12992 candidates
12993 .iter()
12994 .all(|candidate| {
12995 !declared_type_alias(analyzer, candidate)
12996 && candidate.kind() == first.kind()
12997 && candidate.fq_name() == first.fq_name()
12998 })
12999 .then(|| first.clone())
13000}
13001
13002fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13003 is_type_alias(unit)
13004 || analyzer
13005 .type_alias_provider()
13006 .is_some_and(|provider| provider.is_type_alias(unit))
13007}
13008
13009pub fn field_declared_type_binding(
13010 analyzer: &CppGraphSource<'_>,
13011 visibility: &VisibilityIndex<'_>,
13012 visible_from: &ProjectFile,
13013 field: &CodeUnit,
13014) -> Option<(String, Option<CodeUnit>, i32)> {
13015 let fact = visibility.field_declared_type_fact(analyzer, field)?;
13016 let normalized = normalize_field_type_text(&fact.type_text);
13017 let primary = visibility.resolve_unique_canonical_type_for_declaration(
13018 analyzer,
13019 visible_from,
13020 field,
13021 &normalized,
13022 );
13023 let resolved = match (primary, fact.template_arguments.as_deref()) {
13024 (Some(primary), Some(arguments)) => visibility
13025 .resolve_template_arguments(visible_from, primary, arguments)
13026 .ok(),
13027 (resolved, None) => resolved,
13028 (None, Some(_)) => None,
13029 };
13030 Some((normalized, resolved, fact.indirection))
13031}
13032
13033fn decode_field_declared_type_fact(
13034 analyzer: &CppGraphSource<'_>,
13035 field: &CodeUnit,
13036) -> Option<DeclaredFieldTypeFact> {
13037 let Some(declaration) = analyzer.get_source(field, false) else {
13038 return decode_indexed_field_declared_type_fact(analyzer, field);
13039 };
13040 let mut parser = Parser::new();
13041 parser
13042 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13043 .ok()?;
13044 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13048 let contextual_tree = parser.parse(&contextual_declaration, None)?;
13049 let mut stack = vec![contextual_tree.root_node()];
13050 while let Some(node) = stack.pop() {
13051 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13052 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13053 {
13054 return Some(DeclaredFieldTypeFact {
13055 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13056 indirection: recovered.pointer_depth(),
13057 binds_indirectly: recovered.pointer_depth() > 0,
13058 template_arguments: None,
13059 });
13060 }
13061 if let Some(recovered) =
13062 recovered_function_like_field_declarator(node, &contextual_declaration)
13063 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13064 {
13065 let type_node = node
13066 .child_by_field_name("type")
13067 .or_else(|| first_type_child(node))?;
13068 return Some(DeclaredFieldTypeFact {
13069 type_text: node_text(type_node, &contextual_declaration).to_string(),
13070 indirection: recovered.pointer_depth(),
13071 binds_indirectly: true,
13074 template_arguments: cpp_template_reference_arguments(
13075 type_node,
13076 &contextual_declaration,
13077 ),
13078 });
13079 }
13080 if let Some(fact) =
13081 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13082 {
13083 return Some(fact);
13084 }
13085 let mut cursor = node.walk();
13086 stack.extend(node.named_children(&mut cursor));
13087 }
13088 let tree = parser.parse(&declaration, None)?;
13089 let mut stack = vec![tree.root_node()];
13090 while let Some(node) = stack.pop() {
13091 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13092 {
13093 return Some(fact);
13094 }
13095 let mut cursor = node.walk();
13096 stack.extend(node.named_children(&mut cursor));
13097 }
13098 None
13099}
13100
13101fn decode_indexed_field_declared_type_fact(
13106 analyzer: &CppGraphSource<'_>,
13107 field: &CodeUnit,
13108) -> Option<DeclaredFieldTypeFact> {
13109 let cpp = analyzer.cpp?;
13110 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13111 let source = prepared.source();
13112 let root = prepared.tree().root_node();
13113 for range in analyzer.ranges(field) {
13114 let end = range.start_byte.saturating_add(1).min(source.len());
13115 let mut current = root.descendant_for_byte_range(range.start_byte, end);
13116 while let Some(node) = current {
13117 if matches!(node.kind(), "declaration" | "field_declaration")
13118 && let Some(fact) =
13119 decode_declared_field_type_node(node, field.identifier(), source)
13120 {
13121 return Some(fact);
13122 }
13123 current = node.parent();
13124 }
13125 }
13126 None
13127}
13128
13129fn decode_declared_field_type_node(
13130 node: Node<'_>,
13131 field_name: &str,
13132 source: &str,
13133) -> Option<DeclaredFieldTypeFact> {
13134 if !matches!(node.kind(), "declaration" | "field_declaration") {
13135 return None;
13136 }
13137 let type_node = node
13138 .child_by_field_name("type")
13139 .or_else(|| first_type_child(node))?;
13140 let binding = declared_name_binding(node, type_node, field_name, source)?;
13141 let declared_type = if matches!(
13142 type_node.kind(),
13143 "class_specifier" | "struct_specifier" | "union_specifier"
13144 ) {
13145 type_node.child_by_field_name("name")
13146 } else {
13147 Some(type_node)
13148 };
13149 Some(DeclaredFieldTypeFact {
13150 type_text: declared_type.map_or_else(
13151 || field_name.to_string(),
13152 |declared_type| node_text(declared_type, source).to_string(),
13153 ),
13154 indirection: binding.pointer_depth,
13155 binds_indirectly: binding.indirect,
13156 template_arguments: declared_type
13157 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13158 })
13159}
13160
13161pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13175 let mut parser = Parser::new();
13176 parser
13177 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13178 .ok()?;
13179 let tree = parser.parse(declaration, None)?;
13180 let mut stack = vec![tree.root_node()];
13181 while let Some(node) = stack.pop() {
13182 let type_node = match node.kind() {
13183 "type_definition" => {
13184 let mut cursor = node.walk();
13185 if node
13186 .children_by_field_name("declarator", &mut cursor)
13187 .any(declarator_names_function_type)
13188 {
13189 return None;
13190 }
13191 node.child_by_field_name("type")?
13192 }
13193 "alias_declaration" => {
13194 let type_node = node.child_by_field_name("type")?;
13195 if type_node
13196 .child_by_field_name("declarator")
13197 .is_some_and(declarator_names_function_type)
13198 {
13199 return None;
13200 }
13201 type_node
13202 }
13203 _ => {
13204 let mut cursor = node.walk();
13205 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13206 stack.extend(children.into_iter().rev());
13207 continue;
13208 }
13209 };
13210 return Some(node_text(type_node, declaration).to_string());
13211 }
13212 None
13213}
13214
13215fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
13224 let mut parser = Parser::new();
13225 if parser
13226 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13227 .is_err()
13228 {
13229 return true;
13230 }
13231 let Some(tree) = parser.parse(declaration, None) else {
13232 return true;
13233 };
13234 let mut stack = vec![tree.root_node()];
13235 while let Some(node) = stack.pop() {
13236 let declarators = match node.kind() {
13237 "type_definition" => {
13238 let mut cursor = node.walk();
13239 node.children_by_field_name("declarator", &mut cursor)
13240 .collect::<Vec<_>>()
13241 }
13242 "alias_declaration" => node
13243 .child_by_field_name("type")
13244 .and_then(|type_node| type_node.child_by_field_name("declarator"))
13245 .into_iter()
13246 .collect::<Vec<_>>(),
13247 _ => {
13248 let mut cursor = node.walk();
13249 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13250 stack.extend(children.into_iter().rev());
13251 continue;
13252 }
13253 };
13254 return declarators.into_iter().any(cpp_declarator_adds_indirection);
13255 }
13256 true
13257}
13258
13259fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13265 let mut current = Some(declarator);
13266 while let Some(node) = current {
13267 match node.kind() {
13268 "function_declarator" | "abstract_function_declarator" => return true,
13269 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13270 current = node.named_child(0);
13271 }
13272 _ => current = node.child_by_field_name("declarator"),
13273 }
13274 }
13275 false
13276}
13277
13278pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
13282 let mut parser = Parser::new();
13283 if parser
13284 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13285 .is_err()
13286 {
13287 return false;
13288 }
13289 let Some(tree) = parser.parse(declaration, None) else {
13290 return false;
13291 };
13292 let mut stack = vec![tree.root_node()];
13293 while let Some(node) = stack.pop() {
13294 if matches!(node.kind(), "declaration" | "field_declaration") {
13295 let mut cursor = node.walk();
13296 if node
13297 .children_by_field_name("declarator", &mut cursor)
13298 .any(|declarator| {
13299 declarator_name_node(declarator).is_some_and(|name| {
13300 node_text(name, declaration) == field_name
13301 && declarator_names_function_type(declarator)
13302 })
13303 })
13304 {
13305 return true;
13306 }
13307 }
13308 let mut cursor = node.walk();
13309 stack.extend(node.named_children(&mut cursor));
13310 }
13311 false
13312}
13313
13314pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
13318 let mut parser = Parser::new();
13319 if parser
13320 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13321 .is_err()
13322 {
13323 return false;
13324 }
13325 let Some(tree) = parser.parse(declaration, None) else {
13326 return false;
13327 };
13328 let mut stack = vec![tree.root_node()];
13329 while let Some(node) = stack.pop() {
13330 match node.kind() {
13331 "type_definition" => {
13332 let mut cursor = node.walk();
13333 if node
13334 .children_by_field_name("declarator", &mut cursor)
13335 .any(|declarator| {
13336 extract_typedef_declarator_name(declarator, declaration)
13337 .is_some_and(|name| name == alias_name)
13338 && declarator_names_function_type(declarator)
13339 })
13340 {
13341 return true;
13342 }
13343 }
13344 "alias_declaration" => {
13345 let names_alias = node
13346 .child_by_field_name("name")
13347 .is_some_and(|name| node_text(name, declaration) == alias_name);
13348 if names_alias
13349 && node
13350 .child_by_field_name("type")
13351 .and_then(|type_node| type_node.child_by_field_name("declarator"))
13352 .is_some_and(declarator_names_function_type)
13353 {
13354 return true;
13355 }
13356 }
13357 _ => {}
13358 }
13359 let mut cursor = node.walk();
13360 stack.extend(node.named_children(&mut cursor));
13361 }
13362 false
13363}
13364
13365fn decode_structured_alias_target(
13366 analyzer: &CppGraphSource<'_>,
13367 unit: &CodeUnit,
13368) -> Option<StructuredAliasTarget> {
13369 analyzer
13370 .get_source(unit, false)
13371 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
13372 .or_else(|| {
13373 let signature = unit.signature()?;
13374 decode_structured_alias_target_source(unit, signature, false)
13375 })
13376}
13377
13378fn decode_structured_alias_target_source(
13379 unit: &CodeUnit,
13380 declaration: &str,
13381 require_top_level: bool,
13382) -> Option<StructuredAliasTarget> {
13383 let mut parser = Parser::new();
13384 parser
13385 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13386 .ok()?;
13387 let tree = parser.parse(declaration, None)?;
13388 let mut stack = vec![tree.root_node()];
13389 while let Some(node) = stack.pop() {
13390 let type_node = match node.kind() {
13391 "type_definition" => {
13392 if require_top_level
13393 && node
13394 .parent()
13395 .is_none_or(|parent| parent.kind() != "translation_unit")
13396 {
13397 let mut cursor = node.walk();
13398 stack.extend(node.named_children(&mut cursor));
13399 continue;
13400 }
13401 let mut declarator_cursor = node.walk();
13402 let declarator = node
13403 .children_by_field_name("declarator", &mut declarator_cursor)
13404 .find(|declarator| {
13405 extract_typedef_declarator_name(*declarator, declaration)
13406 .is_some_and(|name| name == unit.identifier())
13407 })?;
13408 if declarator_names_function_type(declarator) {
13409 return None;
13410 }
13411 node.child_by_field_name("type")?
13412 }
13413 "alias_declaration" => {
13414 if require_top_level
13415 && node
13416 .parent()
13417 .is_none_or(|parent| parent.kind() != "translation_unit")
13418 {
13419 let mut cursor = node.walk();
13420 stack.extend(node.named_children(&mut cursor));
13421 continue;
13422 }
13423 let name = node.child_by_field_name("name")?;
13424 if node_text(name, declaration) != unit.identifier() {
13425 return None;
13426 }
13427 let type_node = node.child_by_field_name("type")?;
13428 if type_node
13429 .child_by_field_name("declarator")
13430 .is_some_and(declarator_names_function_type)
13431 {
13432 return None;
13433 }
13434 type_node
13435 }
13436 _ => {
13437 let mut cursor = node.walk();
13438 stack.extend(node.named_children(&mut cursor));
13439 continue;
13440 }
13441 };
13442 return structured_alias_type_target(type_node, declaration);
13443 }
13444 None
13445}
13446
13447fn structured_alias_type_target(
13448 mut type_node: Node<'_>,
13449 source: &str,
13450) -> Option<StructuredAliasTarget> {
13451 while type_node.kind() == "type_descriptor" {
13452 type_node = type_node.child_by_field_name("type")?;
13453 }
13454 if type_node.kind() == "primitive_type" {
13455 return Some(StructuredAliasTarget::Builtin);
13456 }
13457 if matches!(
13458 type_node.kind(),
13459 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13460 ) {
13461 type_node = type_node.child_by_field_name("name")?;
13462 }
13463 let global = type_node.child_by_field_name("scope").is_none()
13464 && type_node.child(0).is_some_and(|child| child.kind() == "::");
13465 let mut components = Vec::new();
13466 append_structured_type_components(type_node, source, &mut components)?;
13467 let arguments = cpp_template_reference_arguments(type_node, source);
13468 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13469 components,
13470 global,
13471 arguments,
13472 })
13473}
13474
13475fn append_structured_type_components(
13476 node: Node<'_>,
13477 source: &str,
13478 out: &mut Vec<String>,
13479) -> Option<()> {
13480 match node.kind() {
13481 "identifier" | "namespace_identifier" | "type_identifier" => {
13482 out.push(node_text(node, source).to_string());
13483 Some(())
13484 }
13485 "template_type" => {
13486 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13487 }
13488 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13489 if let Some(scope) = node.child_by_field_name("scope") {
13490 append_structured_type_components(scope, source, out)?;
13491 }
13492 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13493 }
13494 _ => None,
13495 }
13496}
13497
13498pub(crate) struct DeclaredNameBinding {
13501 pub pointer_depth: i32,
13503 pub indirect: bool,
13507}
13508
13509pub(crate) fn declared_name_binding(
13510 declaration: Node<'_>,
13511 type_node: Node<'_>,
13512 field_name: &str,
13513 source: &str,
13514) -> Option<DeclaredNameBinding> {
13515 let mut stack = Vec::new();
13516 let mut cursor = declaration.walk();
13517 stack.extend(
13518 declaration
13519 .named_children(&mut cursor)
13520 .filter(|child| !same_node(*child, type_node)),
13521 );
13522 while let Some(node) = stack.pop() {
13523 if matches!(node.kind(), "identifier" | "field_identifier")
13524 && node_text(node, source) == field_name
13525 {
13526 let mut binding = DeclaredNameBinding {
13527 pointer_depth: 0,
13528 indirect: false,
13529 };
13530 let mut current = node.parent();
13531 while let Some(parent) = current {
13532 if same_node(parent, declaration) {
13533 return Some(binding);
13534 }
13535 match parent.kind() {
13536 "pointer_declarator" => {
13537 binding.pointer_depth += 1;
13538 binding.indirect = true;
13539 }
13540 "reference_declarator" => binding.indirect = true,
13541 _ => {}
13542 }
13543 current = parent.parent();
13544 }
13545 return None;
13546 }
13547 let mut cursor = node.walk();
13548 stack.extend(node.named_children(&mut cursor));
13549 }
13550 None
13551}
13552
13553fn field_declaration_type_matches(
13554 declaration: &str,
13555 unit: &CodeUnit,
13556 ctx: &ScanCtx<'_>,
13557 owner: &CodeUnit,
13558) -> bool {
13559 ctx.visibility
13560 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
13561 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
13562 let normalized = normalize_field_type_text(type_text);
13563 ctx.visibility
13564 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
13565 || ctx.visibility.resolves_to_type(
13566 &ctx.analyzer,
13567 ctx.file,
13568 normalized.as_str(),
13569 owner,
13570 )
13571 })
13572}
13573
13574fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
13575 let declaration = declaration
13576 .split(['=', ';'])
13577 .next()
13578 .unwrap_or(declaration)
13579 .trim();
13580 let index = declaration.rfind(field_name)?;
13581 let before = &declaration[..index];
13582 let after = &declaration[index + field_name.len()..];
13583 if before.chars().next_back().is_some_and(is_identifier_char)
13584 || after.chars().next().is_some_and(is_identifier_char)
13585 {
13586 return None;
13587 }
13588 Some(before.trim())
13589}
13590
13591fn normalize_field_type_text(type_text: &str) -> String {
13592 const FIELD_SPECIFIERS: [&str; 8] = [
13593 "extern ",
13594 "static ",
13595 "mutable ",
13596 "constexpr ",
13597 "constinit ",
13598 "inline ",
13599 "volatile ",
13600 "const ",
13601 ];
13602
13603 let mut normalized = normalize_type_text(type_text);
13604 loop {
13605 let Some(stripped) = FIELD_SPECIFIERS
13606 .iter()
13607 .find_map(|specifier| normalized.strip_prefix(specifier))
13608 else {
13609 return normalized;
13610 };
13611 normalized = normalize_type_text(stripped);
13612 }
13613}
13614
13615fn is_identifier_char(ch: char) -> bool {
13616 ch == '_' || ch.is_ascii_alphanumeric()
13617}
13618
13619pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13620 let Some(type_node) = node.child_by_field_name("type") else {
13621 return false;
13622 };
13623 ctx.visibility.resolves_to_type(
13624 &ctx.analyzer,
13625 ctx.file,
13626 node_text(type_node, ctx.source),
13627 owner,
13628 )
13629}
13630
13631pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13632 !ctx.analyzer
13633 .declarations(ctx.file)
13634 .into_iter()
13635 .filter(|unit| unit.is_function())
13636 .any(|unit| {
13637 ctx.analyzer.ranges(&unit).iter().any(|range| {
13638 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13639 })
13640 })
13641}
13642
13643pub enum DeclarationConstructorInitializer<'tree> {
13645 Arguments(Node<'tree>),
13648 Expression(Node<'tree>),
13651 Empty,
13653}
13654
13655pub fn declaration_constructor_initializer(
13656 node: Node<'_>,
13657) -> DeclarationConstructorInitializer<'_> {
13658 let mut cursor = node.walk();
13659 for child in node.named_children(&mut cursor) {
13660 if child.kind() == "init_declarator" {
13661 let Some(value) = child
13662 .child_by_field_name("value")
13663 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13664 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13665 else {
13666 return DeclarationConstructorInitializer::Empty;
13667 };
13668 return match value.kind() {
13669 "argument_list" | "initializer_list" => {
13670 DeclarationConstructorInitializer::Arguments(value)
13671 }
13672 "compound_literal_expression" => call_arguments_node(value)
13673 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13674 DeclarationConstructorInitializer::Arguments(arguments)
13675 }),
13676 _ => DeclarationConstructorInitializer::Expression(value),
13677 };
13678 }
13679 if let Some(declarator) = declaration_declarator(node, child) {
13680 return declarator_parameters(declarator)
13681 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13682 DeclarationConstructorInitializer::Arguments(parameters)
13683 });
13684 }
13685 }
13686 DeclarationConstructorInitializer::Empty
13687}
13688
13689pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13690 match declaration_constructor_initializer(node) {
13691 DeclarationConstructorInitializer::Arguments(arguments) => {
13692 argument_children(arguments).count()
13693 }
13694 DeclarationConstructorInitializer::Expression(_) => 1,
13695 DeclarationConstructorInitializer::Empty => 0,
13696 }
13697}
13698
13699fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13703 let mut current = node;
13704 loop {
13705 if let Some(parameters) = current.child_by_field_name("parameters") {
13706 return Some(parameters);
13707 }
13708 current = current.child_by_field_name("declarator")?;
13709 }
13710}
13711
13712pub(super) fn first_named_child_of_kind<'tree>(
13713 node: Node<'tree>,
13714 kind: &str,
13715) -> Option<Node<'tree>> {
13716 let mut cursor = node.walk();
13717 node.named_children(&mut cursor)
13718 .find(|child| child.kind() == kind)
13719}
13720
13721fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13722 let mut stack = vec![root];
13723 while let Some(node) = stack.pop() {
13724 if node.kind() == kind {
13725 return Some(node);
13726 }
13727 push_named_children_reversed(node, &mut stack);
13728 }
13729 None
13730}
13731
13732fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13733 if node.kind() == "identifier" {
13734 return true;
13735 }
13736 if node.kind() == "parenthesized_expression" {
13737 return false;
13738 }
13739 if node.kind() == "call_expression" {
13740 return node
13741 .child_by_field_name("function")
13742 .is_some_and(|function| function.kind() == "identifier");
13743 }
13744 let mut stack = vec![node];
13745 while let Some(descendant) = stack.pop() {
13746 if descendant != node && descendant.kind() == "parenthesized_expression" {
13747 continue;
13748 }
13749 if descendant.kind() == "identifier" {
13750 return true;
13751 }
13752 if descendant.kind() == "call_expression" {
13753 if descendant
13754 .child_by_field_name("function")
13755 .is_some_and(|function| function.kind() == "identifier")
13756 {
13757 return true;
13758 }
13759 continue;
13760 }
13761 push_named_children_reversed(descendant, &mut stack);
13762 }
13763 false
13764}
13765
13766fn macro_expansion_shape_is_safe(
13767 node: Node<'_>,
13768 source: &str,
13769 parameters: &[String],
13770 environment: &MacroEnvironment,
13771) -> bool {
13772 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13773 return true;
13774 }
13775 if node.kind() == "call_expression" {
13776 let Some(function) = node.child_by_field_name("function") else {
13777 return true;
13778 };
13779 if function.kind() != "identifier" {
13780 return true;
13781 }
13782 let function_name = node_text(function, source);
13783 if parameters
13784 .iter()
13785 .any(|parameter| parameter == function_name)
13786 {
13787 return false;
13788 }
13789 if !environment.may_bind(function_name) {
13790 return true;
13791 }
13792 let Some(arguments) = node.child_by_field_name("arguments") else {
13793 return false;
13794 };
13795 return argument_children(arguments).all(|argument| {
13796 if argument.kind() == "identifier"
13797 && parameters
13798 .iter()
13799 .any(|parameter| parameter == node_text(argument, source))
13800 {
13801 return false;
13802 }
13803 macro_expansion_shape_is_safe(argument, source, parameters, environment)
13804 });
13805 }
13806 let mut stack = vec![node];
13807 while let Some(descendant) = stack.pop() {
13808 if descendant != node {
13809 if descendant.kind() == "parenthesized_expression" {
13810 continue;
13811 }
13812 if descendant.kind() == "call_expression" {
13813 let expands = descendant
13814 .child_by_field_name("function")
13815 .filter(|function| function.kind() == "identifier")
13816 .is_some_and(|function| environment.may_bind(node_text(function, source)));
13817 if expands {
13818 return false;
13819 }
13820 continue;
13821 }
13822 }
13823 if descendant.kind() == "identifier" {
13824 let identifier = node_text(descendant, source);
13825 if parameters.iter().any(|parameter| parameter == identifier)
13826 || environment.may_bind(identifier)
13827 {
13828 return false;
13829 }
13830 }
13831 push_named_children_reversed(descendant, &mut stack);
13832 }
13833 true
13834}
13835
13836fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13837 let text = node_text(path, source);
13838 match path.kind() {
13839 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13840 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13841 _ => None,
13842 }
13843}
13844
13845fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13846 let source = prepared.source();
13847 let mut facts = Vec::new();
13848 let mut nodes = vec![prepared.tree().root_node()];
13849 while let Some(node) = nodes.pop() {
13850 if node.kind() == "preproc_include" {
13851 let Some(path) = node
13852 .child_by_field_name("path")
13853 .and_then(|path| structured_include_path(path, source))
13854 .map(str::to_owned)
13855 else {
13856 continue;
13857 };
13858 facts.push(StructuredIncludeFact {
13859 start_byte: node.start_byte(),
13860 end_byte: node.end_byte(),
13861 path,
13862 });
13863 continue;
13864 }
13865 push_named_children_reversed(node, &mut nodes);
13866 }
13867 Arc::from(facts.into_boxed_slice())
13868}
13869
13870fn has_unresolved_include_visible_before_in_prepared(
13871 file: &ProjectFile,
13872 prepared: &PreparedSyntaxTree,
13873 include_targets: &IncludeTargetIndex,
13874 facts: &[StructuredIncludeFact],
13875 before_byte: usize,
13876) -> bool {
13877 let guards = OnceCell::new();
13878 let reference = CallableReferenceContext {
13879 file,
13880 position: Some(CallableReferencePosition {
13881 prepared,
13882 byte: before_byte,
13883 guards: &guards,
13884 }),
13885 };
13886 let root = prepared.tree().root_node();
13887 facts
13888 .iter()
13889 .filter(|fact| fact.end_byte <= before_byte)
13890 .any(|fact| {
13891 let node = root
13892 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13893 .expect("structured include fact range must be in prepared tree");
13894 assert_eq!(
13895 node.kind(),
13896 "preproc_include",
13897 "structured include fact range must identify its include node"
13898 );
13899 callable_preprocessor_context_is_visible_for_reference(
13900 node,
13901 prepared.source(),
13902 &reference,
13903 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13904 })
13905}
13906
13907fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13908 let descendant = node;
13909 while let Some(parent) = node.parent() {
13910 if is_preprocessor_conditional(parent)
13911 && !is_file_covering_include_guard(parent, source)
13912 && preprocessor_conditional_contains_descendant(parent, descendant)
13913 {
13914 return true;
13915 }
13916 node = parent;
13917 }
13918 false
13919}
13920
13921fn owning_preprocessor_conditionals(
13933 root: Node<'_>,
13934 event: Node<'_>,
13935 source: &str,
13936) -> OwningPreprocessorConditionals {
13937 if !has_preprocessor_conditional_ancestor(event, source) {
13938 return OwningPreprocessorConditionals::default();
13939 }
13940 let start = event.start_byte();
13941 let descendant = root
13942 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13943 .expect("a byte inside the parsed tree names a descendant");
13944 let mut owners = Vec::new();
13945 let mut current = descendant.parent();
13946 while let Some(conditional) = current {
13947 if is_preprocessor_conditional(conditional)
13948 && !is_file_covering_include_guard(conditional, source)
13949 && preprocessor_conditional_contains_descendant(conditional, descendant)
13950 {
13951 owners.push(conditional.start_byte());
13952 }
13953 current = conditional.parent();
13954 }
13955 owners.into_boxed_slice()
13956}
13957
13958fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13959 matches!(
13960 node.kind(),
13961 "preproc_if"
13962 | "preproc_ifdef"
13963 | "preproc_ifndef"
13964 | "preproc_elif"
13965 | "preproc_elifdef"
13966 | "preproc_else"
13967 )
13968}
13969
13970fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13971 node.parent()
13972 .filter(|parent| parent.kind() == "translation_unit")
13973 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13974 && is_canonical_include_guard(node, source)
13975}
13976
13977fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13978 if node.kind() != "preproc_ifdef"
13979 || node
13980 .child(0)
13981 .is_none_or(|directive| directive.kind() != "#ifndef")
13982 || node.child_by_field_name("alternative").is_some()
13983 {
13984 return false;
13985 }
13986 let Some(guard_name) = node.child_by_field_name("name") else {
13987 return false;
13988 };
13989 let mut cursor = node.walk();
13990 node.named_children(&mut cursor)
13991 .find(|child| *child != guard_name && child.kind() != "comment")
13992 .filter(|child| child.kind() == "preproc_def")
13993 .and_then(|definition| definition.child_by_field_name("name"))
13994 .is_some_and(|defined_name| {
13995 node_text(defined_name, source) == node_text(guard_name, source)
13996 })
13997}
13998
13999fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14000 let mut guard = None;
14001 for child in named_children_iter(root) {
14002 if child.kind() == "comment" || is_pragma_once(child, source) {
14003 continue;
14004 }
14005 if guard.is_none() && is_canonical_include_guard(child, source) {
14006 guard = Some(child);
14007 } else {
14008 return None;
14009 }
14010 }
14011 guard
14012 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14013 .map(|name| node_text(name, source).to_string())
14014}
14015
14016fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14017 if (0..root.named_child_count())
14018 .filter_map(|index| root.named_child(index))
14019 .any(|child| is_pragma_once(child, source))
14020 {
14021 return MacroIncludeProtection::PragmaOnce;
14022 }
14023 top_level_canonical_include_guard_name(root, source)
14024 .map(MacroIncludeProtection::MacroGuard)
14025 .unwrap_or(MacroIncludeProtection::None)
14026}
14027
14028fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14029 node.kind() == "preproc_call"
14030 && node
14031 .child_by_field_name("directive")
14032 .is_some_and(|directive| node_text(directive, source) == "#pragma")
14033 && node
14034 .child_by_field_name("argument")
14035 .is_some_and(|argument| node_text(argument, source).trim() == "once")
14036}
14037
14038fn parse_preproc_identifier(argument: &str) -> Option<String> {
14039 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14040 let mut parser = Parser::new();
14041 parser
14042 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14043 .ok()?;
14044 let tree = parser.parse(&sentinel, None)?;
14045 if tree.root_node().has_error() {
14046 return None;
14047 }
14048 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14049 let identifier = statement.named_child(0)?;
14050 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14051 .then(|| node_text(identifier, &sentinel).to_string())
14052}
14053
14054pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14055 match node.kind() {
14056 "identifier" | "field_identifier" => {
14057 let name = node_text(node, source).trim();
14058 (!name.is_empty()).then(|| name.to_string())
14059 }
14060 "abstract_array_declarator"
14061 | "abstract_function_declarator"
14062 | "abstract_parenthesized_declarator"
14063 | "abstract_pointer_declarator"
14064 | "abstract_reference_declarator" => None,
14065 "function_declarator" => node
14066 .child_by_field_name("declarator")
14067 .or_else(|| node.child_by_field_name("name"))
14068 .and_then(|child| extract_variable_name(child, source)),
14069 _ => node
14070 .child_by_field_name("declarator")
14071 .or_else(|| node.child_by_field_name("name"))
14072 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14073 .and_then(|child| extract_variable_name(child, source)),
14074 }
14075}
14076
14077pub fn is_c_source_file(file: &ProjectFile) -> bool {
14088 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14089}
14090
14091pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14098 if !is_c_source_file(file) || node.kind() != "identifier" {
14099 return false;
14100 }
14101 let mut operand = node;
14102 while let Some(parent) = operand.parent().filter(|parent| {
14103 parent.kind() == "parenthesized_expression"
14104 && parent.named_child_count() == 1
14105 && parent.named_child(0) == Some(operand)
14106 }) {
14107 operand = parent;
14108 }
14109 operand.parent().is_some_and(|parent| {
14110 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14111 })
14112}
14113
14114pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14123 if node.kind() != "field_identifier" {
14124 return None;
14125 }
14126 let expression = node.parent().filter(|parent| {
14127 parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
14128 })?;
14129 if expression.has_error() {
14130 return None;
14131 }
14132 let closing = expression.child(expression.child_count().saturating_sub(1))?;
14133 if closing.kind() != ")" || closing.is_missing() {
14134 return None;
14135 }
14136 let type_descriptor = expression.child_by_field_name("type")?;
14137 if type_descriptor.kind() != "type_descriptor"
14138 || type_descriptor.is_missing()
14139 || type_descriptor.has_error()
14140 {
14141 return None;
14142 }
14143 let type_specifier = type_descriptor.child_by_field_name("type")?;
14144 if type_specifier.is_missing() || type_specifier.has_error() {
14145 return None;
14146 }
14147 let type_reference = match type_specifier.kind() {
14148 "class_specifier" | "struct_specifier" | "union_specifier" => {
14149 type_specifier.child_by_field_name("name")?
14150 }
14151 _ => type_specifier,
14152 };
14153 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14154}
14155
14156pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14161 node.kind() == "field_identifier"
14162 && node.parent().is_some_and(|parent| {
14163 parent.kind() == "offsetof_expression"
14164 && parent.child_by_field_name("member") == Some(node)
14165 })
14166}
14167
14168pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14184 if node.kind() != "type_identifier" {
14185 return false;
14186 }
14187 let Some(descriptor) = node
14188 .parent()
14189 .filter(|parent| parent.kind() == "type_descriptor")
14190 else {
14191 return false;
14192 };
14193 if descriptor.child_by_field_name("type") != Some(node) {
14194 return false;
14195 }
14196 let Some(arguments) = descriptor
14197 .parent()
14198 .filter(|parent| parent.kind() == "template_argument_list")
14199 else {
14200 return false;
14201 };
14202 arguments.parent().is_some_and(|owner| {
14203 matches!(
14204 owner.kind(),
14205 "template_type" | "template_function" | "template_method"
14206 ) && owner.child_by_field_name("arguments") == Some(arguments)
14207 })
14208}
14209
14210pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14223 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14224}
14225
14226pub fn is_declarator_node(node: Node<'_>) -> bool {
14227 matches!(
14228 node.kind(),
14229 "identifier"
14230 | "field_identifier"
14231 | "qualified_identifier"
14232 | "scoped_identifier"
14233 | "pointer_declarator"
14234 | "reference_declarator"
14235 | "array_declarator"
14236 | "parenthesized_declarator"
14237 | "function_declarator"
14238 )
14239}
14240
14241pub fn declaration_declarator<'tree>(
14250 declaration: Node<'tree>,
14251 child: Node<'tree>,
14252) -> Option<Node<'tree>> {
14253 if !matches!(
14254 declaration.kind(),
14255 "declaration"
14256 | "field_declaration"
14257 | "parameter_declaration"
14258 | "optional_parameter_declaration"
14259 | "function_definition"
14260 | "type_definition"
14261 | "alias_declaration"
14262 | "template_instantiation"
14263 ) {
14264 return None;
14265 }
14266 if declaration
14267 .child_by_field_name("type")
14268 .is_some_and(|type_node| same_node(type_node, child))
14269 {
14270 return None;
14271 }
14272 if child.kind() == "init_declarator" {
14273 return child.child_by_field_name("declarator");
14274 }
14275 let field = field_name_in_parent(declaration, child);
14276 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14277 || (declaration.kind() == "type_definition"
14278 && child.kind() == "type_identifier"
14279 && matches!(field, Some("declarator") | None))
14280 {
14281 Some(child)
14282 } else {
14283 None
14284 }
14285}
14286
14287#[derive(Clone, Debug, PartialEq, Eq)]
14290pub struct RecoveredNamespaceRegion {
14291 pub start: usize,
14293 pub end: usize,
14295 pub components: Vec<String>,
14298 pub component_ranges: Vec<Range>,
14307}
14308
14309#[derive(Clone, Debug, Default)]
14345pub struct OrphanedNamespaceScopeIndex {
14346 regions: Vec<RecoveredNamespaceRegion>,
14347 brace_closes: HashMap<usize, Range>,
14350}
14351
14352impl OrphanedNamespaceScopeIndex {
14353 pub fn build(root: Node<'_>, source: &str) -> Self {
14354 if !root.has_error() {
14355 return Self::default();
14356 }
14357 #[derive(Clone, Copy)]
14363 struct OpenHead {
14364 head: Range,
14365 open: usize,
14366 }
14367 struct PendingRegion {
14370 start: usize,
14371 end: usize,
14372 components: Vec<String>,
14373 heads: Vec<OpenHead>,
14374 }
14375 struct Frame<'tree> {
14376 node: Node<'tree>,
14377 children: Vec<Node<'tree>>,
14378 next: usize,
14379 parsed_scope: Vec<String>,
14380 run: Option<PendingRegion>,
14381 }
14382 fn frame<'tree>(
14383 node: Node<'tree>,
14384 mut parsed_scope: Vec<String>,
14385 source: &str,
14386 ) -> Frame<'tree> {
14387 if node.kind() == "namespace_definition"
14388 && let Some(name) = node.child_by_field_name("name")
14389 {
14390 let mut components = Vec::new();
14391 if append_cpp_name_components(name, source, &mut components).is_some() {
14392 parsed_scope.extend(components);
14393 }
14394 }
14395 let mut cursor = node.walk();
14396 Frame {
14397 node,
14398 children: node.children(&mut cursor).collect(),
14399 next: 0,
14400 parsed_scope,
14401 run: None,
14402 }
14403 }
14404 let mut regions = Vec::new();
14405 let mut brace_closes = HashMap::default();
14406 let mut unpaired_closes: Vec<Range> = Vec::new();
14412 let mut open = Vec::new();
14416 let mut lexical_scope = Vec::new();
14417 let mut lexical_heads: Vec<OpenHead> = Vec::new();
14422 let mut frames = vec![frame(root, Vec::new(), source)];
14423 while !frames.is_empty() {
14428 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14429 let current = frames.last_mut().expect("frames is non-empty");
14430 if current.next == current.children.len() {
14431 regions.extend(frames.pop().expect("the frame just borrowed").run);
14432 continue;
14433 }
14434 let child = current.children[current.next];
14435 current.next += 1;
14436 match child.kind() {
14437 "{" if !child.is_missing() => {
14438 regions.extend(current.run.take());
14439 let mut named = parent_node.and_then(|parent| {
14444 let components =
14445 namespace_body_name_components(parent, current.node, source);
14446 (!components.is_empty()).then_some((parent, components))
14447 });
14448 if named.is_none() {
14449 named = recovered_namespace_open_components(
14450 ¤t.children[..current.next - 1],
14451 source,
14452 );
14453 }
14454 open.push((child.start_byte(), lexical_scope.len()));
14455 if let Some((written_at, components)) = named {
14456 let head = OpenHead {
14457 head: Range {
14458 start_byte: written_at.start_byte(),
14459 end_byte: child.end_byte(),
14460 start_line: written_at.start_position().row + 1,
14461 end_line: child.end_position().row + 1,
14462 },
14463 open: child.start_byte(),
14464 };
14465 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14466 lexical_scope.extend(components);
14467 }
14468 continue;
14469 }
14470 "}" if !child.is_missing() => {
14471 regions.extend(current.run.take());
14472 let close = Range {
14473 start_byte: child.start_byte(),
14474 end_byte: child.end_byte(),
14475 start_line: child.start_position().row + 1,
14476 end_line: child.end_position().row + 1,
14477 };
14478 match open.pop() {
14479 Some((start, namespace_len)) => {
14480 debug_assert!(
14481 start < close.start_byte,
14482 "a close pairs with an open before it: {start} then {close:?}"
14483 );
14484 lexical_scope.truncate(namespace_len);
14485 lexical_heads.truncate(namespace_len);
14486 brace_closes.insert(start, close);
14487 }
14488 None => unpaired_closes.push(close),
14489 }
14490 continue;
14491 }
14492 _ => {}
14493 }
14494 if current.node.kind() != "namespace_definition"
14498 && lexical_scope != current.parsed_scope
14499 {
14500 match &mut current.run {
14501 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14502 run => {
14503 regions.extend(run.take());
14504 *run = Some(PendingRegion {
14505 start: child.start_byte(),
14506 end: child.end_byte(),
14507 components: lexical_scope.clone(),
14508 heads: lexical_heads.clone(),
14509 });
14510 }
14511 }
14512 } else {
14513 regions.extend(current.run.take());
14514 }
14515 if child.has_error() {
14519 let parsed_scope = current.parsed_scope.clone();
14520 frames.push(frame(child, parsed_scope, source));
14521 }
14522 }
14523 if !unpaired_closes.is_empty() {
14532 brace_closes.clear();
14533 }
14534 let regions = regions
14539 .into_iter()
14540 .map(|region| {
14541 debug_assert_eq!(
14542 region.components.len(),
14543 region.heads.len(),
14544 "every recovered namespace level carries the head that writes it: {:?}",
14545 region.components
14546 );
14547 RecoveredNamespaceRegion {
14548 start: region.start,
14549 end: region.end,
14550 component_ranges: region
14551 .heads
14552 .iter()
14553 .map(|level| match brace_closes.get(&level.open) {
14554 Some(close) => Range {
14555 start_byte: level.head.start_byte,
14556 end_byte: close.end_byte,
14557 start_line: level.head.start_line,
14558 end_line: close.end_line,
14559 },
14560 None => level.head,
14561 })
14562 .collect(),
14563 components: region.components,
14564 }
14565 })
14566 .collect();
14567 Self {
14568 regions,
14569 brace_closes,
14570 }
14571 }
14572
14573 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14580 self.brace_closes.get(&open).copied()
14581 }
14582
14583 pub fn is_empty(&self) -> bool {
14584 self.regions.is_empty()
14585 }
14586
14587 pub fn approximate_size(&self) -> usize {
14589 self.regions.iter().fold(
14590 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14591 |total, region| {
14592 total
14593 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14594 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14595 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14596 },
14597 )
14598 }
14599
14600 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14602 self.regions
14603 .iter()
14604 .filter(|region| region.start <= byte && byte < region.end)
14605 .min_by_key(|region| region.end - region.start)
14606 }
14607
14608 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14612 self.enclosing_namespace_levels(node, source)
14613 .into_iter()
14614 .map(|(component, _)| component)
14615 .collect()
14616 }
14617
14618 pub fn enclosing_namespace_levels(&self, node: Node<'_>, source: &str) -> Vec<(String, Range)> {
14625 let mut parsed = Vec::new();
14626 let mut current = node.parent();
14627 while let Some(parent) = current {
14628 if parent.kind() == "namespace_definition"
14629 && let Some(name) = parent.child_by_field_name("name")
14630 {
14631 let mut components = Vec::new();
14632 if append_cpp_name_components(name, source, &mut components).is_some() {
14633 let range = node_range(parent);
14634 parsed.push((
14635 parent.start_byte(),
14636 components
14637 .into_iter()
14638 .map(|component| (component, range))
14639 .collect(),
14640 ));
14641 }
14642 }
14643 current = parent.parent();
14644 }
14645 parsed.reverse();
14646 self.splice_recovered_path(parsed, node.start_byte(), |region| {
14647 region
14648 .components
14649 .iter()
14650 .cloned()
14651 .zip(region.component_ranges.iter().copied())
14652 .collect()
14653 })
14654 }
14655
14656 pub fn restore_enclosing_namespaces(
14662 &self,
14663 parsed: Vec<(usize, Vec<String>)>,
14664 node_start: usize,
14665 ) -> Vec<String> {
14666 self.splice_recovered_path(parsed, node_start, |region| region.components.clone())
14667 }
14668
14669 fn splice_recovered_path<T>(
14675 &self,
14676 parsed: Vec<(usize, Vec<T>)>,
14677 node_start: usize,
14678 recovered: impl FnOnce(&RecoveredNamespaceRegion) -> Vec<T>,
14679 ) -> Vec<T> {
14680 let Some(region) = self.region_at(node_start) else {
14681 return parsed.into_iter().flat_map(|(_, levels)| levels).collect();
14682 };
14683 recovered(region)
14684 .into_iter()
14685 .chain(
14686 parsed
14687 .into_iter()
14688 .filter(|(start, _)| *start >= region.start)
14689 .flat_map(|(_, levels)| levels),
14690 )
14691 .collect()
14692 }
14693}
14694
14695fn recovered_namespace_open_components<'tree>(
14711 preceding: &[Node<'tree>],
14712 source: &str,
14713) -> Option<(Node<'tree>, Vec<String>)> {
14714 let mut head = Vec::new();
14715 for &sibling in preceding.iter().rev() {
14716 if sibling.kind() != "comment" {
14717 head.push(sibling);
14718 if head.len() == 2 {
14719 break;
14720 }
14721 }
14722 }
14723 let [name, keyword] = head[..] else {
14724 return None;
14725 };
14726 if keyword.kind() != "namespace" {
14727 return None;
14728 }
14729 let mut components = Vec::new();
14730 append_cpp_name_components(name, source, &mut components)?;
14731 (!components.is_empty()).then_some((keyword, components))
14732}
14733
14734fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14737 let mut components = Vec::new();
14738 if body.kind() == "declaration_list"
14739 && parent.kind() == "namespace_definition"
14740 && parent.child_by_field_name("body") == Some(body)
14741 && let Some(name) = parent.child_by_field_name("name")
14742 && append_cpp_name_components(name, source, &mut components).is_none()
14743 {
14744 components.clear();
14745 }
14746 components
14747}
14748
14749#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14750pub enum RecoveredDeclaratorTypeContext {
14751 Declaration,
14752 FunctionDefinition,
14753 Parameter,
14754}
14755
14756pub fn recovered_macro_decorated_declarator_type(
14771 node: Node<'_>,
14772) -> Option<RecoveredDeclaratorTypeContext> {
14773 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14774}
14775
14776pub fn recovered_macro_decorated_type_node(
14781 node: Node<'_>,
14782) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14783 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14784 return None;
14785 }
14786 let qualified = node.parent()?;
14787 if qualified.kind() != "qualified_identifier"
14788 || qualified.child_by_field_name("scope") != Some(node)
14789 || !(0..qualified.child_count())
14790 .filter_map(|index| qualified.child(index))
14791 .any(|child| child.kind() == "::" && child.is_missing())
14792 {
14793 return None;
14794 }
14795 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14796 return None;
14797 }
14798
14799 let (declaration, context) = recovered_declarator_container(qualified)?;
14800 let type_node = declaration
14801 .child_by_field_name("type")
14802 .filter(|type_node| {
14803 *type_node != qualified
14804 && !type_node.is_missing()
14805 && type_node.start_byte() != type_node.end_byte()
14806 })?;
14807 Some((type_node, context))
14808}
14809
14810fn recovered_declarator_container(
14811 mut declarator: Node<'_>,
14812) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14813 loop {
14814 let parent = declarator.parent()?;
14815 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14816 return Some((
14817 parent
14818 .parent()
14819 .filter(|declaration| declaration.kind() == "declaration")?,
14820 RecoveredDeclaratorTypeContext::Declaration,
14821 ));
14822 }
14823 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14824 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14825 }
14826 if parent.kind() == "function_definition"
14827 && has_field_child(parent, "declarator", declarator)
14828 {
14829 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14830 }
14831 if matches!(
14837 parent.kind(),
14838 "parameter_declaration" | "optional_parameter_declaration"
14839 ) && has_field_child(parent, "declarator", declarator)
14840 {
14841 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14842 }
14843 if !matches!(
14844 parent.kind(),
14845 "array_declarator"
14846 | "function_declarator"
14847 | "parenthesized_declarator"
14848 | "pointer_declarator"
14849 | "pointer_type_declarator"
14850 | "reference_declarator"
14851 ) || !has_field_child(parent, "declarator", declarator)
14852 {
14853 return None;
14854 }
14855 declarator = parent;
14856 }
14857}
14858
14859fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14860 let mut cursor = parent.walk();
14861 parent
14862 .children_by_field_name(field, &mut cursor)
14863 .any(|child| child == target)
14864}
14865
14866fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14867 loop {
14868 if node.is_missing() || node.start_byte() == node.end_byte() {
14869 return false;
14870 }
14871 match node.kind() {
14872 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14873 return true;
14874 }
14875 "array_declarator"
14876 | "function_declarator"
14877 | "parenthesized_declarator"
14878 | "pointer_declarator"
14879 | "pointer_type_declarator"
14880 | "reference_declarator" => {
14881 let Some(declarator) = node.child_by_field_name("declarator") else {
14882 return false;
14883 };
14884 node = declarator;
14885 }
14886 _ => return false,
14887 }
14888 }
14889}
14890
14891pub enum DesignatedInitializerOwner {
14893 Resolved(CodeUnit),
14894 Unresolved,
14895}
14896
14897enum InitializerOwnerStep {
14898 Field(String),
14899 AggregateWrapper,
14900}
14901
14902pub fn designated_initializer_owner(
14912 analyzer: &CppGraphSource<'_>,
14913 visibility: &VisibilityIndex<'_>,
14914 file: &ProjectFile,
14915 source: &str,
14916 node: Node<'_>,
14917) -> Option<DesignatedInitializerOwner> {
14918 if let Some(designator) = node
14919 .parent()
14920 .filter(|parent| parent.kind() == "field_designator")
14921 {
14922 let pair = designator.parent()?;
14923 if pair.kind() != "initializer_pair" {
14924 return None;
14925 }
14926 let mut cursor = pair.walk();
14927 let designators = pair
14928 .children_by_field_name("designator", &mut cursor)
14929 .collect::<Vec<_>>();
14930 let position = designators
14931 .iter()
14932 .position(|candidate| same_node(*candidate, designator))?;
14933 let initializer = pair.parent()?;
14934 if initializer.kind() != "initializer_list" {
14935 return None;
14936 }
14937 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14938 for prior in &designators[..position] {
14939 let field = prior
14940 .child_by_field_name("field")
14941 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14942 owner = owner.and_then(|owner| {
14943 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14944 });
14945 }
14946 return Some(classified_designated_owner(owner));
14947 }
14948
14949 let init_declarator = node.parent()?;
14950 if init_declarator.child_by_field_name("declarator") != Some(node)
14951 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14952 {
14953 return None;
14954 }
14955 Some(classified_designated_owner(declaration_owner(
14956 analyzer,
14957 visibility,
14958 file,
14959 source,
14960 init_declarator.parent()?,
14961 )))
14962}
14963
14964fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14965 owner.map_or(
14966 DesignatedInitializerOwner::Unresolved,
14967 DesignatedInitializerOwner::Resolved,
14968 )
14969}
14970
14971fn initializer_list_owner(
14972 analyzer: &CppGraphSource<'_>,
14973 visibility: &VisibilityIndex<'_>,
14974 file: &ProjectFile,
14975 source: &str,
14976 initializer: Node<'_>,
14977) -> Option<CodeUnit> {
14978 let mut current = initializer;
14979 let mut steps = Vec::new();
14980 loop {
14981 let parent = current.parent()?;
14982 match parent.kind() {
14983 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14984 let designator = parent.child_by_field_name("designator")?;
14985 let step = designator
14986 .child_by_field_name("field")
14987 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14988 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14989 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14990 steps.push(step);
14991 current = parent.parent()?;
14992 }
14993 "initializer_list" => {
14994 current = parent;
14995 }
14996 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14997 let declaration = parent.parent()?;
14998 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14999 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15000 }
15001 "compound_literal_expression"
15002 if parent.child_by_field_name("value") == Some(current) =>
15003 {
15004 let type_node = parent.child_by_field_name("type")?;
15005 let owner =
15006 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15007 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15008 }
15009 "ERROR" => current = parent,
15010 _ => return None,
15011 }
15012 }
15013}
15014
15015fn apply_initializer_owner_steps(
15016 analyzer: &CppGraphSource<'_>,
15017 visibility: &VisibilityIndex<'_>,
15018 file: &ProjectFile,
15019 mut owner: CodeUnit,
15020 steps: Vec<InitializerOwnerStep>,
15021) -> Option<CodeUnit> {
15022 for step in steps.into_iter().rev() {
15023 if let InitializerOwnerStep::Field(field_name) = step {
15024 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15025 }
15026 }
15027 Some(owner)
15028}
15029
15030fn initializer_field_owner(
15031 analyzer: &CppGraphSource<'_>,
15032 visibility: &VisibilityIndex<'_>,
15033 file: &ProjectFile,
15034 owner: CodeUnit,
15035 field_name: &str,
15036) -> Option<CodeUnit> {
15037 let fields = visibility
15038 .visible_members_for_owner_name(file, &owner, field_name)
15039 .into_iter()
15040 .filter(|field| field.is_field())
15041 .collect::<Vec<_>>();
15042 let field = match fields.as_slice() {
15043 [field] => *field,
15044 _ => return None,
15045 };
15046 field_declared_binding(analyzer, visibility, file, field)?.unit
15047}
15048
15049fn declaration_owner(
15050 analyzer: &CppGraphSource<'_>,
15051 visibility: &VisibilityIndex<'_>,
15052 file: &ProjectFile,
15053 source: &str,
15054 declaration: Node<'_>,
15055) -> Option<CodeUnit> {
15056 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15057 return None;
15058 }
15059 let type_node = declaration
15060 .child_by_field_name("type")
15061 .or_else(|| first_type_child(declaration))?;
15062 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15063}
15064
15065fn resolve_designated_owner_type(
15066 analyzer: &CppGraphSource<'_>,
15067 visibility: &VisibilityIndex<'_>,
15068 file: &ProjectFile,
15069 source: &str,
15070 type_node: Node<'_>,
15071) -> Option<CodeUnit> {
15072 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15073 return Some(owner);
15074 }
15075 let type_name = normalize_type_text(node_text(type_node, source));
15076 visibility
15077 .resolve_type(file, &type_name)
15078 .filter(CodeUnit::is_class)
15079}
15080
15081pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15082 let mut cursor = node.walk();
15083 node.named_children(&mut cursor).find(|child| {
15084 matches!(
15085 child.kind(),
15086 "type_identifier"
15087 | "primitive_type"
15088 | "qualified_identifier"
15089 | "scoped_type_identifier"
15090 | "struct_specifier"
15091 | "union_specifier"
15092 | "enum_specifier"
15093 )
15094 })
15095}
15096
15097pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15098 visibility: &VisibilityIndex<'_>,
15099 file: &ProjectFile,
15100 source: &str,
15101 declarator: Node<'_>,
15102 type_text: Option<&str>,
15103 bindings: &LocalInferenceEngine<T>,
15104) -> bool {
15105 if !has_ancestor_kind(declarator, "compound_statement") {
15106 return false;
15107 }
15108 if declarator
15109 .child_by_field_name("declarator")
15110 .is_none_or(|declarator| declarator.kind() != "identifier")
15111 {
15112 return false;
15113 }
15114 if !type_text
15115 .and_then(|text| visibility.resolve_type(file, text))
15116 .is_some_and(|unit| unit.is_class())
15117 {
15118 return false;
15119 }
15120 declarator
15121 .child_by_field_name("parameters")
15122 .is_some_and(|parameters| {
15123 constructor_parameters_look_like_expressions(parameters, source, bindings)
15124 })
15125}
15126
15127fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15128 parameters: Node<'_>,
15129 source: &str,
15130 bindings: &LocalInferenceEngine<T>,
15131) -> bool {
15132 let mut cursor = parameters.walk();
15133 parameters.named_children(&mut cursor).any(|parameter| {
15134 !matches!(
15135 parameter.kind(),
15136 "parameter_declaration" | "optional_parameter_declaration"
15137 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15138 })
15139}
15140
15141fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15142 parameter: Node<'_>,
15143 source: &str,
15144 bindings: &LocalInferenceEngine<T>,
15145) -> bool {
15146 let text = node_text(parameter, source).trim();
15147 if text
15148 .chars()
15149 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15150 && bindings.is_shadowed(text)
15151 {
15152 return true;
15153 }
15154
15155 let Some(base) = parameter
15156 .child_by_field_name("type")
15157 .filter(|base| base.kind() == "type_identifier")
15158 else {
15159 return false;
15160 };
15161 let Some(subscript) = parameter
15162 .child_by_field_name("declarator")
15163 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15164 else {
15165 return false;
15166 };
15167 subscript.child_by_field_name("size").is_some()
15168 && bindings.is_shadowed(node_text(base, source).trim())
15169}
15170
15171pub fn is_declaration_name(node: Node<'_>) -> bool {
15172 let Some(parent) = node.parent() else {
15173 return false;
15174 };
15175 if parent
15176 .child_by_field_name("name")
15177 .is_some_and(|name| same_node(name, node))
15178 {
15179 if matches!(
15180 parent.kind(),
15181 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15182 ) {
15183 return cpp_tag_specifier_declares_name(parent);
15184 }
15185 if matches!(
15186 parent.kind(),
15187 "namespace_definition"
15188 | "namespace_alias_definition"
15189 | "alias_declaration"
15190 | "enumerator"
15191 ) {
15192 return true;
15193 }
15194 }
15195
15196 let mut current = Some(parent);
15197 while let Some(ancestor) = current {
15198 let type_definition = ancestor.kind() == "type_definition";
15199 let mut child_cursor = ancestor.walk();
15200 if ancestor.named_children(&mut child_cursor).any(|child| {
15201 declaration_declarator(ancestor, child).is_some_and(|declarator| {
15202 declarator_name_path_contains(declarator, node, type_definition)
15203 })
15204 }) {
15205 return true;
15206 }
15207 if matches!(
15208 ancestor.kind(),
15209 "declaration"
15210 | "field_declaration"
15211 | "parameter_declaration"
15212 | "optional_parameter_declaration"
15213 | "function_definition"
15214 | "type_definition"
15215 | "alias_declaration"
15216 | "template_instantiation"
15217 | "class_specifier"
15218 | "struct_specifier"
15219 | "union_specifier"
15220 | "enum_specifier"
15221 ) {
15222 return false;
15223 }
15224 current = ancestor.parent();
15225 }
15226 false
15227}
15228
15229pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15238 if !matches!(
15239 node.kind(),
15240 "qualified_identifier" | "scoped_type_identifier"
15241 ) {
15242 return false;
15243 }
15244 let Some(declaration) = node
15245 .parent()
15246 .filter(|parent| parent.kind() == "declaration")
15247 else {
15248 return false;
15249 };
15250 if declaration.child_by_field_name("declarator") != Some(node)
15251 || !declaration
15252 .child_by_field_name("type")
15253 .is_some_and(|friend| {
15254 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15255 })
15256 {
15257 return false;
15258 }
15259 let mut cursor = declaration.walk();
15260 let mut errors = declaration
15261 .named_children(&mut cursor)
15262 .filter(|child| child.kind() == "ERROR");
15263 let Some(error) = errors.next() else {
15264 return false;
15265 };
15266 errors.next().is_none()
15267 && error.named_child_count() == 1
15268 && error.named_child(0).is_some_and(|class| {
15269 class.kind() == "identifier" && node_text(class, source) == "class"
15270 })
15271}
15272
15273pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15274 if !matches!(node.kind(), "identifier" | "field_identifier") {
15275 return false;
15276 }
15277 if let Some(parent) = node.parent() {
15278 if parent.kind() == "call_expression"
15279 && parent.child_by_field_name("function") == Some(node)
15280 {
15281 return false;
15282 }
15283 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15284 && parent.child_by_field_name("label") == Some(node)
15285 {
15286 return false;
15287 }
15288 }
15289 if is_declaration_name(node) {
15290 return false;
15291 }
15292 let mut current = node.parent();
15293 while let Some(ancestor) = current {
15294 match ancestor.kind() {
15295 "preproc_ifdef" | "preproc_ifndef" => {
15296 if ancestor
15297 .child_by_field_name("name")
15298 .is_some_and(|name| node_range_contains(name, node))
15299 {
15300 return false;
15301 }
15302 }
15303 "preproc_if" | "preproc_elif" => {
15304 if ancestor
15305 .child_by_field_name("condition")
15306 .is_some_and(|condition| node_range_contains(condition, node))
15307 {
15308 return false;
15309 }
15310 }
15311 "preproc_else" => {}
15312 kind if kind.starts_with("preproc_") => return false,
15313 _ => {}
15314 }
15315 if matches!(
15316 ancestor.kind(),
15317 "translation_unit" | "function_definition" | "compound_statement"
15318 ) {
15319 break;
15320 }
15321 current = ancestor.parent();
15322 }
15323 true
15324}
15325
15326fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
15327 outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
15328}
15329
15330fn recovered_c_reference_node(
15331 visibility: &VisibilityIndex<'_>,
15332 file: &ProjectFile,
15333 node: Node<'_>,
15334 source: &str,
15335) -> bool {
15336 if node.start_byte() >= node.end_byte()
15337 || node.is_error()
15338 || node.is_missing()
15339 || !matches!(
15340 node.kind(),
15341 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15342 )
15343 || recovered_c_macro_binding_role(node)
15344 || recovered_c_label_role(node)
15345 {
15346 return false;
15347 }
15348 let name = node_text(node, source);
15356 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15357 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15358 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15359 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15360 if is_declaration_name(node)
15361 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15362 && !recovered_function_call
15363 && !recovered_macro_call
15364 && !recovered_parenthesized_reference
15365 {
15366 return false;
15367 }
15368
15369 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15370 return true;
15371 }
15372 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15373 return true;
15374 }
15375 if recovered_parenthesized_reference {
15376 return true;
15377 }
15378 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15379 if recovered_function_call {
15380 return true;
15381 }
15382 return visibility
15383 .visible_identifier_candidates(file, name)
15384 .any(|candidate| {
15385 candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
15386 });
15387 }
15388 let visible = visibility
15389 .visible_identifier_candidates(file, name)
15390 .next()
15391 .is_some();
15392 visible
15393 && (recovered_c_reference_anchor(node)
15394 || recovered_c_error_expression_leaf(node)
15395 || recovered_function_call)
15396}
15397
15398fn push_recovered_c_range(
15399 ranges: &mut Vec<Range>,
15400 seen: &mut HashSet<(usize, usize)>,
15401 start_byte: usize,
15402 end_byte: usize,
15403 node: Node<'_>,
15404 limit: usize,
15405) -> bool {
15406 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15407 return true;
15408 }
15409 if ranges.len() >= limit {
15410 return false;
15411 }
15412 ranges.push(Range {
15413 start_byte,
15414 end_byte,
15415 start_line: node.start_position().row,
15416 end_line: node.end_position().row,
15417 });
15418 true
15419}
15420
15421fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15426 let mut current = node.parent();
15427 while let Some(parent) = current {
15428 if parent.is_error() {
15429 let Some(anchor) = parent.parent() else {
15430 return false;
15431 };
15432 return anchor.kind().ends_with("_expression")
15433 || matches!(
15434 anchor.kind(),
15435 "argument_list"
15436 | "return_statement"
15437 | "expression_statement"
15438 | "case_statement"
15439 | "initializer_list"
15440 | "field_designator"
15441 | "enumerator"
15442 );
15443 }
15444 if matches!(
15445 parent.kind(),
15446 "translation_unit" | "function_definition" | "compound_statement"
15447 ) {
15448 return false;
15449 }
15450 current = parent.parent();
15451 }
15452 false
15453}
15454
15455fn recovered_c_function_call(
15462 visibility: &VisibilityIndex<'_>,
15463 file: &ProjectFile,
15464 node: Node<'_>,
15465 name: &str,
15466) -> bool {
15467 if !matches!(
15468 node.kind(),
15469 "identifier" | "field_identifier" | "type_identifier"
15470 ) {
15471 return false;
15472 }
15473 let error_call_prefix = node.parent().is_some_and(|error| {
15478 error.is_error()
15479 && error
15480 .parent()
15481 .is_some_and(|parent| parent.kind() == "compound_statement")
15482 }) && node
15483 .prev_sibling()
15484 .is_none_or(|previous| previous.kind() == ";")
15485 && node.next_sibling().is_some_and(|open| {
15486 open.kind() == "("
15487 && open.next_named_sibling().is_some_and(|argument| {
15488 argument.kind() == "parameter_declaration" && !argument.has_error()
15489 })
15490 });
15491 (error_call_prefix || recovered_c_function_declarator_invocation(node))
15492 && visibility
15493 .visible_identifier_candidates(file, name)
15494 .any(CodeUnit::is_function)
15495}
15496
15497fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15507 let mut function_declarator = if node.parent().is_some_and(|parent| {
15508 parent.kind() == "function_declarator"
15509 && parent.child_by_field_name("declarator") == Some(node)
15510 }) {
15511 node.parent().expect("checked function declarator parent")
15512 } else {
15513 let Some(parameter) = node.parent().filter(|parent| {
15514 parent.kind() == "parameter_declaration"
15515 && parent.child_by_field_name("type") == Some(node)
15516 }) else {
15517 return false;
15518 };
15519 if !parameter
15520 .child_by_field_name("declarator")
15521 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15522 {
15523 return false;
15524 }
15525 let Some(parameters) = parameter
15526 .parent()
15527 .filter(|parent| parent.kind() == "parameter_list")
15528 else {
15529 return false;
15530 };
15531 let Some(function_declarator) = parameters
15532 .parent()
15533 .filter(|parent| parent.kind() == "function_declarator")
15534 else {
15535 return false;
15536 };
15537 function_declarator
15538 };
15539
15540 while let Some(parent) = function_declarator.parent().filter(|parent| {
15543 parent.kind() == "function_declarator"
15544 && parent.child_by_field_name("declarator") == Some(function_declarator)
15545 }) {
15546 function_declarator = parent;
15547 }
15548 let Some(mut current) = function_declarator
15549 .parent()
15550 .filter(|parent| parent.is_error())
15551 else {
15552 return false;
15553 };
15554 loop {
15555 let Some(parent) = current.parent() else {
15556 return false;
15557 };
15558 if matches!(
15559 parent.kind(),
15560 "translation_unit"
15561 | "compound_statement"
15562 | "preproc_if"
15563 | "preproc_ifdef"
15564 | "preproc_ifndef"
15565 | "preproc_else"
15566 | "preproc_elif"
15567 ) {
15568 return true;
15569 }
15570 if parent.kind() == "function_definition"
15571 && parent.child_by_field_name("declarator") == Some(current)
15572 && parent.named_child(0) == Some(current)
15573 && parent.child_by_field_name("body").is_some()
15574 {
15575 return true;
15576 }
15577 if parent.is_error()
15578 || matches!(
15579 parent.kind(),
15580 "parameter_declaration"
15581 | "parameter_list"
15582 | "function_declarator"
15583 | "abstract_function_declarator"
15584 | "parenthesized_declarator"
15585 )
15586 {
15587 current = parent;
15588 continue;
15589 }
15590 return false;
15591 }
15592}
15593
15594fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15600 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15601 return false;
15602 };
15603 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15604 return false;
15605 }
15606 let Some(declarator) = error
15607 .parent()
15608 .filter(|parent| parent.kind() == "parenthesized_declarator")
15609 else {
15610 return false;
15611 };
15612 let Some(declaration) = declarator
15613 .parent()
15614 .filter(|parent| parent.kind() == "declaration")
15615 else {
15616 return false;
15617 };
15618 if declaration.child_by_field_name("declarator") != Some(declarator) {
15619 return false;
15620 }
15621 let Some(type_node) = declaration.child_by_field_name("type") else {
15622 return false;
15623 };
15624 type_node.kind() == "dependent_type"
15625 && type_node
15626 .child(0)
15627 .is_some_and(|keyword| keyword.kind() == "typename")
15628}
15629
15630fn recovered_c_explicit_assignment_callee(
15631 visibility: &VisibilityIndex<'_>,
15632 file: &ProjectFile,
15633 node: Node<'_>,
15634 name: &str,
15635) -> bool {
15636 let mut current = node;
15637 let error = loop {
15638 let Some(parent) = current.parent() else {
15639 return false;
15640 };
15641 if parent.is_error() {
15642 break parent;
15643 }
15644 current = parent;
15645 };
15646 let mut cursor = error.walk();
15647 let explicit_recovery_precedes_callee = error
15648 .named_children(&mut cursor)
15649 .take_while(|child| child.start_byte() < node.start_byte())
15650 .any(|child| child.kind() == "explicit_function_specifier");
15651 if !explicit_recovery_precedes_callee {
15652 return false;
15653 }
15654 visibility
15655 .visible_identifier_candidates(file, name)
15656 .any(CodeUnit::is_function)
15657}
15658
15659fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15660 while let Some(parent) = node.parent() {
15661 if matches!(
15662 parent.kind(),
15663 "preproc_def" | "preproc_function_def" | "preproc_params"
15664 ) {
15665 return true;
15666 }
15667 if parent.is_error()
15668 || matches!(
15669 parent.kind(),
15670 "translation_unit" | "function_definition" | "compound_statement"
15671 )
15672 {
15673 return false;
15674 }
15675 node = parent;
15676 }
15677 false
15678}
15679
15680fn recovered_c_label_role(node: Node<'_>) -> bool {
15681 node.parent().is_some_and(|parent| {
15682 matches!(parent.kind(), "labeled_statement" | "goto_statement")
15683 && parent.child_by_field_name("label") == Some(node)
15684 })
15685}
15686
15687fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15688 while let Some(parent) = node.parent() {
15689 if parent.is_error() {
15690 return false;
15691 }
15692 if parent.kind() == "optional_parameter_declaration"
15697 && parent
15698 .child_by_field_name("default_value")
15699 .is_some_and(|value| node_range_contains(value, node))
15700 {
15701 return true;
15702 }
15703 if parent.kind().ends_with("_expression")
15704 || matches!(
15705 parent.kind(),
15706 "argument_list"
15707 | "return_statement"
15708 | "expression_statement"
15709 | "case_statement"
15710 | "initializer_list"
15711 | "init_declarator"
15712 | "array_declarator"
15713 | "field_designator"
15714 | "enumerator"
15715 )
15716 {
15717 return true;
15718 }
15719 if matches!(
15720 parent.kind(),
15721 "translation_unit"
15722 | "function_definition"
15723 | "compound_statement"
15724 | "declaration"
15725 | "field_declaration"
15726 | "parameter_declaration"
15727 ) {
15728 return false;
15729 }
15730 node = parent;
15731 }
15732 false
15733}
15734
15735pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15743 let mut current = parameter.parent();
15744 while let Some(ancestor) = current {
15745 if ancestor.kind() == "lambda_expression" {
15746 return ancestor
15747 .child_by_field_name("declarator")
15748 .is_some_and(|declarator| {
15749 declarator.start_byte() <= parameter.start_byte()
15750 && parameter.end_byte() <= declarator.end_byte()
15751 });
15752 }
15753 if ancestor.kind() == "function_definition" {
15754 return ancestor
15755 .child_by_field_name("declarator")
15756 .is_some_and(|declarator| {
15757 declarator.start_byte() <= parameter.start_byte()
15758 && parameter.end_byte() <= declarator.end_byte()
15759 });
15760 }
15761 current = ancestor.parent();
15762 }
15763 false
15764}
15765
15766pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15767 let mut current = node.parent();
15768 while let Some(ancestor) = current {
15769 if matches!(
15770 ancestor.kind(),
15771 "parameter_declaration" | "optional_parameter_declaration"
15772 ) {
15773 return ancestor
15774 .child_by_field_name("type")
15775 .is_some_and(|type_node| {
15776 type_node.start_byte() <= node.start_byte()
15777 && node.end_byte() <= type_node.end_byte()
15778 });
15779 }
15780 if matches!(
15781 ancestor.kind(),
15782 "function_definition" | "lambda_expression" | "compound_statement"
15783 ) {
15784 return false;
15785 }
15786 current = ancestor.parent();
15787 }
15788 false
15789}
15790
15791fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15792 if specifier.child_by_field_name("body").is_some() {
15793 return true;
15794 }
15795 let mut current = specifier.parent();
15796 while let Some(ancestor) = current {
15797 match ancestor.kind() {
15798 "type_descriptor"
15799 | "parameter_declaration"
15800 | "optional_parameter_declaration"
15801 | "template_argument_list"
15802 | "cast_expression" => return false,
15803 "declaration" | "field_declaration" => {
15804 let mut cursor = ancestor.walk();
15805 return ancestor
15806 .children_by_field_name("declarator", &mut cursor)
15807 .next()
15808 .is_none();
15809 }
15810 "translation_unit" => return true,
15811 _ => current = ancestor.parent(),
15812 }
15813 }
15814 false
15815}
15816
15817pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15818 match node.kind() {
15819 "identifier"
15820 | "field_identifier"
15821 | "qualified_identifier"
15822 | "scoped_identifier"
15823 | "operator_name"
15824 | "destructor_name"
15825 | "literal_operator_name" => Some(node),
15826 "reference_declarator" | "parenthesized_declarator" => {
15827 node.named_child(0).and_then(declarator_name_node)
15828 }
15829 _ => node
15830 .child_by_field_name("declarator")
15831 .or_else(|| node.child_by_field_name("name"))
15832 .or_else(|| node.child_by_field_name("field"))
15833 .and_then(declarator_name_node),
15834 }
15835}
15836
15837fn declarator_name_path_contains(
15838 declarator: Node<'_>,
15839 candidate: Node<'_>,
15840 allow_type_identifier: bool,
15841) -> bool {
15842 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15843 return false;
15844 };
15845 let mut current = Some(declarator);
15846 while let Some(node) = current {
15847 if same_node(node, candidate) {
15848 return true;
15849 }
15850 if same_node(node, name) {
15851 return false;
15852 }
15853 current = 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 }
15858 false
15859}
15860
15861fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15862 match node.kind() {
15863 "identifier"
15864 | "field_identifier"
15865 | "operator_name"
15866 | "destructor_name"
15867 | "literal_operator_name" => Some(node),
15868 "type_identifier" if allow_type_identifier => Some(node),
15869 _ => node
15870 .child_by_field_name("declarator")
15871 .or_else(|| node.child_by_field_name("name"))
15872 .or_else(|| node.child_by_field_name("field"))
15873 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15874 }
15875}
15876
15877pub fn is_nested_type_node(node: Node<'_>) -> bool {
15880 node.parent().is_some_and(|parent| {
15881 matches!(
15882 parent.kind(),
15883 "qualified_identifier" | "scoped_type_identifier" | "template_type"
15884 )
15885 })
15886}
15887
15888pub struct OutOfLineMemberDefinitionOwners<'tree> {
15889 pub owners: Vec<(Node<'tree>, CodeUnit)>,
15890 innermost: Option<(Node<'tree>, CodeUnit)>,
15891}
15892
15893impl OutOfLineMemberDefinitionOwners<'_> {
15894 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15895 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15896 }
15897}
15898
15899pub struct QualifiedOwnerComponents<'tree> {
15900 pub nodes: Vec<Node<'tree>>,
15901 pub names: Vec<String>,
15902 pub global: bool,
15903}
15904
15905pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15910 let mut stack = vec![node];
15911 let mut found_separator = false;
15912 while let Some(current) = stack.pop() {
15913 if !matches!(
15914 current.kind(),
15915 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15916 ) {
15917 continue;
15918 }
15919 let mut current_has_separator = false;
15920 for child in children_iter(current) {
15921 if child.kind() == "::" {
15922 if child.is_missing() {
15923 return false;
15924 }
15925 current_has_separator = true;
15926 found_separator = true;
15927 }
15928 }
15929 if !current_has_separator {
15930 return false;
15931 }
15932 for field in ["scope", "name"] {
15933 if let Some(child) = current.child_by_field_name(field)
15934 && matches!(
15935 child.kind(),
15936 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15937 )
15938 {
15939 stack.push(child);
15940 }
15941 }
15942 }
15943 found_separator
15944}
15945
15946pub fn qualified_owner_components<'tree>(
15947 node: Node<'tree>,
15948 source: &str,
15949) -> Option<QualifiedOwnerComponents<'tree>> {
15950 if !qualified_name_has_concrete_scope_separators(node) {
15951 return None;
15952 }
15953 let mut nodes = cpp_name_component_nodes(node)?;
15954 nodes.pop()?;
15955 if nodes.is_empty() {
15956 return None;
15957 }
15958 let names = nodes
15959 .iter()
15960 .map(|component| node_text(*component, source).to_string())
15961 .collect();
15962 Some(QualifiedOwnerComponents {
15963 nodes,
15964 names,
15965 global: is_globally_qualified_cpp_name(node),
15966 })
15967}
15968
15969pub fn out_of_line_member_definition_owner<'tree>(
15970 analyzer: &CppGraphSource<'_>,
15971 visibility: &VisibilityIndex<'_>,
15972 file: &ProjectFile,
15973 source: &str,
15974 node: Node<'tree>,
15975) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15976 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15977 || !has_ancestor_kind(node, "function_definition")
15978 || !is_function_declarator_name_root(node)
15979 {
15980 return None;
15981 }
15982 let qualified = qualified_owner_components(node, source)?;
15983 let lexical_scope = enclosing_namespace_components(node, source)?;
15984 let mut owners = Vec::new();
15985 let mut innermost = None;
15986
15987 for component_count in 1..=qualified.names.len() {
15988 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15989 .resolve_type_components_lexically(
15990 analyzer,
15991 file,
15992 &qualified.names[..component_count],
15993 qualified.global,
15994 &lexical_scope,
15995 )
15996 && !owners
15997 .iter()
15998 .any(|(_, existing)| same_visible_symbol(existing, &unit))
15999 {
16000 if component_count == qualified.names.len() {
16001 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
16002 }
16003 owners.push((qualified.nodes[component_count - 1], unit));
16004 }
16005 }
16006
16007 if innermost.is_none() {
16017 let indexed_owner_components = visibility
16018 .indexed_enclosing_owner_scope(analyzer, file, node)
16019 .or_else(|| {
16020 if qualified.names.len() <= 1 {
16025 return None;
16026 }
16027 let range = Range {
16028 start_byte: node.start_byte(),
16029 end_byte: node.end_byte(),
16030 start_line: node.start_position().row,
16031 end_line: node.end_position().row,
16032 };
16033 let start = analyzer.enclosing_code_unit(file, &range)?;
16034 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16035 brokk_bifrost_core::analyzer::Language::Cpp,
16036 &cpp_name_for(&start),
16037 );
16038 components.pop();
16039 Some(components)
16040 });
16041 if let Some(indexed_owner_components) = indexed_owner_components
16042 && indexed_owner_components.len() > qualified.names.len()
16043 && indexed_owner_components.ends_with(&qualified.names)
16044 && indexed_namespace_path_is_recoverable(
16045 &lexical_scope,
16046 &indexed_owner_components,
16047 qualified.names.len(),
16048 )
16049 && (qualified.names.len() > 1 || !qualified.global)
16054 {
16055 let namespace_count = indexed_owner_components.len() - qualified.names.len();
16056 for component_count in 1..=qualified.names.len() {
16057 let expected = &indexed_owner_components[..namespace_count + component_count];
16058 let owner_node = qualified.nodes[component_count - 1];
16059 for owner in visibility
16060 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16061 .filter(|candidate| candidate.is_class())
16062 .filter(|candidate| {
16063 canonical_cpp_scope_components(candidate) == expected
16064 && visibility.external_type_candidate_visible_in_context(
16065 analyzer, file, candidate, node,
16066 )
16067 })
16068 {
16069 if component_count == qualified.names.len() && innermost.is_none() {
16070 innermost = Some((owner_node, owner.clone()));
16071 }
16072 if !owners
16073 .iter()
16074 .any(|(_, existing)| same_symbol(existing, owner))
16075 {
16076 owners.push((owner_node, owner.clone()));
16077 }
16078 }
16079 }
16080 }
16081 }
16082 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16083}
16084
16085fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16086 let mut current = node;
16087 while let Some(parent) = current.parent() {
16088 if parent.kind() == "function_declarator" {
16089 return parent.child_by_field_name("declarator") == Some(current);
16090 }
16091 if matches!(
16092 parent.kind(),
16093 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16094 ) && parent.child_by_field_name("declarator") == Some(current)
16095 {
16096 current = parent;
16097 continue;
16098 }
16099 return false;
16100 }
16101 false
16102}
16103
16104pub fn append_cpp_name_components(
16105 node: Node<'_>,
16106 source: &str,
16107 out: &mut Vec<String>,
16108) -> Option<()> {
16109 out.extend(
16110 cpp_name_component_nodes(node)?
16111 .into_iter()
16112 .map(|component| node_text(component, source).to_string()),
16113 );
16114 Some(())
16115}
16116
16117pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16118 let mut components = Vec::new();
16119 append_cpp_name_components(node, source, &mut components)?;
16120 Some(components)
16121}
16122
16123pub fn unique_macro_replacement_type_candidate(
16132 analyzer: &CppGraphSource<'_>,
16133 visibility: &VisibilityIndex<'_>,
16134 file: &ProjectFile,
16135 components: &[String],
16136) -> Option<CodeUnit> {
16137 let terminal = components.last()?;
16138 let mut candidates = Vec::new();
16139 for candidate in visibility
16140 .visible_identifier_candidates(file, terminal)
16141 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16142 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16143 {
16144 if !candidates
16145 .iter()
16146 .any(|existing| same_logical_symbol(existing, candidate))
16147 {
16148 candidates.push(candidate.clone());
16149 }
16150 }
16151 (candidates.len() == 1).then(|| candidates.remove(0))
16152}
16153
16154pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16162 let mut parser = Parser::new();
16163 if parser
16164 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16165 .is_err()
16166 {
16167 return Vec::new();
16168 }
16169 let Some(tree) = parser.parse(source, None) else {
16170 return Vec::new();
16171 };
16172 let mut scopes = Vec::new();
16173 let mut pending = vec![tree.root_node()];
16174 while let Some(node) = pending.pop() {
16175 if node.kind() == "using_declaration" {
16176 let Some(imported) = node.named_child(0) else {
16177 continue;
16178 };
16179 let Some(mut components) = cpp_type_name_components(imported, source) else {
16180 continue;
16181 };
16182 if components.pop().as_deref() == Some(member) && !components.is_empty() {
16183 scopes.push(components.join("::"));
16184 }
16185 continue;
16186 }
16187 push_named_children_reversed(node, &mut pending);
16188 }
16189 scopes
16190}
16191
16192pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16196 qualified == scope
16197 || qualified
16198 .strip_suffix(scope)
16199 .is_some_and(|prefix| prefix.ends_with("::"))
16200}
16201
16202pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16207 let Some(type_descriptor) = node.parent() else {
16208 return false;
16209 };
16210 if type_descriptor.kind() != "type_descriptor"
16211 || type_descriptor.child_by_field_name("type") != Some(node)
16212 {
16213 return false;
16214 }
16215 let Some(arguments) = type_descriptor.parent() else {
16216 return false;
16217 };
16218 if arguments.kind() != "template_argument_list" {
16219 return false;
16220 }
16221 arguments.parent().is_some_and(|parent| {
16222 matches!(parent.kind(), "template_type" | "template_function")
16223 && parent.child_by_field_name("arguments") == Some(arguments)
16224 })
16225}
16226
16227pub fn cpp_template_reference_arguments(
16228 mut node: Node<'_>,
16229 source: &str,
16230) -> Option<Vec<CppTemplateExpression>> {
16231 loop {
16232 match node.kind() {
16233 "template_type" | "template_function" => {
16234 let arguments = node.child_by_field_name("arguments")?;
16235 let mut cursor = arguments.walk();
16236 return Some(
16237 arguments
16238 .named_children(&mut cursor)
16239 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
16240 .map(|argument| CppTemplateExpression {
16241 text: normalize_cpp_whitespace(node_text(argument, source)),
16242 term: cpp_template_term(
16244 argument,
16245 source,
16246 &[],
16247 &ParentIndex::unindexed(),
16248 ),
16249 })
16250 .collect(),
16251 );
16252 }
16253 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
16254 node = node
16255 .child_by_field_name("name")
16256 .or_else(|| node.child_by_field_name("type"))?;
16257 }
16258 _ => return None,
16259 }
16260 }
16261}
16262
16263fn cpp_reconcile_primary_template_parameters(
16264 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
16265 preferred: &CodeUnit,
16266) -> Option<Vec<CppTemplateParameterMetadata>> {
16267 let canonical = candidates
16268 .iter()
16269 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
16270 let mut merged = canonical
16271 .parameters
16272 .iter()
16273 .map(|parameter| CppTemplateParameterMetadata {
16274 name: parameter.name.clone(),
16275 kind: parameter.kind,
16276 variadic: parameter.variadic,
16277 default: None,
16278 })
16279 .collect::<Vec<_>>();
16280
16281 for (_, metadata) in candidates {
16282 if metadata.parameters.len() != merged.len() {
16283 return None;
16284 }
16285 let rename_bindings = metadata
16286 .parameters
16287 .iter()
16288 .zip(&merged)
16289 .map(|(parameter, canonical)| {
16290 (
16291 parameter.name.clone(),
16292 CppTemplateTerm::Parameter(canonical.name.clone()),
16293 )
16294 })
16295 .collect::<HashMap<_, _>>();
16296 for ((parameter, canonical), merged_parameter) in metadata
16297 .parameters
16298 .iter()
16299 .zip(&canonical.parameters)
16300 .zip(&mut merged)
16301 {
16302 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
16303 return None;
16304 }
16305 let Some(default) = ¶meter.default else {
16306 continue;
16307 };
16308 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
16309 if let Some(existing) = &merged_parameter.default {
16310 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
16311 return None;
16312 }
16313 } else {
16314 merged_parameter.default = Some(CppTemplateExpression {
16315 text: default.text.clone(),
16316 term: normalized_term,
16317 });
16318 }
16319 }
16320 }
16321 Some(merged)
16322}
16323
16324pub fn cpp_bind_template_arguments(
16325 parameters: &[CppTemplateParameterMetadata],
16326 explicit_arguments: &[CppTemplateExpression],
16327) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16328 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16329 if variadic_index.is_some_and(|index| {
16330 index + 1 != parameters.len()
16331 || parameters[index + 1..]
16332 .iter()
16333 .any(|parameter| parameter.variadic)
16334 }) {
16335 return None;
16336 }
16337 let fixed_count = variadic_index.unwrap_or(parameters.len());
16338 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16339 return None;
16340 }
16341 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16342 let mut expanded = explicit_arguments[..explicit_fixed_count]
16343 .iter()
16344 .map(cpp_clone_template_expression_iterative)
16345 .collect::<Vec<_>>();
16346 let mut bindings = HashMap::default();
16347 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16348 bindings.insert(
16349 parameter.name.clone(),
16350 cpp_clone_template_term_iterative(&argument.term),
16351 );
16352 }
16353 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
16354 let default = parameter.default.as_ref()?;
16355 let term = cpp_substitute_template_term(&default.term, &bindings)?;
16356 bindings.insert(parameter.name.clone(), term.clone());
16357 expanded.push(CppTemplateExpression {
16358 text: default.text.clone(),
16359 term,
16360 });
16361 }
16362 if let Some(index) = variadic_index {
16363 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16364 expanded.extend(
16365 packed_arguments
16366 .iter()
16367 .map(cpp_clone_template_expression_iterative),
16368 );
16369 bindings.insert(
16370 parameters[index].name.clone(),
16371 CppTemplateTerm::Node {
16372 kind: "parameter_pack".to_string(),
16373 children: packed_arguments
16374 .iter()
16375 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16376 .collect(),
16377 },
16378 );
16379 }
16380 Some((expanded, bindings))
16381}
16382
16383fn cpp_specialization_matches(
16384 metadata: &CppTemplateMetadata,
16385 arguments: &[CppTemplateExpression],
16386) -> bool {
16387 if metadata.specialization_arguments.len() != arguments.len() {
16388 return false;
16389 }
16390 let parameter_names = metadata
16391 .parameters
16392 .iter()
16393 .map(|parameter| parameter.name.as_str())
16394 .collect::<HashSet<_>>();
16395 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16396 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
16397 if !cpp_unify_template_term(
16398 &pattern.term,
16399 &argument.term,
16400 ¶meter_names,
16401 &mut bindings,
16402 ) {
16403 return false;
16404 }
16405 }
16406 true
16407}
16408
16409fn cpp_specialization_more_specialized(
16410 candidate: &CppTemplateMetadata,
16411 other: &CppTemplateMetadata,
16412) -> bool {
16413 cpp_specialization_pattern_accepts(other, candidate)
16414 && !cpp_specialization_pattern_accepts(candidate, other)
16415}
16416
16417fn cpp_specialization_pattern_accepts(
16418 broader: &CppTemplateMetadata,
16419 narrower: &CppTemplateMetadata,
16420) -> bool {
16421 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
16422 return false;
16423 }
16424 let parameter_names = broader
16425 .parameters
16426 .iter()
16427 .map(|parameter| parameter.name.as_str())
16428 .collect::<HashSet<_>>();
16429 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16430 broader
16431 .specialization_arguments
16432 .iter()
16433 .zip(&narrower.specialization_arguments)
16434 .all(|(pattern, argument)| {
16435 cpp_unify_template_term(
16436 &pattern.term,
16437 &argument.term,
16438 ¶meter_names,
16439 &mut bindings,
16440 )
16441 })
16442}
16443
16444pub fn cpp_substitute_template_term(
16445 term: &CppTemplateTerm,
16446 bindings: &HashMap<String, CppTemplateTerm>,
16447) -> Option<CppTemplateTerm> {
16448 enum Work<'a> {
16449 Visit(&'a CppTemplateTerm),
16450 Build { kind: String, child_count: usize },
16451 }
16452
16453 let mut work = vec![Work::Visit(term)];
16454 let mut substituted = Vec::new();
16455 while let Some(next) = work.pop() {
16456 match next {
16457 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16458 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16459 }
16460 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16461 substituted.push(CppTemplateTerm::Atom {
16462 kind: kind.clone(),
16463 text: text.clone(),
16464 });
16465 }
16466 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16467 work.push(Work::Build {
16468 kind: kind.clone(),
16469 child_count: children.len(),
16470 });
16471 work.extend(children.iter().rev().map(Work::Visit));
16472 }
16473 Work::Build { kind, child_count } => {
16474 let children = substituted.split_off(substituted.len() - child_count);
16475 substituted.push(CppTemplateTerm::Node { kind, children });
16476 }
16477 }
16478 }
16479 substituted.pop()
16480}
16481
16482pub fn cpp_substitute_template_arguments(
16483 arguments: &[CppTemplateExpression],
16484 bindings: &HashMap<String, CppTemplateTerm>,
16485) -> Option<Vec<CppTemplateExpression>> {
16486 let mut substituted = Vec::new();
16487 for argument in arguments {
16488 let CppTemplateTerm::Node { kind, children } = &argument.term else {
16489 substituted.push(CppTemplateExpression {
16490 text: argument.text.clone(),
16491 term: cpp_substitute_template_term(&argument.term, bindings)?,
16492 });
16493 continue;
16494 };
16495 if kind != "parameter_pack_expansion" {
16496 substituted.push(CppTemplateExpression {
16497 text: argument.text.clone(),
16498 term: cpp_substitute_template_term(&argument.term, bindings)?,
16499 });
16500 continue;
16501 }
16502 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16503 return None;
16504 };
16505 if ellipsis != "..." {
16506 return None;
16507 }
16508
16509 let mut pack_names = Vec::new();
16510 let mut work = vec![pattern];
16511 while let Some(term) = work.pop() {
16512 match term {
16513 CppTemplateTerm::Parameter(name)
16514 if matches!(
16515 bindings.get(name),
16516 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16517 ) =>
16518 {
16519 if !pack_names.contains(name) {
16520 pack_names.push(name.clone());
16521 }
16522 }
16523 CppTemplateTerm::Node { children, .. } => work.extend(children),
16524 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16525 }
16526 }
16527 let first_pack = pack_names.first()?;
16528 let CppTemplateTerm::Node {
16529 children: first_elements,
16530 ..
16531 } = bindings.get(first_pack)?
16532 else {
16533 return None;
16534 };
16535 let pack_len = first_elements.len();
16536 for pack_name in &pack_names {
16537 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16538 return None;
16539 };
16540 if children.len() != pack_len {
16541 return None;
16542 }
16543 }
16544 for index in 0..pack_len {
16545 let mut element_bindings = bindings.clone();
16546 for pack_name in &pack_names {
16547 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16548 return None;
16549 };
16550 element_bindings.insert(
16551 pack_name.clone(),
16552 cpp_clone_template_term_iterative(&children[index]),
16553 );
16554 }
16555 substituted.push(CppTemplateExpression {
16556 text: argument.text.clone(),
16557 term: cpp_substitute_template_term(pattern, &element_bindings)?,
16558 });
16559 }
16560 }
16561 Some(substituted)
16562}
16563
16564fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16565 enum Work<'a> {
16566 Visit(&'a CppTemplateTerm),
16567 Build { kind: String, child_count: usize },
16568 }
16569
16570 let mut work = vec![Work::Visit(term)];
16571 let mut cloned = Vec::new();
16572 while let Some(next) = work.pop() {
16573 match next {
16574 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16575 cloned.push(CppTemplateTerm::Parameter(name.clone()));
16576 }
16577 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16578 cloned.push(CppTemplateTerm::Atom {
16579 kind: kind.clone(),
16580 text: text.clone(),
16581 });
16582 }
16583 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16584 work.push(Work::Build {
16585 kind: kind.clone(),
16586 child_count: children.len(),
16587 });
16588 work.extend(children.iter().rev().map(Work::Visit));
16589 }
16590 Work::Build { kind, child_count } => {
16591 let children = cloned.split_off(cloned.len() - child_count);
16592 cloned.push(CppTemplateTerm::Node { kind, children });
16593 }
16594 }
16595 }
16596 cloned
16597 .pop()
16598 .expect("template term traversal emits one root")
16599}
16600
16601fn cpp_clone_template_expression_iterative(
16602 expression: &CppTemplateExpression,
16603) -> CppTemplateExpression {
16604 CppTemplateExpression {
16605 text: expression.text.clone(),
16606 term: cpp_clone_template_term_iterative(&expression.term),
16607 }
16608}
16609
16610pub fn cpp_unify_template_term(
16611 pattern: &CppTemplateTerm,
16612 argument: &CppTemplateTerm,
16613 parameters: &HashSet<&str>,
16614 bindings: &mut HashMap<String, CppTemplateTerm>,
16615) -> bool {
16616 let mut work = vec![(pattern, argument)];
16617 while let Some((pattern, argument)) = work.pop() {
16618 match pattern {
16619 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16620 if let Some(bound) = bindings.get(name) {
16621 if !cpp_template_terms_equal(bound, argument) {
16622 return false;
16623 }
16624 } else {
16625 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16626 }
16627 }
16628 CppTemplateTerm::Atom {
16629 kind: pattern_kind,
16630 text: pattern_text,
16631 } => {
16632 if !matches!(
16633 argument,
16634 CppTemplateTerm::Atom { kind, text }
16635 if kind == pattern_kind && text == pattern_text
16636 ) {
16637 return false;
16638 }
16639 }
16640 CppTemplateTerm::Node {
16641 kind: pattern_kind,
16642 children: pattern_children,
16643 } => {
16644 let CppTemplateTerm::Node { kind, children } = argument else {
16645 return false;
16646 };
16647 if kind != pattern_kind || children.len() != pattern_children.len() {
16648 return false;
16649 }
16650 work.extend(pattern_children.iter().zip(children).rev());
16651 }
16652 CppTemplateTerm::Parameter(_) => return false,
16653 }
16654 }
16655 true
16656}
16657
16658fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16659 let mut work = vec![(left, right)];
16660 while let Some((left, right)) = work.pop() {
16661 match (left, right) {
16662 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16663 if left != right {
16664 return false;
16665 }
16666 }
16667 (
16668 CppTemplateTerm::Atom {
16669 kind: left_kind,
16670 text: left_text,
16671 },
16672 CppTemplateTerm::Atom {
16673 kind: right_kind,
16674 text: right_text,
16675 },
16676 ) => {
16677 if left_kind != right_kind || left_text != right_text {
16678 return false;
16679 }
16680 }
16681 (
16682 CppTemplateTerm::Node {
16683 kind: left_kind,
16684 children: left_children,
16685 },
16686 CppTemplateTerm::Node {
16687 kind: right_kind,
16688 children: right_children,
16689 },
16690 ) => {
16691 if left_kind != right_kind || left_children.len() != right_children.len() {
16692 return false;
16693 }
16694 work.extend(left_children.iter().zip(right_children).rev());
16695 }
16696 _ => return false,
16697 }
16698 }
16699 true
16700}
16701
16702pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16703 let mut components = Vec::new();
16704 let mut stack = vec![node];
16705 while let Some(current) = stack.pop() {
16706 match current.kind() {
16707 "identifier"
16708 | "field_identifier"
16709 | "namespace_identifier"
16710 | "type_identifier"
16711 | "operator_name"
16712 | "destructor_name" => components.push(current),
16713 "template_type" | "template_function" => {
16714 stack.push(current.child_by_field_name("name")?);
16715 }
16716 "dependent_name" => stack.push(current.named_child(0)?),
16717 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16718 stack.push(current.child_by_field_name("name")?);
16719 if let Some(scope) = current.child_by_field_name("scope") {
16720 stack.push(scope);
16721 }
16722 }
16723 "nested_namespace_specifier" => {
16724 for index in (0..current.named_child_count()).rev() {
16725 stack.push(current.named_child(index)?);
16726 }
16727 }
16728 _ => return None,
16729 }
16730 }
16731 Some(components)
16732}
16733
16734pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16735 node.child_by_field_name("scope").is_none()
16736 && node.child(0).is_some_and(|child| child.kind() == "::")
16737}
16738
16739fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16740 let mut namespaces = Vec::new();
16741 let mut current = node.parent();
16742 while let Some(parent) = current {
16743 if parent.kind() == "namespace_definition"
16744 && let Some(name) = parent.child_by_field_name("name")
16745 {
16746 let mut components = Vec::new();
16747 append_cpp_name_components(name, source, &mut components)?;
16748 namespaces.push(components);
16749 }
16750 current = parent.parent();
16751 }
16752 namespaces.reverse();
16753 Some(namespaces.into_iter().flatten().collect())
16754}
16755
16756fn indexed_namespace_path_is_recoverable(
16767 lexical_scope: &[String],
16768 indexed_owner_scope: &[String],
16769 explicit_owner_component_count: usize,
16770) -> bool {
16771 if lexical_scope.is_empty() {
16772 return explicit_owner_component_count > 1;
16773 }
16774 if lexical_scope.len() >= indexed_owner_scope.len() {
16775 return false;
16776 }
16777 let mut indexed = indexed_owner_scope.iter();
16778 lexical_scope
16779 .iter()
16780 .all(|component| indexed.any(|candidate| candidate == component))
16781}
16782
16783pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16784 let mut current = node.parent();
16785 while let Some(parent) = current {
16786 if parent.kind() == kind {
16787 return true;
16788 }
16789 current = parent.parent();
16790 }
16791 false
16792}
16793
16794pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16800 let mut current = Some(node);
16801 while let Some(candidate) = current {
16802 if candidate.kind() == "declaration" {
16803 let Some(type_node) = candidate.child_by_field_name("type") else {
16804 return false;
16805 };
16806 if !(type_node.start_byte() <= node.start_byte()
16807 && node.end_byte() <= type_node.end_byte())
16808 {
16809 return false;
16810 }
16811 let mut cursor = candidate.walk();
16812 return candidate.named_children(&mut cursor).any(|child| {
16813 child.kind() == "init_declarator"
16814 && child
16815 .child_by_field_name("value")
16816 .is_some_and(|value| value.kind() == "cast_expression")
16817 });
16818 }
16819 current = candidate.parent();
16820 }
16821 false
16822}
16823
16824#[derive(Clone, Copy, PartialEq, Eq)]
16825pub(crate) enum QualifiedAliasReferenceKind {
16826 Ordinary,
16827 ConstructorWithExpressionArgument,
16828 ExhaustiveTemplate,
16829}
16830
16831pub(crate) fn qualified_alias_reference_preserves_target(
16838 node: Node<'_>,
16839 target: &CodeUnit,
16840 analyzer: &CppGraphSource<'_>,
16841 visibility: &VisibilityIndex<'_>,
16842 file: &ProjectFile,
16843 source: &str,
16844) -> Option<QualifiedAliasReferenceKind> {
16845 if !matches!(
16846 node.kind(),
16847 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16848 ) {
16849 return None;
16850 }
16851 let components = cpp_type_name_components(node, source)?;
16852 let name = components.last()?;
16853 analyzer.type_alias_provider().and_then(|provider| {
16854 visibility
16855 .visible_identifier_candidates(file, name)
16856 .find_map(|candidate| {
16857 let proof = provider.is_type_alias(candidate)
16858 && canonical_cpp_scope_components(candidate) == components
16859 && visibility.external_type_candidate_visible_in_context(
16860 analyzer, file, candidate, node,
16861 )
16862 && match cpp_template_reference_arguments(node, source) {
16863 Some(arguments) => visibility.template_alias_arguments_preserve_target(
16864 analyzer, file, candidate, &arguments, target,
16865 ),
16866 None => visibility.structured_alias_primary_preserves_target(
16867 analyzer, file, candidate, target,
16868 ),
16869 };
16870 proof.then(|| {
16871 if cpp_template_reference_arguments(node, source).is_some()
16872 && visibility.is_exhaustive_same_fqn_type_declaration_family(
16873 analyzer, file, candidate,
16874 )
16875 {
16876 QualifiedAliasReferenceKind::ExhaustiveTemplate
16877 } else if qualified_alias_constructor_has_expression_argument(node)
16878 || qualified_alias_local_constructor_declaration(node)
16879 {
16880 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16881 } else {
16882 QualifiedAliasReferenceKind::Ordinary
16883 }
16884 })
16885 })
16886 })
16887}
16888
16889pub(crate) fn qualified_alias_reference_requires_terminal(
16890 reference: Option<QualifiedAliasReferenceKind>,
16891) -> bool {
16892 matches!(
16893 reference,
16894 Some(
16895 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16896 | QualifiedAliasReferenceKind::ExhaustiveTemplate
16897 )
16898 )
16899}
16900
16901fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16902 let Some(declaration) = node.parent().filter(|parent| {
16903 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16904 }) else {
16905 return false;
16906 };
16907 let mut cursor = declaration.walk();
16908 declaration.named_children(&mut cursor).any(|child| {
16909 child.kind() == "init_declarator"
16910 && child
16911 .child_by_field_name("value")
16912 .filter(|value| value.kind() == "argument_list")
16913 .is_some_and(|arguments| {
16914 let mut cursor = arguments.walk();
16915 arguments.named_children(&mut cursor).any(|argument| {
16916 let is_parameter = matches!(
16917 argument.kind(),
16918 "parameter_declaration" | "optional_parameter_declaration"
16919 );
16920 if is_parameter {
16921 argument
16922 .child_by_field_name("type")
16923 .is_some_and(|type_node| {
16924 type_node.kind() == "type_identifier"
16925 && argument.child_by_field_name("declarator").is_none()
16926 })
16927 } else {
16928 !argument.kind().ends_with("_literal")
16929 && !matches!(argument.kind(), "true" | "false" | "nullptr")
16930 }
16931 })
16932 })
16933 })
16934}
16935
16936fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16941 let Some(declaration) = node.parent().filter(|parent| {
16942 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16943 }) else {
16944 return false;
16945 };
16946 if declaration
16947 .parent()
16948 .is_none_or(|parent| parent.kind() != "compound_statement")
16949 {
16950 return false;
16951 }
16952 let mut cursor = declaration.walk();
16953 declaration
16954 .named_children(&mut cursor)
16955 .any(|child| child.kind() == "function_declarator")
16956}
16957
16958pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16964 loop {
16965 let next = match node.kind() {
16966 "qualified_identifier"
16967 | "scoped_identifier"
16968 | "template_method"
16969 | "template_function"
16970 | "template_type" => node.child_by_field_name("name"),
16971 "field_expression" => node.child_by_field_name("field"),
16972 _ => None,
16973 };
16974 let Some(next) = next else {
16975 return node;
16976 };
16977 node = next;
16978 }
16979}
16980
16981#[derive(Clone, Copy)]
16982pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16983 pub receiver: Node<'tree>,
16984 pub member: Node<'tree>,
16985 pub arity: usize,
16986}
16987
16988pub fn recovered_relational_template_member_call(
16996 field: Node<'_>,
16997) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16998 if field.kind() != "field_expression" {
16999 return None;
17000 }
17001 let receiver = field
17002 .child_by_field_name("argument")
17003 .or_else(|| field.child_by_field_name("object"))?;
17004 let member = field.child_by_field_name("field")?;
17005 let less = field.parent()?;
17006 if less.kind() != "binary_expression"
17007 || less.child_by_field_name("left") != Some(field)
17008 || less
17009 .child_by_field_name("operator")
17010 .is_none_or(|operator| operator.kind() != "<")
17011 || less.child_by_field_name("right").is_none()
17012 {
17013 return None;
17014 }
17015 let greater = less.parent()?;
17016 if greater.kind() != "binary_expression"
17017 || greater.child_by_field_name("left") != Some(less)
17018 || greater
17019 .child_by_field_name("operator")
17020 .is_none_or(|operator| operator.kind() != ">")
17021 {
17022 return None;
17023 }
17024 let arguments = greater.child_by_field_name("right")?;
17025 if arguments.kind() != "parenthesized_expression" {
17026 return None;
17027 }
17028 let arity = parenthesized_call_argument_arity(arguments)?;
17029 Some(RecoveredRelationalTemplateMemberCall {
17030 receiver,
17031 member,
17032 arity,
17033 })
17034}
17035
17036fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17037 let expression = arguments.named_child(0)?;
17038 if expression.kind() != "comma_expression" {
17039 return Some(1);
17040 }
17041 let mut arity = 0usize;
17042 let mut stack = vec![expression];
17043 while let Some(node) = stack.pop() {
17044 if node.kind() == "comma_expression" {
17045 stack.push(node.child_by_field_name("right")?);
17046 stack.push(node.child_by_field_name("left")?);
17047 } else {
17048 arity += 1;
17049 }
17050 }
17051 Some(arity)
17052}
17053
17054pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17057 while let Some(parent) = node.parent() {
17058 match parent.kind() {
17059 "call_expression" => {
17060 return parent
17061 .child_by_field_name("function")
17062 .or_else(|| parent.named_child(0))
17063 == Some(node);
17064 }
17065 "qualified_identifier"
17066 | "scoped_identifier"
17067 | "template_function"
17068 | "template_type"
17069 | "field_expression" => node = parent,
17070 _ => return false,
17071 }
17072 }
17073 false
17074}
17075
17076pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
17077 if is_call_callee_node(node) {
17078 function_terminal_node(node)
17079 } else {
17080 node
17081 }
17082}
17083
17084pub fn normalize_type_text(value: &str) -> String {
17085 strip_tag_type_prefix(
17086 normalize_cpp_whitespace(value)
17087 .trim_start_matches("const ")
17088 .trim_end_matches('*')
17089 .trim_end_matches('&')
17090 .trim(),
17091 )
17092 .to_string()
17093}
17094
17095fn strip_tag_type_prefix(value: &str) -> &str {
17096 let value = value.trim_start_matches("const ");
17097 value
17098 .strip_prefix("struct ")
17099 .or_else(|| value.strip_prefix("class "))
17100 .or_else(|| value.strip_prefix("enum "))
17101 .unwrap_or(value)
17102 .trim()
17103}
17104
17105pub fn normalize_reference_name(value: &str) -> Option<String> {
17106 let normalized = normalize_cpp_reference_text(value);
17107 (!normalized.is_empty()).then_some(normalized)
17108}
17109
17110pub fn normalize_cpp_reference_text(value: &str) -> String {
17111 let mut text = normalize_cpp_whitespace(value)
17112 .trim_start_matches("new ")
17113 .trim()
17114 .to_string();
17115 if let Some(index) = text.find(['(', '{']) {
17116 text.truncate(index);
17117 }
17118 if let Some(index) = text.find('<') {
17119 text.truncate(index);
17120 }
17121 let normalized = text
17122 .trim()
17123 .trim_start_matches("const ")
17124 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17125 .trim_matches(':')
17126 .trim();
17127 strip_tag_type_prefix(normalized).to_string()
17128}
17129
17130pub fn cpp_name_for(unit: &CodeUnit) -> String {
17131 let short = unit.short_name().replace(['.', '$'], "::");
17132 if unit.package_name().is_empty() {
17133 short
17134 } else {
17135 format!("{}::{}", unit.package_name(), short)
17136 }
17137}
17138
17139fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17143 let fq = unit.fq();
17144 if fq.is_empty() {
17145 return None;
17146 }
17147 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17148 Some(
17149 fq.segments()
17150 .iter()
17151 .map(|&segment| interner.resolve(segment).0)
17152 .collect::<Vec<_>>()
17153 .join("::"),
17154 )
17155}
17156
17157fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17158 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17159 || unit.fq().is_empty() && cpp_name_for(unit) == expected
17160}
17161
17162pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17171 let fq = unit.fq();
17172 if !fq.is_empty() {
17173 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17174 let scope = fq
17175 .segments()
17176 .iter()
17177 .filter_map(|&segment| {
17178 let (text, kind) = interner.resolve(segment);
17179 matches!(
17180 kind,
17181 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17182 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17183 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17184 )
17185 .then(|| text.to_string())
17186 })
17187 .collect();
17188 return scope;
17189 }
17190 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17191 brokk_bifrost_core::analyzer::Language::Cpp,
17192 &cpp_name_for(unit),
17193 )
17194}
17195
17196pub fn terminal_name(value: &str) -> &str {
17207 value
17208 .rsplit("::")
17209 .next()
17210 .unwrap_or(value)
17211 .rsplit(['.', '-', '>'])
17212 .next()
17213 .unwrap_or(value)
17214 .trim()
17215}
17216
17217pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17218 terminal_name(&normalize_cpp_reference_text(value)) == expected
17219}
17220
17221pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17222 name_matches_terminal(value, expected)
17223 || expected.starts_with("operator")
17224 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17225}
17226
17227pub fn name_mentions(value: &str, expected: &str) -> bool {
17228 normalize_cpp_reference_text(value)
17229 .split("::")
17230 .any(|part| part == expected)
17231}
17232
17233pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17234 let cpp_name = cpp_name_for(unit);
17235 if reference.contains("::") {
17236 return reference == cpp_name;
17237 }
17238 reference == cpp_name
17239 || terminal_name(reference) == unit.identifier()
17240 && (unit.package_name().is_empty() || reference == unit.identifier())
17241}
17242
17243pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
17244 match kind {
17245 TargetKind::Type
17246 | TargetKind::Constructor
17247 | TargetKind::Method
17248 | TargetKind::MemberField => true,
17249 TargetKind::FreeFunction => unit.is_function(),
17250 TargetKind::GlobalField => unit.is_field(),
17251 TargetKind::Macro => unit.is_macro(),
17252 }
17253}
17254
17255pub fn is_type_alias(unit: &CodeUnit) -> bool {
17256 unit.kind() == CodeUnitType::Field
17257 && unit.signature().is_some_and(|signature| {
17258 signature.starts_with("typedef ") || signature.starts_with("using ")
17259 })
17260}
17261
17262fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
17263 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
17264 let target_name = cpp_name_for(target);
17265 if normalized.contains("::") {
17266 return normalized == target_name;
17267 }
17268 if let Some(namespace) = alias.namespace.as_deref() {
17269 return namespace_prefixes(namespace)
17270 .into_iter()
17271 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
17272 }
17273 target.package_name().is_empty() && normalized == target.identifier()
17274}
17275
17276pub fn cpp_function_return_type_text(
17279 analyzer: &CppGraphSource<'_>,
17280 function: &CodeUnit,
17281) -> Option<String> {
17282 let metadata = analyzer.signature_metadata(function);
17283 if !metadata.is_empty() {
17284 let first = metadata.first()?.return_type_text()?;
17285 return metadata
17286 .iter()
17287 .all(|metadata| metadata.return_type_text() == Some(first))
17288 .then(|| first.to_string());
17289 }
17290 let signature = cpp_function_signature_text(analyzer, function)?;
17291 cpp_function_return_type_text_from_signature(&signature)
17292}
17293
17294fn cpp_function_signature_text(
17295 analyzer: &CppGraphSource<'_>,
17296 function: &CodeUnit,
17297) -> Option<String> {
17298 function
17299 .signature()
17300 .filter(|signature| signature.contains(function.identifier()))
17301 .map(str::to_string)
17302 .or_else(|| analyzer.signatures(function).first().cloned())
17303 .or_else(|| analyzer.get_source(function, false))
17304}
17305
17306fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
17307 let open = signature.find('(')?;
17308 let name_at = cpp_function_name_start(signature, open)?;
17309 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
17310 return Some(return_type);
17311 }
17312 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
17313 .split_whitespace()
17314 .filter(|token| {
17315 !matches!(
17316 *token,
17317 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
17318 )
17319 })
17320 .collect::<Vec<_>>()
17321 .join(" ");
17322 let type_text = type_text.trim();
17323 (!type_text.is_empty()).then(|| type_text.to_string())
17324}
17325
17326fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
17327 let before_parameters = &signature[..open];
17328 if let Some(operator_at) = before_parameters.rfind("operator") {
17329 let boundary = operator_at == 0
17330 || before_parameters[..operator_at]
17331 .chars()
17332 .next_back()
17333 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
17334 if boundary {
17335 return Some(operator_at);
17336 }
17337 }
17338 before_parameters
17339 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
17340 .map(|index| index + 1)
17341}
17342
17343fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
17344 let open = signature_from_name.find('(')?;
17345 let mut depth = 0i32;
17346 for (offset, ch) in signature_from_name[open..].char_indices() {
17347 match ch {
17348 '(' => depth += 1,
17349 ')' => {
17350 depth -= 1;
17351 if depth == 0 {
17352 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
17353 let arrow = rest.find("->")?;
17354 let return_type = rest[arrow + 2..].trim_start();
17355 let return_type = return_type
17356 .split(['{', ';'])
17357 .next()
17358 .unwrap_or(return_type)
17359 .trim();
17360 return (!return_type.is_empty()).then(|| return_type.to_string());
17361 }
17362 }
17363 _ => {}
17364 }
17365 }
17366 None
17367}
17368
17369fn cpp_strip_leading_template_clause(text: &str) -> &str {
17372 let trimmed = text.trim_start();
17373 let Some(rest) = trimmed.strip_prefix("template") else {
17374 return text;
17375 };
17376 let rest = rest.trim_start();
17377 if !rest.starts_with('<') {
17378 return text;
17379 }
17380 let mut depth = 0i32;
17381 for (offset, ch) in rest.char_indices() {
17382 match ch {
17383 '<' => depth += 1,
17384 '>' => {
17385 depth -= 1;
17386 if depth == 0 {
17387 return rest[offset + ch.len_utf8()..].trim_start();
17388 }
17389 }
17390 _ => {}
17391 }
17392 }
17393 text
17394}
17395
17396pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17397 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17407 namespace
17408 .strip_prefix("anonymous_namespace::")
17409 .unwrap_or(namespace)
17410 .to_string()
17411 })
17412}
17413
17414fn namespace_prefixes(namespace: &str) -> Vec<String> {
17415 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17421 brokk_bifrost_core::analyzer::Language::Cpp,
17422 namespace,
17423 );
17424 let mut prefixes = Vec::new();
17425 while !parts.is_empty() {
17426 prefixes.push(parts.join("::"));
17427 parts.pop();
17428 }
17429 prefixes
17430}
17431
17432fn nearest_namespace_candidates(
17433 candidates: Vec<CodeUnit>,
17434 normalized: &str,
17435 lexical_namespace: Option<&str>,
17436) -> Vec<CodeUnit> {
17437 if normalized.contains("::") {
17438 return candidates;
17439 }
17440 if let Some(namespace) = lexical_namespace {
17441 for prefix in namespace_prefixes(namespace) {
17442 let scoped = candidates
17443 .iter()
17444 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17445 .cloned()
17446 .collect::<Vec<_>>();
17447 if !scoped.is_empty() {
17448 return scoped;
17449 }
17450 }
17451 }
17452 candidates
17453 .into_iter()
17454 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17455 .collect()
17456}
17457
17458pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17459 let mut namespaces = Vec::new();
17460 let mut current = node.parent();
17461 while let Some(parent) = current {
17462 if parent.kind() == "namespace_definition"
17463 && let Some(name) = parent.child_by_field_name("name")
17464 {
17465 let namespace = normalize_cpp_reference_text(node_text(name, source));
17466 if !namespace.is_empty() {
17467 namespaces.push(namespace);
17468 }
17469 }
17470 current = parent.parent();
17471 }
17472 if namespaces.is_empty() {
17473 None
17474 } else {
17475 namespaces.reverse();
17476 Some(namespaces.join("::"))
17477 }
17478}
17479
17480pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17484 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17485}
17486
17487fn type_owner_resolution(
17488 analyzer: &CppGraphSource<'_>,
17489 code_unit: &CodeUnit,
17490) -> Option<ResolvedTypeOwner> {
17491 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17492}
17493
17494fn target_type_owner_resolution(
17495 analyzer: &CppGraphSource<'_>,
17496 code_unit: &CodeUnit,
17497) -> Option<ResolvedTypeOwner> {
17498 match type_owner_resolution(analyzer, code_unit) {
17499 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17500 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17501 }
17502}
17503
17504fn target_forward_owner_resolution(
17516 analyzer: &CppGraphSource<'_>,
17517 code_unit: &CodeUnit,
17518) -> Option<ResolvedTypeOwner> {
17519 if !code_unit.is_function() {
17520 return None;
17521 }
17522 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17528 let cpp = analyzer.cpp?;
17529 let mut visible_files = HashSet::default();
17530 collect_include_closure(
17531 analyzer,
17532 cpp.include_target_index(),
17533 code_unit.source(),
17534 &mut visible_files,
17535 None,
17536 );
17537 let candidates = analyzer.workspace_definitions().exact(&owner_name);
17538 let visible_candidates = candidates
17539 .iter()
17540 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17541 .cloned()
17542 .collect::<Vec<_>>();
17543 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17544 DirectOwnerResolution::UniqueFull(unit) => {
17545 return Some(ResolvedTypeOwner {
17546 unit,
17547 is_forward_declaration: false,
17548 });
17549 }
17550 DirectOwnerResolution::ForwardsOnly(forwards) => {
17551 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17552 unit: forwards.into_iter().next().unwrap(),
17553 is_forward_declaration: true,
17554 });
17555 }
17556 DirectOwnerResolution::Ambiguous => return None,
17557 DirectOwnerResolution::None => {}
17558 }
17559
17560 let candidates = candidates
17561 .into_iter()
17562 .filter(|candidate| candidate.is_class())
17563 .collect::<Vec<_>>();
17564 let (unit, is_forward_declaration) =
17565 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17566 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17567 DirectOwnerResolution::ForwardsOnly(forwards) => {
17568 (unique_logical_forward_owner(forwards)?, true)
17569 }
17570 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17571 };
17572 Some(ResolvedTypeOwner {
17573 unit,
17574 is_forward_declaration,
17575 })
17576}
17577
17578pub fn precise_parent_of(
17579 analyzer: &CppGraphSource<'_>,
17580 visibility: &VisibilityIndex<'_>,
17581 code_unit: &CodeUnit,
17582) -> Option<CodeUnit> {
17583 visibility.cached_precise_parent_of(analyzer, code_unit)
17584}
17585
17586fn precise_parent_resolution(
17587 analyzer: &CppGraphSource<'_>,
17588 code_unit: &CodeUnit,
17589) -> Option<ResolvedTypeOwner> {
17590 #[cfg(any(test, feature = "test-support"))]
17591 if let Some(cpp) = analyzer.cpp {
17592 cpp.record_cpp_parent_resolution_for_test();
17593 }
17594 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17595 return Some(ResolvedTypeOwner {
17596 unit,
17597 is_forward_declaration: false,
17598 });
17599 }
17600 let fallback = analyzer.parent_of(code_unit);
17601 if !code_unit.owner_is_type_scope() {
17602 return fallback.map(|unit| ResolvedTypeOwner {
17603 unit,
17604 is_forward_declaration: false,
17605 });
17606 }
17607 let owner_fq = code_unit
17608 .fq()
17609 .parent()
17610 .expect("a unit with an owner identifier has a structured parent");
17611 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17612 match same_source_owner(analyzer, code_unit, &owner_candidates) {
17613 DirectOwnerResolution::UniqueFull(owner) => {
17614 return Some(ResolvedTypeOwner {
17615 unit: owner,
17616 is_forward_declaration: false,
17617 });
17618 }
17619 DirectOwnerResolution::Ambiguous => return None,
17620 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17621 }
17622 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17623 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17624 unit: owner,
17625 is_forward_declaration: false,
17626 }),
17627 DirectOwnerResolution::Ambiguous => None,
17628 DirectOwnerResolution::ForwardsOnly(forwards) => {
17629 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17630 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17631 unit: owner,
17632 is_forward_declaration: false,
17633 }),
17634 FullOwnerResolution::None => {
17635 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17636 unit,
17637 is_forward_declaration: true,
17638 })
17639 }
17640 FullOwnerResolution::Ambiguous => None,
17641 }
17642 }
17643 DirectOwnerResolution::None => {
17644 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17645 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17646 unit: owner,
17647 is_forward_declaration: false,
17648 }),
17649 FullOwnerResolution::Ambiguous => None,
17650 FullOwnerResolution::None => fallback
17651 .filter(|parent| {
17652 parent.source() == code_unit.source()
17653 && parent.fq() == &owner_fq
17654 && (!parent.is_class()
17655 || cpp_class_declaration_strength(analyzer, parent)
17656 == CppClassDeclarationStrength::Full)
17657 })
17658 .map(|unit| ResolvedTypeOwner {
17659 unit,
17660 is_forward_declaration: false,
17661 }),
17662 }
17663 }
17664 }
17665}
17666
17667fn exact_structural_type_parent(
17668 analyzer: &CppGraphSource<'_>,
17669 code_unit: &CodeUnit,
17670) -> Option<CodeUnit> {
17671 if !code_unit.is_function() && !code_unit.is_field() {
17672 return None;
17673 }
17674 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
17676 let parent = cpp.structural_parent_of(code_unit)?;
17677 (!parent.is_module()
17678 && parent.source() == code_unit.source()
17679 && parent.package_name() == code_unit.package_name()
17680 && parent.short_name() == encoded_owner)
17681 .then_some(parent)
17682}
17683
17684fn same_source_owner(
17685 analyzer: &CppGraphSource<'_>,
17686 code_unit: &CodeUnit,
17687 owner_candidates: &[CodeUnit],
17688) -> DirectOwnerResolution {
17689 let candidates = owner_candidates
17690 .iter()
17691 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17692 .cloned()
17693 .collect::<Vec<_>>();
17694 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17695 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17696}
17697
17698fn visible_full_cpp_owner(
17699 analyzer: &CppGraphSource<'_>,
17700 code_unit: &CodeUnit,
17701 owner_candidates: &[CodeUnit],
17702) -> FullOwnerResolution {
17703 let Some(cpp) = analyzer.cpp else {
17704 return FullOwnerResolution::None;
17705 };
17706 let mut visible_files = HashSet::default();
17707 collect_include_closure(
17708 analyzer,
17709 cpp.include_target_index(),
17710 code_unit.source(),
17711 &mut visible_files,
17712 None,
17713 );
17714 let candidates = owner_candidates
17715 .iter()
17716 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17717 .cloned()
17718 .collect::<Vec<_>>();
17719 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17720 let mut full_definition = None;
17721 for candidate in candidates {
17722 match cpp_class_declaration_strength(analyzer, &candidate) {
17723 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17724 return FullOwnerResolution::Ambiguous;
17725 }
17726 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17727 CppClassDeclarationStrength::Forward => {}
17728 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17729 }
17730 }
17731 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17732}
17733
17734pub enum DirectOwnerResolution {
17735 None,
17736 ForwardsOnly(Vec<CodeUnit>),
17737 UniqueFull(CodeUnit),
17738 Ambiguous,
17739}
17740
17741enum FullOwnerResolution {
17742 None,
17743 Unique(CodeUnit),
17744 Ambiguous,
17745}
17746
17747#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17748pub enum CppClassDeclarationStrength {
17749 Full,
17750 Forward,
17751 Unknown,
17752}
17753
17754fn directly_included_owner(
17755 analyzer: &CppGraphSource<'_>,
17756 code_unit: &CodeUnit,
17757 owner_candidates: &[CodeUnit],
17758) -> DirectOwnerResolution {
17759 let Some(cpp) = analyzer.cpp else {
17760 return DirectOwnerResolution::None;
17761 };
17762 let imports = analyzer.import_statements(code_unit.source());
17763 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
17764 .into_iter()
17765 .flat_map(|include| {
17766 resolve_include_targets_with_index(
17767 code_unit.source(),
17768 &include,
17769 cpp.include_target_index(),
17770 )
17771 })
17772 .collect();
17773 let candidates = owner_candidates
17774 .iter()
17775 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17776 .cloned()
17777 .collect::<Vec<_>>();
17778 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17779 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17780}
17781
17782fn prefer_member_declaring_owners(
17783 analyzer: &CppGraphSource<'_>,
17784 member: &CodeUnit,
17785 candidates: Vec<CodeUnit>,
17786) -> Vec<CodeUnit> {
17787 let matching = candidates
17788 .iter()
17789 .filter(|owner| owner_declares_member(analyzer, owner, member))
17790 .cloned()
17791 .collect::<Vec<_>>();
17792 if matching.is_empty() {
17793 candidates
17794 } else {
17795 matching
17796 }
17797}
17798
17799fn owner_declares_member(
17800 analyzer: &CppGraphSource<'_>,
17801 owner: &CodeUnit,
17802 member: &CodeUnit,
17803) -> bool {
17804 analyzer.direct_children(owner).into_iter().any(|child| {
17805 child.kind() == member.kind()
17806 && child.identifier() == member.identifier()
17807 && child.signature() == member.signature()
17808 })
17809}
17810
17811fn classify_direct_owner_candidates(
17812 analyzer: &CppGraphSource<'_>,
17813 candidates: impl Iterator<Item = CodeUnit>,
17814) -> DirectOwnerResolution {
17815 collapse_owner_candidates(candidates.map(|candidate| {
17816 let strength = cpp_class_declaration_strength(analyzer, &candidate);
17817 (candidate, strength)
17818 }))
17819}
17820
17821pub fn collapse_owner_candidates(
17822 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17823) -> DirectOwnerResolution {
17824 let mut full_definition = None;
17825 let mut forwards = Vec::new();
17826 for (candidate, strength) in candidates {
17827 match strength {
17828 CppClassDeclarationStrength::Full if full_definition.is_some() => {
17829 return DirectOwnerResolution::Ambiguous;
17830 }
17831 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17832 CppClassDeclarationStrength::Forward => forwards.push(candidate),
17833 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17834 }
17835 }
17836 if let Some(owner) = full_definition {
17837 DirectOwnerResolution::UniqueFull(owner)
17838 } else if !forwards.is_empty() {
17839 DirectOwnerResolution::ForwardsOnly(forwards)
17840 } else {
17841 DirectOwnerResolution::None
17842 }
17843}
17844
17845#[cfg(any(test, feature = "test-support"))]
17846pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17847 unique_logical_forward_owner(forwards)
17848}
17849
17850fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17851 let first = forwards.pop()?;
17852 forwards
17853 .iter()
17854 .all(|forward| same_logical_symbol(forward, &first))
17855 .then_some(first)
17856}
17857
17858pub fn cpp_class_declaration_strength(
17859 analyzer: &CppGraphSource<'_>,
17860 candidate: &CodeUnit,
17861) -> CppClassDeclarationStrength {
17862 let Some(cpp) = analyzer.cpp else {
17870 return uncached_cpp_class_declaration_strength(analyzer, candidate);
17871 };
17872 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17873 return strength;
17874 }
17875 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17876 cpp.cache_class_declaration_strength(candidate, strength);
17877 strength
17878}
17879
17880fn uncached_cpp_class_declaration_strength(
17881 analyzer: &CppGraphSource<'_>,
17882 candidate: &CodeUnit,
17883) -> CppClassDeclarationStrength {
17884 if let Some(cpp) = analyzer.cpp
17885 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17886 {
17887 return cpp_class_declaration_strength_in_tree(
17888 analyzer,
17889 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17890 candidate,
17891 prepared.source(),
17892 prepared.tree().root_node(),
17893 );
17894 }
17895 let Some(source) = analyzer.indexed_source(candidate.source()) else {
17896 return CppClassDeclarationStrength::Unknown;
17897 };
17898 #[cfg(any(test, feature = "test-support"))]
17899 if let Some(cpp) = analyzer.cpp {
17900 cpp.record_cpp_class_strength_parse_for_test();
17901 }
17902 let mut parser = Parser::new();
17903 if parser
17904 .set_language(&tree_sitter_cpp::LANGUAGE.into())
17905 .is_err()
17906 {
17907 return CppClassDeclarationStrength::Unknown;
17908 }
17909 let Some(tree) = parser.parse(&source, None) else {
17910 return CppClassDeclarationStrength::Unknown;
17911 };
17912 let recovered_export_classes =
17915 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17916 cpp_class_declaration_strength_in_tree(
17917 analyzer,
17918 &recovered_export_classes,
17919 candidate,
17920 &source,
17921 tree.root_node(),
17922 )
17923}
17924
17925fn cpp_class_declaration_strength_in_tree(
17926 analyzer: &CppGraphSource<'_>,
17927 recovered_export_classes: &CppRecoveredExportClassIndex,
17928 candidate: &CodeUnit,
17929 source: &str,
17930 root: Node<'_>,
17931) -> CppClassDeclarationStrength {
17932 let ranges = analyzer.ranges(candidate);
17933 let mut saw_forward = false;
17934 for range in ranges {
17935 match recovered_class_body_at(
17938 recovered_export_classes,
17939 root,
17940 source,
17941 candidate.identifier(),
17942 &range,
17943 ) {
17944 Some(true) => return CppClassDeclarationStrength::Full,
17945 Some(false) => {
17946 saw_forward = true;
17947 continue;
17948 }
17949 None => {}
17950 }
17951 let covers_range_start = |node: &Node<'_>| {
17958 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17959 };
17960 let mut stack = Vec::new();
17961 if covers_range_start(&root) {
17962 stack.push(root);
17963 }
17964 while let Some(node) = stack.pop() {
17965 if node.start_byte() == range.start_byte
17966 && node.end_byte() == range.end_byte
17967 && matches!(
17968 node.kind(),
17969 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17970 )
17971 {
17972 if cpp_class_node_has_body(node) {
17973 return CppClassDeclarationStrength::Full;
17974 }
17975 saw_forward = true;
17976 }
17977 let mut cursor = node.walk();
17978 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17979 }
17980 }
17981 if saw_forward {
17982 CppClassDeclarationStrength::Forward
17983 } else {
17984 CppClassDeclarationStrength::Unknown
17985 }
17986}
17987
17988fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17989 node.child_by_field_name("body").is_some() || {
17990 let mut cursor = node.walk();
17991 node.named_children(&mut cursor).any(|child| {
17992 matches!(
17993 child.kind(),
17994 "declaration_list" | "field_declaration_list" | "enumerator_list"
17995 )
17996 })
17997 }
17998}
17999
18000#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18001enum CppCTagKind {
18002 Struct,
18003 Union,
18004}
18005
18006fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18007 let declaration = analyzer.get_source(code_unit, false)?;
18008 let mut parser = Parser::new();
18009 parser
18010 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18011 .ok()?;
18012 let tree = parser.parse(&declaration, None)?;
18013 let mut stack = vec![tree.root_node()];
18014 while let Some(node) = stack.pop() {
18015 let kind = match node.kind() {
18016 "struct_specifier" => CppCTagKind::Struct,
18017 "union_specifier" => CppCTagKind::Union,
18018 _ => {
18019 let mut cursor = node.walk();
18020 stack.extend(node.named_children(&mut cursor));
18021 continue;
18022 }
18023 };
18024 if node
18025 .child_by_field_name("name")
18026 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18027 {
18028 return Some(kind);
18029 }
18030 let mut cursor = node.walk();
18031 stack.extend(node.named_children(&mut cursor));
18032 }
18033 None
18034}
18035
18036pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18037 if !code_unit.owner_is_type_scope() {
18038 return None;
18039 }
18040 let owner_fq = code_unit.fq().parent()?;
18041 ctx.analyzer
18042 .workspace_definitions()
18043 .exact(&owner_fq)
18044 .into_iter()
18045 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18046}
18047
18048pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18049 left.kind() == right.kind()
18050 && left.fq_name() == right.fq_name()
18051 && left.signature() == right.signature()
18052 && left.source() == right.source()
18053}
18054
18055pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18056 same_symbol(left, right) || same_logical_symbol(left, right)
18057}
18058
18059pub fn same_visible_global_field_symbol(
18060 analyzer: &CppGraphSource<'_>,
18061 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18062 left: &CodeUnit,
18063 right: &CodeUnit,
18064) -> bool {
18065 if same_symbol(left, right) {
18066 return true;
18067 }
18068 if !same_logical_symbol(left, right) {
18069 return false;
18070 }
18071 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18072 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18073 {
18074 left.source() == right.source()
18075 } else {
18076 true
18077 }
18078}
18079
18080fn cpp_global_field_has_internal_linkage_cached(
18081 analyzer: &CppGraphSource<'_>,
18082 cache: &mut HashMap<CodeUnit, bool>,
18083 candidate: &CodeUnit,
18084) -> bool {
18085 if let Some(internal) = cache.get(candidate) {
18086 return *internal;
18087 }
18088 #[cfg(any(test, feature = "test-support"))]
18089 note_cpp_global_field_internal_linkage_classification_for_test();
18090 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18091 cache.insert(candidate.clone(), internal);
18092 internal
18093}
18094
18095#[cfg(any(test, feature = "test-support"))]
18096thread_local! {
18097 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18098}
18099
18100#[cfg(any(test, feature = "test-support"))]
18101fn note_cpp_global_field_internal_linkage_classification_for_test() {
18102 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18103 count.set(count.get() + 1);
18104 });
18105}
18106
18107#[cfg(any(test, feature = "test-support"))]
18108pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18109 body: impl FnOnce() -> T,
18110) -> (T, usize) {
18111 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18112 count.set(0);
18113 let result = body();
18114 let observed = count.get();
18115 count.set(0);
18116 (result, observed)
18117 })
18118}
18119
18120pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18121 left.kind() == right.kind()
18122 && left.fq_name() == right.fq_name()
18123 && left.signature() == right.signature()
18124}
18125
18126pub fn cpp_global_field_has_internal_linkage(
18127 analyzer: &CppGraphSource<'_>,
18128 candidate: &CodeUnit,
18129) -> bool {
18130 if !candidate.is_field() || candidate.short_name().contains('.') {
18131 return false;
18132 }
18133 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18134 return false;
18135 };
18136 match local_linkage {
18137 CppFieldLinkage::Internal => true,
18138 CppFieldLinkage::External => false,
18139 CppFieldLinkage::InternalUnlessExternalPeer => {
18140 !cpp_global_field_linkage_peers(analyzer, candidate)
18141 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18142 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18143 }
18144 }
18145}
18146
18147fn cpp_global_field_linkage_peers<'a>(
18148 analyzer: &CppGraphSource<'a>,
18149 candidate: &'a CodeUnit,
18150) -> impl Iterator<Item = CodeUnit> + 'a {
18151 let name = candidate.fq().clone();
18152 analyzer
18153 .workspace_definitions()
18154 .exact(&name)
18155 .into_iter()
18156 .filter(move |peer| {
18157 if peer == candidate {
18158 return false;
18159 }
18160 #[cfg(any(test, feature = "test-support"))]
18161 note_cpp_global_field_linkage_peer_inspection_for_test();
18162 same_logical_symbol(peer, candidate)
18163 })
18164}
18165
18166#[cfg(any(test, feature = "test-support"))]
18167thread_local! {
18168 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18169}
18170
18171#[cfg(any(test, feature = "test-support"))]
18172fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18173 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18174 count.set(count.get() + 1);
18175 });
18176}
18177
18178#[cfg(any(test, feature = "test-support"))]
18179pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18180 body: impl FnOnce() -> T,
18181) -> (T, usize) {
18182 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18183 count.set(0);
18184 let result = body();
18185 let observed = count.get();
18186 count.set(0);
18187 (result, observed)
18188 })
18189}
18190
18191fn cpp_global_field_declaration_linkage(
18192 analyzer: &CppGraphSource<'_>,
18193 candidate: &CodeUnit,
18194) -> Option<CppFieldLinkage> {
18195 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
18196 return Some(linkage);
18197 }
18198 let cpp = analyzer.cpp?;
18199 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
18200 return cpp_global_field_declaration_linkage_in_tree(
18201 analyzer,
18202 candidate,
18203 prepared.source(),
18204 prepared.tree().root_node(),
18205 );
18206 }
18207 let source = analyzer.indexed_source(candidate.source())?;
18208 let mut parser = Parser::new();
18209 if parser
18210 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18211 .is_err()
18212 {
18213 return None;
18214 }
18215 let tree = parser.parse(&source, None)?;
18216 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
18217}
18218
18219fn cpp_global_field_declaration_linkage_in_tree(
18220 analyzer: &CppGraphSource<'_>,
18221 candidate: &CodeUnit,
18222 source: &str,
18223 root: Node<'_>,
18224) -> Option<CppFieldLinkage> {
18225 analyzer.ranges(candidate).iter().find_map(|range| {
18226 node_for_exact_range(root, range)
18227 .and_then(enclosing_cpp_field_declaration)
18228 .map(|declaration| {
18229 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
18231 })
18232 })
18233}
18234
18235fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
18236 loop {
18237 if matches!(node.kind(), "declaration" | "field_declaration") {
18238 return Some(node);
18239 }
18240 node = node.parent()?;
18241 }
18242}
18243
18244#[cfg(test)]
18245mod tests {
18246 #[test]
18247 fn issue_3089_statement_formal_at_end_of_replacement() {
18248 let parameters = vec!["handle".to_owned(), "block".to_owned()];
18249 for replacement in [
18250 "do { header_event_t* event; if ((handle)->active) block } while (0)",
18251 "do { header_event_t* event; block } while (0)",
18252 ] {
18253 assert!(
18254 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
18255 .is_some(),
18256 "{replacement}"
18257 );
18258 }
18259 }
18260 use super::*;
18261
18262 #[test]
18263 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18264 let source = "int size(void) { return sizeof(((Payload))); }\n";
18265 let mut parser = Parser::new();
18266 parser
18267 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18268 .expect("C++ grammar");
18269 let tree = parser.parse(source, None).expect("fixture tree");
18270 let start = source.find("Payload").expect("sizeof operand");
18271 let node = tree
18272 .root_node()
18273 .named_descendant_for_byte_range(start, start + "Payload".len())
18274 .expect("focused operand");
18275 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18276 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18277
18278 assert_eq!(node.kind(), "identifier");
18279 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18280 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18281 }
18282
18283 fn parse_cpp(source: &str) -> Tree {
18284 let mut parser = Parser::new();
18285 parser
18286 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18287 .expect("C++ grammar");
18288 parser.parse(source, None).expect("fixture tree")
18289 }
18290
18291 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18292 let start = source.find(needle).expect("fixture needle");
18293 tree.root_node()
18294 .named_descendant_for_byte_range(start, start + needle.len())
18295 .expect("node at needle")
18296 }
18297
18298 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18299 let mut parser = Parser::new();
18300 parser
18301 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18302 .expect("C++ grammar");
18303 let tree = parser.parse(source, None).expect("fixture tree");
18304 PreparedSyntaxTree::new(
18305 PreparedSyntaxSource::Exact(Arc::from(source)),
18306 tree,
18307 compute_line_starts(source),
18308 LanguageDialect::Standard(Language::Cpp),
18309 PreparedSourceOrigin::Disk,
18310 None,
18311 )
18312 }
18313
18314 fn unresolved_include_before(source: &str, reference: &str) -> bool {
18315 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18316 let prepared = prepared_cpp(source);
18317 let facts = collect_structured_include_facts(&prepared);
18318 let include_targets = IncludeTargetIndex::build([&file]);
18319 has_unresolved_include_visible_before_in_prepared(
18320 &file,
18321 &prepared,
18322 &include_targets,
18323 &facts,
18324 source.find(reference).expect("reference fixture"),
18325 )
18326 }
18327
18328 #[test]
18329 fn unresolved_include_before_reference_is_visible() {
18330 let source = "#include \"missing.h\"\nint use = Missing;\n";
18331 assert!(unresolved_include_before(source, "Missing"));
18332 }
18333
18334 #[test]
18335 fn unresolved_include_after_reference_is_not_visible() {
18336 let source = "int use = Missing;\n#include \"missing.h\"\n";
18337 assert!(!unresolved_include_before(source, "Missing"));
18338 }
18339
18340 #[test]
18341 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18342 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18343 assert!(!unresolved_include_before(source, "Missing"));
18344 }
18345
18346 #[test]
18347 fn unresolved_include_in_current_branch_is_visible() {
18348 let source =
18349 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18350 assert!(unresolved_include_before(source, "Missing"));
18351 }
18352
18353 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18358namespace matchers {
18359 namespace detail {
18360 class API [[nodiscard]] First {
18361 public:
18362 int value() const { return count_ + 1; }
18363 private:
18364 int count_;
18365 };
18366 class API [[nodiscard]] Second {
18367 public:
18368 int value() const { return count_ + 2; }
18369 private:
18370 int count_;
18371 };
18372 } // namespace detail
18373
18374 template <typename T>
18375 void tail_function(MatcherBase<T> const& value);
18376
18377 class TailClass {};
18378} // namespace matchers
18379} // namespace app
18380
18381struct AfterAll {};
18382"#;
18383
18384 #[test]
18385 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18386 let source = STOLEN_BRACE_CASCADE;
18387 let tree = parse_cpp(source);
18388 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18389
18390 let tail_class = named_node_at(&tree, source, "TailClass");
18391 assert!(
18392 !has_ancestor_kind(tail_class, "namespace_definition"),
18393 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18394 );
18395 let displaced = named_node_at(&tree, source, "Second");
18396 assert_eq!(
18397 enclosing_namespace_components(displaced, source),
18398 Some(vec!["app".to_string(), "matchers".to_string()]),
18399 "the fixture must displace the second class out of detail"
18400 );
18401
18402 let components = |needle: &str| {
18403 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18404 };
18405 assert_eq!(components("First"), ["app", "matchers", "detail"]);
18406 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18407 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18408 assert_eq!(components("tail_function"), ["app", "matchers"]);
18409 assert_eq!(components("TailClass"), ["app", "matchers"]);
18410 assert!(components("AfterAll").is_empty());
18411 }
18412
18413 #[test]
18414 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18415 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18416 let tree = parse_cpp(clean);
18417 assert!(!tree.root_node().has_error());
18418 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18419
18420 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18423 let tree = parse_cpp(damaged);
18424 assert!(tree.root_node().has_error());
18425 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18426 assert_eq!(
18427 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18428 ["a"]
18429 );
18430 }
18431
18432 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18439
18440 class Target {
18441 int value_;
18442 };
18443
18444 template <typename T>
18445 class Holder {
18446 public:
18447 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18448
18449#define HOLDER_DEFINE_OP( id, op ) \
18450 template <typename U> \
18451 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
18452 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
18453 return Target{}; \
18454 }
18455
18456 HOLDER_DEFINE_OP( equal, == )
18457#undef HOLDER_DEFINE_OP
18458 T m_lhs;
18459 };
18460
18461 class Tail {};
18462}
18463"#;
18464
18465 #[test]
18466 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18467 let source = COLLAPSED_NAMESPACE_HEAD;
18468 let tree = parse_cpp(source);
18469 let target = named_node_at(&tree, source, "class Target");
18470
18471 assert!(
18472 !has_ancestor_kind(target, "namespace_definition"),
18473 "the fixture must reproduce the collapse: the class has no namespace ancestor"
18474 );
18475 let head = target.parent().expect("the collapsed namespace envelope");
18476 assert_eq!(
18477 head.kind(),
18478 "ERROR",
18479 "the fixture must keep the namespace head in an ERROR node"
18480 );
18481
18482 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18483 assert_eq!(
18484 index.enclosing_namespace_components(target, source),
18485 ["app"]
18486 );
18487 assert_eq!(
18488 index.enclosing_namespace_levels(target, source),
18489 vec![(
18490 "app".to_string(),
18491 Range {
18492 start_byte: 0,
18493 end_byte: source.len() - 1,
18494 start_line: 1,
18495 end_line: 25,
18496 },
18497 )],
18498 "the recovered namespace owns its full construct, not its first member or just its header"
18499 );
18500 }
18501
18502 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
18511
18512namespace app {
18513namespace detail {
18514
18515bool check(bool active) {
18516 APP_IF_ACTIVE
18517 return true;
18518 }
18519 return false;
18520}
18521
18522class Tail {};
18523
18524} // namespace detail
18525} // namespace app
18526"#;
18527
18528 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
18538namespace compile_time {
18539namespace number_parsing {
18540consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
18541 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
18542 auto digit = [](uint8_t c) -> uint32_t {
18543 }
18544 }
18545 if (a) {
18546 while (a) {
18547 }
18548 }
18549 switch (c) {
18550 case '{': {
18551 }
18552 }
18553 }
18554 while (a) {
18555 cursor += field.second;
18556 if (a) {
18557 case '{': {
18558 if (a) {
18559 }
18560 }
18561 case 'n': {
18562 if (a) {
18563 }
18564 }
18565 }
18566 }
18567 if (a) {
18568 simdjson_consteval_error("Expected '}'");
18569 }
18570}
18571}
18572}
18573"#;
18574
18575 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
18577 let mut stack = vec![root];
18578 let mut counts = (0, 0);
18579 while let Some(node) = stack.pop() {
18580 match (node.kind(), node.is_missing()) {
18581 ("{", false) => counts.0 += 1,
18582 ("}", false) => counts.1 += 1,
18583 _ => {}
18584 }
18585 let mut cursor = node.walk();
18586 stack.extend(node.children(&mut cursor));
18587 }
18588 counts
18589 }
18590
18591 fn namespace_open_byte(source: &str, head: &str) -> usize {
18593 let at = source.find(head).expect("fixture namespace head");
18594 at + head.len() - 1
18595 }
18596
18597 #[test]
18598 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
18599 let source = MACRO_OPENED_SCOPE;
18600 let tree = parse_cpp(source);
18601 let root = tree.root_node();
18602 assert_eq!(
18603 real_brace_token_counts(root),
18604 (4, 5),
18605 "the fixture must reproduce the loss: one scope the source opens \
18606 has no `{{` token, so the stream holds an extra real close"
18607 );
18608
18609 let index = OrphanedNamespaceScopeIndex::build(root, source);
18610 for head in ["namespace app {", "namespace detail {"] {
18611 assert_eq!(
18612 index.matching_close_brace(namespace_open_byte(source, head)),
18613 None,
18614 "`{head}` must not claim a close: the stack had to give it one that \
18615 belongs to an inner scope (line 15 for `app`, line 11 for \
18616 `detail`) once the macro's `{{` went missing"
18617 );
18618 }
18619 }
18620
18621 #[test]
18622 fn orphaned_namespace_scope_index_pairs_nothing_on_the_reflection_recovery() {
18623 let source = REFLECTION_CASE_LABEL_RECOVERY;
18624 let tree = parse_cpp(source);
18625 let root = tree.root_node();
18626 let mut stack = vec![root];
18627 let mut mis_lexed_opens = Vec::new();
18628 while let Some(node) = stack.pop() {
18629 if node.kind() == "character"
18630 && &source[node.start_byte()..node.end_byte()] == "{"
18631 && node
18632 .parent()
18633 .is_some_and(|parent| parent.kind() != "char_literal")
18634 {
18635 mis_lexed_opens.push(node.start_position().row + 1);
18636 }
18637 let mut cursor = node.walk();
18638 stack.extend(node.children(&mut cursor));
18639 }
18640 assert!(
18641 !mis_lexed_opens.is_empty(),
18642 "the fixture must reproduce the mis-lex: a scope-opening `{{` \
18643 consumed as a `character` of a char literal recovery never closed"
18644 );
18645 assert_eq!(
18646 real_brace_token_counts(root),
18647 (17, 18),
18648 "which is what leaves the brace token stream short one open: \
18649 mis-lexed at {mis_lexed_opens:?}"
18650 );
18651
18652 let index = OrphanedNamespaceScopeIndex::build(root, source);
18653 for head in [
18654 "namespace simdjson {",
18655 "namespace compile_time {",
18656 "namespace number_parsing {",
18657 ] {
18658 assert_eq!(
18659 index.matching_close_brace(namespace_open_byte(source, head)),
18660 None,
18661 "`{head}` must not claim a close: every close after the lost open \
18662 belongs one level in, so the stack paired the three heads on \
18663 lines 1..3 with the closes on lines 33..35 instead of 34..36"
18664 );
18665 }
18666 }
18667
18668 #[test]
18669 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18670 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18671 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18672 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18673 assert!(indexed_namespace_path_is_recoverable(
18674 &["cache".to_string()],
18675 &indexed,
18676 1,
18677 ));
18678 }
18679
18680 #[test]
18681 fn sort_lookup_units_totally_orders_every_identity_field() {
18682 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18683 let base = CodeUnit::with_signature(
18684 file.clone(),
18685 CodeUnitType::Function,
18686 "scope",
18687 "value",
18688 Some("()".to_string()),
18689 false,
18690 );
18691 let different_kind = CodeUnit::with_signature(
18692 file.clone(),
18693 CodeUnitType::Field,
18694 "scope",
18695 "value",
18696 Some("()".to_string()),
18697 false,
18698 );
18699 let synthetic = base.with_synthetic(true);
18700
18701 let interner = segment_interner();
18702 let mut member_fq = FqName::new();
18703 member_fq.push(interner.intern("scope", SegmentKind::Package));
18704 member_fq.push(interner.intern("value", SegmentKind::Member));
18705 let different_package_boundary = CodeUnit::from_fq(
18706 file.clone(),
18707 CodeUnitType::Function,
18708 member_fq,
18709 0,
18710 Some("()".to_string()),
18711 false,
18712 );
18713
18714 let mut unknown_fq = FqName::new();
18715 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18716 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18717 let different_segment_kind = CodeUnit::from_fq(
18718 file,
18719 CodeUnitType::Function,
18720 unknown_fq,
18721 1,
18722 Some("()".to_string()),
18723 false,
18724 );
18725
18726 let input = vec![
18727 base,
18728 different_kind,
18729 synthetic,
18730 different_package_boundary,
18731 different_segment_kind,
18732 ];
18733 let mut expected = input.clone();
18734 sort_lookup_units(&mut expected);
18735 assert!(expected.windows(2).all(|pair| {
18736 let mut ordered = pair.to_vec();
18737 sort_lookup_units(&mut ordered);
18738 ordered == pair && pair[0] != pair[1]
18739 }));
18740
18741 let mut reversed = input.clone();
18742 reversed.reverse();
18743 sort_lookup_units(&mut reversed);
18744 assert_eq!(reversed, expected);
18745
18746 let mut rotated = input;
18747 rotated.rotate_left(2);
18748 sort_lookup_units(&mut rotated);
18749 assert_eq!(rotated, expected);
18750 }
18751
18752 #[test]
18753 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18754 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";
18755 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18756 let parse = |source: &str| {
18757 let mut parser = Parser::new();
18758 parser
18759 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18760 .expect("C++ grammar");
18761 parser.parse(source, None).expect("fixture tree")
18762 };
18763
18764 let tree = parse(damaged);
18765 let root = tree.root_node();
18766 let target = damaged.find("target").expect("target byte");
18767 let declaration = root
18768 .descendant_for_byte_range(target, target + "target".len())
18769 .and_then(|mut node| {
18770 loop {
18771 if node.kind() == "declaration" {
18772 break Some(node);
18773 }
18774 node = node.parent()?;
18775 }
18776 })
18777 .expect("declaration after the displaced terminator");
18778 let conditional = declaration
18779 .parent()
18780 .filter(|node| node.kind() == "preproc_ifdef")
18781 .expect("damaged inner conditional");
18782 let outer = conditional
18783 .parent()
18784 .filter(|node| node.kind() == "preproc_ifdef")
18785 .expect("ordinary outer include guard");
18786 let terminator = cpp_displaced_preprocessor_terminator(conditional)
18787 .expect("structured displaced #endif");
18788 assert_eq!(node_text(terminator, damaged), "#endif");
18789 assert!(terminator.end_byte() <= declaration.start_byte());
18790 assert!(!preprocessor_conditional_contains_descendant(
18791 conditional,
18792 declaration
18793 ));
18794 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18795 assert!(preprocessor_conditional_contains_descendant(
18796 outer,
18797 declaration
18798 ));
18799
18800 let tree = parse(guarded);
18801 let conditional = tree
18802 .root_node()
18803 .named_child(0)
18804 .filter(|node| node.kind() == "preproc_ifdef")
18805 .expect("ordinary conditional");
18806 let declaration = conditional
18807 .named_children(&mut conditional.walk())
18808 .find(|node| node.kind() == "declaration")
18809 .expect("guarded declaration");
18810 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18811 assert!(preprocessor_conditional_contains_descendant(
18812 conditional,
18813 declaration
18814 ));
18815
18816 let damaged_alternative = format!(
18817 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18818 "UNUSED(value)\n".repeat(64)
18819 );
18820 let tree = parse(&damaged_alternative);
18821 let conditional = tree
18822 .root_node()
18823 .named_child(0)
18824 .filter(|node| node.kind() == "preproc_ifdef")
18825 .expect("outer conditional with an alternative");
18826 assert!(conditional.has_error());
18827 assert!(conditional.child_by_field_name("alternative").is_some());
18828 assert!(
18829 conditional
18830 .child(conditional.child_count() - 1)
18831 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18832 );
18833 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18834
18835 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";
18836 let tree = parse(split_declaration);
18837 let root = tree.root_node();
18838 let conditional = root
18839 .named_children(&mut root.walk())
18840 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18841 .expect("split declaration conditional");
18842 let target = split_declaration
18843 .find("static int target")
18844 .expect("target byte");
18845 let boundary =
18846 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18847 assert!(boundary.end_byte <= target, "{boundary:?}");
18848 assert_eq!(boundary.end_line, 9, "{boundary:?}");
18849 let target_node = root
18850 .descendant_for_byte_range(target, target + "static".len())
18851 .expect("target node");
18852 assert!(!preprocessor_conditional_contains_descendant(
18853 conditional,
18854 target_node
18855 ));
18856 }
18857
18858 #[test]
18859 fn fragmented_reference_guard_is_recovered() {
18860 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";
18861 let mut parser = Parser::new();
18862 parser
18863 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18864 .expect("C++ grammar");
18865 let tree = parser.parse(source, None).expect("fixture tree");
18866 let start = source.rfind("helper").expect("reference byte");
18867 let node = tree
18868 .root_node()
18869 .descendant_for_byte_range(start, start + "helper".len())
18870 .expect("reference node");
18871 let mut expected = HashSet::default();
18872 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18873 vec![
18874 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18875 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18876 ],
18877 )));
18878 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18879 }
18880
18881 #[test]
18882 fn expression_defined_and_ifndef_guards_are_incompatible() {
18883 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18884 let mut parser = Parser::new();
18885 parser
18886 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18887 .expect("C++ grammar");
18888 let tree = parser.parse(source, None).expect("fixture tree");
18889 let root = tree.root_node();
18890 let selected_start = source.find("selected").expect("selected declaration");
18891 let rejected_start = source.find("rejected").expect("rejected declaration");
18892 let selected = root
18893 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18894 .expect("selected node");
18895 let rejected = root
18896 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18897 .expect("rejected node");
18898 let selected_guards =
18899 preprocessor_guard_environment(selected, source).expect("selected guards");
18900 let rejected_guards =
18901 preprocessor_guard_environment(rejected, source).expect("rejected guards");
18902
18903 assert!(
18904 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18905 "opposite spellings of one macro guard must contradict"
18906 );
18907 }
18908
18909 #[test]
18910 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18911 let source = r#"#ifdef _WIN32
18912#if defined(__cplusplus)
18913extern "C"
18914#endif
18915int platform_api(void);
18916#endif
18917
18918#ifdef _WIN32
18919static int entropy_target(void) { return 0; }
18920#else
18921#ifdef HAVE_COMMON_RANDOM
18922static int other_target(void) { return 0; }
18923#elif defined(HAVE_GETENTROPY)
18924static int entropy_target(void) { return 1; }
18925static int use_entropy(void) { return entropy_target(); }
18926#endif
18927#endif
18928"#;
18929 let mut parser = Parser::new();
18930 parser
18931 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18932 .expect("C++ grammar");
18933 let tree = parser.parse(source, None).expect("fixture tree");
18934 let start = source.rfind("entropy_target()").expect("reference");
18935 let node = tree
18936 .root_node()
18937 .descendant_for_byte_range(start, start + "entropy_target".len())
18938 .expect("reference node");
18939 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18940 assert!(
18941 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18942 "{guards:#?}"
18943 );
18944 assert!(
18945 guards.contains(&PreprocessorGuard::Undefined(
18946 "HAVE_COMMON_RANDOM".to_string()
18947 )),
18948 "{guards:#?}"
18949 );
18950 assert!(
18951 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18952 "{guards:#?}"
18953 );
18954 assert!(
18955 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18956 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18957 );
18958 }
18959
18960 #[test]
18961 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18962 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";
18963 let mut parser = Parser::new();
18964 parser
18965 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18966 .expect("C++ grammar");
18967 let tree = parser.parse(source, None).expect("fixture tree");
18968 let root = tree.root_node();
18969 let node_at = |text: &str, start: usize| {
18970 root.descendant_for_byte_range(start, start + text.len())
18971 .expect("token node")
18972 };
18973
18974 let key_start = source.find("case KEY").expect("case label") + "case ".len();
18975 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18976 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18977 assert!(!is_ordinary_macro_reference_node(node_at(
18978 "ENABLE_KEYS",
18979 guard_start,
18980 )));
18981 }
18982
18983 #[test]
18984 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18985 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";
18986 let mut parser = Parser::new();
18987 parser
18988 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18989 .expect("C++ grammar");
18990 let tree = parser.parse(source, None).expect("fixture tree");
18991 let root = tree.root_node();
18992 let definition_start = source.find("target(void)").expect("definition");
18993 let reference_start = source.rfind("target()").expect("reference");
18994 let definition = root
18995 .descendant_for_byte_range(definition_start, definition_start + "target".len())
18996 .expect("definition node");
18997 let reference = root
18998 .descendant_for_byte_range(reference_start, reference_start + "target".len())
18999 .expect("reference node");
19000 let required =
19001 preprocessor_guard_environment(definition, source).expect("definition guard");
19002 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
19003 assert!(guard_requirements_hold_at_reference(
19004 &required,
19005 Some(&active)
19006 ));
19007 }
19008
19009 #[test]
19010 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
19011 let source = "g_autoptr(FuChunkArray) self = make_array();";
19012 let mut parser = Parser::new();
19013 parser
19014 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19015 .expect("C++ grammar");
19016 let tree = parser.parse(source, None).expect("fixture tree");
19017 let statement = tree.root_node().named_child(0).expect("statement");
19018 let binding =
19019 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
19020 assert_eq!(binding.name, "self");
19021 assert_eq!(binding.type_name, "FuChunkArray");
19022 assert_eq!(binding.pointer_depth, 1);
19023
19024 let near_miss = "holder(FuChunkArray) self = make_array();";
19025 let tree = parser.parse(near_miss, None).expect("near-miss tree");
19026 let statement = tree.root_node().named_child(0).expect("statement");
19027 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
19028 }
19029
19030 #[test]
19031 fn boolean_guard_normalization_proves_equivalence_and_implication() {
19032 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
19033 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
19034 let negated_windows_branch =
19035 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
19036 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
19037 assert_eq!(negated_windows_branch, portable);
19038
19039 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
19040 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
19041 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
19042 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
19043 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
19044 assert!(fallback_branch.implies(&fallback_declaration));
19045 assert!(
19046 BooleanGuardExpression::Truthy("FEATURE".to_string())
19047 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
19048 );
19049 assert!(
19050 BooleanGuardExpression::Undefined("FEATURE".to_string())
19051 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
19052 );
19053 assert!(
19054 !BooleanGuardExpression::Defined("FEATURE".to_string())
19055 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
19056 );
19057 assert!(!fallback_declaration.implies(&fallback_branch));
19058 }
19059
19060 #[test]
19061 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
19062 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";
19063 let mut parser = Parser::new();
19064 parser
19065 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19066 .expect("C++ grammar");
19067 let tree = parser.parse(source, None).expect("fixture tree");
19068 let root = tree.root_node();
19069 let call = |marker: &str| {
19070 let start = source.find(marker).expect("call marker");
19071 let mut node = root
19072 .descendant_for_byte_range(start, start + "helper".len())
19073 .expect("call name node");
19074 loop {
19075 if node.kind() == "call_expression" {
19076 break node;
19077 }
19078 node = node.parent().expect("call expression ancestor");
19079 }
19080 };
19081 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
19082 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
19083 let keyword_call = call("helper(NULL, template); /* bound */");
19084 let keyword_arguments = keyword_call
19085 .child_by_field_name("arguments")
19086 .expect("keyword argument list");
19087 assert_eq!(
19088 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
19089 1
19090 );
19091 assert_eq!(
19092 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
19093 0
19094 );
19095
19096 let unbound_call = call("helper(NULL, template); /* unbound */");
19097 let unbound_arguments = unbound_call
19098 .child_by_field_name("arguments")
19099 .expect("unbound argument list");
19100 assert_eq!(
19101 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
19102 0
19103 );
19104 }
19105
19106 #[test]
19107 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
19108 let source = r#"#define MAKE(type) type *value
19109MAKE(int *);
19110typedef struct Item Item;
19111struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
19112void gen_op_movl(void *s, int first, int second) { }
19113const char *strZ(const char *value) { return value; }
19114int body(void *s) {
19115 MAKE(int *);
19116 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
19117 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
19118 execvp(strZ(value), UNCONSTIFY(char **, args));
19119}
19120 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
19121 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
19122int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
19123 if (d->units) {
19124 switch (target->nodetype) {
19125 case 1:
19126 case 2:
19127 break;
19128 default:
19129 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
19130 }
19131 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
19132 lysdict_remove(ctx, ((struct Item *)target)->units);
19133 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
19134 }
19135 return 0;
19136 }
19137STATIC EFI_STATUS Encode () { return 0; }
19138"#;
19139 let tree = parse_cpp(source);
19140 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
19141 let top_macro = tree
19142 .root_node()
19143 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
19144 .expect("top macro node");
19145 let body_macro_start = source
19146 .match_indices("MAKE(int *);")
19147 .nth(1)
19148 .expect("body macro")
19149 .0;
19150 let body_macro = tree
19151 .root_node()
19152 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
19153 .expect("body macro node");
19154 let function_call_start = source
19155 .find("gen_op_movl(s, offsetof(CPUX86State")
19156 .expect("function call");
19157 let function_call = tree
19158 .root_node()
19159 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
19160 .expect("function call node");
19161 let strz_start = source.find("strZ(value)").expect("nested function call");
19162 let strz = tree
19163 .root_node()
19164 .named_descendant_for_byte_range(strz_start, strz_start + 4)
19165 .expect("nested function call node");
19166 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
19167 let recovered_call = tree
19168 .root_node()
19169 .named_descendant_for_byte_range(
19170 recovered_call_start,
19171 recovered_call_start + "lysdict_remove".len(),
19172 )
19173 .expect("recovered call node");
19174 let binder_start = source.find("Encode").expect("binder");
19175 let binder = tree
19176 .root_node()
19177 .named_descendant_for_byte_range(binder_start, binder_start + 6)
19178 .expect("binder node");
19179
19180 assert!(recovered_c_function_declarator_invocation(top_macro));
19181 assert!(recovered_c_function_declarator_invocation(body_macro));
19182 assert!(recovered_c_function_declarator_invocation(function_call));
19183 assert!(recovered_c_function_declarator_invocation(strz));
19184 assert!(recovered_c_function_declarator_invocation(recovered_call));
19185 assert!(!recovered_c_function_declarator_invocation(binder));
19186 }
19187
19188 #[test]
19189 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
19190 let source = r#"typedef int krb5_context;
19191int helper(int first, int second) { return first + second; }
19192static krb5_context ctx;
19193int main(int argc, char **argv) {
19194 int ccinitial;
19195 const char *collection_name, *typename;
19196 typename = helper(ctx, ccinitial);
19197 return 0;
19198}
19199"#;
19200 let tree = parse_cpp(source);
19201 let ctx = tree
19202 .root_node()
19203 .descendant_for_byte_range(
19204 source.find("ctx, ccinitial").expect("ctx argument"),
19205 source.find("ctx, ccinitial").expect("ctx argument") + 3,
19206 )
19207 .expect("ctx node");
19208 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
19209 let ccinitial = tree
19210 .root_node()
19211 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
19212 .expect("sibling node");
19213 let typename = named_node_at(&tree, source, "typename = helper");
19214 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
19215
19216 assert_eq!(ctx.kind(), "identifier");
19217 assert!(recovered_c_parenthesized_declarator_reference(ctx));
19218 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
19219 assert!(!recovered_c_parenthesized_declarator_reference(typename));
19220 assert!(!recovered_c_parenthesized_declarator_reference(helper));
19221 }
19222
19223 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
19224 let mut parser = Parser::new();
19225 parser
19226 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19227 .expect("C++ grammar");
19228 let tree = parser.parse(source, None).expect("C++ fixture tree");
19229 let mut stack = vec![tree.root_node()];
19230 while let Some(node) = stack.pop() {
19231 if node.kind() == "enum_specifier" {
19232 return flattened_macro_namespace_components(node, source);
19233 }
19234 let mut cursor = node.walk();
19235 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
19236 stack.extend(children.into_iter().rev());
19237 }
19238 None
19239 }
19240
19241 #[test]
19242 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
19243 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19244namespace detail
19245{
19246enum class value_t { null };
19247}
19248NLOHMANN_JSON_NAMESPACE_END
19249NLOHMANN_JSON_NAMESPACE_BEGIN
19250namespace next
19251{
19252struct next_type {};
19253}
19254NLOHMANN_JSON_NAMESPACE_END
19255"#;
19256 assert_eq!(
19257 first_enum_flattened_namespace(complete),
19258 Some(vec!["detail".to_string()])
19259 );
19260
19261 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
19262 assert_eq!(
19263 first_enum_flattened_namespace(&stale_end),
19264 Some(vec!["detail".to_string()]),
19265 "a stale end marker before the begin marker must not replace the intended namespace"
19266 );
19267
19268 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19269namespace detail
19270{
19271enum class value_t { null };
19272}
19273struct next_type {};
19274"#;
19275 assert_eq!(first_enum_flattened_namespace(incomplete), None);
19276 }
19277}
19278
19279#[cfg(test)]
19295mod lookup_order_properties {
19296 use super::*;
19297 use proptest::prelude::*;
19298
19299 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
19303 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
19304 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
19308 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
19309 const KINDS: [CodeUnitType; 6] = [
19310 CodeUnitType::Class,
19311 CodeUnitType::Function,
19312 CodeUnitType::Field,
19313 CodeUnitType::Module,
19314 CodeUnitType::Macro,
19315 CodeUnitType::FileScope,
19316 ];
19317
19318 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19321 enum ProbedOrder {
19322 Before,
19323 Tied,
19324 After,
19325 Contradictory,
19328 }
19329
19330 impl ProbedOrder {
19331 fn mirror(self) -> Self {
19332 match self {
19333 ProbedOrder::Before => ProbedOrder::After,
19334 ProbedOrder::After => ProbedOrder::Before,
19335 other => other,
19336 }
19337 }
19338
19339 fn signum(self) -> i8 {
19341 match self {
19342 ProbedOrder::Before => -1,
19343 ProbedOrder::Tied => 0,
19344 ProbedOrder::After => 1,
19345 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
19346 }
19347 }
19348 }
19349
19350 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
19358 if left == right {
19359 return ProbedOrder::Tied;
19362 }
19363 let mut forward = vec![left.clone(), right.clone()];
19364 sort_lookup_units(&mut forward);
19365 let mut backward = vec![right.clone(), left.clone()];
19366 sort_lookup_units(&mut backward);
19367 let left_first = backward[0] == *left;
19368 let right_first = forward[0] == *right;
19369 match (left_first, right_first) {
19370 (true, true) => ProbedOrder::Contradictory,
19371 (true, false) => ProbedOrder::Before,
19372 (false, true) => ProbedOrder::After,
19373 (false, false) => ProbedOrder::Tied,
19374 }
19375 }
19376
19377 fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
19380 let interner = segment_interner();
19381 unit.fq()
19382 .segments()
19383 .iter()
19384 .map(|&id| {
19385 let (text, kind) = interner.resolve(id);
19386 (kind.name(), text)
19387 })
19388 .collect()
19389 }
19390
19391 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
19392 (
19393 0..ROOT_NAMES.len(),
19394 0..REL_PATHS.len(),
19395 0..KINDS.len(),
19396 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
19397 0..3usize,
19398 0..SIGNATURES.len(),
19399 any::<bool>(),
19400 )
19401 .prop_map(
19402 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
19403 let source = ProjectFile::new(
19404 std::env::temp_dir().join(ROOT_NAMES[root]),
19405 REL_PATHS[rel_path],
19406 );
19407 let interner = segment_interner();
19408 let mut fq = FqName::new();
19409 for (atom, segment_kind) in &segments {
19410 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
19411 }
19412 let package_segment_count = package_prefix % fq.len();
19414 CodeUnit::from_fq(
19415 source,
19416 KINDS[kind],
19417 fq,
19418 package_segment_count,
19419 SIGNATURES[signature].map(str::to_string),
19420 synthetic,
19421 )
19422 },
19423 )
19424 }
19425
19426 proptest! {
19427 #![proptest_config(ProptestConfig::with_cases(256))]
19428
19429 #[test]
19432 fn lookup_order_is_reflexive_and_dual(
19433 left in code_unit_strategy(),
19434 right in code_unit_strategy(),
19435 ) {
19436 prop_assert_eq!(
19437 probe_order(&left, &left),
19438 ProbedOrder::Tied,
19439 "a unit must tie with itself: {:?}",
19440 left
19441 );
19442 let forward = probe_order(&left, &right);
19443 prop_assert_ne!(
19444 forward,
19445 ProbedOrder::Contradictory,
19446 "comparator put each of these strictly first: left={:?} right={:?}",
19447 left,
19448 right
19449 );
19450 prop_assert_eq!(
19451 probe_order(&right, &left),
19452 forward.mirror(),
19453 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
19454 left,
19455 right
19456 );
19457 }
19458
19459 #[test]
19461 fn lookup_order_is_transitive(
19462 a in code_unit_strategy(),
19463 b in code_unit_strategy(),
19464 c in code_unit_strategy(),
19465 ) {
19466 let ab = probe_order(&a, &b);
19467 let bc = probe_order(&b, &c);
19468 let ac = probe_order(&a, &c);
19469 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
19470 prop_assert_ne!(
19471 probed,
19472 ProbedOrder::Contradictory,
19473 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
19474 pair,
19475 a,
19476 b,
19477 c
19478 );
19479 }
19480 if ab.signum() <= 0 && bc.signum() <= 0 {
19481 prop_assert!(
19482 ac.signum() <= 0,
19483 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
19484 a={:?} b={:?} c={:?}",
19485 ab,
19486 bc,
19487 ac,
19488 a,
19489 b,
19490 c
19491 );
19492 }
19493 }
19494
19495 #[test]
19498 fn lookup_order_separates_distinct_identities(
19499 left in code_unit_strategy(),
19500 right in code_unit_strategy(),
19501 ) {
19502 if probe_order(&left, &right) == ProbedOrder::Tied {
19503 prop_assert_eq!(
19504 &left,
19505 &right,
19506 "distinct identities tied, so their order is whatever order they \
19507 arrived in: left_segments={:?} right_segments={:?}",
19508 fq_segments(&left),
19509 fq_segments(&right)
19510 );
19511 }
19512 }
19513
19514 #[test]
19517 fn lookup_sort_is_permutation_invariant(
19518 units in prop::collection::vec(code_unit_strategy(), 1..=8),
19519 ) {
19520 let mut sorted = units.clone();
19521 sort_lookup_units(&mut sorted);
19522 for rotation in 0..units.len() {
19523 for reversed in [false, true] {
19524 let mut permuted = units.clone();
19525 permuted.rotate_left(rotation);
19526 if reversed {
19527 permuted.reverse();
19528 }
19529 sort_lookup_units(&mut permuted);
19530 prop_assert_eq!(
19531 &permuted,
19532 &sorted,
19533 "sorting a permutation gave a different list \
19534 (rotation={}, reversed={}): input={:?}",
19535 rotation,
19536 reversed,
19537 units
19538 );
19539 }
19540 }
19541 }
19542 }
19543}