aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_def/src/nameres/path_resolution.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_def/src/nameres/path_resolution.rs')
-rw-r--r--crates/ra_hir_def/src/nameres/path_resolution.rs261
1 files changed, 261 insertions, 0 deletions
diff --git a/crates/ra_hir_def/src/nameres/path_resolution.rs b/crates/ra_hir_def/src/nameres/path_resolution.rs
new file mode 100644
index 000000000..95692f826
--- /dev/null
+++ b/crates/ra_hir_def/src/nameres/path_resolution.rs
@@ -0,0 +1,261 @@
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 hir_expand::name::Name;
14use ra_db::Edition;
15use test_utils::tested_by;
16
17use crate::{
18 db::DefDatabase2,
19 nameres::{per_ns::PerNs, CrateDefMap},
20 path::{Path, PathKind},
21 AdtId, CrateModuleId, EnumVariantId, ModuleDefId, ModuleId,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub(super) enum ResolveMode {
26 Import,
27 Other,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub(super) enum ReachedFixedPoint {
32 Yes,
33 No,
34}
35
36#[derive(Debug, Clone)]
37pub(super) struct ResolvePathResult {
38 pub(super) resolved_def: PerNs,
39 pub(super) segment_index: Option<usize>,
40 pub(super) reached_fixedpoint: ReachedFixedPoint,
41}
42
43impl ResolvePathResult {
44 fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult {
45 ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None)
46 }
47
48 fn with(
49 resolved_def: PerNs,
50 reached_fixedpoint: ReachedFixedPoint,
51 segment_index: Option<usize>,
52 ) -> ResolvePathResult {
53 ResolvePathResult { resolved_def, reached_fixedpoint, segment_index }
54 }
55}
56
57impl CrateDefMap {
58 pub(super) fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs {
59 self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it))
60 }
61
62 // Returns Yes if we are sure that additions to `ItemMap` wouldn't change
63 // the result.
64 pub(super) fn resolve_path_fp_with_macro(
65 &self,
66 db: &impl DefDatabase2,
67 mode: ResolveMode,
68 original_module: CrateModuleId,
69 path: &Path,
70 ) -> ResolvePathResult {
71 let mut segments = path.segments.iter().enumerate();
72 let mut curr_per_ns: PerNs = match path.kind {
73 PathKind::DollarCrate(krate) => {
74 if krate == self.krate {
75 tested_by!(macro_dollar_crate_self);
76 PerNs::types(ModuleId { krate: self.krate, module_id: self.root }.into())
77 } else {
78 let def_map = db.crate_def_map(krate);
79 let module = ModuleId { krate, module_id: def_map.root };
80 tested_by!(macro_dollar_crate_other);
81 PerNs::types(module.into())
82 }
83 }
84 PathKind::Crate => {
85 PerNs::types(ModuleId { krate: self.krate, module_id: self.root }.into())
86 }
87 PathKind::Self_ => {
88 PerNs::types(ModuleId { krate: self.krate, module_id: original_module }.into())
89 }
90 // plain import or absolute path in 2015: crate-relative with
91 // fallback to extern prelude (with the simplification in
92 // rust-lang/rust#57745)
93 // FIXME there must be a nicer way to write this condition
94 PathKind::Plain | PathKind::Abs
95 if self.edition == Edition::Edition2015
96 && (path.kind == PathKind::Abs || mode == ResolveMode::Import) =>
97 {
98 let segment = match segments.next() {
99 Some((_, segment)) => segment,
100 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
101 };
102 log::debug!("resolving {:?} in crate root (+ extern prelude)", segment);
103 self.resolve_name_in_crate_root_or_extern_prelude(&segment.name)
104 }
105 PathKind::Plain => {
106 let segment = match segments.next() {
107 Some((_, segment)) => segment,
108 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
109 };
110 log::debug!("resolving {:?} in module", segment);
111 self.resolve_name_in_module(db, original_module, &segment.name)
112 }
113 PathKind::Super => {
114 if let Some(p) = self.modules[original_module].parent {
115 PerNs::types(ModuleId { krate: self.krate, module_id: p }.into())
116 } else {
117 log::debug!("super path in root module");
118 return ResolvePathResult::empty(ReachedFixedPoint::Yes);
119 }
120 }
121 PathKind::Abs => {
122 // 2018-style absolute path -- only extern prelude
123 let segment = match segments.next() {
124 Some((_, segment)) => segment,
125 None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
126 };
127 if let Some(def) = self.extern_prelude.get(&segment.name) {
128 log::debug!("absolute path {:?} resolved to crate {:?}", path, def);
129 PerNs::types(*def)
130 } else {
131 return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude
132 }
133 }
134 PathKind::Type(_) => {
135 // This is handled in `infer::infer_path_expr`
136 // The result returned here does not matter
137 return ResolvePathResult::empty(ReachedFixedPoint::Yes);
138 }
139 };
140
141 for (i, segment) in segments {
142 let curr = match curr_per_ns.take_types() {
143 Some(r) => r,
144 None => {
145 // we still have path segments left, but the path so far
146 // didn't resolve in the types namespace => no resolution
147 // (don't break here because `curr_per_ns` might contain
148 // something in the value namespace, and it would be wrong
149 // to return that)
150 return ResolvePathResult::empty(ReachedFixedPoint::No);
151 }
152 };
153 // resolve segment in curr
154
155 curr_per_ns = match curr {
156 ModuleDefId::ModuleId(module) => {
157 if module.krate != self.krate {
158 let path =
159 Path { segments: path.segments[i..].to_vec(), kind: PathKind::Self_ };
160 log::debug!("resolving {:?} in other crate", path);
161 let defp_map = db.crate_def_map(module.krate);
162 let (def, s) = defp_map.resolve_path(db, module.module_id, &path);
163 return ResolvePathResult::with(
164 def,
165 ReachedFixedPoint::Yes,
166 s.map(|s| s + i),
167 );
168 }
169
170 // Since it is a qualified path here, it should not contains legacy macros
171 match self[module.module_id].scope.get(&segment.name) {
172 Some(res) => res.def,
173 _ => {
174 log::debug!("path segment {:?} not found", segment.name);
175 return ResolvePathResult::empty(ReachedFixedPoint::No);
176 }
177 }
178 }
179 ModuleDefId::AdtId(AdtId::EnumId(e)) => {
180 // enum variant
181 tested_by!(can_import_enum_variant);
182 let enum_data = db.enum_data(e);
183 match enum_data.variant(&segment.name) {
184 Some(local_id) => {
185 let variant = EnumVariantId { parent: e, local_id };
186 PerNs::both(variant.into(), variant.into())
187 }
188 None => {
189 return ResolvePathResult::with(
190 PerNs::types(e.into()),
191 ReachedFixedPoint::Yes,
192 Some(i),
193 );
194 }
195 }
196 }
197 s => {
198 // could be an inherent method call in UFCS form
199 // (`Struct::method`), or some other kind of associated item
200 log::debug!(
201 "path segment {:?} resolved to non-module {:?}, but is not last",
202 segment.name,
203 curr,
204 );
205
206 return ResolvePathResult::with(
207 PerNs::types(s),
208 ReachedFixedPoint::Yes,
209 Some(i),
210 );
211 }
212 };
213 }
214 ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None)
215 }
216
217 fn resolve_name_in_module(
218 &self,
219 db: &impl DefDatabase2,
220 module: CrateModuleId,
221 name: &Name,
222 ) -> PerNs {
223 // Resolve in:
224 // - legacy scope of macro
225 // - current module / scope
226 // - extern prelude
227 // - std prelude
228 let from_legacy_macro =
229 self[module].scope.get_legacy_macro(name).map_or_else(PerNs::none, PerNs::macros);
230 let from_scope = self[module].scope.get(name).map_or_else(PerNs::none, |res| res.def);
231 let from_extern_prelude =
232 self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it));
233 let from_prelude = self.resolve_in_prelude(db, name);
234
235 from_legacy_macro.or(from_scope).or(from_extern_prelude).or(from_prelude)
236 }
237
238 fn resolve_name_in_crate_root_or_extern_prelude(&self, name: &Name) -> PerNs {
239 let from_crate_root =
240 self[self.root].scope.get(name).map_or_else(PerNs::none, |res| res.def);
241 let from_extern_prelude = self.resolve_name_in_extern_prelude(name);
242
243 from_crate_root.or(from_extern_prelude)
244 }
245
246 fn resolve_in_prelude(&self, db: &impl DefDatabase2, name: &Name) -> PerNs {
247 if let Some(prelude) = self.prelude {
248 let keep;
249 let def_map = if prelude.krate == self.krate {
250 self
251 } else {
252 // Extend lifetime
253 keep = db.crate_def_map(prelude.krate);
254 &keep
255 };
256 def_map[prelude.module_id].scope.get(name).map_or_else(PerNs::none, |res| res.def)
257 } else {
258 PerNs::none()
259 }
260 }
261}