diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /crates/base_db/src/lib.rs | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/base_db/src/lib.rs')
-rw-r--r-- | crates/base_db/src/lib.rs | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/crates/base_db/src/lib.rs b/crates/base_db/src/lib.rs new file mode 100644 index 000000000..ee3415850 --- /dev/null +++ b/crates/base_db/src/lib.rs | |||
@@ -0,0 +1,167 @@ | |||
1 | //! base_db defines basic database traits. The concrete DB is defined by ide. | ||
2 | mod cancellation; | ||
3 | mod input; | ||
4 | pub mod fixture; | ||
5 | |||
6 | use std::{panic, sync::Arc}; | ||
7 | |||
8 | use rustc_hash::FxHashSet; | ||
9 | use syntax::{ast, Parse, SourceFile, TextRange, TextSize}; | ||
10 | |||
11 | pub use crate::{ | ||
12 | cancellation::Canceled, | ||
13 | input::{ | ||
14 | CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacroId, | ||
15 | SourceRoot, SourceRootId, | ||
16 | }, | ||
17 | }; | ||
18 | pub use salsa; | ||
19 | pub use vfs::{file_set::FileSet, VfsPath}; | ||
20 | |||
21 | #[macro_export] | ||
22 | macro_rules! impl_intern_key { | ||
23 | ($name:ident) => { | ||
24 | impl $crate::salsa::InternKey for $name { | ||
25 | fn from_intern_id(v: $crate::salsa::InternId) -> Self { | ||
26 | $name(v) | ||
27 | } | ||
28 | fn as_intern_id(&self) -> $crate::salsa::InternId { | ||
29 | self.0 | ||
30 | } | ||
31 | } | ||
32 | }; | ||
33 | } | ||
34 | |||
35 | pub trait Upcast<T: ?Sized> { | ||
36 | fn upcast(&self) -> &T; | ||
37 | } | ||
38 | |||
39 | pub trait CheckCanceled { | ||
40 | /// Aborts current query if there are pending changes. | ||
41 | /// | ||
42 | /// rust-analyzer needs to be able to answer semantic questions about the | ||
43 | /// code while the code is being modified. A common problem is that a | ||
44 | /// long-running query is being calculated when a new change arrives. | ||
45 | /// | ||
46 | /// We can't just apply the change immediately: this will cause the pending | ||
47 | /// query to see inconsistent state (it will observe an absence of | ||
48 | /// repeatable read). So what we do is we **cancel** all pending queries | ||
49 | /// before applying the change. | ||
50 | /// | ||
51 | /// We implement cancellation by panicking with a special value and catching | ||
52 | /// it on the API boundary. Salsa explicitly supports this use-case. | ||
53 | fn check_canceled(&self); | ||
54 | |||
55 | fn catch_canceled<F, T>(&self, f: F) -> Result<T, Canceled> | ||
56 | where | ||
57 | Self: Sized + panic::RefUnwindSafe, | ||
58 | F: FnOnce(&Self) -> T + panic::UnwindSafe, | ||
59 | { | ||
60 | panic::catch_unwind(|| f(self)).map_err(|err| match err.downcast::<Canceled>() { | ||
61 | Ok(canceled) => *canceled, | ||
62 | Err(payload) => panic::resume_unwind(payload), | ||
63 | }) | ||
64 | } | ||
65 | } | ||
66 | |||
67 | impl<T: salsa::Database> CheckCanceled for T { | ||
68 | fn check_canceled(&self) { | ||
69 | if self.salsa_runtime().is_current_revision_canceled() { | ||
70 | Canceled::throw() | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | #[derive(Clone, Copy, Debug)] | ||
76 | pub struct FilePosition { | ||
77 | pub file_id: FileId, | ||
78 | pub offset: TextSize, | ||
79 | } | ||
80 | |||
81 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
82 | pub struct FileRange { | ||
83 | pub file_id: FileId, | ||
84 | pub range: TextRange, | ||
85 | } | ||
86 | |||
87 | pub const DEFAULT_LRU_CAP: usize = 128; | ||
88 | |||
89 | pub trait FileLoader { | ||
90 | /// Text of the file. | ||
91 | fn file_text(&self, file_id: FileId) -> Arc<String>; | ||
92 | /// Note that we intentionally accept a `&str` and not a `&Path` here. This | ||
93 | /// method exists to handle `#[path = "/some/path.rs"] mod foo;` and such, | ||
94 | /// so the input is guaranteed to be utf-8 string. One might be tempted to | ||
95 | /// introduce some kind of "utf-8 path with / separators", but that's a bad idea. Behold | ||
96 | /// `#[path = "C://no/way"]` | ||
97 | fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId>; | ||
98 | fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>>; | ||
99 | } | ||
100 | |||
101 | /// Database which stores all significant input facts: source code and project | ||
102 | /// model. Everything else in rust-analyzer is derived from these queries. | ||
103 | #[salsa::query_group(SourceDatabaseStorage)] | ||
104 | pub trait SourceDatabase: CheckCanceled + FileLoader + std::fmt::Debug { | ||
105 | // Parses the file into the syntax tree. | ||
106 | #[salsa::invoke(parse_query)] | ||
107 | fn parse(&self, file_id: FileId) -> Parse<ast::SourceFile>; | ||
108 | |||
109 | /// The crate graph. | ||
110 | #[salsa::input] | ||
111 | fn crate_graph(&self) -> Arc<CrateGraph>; | ||
112 | } | ||
113 | |||
114 | fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse<ast::SourceFile> { | ||
115 | let _p = profile::span("parse_query").detail(|| format!("{:?}", file_id)); | ||
116 | let text = db.file_text(file_id); | ||
117 | SourceFile::parse(&*text) | ||
118 | } | ||
119 | |||
120 | /// We don't want to give HIR knowledge of source roots, hence we extract these | ||
121 | /// methods into a separate DB. | ||
122 | #[salsa::query_group(SourceDatabaseExtStorage)] | ||
123 | pub trait SourceDatabaseExt: SourceDatabase { | ||
124 | #[salsa::input] | ||
125 | fn file_text(&self, file_id: FileId) -> Arc<String>; | ||
126 | /// Path to a file, relative to the root of its source root. | ||
127 | /// Source root of the file. | ||
128 | #[salsa::input] | ||
129 | fn file_source_root(&self, file_id: FileId) -> SourceRootId; | ||
130 | /// Contents of the source root. | ||
131 | #[salsa::input] | ||
132 | fn source_root(&self, id: SourceRootId) -> Arc<SourceRoot>; | ||
133 | |||
134 | fn source_root_crates(&self, id: SourceRootId) -> Arc<FxHashSet<CrateId>>; | ||
135 | } | ||
136 | |||
137 | fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<FxHashSet<CrateId>> { | ||
138 | let graph = db.crate_graph(); | ||
139 | let res = graph | ||
140 | .iter() | ||
141 | .filter(|&krate| { | ||
142 | let root_file = graph[krate].root_file_id; | ||
143 | db.file_source_root(root_file) == id | ||
144 | }) | ||
145 | .collect::<FxHashSet<_>>(); | ||
146 | Arc::new(res) | ||
147 | } | ||
148 | |||
149 | /// Silly workaround for cyclic deps between the traits | ||
150 | pub struct FileLoaderDelegate<T>(pub T); | ||
151 | |||
152 | impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> { | ||
153 | fn file_text(&self, file_id: FileId) -> Arc<String> { | ||
154 | SourceDatabaseExt::file_text(self.0, file_id) | ||
155 | } | ||
156 | fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> { | ||
157 | // FIXME: this *somehow* should be platform agnostic... | ||
158 | let source_root = self.0.file_source_root(anchor); | ||
159 | let source_root = self.0.source_root(source_root); | ||
160 | source_root.file_set.resolve_path(anchor, path) | ||
161 | } | ||
162 | |||
163 | fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { | ||
164 | let source_root = self.0.file_source_root(file_id); | ||
165 | self.0.source_root_crates(source_root) | ||
166 | } | ||
167 | } | ||