1use crate::ConfiguredModuleResolution;
4use crate::configured_modules::ConfiguredModuleSource;
5use crate::semantic;
6use crate::{SourceCallBinding, SourceCallFact, SourceSemanticFacts};
7use blake3::Hasher;
8use projectatlas_core::graph::{
9 CanonicalResolutionKey, GraphContractError, GraphIdentityText, GraphLimits, GraphRelationKind,
10 ProjectInstanceId, ResolutionKeyDomain, SourceSpan,
11};
12use projectatlas_core::language::{LANGUAGE_CAPABILITIES, SemanticProviderOwner};
13use projectatlas_core::symbols::{RelationKind, SymbolGraph, SymbolKind};
14use std::collections::{BTreeMap, HashSet};
15use std::error::Error;
16use std::fmt;
17use std::sync::Arc;
18
19pub const MAX_RESOLUTION_KEYS_PER_FACT: usize = 64;
21pub const SEMANTIC_RESOLUTION_CONTRACT_VERSION: u32 = 23;
23const ECMA_FILE_SCOPE: &str = "file:";
25const ECMA_MODULE_SCOPE: &str = "module:";
27
28#[must_use]
34pub fn semantic_resolution_contract_digest() -> String {
35 semantic_resolution_contract_digest_for_identifier_profile(unicode_ident::UNICODE_VERSION)
36}
37
38fn semantic_resolution_contract_digest_for_identifier_profile(
40 identifier_version: (u8, u8, u8),
41) -> String {
42 let mut hasher = Hasher::new();
43 hasher.update(&SEMANTIC_RESOLUTION_CONTRACT_VERSION.to_le_bytes());
44 hasher.update(&(MAX_RESOLUTION_KEYS_PER_FACT as u64).to_le_bytes());
45 let mut providers = LANGUAGE_CAPABILITIES
46 .iter()
47 .filter_map(|capability| capability.effective_semantic_provider())
48 .collect::<Vec<_>>();
49 providers.sort_by_key(|provider| provider.as_str());
50 providers.dedup();
51 for provider in providers {
52 hash_contract_value(&mut hasher, provider.as_str());
53 hash_contract_value(
54 &mut hasher,
55 provider.resolution_family().unwrap_or("unavailable"),
56 );
57 if provider == SemanticProviderOwner::Python {
58 hash_contract_value(&mut hasher, "python_xid_unicode_version");
59 let (major, minor, patch) = identifier_version;
60 hasher.update(&[major, minor, patch]);
61 }
62 if semantic::supports_source_dependencies(provider) {
63 hash_relation_contract(
64 &mut hasher,
65 RelationKind::Imports,
66 ResolutionKeyDomain::Module,
67 );
68 hash_relation_contract(
69 &mut hasher,
70 RelationKind::Calls,
71 ResolutionKeyDomain::Declaration,
72 );
73 }
74 if semantic::supports_package_dependencies(provider) {
75 hash_relation_contract(
76 &mut hasher,
77 RelationKind::DependsOn,
78 ResolutionKeyDomain::Package,
79 );
80 }
81 }
82 for outcome in ["resolved", "ambiguous", "unresolved", "external"] {
83 hash_contract_value(&mut hasher, outcome);
84 }
85 hasher.finalize().to_hex().to_string()
86}
87
88fn hash_relation_contract(
90 hasher: &mut Hasher,
91 relation: RelationKind,
92 domain: ResolutionKeyDomain,
93) {
94 hash_contract_value(hasher, &relation.to_string());
95 hash_contract_value(hasher, domain.as_str());
96}
97
98fn hash_contract_value(hasher: &mut Hasher, value: &str) {
100 hasher.update(&(value.len() as u64).to_le_bytes());
101 hasher.update(value.as_bytes());
102}
103#[derive(Clone, Debug, Eq, PartialEq)]
105pub struct SymbolResolutionKeys {
106 symbol_index: usize,
108 keys: Vec<CanonicalResolutionKey>,
110}
111
112impl SymbolResolutionKeys {
113 #[must_use]
115 pub const fn symbol_index(&self) -> usize {
116 self.symbol_index
117 }
118
119 #[must_use]
121 pub fn keys(&self) -> &[CanonicalResolutionKey] {
122 &self.keys
123 }
124}
125
126#[derive(Clone, Debug, Eq, PartialEq)]
130pub struct ExportResolutionKeys {
131 export_index: usize,
133 keys: Vec<CanonicalResolutionKey>,
135}
136
137impl ExportResolutionKeys {
138 #[must_use]
140 pub const fn export_index(&self) -> usize {
141 self.export_index
142 }
143
144 #[must_use]
146 pub fn keys(&self) -> &[CanonicalResolutionKey] {
147 &self.keys
148 }
149}
150
151#[derive(Clone, Debug, Eq, PartialEq)]
153pub struct RelationResolutionKeys {
154 relation_index: usize,
156 keys: Vec<CanonicalResolutionKey>,
158}
159
160impl RelationResolutionKeys {
161 #[must_use]
163 pub const fn relation_index(&self) -> usize {
164 self.relation_index
165 }
166
167 #[must_use]
169 pub fn keys(&self) -> &[CanonicalResolutionKey] {
170 &self.keys
171 }
172}
173
174#[derive(Clone, Debug, Eq, PartialEq)]
176pub struct ResolutionKeyProjection {
177 source: Vec<CanonicalResolutionKey>,
179 symbols: Vec<SymbolResolutionKeys>,
181 exports: Vec<ExportResolutionKeys>,
183 relations: Vec<RelationResolutionKeys>,
185 source_calls: Option<Vec<SourceCallFact>>,
187 source_spans: BTreeMap<usize, SourceSpan>,
189 rust_toolchain_imports: Option<BTreeMap<usize, Option<ImportReference>>>,
191}
192
193#[derive(Clone, Copy, Debug, Default)]
195pub struct ResolutionProjectionContext<'a> {
196 configured_modules: Option<&'a ConfiguredModuleResolution>,
198 source_semantics: Option<&'a SourceSemanticFacts>,
200 rust_package: Option<(&'a str, &'a str)>,
202 rust_root_cohort: Option<&'a str>,
204 retained_bytes_limit: Option<u64>,
206}
207
208struct ProjectionBudget {
210 limit: Option<u64>,
212 used: u64,
214 failure: Option<(u64, u64)>,
216}
217
218impl ProjectionBudget {
219 const fn new(limit: Option<u64>) -> Self {
221 Self {
222 limit,
223 used: 0,
224 failure: None,
225 }
226 }
227
228 fn charge(&mut self, bytes: u64) -> bool {
230 if self.failure.is_some() {
231 return false;
232 }
233 let Some(limit) = self.limit else { return true };
234 let requested = self.used.saturating_add(bytes);
235 if requested > limit {
236 self.failure = Some((limit, requested));
237 return false;
238 }
239 self.used = requested;
240 true
241 }
242
243 fn check(&self) -> Result<(), ResolutionProjectionError> {
245 if let Some((limit, requested)) = self.failure {
246 return Err(ResolutionProjectionError::MemoryLimit { limit, requested });
247 }
248 Ok(())
249 }
250
251 fn reserve(&mut self, bytes: u64) -> Result<(), ResolutionProjectionError> {
253 self.charge(bytes);
254 self.check()
255 }
256
257 fn key(&mut self, key: &CanonicalResolutionKey) -> bool {
259 self.charge(
260 key.retained_heap_bytes()
261 .saturating_add((4 * std::mem::size_of::<CanonicalResolutionKey>()) as u64),
262 )
263 }
264}
265
266type ImportScopeCache = BTreeMap<String, BTreeMap<String, Vec<String>>>;
268
269struct ContractFailures {
271 budget: ProjectionBudget,
273 failures: Vec<ResolutionProjectionFactFailure>,
275 rejected_count: usize,
277 source_seen: bool,
279 symbol_seen: Vec<bool>,
281 relation_seen: Vec<bool>,
283 export_seen: Vec<bool>,
285}
286
287impl ContractFailures {
288 fn new(
290 symbol_count: usize,
291 relation_count: usize,
292 export_count: usize,
293 mut budget: ProjectionBudget,
294 ) -> Result<Self, ResolutionProjectionError> {
295 budget.reserve(
296 (std::mem::size_of::<Self>() as u64).saturating_add(
297 symbol_count
298 .saturating_add(relation_count)
299 .saturating_add(export_count)
300 .saturating_mul(std::mem::size_of::<bool>()) as u64,
301 ),
302 )?;
303 Ok(Self {
304 budget,
305 failures: Vec::new(),
306 rejected_count: 0,
307 source_seen: false,
308 symbol_seen: vec![false; symbol_count],
309 relation_seen: vec![false; relation_count],
310 export_seen: vec![false; export_count],
311 })
312 }
313
314 fn remember(&mut self, fact: ResolutionProjectionFact, error: GraphContractError) {
316 let seen = match fact {
317 ResolutionProjectionFact::Source => {
318 let seen = self.source_seen;
319 self.source_seen = true;
320 !seen
321 }
322 ResolutionProjectionFact::Symbol(index) => {
323 self.symbol_seen.get_mut(index).is_some_and(|seen| {
324 let was_seen = *seen;
325 *seen = true;
326 !was_seen
327 })
328 }
329 ResolutionProjectionFact::Export(index) => {
330 self.export_seen.get_mut(index).is_some_and(|seen| {
331 let was_seen = *seen;
332 *seen = true;
333 !was_seen
334 })
335 }
336 ResolutionProjectionFact::Relation(index) => {
337 self.relation_seen.get_mut(index).is_some_and(|seen| {
338 let was_seen = *seen;
339 *seen = true;
340 !was_seen
341 })
342 }
343 };
344 if !seen {
345 return;
346 }
347 self.rejected_count = self.rejected_count.saturating_add(1);
348 if self.failures.len() < MAX_RESOLUTION_PROJECTION_FAILURES {
349 if !self
350 .budget
351 .charge((4 * std::mem::size_of::<ResolutionProjectionFactFailure>()) as u64)
352 {
353 return;
354 }
355 self.failures
356 .push(ResolutionProjectionFactFailure { error, fact });
357 }
358 }
359}
360
361impl<'a> ResolutionProjectionContext<'a> {
362 #[must_use]
364 pub const fn with_configured_modules(
365 configured_modules: &'a ConfiguredModuleResolution,
366 ) -> Self {
367 Self {
368 configured_modules: Some(configured_modules),
369 source_semantics: None,
370 rust_package: None,
371 rust_root_cohort: None,
372 retained_bytes_limit: None,
373 }
374 }
375
376 #[must_use]
378 pub const fn with_source_semantics(mut self, semantics: &'a SourceSemanticFacts) -> Self {
379 self.source_semantics = Some(semantics);
380 self
381 }
382
383 #[must_use]
385 pub const fn with_rust_package(mut self, manifest: &'a str, root: &'a str) -> Self {
386 self.rust_package = Some((manifest, root));
387 self
388 }
389
390 #[must_use]
392 pub const fn with_rust_root_cohort(mut self, directory: &'a str) -> Self {
393 self.rust_root_cohort = Some(directory);
394 self
395 }
396
397 #[must_use]
402 pub const fn with_retained_bytes_limit(mut self, limit: u64) -> Self {
403 self.retained_bytes_limit = Some(limit);
404 self
405 }
406}
407
408impl ResolutionKeyProjection {
409 #[must_use]
413 pub fn retained_bytes(&self) -> u64 {
414 fn keys_bytes(keys: &Vec<CanonicalResolutionKey>) -> u64 {
415 keys.iter().fold(
416 (keys.capacity() as u64)
417 .saturating_mul(std::mem::size_of::<CanonicalResolutionKey>() as u64),
418 |bytes, key| bytes.saturating_add(key.retained_heap_bytes()),
419 )
420 }
421 keys_bytes(&self.source)
422 .saturating_add(
423 (self.symbols.capacity() * std::mem::size_of::<SymbolResolutionKeys>()) as u64,
424 )
425 .saturating_add(
426 (self.exports.capacity() * std::mem::size_of::<ExportResolutionKeys>()) as u64,
427 )
428 .saturating_add(
429 (self.relations.capacity() * std::mem::size_of::<RelationResolutionKeys>()) as u64,
430 )
431 .saturating_add(
432 self.symbols
433 .iter()
434 .map(|row| keys_bytes(&row.keys))
435 .fold(0, u64::saturating_add),
436 )
437 .saturating_add(
438 self.exports
439 .iter()
440 .map(|row| keys_bytes(&row.keys))
441 .fold(0, u64::saturating_add),
442 )
443 .saturating_add(
444 self.relations
445 .iter()
446 .map(|row| keys_bytes(&row.keys))
447 .fold(0, u64::saturating_add),
448 )
449 .saturating_add(self.source_calls_retained_bytes())
450 .saturating_add(self.source_spans_retained_bytes())
451 .saturating_add(self.rust_toolchain_imports_retained_bytes())
452 }
453
454 #[must_use]
458 pub const fn empty() -> Self {
459 Self {
460 source: Vec::new(),
461 symbols: Vec::new(),
462 exports: Vec::new(),
463 relations: Vec::new(),
464 source_calls: None,
465 source_spans: BTreeMap::new(),
466 rust_toolchain_imports: None,
467 }
468 }
469
470 #[must_use]
472 pub fn source_keys(&self) -> &[CanonicalResolutionKey] {
473 &self.source
474 }
475
476 #[must_use]
478 pub fn symbol_keys(&self) -> &[SymbolResolutionKeys] {
479 &self.symbols
480 }
481
482 #[must_use]
484 pub fn export_lookup_vector_bytes(&self) -> u64 {
485 (self.exports.capacity() as u64)
486 .saturating_mul(std::mem::size_of::<ExportResolutionKeys>() as u64)
487 .saturating_add(
488 self.exports
489 .iter()
490 .map(|export| {
491 (export.keys.capacity() as u64)
492 .saturating_mul(std::mem::size_of::<CanonicalResolutionKey>() as u64)
493 })
494 .fold(0_u64, u64::saturating_add),
495 )
496 }
497
498 #[must_use]
500 pub fn export_keys(&self) -> &[ExportResolutionKeys] {
501 &self.exports
502 }
503
504 #[must_use]
506 pub fn relation_keys(&self) -> &[RelationResolutionKeys] {
507 &self.relations
508 }
509
510 #[must_use]
512 pub fn source_calls(&self) -> Option<&[SourceCallFact]> {
513 self.source_calls.as_deref()
514 }
515
516 #[must_use]
518 pub fn source_calls_retained_bytes(&self) -> u64 {
519 self.source_calls.as_ref().map_or(0, |calls| {
520 (calls.capacity() as u64)
521 .saturating_mul(std::mem::size_of::<SourceCallFact>() as u64)
522 .saturating_add(crate::source_semantics::call_binding_heap_bytes(calls))
523 })
524 }
525
526 #[must_use]
528 pub fn source_span(&self, relation: usize) -> Option<SourceSpan> {
529 self.source_spans.get(&relation).copied()
530 }
531
532 #[must_use]
534 pub fn source_spans_retained_bytes(&self) -> u64 {
535 (self.source_spans.len() as u64).saturating_mul(
536 (std::mem::size_of::<(usize, SourceSpan)>() + 4 * std::mem::size_of::<usize>()) as u64,
537 )
538 }
539
540 #[must_use]
542 pub fn rust_toolchain_imports(&self) -> Option<&BTreeMap<usize, Option<ImportReference>>> {
543 self.rust_toolchain_imports.as_ref()
544 }
545
546 #[must_use]
548 pub fn rust_toolchain_imports_retained_bytes(&self) -> u64 {
549 self.rust_toolchain_imports.as_ref().map_or(0, |imports| {
550 let mut modules = HashSet::new();
551 imports.values().fold(0_u64, |bytes, reference| {
552 bytes.saturating_add(128).saturating_add(
553 reference
554 .as_ref()
555 .map_or(0, |reference| reference.retained_heap_bytes(&mut modules)),
556 )
557 })
558 })
559 }
560}
561
562#[derive(Clone, Copy, Debug, Eq, PartialEq)]
564pub enum ResolutionProjectionFact {
565 Source,
567 Symbol(usize),
569 Relation(usize),
571 Export(usize),
573}
574
575pub const MAX_RESOLUTION_PROJECTION_FAILURES: usize = GraphLimits::MAX_ROWS as usize;
577
578#[derive(Debug)]
580pub struct ResolutionProjectionFactFailure {
581 error: GraphContractError,
583 fact: ResolutionProjectionFact,
585}
586
587impl ResolutionProjectionFactFailure {
588 #[must_use]
590 pub fn error(&self) -> &GraphContractError {
591 &self.error
592 }
593
594 #[must_use]
596 pub const fn fact(&self) -> ResolutionProjectionFact {
597 self.fact
598 }
599
600 #[must_use]
602 pub fn into_parts(self) -> (ResolutionProjectionFact, GraphContractError) {
603 (self.fact, self.error)
604 }
605}
606
607#[derive(Debug)]
609pub struct ResolutionProjectionFailure {
610 failures: Vec<ResolutionProjectionFactFailure>,
612 rejected_count: usize,
614 projection: ResolutionKeyProjection,
616}
617
618impl ResolutionProjectionFailure {
619 #[must_use]
621 pub fn failures(&self) -> &[ResolutionProjectionFactFailure] {
622 &self.failures
623 }
624
625 #[must_use]
627 pub const fn rejected_count(&self) -> usize {
628 self.rejected_count
629 }
630
631 #[must_use]
633 pub fn projection(&self) -> &ResolutionKeyProjection {
634 &self.projection
635 }
636
637 #[must_use]
639 pub fn into_parts(
640 self,
641 ) -> (
642 Vec<ResolutionProjectionFactFailure>,
643 usize,
644 ResolutionKeyProjection,
645 ) {
646 (self.failures, self.rejected_count, self.projection)
647 }
648}
649
650#[derive(Debug)]
652pub enum ResolutionProjectionError {
653 MemoryLimit {
655 limit: u64,
657 requested: u64,
659 },
660 SourceEvidenceMismatch,
662 Contract(Box<ResolutionProjectionFailure>),
664 KeyLimit {
666 fact: &'static str,
668 index: usize,
670 requested: usize,
672 },
673}
674
675impl fmt::Display for ResolutionProjectionError {
676 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
677 match self {
678 Self::MemoryLimit { limit, requested } => write!(
679 formatter,
680 "resolution projection requires {requested} retained bytes, exceeding its {limit}-byte allowance"
681 ),
682 Self::SourceEvidenceMismatch => {
683 formatter.write_str("source semantic evidence does not match the projection graph")
684 }
685 Self::Contract(failure) => match failure.failures.first() {
686 Some(failure) => {
687 write!(formatter, "invalid resolution identity: {}", failure.error)
688 }
689 None => formatter.write_str("invalid resolution identity"),
690 },
691 Self::KeyLimit {
692 fact,
693 index,
694 requested,
695 } => write!(
696 formatter,
697 "{fact} fact {index} requires {requested} canonical keys, exceeding the per-fact limit of {MAX_RESOLUTION_KEYS_PER_FACT}"
698 ),
699 }
700 }
701}
702
703impl Error for ResolutionProjectionError {
704 fn source(&self) -> Option<&(dyn Error + 'static)> {
705 match self {
706 Self::Contract(failure) => failure
707 .failures
708 .first()
709 .map(|failure| &failure.error as &(dyn Error + 'static)),
710 Self::MemoryLimit { .. } | Self::KeyLimit { .. } | Self::SourceEvidenceMismatch => None,
711 }
712 }
713}
714
715impl From<GraphContractError> for ResolutionProjectionError {
716 fn from(value: GraphContractError) -> Self {
717 Self::Contract(Box::new(ResolutionProjectionFailure {
718 failures: vec![ResolutionProjectionFactFailure {
719 error: value,
720 fact: ResolutionProjectionFact::Source,
721 }],
722 rejected_count: 1,
723 projection: ResolutionKeyProjection::empty(),
724 }))
725 }
726}
727
728#[derive(Clone, Copy, Debug, Eq, PartialEq)]
730pub enum ImportSyntax {
731 Rust,
733 EcmaScript,
735 Python,
737}
738
739#[derive(Clone, Debug, Eq, PartialEq)]
741pub struct ImportReference {
742 syntax: ImportSyntax,
744 module: Arc<str>,
746 imported: Option<String>,
748 local: String,
750}
751
752impl ImportReference {
753 pub(crate) fn new(
755 syntax: ImportSyntax,
756 module: &str,
757 imported: Option<&str>,
758 local: &str,
759 ) -> Self {
760 Self::with_shared_module(syntax, Arc::from(module), imported, local)
761 }
762
763 pub(crate) fn with_shared_module(
765 syntax: ImportSyntax,
766 module: Arc<str>,
767 imported: Option<&str>,
768 local: &str,
769 ) -> Self {
770 Self {
771 syntax,
772 module,
773 imported: imported.map(ToString::to_string),
774 local: local.to_string(),
775 }
776 }
777
778 #[must_use]
780 pub const fn syntax(&self) -> ImportSyntax {
781 self.syntax
782 }
783
784 #[must_use]
786 pub fn module(&self) -> &str {
787 &self.module
788 }
789
790 #[must_use]
792 pub fn shared_module(&self) -> Arc<str> {
793 Arc::clone(&self.module)
794 }
795
796 #[must_use]
798 pub fn imported(&self) -> Option<&str> {
799 self.imported.as_deref()
800 }
801
802 #[must_use]
804 pub fn local(&self) -> &str {
805 &self.local
806 }
807
808 pub(crate) fn retained_heap_bytes(&self, modules: &mut HashSet<*const str>) -> u64 {
810 let module = if modules.insert(Arc::as_ptr(&self.module)) {
811 (self.module.len() as u64).saturating_add((2 * std::mem::size_of::<usize>()) as u64)
812 } else {
813 0
814 };
815 module
816 .saturating_add(self.imported.as_ref().map_or(0, String::capacity) as u64)
817 .saturating_add(self.local.capacity() as u64)
818 }
819}
820
821#[must_use]
826pub fn parse_import_references(import_text: &str) -> Vec<ImportReference> {
827 let mut references = semantic::parse_display_import(import_text);
828 references.sort_by(|left, right| {
829 left.module
830 .cmp(&right.module)
831 .then_with(|| left.imported.cmp(&right.imported))
832 .then_with(|| left.local.cmp(&right.local))
833 });
834 references.dedup();
835 references
836}
837
838#[must_use]
840pub fn resolve_relative_import_path(caller_path: &str, module_spec: &str) -> Option<String> {
841 semantic::ecmascript::resolve_relative_import_path(caller_path, module_spec)
842}
843
844#[must_use]
846pub fn module_aliases_for_path(path: &str) -> Vec<String> {
847 let mut aliases = Vec::new();
848 for stem in source_stems_for_path(path) {
849 let mut components = stem
850 .split('/')
851 .filter(|component| !component.is_empty())
852 .collect::<Vec<_>>();
853 if components
854 .first()
855 .is_some_and(|component| *component == "src")
856 {
857 components.remove(0);
858 }
859 if components.last().is_some_and(|component| {
860 matches!(*component, "lib" | "main" | "mod" | "index" | "__init__")
861 }) {
862 components.pop();
863 }
864 if components.is_empty() {
865 continue;
866 }
867 aliases.push(components.join("::"));
868 aliases.push(components.join("."));
869 if let Some(last) = components.last() {
870 aliases.push((*last).to_string());
871 }
872 }
873 aliases.sort();
874 aliases.dedup();
875 aliases
876}
877
878#[must_use]
880pub fn source_stems_for_path(path: &str) -> Vec<String> {
881 let stem = strip_known_source_extension(path);
882 let mut stems = vec![stem.clone()];
883 if let Some((parent, entry_name)) = stem.rsplit_once('/')
884 && matches!(entry_name, "index" | "__init__" | "mod")
885 {
886 stems.push(parent.to_string());
887 }
888 stems.sort();
889 stems.dedup();
890 stems
891}
892
893pub fn derive_resolution_keys(
904 project: ProjectInstanceId,
905 package: Option<&str>,
906 graph: &SymbolGraph,
907) -> Result<ResolutionKeyProjection, ResolutionProjectionError> {
908 derive_resolution_keys_with_context(
909 project,
910 package,
911 graph,
912 ResolutionProjectionContext::default(),
913 )
914}
915
916pub fn derive_resolution_keys_with_context(
924 project: ProjectInstanceId,
925 package: Option<&str>,
926 graph: &SymbolGraph,
927 context: ResolutionProjectionContext<'_>,
928) -> Result<ResolutionKeyProjection, ResolutionProjectionError> {
929 if context
930 .source_semantics
931 .is_some_and(|facts| !facts.matches_graph(graph))
932 {
933 return Err(ResolutionProjectionError::SourceEvidenceMismatch);
934 }
935 let mut budget = ProjectionBudget::new(context.retained_bytes_limit);
936 budget.reserve(std::mem::size_of::<ResolutionKeyProjection>() as u64)?;
937 if let Some(facts) = context.source_semantics {
938 budget.reserve(
939 (facts.calls().len() as u64)
940 .saturating_mul((2 * std::mem::size_of::<SourceCallFact>()) as u64)
941 .saturating_add(crate::source_semantics::call_binding_heap_bytes(
942 facts.calls(),
943 )),
944 )?;
945 }
946 let source_calls = context.source_semantics.map(|facts| facts.calls().to_vec());
947 let mut source_spans = BTreeMap::new();
948 if let Some(facts) = context.source_semantics {
949 for (relation, span) in facts
950 .calls()
951 .iter()
952 .map(|fact| (fact.relation, fact.source_span))
953 .chain(
954 facts
955 .imports()
956 .iter()
957 .map(|fact| (fact.relation, fact.source_span)),
958 )
959 {
960 if let Some(span) = span {
961 if let Some(previous) = source_spans.get(&relation) {
962 if *previous != span {
963 return Err(ResolutionProjectionError::SourceEvidenceMismatch);
964 }
965 } else {
966 budget.reserve(128)?;
967 source_spans.insert(relation, span);
968 }
969 }
970 }
971 }
972 let rust_toolchain_imports = context
973 .source_semantics
974 .filter(|_| semantic::provider_for_graph(graph) == Some(SemanticProviderOwner::Rust))
975 .map(|facts| {
976 let mut modules = HashSet::new();
977 let bytes = facts.imports().iter().fold(0_u64, |bytes, import| {
978 bytes.saturating_add(128).saturating_add(
979 import
980 .rust_toolchain
981 .as_deref()
982 .map_or(0, |reference| reference.retained_heap_bytes(&mut modules)),
983 )
984 });
985 budget.reserve(bytes)?;
986 Ok::<_, ResolutionProjectionError>(
987 facts
988 .imports()
989 .iter()
990 .map(|import| (import.relation, import.rust_toolchain.as_deref().cloned()))
991 .collect::<BTreeMap<_, _>>(),
992 )
993 })
994 .transpose()?;
995 let Some(provider_owner) = semantic::provider_for_graph(graph) else {
996 return Ok(ResolutionKeyProjection {
997 source: Vec::new(),
998 symbols: Vec::new(),
999 exports: Vec::new(),
1000 relations: Vec::new(),
1001 source_calls,
1002 source_spans,
1003 rust_toolchain_imports,
1004 });
1005 };
1006 let provider = GraphIdentityText::new(provider_owner.as_str())?;
1007 let Some(resolution_family) = provider_owner.resolution_family() else {
1008 return Ok(ResolutionKeyProjection {
1009 source: Vec::new(),
1010 symbols: Vec::new(),
1011 exports: Vec::new(),
1012 relations: Vec::new(),
1013 source_calls,
1014 source_spans,
1015 rust_toolchain_imports,
1016 });
1017 };
1018 let mut contract_failures = ContractFailures::new(
1019 graph.symbols.len(),
1020 graph.relations.len(),
1021 context
1022 .source_semantics
1023 .map_or(0, |facts| facts.exports().len()),
1024 budget,
1025 )?;
1026 let language = GraphIdentityText::new(resolution_family)?;
1027 let package = package.map(GraphIdentityText::new).transpose()?;
1028
1029 let source_scopes = canonical_source_scopes(&graph.path, provider_owner);
1030 let configured_source_scopes =
1031 if provider_owner == SemanticProviderOwner::EcmaScript && package.is_some() {
1032 canonical_configured_source_scopes(&graph.path)
1033 } else {
1034 Vec::new()
1035 };
1036 let emits_source_module_keys = semantic::emits_source_module_keys(graph)
1037 || (provider_owner == SemanticProviderOwner::EcmaScript
1038 && context.source_semantics.is_some_and(|facts| {
1039 !facts.imports().is_empty()
1040 || !facts.calls().is_empty()
1041 || !facts.exports().is_empty()
1042 }));
1043 let mut source_keys = Vec::new();
1044 if emits_source_module_keys {
1045 for scope in &source_scopes {
1046 if let Some(key) = retain_derived_key(
1047 canonical_key(
1048 project,
1049 ResolutionKeyDomain::Module,
1050 &provider,
1051 &language,
1052 package.as_ref(),
1053 None,
1054 RelationKind::Imports,
1055 scope,
1056 ),
1057 ResolutionProjectionFact::Source,
1058 &mut contract_failures,
1059 ) {
1060 source_keys.push(key);
1061 }
1062 }
1063 for scope in &configured_source_scopes {
1064 if let Some(key) = retain_derived_key(
1065 canonical_key(
1066 project,
1067 ResolutionKeyDomain::Module,
1068 &provider,
1069 &language,
1070 None,
1071 None,
1072 RelationKind::Imports,
1073 scope,
1074 ),
1075 ResolutionProjectionFact::Source,
1076 &mut contract_failures,
1077 ) {
1078 source_keys.push(key);
1079 }
1080 }
1081 }
1082 if provider_owner == SemanticProviderOwner::Rust
1083 && let Some((manifest, root)) = context.rust_package
1084 && let Some(package) = package.as_ref()
1085 && let Some(directory) = crate::cargo_automatic_target_directory(&graph.path, root)
1086 && let Some(key) = retain_derived_key(
1087 crate::cargo_target_inventory_key(project, package.as_str(), manifest, directory),
1088 ResolutionProjectionFact::Source,
1089 &mut contract_failures,
1090 )
1091 {
1092 source_keys.push(key);
1093 }
1094 if provider_owner == SemanticProviderOwner::Rust
1095 && let Some((manifest, _root)) = context.rust_package
1096 && let Some(package) = package.as_ref()
1097 && let Some(directory) = context.rust_root_cohort
1098 && let Some(key) = retain_derived_key(
1099 crate::cargo_root_cohort_key(project, package.as_str(), manifest, directory),
1100 ResolutionProjectionFact::Source,
1101 &mut contract_failures,
1102 )
1103 {
1104 source_keys.push(key);
1105 }
1106 contract_failures.budget.check()?;
1107 let source_keys = bounded_keys(source_keys, "source", 0)?;
1108
1109 let explicit_export_names = context
1110 .source_semantics
1111 .filter(|_| {
1112 matches!(
1113 provider_owner,
1114 SemanticProviderOwner::EcmaScript | SemanticProviderOwner::Python
1115 )
1116 })
1117 .map(|facts| {
1118 let mut names = BTreeMap::<usize, Vec<&str>>::new();
1119 if !facts.exports_complete() {
1120 return (0..graph.symbols.len())
1121 .map(|symbol| (symbol, Vec::new()))
1122 .collect();
1123 }
1124 let mut slots = BTreeMap::<&str, usize>::new();
1125 for export in facts
1126 .exports()
1127 .iter()
1128 .filter(|export| export.namespace == crate::SourceExportNamespace::Value)
1129 {
1130 *slots.entry(export.name.as_str()).or_default() += 1;
1131 }
1132 for export in facts
1133 .exports()
1134 .iter()
1135 .filter(|export| export.namespace == crate::SourceExportNamespace::Value)
1136 {
1137 match &export.binding {
1138 crate::SourceExportBinding::LocalDeclaration(symbol) => {
1139 let names = names.entry(*symbol).or_default();
1140 if slots.get(export.name.as_str()) == Some(&1) {
1141 names.push(export.name.as_str());
1142 }
1143 }
1144 crate::SourceExportBinding::AmbiguousDeclarations(symbols)
1145 | crate::SourceExportBinding::UnresolvedDeclarations(symbols) => {
1146 for symbol in symbols.iter() {
1147 names.entry(*symbol).or_default();
1148 }
1149 }
1150 crate::SourceExportBinding::ReExport(_)
1151 | crate::SourceExportBinding::RustReExport(_)
1152 | crate::SourceExportBinding::Unresolved => {}
1153 }
1154 }
1155 for names in names.values_mut() {
1156 names.sort_unstable();
1157 names.dedup();
1158 }
1159 names
1160 });
1161 let mut symbol_keys = Vec::new();
1162 for (symbol_index, symbol) in graph.symbols.iter().enumerate() {
1163 contract_failures.budget.check()?;
1164 let mut keys = Vec::new();
1165 match symbol.kind {
1166 SymbolKind::Package if provider_owner == SemanticProviderOwner::Cargo => {
1167 if let Some(key) = retain_derived_key(
1168 canonical_key(
1169 project,
1170 ResolutionKeyDomain::Package,
1171 &provider,
1172 &language,
1173 None,
1174 None,
1175 RelationKind::DependsOn,
1176 &symbol.name,
1177 ),
1178 ResolutionProjectionFact::Symbol(symbol_index),
1179 &mut contract_failures,
1180 ) {
1181 keys.push(key);
1182 }
1183 }
1184 SymbolKind::Dependency | SymbolKind::Import | SymbolKind::Workspace => {}
1185 _ if emits_source_module_keys
1186 && context
1187 .source_semantics
1188 .is_none_or(|facts| !facts.bindings_incomplete)
1189 && !semantic::blocks_direct_call_export(provider_owner, symbol.kind)
1190 && (semantic::is_export_candidate(provider_owner, graph, symbol_index)
1191 || explicit_export_names
1192 .as_ref()
1193 .is_some_and(|names| names.contains_key(&symbol_index))) =>
1194 {
1195 let names = match explicit_export_names
1196 .as_ref()
1197 .and_then(|names| names.get(&symbol_index))
1198 {
1199 Some(names) => names
1200 .iter()
1201 .map(|name| std::borrow::Cow::Borrowed(*name))
1202 .collect::<Vec<_>>(),
1203 None => vec![if provider_owner == SemanticProviderOwner::EcmaScript {
1204 semantic::ecmascript_identifier_value(&symbol.name).map_or(
1205 std::borrow::Cow::Borrowed(symbol.name.as_str()),
1206 std::borrow::Cow::Owned,
1207 )
1208 } else {
1209 std::borrow::Cow::Borrowed(symbol.name.as_str())
1210 }],
1211 };
1212 for name in names {
1213 let identity = match GraphIdentityText::new(name.into_owned()) {
1214 Ok(identity) => identity,
1215 Err(error) => {
1216 remember_contract_failure(
1217 &mut contract_failures,
1218 ResolutionProjectionFact::Symbol(symbol_index),
1219 error,
1220 );
1221 continue;
1222 }
1223 };
1224 let mut scopes = Vec::new();
1225 if let Some(parent) = symbol.parent.as_deref() {
1226 scopes.extend(
1227 source_scopes.iter().map(|scope| {
1228 normalize_repository_scope(&format!("{scope}/{parent}"))
1229 }),
1230 );
1231 scopes.push(normalize_repository_scope(parent));
1232 } else {
1233 scopes.extend(source_scopes.iter().cloned());
1234 scopes.push(String::new());
1235 }
1236 scopes.sort();
1237 scopes.dedup();
1238 for scope in &scopes {
1239 let scope = if scope.is_empty() {
1240 None
1241 } else {
1242 match GraphIdentityText::new(scope.clone()) {
1243 Ok(scope) => Some(scope),
1244 Err(error) => {
1245 remember_contract_failure(
1246 &mut contract_failures,
1247 ResolutionProjectionFact::Symbol(symbol_index),
1248 error,
1249 );
1250 continue;
1251 }
1252 }
1253 };
1254 let key = CanonicalResolutionKey::new(
1255 project,
1256 ResolutionKeyDomain::Declaration,
1257 &provider,
1258 &language,
1259 package.as_ref(),
1260 scope.as_ref(),
1261 Some(GraphRelationKind::from_legacy(RelationKind::Calls)),
1262 &identity,
1263 );
1264 if contract_failures.budget.key(&key) {
1265 keys.push(key);
1266 }
1267 contract_failures.budget.check()?;
1268 }
1269 let configured_scopes = configured_source_scopes.iter().map(|scope| {
1270 symbol.parent.as_deref().map_or_else(
1271 || scope.clone(),
1272 |parent| normalize_repository_scope(&format!("{scope}/{parent}")),
1273 )
1274 });
1275 for scope in configured_scopes {
1276 let scope = match GraphIdentityText::new(scope) {
1277 Ok(scope) => scope,
1278 Err(error) => {
1279 remember_contract_failure(
1280 &mut contract_failures,
1281 ResolutionProjectionFact::Symbol(symbol_index),
1282 error,
1283 );
1284 continue;
1285 }
1286 };
1287 let key = CanonicalResolutionKey::new(
1288 project,
1289 ResolutionKeyDomain::Declaration,
1290 &provider,
1291 &language,
1292 None,
1293 Some(&scope),
1294 Some(GraphRelationKind::from_legacy(RelationKind::Calls)),
1295 &identity,
1296 );
1297 if contract_failures.budget.key(&key) {
1298 keys.push(key);
1299 }
1300 contract_failures.budget.check()?;
1301 }
1302 if keys.len() > MAX_RESOLUTION_KEYS_PER_FACT {
1303 keys = bounded_keys(keys, "symbol", symbol_index)?;
1304 }
1305 }
1306 }
1307 _ => {}
1308 }
1309 let keys = bounded_keys(keys, "symbol", symbol_index)?;
1310 if !keys.is_empty() {
1311 contract_failures
1312 .budget
1313 .reserve((4 * std::mem::size_of::<SymbolResolutionKeys>()) as u64)?;
1314 symbol_keys.push(SymbolResolutionKeys { symbol_index, keys });
1315 }
1316 }
1317
1318 let mut export_keys = Vec::new();
1319 if matches!(
1320 provider_owner,
1321 SemanticProviderOwner::EcmaScript | SemanticProviderOwner::Rust
1322 ) && let Some(facts) = context
1323 .source_semantics
1324 .filter(|facts| facts.exports_complete())
1325 {
1326 for (export_index, export) in facts.exports().iter().enumerate() {
1327 contract_failures.budget.check()?;
1328 if export.namespace != crate::SourceExportNamespace::Value
1329 || !matches!(
1330 export.binding,
1331 crate::SourceExportBinding::ReExport(_)
1332 | crate::SourceExportBinding::RustReExport(_)
1333 | crate::SourceExportBinding::UnresolvedDeclarations(_)
1334 | crate::SourceExportBinding::Unresolved
1335 )
1336 {
1337 continue;
1338 }
1339 let fact = ResolutionProjectionFact::Export(export_index);
1340 let mut keys = Vec::new();
1341 let rust_module = match &export.binding {
1342 crate::SourceExportBinding::RustReExport(binding) => binding.module.as_ref(),
1343 _ => &[],
1344 };
1345 for scope in source_scopes.iter().map(|scope| {
1346 if rust_module.is_empty() {
1347 scope.clone()
1348 } else {
1349 format!("{scope}/{}", rust_module.join("/"))
1350 }
1351 }) {
1352 if let Some(key) = retain_derived_key(
1353 canonical_key(
1354 project,
1355 ResolutionKeyDomain::Declaration,
1356 &provider,
1357 &language,
1358 package.as_ref(),
1359 Some(&scope),
1360 RelationKind::Calls,
1361 &export.name,
1362 ),
1363 fact,
1364 &mut contract_failures,
1365 ) {
1366 keys.push(key);
1367 }
1368 }
1369 for scope in &configured_source_scopes {
1370 if let Some(key) = retain_derived_key(
1371 canonical_key(
1372 project,
1373 ResolutionKeyDomain::Declaration,
1374 &provider,
1375 &language,
1376 None,
1377 Some(scope),
1378 RelationKind::Calls,
1379 &export.name,
1380 ),
1381 fact,
1382 &mut contract_failures,
1383 ) {
1384 keys.push(key);
1385 }
1386 }
1387 let keys = bounded_keys(keys, "export", export_index)?;
1388 if !keys.is_empty() {
1389 contract_failures
1390 .budget
1391 .reserve((4 * std::mem::size_of::<ExportResolutionKeys>()) as u64)?;
1392 export_keys.push(ExportResolutionKeys { export_index, keys });
1393 }
1394 }
1395 }
1396
1397 contract_failures.budget.reserve(
1398 (graph.relations.len() as u64)
1399 .saturating_mul(std::mem::size_of::<Vec<ImportReference>>() as u64),
1400 )?;
1401 let mut parsed_imports = vec![Vec::new(); graph.relations.len()];
1402 if let Some(source) = context.source_semantics {
1403 for import in source.imports() {
1404 reserve_import_references(&import.references, &mut contract_failures.budget)?;
1405 parsed_imports[import.relation].clone_from(&import.references);
1406 }
1407 } else {
1408 for (relation, references) in graph.relations.iter().zip(&mut parsed_imports) {
1409 if relation.kind == RelationKind::Imports {
1410 let parsed = semantic::parse_imports(provider_owner, &relation.target_name);
1411 reserve_import_references(&parsed, &mut contract_failures.budget)?;
1412 *references = parsed;
1413 }
1414 }
1415 }
1416 let mut aliases: BTreeMap<&str, Vec<&ImportReference>> = BTreeMap::new();
1417 for references in &parsed_imports {
1418 for reference in references {
1419 contract_failures
1420 .budget
1421 .reserve(128 + (4 * std::mem::size_of::<&ImportReference>()) as u64)?;
1422 aliases
1423 .entry(reference.local())
1424 .or_default()
1425 .push(reference);
1426 }
1427 }
1428 let configured_source = context
1429 .configured_modules
1430 .map(|configured| configured.for_source(&graph.path));
1431 let mut import_scopes = build_import_scope_cache(
1432 provider_owner,
1433 graph,
1434 &parsed_imports,
1435 configured_source.as_ref(),
1436 &mut contract_failures.budget,
1437 )?;
1438 contract_failures
1439 .budget
1440 .reserve(context.source_semantics.map_or(0, |facts| {
1441 (facts.calls().len().saturating_mul(256) + facts.imports().len().saturating_mul(128))
1442 as u64
1443 }))?;
1444 let rust_calls = context
1445 .source_semantics
1446 .map(|facts| {
1447 facts
1448 .calls()
1449 .iter()
1450 .filter_map(|call| {
1451 if let SourceCallBinding::RustPath(path) = &call.binding {
1452 path.reference().map(|reference| (call.relation, reference))
1453 } else {
1454 None
1455 }
1456 })
1457 .collect::<BTreeMap<_, _>>()
1458 })
1459 .unwrap_or_default();
1460 for reference in rust_calls.values().chain(
1461 context
1462 .source_semantics
1463 .into_iter()
1464 .flat_map(SourceSemanticFacts::calls)
1465 .filter_map(|call| {
1466 if let SourceCallBinding::Qualified(reference) = &call.binding {
1467 Some(reference)
1468 } else {
1469 None
1470 }
1471 }),
1472 ) {
1473 cache_import_scopes(
1474 &mut import_scopes,
1475 provider_owner,
1476 &graph.path,
1477 reference,
1478 configured_source.as_ref(),
1479 &mut contract_failures.budget,
1480 )?;
1481 }
1482
1483 let mut relation_keys = Vec::new();
1484 let source_import_index = context.source_semantics.map(|facts| {
1485 facts
1486 .imports()
1487 .iter()
1488 .map(|fact| (fact.relation, fact))
1489 .collect::<BTreeMap<_, _>>()
1490 });
1491 let source_call_index = source_calls.as_ref().map(|calls| {
1492 calls
1493 .iter()
1494 .map(|fact| (fact.relation, fact))
1495 .collect::<BTreeMap<_, _>>()
1496 });
1497 for (relation_index, relation) in graph.relations.iter().enumerate() {
1498 contract_failures.budget.check()?;
1499 let mut keys = match relation.kind {
1500 RelationKind::Imports if semantic::supports_source_dependencies(provider_owner) => {
1501 if provider_owner == SemanticProviderOwner::EcmaScript
1502 && let Some(module) = source_import_index
1503 .as_ref()
1504 .and_then(|imports| imports.get(&relation_index))
1505 .and_then(|fact| fact.module.as_deref())
1506 {
1507 semantic::ecmascript_module_scopes(
1508 &relation.path,
1509 module,
1510 configured_source.as_ref(),
1511 )
1512 .iter()
1513 .filter_map(|scope| {
1514 retain_derived_key(
1515 canonical_key(
1516 project,
1517 ResolutionKeyDomain::Module,
1518 &provider,
1519 &language,
1520 dependency_package(provider_owner, module, package.as_ref()),
1521 None,
1522 RelationKind::Imports,
1523 scope,
1524 ),
1525 ResolutionProjectionFact::Relation(relation_index),
1526 &mut contract_failures,
1527 )
1528 })
1529 .collect()
1530 } else {
1531 import_dependency_keys(
1532 project,
1533 provider_owner,
1534 &provider,
1535 &language,
1536 package.as_ref(),
1537 &relation.path,
1538 &parsed_imports[relation_index],
1539 &import_scopes,
1540 relation_index,
1541 &mut contract_failures,
1542 )
1543 }
1544 }
1545 RelationKind::Calls if semantic::supports_source_dependencies(provider_owner) => {
1546 if let Some(calls) = &source_call_index {
1547 if let Some(SourceCallFact {
1548 binding:
1549 SourceCallBinding::Imported {
1550 import,
1551 reference,
1552 member,
1553 ..
1554 },
1555 ..
1556 }) = calls.get(&relation_index).copied()
1557 {
1558 let reference = &context
1559 .source_semantics
1560 .ok_or(ResolutionProjectionError::SourceEvidenceMismatch)?
1561 .imports()[*import]
1562 .references[*reference];
1563 imported_reference_call_keys(
1564 project,
1565 provider_owner,
1566 &provider,
1567 &language,
1568 package.as_ref(),
1569 &relation.path,
1570 reference,
1571 member.as_deref(),
1572 &import_scopes,
1573 relation_index,
1574 &mut contract_failures,
1575 )
1576 } else if let Some(SourceCallFact {
1577 binding: SourceCallBinding::Qualified(reference),
1578 ..
1579 }) = calls.get(&relation_index).copied()
1580 {
1581 imported_reference_call_keys(
1582 project,
1583 provider_owner,
1584 &provider,
1585 &language,
1586 package.as_ref(),
1587 &relation.path,
1588 reference,
1589 None,
1590 &import_scopes,
1591 relation_index,
1592 &mut contract_failures,
1593 )
1594 } else if let Some(reference) = rust_calls.get(&relation_index) {
1595 imported_reference_call_keys(
1596 project,
1597 provider_owner,
1598 &provider,
1599 &language,
1600 package.as_ref(),
1601 &relation.path,
1602 reference,
1603 None,
1604 &import_scopes,
1605 relation_index,
1606 &mut contract_failures,
1607 )
1608 } else {
1609 Vec::new()
1610 }
1611 } else {
1612 call_dependency_keys(
1613 project,
1614 provider_owner,
1615 &provider,
1616 &language,
1617 package.as_ref(),
1618 &relation.path,
1619 &relation.target_name,
1620 &aliases,
1621 &import_scopes,
1622 relation_index,
1623 &mut contract_failures,
1624 )
1625 }
1626 }
1627 RelationKind::DependsOn if semantic::supports_package_dependencies(provider_owner) => {
1628 retain_derived_key(
1629 canonical_key(
1630 project,
1631 ResolutionKeyDomain::Package,
1632 &provider,
1633 &language,
1634 None,
1635 None,
1636 RelationKind::DependsOn,
1637 &relation.target_name,
1638 ),
1639 ResolutionProjectionFact::Relation(relation_index),
1640 &mut contract_failures,
1641 )
1642 .into_iter()
1643 .collect()
1644 }
1645 RelationKind::Contains => continue,
1646 RelationKind::Imports | RelationKind::Calls | RelationKind::DependsOn => Vec::new(),
1647 };
1648 keys = bounded_keys(keys, "relation", relation_index)?;
1649 contract_failures.budget.check()?;
1650 contract_failures
1651 .budget
1652 .reserve((4 * std::mem::size_of::<RelationResolutionKeys>()) as u64)?;
1653 relation_keys.push(RelationResolutionKeys {
1654 relation_index,
1655 keys,
1656 });
1657 }
1658
1659 contract_failures.budget.check()?;
1660 let projection = ResolutionKeyProjection {
1661 source: source_keys,
1662 symbols: symbol_keys,
1663 exports: export_keys,
1664 relations: relation_keys,
1665 source_calls,
1666 source_spans,
1667 rust_toolchain_imports,
1668 };
1669 if contract_failures.failures.is_empty() {
1670 Ok(projection)
1671 } else {
1672 Err(ResolutionProjectionError::Contract(Box::new(
1673 ResolutionProjectionFailure {
1674 failures: contract_failures.failures,
1675 rejected_count: contract_failures.rejected_count,
1676 projection,
1677 },
1678 )))
1679 }
1680}
1681
1682fn build_import_scope_cache(
1684 provider_owner: SemanticProviderOwner,
1685 graph: &SymbolGraph,
1686 parsed_imports: &[Vec<ImportReference>],
1687 configured_source: Option<&ConfiguredModuleSource<'_>>,
1688 budget: &mut ProjectionBudget,
1689) -> Result<ImportScopeCache, ResolutionProjectionError> {
1690 let mut cache = ImportScopeCache::new();
1691 for (relation, references) in graph.relations.iter().zip(parsed_imports) {
1692 for reference in references {
1693 cache_import_scopes(
1694 &mut cache,
1695 provider_owner,
1696 &relation.path,
1697 reference,
1698 configured_source,
1699 budget,
1700 )?;
1701 }
1702 }
1703 Ok(cache)
1704}
1705
1706fn reserve_import_references(
1708 references: &[ImportReference],
1709 budget: &mut ProjectionBudget,
1710) -> Result<(), ResolutionProjectionError> {
1711 let mut modules = HashSet::new();
1712 budget.reserve(
1713 references.iter().fold(
1714 (references.len() as u64)
1715 .saturating_mul((2 * std::mem::size_of::<ImportReference>()) as u64),
1716 |bytes, reference| bytes.saturating_add(reference.retained_heap_bytes(&mut modules)),
1717 ),
1718 )
1719}
1720
1721fn cache_import_scopes(
1723 cache: &mut ImportScopeCache,
1724 provider: SemanticProviderOwner,
1725 caller: &str,
1726 reference: &ImportReference,
1727 configured: Option<&ConfiguredModuleSource<'_>>,
1728 budget: &mut ProjectionBudget,
1729) -> Result<(), ResolutionProjectionError> {
1730 if cache
1731 .get(caller)
1732 .is_some_and(|modules| modules.contains_key(reference.module()))
1733 {
1734 return Ok(());
1735 }
1736 let scopes = semantic::import_scopes(provider, caller, reference, configured);
1739 let bytes = scopes
1740 .iter()
1741 .fold(
1742 128_u64
1743 .saturating_add(reference.module().len() as u64)
1744 .saturating_add((scopes.capacity() * std::mem::size_of::<String>()) as u64),
1745 |bytes, scope| bytes.saturating_add(scope.capacity() as u64),
1746 )
1747 .saturating_add(if cache.contains_key(caller) {
1748 0
1749 } else {
1750 128_u64.saturating_add(caller.len() as u64)
1751 });
1752 budget.reserve(bytes)?;
1753 cache
1754 .entry(caller.to_string())
1755 .or_default()
1756 .insert(reference.module().to_string(), scopes);
1757 Ok(())
1758}
1759
1760fn cached_import_scopes<'a>(
1762 cache: &'a ImportScopeCache,
1763 caller_path: &str,
1764 reference: &ImportReference,
1765) -> &'a [String] {
1766 cache
1767 .get(caller_path)
1768 .and_then(|modules| modules.get(reference.module()))
1769 .map_or(&[], Vec::as_slice)
1770}
1771
1772fn import_dependency_keys(
1774 project: ProjectInstanceId,
1775 provider_owner: SemanticProviderOwner,
1776 provider: &GraphIdentityText,
1777 language: &GraphIdentityText,
1778 package: Option<&GraphIdentityText>,
1779 caller_path: &str,
1780 references: &[ImportReference],
1781 import_scopes: &ImportScopeCache,
1782 relation_index: usize,
1783 contract_failures: &mut ContractFailures,
1784) -> Vec<CanonicalResolutionKey> {
1785 let mut keys = Vec::new();
1786 for reference in references {
1787 let scopes = cached_import_scopes(import_scopes, caller_path, reference);
1788 let package = dependency_package(provider_owner, reference.module(), package);
1789 for scope in scopes {
1790 if let Some(key) = retain_derived_key(
1791 canonical_key(
1792 project,
1793 ResolutionKeyDomain::Module,
1794 provider,
1795 language,
1796 package,
1797 None,
1798 RelationKind::Imports,
1799 scope,
1800 ),
1801 ResolutionProjectionFact::Relation(relation_index),
1802 contract_failures,
1803 ) {
1804 keys.push(key);
1805 }
1806 }
1807 }
1808 keys
1809}
1810
1811fn call_dependency_keys(
1813 project: ProjectInstanceId,
1814 provider_owner: SemanticProviderOwner,
1815 provider: &GraphIdentityText,
1816 language: &GraphIdentityText,
1817 package: Option<&GraphIdentityText>,
1818 caller_path: &str,
1819 target: &str,
1820 aliases: &BTreeMap<&str, Vec<&ImportReference>>,
1821 import_scopes: &ImportScopeCache,
1822 relation_index: usize,
1823 contract_failures: &mut ContractFailures,
1824) -> Vec<CanonicalResolutionKey> {
1825 let (prefix, remainder) = split_qualified_target(target);
1826 let alias = prefix.unwrap_or(target).trim();
1827 if let Some(references) = aliases.get(alias) {
1828 let mut keys = Vec::new();
1829 for reference in references {
1830 keys.extend(imported_reference_call_keys(
1831 project,
1832 provider_owner,
1833 provider,
1834 language,
1835 package,
1836 caller_path,
1837 reference,
1838 remainder,
1839 import_scopes,
1840 relation_index,
1841 contract_failures,
1842 ));
1843 }
1844 return keys;
1845 }
1846
1847 let (scope, identity) = split_qualified_target(target);
1848 if scope.is_some() {
1849 return Vec::new();
1850 }
1851 let Some(identity) = identity.or_else(|| (!target.trim().is_empty()).then_some(target.trim()))
1852 else {
1853 return Vec::new();
1854 };
1855 retain_derived_key(
1856 canonical_key(
1857 project,
1858 ResolutionKeyDomain::Declaration,
1859 provider,
1860 language,
1861 package,
1862 None,
1863 RelationKind::Calls,
1864 identity,
1865 ),
1866 ResolutionProjectionFact::Relation(relation_index),
1867 contract_failures,
1868 )
1869 .into_iter()
1870 .collect()
1871}
1872
1873fn imported_reference_call_keys(
1875 project: ProjectInstanceId,
1876 provider_owner: SemanticProviderOwner,
1877 provider: &GraphIdentityText,
1878 language: &GraphIdentityText,
1879 package: Option<&GraphIdentityText>,
1880 caller_path: &str,
1881 reference: &ImportReference,
1882 member: Option<&str>,
1883 import_scopes: &ImportScopeCache,
1884 relation_index: usize,
1885 contract_failures: &mut ContractFailures,
1886) -> Vec<CanonicalResolutionKey> {
1887 let mut keys = Vec::new();
1888 let (member_parent, identity) = match member {
1889 Some(member) if provider_owner == SemanticProviderOwner::Rust => member
1890 .rsplit_once("::")
1891 .map_or((None, Some(member)), |(parent, name)| {
1892 (Some(parent), Some(name))
1893 }),
1894 _ => (None, member.or_else(|| reference.imported())),
1895 };
1896 let Some(identity) = identity else {
1897 return Vec::new();
1898 };
1899 let scopes = cached_import_scopes(import_scopes, caller_path, reference);
1900 let package = dependency_package(provider_owner, reference.module(), package);
1901 for scope in scopes {
1902 let mut scope = std::borrow::Cow::Borrowed(scope.as_str());
1903 if member.is_some()
1904 && let Some(imported_parent) = reference.imported()
1905 {
1906 scope = std::borrow::Cow::Owned(format!("{scope}/{imported_parent}"));
1907 }
1908 if let Some(parent) = member_parent {
1909 scope =
1910 std::borrow::Cow::Owned(format!("{scope}/{}", normalize_repository_scope(parent)));
1911 }
1912 if let Some(key) = retain_derived_key(
1913 canonical_key(
1914 project,
1915 ResolutionKeyDomain::Declaration,
1916 provider,
1917 language,
1918 package,
1919 Some(scope.as_ref()),
1920 RelationKind::Calls,
1921 identity,
1922 ),
1923 ResolutionProjectionFact::Relation(relation_index),
1924 contract_failures,
1925 ) {
1926 keys.push(key);
1927 }
1928 }
1929 keys
1930}
1931
1932fn dependency_package<'a>(
1935 provider_owner: SemanticProviderOwner,
1936 module: &str,
1937 package: Option<&'a GraphIdentityText>,
1938) -> Option<&'a GraphIdentityText> {
1939 if provider_owner == SemanticProviderOwner::EcmaScript
1940 && !(module.starts_with("./") || module.starts_with("../"))
1941 {
1942 None
1943 } else {
1944 package
1945 }
1946}
1947
1948#[allow(clippy::too_many_arguments)]
1949fn canonical_key(
1951 project: ProjectInstanceId,
1952 domain: ResolutionKeyDomain,
1953 provider: &GraphIdentityText,
1954 language: &GraphIdentityText,
1955 package: Option<&GraphIdentityText>,
1956 scope: Option<&str>,
1957 relation: RelationKind,
1958 identity: &str,
1959) -> Result<CanonicalResolutionKey, GraphContractError> {
1960 let scope = scope.map(GraphIdentityText::new).transpose()?;
1961 let identity = GraphIdentityText::new(identity.to_string())?;
1962 Ok(CanonicalResolutionKey::new(
1963 project,
1964 domain,
1965 provider,
1966 language,
1967 package,
1968 scope.as_ref(),
1969 Some(GraphRelationKind::from_legacy(relation)),
1970 &identity,
1971 ))
1972}
1973
1974fn retain_derived_key(
1976 result: Result<CanonicalResolutionKey, GraphContractError>,
1977 fact: ResolutionProjectionFact,
1978 contract_failures: &mut ContractFailures,
1979) -> Option<CanonicalResolutionKey> {
1980 match result {
1981 Ok(key) => contract_failures.budget.key(&key).then_some(key),
1982 Err(error) => {
1983 remember_contract_failure(contract_failures, fact, error);
1984 None
1985 }
1986 }
1987}
1988
1989fn remember_contract_failure(
1991 contract_failures: &mut ContractFailures,
1992 fact: ResolutionProjectionFact,
1993 error: GraphContractError,
1994) {
1995 contract_failures.remember(fact, error);
1996}
1997
1998fn bounded_keys(
2000 mut keys: Vec<CanonicalResolutionKey>,
2001 fact: &'static str,
2002 index: usize,
2003) -> Result<Vec<CanonicalResolutionKey>, ResolutionProjectionError> {
2004 keys.sort();
2005 keys.dedup();
2006 if keys.len() > MAX_RESOLUTION_KEYS_PER_FACT {
2007 return Err(ResolutionProjectionError::KeyLimit {
2008 fact,
2009 index,
2010 requested: keys.len(),
2011 });
2012 }
2013 Ok(keys)
2014}
2015
2016fn canonical_source_scopes(path: &str, provider: SemanticProviderOwner) -> Vec<String> {
2018 if provider == SemanticProviderOwner::EcmaScript {
2019 return canonical_configured_source_scopes(path);
2020 }
2021 let mut scopes = source_stems_for_path(path);
2022 scopes.extend(module_aliases_for_path(path));
2023 normalize_scopes(scopes, normalize_repository_scope)
2024}
2025
2026fn canonical_configured_source_scopes(path: &str) -> Vec<String> {
2028 let mut scopes = source_stems_for_path(path)
2029 .into_iter()
2030 .filter(|scope| !scope.is_empty())
2031 .collect::<Vec<_>>();
2032 scopes.sort();
2033 scopes.dedup();
2034 ecmascript_source_scopes(path, scopes)
2035}
2036
2037fn ecmascript_source_scopes(path: &str, module_scopes: Vec<String>) -> Vec<String> {
2043 let mut scopes = module_scopes
2044 .into_iter()
2045 .map(|scope| format!("{ECMA_MODULE_SCOPE}{scope}"))
2046 .collect::<Vec<_>>();
2047 scopes.push(format!("{ECMA_FILE_SCOPE}{path}"));
2048 let lower = path.to_ascii_lowercase();
2049 let substitution = [".d.ts", ".tsx", ".ts", ".jsx"]
2050 .into_iter()
2051 .find(|extension| lower.ends_with(extension));
2052 if let Some(extension) = substitution {
2053 let stem = &path[..path.len() - extension.len()];
2054 scopes.push(format!("{ECMA_FILE_SCOPE}{stem}.js"));
2055 if matches!(extension, ".d.ts" | ".tsx") {
2056 scopes.push(format!("{ECMA_FILE_SCOPE}{stem}.jsx"));
2057 }
2058 }
2059 scopes.sort();
2060 scopes.dedup();
2061 scopes
2062}
2063
2064pub(super) fn ecmascript_module_scope(path: &str) -> String {
2067 let explicit = path
2068 .rsplit('/')
2069 .next()
2070 .is_some_and(|name| name.contains('.'));
2071 let namespace = if explicit {
2072 ECMA_FILE_SCOPE
2073 } else {
2074 ECMA_MODULE_SCOPE
2075 };
2076 format!("{namespace}{path}")
2077}
2078
2079fn normalize_scopes(scopes: Vec<String>, normalize: fn(&str) -> String) -> Vec<String> {
2081 let mut normalized = Vec::new();
2082 for scope in scopes {
2083 let scope = normalize(&scope);
2084 if scope.is_empty() {
2085 continue;
2086 }
2087 normalized.push(scope.clone());
2088 if let Some(stripped) = scope.strip_prefix("src/") {
2089 normalized.push(stripped.to_string());
2090 }
2091 if let Some(last) = scope.rsplit('/').next() {
2092 normalized.push(last.to_string());
2093 }
2094 }
2095 normalized.sort();
2096 normalized.dedup();
2097 normalized
2098}
2099
2100fn normalize_repository_scope(value: &str) -> String {
2102 value
2103 .trim()
2104 .trim_start_matches("./")
2105 .replace("::", "/")
2106 .trim_matches('/')
2107 .to_string()
2108}
2109
2110fn split_qualified_target(target: &str) -> (Option<&str>, Option<&str>) {
2112 let target = target.trim();
2113 let rust = target.rsplit_once("::");
2114 let dotted = target.rsplit_once('.');
2115 match (rust, dotted) {
2116 (Some(rust), Some(dotted)) => {
2117 if rust.0.len() >= dotted.0.len() {
2118 (Some(rust.0), Some(rust.1))
2119 } else {
2120 (Some(dotted.0), Some(dotted.1))
2121 }
2122 }
2123 (Some((scope, identity)), None) | (None, Some((scope, identity))) => {
2124 (Some(scope), Some(identity))
2125 }
2126 (None, None) => (None, None),
2127 }
2128}
2129
2130pub(super) fn strip_known_source_extension(path: &str) -> String {
2132 for extension in [
2133 ".d.ts", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".rs",
2134 ] {
2135 if let Some(stem) = path.strip_suffix(extension) {
2136 return stem.to_string();
2137 }
2138 }
2139 path.rsplit_once('.').map_or_else(
2140 || path.to_string(),
2141 |(stem, extension)| {
2142 if extension.contains('/') {
2143 path.to_string()
2144 } else {
2145 stem.to_string()
2146 }
2147 },
2148 )
2149}
2150
2151#[cfg(test)]
2152mod tests {
2153 use super::{
2154 ImportReference, ImportSyntax, ResolutionProjectionContext, ResolutionProjectionError,
2155 SEMANTIC_RESOLUTION_CONTRACT_VERSION, build_import_scope_cache, derive_resolution_keys,
2156 derive_resolution_keys_with_context, parse_import_references, resolve_relative_import_path,
2157 semantic_resolution_contract_digest,
2158 };
2159 use crate::{
2160 ConfiguredModuleResolution, EcmaScriptConfigKind, EcmaScriptModuleConfig,
2161 EcmaScriptPathMapping, extract_symbol_graph,
2162 };
2163 use projectatlas_core::graph::{CanonicalResolutionKey, ProjectInstanceId};
2164 use projectatlas_core::language::SemanticProviderOwner;
2165 use projectatlas_core::symbols::{
2166 CodeSymbol, ParserKind, RelationKind, SymbolGraph, SymbolKind, SymbolRelation,
2167 };
2168 use std::error::Error;
2169 use std::io;
2170
2171 fn reference(
2172 syntax: ImportSyntax,
2173 module: &str,
2174 imported: Option<&str>,
2175 local: &str,
2176 ) -> ImportReference {
2177 ImportReference::new(syntax, module, imported, local)
2178 }
2179
2180 #[test]
2181 fn explicit_forwarding_slots_have_separate_file_owned_lookup_budgets()
2182 -> Result<(), Box<dyn Error>> {
2183 use std::fmt::Write;
2184 let project = project_id(91)?;
2185 let control = projectatlas_core::IndexWorkControl::new(
2186 projectatlas_core::IndexCancellation::new(),
2187 None,
2188 );
2189 let mut source = String::new();
2190 for index in 0..80 {
2191 writeln!(
2192 source,
2193 "export {{ work as slot{index} }} from './worker.js';"
2194 )?;
2195 }
2196 source.push_str("export type { Item as typed } from './types'; export * from './wild';");
2197 let extracted = crate::extract_symbol_graph_with_semantics_controlled(
2198 "src/barrel.ts",
2199 Some("typescript"),
2200 &source,
2201 &control,
2202 )?;
2203 let projection = derive_resolution_keys_with_context(
2204 project,
2205 None,
2206 extracted.graph(),
2207 ResolutionProjectionContext::default().with_source_semantics(extracted.semantics()),
2208 )?;
2209 require(
2210 projection.export_keys().len() == 80,
2211 "forwarding slots used an aggregate file fanout budget or admitted a type/wildcard slot",
2212 )?;
2213 require(
2214 projection.symbol_keys().is_empty(),
2215 "forwarding slot synthesized declaration keys",
2216 )?;
2217 require(
2218 projection.export_lookup_vector_bytes() > 0,
2219 "slot vector capacity not accounted",
2220 )?;
2221 for export in projection.export_keys() {
2222 require(
2223 !export.keys().is_empty()
2224 && export.keys().len() <= super::MAX_RESOLUTION_KEYS_PER_FACT,
2225 "per-slot key bound",
2226 )?;
2227 require(
2228 matches!(
2229 extracted.semantics().exports()[export.export_index()].binding,
2230 crate::SourceExportBinding::ReExport(_)
2231 ),
2232 "lookup keys lost source association",
2233 )?;
2234 }
2235 let caller = crate::extract_symbol_graph_with_semantics_controlled(
2236 "src/caller.ts",
2237 Some("typescript"),
2238 "import {slot79} from './barrel'; function caller() {slot79();}",
2239 &control,
2240 )?;
2241 let caller_projection = derive_resolution_keys_with_context(
2242 project,
2243 None,
2244 caller.graph(),
2245 ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2246 )?;
2247 require(
2248 projection.export_keys().iter().any(|slot| {
2249 keys_intersect(
2250 relation_keys(caller.graph(), &caller_projection, "slot79"),
2251 slot.keys(),
2252 )
2253 }),
2254 "consumer cannot find file-owned forwarding slot",
2255 )?;
2256 Ok(())
2257 }
2258
2259 #[test]
2260 fn invalid_forwarding_identity_retains_separately_valid_export_slots()
2261 -> Result<(), Box<dyn Error>> {
2262 let project = project_id(92)?;
2263 let control = projectatlas_core::IndexWorkControl::new(
2264 projectatlas_core::IndexCancellation::new(),
2265 None,
2266 );
2267 let extracted = crate::extract_symbol_graph_with_semantics_controlled(
2268 "src/barrel.js",
2269 Some("javascript"),
2270 "export { work as '' } from './worker';\nexport {work as good} from './worker';",
2271 &control,
2272 )?;
2273 let Err(ResolutionProjectionError::Contract(failure)) = derive_resolution_keys_with_context(
2274 project,
2275 None,
2276 extracted.graph(),
2277 ResolutionProjectionContext::default().with_source_semantics(extracted.semantics()),
2278 ) else {
2279 return Err("invalid export lookup identity did not reach admission".into());
2280 };
2281 require(
2282 failure.rejected_count() == 1
2283 && failure.failures()[0].fact() == super::ResolutionProjectionFact::Export(0),
2284 "export rejection did not retain exact source ordinal",
2285 )?;
2286 require(
2287 failure.projection().export_keys().len() == 1
2288 && failure.projection().export_keys()[0].export_index() == 1,
2289 "invalid slot erased valid sibling",
2290 )?;
2291 Ok(())
2292 }
2293
2294 #[test]
2295 fn duplicate_default_slots_never_narrow_to_one_surviving_declaration()
2296 -> Result<(), Box<dyn Error>> {
2297 let project = project_id(87)?;
2298 let control = projectatlas_core::IndexWorkControl::new(
2299 projectatlas_core::IndexCancellation::new(),
2300 None,
2301 );
2302 for (language, path) in [
2303 ("javascript", "target.js"),
2304 ("typescript", "target.ts"),
2305 ("tsx", "target.tsx"),
2306 ("vue", "target.vue"),
2307 ("svelte", "target.svelte"),
2308 ("html", "target.html"),
2309 ] {
2310 for second in [
2311 "export default class Second {}",
2312 "export default function () {}",
2313 "export default {};",
2314 "export default function First() {}",
2315 "export { secondary as default };",
2316 "export { secondary as 'default' };",
2317 "export { default } from './other';",
2318 "export * as default from './other';",
2319 ] {
2320 let script = format!(
2321 "export default function First() {{}}\n{second}\nexport function secondary() {{}}"
2322 );
2323 let content = if matches!(language, "vue" | "svelte" | "html") {
2324 let role = match language {
2325 "html" => "type=module",
2326 "svelte" => "module",
2327 _ => "",
2328 };
2329 format!("<script {role}>{script}</script>")
2330 } else {
2331 script
2332 };
2333 let extracted = crate::extract_symbol_graph_with_semantics_controlled(
2334 path,
2335 Some(language),
2336 &content,
2337 &control,
2338 )?;
2339 let projection = derive_resolution_keys_with_context(
2340 project,
2341 None,
2342 extracted.graph(),
2343 ResolutionProjectionContext::default()
2344 .with_source_semantics(extracted.semantics()),
2345 )?;
2346 if !symbol_keys(extracted.graph(), &projection, "First").is_empty() {
2347 return Err(
2348 format!("duplicate default gained a target: {language}: {second}").into(),
2349 );
2350 }
2351 if symbol_keys(extracted.graph(), &projection, "secondary").is_empty() {
2352 return Err(format!(
2353 "invalid default removed independent named export: {language}: {second}"
2354 )
2355 .into());
2356 }
2357 let (_, mut graph, mut facts) = extracted.into_parts();
2358 let symbols = graph
2359 .symbols
2360 .iter()
2361 .map(|symbol| symbol.name != "Second")
2362 .collect::<Vec<_>>();
2363 let relations = vec![true; graph.relations.len()];
2364 facts.retain_graph_facts(&mut graph, &symbols, &relations)?;
2365 let projection = derive_resolution_keys_with_context(
2366 project,
2367 None,
2368 &graph,
2369 ResolutionProjectionContext::default().with_source_semantics(&facts),
2370 )?;
2371 require(
2372 symbol_keys(&graph, &projection, "First").is_empty(),
2373 "filtering a competing default narrowed export proof",
2374 )?;
2375 }
2376 }
2377 Ok(())
2378 }
2379
2380 #[test]
2381 fn duplicate_export_slots_do_not_erase_independent_names_for_one_declaration()
2382 -> Result<(), Box<dyn Error>> {
2383 let project = project_id(88)?;
2384 let control = projectatlas_core::IndexWorkControl::new(
2385 projectatlas_core::IndexCancellation::new(),
2386 None,
2387 );
2388 let caller = crate::extract_symbol_graph_with_semantics_controlled(
2389 "src/caller.js",
2390 Some("javascript"),
2391 "import Entry, { named, duplicate, Actual } from './target'; function caller() { Entry(); named(); duplicate(); Actual(); }",
2392 &control,
2393 )?;
2394 let caller_keys = derive_resolution_keys_with_context(
2395 project,
2396 None,
2397 caller.graph(),
2398 ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2399 )?;
2400 for source in [
2401 "export default function Actual() {} export { Actual as named, Actual as duplicate }; export { missing as default, missing as duplicate };",
2402 "function Actual() {} export { Actual as default, Actual as named, Actual as duplicate }; export { missing as default, missing as duplicate };",
2403 ] {
2404 let target = crate::extract_symbol_graph_with_semantics_controlled(
2405 "src/target.js",
2406 Some("javascript"),
2407 source,
2408 &control,
2409 )?;
2410 let target_keys = derive_resolution_keys_with_context(
2411 project,
2412 None,
2413 target.graph(),
2414 ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2415 )?;
2416 let actual = symbol_keys(target.graph(), &target_keys, "Actual");
2417 require(
2418 keys_intersect(relation_keys(caller.graph(), &caller_keys, "named"), actual),
2419 "duplicate removed independent named slot",
2420 )?;
2421 for name in ["Entry", "duplicate", "Actual"] {
2422 require(
2423 !keys_intersect(relation_keys(caller.graph(), &caller_keys, name), actual),
2424 "duplicate or absent slot fabricated a target",
2425 )?;
2426 }
2427 }
2428 Ok(())
2429 }
2430
2431 #[test]
2432 fn python_rebound_module_names_withhold_only_their_export_keys() -> Result<(), Box<dyn Error>> {
2433 let project = project_id(93)?;
2434 let control = projectatlas_core::IndexWorkControl::new(
2435 projectatlas_core::IndexCancellation::new(),
2436 None,
2437 );
2438 for (suffix, rebound) in [
2439 ("def mutate():\n global run\n run = unknown\n", true),
2440 ("run = unknown\n", true),
2441 (
2442 "def invalid():\n run()\n global run\n run = unknown\n",
2443 false,
2444 ),
2445 ] {
2446 let source =
2447 format!("def run(): pass\ndef safe(): pass\ndef _private(): pass\n{suffix}");
2448 let extracted = crate::extract_symbol_graph_with_semantics_controlled(
2449 "worker.py",
2450 Some("python"),
2451 &source,
2452 &control,
2453 )?;
2454 let keys = derive_resolution_keys_with_context(
2455 project,
2456 None,
2457 extracted.graph(),
2458 ResolutionProjectionContext::default().with_source_semantics(extracted.semantics()),
2459 )?;
2460 require(
2461 symbol_keys(extracted.graph(), &keys, "run").is_empty() == rebound,
2462 "Python export authority disagreed with valid writer evidence",
2463 )?;
2464 require(
2465 symbol_keys(extracted.graph(), &keys, "_private").is_empty(),
2466 "source export roles exposed a private Python declaration",
2467 )?;
2468 require(
2469 !symbol_keys(extracted.graph(), &keys, "safe").is_empty(),
2470 "unrelated Python export lost its callable keys",
2471 )?;
2472 }
2473 Ok(())
2474 }
2475
2476 #[test]
2477 fn type_and_ordinary_value_exports_do_not_supply_callable_keys() -> Result<(), Box<dyn Error>> {
2478 let project = project_id(90)?;
2479 let control = projectatlas_core::IndexWorkControl::new(
2480 projectatlas_core::IndexCancellation::new(),
2481 None,
2482 );
2483 for (language, extension, source) in [
2484 (
2485 "typescript",
2486 "ts",
2487 "export type Alias = () => void; export interface Shape {} export class Runtime {} export enum Values { One } export const scalar = 1; export function run() {}",
2488 ),
2489 (
2490 "tsx",
2491 "tsx",
2492 "export type Alias = () => void; export interface Shape {} export class Runtime {} export enum Values { One } export const scalar = 1; export function run() {}",
2493 ),
2494 (
2495 "rust",
2496 "rs",
2497 "pub type Alias = fn(); pub trait Shape {} pub struct Runtime(); pub enum Values { One } pub const scalar: i32 = 1; pub fn run() {}",
2498 ),
2499 (
2500 "rust-build-script",
2501 "rs",
2502 "pub type Alias = fn(); pub trait Shape {} pub struct Runtime(); pub enum Values { One } pub const scalar: i32 = 1; pub fn run() {}",
2503 ),
2504 ] {
2505 let target = crate::extract_symbol_graph_with_semantics_controlled(
2506 &format!("src/worker.{extension}"),
2507 Some(language),
2508 source,
2509 &control,
2510 )?;
2511 let keys = derive_resolution_keys_with_context(
2512 project,
2513 None,
2514 target.graph(),
2515 ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2516 )?;
2517 for name in ["Alias", "Shape", "scalar"] {
2518 require(
2519 symbol_keys(target.graph(), &keys, name).is_empty(),
2520 "type-only declaration supplied call keys",
2521 )?;
2522 }
2523 require(
2524 !symbol_keys(target.graph(), &keys, "run").is_empty(),
2525 "function positive lost callable keys",
2526 )?;
2527 require(
2528 !symbol_keys(target.graph(), &keys, "Runtime").is_empty(),
2529 "runtime constructor positive lost callable keys",
2530 )?;
2531 require(
2532 !symbol_keys(target.graph(), &keys, "Values").is_empty(),
2533 "runtime enum export compatibility lost keys",
2534 )?;
2535 }
2536 Ok(())
2537 }
2538
2539 #[test]
2540 fn type_only_export_names_do_not_suppress_runtime_callable_exports()
2541 -> Result<(), Box<dyn Error>> {
2542 let project = project_id(89)?;
2543 let control = projectatlas_core::IndexWorkControl::new(
2544 projectatlas_core::IndexCancellation::new(),
2545 None,
2546 );
2547 let caller = crate::extract_symbol_graph_with_semantics_controlled(
2548 "src/caller.ts",
2549 Some("typescript"),
2550 "import { named } from './target'; function caller() { named(); }",
2551 &control,
2552 )?;
2553 let caller_keys = derive_resolution_keys_with_context(
2554 project,
2555 None,
2556 caller.graph(),
2557 ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2558 )?;
2559 let target = crate::extract_symbol_graph_with_semantics_controlled(
2560 "src/target.ts",
2561 Some("typescript"),
2562 "type Item = unknown; function run() {} export type {Item as named}; export {type Item as typed, run as named};",
2563 &control,
2564 )?;
2565 let target_keys = derive_resolution_keys_with_context(
2566 project,
2567 None,
2568 target.graph(),
2569 ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2570 )?;
2571 require(
2572 keys_intersect(
2573 relation_keys(caller.graph(), &caller_keys, "named"),
2574 symbol_keys(target.graph(), &target_keys, "run"),
2575 ),
2576 "type-only slot suppressed runtime export",
2577 )?;
2578 require(
2579 symbol_keys(target.graph(), &target_keys, "Item").is_empty(),
2580 "type-only export became callable",
2581 )?;
2582 Ok(())
2583 }
2584
2585 #[test]
2586 fn source_default_slots_target_named_declarations_without_named_export_guessing()
2587 -> Result<(), Box<dyn Error>> {
2588 let project = ProjectInstanceId::from_bytes([81; 16])?;
2589 let control = projectatlas_core::IndexWorkControl::new(
2590 projectatlas_core::IndexCancellation::new(),
2591 None,
2592 );
2593 for (language, extension) in [("javascript", "js"), ("typescript", "ts"), ("tsx", "tsx")] {
2594 let caller = crate::extract_symbol_graph_with_semantics_controlled(
2595 &format!("src/caller.{extension}"),
2596 Some(language),
2597 "import Entry, { secondary as named } from './target'; function caller() { Entry(); named(); }",
2598 &control,
2599 )?;
2600 let caller_keys = derive_resolution_keys_with_context(
2601 project,
2602 None,
2603 caller.graph(),
2604 ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2605 )?;
2606 let leaked = crate::extract_symbol_graph_with_semantics_controlled(
2607 &format!("src/leaked.{extension}"),
2608 Some(language),
2609 "import { Actual as leaked } from './target'; function caller() { leaked(); }",
2610 &control,
2611 )?;
2612 let leaked_keys = derive_resolution_keys_with_context(
2613 project,
2614 None,
2615 leaked.graph(),
2616 ResolutionProjectionContext::default().with_source_semantics(leaked.semantics()),
2617 )?;
2618 for declaration in ["function Actual() {}", "class Actual {}"] {
2619 let target = crate::extract_symbol_graph_with_semantics_controlled(
2620 &format!("src/target.{extension}"),
2621 Some(language),
2622 &format!("export default {declaration}\nexport function secondary() {{}}"),
2623 &control,
2624 )?;
2625 let target_keys = derive_resolution_keys_with_context(
2626 project,
2627 None,
2628 target.graph(),
2629 ResolutionProjectionContext::default()
2630 .with_source_semantics(target.semantics()),
2631 )?;
2632 let actual = symbol_keys(target.graph(), &target_keys, "Actual");
2633 require(
2634 keys_intersect(relation_keys(caller.graph(), &caller_keys, "Entry"), actual),
2635 "default slot did not identify actual declaration",
2636 )?;
2637 require(
2638 !keys_intersect(
2639 relation_keys(leaked.graph(), &leaked_keys, "leaked"),
2640 actual,
2641 ),
2642 "default declaration fabricated a named export",
2643 )?;
2644 require(
2645 keys_intersect(
2646 relation_keys(caller.graph(), &caller_keys, "named"),
2647 symbol_keys(target.graph(), &target_keys, "secondary"),
2648 ),
2649 "default slot lost named sibling",
2650 )?;
2651 }
2652 }
2653 Ok(())
2654 }
2655
2656 #[test]
2657 fn default_export_roles_survive_hosts_and_never_narrow_coalesced_declarations()
2658 -> Result<(), Box<dyn Error>> {
2659 let project = ProjectInstanceId::from_bytes([82; 16])?;
2660 let control = projectatlas_core::IndexWorkControl::new(
2661 projectatlas_core::IndexCancellation::new(),
2662 None,
2663 );
2664 let caller = crate::extract_symbol_graph_with_semantics_controlled(
2665 "src/caller.js",
2666 Some("javascript"),
2667 "import Entry, { Actual as leaked } from './target'; function caller() { Entry(); leaked(); }",
2668 &control,
2669 )?;
2670 let caller_keys = derive_resolution_keys_with_context(
2671 project,
2672 None,
2673 caller.graph(),
2674 ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2675 )?;
2676 for (language, attributes, admits_default) in [
2677 ("vue", "", true),
2678 ("vue", "setup", false),
2679 ("svelte", "module", false),
2680 ("svelte", "", false),
2681 ("html", "type=module", true),
2682 ("html", "", false),
2683 ] {
2684 for (source, valid) in [
2685 ("export default function Actual() {}", true),
2686 (
2687 "export default function Actual() {} export default function Actual() {}",
2688 false,
2689 ),
2690 ] {
2691 let target = crate::extract_symbol_graph_with_semantics_controlled(
2692 &format!("src/target.{language}"),
2693 Some(language),
2694 &format!("<script {attributes}>{source}</script>"),
2695 &control,
2696 )?;
2697 let keys = derive_resolution_keys_with_context(
2698 project,
2699 None,
2700 target.graph(),
2701 ResolutionProjectionContext::default()
2702 .with_source_semantics(target.semantics()),
2703 )?;
2704 let actual = symbol_keys(target.graph(), &keys, "Actual");
2705 require(
2706 keys_intersect(relation_keys(caller.graph(), &caller_keys, "Entry"), actual)
2707 == (valid && admits_default),
2708 "host default target correspondence changed",
2709 )?;
2710 require(
2711 !keys_intersect(
2712 relation_keys(caller.graph(), &caller_keys, "leaked"),
2713 actual,
2714 ),
2715 "host coalescing fabricated the default declaration as a named export",
2716 )?;
2717 require(
2718 !keys.source_keys().is_empty(),
2719 "default ambiguity discarded admitted module evidence",
2720 )?;
2721 }
2722 }
2723 Ok(())
2724 }
2725
2726 #[test]
2727 fn component_export_targets_require_unambiguous_script_roles() -> Result<(), Box<dyn Error>> {
2728 let project = project_id(90)?;
2729 let control = projectatlas_core::IndexWorkControl::new(
2730 projectatlas_core::IndexCancellation::new(),
2731 None,
2732 );
2733 let caller = crate::extract_symbol_graph_with_semantics_controlled(
2734 "caller.js",
2735 Some("javascript"),
2736 "import {run} from './target'; function caller(){run();}",
2737 &control,
2738 )?;
2739 let caller_keys = derive_resolution_keys_with_context(
2740 project,
2741 None,
2742 caller.graph(),
2743 ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2744 )?;
2745 let verify = |language: &str, source: &str, valid: bool| -> Result<(), Box<dyn Error>> {
2746 let target = crate::extract_symbol_graph_with_semantics_controlled(
2747 &format!("target.{language}"),
2748 Some(language),
2749 source,
2750 &control,
2751 )?;
2752 let keys = derive_resolution_keys_with_context(
2753 project,
2754 None,
2755 target.graph(),
2756 ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2757 )?;
2758 require(
2759 keys_intersect(
2760 relation_keys(caller.graph(), &caller_keys, "run"),
2761 symbol_keys(target.graph(), &keys, "run"),
2762 ) == valid,
2763 "host role changed its exported target authority",
2764 )
2765 };
2766 for (language, attributes, valid) in [
2767 ("svelte", "context=instance context=module", false),
2768 ("svelte", "context=module context=module", false),
2769 ("svelte", "module module", false),
2770 ("svelte", "module context=instance", false),
2771 ("svelte", "module='false'", false),
2772 ("vue", "setup setup", false),
2773 ("vue", "", true),
2774 ("svelte", "module", true),
2775 ("svelte", "context=module", true),
2776 ("svelte", "module context=module", true),
2777 ] {
2778 verify(
2779 language,
2780 &format!("<script {attributes}>export function run(){{}}</script>"),
2781 valid,
2782 )?;
2783 }
2784 for (language, source) in [
2785 (
2786 "vue",
2787 "<script>export function run(){}</script><script>function other(){}</script>",
2788 ),
2789 (
2790 "svelte",
2791 "<script module>export function run(){}</script><script module>function other(){}</script>",
2792 ),
2793 ] {
2794 verify(language, source, false)?;
2795 }
2796
2797 Ok(())
2798 }
2799
2800 #[test]
2801 fn default_expressions_and_anonymous_exports_do_not_select_a_named_sibling()
2802 -> Result<(), Box<dyn Error>> {
2803 let project = ProjectInstanceId::from_bytes([83; 16])?;
2804 let control = projectatlas_core::IndexWorkControl::new(
2805 projectatlas_core::IndexCancellation::new(),
2806 None,
2807 );
2808 let caller = crate::extract_symbol_graph_with_semantics_controlled(
2809 "src/caller.js",
2810 Some("javascript"),
2811 "import Entry from './target'; function caller() { Entry(); }",
2812 &control,
2813 )?;
2814 let caller_keys = derive_resolution_keys_with_context(
2815 project,
2816 None,
2817 caller.graph(),
2818 ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2819 )?;
2820 for source in [
2821 "export function Only() {} export default Only;",
2822 "export function Only() {} export default function() {}",
2823 "export function Only() {} export default { run() {} };",
2824 ] {
2825 let target = crate::extract_symbol_graph_with_semantics_controlled(
2826 "src/target.js",
2827 Some("javascript"),
2828 source,
2829 &control,
2830 )?;
2831 let keys = derive_resolution_keys_with_context(
2832 project,
2833 None,
2834 target.graph(),
2835 ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2836 )?;
2837 for symbol in keys.symbol_keys() {
2838 require(
2839 !keys_intersect(
2840 relation_keys(caller.graph(), &caller_keys, "Entry"),
2841 symbol.keys(),
2842 ),
2843 "unsupported default expression guessed an unrelated declaration",
2844 )?;
2845 }
2846 require(
2847 !keys.source_keys().is_empty(),
2848 "unsupported default lost module dependency evidence",
2849 )?;
2850 }
2851 Ok(())
2852 }
2853
2854 #[test]
2855 fn contract_rejection_flags_admit_actual_bool_vector_storage() -> Result<(), Box<dyn Error>> {
2856 let counts = [19_usize, 23, 29];
2857 let header = std::mem::size_of::<super::ContractFailures>() as u64;
2858 let flags = (counts.iter().sum::<usize>() * std::mem::size_of::<bool>()) as u64;
2859 let exact = header + flags;
2860 let admitted = super::ContractFailures::new(
2861 counts[0],
2862 counts[1],
2863 counts[2],
2864 super::ProjectionBudget::new(Some(exact)),
2865 )?;
2866 let actual = header
2867 + ([
2868 admitted.symbol_seen.capacity(),
2869 admitted.relation_seen.capacity(),
2870 admitted.export_seen.capacity(),
2871 ]
2872 .iter()
2873 .sum::<usize>()
2874 * std::mem::size_of::<bool>()) as u64;
2875 require(
2876 actual <= exact && admitted.budget.used >= actual,
2877 "dense rejection flags were charged as packed bits instead of owned bool bytes",
2878 )?;
2879 require(
2880 matches!(
2881 super::ContractFailures::new(
2882 counts[0],
2883 counts[1],
2884 counts[2],
2885 super::ProjectionBudget::new(Some(exact - 1))
2886 ),
2887 Err(ResolutionProjectionError::MemoryLimit { .. })
2888 ),
2889 "flag vectors were allocated past the remaining byte allowance",
2890 )?;
2891 super::ContractFailures::new(
2892 counts[0],
2893 counts[1],
2894 counts[2],
2895 super::ProjectionBudget::new(None),
2896 )?;
2897 Ok(())
2898 }
2899
2900 #[test]
2901 fn semantic_resolution_contract_digest_is_bounded_and_deterministic() {
2902 const PRE_ARROW_CONTEXT_DIGEST: &str =
2903 "66cab1855f1689308cdc5bb858a63e8d3ed42e825b073d62cada7347383731a7";
2904 const PRE_SOURCE_CONTEXT_DIGEST: &str =
2905 "784435da80445735c78b112bebab6352f74c660d550399a9617dae87d2df0424";
2906 const PRE_GENERATOR_DECLARATION_DIGEST: &str =
2907 "4542cd9a22a1df432d3a3d212f2ee5b6c52067411c3f54ca77221566a8f2ab7d";
2908 const PRE_MODULE_CALLBACK_DIGEST: &str =
2909 "487625adf2f9ec76f98034d4ef5667e707960b6b8afd280b213021cb64a0f10f";
2910 let first = semantic_resolution_contract_digest();
2911 assert_eq!(SEMANTIC_RESOLUTION_CONTRACT_VERSION, 23);
2912 assert_eq!(first.len(), 64);
2913 assert!(first.bytes().all(|byte| byte.is_ascii_hexdigit()));
2914 assert_eq!(first, semantic_resolution_contract_digest());
2915 assert_ne!(first, PRE_MODULE_CALLBACK_DIGEST);
2916 assert_ne!(first, PRE_GENERATOR_DECLARATION_DIGEST);
2917 assert_ne!(first, PRE_SOURCE_CONTEXT_DIGEST);
2918 assert_ne!(first, PRE_ARROW_CONTEXT_DIGEST);
2919 }
2920
2921 #[test]
2922 fn semantic_resolution_contract_tracks_identifier_data_version() {
2923 let before = super::semantic_resolution_contract_digest_for_identifier_profile((17, 0, 0));
2924 let after = super::semantic_resolution_contract_digest_for_identifier_profile((18, 0, 0));
2925 assert_ne!(before, after);
2926 assert_eq!(
2927 semantic_resolution_contract_digest(),
2928 super::semantic_resolution_contract_digest_for_identifier_profile(
2929 unicode_ident::UNICODE_VERSION
2930 )
2931 );
2932 }
2933
2934 #[test]
2935 fn parses_language_import_aliases_without_display_statement_identity() {
2936 assert_eq!(
2937 parse_import_references("use crate::worker::{run as execute, stop};"),
2938 vec![
2939 reference(ImportSyntax::Rust, "crate::worker", Some("run"), "execute"),
2940 reference(ImportSyntax::Rust, "crate::worker", Some("stop"), "stop"),
2941 ]
2942 );
2943 assert_eq!(
2944 parse_import_references("use crate::worker::run_function_alias as run_rust_function;"),
2945 vec![reference(
2946 ImportSyntax::Rust,
2947 "crate::worker",
2948 Some("run_function_alias"),
2949 "run_rust_function"
2950 ),]
2951 );
2952 assert_eq!(
2953 parse_import_references("import * as reader from './reader';"),
2954 vec![reference(
2955 ImportSyntax::EcmaScript,
2956 "./reader",
2957 None,
2958 "reader",
2959 )]
2960 );
2961 assert_eq!(
2962 parse_import_references("import { runAlias as runLocal } from './worker';"),
2963 vec![reference(
2964 ImportSyntax::EcmaScript,
2965 "./worker",
2966 Some("runAlias"),
2967 "runLocal",
2968 )]
2969 );
2970 assert_eq!(
2971 parse_import_references("from package.reader import read as load, close"),
2972 vec![
2973 reference(
2974 ImportSyntax::Python,
2975 "package.reader",
2976 Some("close"),
2977 "close"
2978 ),
2979 reference(ImportSyntax::Python, "package.reader", Some("read"), "load"),
2980 ]
2981 );
2982 assert_eq!(
2983 parse_import_references("from package.worker import run_alias as execute"),
2984 vec![reference(
2985 ImportSyntax::Python,
2986 "package.worker",
2987 Some("run_alias"),
2988 "execute",
2989 )]
2990 );
2991 assert!(parse_import_references("import { broken from './reader'").is_empty());
2992 assert!(parse_import_references("use crate::worker::run as ;").is_empty());
2993 assert!(parse_import_references("import { read } from module './reader'").is_empty());
2994 for malformed in [
2995 "use crate::worker::{run, nested::{start, stop}};",
2996 "use crate::worker::{run, broken as };",
2997 "import defaultValue, { read } unexpected from './reader';",
2998 "import { read, nested { broken } } from './reader';",
2999 "import { read as load as fetch } from './reader';",
3000 "from package.reader import read, broken as ",
3001 "import package.reader as reader as other",
3002 ] {
3003 assert!(
3004 parse_import_references(malformed).is_empty(),
3005 "unsupported syntax fabricated references: {malformed}"
3006 );
3007 }
3008 }
3009
3010 #[test]
3011 fn normalizes_flat_parenthesized_python_imports_without_guessing() {
3012 let expected = vec![
3013 reference(
3014 ImportSyntax::Python,
3015 "package.reader",
3016 Some("close"),
3017 "close",
3018 ),
3019 reference(ImportSyntax::Python, "package.reader", Some("read"), "load"),
3020 ];
3021 for statement in [
3022 "from package.reader import (read as load, close)",
3023 "from package.reader import (read as load, close,)",
3024 "from package.reader import (\n read as load,\n close,\n)",
3025 ] {
3026 assert_eq!(parse_import_references(statement), expected, "{statement}");
3027 }
3028 assert_eq!(
3029 parse_import_references("from .reader import (read,)"),
3030 vec![reference(
3031 ImportSyntax::Python,
3032 ".reader",
3033 Some("read"),
3034 "read"
3035 )]
3036 );
3037 assert_eq!(
3038 parse_import_references("from match.case import (type as match, case)"),
3039 vec![
3040 reference(ImportSyntax::Python, "match.case", Some("case"), "case"),
3041 reference(ImportSyntax::Python, "match.case", Some("type"), "match"),
3042 ]
3043 );
3044 for statement in [
3045 "from package.reader import ()",
3046 "from package.reader import (read",
3047 "from package.reader import read)",
3048 "from package.reader import ((read))",
3049 "from package.reader import (read),",
3050 "from package.reader import (read,,close)",
3051 "from package.reader import (,read)",
3052 "from package.reader import (read,,)",
3053 "from package.reader import (read as)",
3054 "from package.reader import (read as load as other)",
3055 "from package.reader import (read.attr)",
3056 "from package.reader import (read as load.attr)",
3057 "from package.reader import (class)",
3058 "from package.reader import (read as None)",
3059 "from package.class import (read)",
3060 "from package.reader import (read())",
3061 "from package.reader import ([read])",
3062 "from package.reader import ({read})",
3063 "from package.reader import (*)",
3064 "from package.reader import (read, # note\n close)",
3065 ] {
3066 assert!(parse_import_references(statement).is_empty(), "{statement}");
3067 }
3068 }
3069
3070 #[test]
3071 fn preserves_unicode_python_import_names_and_aliases() {
3072 let name = "a\u{301}";
3073 let alias = "r\u{301}";
3074 let module = "helper\u{301}.reader";
3075 assert_eq!(
3076 parse_import_references(&format!("from {module} import (run)")),
3077 vec![reference(ImportSyntax::Python, module, Some("run"), "run")]
3078 );
3079 for list in [
3080 format!("{name}, run"),
3081 format!("({name}, run,)"),
3082 format!("(\n{name},\nrun,\n)"),
3083 ] {
3084 assert_eq!(
3085 parse_import_references(&format!("from helper import {list}")),
3086 vec![
3087 reference(ImportSyntax::Python, "helper", Some(name), name),
3088 reference(ImportSyntax::Python, "helper", Some("run"), "run"),
3089 ]
3090 );
3091 }
3092 for list in [
3093 format!("run as {alias}, {name}"),
3094 format!("(run as {alias}, {name},)"),
3095 ] {
3096 assert_eq!(
3097 parse_import_references(&format!("from helper import {list}")),
3098 vec![
3099 reference(ImportSyntax::Python, "helper", Some(name), name),
3100 reference(ImportSyntax::Python, "helper", Some("run"), alias),
3101 ]
3102 );
3103 }
3104 for invalid in ["\u{301}a", "a\u{2022}", "a\u{b2}", "a\u{200c}", "a\u{200d}"] {
3105 assert!(
3106 parse_import_references(&format!("from helper import ({invalid}, run)")).is_empty()
3107 );
3108 assert!(
3109 parse_import_references(&format!("from helper import (run as {invalid})"))
3110 .is_empty()
3111 );
3112 assert!(
3113 parse_import_references(&format!("from {invalid}.reader import run")).is_empty()
3114 );
3115 }
3116 }
3117
3118 #[test]
3119 fn resolves_relative_imports_without_platform_path_rules() {
3120 assert_eq!(
3121 resolve_relative_import_path("src/features/main.ts", "../shared/reader.ts"),
3122 Some("src/shared/reader".to_string())
3123 );
3124 assert_eq!(
3125 resolve_relative_import_path("src/main.ts", "../../outside"),
3126 None
3127 );
3128 assert_eq!(resolve_relative_import_path("src/main.ts", "reader"), None);
3129 assert_eq!(
3130 resolve_relative_import_path("src/foo.bar/main.ts", "./reader"),
3131 Some("src/foo.bar/reader".to_string())
3132 );
3133 }
3134
3135 #[test]
3136 fn explicit_ecmascript_suffixes_do_not_merge_module_or_callable_targets()
3137 -> Result<(), Box<dyn Error>> {
3138 let project = project_id(94)?;
3139 let configured = ConfiguredModuleResolution::new(vec![EcmaScriptModuleConfig::new(
3140 "tsconfig.json",
3141 EcmaScriptConfigKind::TypeScript,
3142 Some("src".to_string()),
3143 vec![EcmaScriptPathMapping::new(
3144 "@/*",
3145 vec!["src/*".to_string()],
3146 )?],
3147 )?])?;
3148 let control = projectatlas_core::IndexWorkControl::new(
3149 projectatlas_core::IndexCancellation::new(),
3150 None,
3151 );
3152 let targets = [
3153 ("js", "javascript", "export function run(){}"),
3154 ("jsx", "javascript", "export function run(){}"),
3155 ("ts", "typescript", "export function run(){}"),
3156 ("tsx", "tsx", "export function run(){}"),
3157 ("d.ts", "typescript", "export declare function run(): void;"),
3158 ("mjs", "javascript", "export function run(){}"),
3159 ("cjs", "javascript", "function run(){}"),
3160 ("vue", "vue", "<script>export function run(){}</script>"),
3161 (
3162 "svelte",
3163 "svelte",
3164 "<script module>export function run(){}</script>",
3165 ),
3166 (
3167 "html",
3168 "html",
3169 "<script type='module'>export function run(){}</script>",
3170 ),
3171 (
3172 "htm",
3173 "html",
3174 "<script type='module'>export function run(){}</script>",
3175 ),
3176 ("js.ts", "typescript", "export function run(){}"),
3177 ("json.ts", "typescript", "export function run(){}"),
3178 ("widget", "typescript", "export function run(){}"),
3179 ];
3180 for (specifier, allowed) in [
3181 ("target.svelte", &["svelte"][..]),
3182 ("target.html", &["html"][..]),
3183 ("target.htm", &["htm"][..]),
3184 ("target.vue", &["vue"][..]),
3185 ("target.mjs", &["mjs"][..]),
3186 ("target.cjs", &["cjs"][..]),
3187 ("target.ts", &["ts"][..]),
3188 ("target.tsx", &["tsx"][..]),
3189 ("target.d.ts", &["d.ts"][..]),
3190 ("target.js", &["js", "jsx", "ts", "tsx", "d.ts"][..]),
3191 ("target.jsx", &["jsx", "tsx", "d.ts"][..]),
3192 ("target.json", &[][..]),
3193 ("target.widget", &["widget"][..]),
3194 ("target/index.js", &[][..]),
3195 ("../target", &[][..]),
3196 ] {
3197 for prefix in ["./", "@/", ""] {
3198 let source = format!(
3199 "import {{run}} from '{prefix}{specifier}'; function caller(){{run();}}"
3200 );
3201 let caller = crate::extract_symbol_graph_with_semantics_controlled(
3202 "src/caller.ts",
3203 Some("typescript"),
3204 &source,
3205 &control,
3206 )?;
3207 let caller_keys = derive_resolution_keys_with_context(
3208 project,
3209 Some("app"),
3210 caller.graph(),
3211 ResolutionProjectionContext::with_configured_modules(&configured)
3212 .with_source_semantics(caller.semantics()),
3213 )?;
3214 let dependencies =
3215 relation_keys_of_kind(caller.graph(), &caller_keys, RelationKind::Imports);
3216 for (suffix, language, source) in targets {
3217 let path = format!("src/target.{suffix}");
3218 let target = crate::extract_symbol_graph_with_semantics_controlled(
3219 &path,
3220 Some(language),
3221 source,
3222 &control,
3223 )?;
3224 let target_keys = derive_resolution_keys_with_context(
3225 project,
3226 Some("app"),
3227 target.graph(),
3228 ResolutionProjectionContext::default()
3229 .with_source_semantics(target.semantics()),
3230 )?;
3231 let expected = allowed.contains(&suffix);
3232 require(
3233 dependencies
3234 .iter()
3235 .any(|keys| keys_intersect(keys, target_keys.source_keys()))
3236 == expected,
3237 &format!("module: {prefix}{specifier} -> {path}"),
3238 )?;
3239 let calls = relation_keys(caller.graph(), &caller_keys, "run");
3240 let exports = symbol_keys(target.graph(), &target_keys, "run");
3241 require(
3242 keys_intersect(calls, exports)
3243 == (expected && !matches!(suffix, "cjs" | "d.ts")),
3244 &format!("call: {prefix}{specifier} -> {path}"),
3245 )?;
3246 }
3247 }
3248 }
3249 Ok(())
3250 }
3251
3252 #[test]
3253 fn ecmascript_module_paths_preserve_entry_aliases_and_literal_file_names()
3254 -> Result<(), Box<dyn Error>> {
3255 let project = project_id(95)?;
3256 for (caller_path, module, target_path, expected) in [
3257 ("src/main.ts", "./tools", "src/tools/index.ts", true),
3258 (
3259 "src/main.ts",
3260 "./tools/index.ts",
3261 "src/tools/index.ts",
3262 true,
3263 ),
3264 ("src/main.ts", "./tools.js", "src/tools/index.ts", false),
3265 (
3266 "src/foo.bar/main.ts",
3267 "./tools",
3268 "src/foo.bar/tools.ts",
3269 true,
3270 ),
3271 ("src/main.ts", "./tools", "packages/other/tools.ts", false),
3272 ("main.ts", "./target name.js", "target name.js", true),
3273 ("main.ts", "./target::name.js", "target::name.js", true),
3274 ] {
3275 let caller = extract_symbol_graph(
3276 caller_path,
3277 Some("typescript"),
3278 &format!("import {{run}} from '{module}'; function caller(){{run();}}"),
3279 );
3280 let target =
3281 extract_symbol_graph(target_path, Some("typescript"), "export function run(){}");
3282 let caller_keys = derive_resolution_keys(project, None, &caller)?;
3283 let target_keys = derive_resolution_keys(project, None, &target)?;
3284 require(
3285 relation_keys_of_kind(&caller, &caller_keys, RelationKind::Imports)
3286 .iter()
3287 .any(|keys| keys_intersect(keys, target_keys.source_keys()))
3288 == expected,
3289 &format!("module: {caller_path}: {module} -> {target_path}"),
3290 )?;
3291 require(
3292 keys_intersect(
3293 relation_keys(&caller, &caller_keys, "run"),
3294 symbol_keys(&target, &target_keys, "run"),
3295 ) == expected,
3296 &format!("call: {caller_path}: {module} -> {target_path}"),
3297 )?;
3298 }
3299 Ok(())
3300 }
3301
3302 #[test]
3303 fn real_language_graphs_share_alias_qualified_keys() -> Result<(), Box<dyn Error>> {
3304 let project = project_id(1)?;
3305
3306 let rust_target = extract_symbol_graph("src/worker.rs", Some("rust"), "pub fn run() {}\n");
3307 let rust_caller = extract_symbol_graph(
3308 "src/main.rs",
3309 Some("rust"),
3310 "use crate::worker::run as execute;\nfn main() { execute(); }\n",
3311 );
3312 assert_alias_resolution(
3313 project,
3314 Some("atlas"),
3315 &rust_target,
3316 "run",
3317 &rust_caller,
3318 &["execute"],
3319 )?;
3320 let rust_module_target = extract_symbol_graph(
3321 "src/config.rs",
3322 Some("rust"),
3323 "pub fn load_timeout_millis() -> u64 { 250 }\n",
3324 );
3325 let rust_module_caller = extract_symbol_graph(
3326 "src/handler.rs",
3327 Some("rust"),
3328 "use crate::config;\npub fn health_response() { config::load_timeout_millis(); }\n",
3329 );
3330 assert_alias_resolution(
3331 project,
3332 Some("atlas"),
3333 &rust_module_target,
3334 "load_timeout_millis",
3335 &rust_module_caller,
3336 &["config::load_timeout_millis"],
3337 )?;
3338 let rust_callback_caller = extract_symbol_graph(
3339 "src/router.rs",
3340 Some("rust"),
3341 "use crate::handler;\nfn dispatch(path: &str) -> Option<()> { (path == \"/health\").then(handler::health_response) }\n",
3342 );
3343 assert_alias_resolution(
3344 project,
3345 Some("atlas"),
3346 &rust_module_caller,
3347 "health_response",
3348 &rust_callback_caller,
3349 &["handler::health_response"],
3350 )?;
3351 let user_defined_then = extract_symbol_graph(
3352 "src/scheduler.rs",
3353 Some("rust"),
3354 "use crate::handler;\nstruct Scheduler;\nimpl Scheduler { fn then(self, callback: fn()) { let _ = callback; } }\nfn schedule(scheduler: Scheduler) { scheduler.then(handler::health_response); }\n",
3355 );
3356 if user_defined_then.relations.iter().any(|relation| {
3357 relation.kind == RelationKind::Calls
3358 && relation.target_name == "handler::health_response"
3359 }) {
3360 return Err(io::Error::other(
3361 "a user-defined then method fabricated a callback call edge",
3362 )
3363 .into());
3364 }
3365
3366 let typescript_target = extract_symbol_graph(
3367 "src/shared/reader.ts",
3368 Some("typescript"),
3369 "export function read() {}\n",
3370 );
3371 let typescript_caller = extract_symbol_graph(
3372 "src/features/main.ts",
3373 Some("typescript"),
3374 "import { read as load } from '../shared/reader';\nimport * as reader from '../shared/reader';\nexport function main() { load(); reader.read(); }\n",
3375 );
3376 assert_alias_resolution(
3377 project,
3378 Some("web"),
3379 &typescript_target,
3380 "read",
3381 &typescript_caller,
3382 &["load", "reader.read"],
3383 )?;
3384
3385 let dotted_directory_target = extract_symbol_graph(
3386 "src/foo.bar/reader.ts",
3387 Some("typescript"),
3388 "export function read() {}\n",
3389 );
3390 let dotted_directory_caller = extract_symbol_graph(
3391 "src/foo.bar/main.ts",
3392 Some("typescript"),
3393 "import { read } from './reader';\nexport function main() { read(); }\n",
3394 );
3395 assert_alias_resolution(
3396 project,
3397 Some("web"),
3398 &dotted_directory_target,
3399 "read",
3400 &dotted_directory_caller,
3401 &["read"],
3402 )?;
3403
3404 let python_target = extract_symbol_graph(
3405 "src/package/reader.py",
3406 Some("python"),
3407 "def read():\n pass\n",
3408 );
3409 let python_caller = extract_symbol_graph(
3410 "src/main.py",
3411 Some("python"),
3412 "import package.reader as reader\nreader.read()\n",
3413 );
3414 assert_alias_resolution(
3415 project,
3416 Some("python-app"),
3417 &python_target,
3418 "read",
3419 &python_caller,
3420 &["reader.read"],
3421 )?;
3422 Ok(())
3423 }
3424
3425 #[test]
3426 fn configured_aliases_share_file_and_declaration_keys_across_ecmascript_hosts()
3427 -> Result<(), Box<dyn Error>> {
3428 let project = project_id(21)?;
3429 let configured = ConfiguredModuleResolution::new(vec![
3430 EcmaScriptModuleConfig::new(
3431 "tsconfig.json",
3432 EcmaScriptConfigKind::TypeScript,
3433 Some("src".to_string()),
3434 vec![
3435 EcmaScriptPathMapping::new("@/*", vec!["src/*".to_string()])?,
3436 EcmaScriptPathMapping::new("@index/*", vec!["src/*/index.ts".to_string()])?,
3437 ],
3438 )?,
3439 EcmaScriptModuleConfig::new(
3440 "jsconfig.json",
3441 EcmaScriptConfigKind::JavaScript,
3442 Some("src".to_string()),
3443 vec![EcmaScriptPathMapping::new(
3444 "@/*",
3445 vec!["src/*".to_string()],
3446 )?],
3447 )?,
3448 ])?;
3449 let context = ResolutionProjectionContext::with_configured_modules(&configured);
3450
3451 for (target_path, language, target_source, caller_path, caller_source) in [
3452 (
3453 "src/controller.ts",
3454 "typescript",
3455 "export function useController(): string { return 'ok'; }\n",
3456 "src/page.ts",
3457 "import { useController } from '@/controller';\nexport const value = useController();\n",
3458 ),
3459 (
3460 "src/controller.ts",
3461 "typescript",
3462 "export function useController(): string { return 'ok'; }\n",
3463 "src/page.tsx",
3464 "import { useController } from '@/controller';\nexport function Page() { useController(); return <div />; }\n",
3465 ),
3466 (
3467 "src/controller.js",
3468 "javascript",
3469 "export function useController() { return 'ok'; }\n",
3470 "src/page.jsx",
3471 "import { useController } from '@/controller';\nexport function Page() { useController(); return <div />; }\n",
3472 ),
3473 (
3474 "src/controller.ts",
3475 "typescript",
3476 "export function useController(): string { return 'ok'; }\n",
3477 "src/Page.vue",
3478 "<script setup lang=\"ts\">\nimport { useController } from '@/controller';\nconst value = useController();\n</script>\n<template><div>{{ value }}</div></template>\n",
3479 ),
3480 ] {
3481 let target = extract_symbol_graph(target_path, Some(language), target_source);
3482 let caller_extension = std::path::Path::new(caller_path)
3483 .extension()
3484 .and_then(std::ffi::OsStr::to_str);
3485 let caller_language = match caller_extension {
3486 Some(extension) if extension.eq_ignore_ascii_case("vue") => "vue",
3487 Some(extension)
3488 if extension.eq_ignore_ascii_case("js")
3489 || extension.eq_ignore_ascii_case("jsx") =>
3490 {
3491 "javascript"
3492 }
3493 Some(extension) if extension.eq_ignore_ascii_case("tsx") => "tsx",
3494 _ => "typescript",
3495 };
3496 let caller = extract_symbol_graph(caller_path, Some(caller_language), caller_source);
3497 let target_projection = derive_resolution_keys(project, Some("shared"), &target)?;
3498 let caller_projection =
3499 derive_resolution_keys_with_context(project, Some("app"), &caller, context)?;
3500 let imports = relation_keys_of_kind(&caller, &caller_projection, RelationKind::Imports);
3501 require(
3502 imports
3503 .iter()
3504 .any(|keys| keys_intersect(keys, target_projection.source_keys())),
3505 "configured import did not share the target file key",
3506 )?;
3507 require(
3508 keys_intersect(
3509 relation_keys(&caller, &caller_projection, "useController"),
3510 symbol_keys(&target, &target_projection, "useController"),
3511 ),
3512 "configured call did not share the target declaration key",
3513 )?;
3514 }
3515
3516 let index_target = extract_symbol_graph(
3517 "src/tools/index.ts",
3518 Some("typescript"),
3519 "export function useTool() { return 'ok'; }\n",
3520 );
3521 let index_caller = extract_symbol_graph(
3522 "src/index-page.ts",
3523 Some("typescript"),
3524 "import { useTool } from '@index/tools';\nexport const value = useTool();\n",
3525 );
3526 let index_target_projection = derive_resolution_keys(project, Some("app"), &index_target)?;
3527 let index_caller_projection =
3528 derive_resolution_keys_with_context(project, Some("app"), &index_caller, context)?;
3529 require(
3530 relation_keys_of_kind(
3531 &index_caller,
3532 &index_caller_projection,
3533 RelationKind::Imports,
3534 )
3535 .iter()
3536 .any(|keys| keys_intersect(keys, index_target_projection.source_keys())),
3537 "configured index target did not reuse package-entry source keys",
3538 )?;
3539 require(
3540 keys_intersect(
3541 relation_keys(&index_caller, &index_caller_projection, "useTool"),
3542 symbol_keys(&index_target, &index_target_projection, "useTool"),
3543 ),
3544 "configured index target did not reuse declaration keys",
3545 )?;
3546
3547 let extension_caller = extract_symbol_graph(
3548 "src/extension-page.ts",
3549 Some("typescript"),
3550 "import { useController } from '@/controller.ts';\nexport const value = useController();\n",
3551 );
3552 let extension_target = extract_symbol_graph(
3553 "src/controller.ts",
3554 Some("typescript"),
3555 "export function useController(): string { return 'ok'; }\n",
3556 );
3557 let extension_caller_projection =
3558 derive_resolution_keys_with_context(project, Some("app"), &extension_caller, context)?;
3559 let extension_target_projection =
3560 derive_resolution_keys(project, Some("app"), &extension_target)?;
3561 require(
3562 relation_keys_of_kind(
3563 &extension_caller,
3564 &extension_caller_projection,
3565 RelationKind::Imports,
3566 )
3567 .iter()
3568 .any(|keys| keys_intersect(keys, extension_target_projection.source_keys())),
3569 "configured extension target did not reuse extensionless source keys",
3570 )?;
3571
3572 let root_configured = ConfiguredModuleResolution::new(vec![EcmaScriptModuleConfig::new(
3573 "tsconfig.json",
3574 EcmaScriptConfigKind::TypeScript,
3575 Some(String::new()),
3576 Vec::new(),
3577 )?])?;
3578 let root_context = ResolutionProjectionContext::with_configured_modules(&root_configured);
3579 let root_target = extract_symbol_graph(
3580 "controller.ts",
3581 Some("typescript"),
3582 "export function useController() { return 'root'; }\n",
3583 );
3584 let nested_same_name = extract_symbol_graph(
3585 "packages/app/src/controller.ts",
3586 Some("typescript"),
3587 "export function useController() { return 'nested'; }\n",
3588 );
3589 let root_caller = extract_symbol_graph(
3590 "page.ts",
3591 Some("typescript"),
3592 "import { useController } from 'controller';\nexport const value = useController();\n",
3593 );
3594 let root_target_projection = derive_resolution_keys(project, Some("shared"), &root_target)?;
3595 let nested_projection = derive_resolution_keys(project, Some("nested"), &nested_same_name)?;
3596 let root_caller_projection =
3597 derive_resolution_keys_with_context(project, Some("app"), &root_caller, root_context)?;
3598 let configured_imports =
3599 relation_keys_of_kind(&root_caller, &root_caller_projection, RelationKind::Imports);
3600 require(
3601 configured_imports
3602 .iter()
3603 .any(|keys| keys_intersect(keys, root_target_projection.source_keys())),
3604 "root baseUrl import did not resolve its repository-exact target",
3605 )?;
3606 require(
3607 configured_imports
3608 .iter()
3609 .all(|keys| !keys_intersect(keys, nested_projection.source_keys())),
3610 "root baseUrl import also matched a nested basename alias",
3611 )?;
3612 let configured_call = relation_keys(&root_caller, &root_caller_projection, "useController");
3613 require(
3614 keys_intersect(
3615 configured_call,
3616 symbol_keys(&root_target, &root_target_projection, "useController"),
3617 ),
3618 "root baseUrl call did not resolve its repository-exact declaration",
3619 )?;
3620 require(
3621 !keys_intersect(
3622 configured_call,
3623 symbol_keys(&nested_same_name, &nested_projection, "useController"),
3624 ),
3625 "root baseUrl call also matched a nested basename alias",
3626 )?;
3627 Ok(())
3628 }
3629
3630 #[test]
3631 fn configured_import_scopes_are_expanded_once_per_source_graph() -> Result<(), Box<dyn Error>> {
3632 let configured = ConfiguredModuleResolution::new(vec![EcmaScriptModuleConfig::new(
3633 "tsconfig.json",
3634 EcmaScriptConfigKind::TypeScript,
3635 Some("src".to_string()),
3636 vec![EcmaScriptPathMapping::new(
3637 "@/*",
3638 vec!["src/*".to_string()],
3639 )?],
3640 )?])?;
3641 let graph = extract_symbol_graph(
3642 "src/page.ts",
3643 Some("typescript"),
3644 "import { useController } from '@/controller';\n\
3645 export const first = useController();\n\
3646 export const second = useController();\n",
3647 );
3648 let parsed_imports = graph
3649 .relations
3650 .iter()
3651 .map(|relation| {
3652 if relation.kind == RelationKind::Imports {
3653 crate::semantic::parse_imports(
3654 SemanticProviderOwner::EcmaScript,
3655 &relation.target_name,
3656 )
3657 } else {
3658 Vec::new()
3659 }
3660 })
3661 .collect::<Vec<_>>();
3662 let configured_source = configured.for_source(&graph.path);
3663 let cache = build_import_scope_cache(
3664 SemanticProviderOwner::EcmaScript,
3665 &graph,
3666 &parsed_imports,
3667 Some(&configured_source),
3668 &mut super::ProjectionBudget::new(None),
3669 )?;
3670 let modules = cache
3671 .get("src/page.ts")
3672 .ok_or_else(|| io::Error::other("source graph omitted its import-scope cache"))?;
3673 if modules.len() != 1
3674 || modules.get("@/controller") != Some(&vec!["module:src/controller".into()])
3675 {
3676 return Err(io::Error::other(
3677 "repeated imported calls did not reuse one configured scope expansion",
3678 )
3679 .into());
3680 }
3681 Ok(())
3682 }
3683
3684 #[test]
3685 fn imports_target_modules_and_qualified_calls_target_members() -> Result<(), Box<dyn Error>> {
3686 let project = project_id(10)?;
3687 let target = extract_symbol_graph(
3688 "src/shared/reader.ts",
3689 Some("typescript"),
3690 "export function read() {}\nexport function close() {}\nexport function fetch() {}\nexport const client = { fetch() {} };\n",
3691 );
3692 let caller = extract_symbol_graph(
3693 "src/features/main.ts",
3694 Some("typescript"),
3695 "import { read, close, client } from '../shared/reader';\nexport function main() { read(); close(); client.fetch(); }\n",
3696 );
3697 let target_projection = derive_resolution_keys(project, Some("web"), &target)?;
3698 let caller_projection = derive_resolution_keys(project, Some("web"), &caller)?;
3699 let imports = relation_keys_of_kind(&caller, &caller_projection, RelationKind::Imports);
3700 require(
3701 imports.len() == 1,
3702 "one import emitted more than one module target",
3703 )?;
3704 require(
3705 imports[0]
3706 .iter()
3707 .all(|key| key.domain() == projectatlas_core::graph::ResolutionKeyDomain::Module),
3708 "named imports emitted declaration resolution targets",
3709 )?;
3710 require(
3711 keys_intersect(imports[0], target_projection.source_keys()),
3712 "named import did not target its source module",
3713 )?;
3714 for symbol in ["read", "close"] {
3715 require(
3716 !keys_intersect(imports[0], symbol_keys(&target, &target_projection, symbol)),
3717 "module import also targeted a named declaration",
3718 )?;
3719 }
3720 assert_alias_resolution(project, Some("web"), &target, "read", &caller, &["read"])?;
3721 assert_alias_resolution(project, Some("web"), &target, "close", &caller, &["close"])?;
3722 let qualified_call = relation_keys(&caller, &caller_projection, "client.fetch");
3723 let fetch_candidates = target_projection
3724 .symbol_keys()
3725 .iter()
3726 .filter(|entry| target.symbols[entry.symbol_index()].name == "fetch")
3727 .map(|entry| {
3728 (
3729 target.symbols[entry.symbol_index()].parent.as_deref(),
3730 entry.keys(),
3731 )
3732 })
3733 .collect::<Vec<_>>();
3734 require(
3735 fetch_candidates.len() == 2,
3736 "qualified-call fixture lost one fetch declaration",
3737 )?;
3738 require(
3739 fetch_candidates.iter().any(|(parent, keys)| {
3740 *parent == Some("client") && keys_intersect(qualified_call, keys)
3741 }),
3742 "qualified call did not target the imported parent member",
3743 )?;
3744 require(
3745 fetch_candidates
3746 .iter()
3747 .all(|(parent, keys)| parent.is_some() || !keys_intersect(qualified_call, keys)),
3748 "qualified call also targeted a same-module top-level declaration",
3749 )
3750 }
3751
3752 #[test]
3753 fn actual_complex_import_syntax_abstains_instead_of_fabricating_keys()
3754 -> Result<(), Box<dyn Error>> {
3755 let project = project_id(15)?;
3756 for (path, language, source) in [
3757 (
3758 "src/main.rs",
3759 "rust",
3760 "use crate::worker::{run, broken as };\nfn main() { run(); }\n",
3761 ),
3762 (
3763 "src/main.ts",
3764 "typescript",
3765 "import defaultValue, { read } unexpected from './reader';\ndefaultValue();\n",
3766 ),
3767 ] {
3768 let graph = extract_symbol_graph(path, Some(language), source);
3769 let projection = derive_resolution_keys(project, Some("app"), &graph)?;
3770 let imports = relation_keys_of_kind(&graph, &projection, RelationKind::Imports);
3771 require(
3772 !imports.is_empty(),
3773 "complex-import fixture did not reach provider normalization",
3774 )?;
3775 require(
3776 imports.iter().all(|keys| keys.is_empty()),
3777 "unsupported complex import syntax fabricated resolution keys",
3778 )?;
3779 }
3780 Ok(())
3781 }
3782
3783 #[test]
3784 fn syntax_trivia_and_literals_keep_dependency_and_aliased_call_keys()
3785 -> Result<(), Box<dyn Error>> {
3786 let project = project_id(15)?;
3787 for (language, target_path, target_source, caller_path, caller_source) in [
3788 (
3789 "rust",
3790 "src/worker.rs",
3791 "pub fn run() {}\npub fn stop() {}\n",
3792 "src/main.rs",
3793 "use crate::worker::{run as launch, /* separator */ stop};\nfn caller() { launch(); }\n",
3794 ),
3795 (
3796 "typescript",
3797 "src/worker.ts",
3798 "export function run() {}\nexport function stop() {}\n",
3799 "src/main.ts",
3800 "import { run as launch, /* separator */ stop } from './worker';\nexport function caller() { launch(); }\n",
3801 ),
3802 (
3803 "python",
3804 "package/worker.py",
3805 "def run():\n pass\ndef stop():\n pass\n",
3806 "main.py",
3807 "from package.worker import (run as launch, # separator\n stop)\ndef caller():\n launch()\n",
3808 ),
3809 (
3810 "javascript",
3811 "src/dep space.js",
3812 "export function run() {}\n",
3813 "src/main.js",
3814 "import { run as launch } from './dep space.js';\nexport function caller() { launch(); }\n",
3815 ),
3816 (
3817 "javascript",
3818 "src/odd'name.js",
3819 "export function run() {}\n",
3820 "src/main.js",
3821 "import { run as launch } from './odd\\'name.js';\nexport function caller() { launch(); }\n",
3822 ),
3823 (
3824 "javascript",
3825 "src/🚀.js",
3826 "export function run() {}\n",
3827 "src/main.js",
3828 "import { run as launch } from './\\uD83D\\uDE80.js';\nexport function caller() { launch(); }\n",
3829 ),
3830 (
3831 "python",
3832 "package/worker.py",
3833 "def run():\n pass\n",
3834 "main.py",
3835 "from package.worker import run \\\n as launch\ndef caller():\n launch()\n",
3836 ),
3837 ] {
3838 let target = extract_symbol_graph(target_path, Some(language), target_source);
3839 let caller = extract_symbol_graph(caller_path, Some(language), caller_source);
3840 let projection = derive_resolution_keys(project, Some("app"), &caller)?;
3841 require(
3842 relation_keys_of_kind(&caller, &projection, RelationKind::Imports)
3843 .iter()
3844 .any(|keys| !keys.is_empty()),
3845 "syntax-preserving import lost dependency keys",
3846 )?;
3847 assert_alias_resolution(project, Some("app"), &target, "run", &caller, &["launch"])?;
3848 }
3849 Ok(())
3850 }
3851
3852 #[test]
3853 fn rust_namespace_aliases_preserve_nested_member_ownership() -> Result<(), Box<dyn Error>> {
3854 let project = project_id(86)?;
3855 let control = projectatlas_core::IndexWorkControl::new(
3856 projectatlas_core::IndexCancellation::new(),
3857 None,
3858 );
3859 let target = crate::extract_symbol_graph_with_semantics_controlled(
3860 "src/fake.rs",
3861 Some("rust"),
3862 "pub fn read(_: &str) {}\npub mod fs { pub fn read(_: &str) {} }",
3863 &control,
3864 )?;
3865 let caller = crate::extract_symbol_graph_with_semantics_controlled(
3866 "src/main.rs",
3867 Some("rust"),
3868 "use crate::fake as std; fn root_call(std: u8) { std::read(\"x\"); } fn nested_call() { std::fs::read(\"x\"); }",
3869 &control,
3870 )?;
3871 let target_keys = derive_resolution_keys_with_context(
3872 project,
3873 Some("app"),
3874 target.graph(),
3875 ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
3876 )?;
3877 let caller_keys = derive_resolution_keys_with_context(
3878 project,
3879 Some("app"),
3880 caller.graph(),
3881 ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
3882 )?;
3883 for (reference, parent) in [("std::read", None), ("std::fs::read", Some("fs"))] {
3884 let requests = relation_keys(caller.graph(), &caller_keys, reference);
3885 let matches = target_keys
3886 .symbol_keys()
3887 .iter()
3888 .filter(|keys| keys_intersect(requests, keys.keys()))
3889 .map(|keys| &target.graph().symbols[keys.symbol_index()])
3890 .collect::<Vec<_>>();
3891 require(
3892 matches.len() == 1,
3893 "qualified member did not resolve uniquely",
3894 )?;
3895 require(matches[0].name == "read", "qualified member lost leaf name")?;
3896 require(
3897 matches[0].parent.as_deref() == parent,
3898 "qualified member escaped owning namespace",
3899 )?;
3900 }
3901 Ok(())
3902 }
3903
3904 #[test]
3905 fn nested_rust_imports_keep_distinct_dependencies_and_alias_callers()
3906 -> Result<(), Box<dyn Error>> {
3907 let project = project_id(15)?;
3908 let worker = extract_symbol_graph("src/worker.rs", Some("rust"), "pub fn run() {}\n");
3909 let nested =
3910 extract_symbol_graph("src/worker/nested.rs", Some("rust"), "pub fn tail() {}\n");
3911 let caller = extract_symbol_graph(
3912 "src/main.rs",
3913 Some("rust"),
3914 "use crate::worker::{run as launch, nested::{self as tools, tail as last}, *};\nfn caller() { launch(); last(); tools::tail(); }\n",
3915 );
3916 let projection = derive_resolution_keys(project, Some("app"), &caller)?;
3917 let worker_projection = derive_resolution_keys(project, Some("app"), &worker)?;
3918 let nested_projection = derive_resolution_keys(project, Some("app"), &nested)?;
3919 let imports = relation_keys_of_kind(&caller, &projection, RelationKind::Imports);
3920 for expected in [
3921 worker_projection.source_keys(),
3922 nested_projection.source_keys(),
3923 ] {
3924 require(
3925 imports.iter().any(|keys| keys_intersect(keys, expected)),
3926 "nested use lost a distinct module dependency",
3927 )?;
3928 }
3929 assert_alias_resolution(project, Some("app"), &worker, "run", &caller, &["launch"])?;
3930 assert_alias_resolution(
3931 project,
3932 Some("app"),
3933 &nested,
3934 "tail",
3935 &caller,
3936 &["last", "tools::tail"],
3937 )?;
3938 Ok(())
3939 }
3940
3941 #[test]
3942 fn provider_scopes_abstain_instead_of_matching_packages_or_wrong_siblings()
3943 -> Result<(), Box<dyn Error>> {
3944 let project = project_id(11)?;
3945 let correct_typescript = extract_symbol_graph(
3946 "src/a/reader.ts",
3947 Some("typescript"),
3948 "export function read() {}\n",
3949 );
3950 let wrong_typescript = extract_symbol_graph(
3951 "src/b/reader.ts",
3952 Some("typescript"),
3953 "export function read() {}\n",
3954 );
3955 let typescript_caller = extract_symbol_graph(
3956 "src/main.ts",
3957 Some("typescript"),
3958 "import { read } from './a/reader';\nread();\n",
3959 );
3960 assert_import_matches_only_source(
3961 project,
3962 Some("web"),
3963 &typescript_caller,
3964 &correct_typescript,
3965 &wrong_typescript,
3966 )?;
3967
3968 let package_caller = extract_symbol_graph(
3969 "src/package.ts",
3970 Some("typescript"),
3971 "import { read } from 'reader-package';\nread();\n",
3972 );
3973 let package_projection = derive_resolution_keys(project, Some("web"), &package_caller)?;
3974 require(
3975 relation_keys_of_kind(&package_caller, &package_projection, RelationKind::Imports)
3976 .iter()
3977 .all(|keys| keys.is_empty()),
3978 "bare ECMAScript package import entered a repository-local module scope",
3979 )?;
3980 require(
3981 relation_keys(&package_caller, &package_projection, "read").is_empty(),
3982 "call through an unresolved package import fell back to a global declaration",
3983 )?;
3984
3985 for (path, language, source, target) in [
3986 (
3987 "src/unimported.ts",
3988 "typescript",
3989 "export function run() { return client.fetch(); }\n",
3990 "client.fetch",
3991 ),
3992 (
3993 "src/unimported.rs",
3994 "rust",
3995 "pub fn run() { worker::execute(); }\n",
3996 "worker::execute",
3997 ),
3998 (
3999 "src/unimported.py",
4000 "python",
4001 "def run():\n return reader.read()\n",
4002 "reader.read",
4003 ),
4004 ] {
4005 let graph = extract_symbol_graph(path, Some(language), source);
4006 let projection = derive_resolution_keys(project, Some("app"), &graph)?;
4007 require(
4008 graph.relations.iter().any(|relation| {
4009 relation.kind == RelationKind::Calls && relation.target_name == target
4010 }),
4011 "unimported qualified-call fixture did not reach semantic normalization",
4012 )?;
4013 require(
4014 relation_keys(&graph, &projection, target).is_empty(),
4015 "unimported qualified call fell back to a project basename scope",
4016 )?;
4017 }
4018
4019 let rust_target =
4020 extract_symbol_graph("src/feature/worker.rs", Some("rust"), "pub fn run() {}\n");
4021 let wrong_rust =
4022 extract_symbol_graph("src/other/worker.rs", Some("rust"), "pub fn run() {}\n");
4023 let rust_caller = extract_symbol_graph(
4024 "src/feature/main.rs",
4025 Some("rust"),
4026 "use super::worker::run;\nfn main() { run(); }\n",
4027 );
4028 let rust_crate_caller = extract_symbol_graph(
4029 "src/main.rs",
4030 Some("rust"),
4031 "use crate::feature::worker::run;\nfn main() { run(); }\n",
4032 );
4033 let rust_self_caller = extract_symbol_graph(
4034 "src/feature/mod.rs",
4035 Some("rust"),
4036 "use self::worker::run;\nfn main() { run(); }\n",
4037 );
4038 for caller in [&rust_caller, &rust_crate_caller, &rust_self_caller] {
4039 assert_import_matches_only_source(
4040 project,
4041 Some("atlas"),
4042 caller,
4043 &rust_target,
4044 &wrong_rust,
4045 )?;
4046 }
4047
4048 let python_target = extract_symbol_graph(
4049 "src/package/reader.py",
4050 Some("python"),
4051 "def read():\n pass\n",
4052 );
4053 let wrong_python = extract_symbol_graph(
4054 "src/other/reader.py",
4055 Some("python"),
4056 "def read():\n pass\n",
4057 );
4058 let python_caller = extract_symbol_graph(
4059 "src/package/main.py",
4060 Some("python"),
4061 "from .reader import read\nread()\n",
4062 );
4063 assert_import_matches_only_source(
4064 project,
4065 Some("python-app"),
4066 &python_caller,
4067 &python_target,
4068 &wrong_python,
4069 )?;
4070 let parent_python_target = extract_symbol_graph(
4071 "src/package/reader.py",
4072 Some("python"),
4073 "def read():\n pass\n",
4074 );
4075 let parent_python_wrong = extract_symbol_graph(
4076 "src/package/sub/reader.py",
4077 Some("python"),
4078 "def read():\n pass\n",
4079 );
4080 let parent_python_caller = extract_symbol_graph(
4081 "src/package/sub/main.py",
4082 Some("python"),
4083 "from ..reader import read\nread()\n",
4084 );
4085 assert_import_matches_only_source(
4086 project,
4087 Some("python-app"),
4088 &parent_python_caller,
4089 &parent_python_target,
4090 &parent_python_wrong,
4091 )
4092 }
4093
4094 #[test]
4095 fn direct_provider_families_retain_duplicate_ambiguity_keys() -> Result<(), Box<dyn Error>> {
4096 let project = project_id(12)?;
4097 for (path, language, target_source, caller_path, caller_source, target_name) in [
4098 (
4099 "src/shared/reader.ts",
4100 "typescript",
4101 "export function read() {}\nexport function read() {}\n",
4102 "src/main.ts",
4103 "import { read } from './shared/reader';\nread();\n",
4104 "read",
4105 ),
4106 (
4107 "src/package/reader.py",
4108 "python",
4109 "def read():\n pass\ndef read():\n pass\n",
4110 "src/main.py",
4111 "from package.reader import read\nread()\n",
4112 "read",
4113 ),
4114 ] {
4115 let target = extract_symbol_graph(path, Some(language), target_source);
4116 let caller = extract_symbol_graph(caller_path, Some(language), caller_source);
4117 let target_projection = derive_resolution_keys(project, Some("app"), &target)?;
4118 let caller_projection = derive_resolution_keys(project, Some("app"), &caller)?;
4119 let duplicate_keys = target_projection
4120 .symbol_keys()
4121 .iter()
4122 .filter(|entry| target.symbols[entry.symbol_index()].name == target_name)
4123 .map(super::SymbolResolutionKeys::keys)
4124 .collect::<Vec<_>>();
4125 require(
4126 duplicate_keys.len() == 2,
4127 "duplicate declarations were not both projected",
4128 )?;
4129 let dependency = relation_keys(&caller, &caller_projection, target_name);
4130 require(
4131 duplicate_keys
4132 .iter()
4133 .all(|keys| keys_intersect(dependency, keys)),
4134 "call dependency did not retain every duplicate declaration candidate",
4135 )?;
4136 }
4137 Ok(())
4138 }
4139
4140 #[test]
4141 fn resolution_identity_separates_provider_from_cross_dialect_family()
4142 -> Result<(), Box<dyn Error>> {
4143 let project = project_id(13)?;
4144 let typescript = extract_symbol_graph(
4145 "src/shared/reader.ts",
4146 Some("typescript"),
4147 "export function read() {}\n",
4148 );
4149 let javascript = extract_symbol_graph(
4150 "src/shared/reader.ts",
4151 Some("javascript"),
4152 "export function read() {}\n",
4153 );
4154 let typescript_projection = derive_resolution_keys(project, Some("web"), &typescript)?;
4155 let javascript_projection = derive_resolution_keys(project, Some("web"), &javascript)?;
4156 require(
4157 typescript_projection.source_keys() == javascript_projection.source_keys(),
4158 "ECMAScript-compatible dialects entered different resolution families",
4159 )?;
4160 let provider = projectatlas_core::graph::GraphIdentityText::new("ecma-script")?;
4161 let family = projectatlas_core::graph::GraphIdentityText::new("ecmascript")?;
4162 let package = projectatlas_core::graph::GraphIdentityText::new("web")?;
4163 let identity =
4164 projectatlas_core::graph::GraphIdentityText::new("module:src/shared/reader")?;
4165 let expected = CanonicalResolutionKey::new(
4166 project,
4167 projectatlas_core::graph::ResolutionKeyDomain::Module,
4168 &provider,
4169 &family,
4170 Some(&package),
4171 None,
4172 Some(projectatlas_core::graph::GraphRelationKind::from_legacy(
4173 RelationKind::Imports,
4174 )),
4175 &identity,
4176 );
4177 require(
4178 typescript_projection.source_keys().contains(&expected),
4179 "provider owner and cross-dialect family were not projected independently",
4180 )
4181 }
4182
4183 #[test]
4184 fn embedded_hosts_publish_module_keys_only_for_admitted_component_facts()
4185 -> Result<(), Box<dyn Error>> {
4186 let project = project_id(14)?;
4187 for (path, language, source) in [
4188 (
4189 "page.html",
4190 "html",
4191 "<script>export function run() {}</script>",
4192 ),
4193 ("empty.vue", "vue", "<template><p>empty</p></template>"),
4194 (
4195 "external.vue",
4196 "vue",
4197 "<script src='./external.js'></script>",
4198 ),
4199 ("empty.svelte", "svelte", "<p>empty</p>"),
4200 (
4201 "external.svelte",
4202 "svelte",
4203 "<script src='./external.js'></script>",
4204 ),
4205 (
4206 "external-inline.vue",
4207 "vue",
4208 "<script>import * as fs from 'node:fs';</script>",
4209 ),
4210 (
4211 "external-inline.svelte",
4212 "svelte",
4213 "<script>import * as fs from 'node:fs';</script>",
4214 ),
4215 ("malformed.vue", "vue", "<script"),
4216 ("malformed.svelte", "svelte", "<script"),
4217 ] {
4218 let graph = extract_symbol_graph(path, Some(language), source);
4219 let projection = derive_resolution_keys(project, Some("web"), &graph)?;
4220 require(
4221 projection.source_keys().is_empty(),
4222 "host without an admitted component module surface emitted source keys",
4223 )?;
4224 if path.starts_with("external-inline") {
4225 require(
4226 relation_keys_of_kind(&graph, &projection, RelationKind::Imports).len() == 1,
4227 "external-only component lost its outbound import relationship",
4228 )?;
4229 }
4230 }
4231 for (path, language) in [("component.vue", "vue"), ("component.svelte", "svelte")] {
4232 let graph = extract_symbol_graph(
4233 path,
4234 Some(language),
4235 "<script>export function run() {}</script>",
4236 );
4237 require(
4238 !derive_resolution_keys(project, Some("web"), &graph)?
4239 .source_keys()
4240 .is_empty(),
4241 "admitted component script facts did not expose their host module",
4242 )?;
4243 }
4244 Ok(())
4245 }
4246
4247 #[test]
4248 fn canonical_projection_is_stable_and_retains_ambiguous_unresolved_and_package_keys()
4249 -> Result<(), Box<dyn Error>> {
4250 let project = project_id(2)?;
4251 let first = extract_symbol_graph("src/worker.rs", Some("rust"), "pub fn run() {}\n");
4252 let moved = extract_symbol_graph(
4253 "src/worker.rs",
4254 Some("rust"),
4255 "\n\n// formatting moved the declaration\npub fn run() {}\n",
4256 );
4257 let first_projection = derive_resolution_keys(project, Some("atlas"), &first)?;
4258 let moved_projection = derive_resolution_keys(project, Some("atlas"), &moved)?;
4259 require(
4260 symbol_keys(&first, &first_projection, "run")
4261 == symbol_keys(&moved, &moved_projection, "run"),
4262 "formatting-only line movement changed canonical symbol identity",
4263 )?;
4264 require(
4265 first_projection.source_keys()
4266 != derive_resolution_keys(project_id(3)?, Some("atlas"), &first)?.source_keys(),
4267 "different projects produced the same canonical source identity",
4268 )?;
4269 require(
4270 symbol_keys(&first, &first_projection, "run")
4271 != symbol_keys(
4272 &first,
4273 &derive_resolution_keys(project, Some("other-package"), &first)?,
4274 "run",
4275 ),
4276 "different packages produced the same canonical declaration identity",
4277 )?;
4278
4279 let duplicate = extract_symbol_graph("src/other.rs", Some("rust"), "pub fn run() {}\n");
4280 let caller = extract_symbol_graph(
4281 "src/main.rs",
4282 Some("rust"),
4283 "fn main() { run(); missing(); }\n",
4284 );
4285 let duplicate_projection = derive_resolution_keys(project, Some("atlas"), &duplicate)?;
4286 let caller_projection = derive_resolution_keys(project, Some("atlas"), &caller)?;
4287 let run_dependencies = relation_keys(&caller, &caller_projection, "run");
4288 require(
4289 keys_intersect(
4290 run_dependencies,
4291 symbol_keys(&first, &first_projection, "run"),
4292 ),
4293 "ambiguous call did not retain the first matching declaration key",
4294 )?;
4295 require(
4296 keys_intersect(
4297 run_dependencies,
4298 symbol_keys(&duplicate, &duplicate_projection, "run"),
4299 ),
4300 "ambiguous call did not retain the duplicate declaration key",
4301 )?;
4302 let missing_dependencies = relation_keys(&caller, &caller_projection, "missing");
4303 require(
4304 !missing_dependencies.is_empty(),
4305 "unresolved call lost its canonical dependency key",
4306 )?;
4307 require(
4308 !keys_intersect(
4309 missing_dependencies,
4310 symbol_keys(&first, &first_projection, "run"),
4311 ),
4312 "unresolved call matched an unrelated declaration key",
4313 )?;
4314
4315 let package = extract_symbol_graph(
4316 "vendor/library/Cargo.toml",
4317 Some("cargo-manifest"),
4318 "[package]\nname = \"library\"\n",
4319 );
4320 let manifest = extract_symbol_graph(
4321 "Cargo.toml",
4322 Some("cargo-manifest"),
4323 "[package]\nname = \"app\"\n[dependencies]\nlibrary = \"1\"\n",
4324 );
4325 let package_projection = derive_resolution_keys(project, None, &package)?;
4326 let manifest_projection = derive_resolution_keys(project, None, &manifest)?;
4327 require(
4328 package_projection.source_keys().is_empty()
4329 && manifest_projection.source_keys().is_empty(),
4330 "Cargo manifests advertised ordinary source-module identities",
4331 )?;
4332 require(
4333 keys_intersect(
4334 relation_keys(&manifest, &manifest_projection, "library"),
4335 symbol_keys(&package, &package_projection, "library"),
4336 ),
4337 "Cargo dependency did not match its package declaration key",
4338 )?;
4339 Ok(())
4340 }
4341
4342 #[test]
4343 fn cargo_provider_abstains_on_malformed_input_and_retains_duplicate_package_candidates()
4344 -> Result<(), Box<dyn Error>> {
4345 let project = project_id(16)?;
4346 let malformed = extract_symbol_graph(
4347 "Cargo.toml",
4348 Some("cargo-manifest"),
4349 "[package\nname = \"broken\"\n",
4350 );
4351 let malformed_projection = derive_resolution_keys(project, None, &malformed)?;
4352 require(
4353 malformed_projection.source_keys().is_empty()
4354 && malformed_projection.symbol_keys().is_empty()
4355 && malformed_projection.relation_keys().is_empty(),
4356 "malformed Cargo input fabricated semantic identities",
4357 )?;
4358
4359 let first = extract_symbol_graph(
4360 "vendor/first/Cargo.toml",
4361 Some("cargo-manifest"),
4362 "[package]\nname = \"shared-package\"\n",
4363 );
4364 let second = extract_symbol_graph(
4365 "vendor/second/Cargo.toml",
4366 Some("cargo-manifest"),
4367 "[package]\nname = \"shared-package\"\n",
4368 );
4369 let caller = extract_symbol_graph(
4370 "Cargo.toml",
4371 Some("cargo-manifest"),
4372 "[package]\nname = \"app\"\n[dependencies]\nshared-package = \"1\"\n",
4373 );
4374 let first_projection = derive_resolution_keys(project, None, &first)?;
4375 let second_projection = derive_resolution_keys(project, None, &second)?;
4376 let caller_projection = derive_resolution_keys(project, None, &caller)?;
4377 let dependency = relation_keys(&caller, &caller_projection, "shared-package");
4378 require(
4379 keys_intersect(
4380 dependency,
4381 symbol_keys(&first, &first_projection, "shared-package"),
4382 ) && keys_intersect(
4383 dependency,
4384 symbol_keys(&second, &second_projection, "shared-package"),
4385 ),
4386 "duplicate Cargo packages did not remain ambiguity candidates",
4387 )
4388 }
4389
4390 #[test]
4391 fn projection_rejects_invalid_identity_and_excessive_key_fanout() -> Result<(), Box<dyn Error>>
4392 {
4393 let graph = extract_symbol_graph("src/lib.rs", Some("rust"), "pub fn run() {}\n");
4394 require(
4395 matches!(
4396 derive_resolution_keys(project_id(4)?, Some("bad\0package"), &graph),
4397 Err(ResolutionProjectionError::Contract(_))
4398 ),
4399 "invalid package identity was accepted",
4400 )?;
4401
4402 let imports = (0..65)
4403 .map(|index| format!("import {{ run as execute }} from './module{index}';"))
4404 .collect::<Vec<_>>()
4405 .join("\n");
4406 let source = format!("{imports}\nexecute();\n");
4407 let graph = extract_symbol_graph("src/main.ts", Some("typescript"), &source);
4408 require(
4409 matches!(
4410 derive_resolution_keys(project_id(4)?, Some("web"), &graph),
4411 Err(ResolutionProjectionError::KeyLimit {
4412 fact: "relation",
4413 ..
4414 })
4415 ),
4416 "excessive relation-key fan-out was not rejected",
4417 )?;
4418 Ok(())
4419 }
4420
4421 #[test]
4422 fn contract_failure_retains_valid_resolution_projection_siblings() -> Result<(), Box<dyn Error>>
4423 {
4424 let path = format!("pkg/{0}/{0}/{0}/{0}/page.rs", "d".repeat(100));
4425 let imported = "i".repeat(3_800);
4426 let mut graph = extract_symbol_graph(
4427 &path,
4428 Some("rust"),
4429 "pub fn caller() { helper(); }\npub fn helper() {}\n",
4430 );
4431 graph
4432 .relations
4433 .retain(|relation| relation.kind == RelationKind::Calls);
4434 graph.relations.push(SymbolRelation {
4435 path,
4436 source_name: "<module>".to_string(),
4437 target_name: format!("use self::{{{imported}}};"),
4438 kind: RelationKind::Imports,
4439 line: 1,
4440 context: "partial projection fixture".to_string(),
4441 parser: ParserKind::TreeSitter,
4442 });
4443 graph.relations.push(SymbolRelation {
4444 path: graph.path.clone(),
4445 source_name: "caller".to_string(),
4446 target_name: format!("{imported}.member"),
4447 kind: RelationKind::Calls,
4448 line: 3,
4449 context: "partial projection fixture".to_string(),
4450 parser: ParserKind::TreeSitter,
4451 });
4452
4453 let result = derive_resolution_keys(project_id(23)?, None, &graph);
4454 let Err(ResolutionProjectionError::Contract(failure)) = result else {
4455 return Err(io::Error::other(
4456 "invalid derived resolution key did not retain a typed contract failure",
4457 )
4458 .into());
4459 };
4460 require(
4461 !failure.projection().source_keys().is_empty(),
4462 "contract failure discarded valid source resolution keys",
4463 )?;
4464 require(
4465 !failure.projection().symbol_keys().is_empty(),
4466 "contract failure discarded valid symbol resolution keys",
4467 )?;
4468 require(
4469 failure
4470 .projection()
4471 .relation_keys()
4472 .iter()
4473 .any(|relation| relation.relation_index() == 0 && !relation.keys().is_empty()),
4474 "contract failure discarded a valid sibling relation key",
4475 )?;
4476 require(
4477 failure
4478 .failures()
4479 .iter()
4480 .all(|failure| !format!("{:?}", failure.error()).contains(&imported)),
4481 "contract failure retained rejected raw identity text",
4482 )?;
4483 Ok(())
4484 }
4485
4486 #[test]
4487 fn contract_failure_deduplicates_repeated_facts_after_detail_ceiling()
4488 -> Result<(), Box<dyn Error>> {
4489 let mut graph = SymbolGraph {
4490 path: "src/main.rs".to_string(),
4491 language: Some("rust".to_string()),
4492 parser: ParserKind::TreeSitter,
4493 symbols: Vec::with_capacity(super::MAX_RESOLUTION_PROJECTION_FAILURES + 1),
4494 relations: Vec::new(),
4495 };
4496 for index in 0..super::MAX_RESOLUTION_PROJECTION_FAILURES {
4497 graph.symbols.push(CodeSymbol {
4498 path: graph.path.clone(),
4499 language: graph.language.clone(),
4500 name: format!("invalid\0{index}"),
4501 kind: SymbolKind::Function,
4502 signature: "fn invalid()".to_string(),
4503 exported: true,
4504 documentation: None,
4505 line_start: index + 1,
4506 line_end: index + 1,
4507 source_selector: None,
4508 parent: None,
4509 parser: ParserKind::TreeSitter,
4510 detail: None,
4511 });
4512 }
4513 graph.symbols.push(CodeSymbol {
4514 path: graph.path.clone(),
4515 language: graph.language.clone(),
4516 name: "valid_tail".to_string(),
4517 kind: SymbolKind::Function,
4518 signature: "fn valid_tail()".to_string(),
4519 exported: true,
4520 documentation: None,
4521 line_start: graph.symbols.len() + 1,
4522 line_end: graph.symbols.len() + 1,
4523 source_selector: None,
4524 parent: Some("\0invalid-parent".to_string()),
4525 parser: ParserKind::TreeSitter,
4526 detail: None,
4527 });
4528
4529 let Err(ResolutionProjectionError::Contract(failure)) =
4530 derive_resolution_keys(project_id(24)?, None, &graph)
4531 else {
4532 return Err(
4533 io::Error::other("repeated post-ceiling failures were not reported").into(),
4534 );
4535 };
4536 require(
4537 failure.failures().len() == super::MAX_RESOLUTION_PROJECTION_FAILURES,
4538 "detail ceiling retained repeated fact attempts",
4539 )?;
4540 require(
4541 failure.rejected_count() == super::MAX_RESOLUTION_PROJECTION_FAILURES + 1,
4542 "repeated post-ceiling fact inflated rejected count",
4543 )?;
4544 Ok(())
4545 }
4546
4547 #[test]
4548 fn supported_typescript_parser_retains_control_bearing_import_identities()
4549 -> Result<(), Box<dyn Error>> {
4550 let graph = extract_symbol_graph(
4551 "src/page.ts",
4552 Some("typescript"),
4553 "import * as first from './bad\0one';\nimport * as second from './bad\0two';\nexport function caller() { first(); second(); }\n",
4554 );
4555 require(
4556 graph.parser == ParserKind::TreeSitter,
4557 "supported TypeScript fixture did not use the semantic parser",
4558 )?;
4559 require(
4560 graph.relations.iter().any(|relation| {
4561 relation.kind == RelationKind::Imports && relation.target_name.contains('\0')
4562 }),
4563 "supported TypeScript parser did not retain the control-bearing import",
4564 )?;
4565 Ok(())
4566 }
4567
4568 #[test]
4569 fn unsupported_and_lockfile_languages_do_not_gain_generic_name_resolution()
4570 -> Result<(), Box<dyn Error>> {
4571 for graph in [
4572 extract_symbol_graph(
4573 "src/Service.java",
4574 Some("java"),
4575 "public class Service { void run() {} }\n",
4576 ),
4577 extract_symbol_graph(
4578 "Cargo.lock",
4579 Some("cargo-lock"),
4580 "[[package]]\nname = \"dependency\"\nversion = \"1.0.0\"\n",
4581 ),
4582 ] {
4583 let projection = derive_resolution_keys(project_id(9)?, Some("app"), &graph)?;
4584 require(
4585 projection.source_keys().is_empty()
4586 && projection.symbol_keys().is_empty()
4587 && projection.relation_keys().is_empty(),
4588 "unsupported language emitted generic resolution keys",
4589 )?;
4590 }
4591 Ok(())
4592 }
4593
4594 fn require(condition: bool, message: &str) -> Result<(), Box<dyn Error>> {
4595 if condition {
4596 Ok(())
4597 } else {
4598 Err(io::Error::other(message.to_string()).into())
4599 }
4600 }
4601
4602 fn project_id(byte: u8) -> Result<ProjectInstanceId, Box<dyn Error>> {
4603 Ok(ProjectInstanceId::from_bytes([byte; 16])?)
4604 }
4605
4606 fn assert_alias_resolution(
4607 project: ProjectInstanceId,
4608 package: Option<&str>,
4609 target: &SymbolGraph,
4610 symbol_name: &str,
4611 caller: &SymbolGraph,
4612 call_targets: &[&str],
4613 ) -> Result<(), Box<dyn Error>> {
4614 let target_projection = derive_resolution_keys(project, package, target)?;
4615 let caller_projection = derive_resolution_keys(project, package, caller)?;
4616 let exports = symbol_keys(target, &target_projection, symbol_name);
4617 for call_target in call_targets {
4618 if !keys_intersect(
4619 relation_keys(caller, &caller_projection, call_target),
4620 exports,
4621 ) {
4622 return Err(io::Error::other(format!(
4623 "{call_target} did not share a canonical key with {symbol_name}"
4624 ))
4625 .into());
4626 }
4627 }
4628 Ok(())
4629 }
4630
4631 fn assert_import_matches_only_source(
4632 project: ProjectInstanceId,
4633 package: Option<&str>,
4634 caller: &SymbolGraph,
4635 expected: &SymbolGraph,
4636 wrong: &SymbolGraph,
4637 ) -> Result<(), Box<dyn Error>> {
4638 let caller_projection = derive_resolution_keys(project, package, caller)?;
4639 let expected_projection = derive_resolution_keys(project, package, expected)?;
4640 let wrong_projection = derive_resolution_keys(project, package, wrong)?;
4641 let imports = relation_keys_of_kind(caller, &caller_projection, RelationKind::Imports);
4642 require(
4643 imports.len() == 1,
4644 "caller did not retain one import relationship",
4645 )?;
4646 require(
4647 keys_intersect(imports[0], expected_projection.source_keys()),
4648 "caller import missed its exact source",
4649 )?;
4650 require(
4651 !keys_intersect(imports[0], wrong_projection.source_keys()),
4652 "caller import matched a wrong-sibling basename",
4653 )
4654 }
4655
4656 fn symbol_keys<'a>(
4657 graph: &SymbolGraph,
4658 projection: &'a super::ResolutionKeyProjection,
4659 name: &str,
4660 ) -> &'a [CanonicalResolutionKey] {
4661 projection
4662 .symbol_keys()
4663 .iter()
4664 .find(|entry| graph.symbols[entry.symbol_index()].name == name)
4665 .map_or(&[], super::SymbolResolutionKeys::keys)
4666 }
4667
4668 fn relation_keys<'a>(
4669 graph: &SymbolGraph,
4670 projection: &'a super::ResolutionKeyProjection,
4671 target: &str,
4672 ) -> &'a [CanonicalResolutionKey] {
4673 projection
4674 .relation_keys()
4675 .iter()
4676 .find(|entry| {
4677 let relation = &graph.relations[entry.relation_index()];
4678 matches!(relation.kind, RelationKind::Calls | RelationKind::DependsOn)
4679 && relation.target_name == target
4680 })
4681 .map_or(&[], super::RelationResolutionKeys::keys)
4682 }
4683
4684 fn relation_keys_of_kind<'a>(
4685 graph: &SymbolGraph,
4686 projection: &'a super::ResolutionKeyProjection,
4687 kind: RelationKind,
4688 ) -> Vec<&'a [CanonicalResolutionKey]> {
4689 projection
4690 .relation_keys()
4691 .iter()
4692 .filter(|entry| graph.relations[entry.relation_index()].kind == kind)
4693 .map(super::RelationResolutionKeys::keys)
4694 .collect()
4695 }
4696
4697 fn keys_intersect(left: &[CanonicalResolutionKey], right: &[CanonicalResolutionKey]) -> bool {
4698 left.iter().any(|key| right.binary_search(key).is_ok())
4699 }
4700}