diff options
Diffstat (limited to 'crates/ra_hir/src/nameres/crate_def_map.rs')
-rw-r--r-- | crates/ra_hir/src/nameres/crate_def_map.rs | 310 |
1 files changed, 187 insertions, 123 deletions
diff --git a/crates/ra_hir/src/nameres/crate_def_map.rs b/crates/ra_hir/src/nameres/crate_def_map.rs index ea9b4fb50..483878c78 100644 --- a/crates/ra_hir/src/nameres/crate_def_map.rs +++ b/crates/ra_hir/src/nameres/crate_def_map.rs | |||
@@ -40,16 +40,21 @@ | |||
40 | /// syntax. | 40 | /// syntax. |
41 | /// | 41 | /// |
42 | /// TBD; | 42 | /// TBD; |
43 | |||
43 | mod raw; | 44 | mod raw; |
44 | mod collector; | 45 | mod collector; |
46 | #[cfg(test)] | ||
47 | mod tests; | ||
45 | 48 | ||
46 | use rustc_hash::FxHashMap; | 49 | use rustc_hash::FxHashMap; |
47 | use ra_arena::{Arena}; | 50 | use test_utils::tested_by; |
51 | use ra_arena::Arena; | ||
48 | 52 | ||
49 | use crate::{ | 53 | use crate::{ |
50 | Name, | 54 | Name, Module, Path, PathKind, ModuleDef, Crate, |
55 | PersistentHirDatabase, | ||
51 | module_tree::ModuleId, | 56 | module_tree::ModuleId, |
52 | nameres::ModuleScope, | 57 | nameres::{ModuleScope, ResolveMode, ResolvePathResult, PerNs, Edition, ReachedFixedPoint}, |
53 | }; | 58 | }; |
54 | 59 | ||
55 | #[derive(Default, Debug)] | 60 | #[derive(Default, Debug)] |
@@ -62,143 +67,202 @@ struct ModuleData { | |||
62 | /// Contans all top-level defs from a macro-expanded crate | 67 | /// Contans all top-level defs from a macro-expanded crate |
63 | #[derive(Debug)] | 68 | #[derive(Debug)] |
64 | pub(crate) struct CrateDefMap { | 69 | pub(crate) struct CrateDefMap { |
70 | krate: Crate, | ||
71 | edition: Edition, | ||
72 | /// The prelude module for this crate. This either comes from an import | ||
73 | /// marked with the `prelude_import` attribute, or (in the normal case) from | ||
74 | /// a dependency (`std` or `core`). | ||
75 | prelude: Option<Module>, | ||
76 | extern_prelude: FxHashMap<Name, ModuleDef>, | ||
65 | root: ModuleId, | 77 | root: ModuleId, |
66 | modules: Arena<ModuleId, ModuleData>, | 78 | modules: Arena<ModuleId, ModuleData>, |
79 | public_macros: FxHashMap<Name, mbe::MacroRules>, | ||
67 | } | 80 | } |
68 | 81 | ||
69 | #[cfg(test)] | 82 | impl std::ops::Index<ModuleId> for CrateDefMap { |
70 | mod tests { | 83 | type Output = ModuleScope; |
71 | use std::sync::Arc; | 84 | fn index(&self, id: ModuleId) -> &ModuleScope { |
72 | 85 | &self.modules[id].scope | |
73 | use ra_db::SourceDatabase; | ||
74 | use insta::assert_snapshot_matches; | ||
75 | |||
76 | use crate::{Crate, mock::MockDatabase, nameres::Resolution}; | ||
77 | |||
78 | use super::*; | ||
79 | |||
80 | fn compute_crate_def_map(fixture: &str) -> Arc<CrateDefMap> { | ||
81 | let db = MockDatabase::with_files(fixture); | ||
82 | let crate_id = db.crate_graph().iter().next().unwrap(); | ||
83 | let krate = Crate { crate_id }; | ||
84 | collector::crate_def_map_query(&db, krate) | ||
85 | } | 86 | } |
87 | } | ||
86 | 88 | ||
87 | fn render_crate_def_map(map: &CrateDefMap) -> String { | 89 | impl CrateDefMap { |
88 | let mut buf = String::new(); | 90 | // Returns Yes if we are sure that additions to `ItemMap` wouldn't change |
89 | go(&mut buf, map, "\ncrate", map.root); | 91 | // the result. |
90 | return buf; | 92 | #[allow(unused)] |
91 | 93 | fn resolve_path_fp( | |
92 | fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: ModuleId) { | 94 | &self, |
93 | *buf += path; | 95 | db: &impl PersistentHirDatabase, |
94 | *buf += "\n"; | 96 | mode: ResolveMode, |
95 | for (name, res) in map.modules[module].scope.items.iter() { | 97 | original_module: ModuleId, |
96 | *buf += &format!("{}: {}\n", name, dump_resolution(res)) | 98 | path: &Path, |
99 | ) -> ResolvePathResult { | ||
100 | let mut segments = path.segments.iter().enumerate(); | ||
101 | let mut curr_per_ns: PerNs<ModuleDef> = match path.kind { | ||
102 | PathKind::Crate => { | ||
103 | PerNs::types(Module { krate: self.krate, module_id: self.root }.into()) | ||
97 | } | 104 | } |
98 | for (name, child) in map.modules[module].children.iter() { | 105 | PathKind::Self_ => { |
99 | let path = path.to_string() + &format!("::{}", name); | 106 | PerNs::types(Module { krate: self.krate, module_id: original_module }.into()) |
100 | go(buf, map, &path, *child); | ||
101 | } | 107 | } |
102 | } | 108 | // plain import or absolute path in 2015: crate-relative with |
103 | 109 | // fallback to extern prelude (with the simplification in | |
104 | fn dump_resolution(resolution: &Resolution) -> &'static str { | 110 | // rust-lang/rust#57745) |
105 | match (resolution.def.types.is_some(), resolution.def.values.is_some()) { | 111 | // TODO there must be a nicer way to write this condition |
106 | (true, true) => "t v", | 112 | PathKind::Plain | PathKind::Abs |
107 | (true, false) => "t", | 113 | if self.edition == Edition::Edition2015 |
108 | (false, true) => "v", | 114 | && (path.kind == PathKind::Abs || mode == ResolveMode::Import) => |
109 | (false, false) => "_", | 115 | { |
116 | let segment = match segments.next() { | ||
117 | Some((_, segment)) => segment, | ||
118 | None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), | ||
119 | }; | ||
120 | log::debug!("resolving {:?} in crate root (+ extern prelude)", segment); | ||
121 | self.resolve_name_in_crate_root_or_extern_prelude(&segment.name) | ||
122 | } | ||
123 | PathKind::Plain => { | ||
124 | let segment = match segments.next() { | ||
125 | Some((_, segment)) => segment, | ||
126 | None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), | ||
127 | }; | ||
128 | log::debug!("resolving {:?} in module", segment); | ||
129 | self.resolve_name_in_module(db, original_module, &segment.name) | ||
130 | } | ||
131 | PathKind::Super => { | ||
132 | if let Some(p) = self.modules[original_module].parent { | ||
133 | PerNs::types(Module { krate: self.krate, module_id: p }.into()) | ||
134 | } else { | ||
135 | log::debug!("super path in root module"); | ||
136 | return ResolvePathResult::empty(ReachedFixedPoint::Yes); | ||
137 | } | ||
138 | } | ||
139 | PathKind::Abs => { | ||
140 | // 2018-style absolute path -- only extern prelude | ||
141 | let segment = match segments.next() { | ||
142 | Some((_, segment)) => segment, | ||
143 | None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), | ||
144 | }; | ||
145 | if let Some(def) = self.extern_prelude.get(&segment.name) { | ||
146 | log::debug!("absolute path {:?} resolved to crate {:?}", path, def); | ||
147 | PerNs::types(*def) | ||
148 | } else { | ||
149 | return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude | ||
150 | } | ||
110 | } | 151 | } |
152 | }; | ||
153 | |||
154 | for (i, segment) in segments { | ||
155 | let curr = match curr_per_ns.as_ref().take_types() { | ||
156 | Some(r) => r, | ||
157 | None => { | ||
158 | // we still have path segments left, but the path so far | ||
159 | // didn't resolve in the types namespace => no resolution | ||
160 | // (don't break here because `curr_per_ns` might contain | ||
161 | // something in the value namespace, and it would be wrong | ||
162 | // to return that) | ||
163 | return ResolvePathResult::empty(ReachedFixedPoint::No); | ||
164 | } | ||
165 | }; | ||
166 | // resolve segment in curr | ||
167 | |||
168 | curr_per_ns = match curr { | ||
169 | ModuleDef::Module(module) => { | ||
170 | if module.krate != self.krate { | ||
171 | let path = Path { | ||
172 | segments: path.segments[i..].iter().cloned().collect(), | ||
173 | kind: PathKind::Self_, | ||
174 | }; | ||
175 | log::debug!("resolving {:?} in other crate", path); | ||
176 | let item_map = db.item_map(module.krate); | ||
177 | let (def, s) = item_map.resolve_path(db, *module, &path); | ||
178 | return ResolvePathResult::with( | ||
179 | def, | ||
180 | ReachedFixedPoint::Yes, | ||
181 | s.map(|s| s + i), | ||
182 | ); | ||
183 | } | ||
184 | |||
185 | match self[module.module_id].items.get(&segment.name) { | ||
186 | Some(res) if !res.def.is_none() => res.def, | ||
187 | _ => { | ||
188 | log::debug!("path segment {:?} not found", segment.name); | ||
189 | return ResolvePathResult::empty(ReachedFixedPoint::No); | ||
190 | } | ||
191 | } | ||
192 | } | ||
193 | ModuleDef::Enum(e) => { | ||
194 | // enum variant | ||
195 | tested_by!(item_map_enum_importing); | ||
196 | match e.variant(db, &segment.name) { | ||
197 | Some(variant) => PerNs::both(variant.into(), variant.into()), | ||
198 | None => { | ||
199 | return ResolvePathResult::with( | ||
200 | PerNs::types((*e).into()), | ||
201 | ReachedFixedPoint::Yes, | ||
202 | Some(i), | ||
203 | ); | ||
204 | } | ||
205 | } | ||
206 | } | ||
207 | s => { | ||
208 | // could be an inherent method call in UFCS form | ||
209 | // (`Struct::method`), or some other kind of associated item | ||
210 | log::debug!( | ||
211 | "path segment {:?} resolved to non-module {:?}, but is not last", | ||
212 | segment.name, | ||
213 | curr, | ||
214 | ); | ||
215 | |||
216 | return ResolvePathResult::with( | ||
217 | PerNs::types((*s).into()), | ||
218 | ReachedFixedPoint::Yes, | ||
219 | Some(i), | ||
220 | ); | ||
221 | } | ||
222 | }; | ||
111 | } | 223 | } |
224 | ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None) | ||
112 | } | 225 | } |
113 | 226 | ||
114 | fn def_map(fixtute: &str) -> String { | 227 | fn resolve_name_in_crate_root_or_extern_prelude(&self, name: &Name) -> PerNs<ModuleDef> { |
115 | let dm = compute_crate_def_map(fixtute); | 228 | let from_crate_root = self[self.root].items.get(name).map_or(PerNs::none(), |it| it.def); |
116 | render_crate_def_map(&dm) | 229 | let from_extern_prelude = self.resolve_name_in_extern_prelude(name); |
230 | |||
231 | from_crate_root.or(from_extern_prelude) | ||
117 | } | 232 | } |
118 | 233 | ||
119 | #[test] | 234 | fn resolve_name_in_module( |
120 | fn crate_def_map_smoke_test() { | 235 | &self, |
121 | let map = def_map( | 236 | db: &impl PersistentHirDatabase, |
122 | " | 237 | module: ModuleId, |
123 | //- /lib.rs | 238 | name: &Name, |
124 | mod foo; | 239 | ) -> PerNs<ModuleDef> { |
125 | struct S; | 240 | // Resolve in: |
126 | 241 | // - current module / scope | |
127 | //- /foo/mod.rs | 242 | // - extern prelude |
128 | pub mod bar; | 243 | // - std prelude |
129 | fn f() {} | 244 | let from_scope = self[module].items.get(name).map_or(PerNs::none(), |it| it.def); |
130 | 245 | let from_extern_prelude = | |
131 | //- /foo/bar.rs | 246 | self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it)); |
132 | pub struct Baz; | 247 | let from_prelude = self.resolve_in_prelude(db, name); |
133 | enum E { V } | 248 | |
134 | ", | 249 | from_scope.or(from_extern_prelude).or(from_prelude) |
135 | ); | ||
136 | assert_snapshot_matches!( | ||
137 | map, | ||
138 | @r###" | ||
139 | crate | ||
140 | S: t v | ||
141 | |||
142 | crate::foo | ||
143 | f: v | ||
144 | |||
145 | crate::foo::bar | ||
146 | Baz: t v | ||
147 | E: t | ||
148 | "### | ||
149 | ) | ||
150 | } | 250 | } |
151 | 251 | ||
152 | #[test] | 252 | fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs<ModuleDef> { |
153 | fn macro_rules_are_globally_visible() { | 253 | self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it)) |
154 | let map = def_map( | ||
155 | " | ||
156 | //- /lib.rs | ||
157 | macro_rules! structs { | ||
158 | ($($i:ident),*) => { | ||
159 | $(struct $i { field: u32 } )* | ||
160 | } | ||
161 | } | ||
162 | structs!(Foo); | ||
163 | mod nested; | ||
164 | |||
165 | //- /nested.rs | ||
166 | structs!(Bar, Baz); | ||
167 | ", | ||
168 | ); | ||
169 | assert_snapshot_matches!(map, @r###" | ||
170 | crate | ||
171 | Foo: t v | ||
172 | |||
173 | crate::nested | ||
174 | Bar: t v | ||
175 | Baz: t v | ||
176 | "###); | ||
177 | } | 254 | } |
178 | 255 | ||
179 | #[test] | 256 | fn resolve_in_prelude(&self, db: &impl PersistentHirDatabase, name: &Name) -> PerNs<ModuleDef> { |
180 | fn macro_rules_can_define_modules() { | 257 | if let Some(prelude) = self.prelude { |
181 | let map = def_map( | 258 | let resolution = if prelude.krate == self.krate { |
182 | " | 259 | self[prelude.module_id].items.get(name).cloned() |
183 | //- /lib.rs | 260 | } else { |
184 | macro_rules! m { | 261 | db.item_map(prelude.krate)[prelude.module_id].items.get(name).cloned() |
185 | ($name:ident) => { mod $name; } | 262 | }; |
186 | } | 263 | resolution.map(|r| r.def).unwrap_or_else(PerNs::none) |
187 | m!(n1); | 264 | } else { |
188 | 265 | PerNs::none() | |
189 | //- /n1.rs | 266 | } |
190 | m!(n2) | ||
191 | //- /n1/n2.rs | ||
192 | struct X; | ||
193 | ", | ||
194 | ); | ||
195 | assert_snapshot_matches!(map, @r###" | ||
196 | crate | ||
197 | |||
198 | crate::n1 | ||
199 | |||
200 | crate::n1::n2 | ||
201 | X: t v | ||
202 | "###); | ||
203 | } | 267 | } |
204 | } | 268 | } |