]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/static_index.rs
Rollup merge of #99724 - ehuss:fix-broken-links-fragment, r=Dylan-DPC
[rust.git] / src / tools / rust-analyzer / 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     FxHashSet, RootDatabase,
11 };
12 use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T};
13
14 use crate::{
15     hover::hover_for_definition,
16     moniker::{crate_for_file, def_to_moniker, MonikerResult},
17     Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig,
18     TryToNav,
19 };
20
21 /// A static representation of fully analyzed source code.
22 ///
23 /// The intended use-case is powering read-only code browsers and emitting LSIF
24 #[derive(Debug)]
25 pub struct StaticIndex<'a> {
26     pub files: Vec<StaticIndexedFile>,
27     pub tokens: TokenStore,
28     analysis: &'a Analysis,
29     db: &'a RootDatabase,
30     def_map: HashMap<Definition, TokenId>,
31 }
32
33 #[derive(Debug)]
34 pub struct ReferenceData {
35     pub range: FileRange,
36     pub is_definition: bool,
37 }
38
39 #[derive(Debug)]
40 pub struct TokenStaticData {
41     pub hover: Option<HoverResult>,
42     pub definition: Option<FileRange>,
43     pub references: Vec<ReferenceData>,
44     pub moniker: Option<MonikerResult>,
45 }
46
47 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48 pub struct TokenId(usize);
49
50 impl TokenId {
51     pub fn raw(self) -> usize {
52         self.0
53     }
54 }
55
56 #[derive(Default, Debug)]
57 pub struct TokenStore(Vec<TokenStaticData>);
58
59 impl TokenStore {
60     pub fn insert(&mut self, data: TokenStaticData) -> TokenId {
61         let id = TokenId(self.0.len());
62         self.0.push(data);
63         id
64     }
65
66     pub fn get_mut(&mut self, id: TokenId) -> Option<&mut TokenStaticData> {
67         self.0.get_mut(id.0)
68     }
69
70     pub fn get(&self, id: TokenId) -> Option<&TokenStaticData> {
71         self.0.get(id.0)
72     }
73
74     pub fn iter(self) -> impl Iterator<Item = (TokenId, TokenStaticData)> {
75         self.0.into_iter().enumerate().map(|(i, x)| (TokenId(i), x))
76     }
77 }
78
79 #[derive(Debug)]
80 pub struct StaticIndexedFile {
81     pub file_id: FileId,
82     pub folds: Vec<Fold>,
83     pub inlay_hints: Vec<InlayHint>,
84     pub tokens: Vec<(TextRange, TokenId)>,
85 }
86
87 fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
88     let mut worklist: Vec<_> =
89         Crate::all(db).into_iter().map(|krate| krate.root_module(db)).collect();
90     let mut modules = Vec::new();
91
92     while let Some(module) = worklist.pop() {
93         modules.push(module);
94         worklist.extend(module.children(db));
95     }
96
97     modules
98 }
99
100 impl StaticIndex<'_> {
101     fn add_file(&mut self, file_id: FileId) {
102         let current_crate = crate_for_file(self.db, file_id);
103         let folds = self.analysis.folding_ranges(file_id).unwrap();
104         let inlay_hints = self
105             .analysis
106             .inlay_hints(
107                 &InlayHintsConfig {
108                     render_colons: true,
109                     type_hints: true,
110                     parameter_hints: true,
111                     chaining_hints: true,
112                     closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock,
113                     lifetime_elision_hints: crate::LifetimeElisionHints::Never,
114                     reborrow_hints: crate::ReborrowHints::Never,
115                     hide_named_constructor_hints: false,
116                     hide_closure_initialization_hints: false,
117                     param_names_for_lifetime_elision_hints: false,
118                     binding_mode_hints: false,
119                     max_length: Some(25),
120                     closing_brace_hints_min_lines: 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 }