diff options
Diffstat (limited to 'crates/hir_def/src/nameres.rs')
-rw-r--r-- | crates/hir_def/src/nameres.rs | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs new file mode 100644 index 000000000..bf302172d --- /dev/null +++ b/crates/hir_def/src/nameres.rs | |||
@@ -0,0 +1,326 @@ | |||
1 | //! This module implements import-resolution/macro expansion algorithm. | ||
2 | //! | ||
3 | //! The result of this module is `CrateDefMap`: a data structure which contains: | ||
4 | //! | ||
5 | //! * a tree of modules for the crate | ||
6 | //! * for each module, a set of items visible in the module (directly declared | ||
7 | //! or imported) | ||
8 | //! | ||
9 | //! Note that `CrateDefMap` contains fully macro expanded code. | ||
10 | //! | ||
11 | //! Computing `CrateDefMap` can be partitioned into several logically | ||
12 | //! independent "phases". The phases are mutually recursive though, there's no | ||
13 | //! strict ordering. | ||
14 | //! | ||
15 | //! ## Collecting RawItems | ||
16 | //! | ||
17 | //! This happens in the `raw` module, which parses a single source file into a | ||
18 | //! set of top-level items. Nested imports are desugared to flat imports in this | ||
19 | //! phase. Macro calls are represented as a triple of (Path, Option<Name>, | ||
20 | //! TokenTree). | ||
21 | //! | ||
22 | //! ## Collecting Modules | ||
23 | //! | ||
24 | //! This happens in the `collector` module. In this phase, we recursively walk | ||
25 | //! tree of modules, collect raw items from submodules, populate module scopes | ||
26 | //! with defined items (so, we assign item ids in this phase) and record the set | ||
27 | //! of unresolved imports and macros. | ||
28 | //! | ||
29 | //! While we walk tree of modules, we also record macro_rules definitions and | ||
30 | //! expand calls to macro_rules defined macros. | ||
31 | //! | ||
32 | //! ## Resolving Imports | ||
33 | //! | ||
34 | //! We maintain a list of currently unresolved imports. On every iteration, we | ||
35 | //! try to resolve some imports from this list. If the import is resolved, we | ||
36 | //! record it, by adding an item to current module scope and, if necessary, by | ||
37 | //! recursively populating glob imports. | ||
38 | //! | ||
39 | //! ## Resolving Macros | ||
40 | //! | ||
41 | //! macro_rules from the same crate use a global mutable namespace. We expand | ||
42 | //! them immediately, when we collect modules. | ||
43 | //! | ||
44 | //! Macros from other crates (including proc-macros) can be used with | ||
45 | //! `foo::bar!` syntax. We handle them similarly to imports. There's a list of | ||
46 | //! unexpanded macros. On every iteration, we try to resolve each macro call | ||
47 | //! path and, upon success, we run macro expansion and "collect module" phase on | ||
48 | //! the result | ||
49 | |||
50 | mod collector; | ||
51 | mod mod_resolution; | ||
52 | mod path_resolution; | ||
53 | |||
54 | #[cfg(test)] | ||
55 | mod tests; | ||
56 | |||
57 | use std::sync::Arc; | ||
58 | |||
59 | use arena::Arena; | ||
60 | use base_db::{CrateId, Edition, FileId}; | ||
61 | use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile}; | ||
62 | use rustc_hash::FxHashMap; | ||
63 | use stdx::format_to; | ||
64 | use syntax::ast; | ||
65 | |||
66 | use crate::{ | ||
67 | db::DefDatabase, | ||
68 | item_scope::{BuiltinShadowMode, ItemScope}, | ||
69 | nameres::{diagnostics::DefDiagnostic, path_resolution::ResolveMode}, | ||
70 | path::ModPath, | ||
71 | per_ns::PerNs, | ||
72 | AstId, LocalModuleId, ModuleDefId, ModuleId, | ||
73 | }; | ||
74 | |||
75 | /// Contains all top-level defs from a macro-expanded crate | ||
76 | #[derive(Debug, PartialEq, Eq)] | ||
77 | pub struct CrateDefMap { | ||
78 | pub root: LocalModuleId, | ||
79 | pub modules: Arena<ModuleData>, | ||
80 | pub(crate) krate: CrateId, | ||
81 | /// The prelude module for this crate. This either comes from an import | ||
82 | /// marked with the `prelude_import` attribute, or (in the normal case) from | ||
83 | /// a dependency (`std` or `core`). | ||
84 | pub(crate) prelude: Option<ModuleId>, | ||
85 | pub(crate) extern_prelude: FxHashMap<Name, ModuleDefId>, | ||
86 | |||
87 | edition: Edition, | ||
88 | diagnostics: Vec<DefDiagnostic>, | ||
89 | } | ||
90 | |||
91 | impl std::ops::Index<LocalModuleId> for CrateDefMap { | ||
92 | type Output = ModuleData; | ||
93 | fn index(&self, id: LocalModuleId) -> &ModuleData { | ||
94 | &self.modules[id] | ||
95 | } | ||
96 | } | ||
97 | |||
98 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] | ||
99 | pub enum ModuleOrigin { | ||
100 | CrateRoot { | ||
101 | definition: FileId, | ||
102 | }, | ||
103 | /// Note that non-inline modules, by definition, live inside non-macro file. | ||
104 | File { | ||
105 | is_mod_rs: bool, | ||
106 | declaration: AstId<ast::Module>, | ||
107 | definition: FileId, | ||
108 | }, | ||
109 | Inline { | ||
110 | definition: AstId<ast::Module>, | ||
111 | }, | ||
112 | } | ||
113 | |||
114 | impl Default for ModuleOrigin { | ||
115 | fn default() -> Self { | ||
116 | ModuleOrigin::CrateRoot { definition: FileId(0) } | ||
117 | } | ||
118 | } | ||
119 | |||
120 | impl ModuleOrigin { | ||
121 | fn declaration(&self) -> Option<AstId<ast::Module>> { | ||
122 | match self { | ||
123 | ModuleOrigin::File { declaration: module, .. } | ||
124 | | ModuleOrigin::Inline { definition: module, .. } => Some(*module), | ||
125 | ModuleOrigin::CrateRoot { .. } => None, | ||
126 | } | ||
127 | } | ||
128 | |||
129 | pub fn file_id(&self) -> Option<FileId> { | ||
130 | match self { | ||
131 | ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => { | ||
132 | Some(*definition) | ||
133 | } | ||
134 | _ => None, | ||
135 | } | ||
136 | } | ||
137 | |||
138 | pub fn is_inline(&self) -> bool { | ||
139 | match self { | ||
140 | ModuleOrigin::Inline { .. } => true, | ||
141 | ModuleOrigin::CrateRoot { .. } | ModuleOrigin::File { .. } => false, | ||
142 | } | ||
143 | } | ||
144 | |||
145 | /// Returns a node which defines this module. | ||
146 | /// That is, a file or a `mod foo {}` with items. | ||
147 | fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> { | ||
148 | match self { | ||
149 | ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => { | ||
150 | let file_id = *definition; | ||
151 | let sf = db.parse(file_id).tree(); | ||
152 | InFile::new(file_id.into(), ModuleSource::SourceFile(sf)) | ||
153 | } | ||
154 | ModuleOrigin::Inline { definition } => InFile::new( | ||
155 | definition.file_id, | ||
156 | ModuleSource::Module(definition.to_node(db.upcast())), | ||
157 | ), | ||
158 | } | ||
159 | } | ||
160 | } | ||
161 | |||
162 | #[derive(Default, Debug, PartialEq, Eq)] | ||
163 | pub struct ModuleData { | ||
164 | pub parent: Option<LocalModuleId>, | ||
165 | pub children: FxHashMap<Name, LocalModuleId>, | ||
166 | pub scope: ItemScope, | ||
167 | |||
168 | /// Where does this module come from? | ||
169 | pub origin: ModuleOrigin, | ||
170 | } | ||
171 | |||
172 | impl CrateDefMap { | ||
173 | pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { | ||
174 | let _p = profile::span("crate_def_map_query").detail(|| { | ||
175 | db.crate_graph()[krate] | ||
176 | .display_name | ||
177 | .as_ref() | ||
178 | .map(ToString::to_string) | ||
179 | .unwrap_or_default() | ||
180 | }); | ||
181 | let def_map = { | ||
182 | let edition = db.crate_graph()[krate].edition; | ||
183 | let mut modules: Arena<ModuleData> = Arena::default(); | ||
184 | let root = modules.alloc(ModuleData::default()); | ||
185 | CrateDefMap { | ||
186 | krate, | ||
187 | edition, | ||
188 | extern_prelude: FxHashMap::default(), | ||
189 | prelude: None, | ||
190 | root, | ||
191 | modules, | ||
192 | diagnostics: Vec::new(), | ||
193 | } | ||
194 | }; | ||
195 | let def_map = collector::collect_defs(db, def_map); | ||
196 | Arc::new(def_map) | ||
197 | } | ||
198 | |||
199 | pub fn add_diagnostics( | ||
200 | &self, | ||
201 | db: &dyn DefDatabase, | ||
202 | module: LocalModuleId, | ||
203 | sink: &mut DiagnosticSink, | ||
204 | ) { | ||
205 | self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink)) | ||
206 | } | ||
207 | |||
208 | pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = LocalModuleId> + '_ { | ||
209 | self.modules | ||
210 | .iter() | ||
211 | .filter(move |(_id, data)| data.origin.file_id() == Some(file_id)) | ||
212 | .map(|(id, _data)| id) | ||
213 | } | ||
214 | |||
215 | pub(crate) fn resolve_path( | ||
216 | &self, | ||
217 | db: &dyn DefDatabase, | ||
218 | original_module: LocalModuleId, | ||
219 | path: &ModPath, | ||
220 | shadow: BuiltinShadowMode, | ||
221 | ) -> (PerNs, Option<usize>) { | ||
222 | let res = | ||
223 | self.resolve_path_fp_with_macro(db, ResolveMode::Other, original_module, path, shadow); | ||
224 | (res.resolved_def, res.segment_index) | ||
225 | } | ||
226 | |||
227 | // FIXME: this can use some more human-readable format (ideally, an IR | ||
228 | // even), as this should be a great debugging aid. | ||
229 | pub fn dump(&self) -> String { | ||
230 | let mut buf = String::new(); | ||
231 | go(&mut buf, self, "crate", self.root); | ||
232 | return buf; | ||
233 | |||
234 | fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: LocalModuleId) { | ||
235 | format_to!(buf, "{}\n", path); | ||
236 | |||
237 | let mut entries: Vec<_> = map.modules[module].scope.resolutions().collect(); | ||
238 | entries.sort_by_key(|(name, _)| name.clone()); | ||
239 | |||
240 | for (name, def) in entries { | ||
241 | format_to!(buf, "{}:", name.map_or("_".to_string(), |name| name.to_string())); | ||
242 | |||
243 | if def.types.is_some() { | ||
244 | buf.push_str(" t"); | ||
245 | } | ||
246 | if def.values.is_some() { | ||
247 | buf.push_str(" v"); | ||
248 | } | ||
249 | if def.macros.is_some() { | ||
250 | buf.push_str(" m"); | ||
251 | } | ||
252 | if def.is_none() { | ||
253 | buf.push_str(" _"); | ||
254 | } | ||
255 | |||
256 | buf.push_str("\n"); | ||
257 | } | ||
258 | |||
259 | for (name, child) in map.modules[module].children.iter() { | ||
260 | let path = format!("{}::{}", path, name); | ||
261 | buf.push('\n'); | ||
262 | go(buf, map, &path, *child); | ||
263 | } | ||
264 | } | ||
265 | } | ||
266 | } | ||
267 | |||
268 | impl ModuleData { | ||
269 | /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items. | ||
270 | pub fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> { | ||
271 | self.origin.definition_source(db) | ||
272 | } | ||
273 | |||
274 | /// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`. | ||
275 | /// `None` for the crate root or block. | ||
276 | pub fn declaration_source(&self, db: &dyn DefDatabase) -> Option<InFile<ast::Module>> { | ||
277 | let decl = self.origin.declaration()?; | ||
278 | let value = decl.to_node(db.upcast()); | ||
279 | Some(InFile { file_id: decl.file_id, value }) | ||
280 | } | ||
281 | } | ||
282 | |||
283 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
284 | pub enum ModuleSource { | ||
285 | SourceFile(ast::SourceFile), | ||
286 | Module(ast::Module), | ||
287 | } | ||
288 | |||
289 | mod diagnostics { | ||
290 | use hir_expand::diagnostics::DiagnosticSink; | ||
291 | use syntax::{ast, AstPtr}; | ||
292 | |||
293 | use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId}; | ||
294 | |||
295 | #[derive(Debug, PartialEq, Eq)] | ||
296 | pub(super) enum DefDiagnostic { | ||
297 | UnresolvedModule { | ||
298 | module: LocalModuleId, | ||
299 | declaration: AstId<ast::Module>, | ||
300 | candidate: String, | ||
301 | }, | ||
302 | } | ||
303 | |||
304 | impl DefDiagnostic { | ||
305 | pub(super) fn add_to( | ||
306 | &self, | ||
307 | db: &dyn DefDatabase, | ||
308 | target_module: LocalModuleId, | ||
309 | sink: &mut DiagnosticSink, | ||
310 | ) { | ||
311 | match self { | ||
312 | DefDiagnostic::UnresolvedModule { module, declaration, candidate } => { | ||
313 | if *module != target_module { | ||
314 | return; | ||
315 | } | ||
316 | let decl = declaration.to_node(db.upcast()); | ||
317 | sink.push(UnresolvedModule { | ||
318 | file: declaration.file_id, | ||
319 | decl: AstPtr::new(&decl), | ||
320 | candidate: candidate.clone(), | ||
321 | }) | ||
322 | } | ||
323 | } | ||
324 | } | ||
325 | } | ||
326 | } | ||