aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_def/src/nameres.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_def/src/nameres.rs')
-rw-r--r--crates/ra_hir_def/src/nameres.rs488
1 files changed, 488 insertions, 0 deletions
diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs
index 11ba8a777..db59344aa 100644
--- a/crates/ra_hir_def/src/nameres.rs
+++ b/crates/ra_hir_def/src/nameres.rs
@@ -2,4 +2,492 @@
2 2
3// FIXME: review privacy of submodules 3// FIXME: review privacy of submodules
4pub mod raw; 4pub mod raw;
5pub mod per_ns;
6pub mod collector;
5pub mod mod_resolution; 7pub mod mod_resolution;
8
9use std::sync::Arc;
10
11use hir_expand::{diagnostics::DiagnosticSink, name::Name, MacroDefId};
12use once_cell::sync::Lazy;
13use ra_arena::Arena;
14use ra_db::{CrateId, Edition, FileId};
15use ra_prof::profile;
16use ra_syntax::ast;
17use rustc_hash::{FxHashMap, FxHashSet};
18// use test_utils::tested_by;
19
20use crate::{
21 builtin_type::BuiltinType,
22 db::DefDatabase2,
23 nameres::{diagnostics::DefDiagnostic, per_ns::PerNs, raw::ImportId},
24 path::{Path, PathKind},
25 AdtId, AstId, CrateModuleId, EnumVariantId, ModuleDefId, ModuleId, TraitId,
26};
27
28/// Contains all top-level defs from a macro-expanded crate
29#[derive(Debug, PartialEq, Eq)]
30pub struct CrateDefMap {
31 krate: CrateId,
32 edition: Edition,
33 /// The prelude module for this crate. This either comes from an import
34 /// marked with the `prelude_import` attribute, or (in the normal case) from
35 /// a dependency (`std` or `core`).
36 prelude: Option<ModuleId>,
37 extern_prelude: FxHashMap<Name, ModuleDefId>,
38 root: CrateModuleId,
39 pub modules: Arena<CrateModuleId, ModuleData>,
40
41 /// Some macros are not well-behavior, which leads to infinite loop
42 /// e.g. macro_rules! foo { ($ty:ty) => { foo!($ty); } }
43 /// We mark it down and skip it in collector
44 ///
45 /// FIXME:
46 /// Right now it only handle a poison macro in a single crate,
47 /// such that if other crate try to call that macro,
48 /// the whole process will do again until it became poisoned in that crate.
49 /// We should handle this macro set globally
50 /// However, do we want to put it as a global variable?
51 poison_macros: FxHashSet<MacroDefId>,
52
53 diagnostics: Vec<DefDiagnostic>,
54}
55
56impl std::ops::Index<CrateModuleId> for CrateDefMap {
57 type Output = ModuleData;
58 fn index(&self, id: CrateModuleId) -> &ModuleData {
59 &self.modules[id]
60 }
61}
62
63#[derive(Default, Debug, PartialEq, Eq)]
64pub struct ModuleData {
65 pub parent: Option<CrateModuleId>,
66 pub children: FxHashMap<Name, CrateModuleId>,
67 pub scope: ModuleScope,
68 /// None for root
69 pub declaration: Option<AstId<ast::Module>>,
70 /// None for inline modules.
71 ///
72 /// Note that non-inline modules, by definition, live inside non-macro file.
73 pub definition: Option<FileId>,
74}
75
76#[derive(Debug, Default, PartialEq, Eq, Clone)]
77pub struct ModuleScope {
78 pub items: FxHashMap<Name, Resolution>,
79 /// Macros visable in current module in legacy textual scope
80 ///
81 /// For macros invoked by an unquatified identifier like `bar!()`, `legacy_macros` will be searched in first.
82 /// If it yields no result, then it turns to module scoped `macros`.
83 /// It macros with name quatified with a path like `crate::foo::bar!()`, `legacy_macros` will be skipped,
84 /// and only normal scoped `macros` will be searched in.
85 ///
86 /// Note that this automatically inherit macros defined textually before the definition of module itself.
87 ///
88 /// Module scoped macros will be inserted into `items` instead of here.
89 // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will
90 // be all resolved to the last one defined if shadowing happens.
91 legacy_macros: FxHashMap<Name, MacroDefId>,
92}
93
94static BUILTIN_SCOPE: Lazy<FxHashMap<Name, Resolution>> = Lazy::new(|| {
95 BuiltinType::ALL
96 .iter()
97 .map(|(name, ty)| {
98 (name.clone(), Resolution { def: PerNs::types(ty.clone().into()), import: None })
99 })
100 .collect()
101});
102
103/// Legacy macros can only be accessed through special methods like `get_legacy_macros`.
104/// Other methods will only resolve values, types and module scoped macros only.
105impl ModuleScope {
106 pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a Name, &'a Resolution)> + 'a {
107 //FIXME: shadowing
108 self.items.iter().chain(BUILTIN_SCOPE.iter())
109 }
110
111 /// Iterate over all module scoped macros
112 pub fn macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a {
113 self.items
114 .iter()
115 .filter_map(|(name, res)| res.def.get_macros().map(|macro_| (name, macro_)))
116 }
117
118 /// Iterate over all legacy textual scoped macros visable at the end of the module
119 pub fn legacy_macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a {
120 self.legacy_macros.iter().map(|(name, def)| (name, *def))
121 }
122
123 /// Get a name from current module scope, legacy macros are not included
124 pub fn get(&self, name: &Name) -> Option<&Resolution> {
125 self.items.get(name).or_else(|| BUILTIN_SCOPE.get(name))
126 }
127
128 pub fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a {
129 self.items.values().filter_map(|r| match r.def.take_types() {
130 Some(ModuleDefId::TraitId(t)) => Some(t),
131 _ => None,
132 })
133 }
134
135 fn get_legacy_macro(&self, name: &Name) -> Option<MacroDefId> {
136 self.legacy_macros.get(name).copied()
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Default)]
141pub struct Resolution {
142 /// None for unresolved
143 pub def: PerNs,
144 /// ident by which this is imported into local scope.
145 pub import: Option<ImportId>,
146}
147
148impl Resolution {
149 pub(crate) fn from_macro(macro_: MacroDefId) -> Self {
150 Resolution { def: PerNs::macros(macro_), import: None }
151 }
152}
153
154#[derive(Debug, Clone)]
155struct ResolvePathResult {
156 resolved_def: PerNs,
157 segment_index: Option<usize>,
158 reached_fixedpoint: ReachedFixedPoint,
159}
160
161impl ResolvePathResult {
162 fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult {
163 ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None)
164 }
165
166 fn with(
167 resolved_def: PerNs,
168 reached_fixedpoint: ReachedFixedPoint,
169 segment_index: Option<usize>,
170 ) -> ResolvePathResult {
171 ResolvePathResult { resolved_def, reached_fixedpoint, segment_index }
172 }
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176enum ResolveMode {
177 Import,
178 Other,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182enum ReachedFixedPoint {
183 Yes,
184 No,
185}
186
187impl CrateDefMap {
188 pub(crate) fn crate_def_map_query(
189 // Note that this doesn't have `+ AstDatabase`!
190 // This gurantess that `CrateDefMap` is stable across reparses.
191 db: &impl DefDatabase2,
192 krate: CrateId,
193 ) -> Arc<CrateDefMap> {
194 let _p = profile("crate_def_map_query");
195 let def_map = {
196 let crate_graph = db.crate_graph();
197 let edition = crate_graph.edition(krate);
198 let mut modules: Arena<CrateModuleId, ModuleData> = Arena::default();
199 let root = modules.alloc(ModuleData::default());
200 CrateDefMap {
201 krate,
202 edition,
203 extern_prelude: FxHashMap::default(),
204 prelude: None,
205 root,
206 modules,
207 poison_macros: FxHashSet::default(),
208 diagnostics: Vec::new(),
209 }
210 };
211 let def_map = collector::collect_defs(db, def_map);
212 Arc::new(def_map)
213 }
214
215 pub fn krate(&self) -> CrateId {
216 self.krate
217 }
218
219 pub fn root(&self) -> CrateModuleId {
220 self.root
221 }
222
223 pub fn prelude(&self) -> Option<ModuleId> {
224 self.prelude
225 }
226
227 pub fn extern_prelude(&self) -> &FxHashMap<Name, ModuleDefId> {
228 &self.extern_prelude
229 }
230
231 pub fn add_diagnostics(
232 &self,
233 db: &impl DefDatabase2,
234 module: CrateModuleId,
235 sink: &mut DiagnosticSink,
236 ) {
237 self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink))
238 }
239
240 pub fn resolve_path(
241 &self,
242 db: &impl DefDatabase2,
243 original_module: CrateModuleId,
244 path: &Path,
245 ) -> (PerNs, Option<usize>) {
246 let res = self.resolve_path_fp_with_macro(db, ResolveMode::Other, original_module, path);
247 (res.resolved_def, res.segment_index)
248 }
249
250 // Returns Yes if we are sure that additions to `ItemMap` wouldn't change
251 // the result.
252 fn resolve_path_fp_with_macro(
253 &self,
254 db: &impl DefDatabase2,
255 mode: ResolveMode,
256 original_module: CrateModuleId,
257 path: &Path,
258 ) -> ResolvePathResult {
259 let mut segments = path.segments.iter().enumerate();
260 let mut curr_per_ns: PerNs = match path.kind {
261 PathKind::DollarCrate(krate) => {
262 if krate == self.krate {
263 // tested_by!(macro_dollar_crate_self);
264 PerNs::types(ModuleId { krate: self.krate, module_id: self.root }.into())
265 } else {
266 let def_map = db.crate_def_map(krate);
267 let module = ModuleId { krate, module_id: def_map.root };
268 // tested_by!(macro_dollar_crate_other);
269 PerNs::types(module.into())
270 }
271 }
272 PathKind::Crate => {
273 PerNs::types(ModuleId { krate: self.krate, module_id: self.root }.into())
274 }
275 PathKind::Self_ => {
276 PerNs::types(ModuleId { krate: self.krate, module_id: original_module }.into())
277 }
278 // plain import or absolute path in 2015: crate-relative with
279 // fallback to extern prelude (with the simplification in
280 // rust-lang/rust#57745)
281 // FIXME there must be a nicer way to write this condition
282 PathKind::Plain | PathKind::Abs
283 if self.edition == Edition::Edition2015
284 && (path.kind == PathKind::Abs || mode == ResolveMode::Import) =>
285 {
286 let segment = match segments.next() {
287 Some((_, segment)) => segment,
288 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
289 };
290 log::debug!("resolving {:?} in crate root (+ extern prelude)", segment);
291 self.resolve_name_in_crate_root_or_extern_prelude(&segment.name)
292 }
293 PathKind::Plain => {
294 let segment = match segments.next() {
295 Some((_, segment)) => segment,
296 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
297 };
298 log::debug!("resolving {:?} in module", segment);
299 self.resolve_name_in_module(db, original_module, &segment.name)
300 }
301 PathKind::Super => {
302 if let Some(p) = self.modules[original_module].parent {
303 PerNs::types(ModuleId { krate: self.krate, module_id: p }.into())
304 } else {
305 log::debug!("super path in root module");
306 return ResolvePathResult::empty(ReachedFixedPoint::Yes);
307 }
308 }
309 PathKind::Abs => {
310 // 2018-style absolute path -- only extern prelude
311 let segment = match segments.next() {
312 Some((_, segment)) => segment,
313 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
314 };
315 if let Some(def) = self.extern_prelude.get(&segment.name) {
316 log::debug!("absolute path {:?} resolved to crate {:?}", path, def);
317 PerNs::types(*def)
318 } else {
319 return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude
320 }
321 }
322 PathKind::Type(_) => {
323 // This is handled in `infer::infer_path_expr`
324 // The result returned here does not matter
325 return ResolvePathResult::empty(ReachedFixedPoint::Yes);
326 }
327 };
328
329 for (i, segment) in segments {
330 let curr = match curr_per_ns.take_types() {
331 Some(r) => r,
332 None => {
333 // we still have path segments left, but the path so far
334 // didn't resolve in the types namespace => no resolution
335 // (don't break here because `curr_per_ns` might contain
336 // something in the value namespace, and it would be wrong
337 // to return that)
338 return ResolvePathResult::empty(ReachedFixedPoint::No);
339 }
340 };
341 // resolve segment in curr
342
343 curr_per_ns = match curr {
344 ModuleDefId::ModuleId(module) => {
345 if module.krate != self.krate {
346 let path =
347 Path { segments: path.segments[i..].to_vec(), kind: PathKind::Self_ };
348 log::debug!("resolving {:?} in other crate", path);
349 let defp_map = db.crate_def_map(module.krate);
350 let (def, s) = defp_map.resolve_path(db, module.module_id, &path);
351 return ResolvePathResult::with(
352 def,
353 ReachedFixedPoint::Yes,
354 s.map(|s| s + i),
355 );
356 }
357
358 // Since it is a qualified path here, it should not contains legacy macros
359 match self[module.module_id].scope.get(&segment.name) {
360 Some(res) => res.def,
361 _ => {
362 log::debug!("path segment {:?} not found", segment.name);
363 return ResolvePathResult::empty(ReachedFixedPoint::No);
364 }
365 }
366 }
367 ModuleDefId::AdtId(AdtId::EnumId(e)) => {
368 // enum variant
369 // tested_by!(can_import_enum_variant);
370 let enum_data = db.enum_data(e);
371 match enum_data.variant(&segment.name) {
372 Some(local_id) => {
373 let variant = EnumVariantId { parent: e, local_id };
374 PerNs::both(variant.into(), variant.into())
375 }
376 None => {
377 return ResolvePathResult::with(
378 PerNs::types(e.into()),
379 ReachedFixedPoint::Yes,
380 Some(i),
381 );
382 }
383 }
384 }
385 s => {
386 // could be an inherent method call in UFCS form
387 // (`Struct::method`), or some other kind of associated item
388 log::debug!(
389 "path segment {:?} resolved to non-module {:?}, but is not last",
390 segment.name,
391 curr,
392 );
393
394 return ResolvePathResult::with(
395 PerNs::types(s),
396 ReachedFixedPoint::Yes,
397 Some(i),
398 );
399 }
400 };
401 }
402 ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None)
403 }
404
405 fn resolve_name_in_crate_root_or_extern_prelude(&self, name: &Name) -> PerNs {
406 let from_crate_root =
407 self[self.root].scope.get(name).map_or_else(PerNs::none, |res| res.def);
408 let from_extern_prelude = self.resolve_name_in_extern_prelude(name);
409
410 from_crate_root.or(from_extern_prelude)
411 }
412
413 pub(crate) fn resolve_name_in_module(
414 &self,
415 db: &impl DefDatabase2,
416 module: CrateModuleId,
417 name: &Name,
418 ) -> PerNs {
419 // Resolve in:
420 // - legacy scope of macro
421 // - current module / scope
422 // - extern prelude
423 // - std prelude
424 let from_legacy_macro =
425 self[module].scope.get_legacy_macro(name).map_or_else(PerNs::none, PerNs::macros);
426 let from_scope = self[module].scope.get(name).map_or_else(PerNs::none, |res| res.def);
427 let from_extern_prelude =
428 self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it));
429 let from_prelude = self.resolve_in_prelude(db, name);
430
431 from_legacy_macro.or(from_scope).or(from_extern_prelude).or(from_prelude)
432 }
433
434 fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs {
435 self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it))
436 }
437
438 fn resolve_in_prelude(&self, db: &impl DefDatabase2, name: &Name) -> PerNs {
439 if let Some(prelude) = self.prelude {
440 let keep;
441 let def_map = if prelude.krate == self.krate {
442 self
443 } else {
444 // Extend lifetime
445 keep = db.crate_def_map(prelude.krate);
446 &keep
447 };
448 def_map[prelude.module_id].scope.get(name).map_or_else(PerNs::none, |res| res.def)
449 } else {
450 PerNs::none()
451 }
452 }
453}
454
455mod diagnostics {
456 use hir_expand::diagnostics::DiagnosticSink;
457 use ra_syntax::{ast, AstPtr};
458 use relative_path::RelativePathBuf;
459
460 use crate::{db::DefDatabase2, diagnostics::UnresolvedModule, nameres::CrateModuleId, AstId};
461
462 #[derive(Debug, PartialEq, Eq)]
463 pub(super) enum DefDiagnostic {
464 UnresolvedModule {
465 module: CrateModuleId,
466 declaration: AstId<ast::Module>,
467 candidate: RelativePathBuf,
468 },
469 }
470
471 impl DefDiagnostic {
472 pub(super) fn add_to(
473 &self,
474 db: &impl DefDatabase2,
475 target_module: CrateModuleId,
476 sink: &mut DiagnosticSink,
477 ) {
478 match self {
479 DefDiagnostic::UnresolvedModule { module, declaration, candidate } => {
480 if *module != target_module {
481 return;
482 }
483 let decl = declaration.to_node(db);
484 sink.push(UnresolvedModule {
485 file: declaration.file_id(),
486 decl: AstPtr::new(&decl),
487 candidate: candidate.clone(),
488 })
489 }
490 }
491 }
492 }
493}