]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/static_index.rs
Merge #11951
[rust.git] / crates / ide / src / static_index.rs
1 //! This module provides `StaticIndex` which is used for powering
2 //! read-only code browsers and emitting LSIF
3
4 use std::collections::HashMap;
5
6 use hir::{db::HirDatabase, Crate, Module, Semantics};
7 use ide_db::{
8     base_db::{FileId, FileRange, SourceDatabaseExt},
9     defs::{Definition, IdentClass},
10     RootDatabase,
11 };
12 use rustc_hash::FxHashSet;
13 use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T};
14
15 use crate::{
16     hover::hover_for_definition, Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult,
17     InlayHint, InlayHintsConfig, TryToNav,
18 };
19 use crate::{
20     moniker::{crate_for_file, def_to_moniker, MonikerResult},
21     LifetimeElisionHints,
22 };
23
24 /// A static representation of fully analyzed source code.
25 ///
26 /// The intended use-case is powering read-only code browsers and emitting LSIF
27 #[derive(Debug)]
28 pub struct StaticIndex<'a> {
29     pub files: Vec<StaticIndexedFile>,
30     pub tokens: TokenStore,
31     analysis: &'a Analysis,
32     db: &'a RootDatabase,
33     def_map: HashMap<Definition, TokenId>,
34 }
35
36 #[derive(Debug)]
37 pub struct ReferenceData {
38     pub range: FileRange,
39     pub is_definition: bool,
40 }
41
42 #[derive(Debug)]
43 pub struct TokenStaticData {
44     pub hover: Option<HoverResult>,
45     pub definition: Option<FileRange>,
46     pub references: Vec<ReferenceData>,
47     pub moniker: Option<MonikerResult>,
48 }
49
50 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51 pub struct TokenId(usize);
52
53 impl TokenId {
54     pub fn raw(self) -> usize {
55         self.0
56     }
57 }
58
59 #[derive(Default, Debug)]
60 pub struct TokenStore(Vec<TokenStaticData>);
61
62 impl TokenStore {
63     pub fn insert(&mut self, data: TokenStaticData) -> TokenId {
64         let id = TokenId(self.0.len());
65         self.0.push(data);
66         id
67     }
68
69     pub fn get_mut(&mut self, id: TokenId) -> Option<&mut TokenStaticData> {
70         self.0.get_mut(id.0)
71     }
72
73     pub fn get(&self, id: TokenId) -> Option<&TokenStaticData> {
74         self.0.get(id.0)
75     }
76
77     pub fn iter(self) -> impl Iterator<Item = (TokenId, TokenStaticData)> {
78         self.0.into_iter().enumerate().map(|(i, x)| (TokenId(i), x))
79     }
80 }
81
82 #[derive(Debug)]
83 pub struct StaticIndexedFile {
84     pub file_id: FileId,
85     pub folds: Vec<Fold>,
86     pub inlay_hints: Vec<InlayHint>,
87     pub tokens: Vec<(TextRange, TokenId)>,
88 }
89
90 fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
91     let mut worklist: Vec<_> =
92         Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect();
93     let mut modules = Vec::new();
94
95     while let Some(module) = worklist.pop() {
96         modules.push(module);
97         worklist.extend(module.children(db));
98     }
99
100     modules
101 }
102
103 impl StaticIndex<'_> {
104     fn add_file(&mut self, file_id: FileId) {
105         let current_crate = crate_for_file(self.db, file_id);
106         let folds = self.analysis.folding_ranges(file_id).unwrap();
107         let inlay_hints = self
108             .analysis
109             .inlay_hints(
110                 &InlayHintsConfig {
111                     render_colons: true,
112                     type_hints: true,
113                     parameter_hints: true,
114                     chaining_hints: true,
115                     closure_return_type_hints: true,
116                     lifetime_elision_hints: LifetimeElisionHints::Never,
117                     reborrow_hints: false,
118                     hide_named_constructor_hints: false,
119                     param_names_for_lifetime_elision_hints: false,
120                     max_length: Some(25),
121                 },
122                 file_id,
123                 None,
124             )
125             .unwrap();
126         // hovers
127         let sema = hir::Semantics::new(self.db);
128         let tokens_or_nodes = sema.parse(file_id).syntax().clone();
129         let tokens = tokens_or_nodes.descendants_with_tokens().filter_map(|x| match x {
130             syntax::NodeOrToken::Node(_) => None,
131             syntax::NodeOrToken::Token(x) => Some(x),
132         });
133         let hover_config =
134             HoverConfig { links_in_hover: true, documentation: Some(HoverDocFormat::Markdown) };
135         let tokens = tokens.filter(|token| {
136             matches!(
137                 token.kind(),
138                 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self]
139             )
140         });
141         let mut result = StaticIndexedFile { file_id, inlay_hints, folds, tokens: vec![] };
142         for token in tokens {
143             let range = token.text_range();
144             let node = token.parent().unwrap();
145             let def = match get_definition(&sema, token.clone()) {
146                 Some(x) => x,
147                 None => continue,
148             };
149             let id = if let Some(x) = self.def_map.get(&def) {
150                 *x
151             } else {
152                 let x = self.tokens.insert(TokenStaticData {
153                     hover: hover_for_definition(&sema, file_id, def, &node, &hover_config),
154                     definition: def
155                         .try_to_nav(self.db)
156                         .map(|x| FileRange { file_id: x.file_id, range: x.focus_or_full_range() }),
157                     references: vec![],
158                     moniker: current_crate.and_then(|cc| def_to_moniker(self.db, def, cc)),
159                 });
160                 self.def_map.insert(def, x);
161                 x
162             };
163             let token = self.tokens.get_mut(id).unwrap();
164             token.references.push(ReferenceData {
165                 range: FileRange { range, file_id },
166                 is_definition: match def.try_to_nav(self.db) {
167                     Some(x) => x.file_id == file_id && x.focus_or_full_range() == range,
168                     None => false,
169                 },
170             });
171             result.tokens.push((range, id));
172         }
173         self.files.push(result);
174     }
175
176     pub fn compute(analysis: &Analysis) -> StaticIndex {
177         let db = &*analysis.db;
178         let work = all_modules(db).into_iter().filter(|module| {
179             let file_id = module.definition_source(db).file_id.original_file(db);
180             let source_root = db.file_source_root(file_id);
181             let source_root = db.source_root(source_root);
182             !source_root.is_library
183         });
184         let mut this = StaticIndex {
185             files: vec![],
186             tokens: Default::default(),
187             analysis,
188             db,
189             def_map: Default::default(),
190         };
191         let mut visited_files = FxHashSet::default();
192         for module in work {
193             let file_id = module.definition_source(db).file_id.original_file(db);
194             if visited_files.contains(&file_id) {
195                 continue;
196             }
197             this.add_file(file_id);
198             // mark the file
199             visited_files.insert(file_id);
200         }
201         this
202     }
203 }
204
205 fn get_definition(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Definition> {
206     for token in sema.descend_into_macros(token) {
207         let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions);
208         if let Some(&[x]) = def.as_deref() {
209             return Some(x);
210         } else {
211             continue;
212         };
213     }
214     None
215 }
216
217 #[cfg(test)]
218 mod tests {
219     use crate::{fixture, StaticIndex};
220     use ide_db::base_db::FileRange;
221     use std::collections::HashSet;
222     use syntax::TextSize;
223
224     fn check_all_ranges(ra_fixture: &str) {
225         let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture);
226         let s = StaticIndex::compute(&analysis);
227         let mut range_set: HashSet<_> = ranges.iter().map(|x| x.0).collect();
228         for f in s.files {
229             for (range, _) in f.tokens {
230                 let x = FileRange { file_id: f.file_id, range };
231                 if !range_set.contains(&x) {
232                     panic!("additional range {:?}", x);
233                 }
234                 range_set.remove(&x);
235             }
236         }
237         if !range_set.is_empty() {
238             panic!("unfound ranges {:?}", range_set);
239         }
240     }
241
242     fn check_definitions(ra_fixture: &str) {
243         let (analysis, ranges) = fixture::annotations_without_marker(ra_fixture);
244         let s = StaticIndex::compute(&analysis);
245         let mut range_set: HashSet<_> = ranges.iter().map(|x| x.0).collect();
246         for (_, t) in s.tokens.iter() {
247             if let Some(x) = t.definition {
248                 if x.range.start() == TextSize::from(0) {
249                     // ignore definitions that are whole of file
250                     continue;
251                 }
252                 if !range_set.contains(&x) {
253                     panic!("additional definition {:?}", x);
254                 }
255                 range_set.remove(&x);
256             }
257         }
258         if !range_set.is_empty() {
259             panic!("unfound definitions {:?}", range_set);
260         }
261     }
262
263     #[test]
264     fn struct_and_enum() {
265         check_all_ranges(
266             r#"
267 struct Foo;
268      //^^^
269 enum E { X(Foo) }
270    //^   ^ ^^^
271 "#,
272         );
273         check_definitions(
274             r#"
275 struct Foo;
276      //^^^
277 enum E { X(Foo) }
278    //^   ^
279 "#,
280         );
281     }
282
283     #[test]
284     fn multi_crate() {
285         check_definitions(
286             r#"
287 //- /main.rs crate:main deps:foo
288
289
290 use foo::func;
291
292 fn main() {
293  //^^^^
294     func();
295 }
296 //- /foo/lib.rs crate:foo
297
298 pub func() {
299
300 }
301 "#,
302         );
303     }
304
305     #[test]
306     fn derives() {
307         check_all_ranges(
308             r#"
309 //- minicore:derive
310 #[rustc_builtin_macro]
311 //^^^^^^^^^^^^^^^^^^^
312 pub macro Copy {}
313         //^^^^
314 #[derive(Copy)]
315 //^^^^^^ ^^^^
316 struct Hello(i32);
317      //^^^^^ ^^^
318 "#,
319         );
320     }
321 }