diff options
Diffstat (limited to 'crates/hir_def/src/nameres/path_resolution.rs')
-rw-r--r-- | crates/hir_def/src/nameres/path_resolution.rs | 330 |
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 | |||
13 | use std::iter::successors; | ||
14 | |||
15 | use base_db::Edition; | ||
16 | use hir_expand::name::Name; | ||
17 | use test_utils::mark; | ||
18 | |||
19 | use 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)] | ||
30 | pub(super) enum ResolveMode { | ||
31 | Import, | ||
32 | Other, | ||
33 | } | ||
34 | |||
35 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
36 | pub(super) enum ReachedFixedPoint { | ||
37 | Yes, | ||
38 | No, | ||
39 | } | ||
40 | |||
41 | #[derive(Debug, Clone)] | ||
42 | pub(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 | |||
49 | impl 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 | |||
64 | impl 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 | } | ||