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