From 182c05a96c25321ac3ff262cea098e0c4d7ed6f8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 13 Mar 2019 16:04:28 +0300 Subject: add name resolution from the old impl unlike the old impl, this also handles macro imports across crates --- crates/ra_hir/src/nameres/crate_def_map.rs | 310 +++++++++++++-------- .../ra_hir/src/nameres/crate_def_map/collector.rs | 283 +++++++++++++++++-- crates/ra_hir/src/nameres/crate_def_map/raw.rs | 30 +- crates/ra_hir/src/nameres/crate_def_map/tests.rs | 265 ++++++++++++++++++ crates/ra_hir/src/nameres/lower.rs | 4 +- crates/ra_mbe/src/lib.rs | 20 +- crates/ra_syntax/src/ast.rs | 9 +- crates/ra_syntax/src/ast/generated.rs | 1 + crates/ra_syntax/src/grammar.ron | 2 +- 9 files changed, 739 insertions(+), 185 deletions(-) create mode 100644 crates/ra_hir/src/nameres/crate_def_map/tests.rs (limited to 'crates') 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 @@ /// syntax. /// /// TBD; + mod raw; mod collector; +#[cfg(test)] +mod tests; use rustc_hash::FxHashMap; -use ra_arena::{Arena}; +use test_utils::tested_by; +use ra_arena::Arena; use crate::{ - Name, + Name, Module, Path, PathKind, ModuleDef, Crate, + PersistentHirDatabase, module_tree::ModuleId, - nameres::ModuleScope, + nameres::{ModuleScope, ResolveMode, ResolvePathResult, PerNs, Edition, ReachedFixedPoint}, }; #[derive(Default, Debug)] @@ -62,143 +67,202 @@ struct ModuleData { /// Contans all top-level defs from a macro-expanded crate #[derive(Debug)] pub(crate) struct CrateDefMap { + krate: Crate, + edition: Edition, + /// The prelude module for this crate. This either comes from an import + /// marked with the `prelude_import` attribute, or (in the normal case) from + /// a dependency (`std` or `core`). + prelude: Option, + extern_prelude: FxHashMap, root: ModuleId, modules: Arena, + public_macros: FxHashMap, } -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use ra_db::SourceDatabase; - use insta::assert_snapshot_matches; - - use crate::{Crate, mock::MockDatabase, nameres::Resolution}; - - use super::*; - - fn compute_crate_def_map(fixture: &str) -> Arc { - let db = MockDatabase::with_files(fixture); - let crate_id = db.crate_graph().iter().next().unwrap(); - let krate = Crate { crate_id }; - collector::crate_def_map_query(&db, krate) +impl std::ops::Index for CrateDefMap { + type Output = ModuleScope; + fn index(&self, id: ModuleId) -> &ModuleScope { + &self.modules[id].scope } +} - fn render_crate_def_map(map: &CrateDefMap) -> String { - let mut buf = String::new(); - go(&mut buf, map, "\ncrate", map.root); - return buf; - - fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: ModuleId) { - *buf += path; - *buf += "\n"; - for (name, res) in map.modules[module].scope.items.iter() { - *buf += &format!("{}: {}\n", name, dump_resolution(res)) +impl CrateDefMap { + // Returns Yes if we are sure that additions to `ItemMap` wouldn't change + // the result. + #[allow(unused)] + fn resolve_path_fp( + &self, + db: &impl PersistentHirDatabase, + mode: ResolveMode, + original_module: ModuleId, + path: &Path, + ) -> ResolvePathResult { + let mut segments = path.segments.iter().enumerate(); + let mut curr_per_ns: PerNs = match path.kind { + PathKind::Crate => { + PerNs::types(Module { krate: self.krate, module_id: self.root }.into()) } - for (name, child) in map.modules[module].children.iter() { - let path = path.to_string() + &format!("::{}", name); - go(buf, map, &path, *child); + PathKind::Self_ => { + PerNs::types(Module { krate: self.krate, module_id: original_module }.into()) } - } - - fn dump_resolution(resolution: &Resolution) -> &'static str { - match (resolution.def.types.is_some(), resolution.def.values.is_some()) { - (true, true) => "t v", - (true, false) => "t", - (false, true) => "v", - (false, false) => "_", + // plain import or absolute path in 2015: crate-relative with + // fallback to extern prelude (with the simplification in + // rust-lang/rust#57745) + // TODO there must be a nicer way to write this condition + PathKind::Plain | PathKind::Abs + if self.edition == Edition::Edition2015 + && (path.kind == PathKind::Abs || mode == ResolveMode::Import) => + { + let segment = match segments.next() { + Some((_, segment)) => segment, + None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), + }; + log::debug!("resolving {:?} in crate root (+ extern prelude)", segment); + self.resolve_name_in_crate_root_or_extern_prelude(&segment.name) + } + PathKind::Plain => { + let segment = match segments.next() { + Some((_, segment)) => segment, + None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), + }; + log::debug!("resolving {:?} in module", segment); + self.resolve_name_in_module(db, original_module, &segment.name) + } + PathKind::Super => { + if let Some(p) = self.modules[original_module].parent { + PerNs::types(Module { krate: self.krate, module_id: p }.into()) + } else { + log::debug!("super path in root module"); + return ResolvePathResult::empty(ReachedFixedPoint::Yes); + } + } + PathKind::Abs => { + // 2018-style absolute path -- only extern prelude + let segment = match segments.next() { + Some((_, segment)) => segment, + None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), + }; + if let Some(def) = self.extern_prelude.get(&segment.name) { + log::debug!("absolute path {:?} resolved to crate {:?}", path, def); + PerNs::types(*def) + } else { + return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude + } } + }; + + for (i, segment) in segments { + let curr = match curr_per_ns.as_ref().take_types() { + Some(r) => r, + None => { + // we still have path segments left, but the path so far + // didn't resolve in the types namespace => no resolution + // (don't break here because `curr_per_ns` might contain + // something in the value namespace, and it would be wrong + // to return that) + return ResolvePathResult::empty(ReachedFixedPoint::No); + } + }; + // resolve segment in curr + + curr_per_ns = match curr { + ModuleDef::Module(module) => { + if module.krate != self.krate { + let path = Path { + segments: path.segments[i..].iter().cloned().collect(), + kind: PathKind::Self_, + }; + log::debug!("resolving {:?} in other crate", path); + let item_map = db.item_map(module.krate); + let (def, s) = item_map.resolve_path(db, *module, &path); + return ResolvePathResult::with( + def, + ReachedFixedPoint::Yes, + s.map(|s| s + i), + ); + } + + match self[module.module_id].items.get(&segment.name) { + Some(res) if !res.def.is_none() => res.def, + _ => { + log::debug!("path segment {:?} not found", segment.name); + return ResolvePathResult::empty(ReachedFixedPoint::No); + } + } + } + ModuleDef::Enum(e) => { + // enum variant + tested_by!(item_map_enum_importing); + match e.variant(db, &segment.name) { + Some(variant) => PerNs::both(variant.into(), variant.into()), + None => { + return ResolvePathResult::with( + PerNs::types((*e).into()), + ReachedFixedPoint::Yes, + Some(i), + ); + } + } + } + s => { + // could be an inherent method call in UFCS form + // (`Struct::method`), or some other kind of associated item + log::debug!( + "path segment {:?} resolved to non-module {:?}, but is not last", + segment.name, + curr, + ); + + return ResolvePathResult::with( + PerNs::types((*s).into()), + ReachedFixedPoint::Yes, + Some(i), + ); + } + }; } + ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None) } - fn def_map(fixtute: &str) -> String { - let dm = compute_crate_def_map(fixtute); - render_crate_def_map(&dm) + fn resolve_name_in_crate_root_or_extern_prelude(&self, name: &Name) -> PerNs { + let from_crate_root = self[self.root].items.get(name).map_or(PerNs::none(), |it| it.def); + let from_extern_prelude = self.resolve_name_in_extern_prelude(name); + + from_crate_root.or(from_extern_prelude) } - #[test] - fn crate_def_map_smoke_test() { - let map = def_map( - " - //- /lib.rs - mod foo; - struct S; - - //- /foo/mod.rs - pub mod bar; - fn f() {} - - //- /foo/bar.rs - pub struct Baz; - enum E { V } - ", - ); - assert_snapshot_matches!( - map, - @r###" -crate -S: t v - -crate::foo -f: v - -crate::foo::bar -Baz: t v -E: t -"### - ) + fn resolve_name_in_module( + &self, + db: &impl PersistentHirDatabase, + module: ModuleId, + name: &Name, + ) -> PerNs { + // Resolve in: + // - current module / scope + // - extern prelude + // - std prelude + let from_scope = self[module].items.get(name).map_or(PerNs::none(), |it| it.def); + let from_extern_prelude = + self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it)); + let from_prelude = self.resolve_in_prelude(db, name); + + from_scope.or(from_extern_prelude).or(from_prelude) } - #[test] - fn macro_rules_are_globally_visible() { - let map = def_map( - " - //- /lib.rs - macro_rules! structs { - ($($i:ident),*) => { - $(struct $i { field: u32 } )* - } - } - structs!(Foo); - mod nested; - - //- /nested.rs - structs!(Bar, Baz); - ", - ); - assert_snapshot_matches!(map, @r###" -crate -Foo: t v - -crate::nested -Bar: t v -Baz: t v -"###); + fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs { + self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it)) } - #[test] - fn macro_rules_can_define_modules() { - let map = def_map( - " - //- /lib.rs - macro_rules! m { - ($name:ident) => { mod $name; } - } - m!(n1); - - //- /n1.rs - m!(n2) - //- /n1/n2.rs - struct X; - ", - ); - assert_snapshot_matches!(map, @r###" -crate - -crate::n1 - -crate::n1::n2 -X: t v -"###); + fn resolve_in_prelude(&self, db: &impl PersistentHirDatabase, name: &Name) -> PerNs { + if let Some(prelude) = self.prelude { + let resolution = if prelude.krate == self.krate { + self[prelude.module_id].items.get(name).cloned() + } else { + db.item_map(prelude.krate)[prelude.module_id].items.get(name).cloned() + }; + resolution.map(|r| r.def).unwrap_or_else(PerNs::none) + } else { + PerNs::none() + } } } diff --git a/crates/ra_hir/src/nameres/crate_def_map/collector.rs b/crates/ra_hir/src/nameres/crate_def_map/collector.rs index 46bef3dbe..cd328b755 100644 --- a/crates/ra_hir/src/nameres/crate_def_map/collector.rs +++ b/crates/ra_hir/src/nameres/crate_def_map/collector.rs @@ -2,12 +2,13 @@ use std::sync::Arc; use rustc_hash::FxHashMap; use ra_arena::Arena; +use test_utils::tested_by; use crate::{ Function, Module, Struct, Enum, Const, Static, Trait, TypeAlias, Crate, PersistentHirDatabase, HirFileId, Name, Path, KnownName, - nameres::{Resolution, PerNs, ModuleDef, ReachedFixedPoint}, + nameres::{Resolution, PerNs, ModuleDef, ReachedFixedPoint, ResolveMode}, ids::{AstItemDef, LocationCtx, MacroCallLoc, SourceItemId, MacroCallId}, module_tree::resolve_module_declaration, }; @@ -19,12 +20,41 @@ pub(crate) fn crate_def_map_query( db: &impl PersistentHirDatabase, krate: Crate, ) -> Arc { - let mut modules: Arena = Arena::default(); - let root = modules.alloc(ModuleData::default()); + let mut def_map = { + let edition = krate.edition(db); + let mut modules: Arena = Arena::default(); + let root = modules.alloc(ModuleData::default()); + CrateDefMap { + krate, + edition, + extern_prelude: FxHashMap::default(), + prelude: None, + root, + modules, + public_macros: FxHashMap::default(), + } + }; + + // populate external prelude + for dep in krate.dependencies(db) { + log::debug!("crate dep {:?} -> {:?}", dep.name, dep.krate); + if let Some(module) = dep.krate.root_module(db) { + def_map.extern_prelude.insert(dep.name.clone(), module.into()); + } + // look for the prelude + if def_map.prelude.is_none() { + let item_map = db.item_map(dep.krate); + if item_map.prelude.is_some() { + def_map.prelude = item_map.prelude; + } + } + } + let mut collector = DefCollector { db, krate, - def_map: CrateDefMap { modules, root }, + def_map, + glob_imports: FxHashMap::default(), unresolved_imports: Vec::new(), unexpanded_macros: Vec::new(), global_macro_scope: FxHashMap::default(), @@ -39,8 +69,9 @@ struct DefCollector { db: DB, krate: Crate, def_map: CrateDefMap, - unresolved_imports: Vec<(ModuleId, raw::Import)>, - unexpanded_macros: Vec<(ModuleId, MacroCallId, tt::Subtree)>, + glob_imports: FxHashMap>, + unresolved_imports: Vec<(ModuleId, raw::ImportId, raw::ImportData)>, + unexpanded_macros: Vec<(ModuleId, MacroCallId, Path, tt::Subtree)>, global_macro_scope: FxHashMap, } @@ -83,8 +114,11 @@ where } } - fn define_macro(&mut self, name: Name, tt: &tt::Subtree) { + fn define_macro(&mut self, name: Name, tt: &tt::Subtree, export: bool) { if let Ok(rules) = mbe::MacroRules::parse(tt) { + if export { + self.def_map.public_macros.insert(name.clone(), rules.clone()); + } self.global_macro_scope.insert(name, rules); } } @@ -94,22 +128,218 @@ where } fn resolve_imports(&mut self) -> ReachedFixedPoint { + let mut imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); + let mut resolved = Vec::new(); + imports.retain(|(module_id, import, import_data)| { + let (def, fp) = self.resolve_import(*module_id, import_data); + if fp == ReachedFixedPoint::Yes { + resolved.push((*module_id, def, *import, import_data.clone())) + } + fp == ReachedFixedPoint::No + }); + self.unresolved_imports = imports; // Resolves imports, filling-in module scopes - ReachedFixedPoint::Yes + let result = + if resolved.is_empty() { ReachedFixedPoint::Yes } else { ReachedFixedPoint::No }; + for (module_id, def, import, import_data) in resolved { + self.record_resolved_import(module_id, def, import, &import_data) + } + result } - fn resolve_macros(&mut self) -> ReachedFixedPoint { - // Resolve macros, calling into `expand_macro` to actually do the - // expansion. - ReachedFixedPoint::Yes + fn resolve_import( + &mut self, + module_id: ModuleId, + import: &raw::ImportData, + ) -> (PerNs, ReachedFixedPoint) { + log::debug!("resolving import: {:?} ({:?})", import, self.def_map.edition); + if import.is_extern_crate { + let res = self.def_map.resolve_name_in_extern_prelude( + &import + .path + .as_ident() + .expect("extern crate should have been desugared to one-element path"), + ); + // FIXME: why do we return No here? + (res, if res.is_none() { ReachedFixedPoint::No } else { ReachedFixedPoint::Yes }) + } else { + let res = + self.def_map.resolve_path_fp(self.db, ResolveMode::Import, module_id, &import.path); + + (res.resolved_def, res.reached_fixedpoint) + } + } + + fn record_resolved_import( + &mut self, + module_id: ModuleId, + def: PerNs, + import_id: raw::ImportId, + import: &raw::ImportData, + ) { + if import.is_glob { + log::debug!("glob import: {:?}", import); + match def.take_types() { + Some(ModuleDef::Module(m)) => { + if import.is_prelude { + tested_by!(std_prelude); + self.def_map.prelude = Some(m); + } else if m.krate != self.krate { + tested_by!(glob_across_crates); + // glob import from other crate => we can just import everything once + let item_map = self.db.item_map(m.krate); + let scope = &item_map[m.module_id]; + let items = scope + .items + .iter() + .map(|(name, res)| (name.clone(), res.clone())) + .collect::>(); + self.update(module_id, Some(import_id), &items); + } else { + // glob import from same crate => we do an initial + // import, and then need to propagate any further + // additions + let scope = &self.def_map[m.module_id]; + let items = scope + .items + .iter() + .map(|(name, res)| (name.clone(), res.clone())) + .collect::>(); + self.update(module_id, Some(import_id), &items); + // record the glob import in case we add further items + self.glob_imports + .entry(m.module_id) + .or_default() + .push((module_id, import_id)); + } + } + Some(ModuleDef::Enum(e)) => { + tested_by!(glob_enum); + // glob import from enum => just import all the variants + let variants = e.variants(self.db); + let resolutions = variants + .into_iter() + .filter_map(|variant| { + let res = Resolution { + def: PerNs::both(variant.into(), variant.into()), + import: Some(import_id), + }; + let name = variant.name(self.db)?; + Some((name, res)) + }) + .collect::>(); + self.update(module_id, Some(import_id), &resolutions); + } + Some(d) => { + log::debug!("glob import {:?} from non-module/enum {:?}", import, d); + } + None => { + log::debug!("glob import {:?} didn't resolve as type", import); + } + } + } else { + let last_segment = import.path.segments.last().unwrap(); + let name = import.alias.clone().unwrap_or_else(|| last_segment.name.clone()); + log::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); + + // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 + if let Some(root_module) = self.krate.root_module(self.db) { + if import.is_extern_crate && module_id == root_module.module_id { + if let Some(def) = def.take_types() { + self.def_map.extern_prelude.insert(name.clone(), def); + } + } + } + let resolution = Resolution { def, import: Some(import_id) }; + self.update(module_id, None, &[(name, resolution)]); + } + } + + fn update( + &mut self, + module_id: ModuleId, + import: Option, + resolutions: &[(Name, Resolution)], + ) { + self.update_recursive(module_id, import, resolutions, 0) } - #[allow(unused)] - fn expand_macro(&mut self, idx: usize, rules: &mbe::MacroRules) { - let (module_id, call_id, arg) = self.unexpanded_macros.swap_remove(idx); - if let Ok(tt) = rules.expand(&arg) { - self.collect_macro_expansion(module_id, call_id, tt); + fn update_recursive( + &mut self, + module_id: ModuleId, + import: Option, + resolutions: &[(Name, Resolution)], + depth: usize, + ) { + if depth > 100 { + // prevent stack overflows (but this shouldn't be possible) + panic!("infinite recursion in glob imports!"); } + let module_items = &mut self.def_map.modules[module_id].scope; + let mut changed = false; + for (name, res) in resolutions { + let existing = module_items.items.entry(name.clone()).or_default(); + if existing.def.types.is_none() && res.def.types.is_some() { + existing.def.types = res.def.types; + existing.import = import.or(res.import); + changed = true; + } + if existing.def.values.is_none() && res.def.values.is_some() { + existing.def.values = res.def.values; + existing.import = import.or(res.import); + changed = true; + } + } + if !changed { + return; + } + let glob_imports = self + .glob_imports + .get(&module_id) + .into_iter() + .flat_map(|v| v.iter()) + .cloned() + .collect::>(); + for (glob_importing_module, glob_import) in glob_imports { + // We pass the glob import so that the tracked import in those modules is that glob import + self.update_recursive(glob_importing_module, Some(glob_import), resolutions, depth + 1); + } + } + + // XXX: this is just a pile of hacks now, because `PerNs` does not handle + // macro namespace. + fn resolve_macros(&mut self) -> ReachedFixedPoint { + let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new()); + let mut resolved = Vec::new(); + macros.retain(|(module_id, call_id, path, tt)| { + if path.segments.len() != 2 { + return true; + } + let crate_name = &path.segments[0].name; + let krate = match self.def_map.resolve_name_in_extern_prelude(crate_name).take_types() { + Some(ModuleDef::Module(m)) => m.krate(self.db), + _ => return true, + }; + let krate = match krate { + Some(it) => it, + _ => return true, + }; + // FIXME: this should be a proper query + let def_map = crate_def_map_query(self.db, krate); + let rules = def_map.public_macros.get(&path.segments[1].name).cloned(); + resolved.push((*module_id, *call_id, rules, tt.clone())); + false + }); + let res = if resolved.is_empty() { ReachedFixedPoint::Yes } else { ReachedFixedPoint::No }; + + for (module_id, macro_call_id, rules, arg) in resolved { + if let Some(rules) = rules { + if let Ok(tt) = rules.expand(&arg) { + self.collect_macro_expansion(module_id, macro_call_id, tt); + } + } + } + res } fn collect_macro_expansion( @@ -145,9 +375,11 @@ where for item in items { match *item { raw::RawItem::Module(m) => self.collect_module(&self.raw_items[m]), - raw::RawItem::Import(import) => { - self.def_collector.unresolved_imports.push((self.module_id, import)) - } + raw::RawItem::Import(import) => self.def_collector.unresolved_imports.push(( + self.module_id, + import, + self.raw_items[import].clone(), + )), raw::RawItem::Def(def) => self.define_def(&self.raw_items[def]), raw::RawItem::Macro(mac) => self.collect_macro(&self.raw_items[mac]), } @@ -216,14 +448,14 @@ where raw::DefKind::TypeAlias => PerNs::types(TypeAlias { id: id!() }.into()), }; let resolution = Resolution { def, import: None }; - self.def_collector.def_map.modules[self.module_id].scope.items.insert(name, resolution); + self.def_collector.update(self.module_id, None, &[(name, resolution)]) } fn collect_macro(&mut self, mac: &raw::MacroData) { // Case 1: macro rules, define a macro in crate-global mutable scope if is_macro_rules(&mac.path) { if let Some(name) = &mac.name { - self.def_collector.define_macro(name.clone(), &mac.arg) + self.def_collector.define_macro(name.clone(), &mac.arg, mac.export) } return; } @@ -247,7 +479,12 @@ where } // Case 3: path to a macro from another crate, expand during name resolution - self.def_collector.unexpanded_macros.push((self.module_id, macro_call_id, mac.arg.clone())) + self.def_collector.unexpanded_macros.push(( + self.module_id, + macro_call_id, + mac.path.clone(), + mac.arg.clone(), + )) } } diff --git a/crates/ra_hir/src/nameres/crate_def_map/raw.rs b/crates/ra_hir/src/nameres/crate_def_map/raw.rs index cec2484eb..fe832b8da 100644 --- a/crates/ra_hir/src/nameres/crate_def_map/raw.rs +++ b/crates/ra_hir/src/nameres/crate_def_map/raw.rs @@ -18,7 +18,7 @@ use crate::{ #[derive(Default, PartialEq, Eq)] pub(crate) struct RawItems { modules: Arena, - imports: Arena, + imports: Arena, defs: Arena, macros: Arena, /// items for top-level module @@ -60,9 +60,9 @@ impl Index for RawItems { } } -impl Index for RawItems { +impl Index for RawItems { type Output = ImportData; - fn index(&self, idx: Import) -> &ImportData { + fn index(&self, idx: ImportId) -> &ImportData { &self.imports[idx] } } @@ -84,7 +84,7 @@ impl Index for RawItems { #[derive(PartialEq, Eq, Clone, Copy)] pub(crate) enum RawItem { Module(Module), - Import(Import), + Import(ImportId), Def(Def), Macro(Macro), } @@ -99,18 +99,8 @@ pub(crate) enum ModuleData { Definition { name: Name, items: Vec }, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct Import(RawId); -impl_arena_id!(Import); - -#[derive(PartialEq, Eq)] -pub(crate) struct ImportData { - path: Path, - alias: Option, - is_glob: bool, - is_prelude: bool, - is_extern_crate: bool, -} +pub(crate) use crate::nameres::lower::ImportId; +pub(super) use crate::nameres::lower::ImportData; #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct Def(RawId); @@ -144,6 +134,7 @@ pub(crate) struct MacroData { pub(crate) path: Path, pub(crate) name: Option, pub(crate) arg: tt::Subtree, + pub(crate) export: bool, } struct RawItemsCollector { @@ -215,9 +206,7 @@ impl RawItemsCollector { } fn add_use_item(&mut self, current_module: Option, use_item: &ast::UseItem) { - let is_prelude = use_item - .attrs() - .any(|attr| attr.as_atom().map(|s| s == "prelude_import").unwrap_or(false)); + let is_prelude = use_item.has_atom_attr("prelude_import"); Path::expand_use_item(use_item, |path, segment, alias| { let import = self.raw_items.imports.alloc(ImportData { @@ -261,7 +250,8 @@ impl RawItemsCollector { let name = m.name().map(|it| it.as_name()); let source_item_id = self.source_file_items.id_of_unchecked(m.syntax()); - let m = self.raw_items.macros.alloc(MacroData { source_item_id, path, arg, name }); + let export = m.has_atom_attr("macro_export"); + let m = self.raw_items.macros.alloc(MacroData { source_item_id, path, arg, name, export }); self.push_item(current_module, RawItem::Macro(m)); } diff --git a/crates/ra_hir/src/nameres/crate_def_map/tests.rs b/crates/ra_hir/src/nameres/crate_def_map/tests.rs new file mode 100644 index 000000000..a56dbaf90 --- /dev/null +++ b/crates/ra_hir/src/nameres/crate_def_map/tests.rs @@ -0,0 +1,265 @@ +use std::sync::Arc; + +use ra_db::SourceDatabase; +use test_utils::covers; +use insta::assert_snapshot_matches; + +use crate::{Crate, mock::{MockDatabase, CrateGraphFixture}, nameres::Resolution}; + +use super::*; + +fn compute_crate_def_map(fixture: &str, graph: Option) -> Arc { + let mut db = MockDatabase::with_files(fixture); + if let Some(graph) = graph { + db.set_crate_graph_from_fixture(graph); + } + let crate_id = db.crate_graph().iter().next().unwrap(); + let krate = Crate { crate_id }; + collector::crate_def_map_query(&db, krate) +} + +fn render_crate_def_map(map: &CrateDefMap) -> String { + let mut buf = String::new(); + go(&mut buf, map, "\ncrate", map.root); + return buf; + + fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: ModuleId) { + *buf += path; + *buf += "\n"; + for (name, res) in map.modules[module].scope.items.iter() { + *buf += &format!("{}: {}\n", name, dump_resolution(res)) + } + for (name, child) in map.modules[module].children.iter() { + let path = path.to_string() + &format!("::{}", name); + go(buf, map, &path, *child); + } + } + + fn dump_resolution(resolution: &Resolution) -> &'static str { + match (resolution.def.types.is_some(), resolution.def.values.is_some()) { + (true, true) => "t v", + (true, false) => "t", + (false, true) => "v", + (false, false) => "_", + } + } +} + +fn def_map(fixtute: &str) -> String { + let dm = compute_crate_def_map(fixtute, None); + render_crate_def_map(&dm) +} + +fn def_map_with_crate_graph(fixtute: &str, graph: CrateGraphFixture) -> String { + let dm = compute_crate_def_map(fixtute, Some(graph)); + render_crate_def_map(&dm) +} + +#[test] +fn crate_def_map_smoke_test() { + let map = def_map( + " + //- /lib.rs + mod foo; + struct S; + + //- /foo/mod.rs + pub mod bar; + fn f() {} + + //- /foo/bar.rs + pub struct Baz; + enum E { V } + ", + ); + assert_snapshot_matches!(map, @r###" +crate +S: t v + +crate::foo +f: v + +crate::foo::bar +Baz: t v +E: t +"### + ) +} + +#[test] +fn macro_rules_are_globally_visible() { + let map = def_map( + " + //- /lib.rs + macro_rules! structs { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } + } + structs!(Foo); + mod nested; + + //- /nested.rs + structs!(Bar, Baz); + ", + ); + assert_snapshot_matches!(map, @r###" +crate +Foo: t v + +crate::nested +Bar: t v +Baz: t v +"###); +} + +#[test] +fn macro_rules_can_define_modules() { + let map = def_map( + " + //- /lib.rs + macro_rules! m { + ($name:ident) => { mod $name; } + } + m!(n1); + + //- /n1.rs + m!(n2) + //- /n1/n2.rs + struct X; + ", + ); + assert_snapshot_matches!(map, @r###" +crate + +crate::n1 + +crate::n1::n2 +X: t v +"###); +} + +#[test] +fn macro_rules_from_other_crates_are_visible() { + let map = def_map_with_crate_graph( + " + //- /main.rs + foo::structs!(Foo, Bar) + mod bar; + + //- /bar.rs + use crate::*; + + //- /lib.rs + #[macro_export] + macro_rules! structs { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } + } + ", + crate_graph! { + "main": ("/main.rs", ["foo"]), + "foo": ("/lib.rs", []), + }, + ); + assert_snapshot_matches!(map, @r###" +crate +Foo: t v +Bar: t v + +crate::bar +Foo: t v +Bar: t v +"###); +} + +#[test] +fn std_prelude() { + covers!(std_prelude); + let map = def_map_with_crate_graph( + " + //- /main.rs + use Foo::*; + + //- /lib.rs + mod prelude; + #[prelude_import] + use prelude::*; + + //- /prelude.rs + pub enum Foo { Bar, Baz }; + ", + crate_graph! { + "main": ("/main.rs", ["test_crate"]), + "test_crate": ("/lib.rs", []), + }, + ); + assert_snapshot_matches!(map, @r###" +crate +Bar: t v +Baz: t v +"###); +} + +#[test] +fn glob_across_crates() { + covers!(glob_across_crates); + let map = def_map_with_crate_graph( + " + //- /main.rs + use test_crate::*; + + //- /lib.rs + pub struct Baz; + ", + crate_graph! { + "main": ("/main.rs", ["test_crate"]), + "test_crate": ("/lib.rs", []), + }, + ); + assert_snapshot_matches!(map, @r###" +crate +Baz: t v +"### + ); +} + +#[test] +fn item_map_enum_importing() { + covers!(item_map_enum_importing); + let map = def_map( + " + //- /lib.rs + enum E { V } + use self::E::V; + ", + ); + assert_snapshot_matches!(map, @r###" +crate +V: t v +E: t +"### + ); +} + +#[test] +fn glob_enum() { + covers!(glob_enum); + let map = def_map( + " + //- /lib.rs + enum Foo { + Bar, Baz + } + use self::Foo::*; + ", + ); + assert_snapshot_matches!(map, @r###" +crate +Foo: t +Bar: t v +Baz: t v +"### + ); +} diff --git a/crates/ra_hir/src/nameres/lower.rs b/crates/ra_hir/src/nameres/lower.rs index 56262ad6d..24707aed1 100644 --- a/crates/ra_hir/src/nameres/lower.rs +++ b/crates/ra_hir/src/nameres/lower.rs @@ -18,8 +18,8 @@ use crate::{ pub struct ImportId(RawId); impl_arena_id!(ImportId); -#[derive(Debug, PartialEq, Eq)] -pub(super) struct ImportData { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ImportData { pub(super) path: Path, pub(super) alias: Option, pub(super) is_glob: bool, diff --git a/crates/ra_mbe/src/lib.rs b/crates/ra_mbe/src/lib.rs index 2c75b7b4f..989308626 100644 --- a/crates/ra_mbe/src/lib.rs +++ b/crates/ra_mbe/src/lib.rs @@ -42,7 +42,7 @@ pub use crate::syntax_bridge::{ast_to_token_tree, token_tree_to_ast_item_list}; /// be very confusing is that AST has almost exactly the same shape as /// `tt::TokenTree`, but there's a crucial difference: in macro rules, `$ident` /// and `$()*` have special meaning (see `Var` and `Repeat` data structures) -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct MacroRules { pub(crate) rules: Vec, } @@ -56,13 +56,13 @@ impl MacroRules { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Rule { pub(crate) lhs: Subtree, pub(crate) rhs: Subtree, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum TokenTree { Leaf(Leaf), Subtree(Subtree), @@ -70,7 +70,7 @@ pub(crate) enum TokenTree { } impl_froms!(TokenTree: Leaf, Subtree, Repeat); -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum Leaf { Literal(Literal), Punct(Punct), @@ -79,37 +79,37 @@ pub(crate) enum Leaf { } impl_froms!(Leaf: Literal, Punct, Ident, Var); -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Subtree { pub(crate) delimiter: Delimiter, pub(crate) token_trees: Vec, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Repeat { pub(crate) subtree: Subtree, pub(crate) kind: RepeatKind, pub(crate) separator: Option, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum RepeatKind { ZeroOrMore, OneOrMore, ZeroOrOne, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Literal { pub(crate) text: SmolStr, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Ident { pub(crate) text: SmolStr, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Var { pub(crate) text: SmolStr, pub(crate) kind: Option, diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 81c709bfb..d8c2cb063 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -114,6 +114,9 @@ pub trait AttrsOwner: AstNode { fn attrs(&self) -> AstChildren { children(self) } + fn has_atom_attr(&self, atom: &str) -> bool { + self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom) + } } pub trait DocCommentsOwner: AstNode { @@ -153,12 +156,6 @@ pub trait DocCommentsOwner: AstNode { } } -impl FnDef { - pub fn has_atom_attr(&self, atom: &str) -> bool { - self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom) - } -} - impl Attr { pub fn is_inner(&self) -> bool { let tt = match self.value() { diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index 7572225b8..54b72f8c5 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs @@ -2108,6 +2108,7 @@ impl ToOwned for MacroCall { impl ast::NameOwner for MacroCall {} +impl ast::AttrsOwner for MacroCall {} impl MacroCall { pub fn token_tree(&self) -> Option<&TokenTree> { super::child_opt(self) diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index 66f1339c1..4f8e19bd0 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron @@ -557,7 +557,7 @@ Grammar( "Name": (), "NameRef": (), "MacroCall": ( - traits: [ "NameOwner" ], + traits: [ "NameOwner", "AttrsOwner" ], options: [ "TokenTree", "Path" ], ), "Attr": ( options: [ ["value", "TokenTree"] ] ), -- cgit v1.2.3