1use std::error::Error;
10use std::hash::BuildHasherDefault;
11use std::{fmt, mem, ops};
12
13use cfg::{CfgOptions, HashableCfgOptions};
14use dashmap::DashMap;
15use dashmap::mapref::entry::Entry;
16use intern::Symbol;
17use la_arena::{Arena, Idx, RawIdx};
18use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet, FxHasher};
19use salsa::{Durability, Setter};
20use span::Edition;
21use triomphe::Arc;
22use vfs::{AbsPathBuf, AnchoredPath, FileId, VfsPath, file_set::FileSet};
23
24use crate::{CrateWorkspaceData, EditionedFileId, FxIndexSet, RootQueryDb};
25
26pub type ProcMacroPaths =
27 FxHashMap<CrateBuilderId, Result<(String, AbsPathBuf), ProcMacroLoadingError>>;
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub enum ProcMacroLoadingError {
31 Disabled,
32 FailedToBuild,
33 ExpectedProcMacroArtifact,
34 MissingDylibPath,
35 NotYetBuilt,
36 NoProcMacros,
37 ProcMacroSrvError(Box<str>),
38}
39impl ProcMacroLoadingError {
40 pub fn is_hard_error(&self) -> bool {
41 match self {
42 ProcMacroLoadingError::Disabled | ProcMacroLoadingError::NotYetBuilt => false,
43 ProcMacroLoadingError::ExpectedProcMacroArtifact
44 | ProcMacroLoadingError::FailedToBuild
45 | ProcMacroLoadingError::MissingDylibPath
46 | ProcMacroLoadingError::NoProcMacros
47 | ProcMacroLoadingError::ProcMacroSrvError(_) => true,
48 }
49 }
50}
51
52impl Error for ProcMacroLoadingError {}
53impl fmt::Display for ProcMacroLoadingError {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 ProcMacroLoadingError::ExpectedProcMacroArtifact => {
57 write!(f, "proc-macro crate did not build proc-macro artifact")
58 }
59 ProcMacroLoadingError::Disabled => write!(f, "proc-macro expansion is disabled"),
60 ProcMacroLoadingError::FailedToBuild => write!(f, "proc-macro failed to build"),
61 ProcMacroLoadingError::MissingDylibPath => {
62 write!(
63 f,
64 "proc-macro crate built but the dylib path is missing, this indicates a problem with your build system."
65 )
66 }
67 ProcMacroLoadingError::NotYetBuilt => write!(f, "proc-macro not yet built"),
68 ProcMacroLoadingError::NoProcMacros => {
69 write!(f, "proc macro library has no proc macros")
70 }
71 ProcMacroLoadingError::ProcMacroSrvError(msg) => {
72 write!(f, "proc macro server error: {msg}")
73 }
74 }
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
79pub struct SourceRootId(pub u32);
80
81#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct SourceRoot {
90 pub is_library: bool,
95 file_set: FileSet,
96}
97
98impl SourceRoot {
99 pub fn new_local(file_set: FileSet) -> SourceRoot {
100 SourceRoot { is_library: false, file_set }
101 }
102
103 pub fn new_library(file_set: FileSet) -> SourceRoot {
104 SourceRoot { is_library: true, file_set }
105 }
106
107 pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
108 self.file_set.path_for_file(file)
109 }
110
111 pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
112 self.file_set.file_for_path(path)
113 }
114
115 pub fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
116 self.file_set.resolve_path(path)
117 }
118
119 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
120 self.file_set.iter()
121 }
122}
123
124#[derive(Default, Clone)]
125pub struct CrateGraphBuilder {
126 arena: Arena<CrateBuilder>,
127}
128
129pub type CrateBuilderId = Idx<CrateBuilder>;
130
131impl ops::Index<CrateBuilderId> for CrateGraphBuilder {
132 type Output = CrateBuilder;
133
134 fn index(&self, index: CrateBuilderId) -> &Self::Output {
135 &self.arena[index]
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct CrateBuilder {
141 pub basic: CrateDataBuilder,
142 pub extra: ExtraCrateData,
143 pub cfg_options: CfgOptions,
144 pub env: Env,
145 ws_data: Arc<CrateWorkspaceData>,
146}
147
148impl fmt::Debug for CrateGraphBuilder {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.debug_map()
151 .entries(self.arena.iter().map(|(id, data)| (u32::from(id.into_raw()), data)))
152 .finish()
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Hash)]
157pub struct CrateName(Symbol);
158
159impl CrateName {
160 pub fn new(name: &str) -> Result<CrateName, &str> {
164 if name.contains('-') { Err(name) } else { Ok(Self(Symbol::intern(name))) }
165 }
166
167 pub fn normalize_dashes(name: &str) -> CrateName {
169 Self(Symbol::intern(&name.replace('-', "_")))
170 }
171
172 pub fn symbol(&self) -> &Symbol {
173 &self.0
174 }
175}
176
177impl fmt::Display for CrateName {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 self.0.fmt(f)
180 }
181}
182
183impl ops::Deref for CrateName {
184 type Target = Symbol;
185 fn deref(&self) -> &Symbol {
186 &self.0
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Hash)]
192pub enum CrateOrigin {
193 Rustc { name: Symbol },
195 Local { repo: Option<String>, name: Option<Symbol> },
197 Library { repo: Option<String>, name: Symbol },
199 Lang(LangCrateOrigin),
201}
202
203impl CrateOrigin {
204 pub fn is_local(&self) -> bool {
205 matches!(self, CrateOrigin::Local { .. })
206 }
207
208 pub fn is_lib(&self) -> bool {
209 matches!(self, CrateOrigin::Library { .. })
210 }
211
212 pub fn is_lang(&self) -> bool {
213 matches!(self, CrateOrigin::Lang { .. })
214 }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
218pub enum LangCrateOrigin {
219 Alloc,
220 Core,
221 ProcMacro,
222 Std,
223 Test,
224 Other,
225}
226
227impl From<&str> for LangCrateOrigin {
228 fn from(s: &str) -> Self {
229 match s {
230 "alloc" => LangCrateOrigin::Alloc,
231 "core" => LangCrateOrigin::Core,
232 "proc-macro" | "proc_macro" => LangCrateOrigin::ProcMacro,
233 "std" => LangCrateOrigin::Std,
234 "test" => LangCrateOrigin::Test,
235 _ => LangCrateOrigin::Other,
236 }
237 }
238}
239
240impl fmt::Display for LangCrateOrigin {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 let text = match self {
243 LangCrateOrigin::Alloc => "alloc",
244 LangCrateOrigin::Core => "core",
245 LangCrateOrigin::ProcMacro => "proc_macro",
246 LangCrateOrigin::Std => "std",
247 LangCrateOrigin::Test => "test",
248 LangCrateOrigin::Other => "other",
249 };
250 f.write_str(text)
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Hash)]
255pub struct CrateDisplayName {
256 crate_name: CrateName,
258 canonical_name: Symbol,
260}
261
262impl CrateDisplayName {
263 pub fn canonical_name(&self) -> &Symbol {
264 &self.canonical_name
265 }
266 pub fn crate_name(&self) -> &CrateName {
267 &self.crate_name
268 }
269}
270
271impl From<CrateName> for CrateDisplayName {
272 fn from(crate_name: CrateName) -> CrateDisplayName {
273 let canonical_name = crate_name.0.clone();
274 CrateDisplayName { crate_name, canonical_name }
275 }
276}
277
278impl fmt::Display for CrateDisplayName {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 self.crate_name.fmt(f)
281 }
282}
283
284impl ops::Deref for CrateDisplayName {
285 type Target = Symbol;
286 fn deref(&self) -> &Symbol {
287 &self.crate_name
288 }
289}
290
291impl CrateDisplayName {
292 pub fn from_canonical_name(canonical_name: &str) -> CrateDisplayName {
293 let crate_name = CrateName::normalize_dashes(canonical_name);
294 CrateDisplayName { crate_name, canonical_name: Symbol::intern(canonical_name) }
295 }
296}
297
298#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
299pub enum ReleaseChannel {
300 Stable,
301 Beta,
302 Nightly,
303}
304
305impl ReleaseChannel {
306 pub fn as_str(self) -> &'static str {
307 match self {
308 ReleaseChannel::Stable => "stable",
309 ReleaseChannel::Beta => "beta",
310 ReleaseChannel::Nightly => "nightly",
311 }
312 }
313
314 #[allow(clippy::should_implement_trait)]
315 pub fn from_str(str: &str) -> Option<Self> {
316 Some(match str {
317 "" | "stable" => ReleaseChannel::Stable,
318 "nightly" => ReleaseChannel::Nightly,
319 _ if str.starts_with("beta") => ReleaseChannel::Beta,
320 _ => return None,
321 })
322 }
323}
324
325#[derive(Debug, Clone, PartialEq, Eq, Hash)]
338pub struct UniqueCrateData {
339 root_file_id: FileId,
340 disambiguator: Option<Box<(BuiltCrateData, HashableCfgOptions)>>,
341}
342
343#[derive(Debug, Clone, PartialEq, Eq, Hash)]
344pub struct CrateData<Id> {
345 pub root_file_id: FileId,
346 pub edition: Edition,
347 pub dependencies: Vec<Dependency<Id>>,
353 pub origin: CrateOrigin,
354 pub is_proc_macro: bool,
355 pub proc_macro_cwd: Arc<AbsPathBuf>,
360}
361
362pub type CrateDataBuilder = CrateData<CrateBuilderId>;
363pub type BuiltCrateData = CrateData<Crate>;
364
365#[derive(Debug, Clone, PartialEq, Eq)]
367pub struct ExtraCrateData {
368 pub version: Option<String>,
369 pub display_name: Option<CrateDisplayName>,
376 pub potential_cfg_options: Option<CfgOptions>,
378}
379
380#[derive(Default, Clone, PartialEq, Eq)]
381pub struct Env {
382 entries: FxHashMap<String, String>,
383}
384
385impl fmt::Debug for Env {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 struct EnvDebug<'s>(Vec<(&'s String, &'s String)>);
388
389 impl fmt::Debug for EnvDebug<'_> {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 f.debug_map().entries(self.0.iter().copied()).finish()
392 }
393 }
394 f.debug_struct("Env")
395 .field("entries", &{
396 let mut entries: Vec<_> = self.entries.iter().collect();
397 entries.sort();
398 EnvDebug(entries)
399 })
400 .finish()
401 }
402}
403
404#[derive(Debug, Clone, PartialEq, Eq, Hash)]
405pub struct Dependency<Id> {
406 pub crate_id: Id,
407 pub name: CrateName,
408 prelude: bool,
409 sysroot: bool,
410}
411
412pub type DependencyBuilder = Dependency<CrateBuilderId>;
413pub type BuiltDependency = Dependency<Crate>;
414
415impl DependencyBuilder {
416 pub fn new(name: CrateName, crate_id: CrateBuilderId) -> Self {
417 Self { name, crate_id, prelude: true, sysroot: false }
418 }
419
420 pub fn with_prelude(
421 name: CrateName,
422 crate_id: CrateBuilderId,
423 prelude: bool,
424 sysroot: bool,
425 ) -> Self {
426 Self { name, crate_id, prelude, sysroot }
427 }
428}
429
430impl BuiltDependency {
431 pub fn is_prelude(&self) -> bool {
433 self.prelude
434 }
435
436 pub fn is_sysroot(&self) -> bool {
438 self.sysroot
439 }
440}
441
442pub type CratesIdMap = FxHashMap<CrateBuilderId, Crate>;
443
444#[salsa_macros::input]
445#[derive(Debug, PartialOrd, Ord)]
446pub struct Crate {
447 #[returns(ref)]
448 pub data: BuiltCrateData,
449 #[returns(ref)]
453 pub extra_data: ExtraCrateData,
454 #[returns(ref)]
456 pub workspace_data: Arc<CrateWorkspaceData>,
457 #[returns(ref)]
458 pub cfg_options: CfgOptions,
459 #[returns(ref)]
460 pub env: Env,
461}
462
463impl Crate {
464 pub fn transitive_deps(self, db: &dyn salsa::Database) -> Vec<Crate> {
469 let mut worklist = vec![self];
472 let mut deps_seen = FxHashSet::default();
473 let mut deps = Vec::new();
474
475 while let Some(krate) = worklist.pop() {
476 if !deps_seen.insert(krate) {
477 continue;
478 }
479 deps.push(krate);
480
481 worklist.extend(krate.data(db).dependencies.iter().map(|dep| dep.crate_id));
482 }
483 deps
484 }
485
486 pub fn transitive_rev_deps(self, db: &dyn RootQueryDb) -> Box<[Crate]> {
491 let mut worklist = vec![self];
492 let mut rev_deps = FxHashSet::default();
493 rev_deps.insert(self);
494
495 let mut inverted_graph = FxHashMap::<_, Vec<_>>::default();
496 db.all_crates().iter().for_each(|&krate| {
497 krate
498 .data(db)
499 .dependencies
500 .iter()
501 .for_each(|dep| inverted_graph.entry(dep.crate_id).or_default().push(krate))
502 });
503
504 while let Some(krate) = worklist.pop() {
505 if let Some(crate_rev_deps) = inverted_graph.get(&krate) {
506 crate_rev_deps
507 .iter()
508 .copied()
509 .filter(|&rev_dep| rev_deps.insert(rev_dep))
510 .for_each(|rev_dep| worklist.push(rev_dep));
511 }
512 }
513
514 rev_deps.into_iter().collect::<Box<_>>()
515 }
516}
517
518#[derive(Debug, Default)]
520pub struct CratesMap(DashMap<UniqueCrateData, Crate, BuildHasherDefault<FxHasher>>);
521
522impl CrateGraphBuilder {
523 pub fn add_crate_root(
524 &mut self,
525 root_file_id: FileId,
526 edition: Edition,
527 display_name: Option<CrateDisplayName>,
528 version: Option<String>,
529 mut cfg_options: CfgOptions,
530 mut potential_cfg_options: Option<CfgOptions>,
531 mut env: Env,
532 origin: CrateOrigin,
533 is_proc_macro: bool,
534 proc_macro_cwd: Arc<AbsPathBuf>,
535 ws_data: Arc<CrateWorkspaceData>,
536 ) -> CrateBuilderId {
537 env.entries.shrink_to_fit();
538 cfg_options.shrink_to_fit();
539 if let Some(potential_cfg_options) = &mut potential_cfg_options {
540 potential_cfg_options.shrink_to_fit();
541 }
542 self.arena.alloc(CrateBuilder {
543 basic: CrateData {
544 root_file_id,
545 edition,
546 dependencies: Vec::new(),
547 origin,
548 is_proc_macro,
549 proc_macro_cwd,
550 },
551 extra: ExtraCrateData { version, display_name, potential_cfg_options },
552 cfg_options,
553 env,
554 ws_data,
555 })
556 }
557
558 pub fn add_dep(
559 &mut self,
560 from: CrateBuilderId,
561 dep: DependencyBuilder,
562 ) -> Result<(), CyclicDependenciesError> {
563 let _p = tracing::info_span!("add_dep").entered();
564
565 if let Some(path) = self.find_path(&mut FxHashSet::default(), dep.crate_id, from) {
569 let path =
570 path.into_iter().map(|it| (it, self[it].extra.display_name.clone())).collect();
571 let err = CyclicDependenciesError { path };
572 assert!(err.from().0 == from && err.to().0 == dep.crate_id);
573 return Err(err);
574 }
575
576 self.arena[from].basic.dependencies.push(dep);
577 Ok(())
578 }
579
580 pub fn set_in_db(self, db: &mut dyn RootQueryDb) -> CratesIdMap {
581 let mut all_crates = FxIndexSet::with_capacity_and_hasher(self.arena.len(), FxBuildHasher);
584 let mut visited = FxHashMap::default();
585 let mut visited_root_files = FxHashSet::default();
586
587 let old_all_crates = db.all_crates();
588
589 let crates_map = db.crates_map();
590 for krate in self.iter() {
592 go(
593 &self,
594 db,
595 &crates_map,
596 &mut visited,
597 &mut visited_root_files,
598 &mut all_crates,
599 krate,
600 );
601 }
602
603 if old_all_crates.len() != all_crates.len()
604 || old_all_crates.iter().any(|&krate| !all_crates.contains(&krate))
605 {
606 db.set_all_crates_with_durability(
607 Arc::new(Vec::from_iter(all_crates).into_boxed_slice()),
608 Durability::MEDIUM,
609 );
610 }
611
612 return visited;
613
614 fn go(
615 graph: &CrateGraphBuilder,
616 db: &mut dyn RootQueryDb,
617 crates_map: &CratesMap,
618 visited: &mut FxHashMap<CrateBuilderId, Crate>,
619 visited_root_files: &mut FxHashSet<FileId>,
620 all_crates: &mut FxIndexSet<Crate>,
621 source: CrateBuilderId,
622 ) -> Crate {
623 if let Some(&crate_id) = visited.get(&source) {
624 return crate_id;
625 }
626 let krate = &graph[source];
627 let dependencies = krate
628 .basic
629 .dependencies
630 .iter()
631 .map(|dep| BuiltDependency {
632 crate_id: go(
633 graph,
634 db,
635 crates_map,
636 visited,
637 visited_root_files,
638 all_crates,
639 dep.crate_id,
640 ),
641 name: dep.name.clone(),
642 prelude: dep.prelude,
643 sysroot: dep.sysroot,
644 })
645 .collect::<Vec<_>>();
646 let crate_data = BuiltCrateData {
647 dependencies,
648 edition: krate.basic.edition,
649 is_proc_macro: krate.basic.is_proc_macro,
650 origin: krate.basic.origin.clone(),
651 root_file_id: krate.basic.root_file_id,
652 proc_macro_cwd: krate.basic.proc_macro_cwd.clone(),
653 };
654 let disambiguator = if visited_root_files.insert(krate.basic.root_file_id) {
655 None
656 } else {
657 Some(Box::new((crate_data.clone(), krate.cfg_options.to_hashable())))
658 };
659
660 let unique_crate_data =
661 UniqueCrateData { root_file_id: krate.basic.root_file_id, disambiguator };
662 let crate_input = match crates_map.0.entry(unique_crate_data) {
663 Entry::Occupied(entry) => {
664 let old_crate = *entry.get();
665 if crate_data != *old_crate.data(db) {
666 old_crate.set_data(db).with_durability(Durability::MEDIUM).to(crate_data);
667 }
668 if krate.extra != *old_crate.extra_data(db) {
669 old_crate
670 .set_extra_data(db)
671 .with_durability(Durability::MEDIUM)
672 .to(krate.extra.clone());
673 }
674 if krate.cfg_options != *old_crate.cfg_options(db) {
675 old_crate
676 .set_cfg_options(db)
677 .with_durability(Durability::MEDIUM)
678 .to(krate.cfg_options.clone());
679 }
680 if krate.env != *old_crate.env(db) {
681 old_crate
682 .set_env(db)
683 .with_durability(Durability::MEDIUM)
684 .to(krate.env.clone());
685 }
686 if krate.ws_data != *old_crate.workspace_data(db) {
687 old_crate
688 .set_workspace_data(db)
689 .with_durability(Durability::MEDIUM)
690 .to(krate.ws_data.clone());
691 }
692 old_crate
693 }
694 Entry::Vacant(entry) => {
695 let input = Crate::builder(
696 crate_data,
697 krate.extra.clone(),
698 krate.ws_data.clone(),
699 krate.cfg_options.clone(),
700 krate.env.clone(),
701 )
702 .durability(Durability::MEDIUM)
703 .new(db);
704 entry.insert(input);
705 input
706 }
707 };
708 all_crates.insert(crate_input);
709 visited.insert(source, crate_input);
710 crate_input
711 }
712 }
713
714 pub fn iter(&self) -> impl Iterator<Item = CrateBuilderId> + '_ {
715 self.arena.iter().map(|(idx, _)| idx)
716 }
717
718 pub fn transitive_deps(&self, of: CrateBuilderId) -> impl Iterator<Item = CrateBuilderId> {
721 let mut worklist = vec![of];
722 let mut deps = FxHashSet::default();
723
724 while let Some(krate) = worklist.pop() {
725 if !deps.insert(krate) {
726 continue;
727 }
728
729 worklist.extend(self[krate].basic.dependencies.iter().map(|dep| dep.crate_id));
730 }
731
732 deps.into_iter()
733 }
734
735 fn crates_in_topological_order(&self) -> Vec<CrateBuilderId> {
738 let mut res = Vec::new();
739 let mut visited = FxHashSet::default();
740
741 for krate in self.iter() {
742 go(self, &mut visited, &mut res, krate);
743 }
744
745 return res;
746
747 fn go(
748 graph: &CrateGraphBuilder,
749 visited: &mut FxHashSet<CrateBuilderId>,
750 res: &mut Vec<CrateBuilderId>,
751 source: CrateBuilderId,
752 ) {
753 if !visited.insert(source) {
754 return;
755 }
756 for dep in graph[source].basic.dependencies.iter() {
757 go(graph, visited, res, dep.crate_id)
758 }
759 res.push(source)
760 }
761 }
762
763 pub fn extend(
771 &mut self,
772 mut other: CrateGraphBuilder,
773 proc_macros: &mut ProcMacroPaths,
774 ) -> FxHashMap<CrateBuilderId, CrateBuilderId> {
775 self.arena
778 .iter_mut()
779 .for_each(|(_, data)| data.basic.dependencies.sort_by_key(|dep| dep.crate_id));
780
781 let m = self.arena.len();
782 let topo = other.crates_in_topological_order();
783 let mut id_map: FxHashMap<CrateBuilderId, CrateBuilderId> = FxHashMap::default();
784 for topo in topo {
785 let crate_data = &mut other.arena[topo];
786
787 crate_data
788 .basic
789 .dependencies
790 .iter_mut()
791 .for_each(|dep| dep.crate_id = id_map[&dep.crate_id]);
792 crate_data.basic.dependencies.sort_by_key(|dep| dep.crate_id);
793
794 let find = self.arena.iter().take(m).find_map(|(k, v)| (v == crate_data).then_some(k));
795 let new_id = find.unwrap_or_else(|| self.arena.alloc(crate_data.clone()));
796 id_map.insert(topo, new_id);
797 }
798
799 *proc_macros =
800 mem::take(proc_macros).into_iter().map(|(id, macros)| (id_map[&id], macros)).collect();
801 id_map
802 }
803
804 fn find_path(
805 &self,
806 visited: &mut FxHashSet<CrateBuilderId>,
807 from: CrateBuilderId,
808 to: CrateBuilderId,
809 ) -> Option<Vec<CrateBuilderId>> {
810 if !visited.insert(from) {
811 return None;
812 }
813
814 if from == to {
815 return Some(vec![to]);
816 }
817
818 for dep in &self[from].basic.dependencies {
819 let crate_id = dep.crate_id;
820 if let Some(mut path) = self.find_path(visited, crate_id, to) {
821 path.push(from);
822 return Some(path);
823 }
824 }
825
826 None
827 }
828
829 pub fn remove_crates_except(
832 &mut self,
833 to_keep: &[CrateBuilderId],
834 ) -> Vec<Option<CrateBuilderId>> {
835 let mut id_map = vec![None; self.arena.len()];
836 self.arena = std::mem::take(&mut self.arena)
837 .into_iter()
838 .filter_map(|(id, data)| if to_keep.contains(&id) { Some((id, data)) } else { None })
839 .enumerate()
840 .map(|(new_id, (id, data))| {
841 id_map[id.into_raw().into_u32() as usize] =
842 Some(CrateBuilderId::from_raw(RawIdx::from_u32(new_id as u32)));
843 data
844 })
845 .collect();
846 for (_, data) in self.arena.iter_mut() {
847 data.basic.dependencies.iter_mut().for_each(|dep| {
848 dep.crate_id =
849 id_map[dep.crate_id.into_raw().into_u32() as usize].expect("crate was filtered")
850 });
851 }
852 id_map
853 }
854
855 pub fn shrink_to_fit(&mut self) {
856 self.arena.shrink_to_fit();
857 }
858}
859
860impl Crate {
861 pub fn root_file_id(self, db: &dyn salsa::Database) -> EditionedFileId {
862 let data = self.data(db);
863 EditionedFileId::new(db, data.root_file_id, data.edition, self)
864 }
865}
866
867impl Extend<(String, String)> for Env {
868 fn extend<T: IntoIterator<Item = (String, String)>>(&mut self, iter: T) {
869 self.entries.extend(iter);
870 }
871}
872
873impl FromIterator<(String, String)> for Env {
874 fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
875 Env { entries: FromIterator::from_iter(iter) }
876 }
877}
878
879impl Env {
880 pub fn set(&mut self, env: &str, value: impl Into<String>) {
881 self.entries.insert(env.to_owned(), value.into());
882 }
883
884 pub fn get(&self, env: &str) -> Option<String> {
885 self.entries.get(env).cloned()
886 }
887
888 pub fn extend_from_other(&mut self, other: &Env) {
889 self.entries.extend(other.entries.iter().map(|(x, y)| (x.to_owned(), y.to_owned())));
890 }
891
892 pub fn is_empty(&self) -> bool {
893 self.entries.is_empty()
894 }
895
896 pub fn insert(&mut self, k: impl Into<String>, v: impl Into<String>) -> Option<String> {
897 self.entries.insert(k.into(), v.into())
898 }
899
900 pub fn contains_key(&self, arg: &str) -> bool {
901 self.entries.contains_key(arg)
902 }
903}
904
905impl From<Env> for Vec<(String, String)> {
906 fn from(env: Env) -> Vec<(String, String)> {
907 let mut entries: Vec<_> = env.entries.into_iter().collect();
908 entries.sort();
909 entries
910 }
911}
912
913impl<'a> IntoIterator for &'a Env {
914 type Item = (&'a String, &'a String);
915 type IntoIter = std::collections::hash_map::Iter<'a, String, String>;
916
917 fn into_iter(self) -> Self::IntoIter {
918 self.entries.iter()
919 }
920}
921
922#[derive(Debug)]
923pub struct CyclicDependenciesError {
924 path: Vec<(CrateBuilderId, Option<CrateDisplayName>)>,
925}
926
927impl CyclicDependenciesError {
928 fn from(&self) -> &(CrateBuilderId, Option<CrateDisplayName>) {
929 self.path.first().unwrap()
930 }
931 fn to(&self) -> &(CrateBuilderId, Option<CrateDisplayName>) {
932 self.path.last().unwrap()
933 }
934}
935
936impl fmt::Display for CyclicDependenciesError {
937 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
938 let render = |(id, name): &(CrateBuilderId, Option<CrateDisplayName>)| match name {
939 Some(it) => format!("{it}({id:?})"),
940 None => format!("{id:?}"),
941 };
942 let path = self.path.iter().rev().map(render).collect::<Vec<String>>().join(" -> ");
943 write!(
944 f,
945 "cyclic deps: {} -> {}, alternative path: {}",
946 render(self.from()),
947 render(self.to()),
948 path
949 )
950 }
951}
952
953#[cfg(test)]
954mod tests {
955 use triomphe::Arc;
956 use vfs::AbsPathBuf;
957
958 use crate::{CrateWorkspaceData, DependencyBuilder};
959
960 use super::{CrateGraphBuilder, CrateName, CrateOrigin, Edition::Edition2018, Env, FileId};
961
962 fn empty_ws_data() -> Arc<CrateWorkspaceData> {
963 Arc::new(CrateWorkspaceData { target: Err("".into()), toolchain: None })
964 }
965
966 #[test]
967 fn detect_cyclic_dependency_indirect() {
968 let mut graph = CrateGraphBuilder::default();
969 let crate1 = graph.add_crate_root(
970 FileId::from_raw(1u32),
971 Edition2018,
972 None,
973 None,
974 Default::default(),
975 Default::default(),
976 Env::default(),
977 CrateOrigin::Local { repo: None, name: None },
978 false,
979 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
980 empty_ws_data(),
981 );
982 let crate2 = graph.add_crate_root(
983 FileId::from_raw(2u32),
984 Edition2018,
985 None,
986 None,
987 Default::default(),
988 Default::default(),
989 Env::default(),
990 CrateOrigin::Local { repo: None, name: None },
991 false,
992 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
993 empty_ws_data(),
994 );
995 let crate3 = graph.add_crate_root(
996 FileId::from_raw(3u32),
997 Edition2018,
998 None,
999 None,
1000 Default::default(),
1001 Default::default(),
1002 Env::default(),
1003 CrateOrigin::Local { repo: None, name: None },
1004 false,
1005 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
1006 empty_ws_data(),
1007 );
1008 assert!(
1009 graph
1010 .add_dep(crate1, DependencyBuilder::new(CrateName::new("crate2").unwrap(), crate2,))
1011 .is_ok()
1012 );
1013 assert!(
1014 graph
1015 .add_dep(crate2, DependencyBuilder::new(CrateName::new("crate3").unwrap(), crate3,))
1016 .is_ok()
1017 );
1018 assert!(
1019 graph
1020 .add_dep(crate3, DependencyBuilder::new(CrateName::new("crate1").unwrap(), crate1,))
1021 .is_err()
1022 );
1023 }
1024
1025 #[test]
1026 fn detect_cyclic_dependency_direct() {
1027 let mut graph = CrateGraphBuilder::default();
1028 let crate1 = graph.add_crate_root(
1029 FileId::from_raw(1u32),
1030 Edition2018,
1031 None,
1032 None,
1033 Default::default(),
1034 Default::default(),
1035 Env::default(),
1036 CrateOrigin::Local { repo: None, name: None },
1037 false,
1038 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
1039 empty_ws_data(),
1040 );
1041 let crate2 = graph.add_crate_root(
1042 FileId::from_raw(2u32),
1043 Edition2018,
1044 None,
1045 None,
1046 Default::default(),
1047 Default::default(),
1048 Env::default(),
1049 CrateOrigin::Local { repo: None, name: None },
1050 false,
1051 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
1052 empty_ws_data(),
1053 );
1054 assert!(
1055 graph
1056 .add_dep(crate1, DependencyBuilder::new(CrateName::new("crate2").unwrap(), crate2,))
1057 .is_ok()
1058 );
1059 assert!(
1060 graph
1061 .add_dep(crate2, DependencyBuilder::new(CrateName::new("crate2").unwrap(), crate2,))
1062 .is_err()
1063 );
1064 }
1065
1066 #[test]
1067 fn it_works() {
1068 let mut graph = CrateGraphBuilder::default();
1069 let crate1 = graph.add_crate_root(
1070 FileId::from_raw(1u32),
1071 Edition2018,
1072 None,
1073 None,
1074 Default::default(),
1075 Default::default(),
1076 Env::default(),
1077 CrateOrigin::Local { repo: None, name: None },
1078 false,
1079 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
1080 empty_ws_data(),
1081 );
1082 let crate2 = graph.add_crate_root(
1083 FileId::from_raw(2u32),
1084 Edition2018,
1085 None,
1086 None,
1087 Default::default(),
1088 Default::default(),
1089 Env::default(),
1090 CrateOrigin::Local { repo: None, name: None },
1091 false,
1092 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
1093 empty_ws_data(),
1094 );
1095 let crate3 = graph.add_crate_root(
1096 FileId::from_raw(3u32),
1097 Edition2018,
1098 None,
1099 None,
1100 Default::default(),
1101 Default::default(),
1102 Env::default(),
1103 CrateOrigin::Local { repo: None, name: None },
1104 false,
1105 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
1106 empty_ws_data(),
1107 );
1108 assert!(
1109 graph
1110 .add_dep(crate1, DependencyBuilder::new(CrateName::new("crate2").unwrap(), crate2,))
1111 .is_ok()
1112 );
1113 assert!(
1114 graph
1115 .add_dep(crate2, DependencyBuilder::new(CrateName::new("crate3").unwrap(), crate3,))
1116 .is_ok()
1117 );
1118 }
1119
1120 #[test]
1121 fn dashes_are_normalized() {
1122 let mut graph = CrateGraphBuilder::default();
1123 let crate1 = graph.add_crate_root(
1124 FileId::from_raw(1u32),
1125 Edition2018,
1126 None,
1127 None,
1128 Default::default(),
1129 Default::default(),
1130 Env::default(),
1131 CrateOrigin::Local { repo: None, name: None },
1132 false,
1133 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
1134 empty_ws_data(),
1135 );
1136 let crate2 = graph.add_crate_root(
1137 FileId::from_raw(2u32),
1138 Edition2018,
1139 None,
1140 None,
1141 Default::default(),
1142 Default::default(),
1143 Env::default(),
1144 CrateOrigin::Local { repo: None, name: None },
1145 false,
1146 Arc::new(AbsPathBuf::assert_utf8(std::env::current_dir().unwrap())),
1147 empty_ws_data(),
1148 );
1149 assert!(
1150 graph
1151 .add_dep(
1152 crate1,
1153 DependencyBuilder::new(
1154 CrateName::normalize_dashes("crate-name-with-dashes"),
1155 crate2,
1156 )
1157 )
1158 .is_ok()
1159 );
1160 assert_eq!(
1161 graph.arena[crate1].basic.dependencies,
1162 vec![
1163 DependencyBuilder::new(CrateName::new("crate_name_with_dashes").unwrap(), crate2,)
1164 ]
1165 );
1166 }
1167}