aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_def/src/nameres/path_resolution.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_def/src/nameres/path_resolution.rs')
-rw-r--r--crates/hir_def/src/nameres/path_resolution.rs330
1 files changed, 330 insertions, 0 deletions
diff --git a/crates/hir_def/src/nameres/path_resolution.rs b/crates/hir_def/src/nameres/path_resolution.rs
new file mode 100644
index 000000000..88e10574e
--- /dev/null
+++ b/crates/hir_def/src/nameres/path_resolution.rs
@@ -0,0 +1,330 @@
1//! This modules implements a function to resolve a path `foo::bar::baz` to a
2//! def, which is used within the name resolution.
3//!
4//! When name resolution is finished, the result of resolving a path is either
5//! `Some(def)` or `None`. However, when we are in process of resolving imports
6//! or macros, there's a third possibility:
7//!
8//! I can't resolve this path right now, but I might be resolve this path
9//! later, when more macros are expanded.
10//!
11//! `ReachedFixedPoint` signals about this.
12
13use std::iter::successors;
14
15use base_db::Edition;
16use hir_expand::name::Name;
17use test_utils::mark;
18
19use crate::{
20 db::DefDatabase,
21 item_scope::BUILTIN_SCOPE,
22 nameres::{BuiltinShadowMode, CrateDefMap},
23 path::{ModPath, PathKind},
24 per_ns::PerNs,
25 visibility::{RawVisibility, Visibility},
26 AdtId, CrateId, EnumVariantId, LocalModuleId, ModuleDefId, ModuleId,
27};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub(super) enum ResolveMode {
31 Import,
32 Other,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub(super) enum ReachedFixedPoint {
37 Yes,
38 No,
39}
40
41#[derive(Debug, Clone)]
42pub(super) struct ResolvePathResult {
43 pub(super) resolved_def: PerNs,
44 pub(super) segment_index: Option<usize>,
45 pub(super) reached_fixedpoint: ReachedFixedPoint,
46 pub(super) krate: Option<CrateId>,
47}
48
49impl ResolvePathResult {
50 fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult {
51 ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None, None)
52 }
53
54 fn with(
55 resolved_def: PerNs,
56 reached_fixedpoint: ReachedFixedPoint,
57 segment_index: Option<usize>,
58 krate: Option<CrateId>,
59 ) -> ResolvePathResult {
60 ResolvePathResult { resolved_def, reached_fixedpoint, segment_index, krate }
61 }
62}
63
64impl CrateDefMap {
65 pub(super) fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs {
66 self.extern_prelude
67 .get(name)
68 .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public))
69 }
70
71 pub(crate) fn resolve_visibility(
72 &self,
73 db: &dyn DefDatabase,
74 original_module: LocalModuleId,
75 visibility: &RawVisibility,
76 ) -> Option<Visibility> {
77 match visibility {
78 RawVisibility::Module(path) => {
79 let (result, remaining) =
80 self.resolve_path(db, original_module, &path, BuiltinShadowMode::Module);
81 if remaining.is_some() {
82 return None;
83 }
84 let types = result.take_types()?;
85 match types {
86 ModuleDefId::ModuleId(m) => Some(Visibility::Module(m)),
87 _ => {
88 // error: visibility needs to refer to module
89 None
90 }
91 }
92 }
93 RawVisibility::Public => Some(Visibility::Public),
94 }
95 }
96
97 // Returns Yes if we are sure that additions to `ItemMap` wouldn't change
98 // the result.
99 pub(super) fn resolve_path_fp_with_macro(
100 &self,
101 db: &dyn DefDatabase,
102 mode: ResolveMode,
103 original_module: LocalModuleId,
104 path: &ModPath,
105 shadow: BuiltinShadowMode,
106 ) -> ResolvePathResult {
107 let mut segments = path.segments.iter().enumerate();
108 let mut curr_per_ns: PerNs = match path.kind {
109 PathKind::DollarCrate(krate) => {
110 if krate == self.krate {
111 mark::hit!(macro_dollar_crate_self);
112 PerNs::types(
113 ModuleId { krate: self.krate, local_id: self.root }.into(),
114 Visibility::Public,
115 )
116 } else {
117 let def_map = db.crate_def_map(krate);
118 let module = ModuleId { krate, local_id: def_map.root };
119 mark::hit!(macro_dollar_crate_other);
120 PerNs::types(module.into(), Visibility::Public)
121 }
122 }
123 PathKind::Crate => PerNs::types(
124 ModuleId { krate: self.krate, local_id: self.root }.into(),
125 Visibility::Public,
126 ),
127 // plain import or absolute path in 2015: crate-relative with
128 // fallback to extern prelude (with the simplification in
129 // rust-lang/rust#57745)
130 // FIXME there must be a nicer way to write this condition
131 PathKind::Plain | PathKind::Abs
132 if self.edition == Edition::Edition2015
133 && (path.kind == PathKind::Abs || mode == ResolveMode::Import) =>
134 {
135 let (_, segment) = match segments.next() {
136 Some((idx, segment)) => (idx, segment),
137 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
138 };
139 log::debug!("resolving {:?} in crate root (+ extern prelude)", segment);
140 self.resolve_name_in_crate_root_or_extern_prelude(&segment)
141 }
142 PathKind::Plain => {
143 let (_, segment) = match segments.next() {
144 Some((idx, segment)) => (idx, segment),
145 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
146 };
147 // The first segment may be a builtin type. If the path has more
148 // than one segment, we first try resolving it as a module
149 // anyway.
150 // FIXME: If the next segment doesn't resolve in the module and
151 // BuiltinShadowMode wasn't Module, then we need to try
152 // resolving it as a builtin.
153 let prefer_module =
154 if path.segments.len() == 1 { shadow } else { BuiltinShadowMode::Module };
155
156 log::debug!("resolving {:?} in module", segment);
157 self.resolve_name_in_module(db, original_module, &segment, prefer_module)
158 }
159 PathKind::Super(lvl) => {
160 let m = successors(Some(original_module), |m| self.modules[*m].parent)
161 .nth(lvl as usize);
162 if let Some(local_id) = m {
163 PerNs::types(
164 ModuleId { krate: self.krate, local_id }.into(),
165 Visibility::Public,
166 )
167 } else {
168 log::debug!("super path in root module");
169 return ResolvePathResult::empty(ReachedFixedPoint::Yes);
170 }
171 }
172 PathKind::Abs => {
173 // 2018-style absolute path -- only extern prelude
174 let segment = match segments.next() {
175 Some((_, segment)) => segment,
176 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
177 };
178 if let Some(def) = self.extern_prelude.get(&segment) {
179 log::debug!("absolute path {:?} resolved to crate {:?}", path, def);
180 PerNs::types(*def, Visibility::Public)
181 } else {
182 return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude
183 }
184 }
185 };
186
187 for (i, segment) in segments {
188 let (curr, vis) = match curr_per_ns.take_types_vis() {
189 Some(r) => r,
190 None => {
191 // we still have path segments left, but the path so far
192 // didn't resolve in the types namespace => no resolution
193 // (don't break here because `curr_per_ns` might contain
194 // something in the value namespace, and it would be wrong
195 // to return that)
196 return ResolvePathResult::empty(ReachedFixedPoint::No);
197 }
198 };
199 // resolve segment in curr
200
201 curr_per_ns = match curr {
202 ModuleDefId::ModuleId(module) => {
203 if module.krate != self.krate {
204 let path = ModPath {
205 segments: path.segments[i..].to_vec(),
206 kind: PathKind::Super(0),
207 };
208 log::debug!("resolving {:?} in other crate", path);
209 let defp_map = db.crate_def_map(module.krate);
210 let (def, s) = defp_map.resolve_path(db, module.local_id, &path, shadow);
211 return ResolvePathResult::with(
212 def,
213 ReachedFixedPoint::Yes,
214 s.map(|s| s + i),
215 Some(module.krate),
216 );
217 }
218
219 // Since it is a qualified path here, it should not contains legacy macros
220 self[module.local_id].scope.get(&segment)
221 }
222 ModuleDefId::AdtId(AdtId::EnumId(e)) => {
223 // enum variant
224 mark::hit!(can_import_enum_variant);
225 let enum_data = db.enum_data(e);
226 match enum_data.variant(&segment) {
227 Some(local_id) => {
228 let variant = EnumVariantId { parent: e, local_id };
229 match &*enum_data.variants[local_id].variant_data {
230 crate::adt::VariantData::Record(_) => {
231 PerNs::types(variant.into(), Visibility::Public)
232 }
233 crate::adt::VariantData::Tuple(_)
234 | crate::adt::VariantData::Unit => {
235 PerNs::both(variant.into(), variant.into(), Visibility::Public)
236 }
237 }
238 }
239 None => {
240 return ResolvePathResult::with(
241 PerNs::types(e.into(), vis),
242 ReachedFixedPoint::Yes,
243 Some(i),
244 Some(self.krate),
245 );
246 }
247 }
248 }
249 s => {
250 // could be an inherent method call in UFCS form
251 // (`Struct::method`), or some other kind of associated item
252 log::debug!(
253 "path segment {:?} resolved to non-module {:?}, but is not last",
254 segment,
255 curr,
256 );
257
258 return ResolvePathResult::with(
259 PerNs::types(s, vis),
260 ReachedFixedPoint::Yes,
261 Some(i),
262 Some(self.krate),
263 );
264 }
265 };
266 }
267
268 ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None, Some(self.krate))
269 }
270
271 fn resolve_name_in_module(
272 &self,
273 db: &dyn DefDatabase,
274 module: LocalModuleId,
275 name: &Name,
276 shadow: BuiltinShadowMode,
277 ) -> PerNs {
278 // Resolve in:
279 // - legacy scope of macro
280 // - current module / scope
281 // - extern prelude
282 // - std prelude
283 let from_legacy_macro = self[module]
284 .scope
285 .get_legacy_macro(name)
286 .map_or_else(PerNs::none, |m| PerNs::macros(m, Visibility::Public));
287 let from_scope = self[module].scope.get(name);
288 let from_builtin = BUILTIN_SCOPE.get(name).copied().unwrap_or_else(PerNs::none);
289 let from_scope_or_builtin = match shadow {
290 BuiltinShadowMode::Module => from_scope.or(from_builtin),
291 BuiltinShadowMode::Other => {
292 if let Some(ModuleDefId::ModuleId(_)) = from_scope.take_types() {
293 from_builtin.or(from_scope)
294 } else {
295 from_scope.or(from_builtin)
296 }
297 }
298 };
299 let from_extern_prelude = self
300 .extern_prelude
301 .get(name)
302 .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public));
303 let from_prelude = self.resolve_in_prelude(db, name);
304
305 from_legacy_macro.or(from_scope_or_builtin).or(from_extern_prelude).or(from_prelude)
306 }
307
308 fn resolve_name_in_crate_root_or_extern_prelude(&self, name: &Name) -> PerNs {
309 let from_crate_root = self[self.root].scope.get(name);
310 let from_extern_prelude = self.resolve_name_in_extern_prelude(name);
311
312 from_crate_root.or(from_extern_prelude)
313 }
314
315 fn resolve_in_prelude(&self, db: &dyn DefDatabase, name: &Name) -> PerNs {
316 if let Some(prelude) = self.prelude {
317 let keep;
318 let def_map = if prelude.krate == self.krate {
319 self
320 } else {
321 // Extend lifetime
322 keep = db.crate_def_map(prelude.krate);
323 &keep
324 };
325 def_map[prelude.local_id].scope.get(name)
326 } else {
327 PerNs::none()
328 }
329 }
330}