From 967a4b64af2f09e11108d28f11565fa0f3e583a5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 16 Mar 2019 17:17:50 +0300 Subject: Reorganize name resolution --- crates/ra_hir/src/nameres/collector.rs | 547 +++++++++++++++++++++ crates/ra_hir/src/nameres/crate_def_map.rs | 368 -------------- .../ra_hir/src/nameres/crate_def_map/collector.rs | 547 --------------------- crates/ra_hir/src/nameres/crate_def_map/raw.rs | 293 ----------- crates/ra_hir/src/nameres/crate_def_map/tests.rs | 528 -------------------- .../src/nameres/crate_def_map/tests/globs.rs | 118 ----- .../src/nameres/crate_def_map/tests/incremental.rs | 123 ----- .../src/nameres/crate_def_map/tests/macros.rs | 94 ---- crates/ra_hir/src/nameres/lower.rs | 40 -- crates/ra_hir/src/nameres/per_ns.rs | 78 +++ crates/ra_hir/src/nameres/raw.rs | 322 ++++++++++++ crates/ra_hir/src/nameres/tests.rs | 528 ++++++++++++++++++++ crates/ra_hir/src/nameres/tests/globs.rs | 118 +++++ crates/ra_hir/src/nameres/tests/incremental.rs | 123 +++++ crates/ra_hir/src/nameres/tests/macros.rs | 94 ++++ 15 files changed, 1810 insertions(+), 2111 deletions(-) create mode 100644 crates/ra_hir/src/nameres/collector.rs delete mode 100644 crates/ra_hir/src/nameres/crate_def_map.rs delete mode 100644 crates/ra_hir/src/nameres/crate_def_map/collector.rs delete mode 100644 crates/ra_hir/src/nameres/crate_def_map/raw.rs delete mode 100644 crates/ra_hir/src/nameres/crate_def_map/tests.rs delete mode 100644 crates/ra_hir/src/nameres/crate_def_map/tests/globs.rs delete mode 100644 crates/ra_hir/src/nameres/crate_def_map/tests/incremental.rs delete mode 100644 crates/ra_hir/src/nameres/crate_def_map/tests/macros.rs delete mode 100644 crates/ra_hir/src/nameres/lower.rs create mode 100644 crates/ra_hir/src/nameres/per_ns.rs create mode 100644 crates/ra_hir/src/nameres/raw.rs create mode 100644 crates/ra_hir/src/nameres/tests.rs create mode 100644 crates/ra_hir/src/nameres/tests/globs.rs create mode 100644 crates/ra_hir/src/nameres/tests/incremental.rs create mode 100644 crates/ra_hir/src/nameres/tests/macros.rs (limited to 'crates/ra_hir/src/nameres') diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs new file mode 100644 index 000000000..cbe850ba4 --- /dev/null +++ b/crates/ra_hir/src/nameres/collector.rs @@ -0,0 +1,547 @@ +use arrayvec::ArrayVec; +use rustc_hash::FxHashMap; +use relative_path::RelativePathBuf; +use test_utils::tested_by; +use ra_db::FileId; + +use crate::{ + Function, Module, Struct, Enum, Const, Static, Trait, TypeAlias, + PersistentHirDatabase, HirFileId, Name, Path, Problem, + KnownName, + nameres::{Resolution, PerNs, ModuleDef, ReachedFixedPoint, ResolveMode, raw}, + ids::{AstItemDef, LocationCtx, MacroCallLoc, SourceItemId, MacroCallId}, +}; + +use super::{CrateDefMap, ModuleId, ModuleData}; + +pub(super) fn collect_defs( + db: &impl PersistentHirDatabase, + mut def_map: CrateDefMap, +) -> CrateDefMap { + // populate external prelude + for dep in def_map.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 map = db.crate_def_map(dep.krate); + if map.prelude.is_some() { + def_map.prelude = map.prelude; + } + } + } + + let mut collector = DefCollector { + db, + def_map, + glob_imports: FxHashMap::default(), + unresolved_imports: Vec::new(), + unexpanded_macros: Vec::new(), + global_macro_scope: FxHashMap::default(), + }; + collector.collect(); + collector.finish() +} + +/// Walks the tree of module recursively +struct DefCollector { + db: DB, + def_map: CrateDefMap, + glob_imports: FxHashMap>, + unresolved_imports: Vec<(ModuleId, raw::ImportId, raw::ImportData)>, + unexpanded_macros: Vec<(ModuleId, MacroCallId, Path, tt::Subtree)>, + global_macro_scope: FxHashMap, +} + +impl<'a, DB> DefCollector<&'a DB> +where + DB: PersistentHirDatabase, +{ + fn collect(&mut self) { + let crate_graph = self.db.crate_graph(); + let file_id = crate_graph.crate_root(self.def_map.krate.crate_id()); + let raw_items = self.db.raw_items(file_id); + let module_id = self.def_map.root; + self.def_map.modules[module_id].definition = Some(file_id); + ModCollector { + def_collector: &mut *self, + module_id, + file_id: file_id.into(), + raw_items: &raw_items, + } + .collect(raw_items.items()); + + // main name resolution fixed-point loop. + let mut i = 0; + loop { + match (self.resolve_imports(), self.resolve_macros()) { + (ReachedFixedPoint::Yes, ReachedFixedPoint::Yes) => break, + _ => i += 1, + } + if i == 1000 { + log::error!("diverging name resolution"); + break; + } + } + + let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); + // show unresolved imports in completion, etc + for (module_id, import, import_data) in unresolved_imports { + self.record_resolved_import(module_id, PerNs::none(), import, &import_data) + } + } + + 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); + } + } + + 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 + 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_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.def_map.krate { + tested_by!(glob_across_crates); + // glob import from other crate => we can just import everything once + let item_map = self.db.crate_def_map(m.krate); + let scope = &item_map[m.module_id].scope; + 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].scope; + 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 import.is_extern_crate && module_id == self.def_map.root { + 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, Some(import_id), &[(name, resolution)]); + } + } + + fn update( + &mut self, + module_id: ModuleId, + import: Option, + resolutions: &[(Name, Resolution)], + ) { + self.update_recursive(module_id, import, resolutions, 0) + } + + 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 existing.def.is_none() + && res.def.is_none() + && existing.import.is_none() + && res.import.is_some() + { + existing.import = res.import; + } + } + 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, + }; + let def_map = self.db.crate_def_map(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( + &mut self, + module_id: ModuleId, + macro_call_id: MacroCallId, + expansion: tt::Subtree, + ) { + // XXX: this **does not** go through a database, because we can't + // identify macro_call without adding the whole state of name resolution + // as a parameter to the query. + // + // So, we run the queries "manually" and we must ensure that + // `db.hir_parse(macro_call_id)` returns the same source_file. + let file_id: HirFileId = macro_call_id.into(); + let source_file = mbe::token_tree_to_ast_item_list(&expansion); + + let raw_items = raw::RawItems::from_source_file(&source_file, file_id); + ModCollector { def_collector: &mut *self, file_id, module_id, raw_items: &raw_items } + .collect(raw_items.items()) + } + + fn finish(self) -> CrateDefMap { + self.def_map + } +} + +/// Walks a single module, populating defs, imports and macros +struct ModCollector<'a, D> { + def_collector: D, + module_id: ModuleId, + file_id: HirFileId, + raw_items: &'a raw::RawItems, +} + +impl ModCollector<'_, &'_ mut DefCollector<&'_ DB>> +where + DB: PersistentHirDatabase, +{ + fn collect(&mut self, items: &[raw::RawItem]) { + 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, + 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]), + } + } + } + + fn collect_module(&mut self, module: &raw::ModuleData) { + match module { + // inline module, just recurse + raw::ModuleData::Definition { name, items, source_item_id } => { + let module_id = self.push_child_module( + name.clone(), + source_item_id.with_file_id(self.file_id), + None, + ); + ModCollector { + def_collector: &mut *self.def_collector, + module_id, + file_id: self.file_id, + raw_items: self.raw_items, + } + .collect(&*items); + } + // out of line module, resovle, parse and recurse + raw::ModuleData::Declaration { name, source_item_id } => { + let source_item_id = source_item_id.with_file_id(self.file_id); + let is_root = self.def_collector.def_map.modules[self.module_id].parent.is_none(); + let (file_ids, problem) = + resolve_submodule(self.def_collector.db, self.file_id, name, is_root); + + if let Some(problem) = problem { + self.def_collector.def_map.problems.add(source_item_id, problem) + } + + if let Some(&file_id) = file_ids.first() { + let module_id = + self.push_child_module(name.clone(), source_item_id, Some(file_id)); + let raw_items = self.def_collector.db.raw_items(file_id); + ModCollector { + def_collector: &mut *self.def_collector, + module_id, + file_id: file_id.into(), + raw_items: &raw_items, + } + .collect(raw_items.items()) + } + } + } + } + + fn push_child_module( + &mut self, + name: Name, + declaration: SourceItemId, + definition: Option, + ) -> ModuleId { + let modules = &mut self.def_collector.def_map.modules; + let res = modules.alloc(ModuleData::default()); + modules[res].parent = Some(self.module_id); + modules[res].declaration = Some(declaration); + modules[res].definition = definition; + modules[self.module_id].children.insert(name.clone(), res); + let resolution = Resolution { + def: PerNs::types( + Module { krate: self.def_collector.def_map.krate, module_id: res }.into(), + ), + import: None, + }; + self.def_collector.update(self.module_id, None, &[(name, resolution)]); + res + } + + fn define_def(&mut self, def: &raw::DefData) { + let module = Module { krate: self.def_collector.def_map.krate, module_id: self.module_id }; + let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id.into()); + macro_rules! id { + () => { + AstItemDef::from_source_item_id_unchecked(ctx, def.source_item_id) + }; + } + let name = def.name.clone(); + let def: PerNs = match def.kind { + raw::DefKind::Function => PerNs::values(Function { id: id!() }.into()), + raw::DefKind::Struct => { + let s = Struct { id: id!() }.into(); + PerNs::both(s, s) + } + raw::DefKind::Enum => PerNs::types(Enum { id: id!() }.into()), + raw::DefKind::Const => PerNs::values(Const { id: id!() }.into()), + raw::DefKind::Static => PerNs::values(Static { id: id!() }.into()), + raw::DefKind::Trait => PerNs::types(Trait { id: id!() }.into()), + raw::DefKind::TypeAlias => PerNs::types(TypeAlias { id: id!() }.into()), + }; + let resolution = Resolution { def, import: None }; + 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, mac.export) + } + return; + } + + let source_item_id = SourceItemId { file_id: self.file_id, item_id: mac.source_item_id }; + let macro_call_id = MacroCallLoc { + module: Module { krate: self.def_collector.def_map.krate, module_id: self.module_id }, + source_item_id, + } + .id(self.def_collector.db); + + // Case 2: try to expand macro_rules from this crate, triggering + // recursive item collection. + if let Some(rules) = + mac.path.as_ident().and_then(|name| self.def_collector.global_macro_scope.get(name)) + { + if let Ok(tt) = rules.expand(&mac.arg) { + self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, tt); + } + return; + } + + // 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.path.clone(), + mac.arg.clone(), + )) + } +} + +fn is_macro_rules(path: &Path) -> bool { + path.as_ident().and_then(Name::as_known_name) == Some(KnownName::MacroRules) +} + +fn resolve_submodule( + db: &impl PersistentHirDatabase, + file_id: HirFileId, + name: &Name, + is_root: bool, +) -> (Vec, Option) { + // FIXME: handle submodules of inline modules properly + let file_id = file_id.original_file(db); + let source_root_id = db.file_source_root(file_id); + let path = db.file_relative_path(file_id); + let root = RelativePathBuf::default(); + let dir_path = path.parent().unwrap_or(&root); + let mod_name = path.file_stem().unwrap_or("unknown"); + let is_dir_owner = is_root || mod_name == "mod"; + + let file_mod = dir_path.join(format!("{}.rs", name)); + let dir_mod = dir_path.join(format!("{}/mod.rs", name)); + let file_dir_mod = dir_path.join(format!("{}/{}.rs", mod_name, name)); + let mut candidates = ArrayVec::<[_; 2]>::new(); + if is_dir_owner { + candidates.push(file_mod.clone()); + candidates.push(dir_mod); + } else { + candidates.push(file_dir_mod.clone()); + }; + let sr = db.source_root(source_root_id); + let points_to = candidates + .into_iter() + .filter_map(|path| sr.files.get(&path)) + .map(|&it| it) + .collect::>(); + let problem = if points_to.is_empty() { + Some(Problem::UnresolvedModule { + candidate: if is_dir_owner { file_mod } else { file_dir_mod }, + }) + } else { + None + }; + (points_to, problem) +} diff --git a/crates/ra_hir/src/nameres/crate_def_map.rs b/crates/ra_hir/src/nameres/crate_def_map.rs deleted file mode 100644 index cc4955053..000000000 --- a/crates/ra_hir/src/nameres/crate_def_map.rs +++ /dev/null @@ -1,368 +0,0 @@ -/// This module implements new import-resolution/macro expansion algorithm. -/// -/// The result of this module is `CrateDefMap`: a datastructure which contains: -/// -/// * a tree of modules for the crate -/// * for each module, a set of items visible in the module (directly declared -/// or imported) -/// -/// Note that `CrateDefMap` contains fully macro expanded code. -/// -/// Computing `CrateDefMap` can be partitioned into several logically -/// independent "phases". The phases are mutually recursive though, there's no -/// stric ordering. -/// -/// ## Collecting RawItems -/// -/// This happens in the `raw` module, which parses a single source file into a -/// set of top-level items. Nested importa are desugared to flat imports in -/// this phase. Macro calls are represented as a triple of (Path, Option, -/// TokenTree). -/// -/// ## Collecting Modules -/// -/// This happens in the `collector` module. In this phase, we recursively walk -/// tree of modules, collect raw items from submodules, populate module scopes -/// with defined items (so, we assign item ids in this phase) and record the set -/// of unresovled imports and macros. -/// -/// While we walk tree of modules, we also record macro_rules defenitions and -/// expand calls to macro_rules defined macros. -/// -/// ## Resolving Imports -/// -/// TBD -/// -/// ## Resolving Macros -/// -/// While macro_rules from the same crate use a global mutable namespace, macros -/// from other crates (including proc-macros) can be used with `foo::bar!` -/// syntax. -/// -/// TBD; - -mod raw; -mod collector; -#[cfg(test)] -mod tests; - -use rustc_hash::FxHashMap; -use test_utils::tested_by; -use ra_arena::{Arena, RawId, impl_arena_id}; -use ra_db::FileId; - -use std::sync::Arc; - -use crate::{ - Name, Module, Path, PathKind, ModuleDef, Crate, Problem, HirFileId, - PersistentHirDatabase, - nameres::{ModuleScope, ResolveMode, ResolvePathResult, PerNs, Edition, ReachedFixedPoint}, - ids::{SourceItemId, SourceFileItemId}, -}; - -pub(crate) use self::raw::RawItems; - -#[derive(Default, Debug, PartialEq, Eq)] -pub(crate) struct ModuleData { - pub(crate) parent: Option, - pub(crate) children: FxHashMap, - pub(crate) scope: ModuleScope, - /// None for root - pub(crate) declaration: Option, - /// None for inline modules. - /// - /// Note that non-inline modules, by definition, live inside non-macro file. - pub(crate) definition: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct ModuleId(RawId); -impl_arena_id!(ModuleId); - -/// Contans all top-level defs from a macro-expanded crate -#[derive(Debug, PartialEq, Eq)] -pub 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, - problems: CrateDefMapProblems, -} - -#[derive(Default, Debug, PartialEq, Eq)] -pub(crate) struct CrateDefMapProblems { - problems: Vec<(SourceItemId, Problem)>, -} - -impl CrateDefMapProblems { - fn add(&mut self, source_item_id: SourceItemId, problem: Problem) { - self.problems.push((source_item_id, problem)) - } - - pub(crate) fn iter<'a>(&'a self) -> impl Iterator + 'a { - self.problems.iter().map(|(s, p)| (s, p)) - } -} - -impl std::ops::Index for CrateDefMap { - type Output = ModuleData; - fn index(&self, id: ModuleId) -> &ModuleData { - &self.modules[id] - } -} - -impl CrateDefMap { - pub(crate) fn crate_def_map_query( - db: &impl PersistentHirDatabase, - krate: Crate, - ) -> Arc { - let 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(), - problems: CrateDefMapProblems::default(), - } - }; - let def_map = collector::collect_defs(db, def_map); - Arc::new(def_map) - } - - pub(crate) fn root(&self) -> ModuleId { - self.root - } - - pub(crate) fn problems(&self) -> &CrateDefMapProblems { - &self.problems - } - - pub(crate) fn mk_module(&self, module_id: ModuleId) -> Module { - Module { krate: self.krate, module_id } - } - - pub(crate) fn prelude(&self) -> Option { - self.prelude - } - - pub(crate) fn extern_prelude(&self) -> &FxHashMap { - &self.extern_prelude - } - - pub(crate) fn find_module_by_source( - &self, - file_id: HirFileId, - decl_id: Option, - ) -> Option { - let decl_id = decl_id.map(|it| it.with_file_id(file_id)); - let (module_id, _module_data) = self.modules.iter().find(|(_module_id, module_data)| { - if decl_id.is_some() { - module_data.declaration == decl_id - } else { - module_data.definition.map(|it| it.into()) == Some(file_id) - } - })?; - Some(module_id) - } - - pub(crate) fn resolve_path( - &self, - db: &impl PersistentHirDatabase, - original_module: ModuleId, - path: &Path, - ) -> (PerNs, Option) { - let res = self.resolve_path_fp(db, ResolveMode::Other, original_module, path); - (res.resolved_def, res.segment_index) - } - - // Returns Yes if we are sure that additions to `ItemMap` wouldn't change - // the result. - 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()) - } - PathKind::Self_ => { - PerNs::types(Module { krate: self.krate, module_id: original_module }.into()) - } - // 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 defp_map = db.crate_def_map(module.krate); - let (def, s) = defp_map.resolve_path(db, module.module_id, &path); - return ResolvePathResult::with( - def, - ReachedFixedPoint::Yes, - s.map(|s| s + i), - ); - } - - match self[module.module_id].scope.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!(can_import_enum_variant); - 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 resolve_name_in_crate_root_or_extern_prelude(&self, name: &Name) -> PerNs { - let from_crate_root = - self[self.root].scope.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) - } - - pub(crate) 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].scope.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) - } - - fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs { - self.extern_prelude.get(name).map_or(PerNs::none(), |&it| PerNs::types(it)) - } - - 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].scope.items.get(name).cloned() - } else { - db.crate_def_map(prelude.krate)[prelude.module_id].scope.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 deleted file mode 100644 index 0f500ce42..000000000 --- a/crates/ra_hir/src/nameres/crate_def_map/collector.rs +++ /dev/null @@ -1,547 +0,0 @@ -use arrayvec::ArrayVec; -use rustc_hash::FxHashMap; -use relative_path::RelativePathBuf; -use test_utils::tested_by; -use ra_db::FileId; - -use crate::{ - Function, Module, Struct, Enum, Const, Static, Trait, TypeAlias, - PersistentHirDatabase, HirFileId, Name, Path, Problem, - KnownName, - nameres::{Resolution, PerNs, ModuleDef, ReachedFixedPoint, ResolveMode}, - ids::{AstItemDef, LocationCtx, MacroCallLoc, SourceItemId, MacroCallId}, -}; - -use super::{CrateDefMap, ModuleId, ModuleData, raw}; - -pub(super) fn collect_defs( - db: &impl PersistentHirDatabase, - mut def_map: CrateDefMap, -) -> CrateDefMap { - // populate external prelude - for dep in def_map.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 map = db.crate_def_map(dep.krate); - if map.prelude.is_some() { - def_map.prelude = map.prelude; - } - } - } - - let mut collector = DefCollector { - db, - def_map, - glob_imports: FxHashMap::default(), - unresolved_imports: Vec::new(), - unexpanded_macros: Vec::new(), - global_macro_scope: FxHashMap::default(), - }; - collector.collect(); - collector.finish() -} - -/// Walks the tree of module recursively -struct DefCollector { - db: DB, - def_map: CrateDefMap, - glob_imports: FxHashMap>, - unresolved_imports: Vec<(ModuleId, raw::ImportId, raw::ImportData)>, - unexpanded_macros: Vec<(ModuleId, MacroCallId, Path, tt::Subtree)>, - global_macro_scope: FxHashMap, -} - -impl<'a, DB> DefCollector<&'a DB> -where - DB: PersistentHirDatabase, -{ - fn collect(&mut self) { - let crate_graph = self.db.crate_graph(); - let file_id = crate_graph.crate_root(self.def_map.krate.crate_id()); - let raw_items = self.db.raw_items(file_id); - let module_id = self.def_map.root; - self.def_map.modules[module_id].definition = Some(file_id); - ModCollector { - def_collector: &mut *self, - module_id, - file_id: file_id.into(), - raw_items: &raw_items, - } - .collect(raw_items.items()); - - // main name resolution fixed-point loop. - let mut i = 0; - loop { - match (self.resolve_imports(), self.resolve_macros()) { - (ReachedFixedPoint::Yes, ReachedFixedPoint::Yes) => break, - _ => i += 1, - } - if i == 1000 { - log::error!("diverging name resolution"); - break; - } - } - - let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); - // show unresolved imports in completion, etc - for (module_id, import, import_data) in unresolved_imports { - self.record_resolved_import(module_id, PerNs::none(), import, &import_data) - } - } - - 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); - } - } - - 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 - 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_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.def_map.krate { - tested_by!(glob_across_crates); - // glob import from other crate => we can just import everything once - let item_map = self.db.crate_def_map(m.krate); - let scope = &item_map[m.module_id].scope; - 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].scope; - 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 import.is_extern_crate && module_id == self.def_map.root { - 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, Some(import_id), &[(name, resolution)]); - } - } - - fn update( - &mut self, - module_id: ModuleId, - import: Option, - resolutions: &[(Name, Resolution)], - ) { - self.update_recursive(module_id, import, resolutions, 0) - } - - 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 existing.def.is_none() - && res.def.is_none() - && existing.import.is_none() - && res.import.is_some() - { - existing.import = res.import; - } - } - 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, - }; - let def_map = self.db.crate_def_map(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( - &mut self, - module_id: ModuleId, - macro_call_id: MacroCallId, - expansion: tt::Subtree, - ) { - // XXX: this **does not** go through a database, because we can't - // identify macro_call without adding the whole state of name resolution - // as a parameter to the query. - // - // So, we run the queries "manually" and we must ensure that - // `db.hir_parse(macro_call_id)` returns the same source_file. - let file_id: HirFileId = macro_call_id.into(); - let source_file = mbe::token_tree_to_ast_item_list(&expansion); - - let raw_items = raw::RawItems::from_source_file(&source_file, file_id); - ModCollector { def_collector: &mut *self, file_id, module_id, raw_items: &raw_items } - .collect(raw_items.items()) - } - - fn finish(self) -> CrateDefMap { - self.def_map - } -} - -/// Walks a single module, populating defs, imports and macros -struct ModCollector<'a, D> { - def_collector: D, - module_id: ModuleId, - file_id: HirFileId, - raw_items: &'a raw::RawItems, -} - -impl ModCollector<'_, &'_ mut DefCollector<&'_ DB>> -where - DB: PersistentHirDatabase, -{ - fn collect(&mut self, items: &[raw::RawItem]) { - 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, - 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]), - } - } - } - - fn collect_module(&mut self, module: &raw::ModuleData) { - match module { - // inline module, just recurse - raw::ModuleData::Definition { name, items, source_item_id } => { - let module_id = self.push_child_module( - name.clone(), - source_item_id.with_file_id(self.file_id), - None, - ); - ModCollector { - def_collector: &mut *self.def_collector, - module_id, - file_id: self.file_id, - raw_items: self.raw_items, - } - .collect(&*items); - } - // out of line module, resovle, parse and recurse - raw::ModuleData::Declaration { name, source_item_id } => { - let source_item_id = source_item_id.with_file_id(self.file_id); - let is_root = self.def_collector.def_map.modules[self.module_id].parent.is_none(); - let (file_ids, problem) = - resolve_submodule(self.def_collector.db, self.file_id, name, is_root); - - if let Some(problem) = problem { - self.def_collector.def_map.problems.add(source_item_id, problem) - } - - if let Some(&file_id) = file_ids.first() { - let module_id = - self.push_child_module(name.clone(), source_item_id, Some(file_id)); - let raw_items = self.def_collector.db.raw_items(file_id); - ModCollector { - def_collector: &mut *self.def_collector, - module_id, - file_id: file_id.into(), - raw_items: &raw_items, - } - .collect(raw_items.items()) - } - } - } - } - - fn push_child_module( - &mut self, - name: Name, - declaration: SourceItemId, - definition: Option, - ) -> ModuleId { - let modules = &mut self.def_collector.def_map.modules; - let res = modules.alloc(ModuleData::default()); - modules[res].parent = Some(self.module_id); - modules[res].declaration = Some(declaration); - modules[res].definition = definition; - modules[self.module_id].children.insert(name.clone(), res); - let resolution = Resolution { - def: PerNs::types( - Module { krate: self.def_collector.def_map.krate, module_id: res }.into(), - ), - import: None, - }; - self.def_collector.update(self.module_id, None, &[(name, resolution)]); - res - } - - fn define_def(&mut self, def: &raw::DefData) { - let module = Module { krate: self.def_collector.def_map.krate, module_id: self.module_id }; - let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id.into()); - macro_rules! id { - () => { - AstItemDef::from_source_item_id_unchecked(ctx, def.source_item_id) - }; - } - let name = def.name.clone(); - let def: PerNs = match def.kind { - raw::DefKind::Function => PerNs::values(Function { id: id!() }.into()), - raw::DefKind::Struct => { - let s = Struct { id: id!() }.into(); - PerNs::both(s, s) - } - raw::DefKind::Enum => PerNs::types(Enum { id: id!() }.into()), - raw::DefKind::Const => PerNs::values(Const { id: id!() }.into()), - raw::DefKind::Static => PerNs::values(Static { id: id!() }.into()), - raw::DefKind::Trait => PerNs::types(Trait { id: id!() }.into()), - raw::DefKind::TypeAlias => PerNs::types(TypeAlias { id: id!() }.into()), - }; - let resolution = Resolution { def, import: None }; - 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, mac.export) - } - return; - } - - let source_item_id = SourceItemId { file_id: self.file_id, item_id: mac.source_item_id }; - let macro_call_id = MacroCallLoc { - module: Module { krate: self.def_collector.def_map.krate, module_id: self.module_id }, - source_item_id, - } - .id(self.def_collector.db); - - // Case 2: try to expand macro_rules from this crate, triggering - // recursive item collection. - if let Some(rules) = - mac.path.as_ident().and_then(|name| self.def_collector.global_macro_scope.get(name)) - { - if let Ok(tt) = rules.expand(&mac.arg) { - self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, tt); - } - return; - } - - // 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.path.clone(), - mac.arg.clone(), - )) - } -} - -fn is_macro_rules(path: &Path) -> bool { - path.as_ident().and_then(Name::as_known_name) == Some(KnownName::MacroRules) -} - -fn resolve_submodule( - db: &impl PersistentHirDatabase, - file_id: HirFileId, - name: &Name, - is_root: bool, -) -> (Vec, Option) { - // FIXME: handle submodules of inline modules properly - let file_id = file_id.original_file(db); - let source_root_id = db.file_source_root(file_id); - let path = db.file_relative_path(file_id); - let root = RelativePathBuf::default(); - let dir_path = path.parent().unwrap_or(&root); - let mod_name = path.file_stem().unwrap_or("unknown"); - let is_dir_owner = is_root || mod_name == "mod"; - - let file_mod = dir_path.join(format!("{}.rs", name)); - let dir_mod = dir_path.join(format!("{}/mod.rs", name)); - let file_dir_mod = dir_path.join(format!("{}/{}.rs", mod_name, name)); - let mut candidates = ArrayVec::<[_; 2]>::new(); - if is_dir_owner { - candidates.push(file_mod.clone()); - candidates.push(dir_mod); - } else { - candidates.push(file_dir_mod.clone()); - }; - let sr = db.source_root(source_root_id); - let points_to = candidates - .into_iter() - .filter_map(|path| sr.files.get(&path)) - .map(|&it| it) - .collect::>(); - let problem = if points_to.is_empty() { - Some(Problem::UnresolvedModule { - candidate: if is_dir_owner { file_mod } else { file_dir_mod }, - }) - } else { - None - }; - (points_to, problem) -} diff --git a/crates/ra_hir/src/nameres/crate_def_map/raw.rs b/crates/ra_hir/src/nameres/crate_def_map/raw.rs deleted file mode 100644 index dca86e394..000000000 --- a/crates/ra_hir/src/nameres/crate_def_map/raw.rs +++ /dev/null @@ -1,293 +0,0 @@ -use std::{ - sync::Arc, - ops::Index, -}; - -use test_utils::tested_by; -use ra_db::FileId; -use ra_arena::{Arena, impl_arena_id, RawId}; -use ra_syntax::{ - AstNode, SourceFile, - ast::{self, NameOwner, AttrsOwner}, -}; - -use crate::{ - PersistentHirDatabase, Name, AsName, Path, HirFileId, - ids::{SourceFileItemId, SourceFileItems}, - nameres::lower::ImportSourceMap, -}; - -#[derive(Debug, Default, PartialEq, Eq)] -pub struct RawItems { - modules: Arena, - imports: Arena, - defs: Arena, - macros: Arena, - /// items for top-level module - items: Vec, -} - -impl RawItems { - pub(crate) fn raw_items_query( - db: &impl PersistentHirDatabase, - file_id: FileId, - ) -> Arc { - db.raw_items_with_source_map(file_id).0 - } - - pub(crate) fn raw_items_with_source_map_query( - db: &impl PersistentHirDatabase, - file_id: FileId, - ) -> (Arc, Arc) { - let mut collector = RawItemsCollector { - raw_items: RawItems::default(), - source_file_items: db.file_items(file_id.into()), - source_map: ImportSourceMap::default(), - }; - let source_file = db.parse(file_id); - collector.process_module(None, &*source_file); - (Arc::new(collector.raw_items), Arc::new(collector.source_map)) - } - - pub(crate) fn items(&self) -> &[RawItem] { - &self.items - } - - // We can't use queries during name resolution for fear of cycles, so this - // is a query-less variant of the above function. - pub(crate) fn from_source_file(source_file: &SourceFile, file_id: HirFileId) -> RawItems { - let source_file_items = SourceFileItems::from_source_file(source_file, file_id); - let mut collector = RawItemsCollector { - raw_items: RawItems::default(), - source_file_items: Arc::new(source_file_items), - source_map: ImportSourceMap::default(), - }; - collector.process_module(None, &*source_file); - collector.raw_items - } -} - -impl Index for RawItems { - type Output = ModuleData; - fn index(&self, idx: Module) -> &ModuleData { - &self.modules[idx] - } -} - -impl Index for RawItems { - type Output = ImportData; - fn index(&self, idx: ImportId) -> &ImportData { - &self.imports[idx] - } -} - -impl Index for RawItems { - type Output = DefData; - fn index(&self, idx: Def) -> &DefData { - &self.defs[idx] - } -} - -impl Index for RawItems { - type Output = MacroData; - fn index(&self, idx: Macro) -> &MacroData { - &self.macros[idx] - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(crate) enum RawItem { - Module(Module), - Import(ImportId), - Def(Def), - Macro(Macro), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct Module(RawId); -impl_arena_id!(Module); - -#[derive(Debug, PartialEq, Eq)] -pub(crate) enum ModuleData { - Declaration { name: Name, source_item_id: SourceFileItemId }, - Definition { name: Name, source_item_id: SourceFileItemId, items: Vec }, -} - -pub(crate) use crate::nameres::lower::ImportId; -pub(super) use crate::nameres::lower::ImportData; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct Def(RawId); -impl_arena_id!(Def); - -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct DefData { - pub(crate) source_item_id: SourceFileItemId, - pub(crate) name: Name, - pub(crate) kind: DefKind, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub(crate) enum DefKind { - Function, - Struct, - Enum, - Const, - Static, - Trait, - TypeAlias, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) struct Macro(RawId); -impl_arena_id!(Macro); - -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct MacroData { - pub(crate) source_item_id: SourceFileItemId, - pub(crate) path: Path, - pub(crate) name: Option, - pub(crate) arg: tt::Subtree, - pub(crate) export: bool, -} - -struct RawItemsCollector { - raw_items: RawItems, - source_file_items: Arc, - source_map: ImportSourceMap, -} - -impl RawItemsCollector { - fn process_module(&mut self, current_module: Option, body: &impl ast::ModuleItemOwner) { - for item_or_macro in body.items_with_macros() { - match item_or_macro { - ast::ItemOrMacro::Macro(m) => self.add_macro(current_module, m), - ast::ItemOrMacro::Item(item) => self.add_item(current_module, item), - } - } - } - - fn add_item(&mut self, current_module: Option, item: &ast::ModuleItem) { - let (kind, name) = match item.kind() { - ast::ModuleItemKind::Module(module) => { - self.add_module(current_module, module); - return; - } - ast::ModuleItemKind::UseItem(use_item) => { - self.add_use_item(current_module, use_item); - return; - } - ast::ModuleItemKind::ExternCrateItem(extern_crate) => { - self.add_extern_crate_item(current_module, extern_crate); - return; - } - ast::ModuleItemKind::ImplBlock(_) => { - // impls don't participate in name resolution - return; - } - ast::ModuleItemKind::StructDef(it) => (DefKind::Struct, it.name()), - ast::ModuleItemKind::EnumDef(it) => (DefKind::Enum, it.name()), - ast::ModuleItemKind::FnDef(it) => (DefKind::Function, it.name()), - ast::ModuleItemKind::TraitDef(it) => (DefKind::Trait, it.name()), - ast::ModuleItemKind::TypeAliasDef(it) => (DefKind::TypeAlias, it.name()), - ast::ModuleItemKind::ConstDef(it) => (DefKind::Const, it.name()), - ast::ModuleItemKind::StaticDef(it) => (DefKind::Static, it.name()), - }; - if let Some(name) = name { - let name = name.as_name(); - let source_item_id = self.source_file_items.id_of_unchecked(item.syntax()); - let def = self.raw_items.defs.alloc(DefData { name, kind, source_item_id }); - self.push_item(current_module, RawItem::Def(def)) - } - } - - fn add_module(&mut self, current_module: Option, module: &ast::Module) { - let name = match module.name() { - Some(it) => it.as_name(), - None => return, - }; - let source_item_id = self.source_file_items.id_of_unchecked(module.syntax()); - if module.has_semi() { - let item = - self.raw_items.modules.alloc(ModuleData::Declaration { name, source_item_id }); - self.push_item(current_module, RawItem::Module(item)); - return; - } - - if let Some(item_list) = module.item_list() { - let item = self.raw_items.modules.alloc(ModuleData::Definition { - name, - source_item_id, - items: Vec::new(), - }); - self.process_module(Some(item), item_list); - self.push_item(current_module, RawItem::Module(item)); - return; - } - tested_by!(name_res_works_for_broken_modules); - } - - fn add_use_item(&mut self, current_module: Option, use_item: &ast::UseItem) { - 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 { - path, - alias, - is_glob: segment.is_none(), - is_prelude, - is_extern_crate: false, - }); - if let Some(segment) = segment { - self.source_map.insert(import, segment) - } - self.push_item(current_module, RawItem::Import(import)) - }) - } - - fn add_extern_crate_item( - &mut self, - current_module: Option, - extern_crate: &ast::ExternCrateItem, - ) { - if let Some(name_ref) = extern_crate.name_ref() { - let path = Path::from_name_ref(name_ref); - let alias = extern_crate.alias().and_then(|a| a.name()).map(AsName::as_name); - let import = self.raw_items.imports.alloc(ImportData { - path, - alias, - is_glob: false, - is_prelude: false, - is_extern_crate: true, - }); - self.push_item(current_module, RawItem::Import(import)) - } - } - - fn add_macro(&mut self, current_module: Option, m: &ast::MacroCall) { - let (path, arg) = match ( - m.path().and_then(Path::from_ast), - m.token_tree().and_then(mbe::ast_to_token_tree), - ) { - (Some(path), Some((token_tree, _token_map))) => (path, token_tree), - _ => return, - }; - - let name = m.name().map(|it| it.as_name()); - let source_item_id = self.source_file_items.id_of_unchecked(m.syntax()); - 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)); - } - - fn push_item(&mut self, current_module: Option, item: RawItem) { - match current_module { - Some(module) => match &mut self.raw_items.modules[module] { - ModuleData::Definition { items, .. } => items, - ModuleData::Declaration { .. } => unreachable!(), - }, - None => &mut self.raw_items.items, - } - .push(item) - } -} diff --git a/crates/ra_hir/src/nameres/crate_def_map/tests.rs b/crates/ra_hir/src/nameres/crate_def_map/tests.rs deleted file mode 100644 index 36c1d74ce..000000000 --- a/crates/ra_hir/src/nameres/crate_def_map/tests.rs +++ /dev/null @@ -1,528 +0,0 @@ -mod macros; -mod globs; -mod incremental; - -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 }; - db.crate_def_map(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; - use crate::foo::bar::E; - use self::E::V; - - //- /foo/mod.rs - pub mod bar; - fn f() {} - - //- /foo/bar.rs - pub struct Baz; - enum E { V } - ", - ); - assert_snapshot_matches!(map, @r###" -crate -V: t v -E: t -foo: t -S: t v - -crate::foo -bar: t -f: v - -crate::foo::bar -Baz: t v -E: t -"### - ) -} - -#[test] -fn use_as() { - let map = def_map( - " - //- /lib.rs - mod foo; - - use crate::foo::Baz as Foo; - - //- /foo/mod.rs - pub struct Baz; - ", - ); - assert_snapshot_matches!(map, - @r###" -crate -Foo: t v -foo: t - -crate::foo -Baz: t v -"### - ); -} - -#[test] -fn use_trees() { - let map = def_map( - " - //- /lib.rs - mod foo; - - use crate::foo::bar::{Baz, Quux}; - - //- /foo/mod.rs - pub mod bar; - - //- /foo/bar.rs - pub struct Baz; - pub enum Quux {}; - ", - ); - assert_snapshot_matches!(map, - @r###" -crate -Quux: t -Baz: t v -foo: t - -crate::foo -bar: t - -crate::foo::bar -Quux: t -Baz: t v -"### - ); -} - -#[test] -fn re_exports() { - let map = def_map( - " - //- /lib.rs - mod foo; - - use self::foo::Baz; - - //- /foo/mod.rs - pub mod bar; - - pub use self::bar::Baz; - - //- /foo/bar.rs - pub struct Baz; - ", - ); - assert_snapshot_matches!(map, - @r###" -crate -Baz: t v -foo: t - -crate::foo -bar: t -Baz: t v - -crate::foo::bar -Baz: 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 can_import_enum_variant() { - covers!(can_import_enum_variant); - 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 edition_2015_imports() { - let map = def_map_with_crate_graph( - " - //- /main.rs - mod foo; - mod bar; - - //- /bar.rs - struct Bar; - - //- /foo.rs - use bar::Bar; - use other_crate::FromLib; - - //- /lib.rs - struct FromLib; - ", - crate_graph! { - "main": ("/main.rs", "2015", ["other_crate"]), - "other_crate": ("/lib.rs", "2018", []), - }, - ); - - assert_snapshot_matches!(map, - @r###" -crate -bar: t -foo: t - -crate::bar -Bar: t v - -crate::foo -FromLib: t v -Bar: t v -"### - ); -} - -#[test] -fn module_resolution_works_for_non_standard_filenames() { - let map = def_map_with_crate_graph( - " - //- /my_library.rs - mod foo; - use self::foo::Bar; - - //- /foo/mod.rs - pub struct Bar; - ", - crate_graph! { - "my_library": ("/my_library.rs", []), - }, - ); - - assert_snapshot_matches!(map, - @r###" -crate -Bar: t v -foo: t - -crate::foo -Bar: t v -"### - ); -} - -#[test] -fn name_res_works_for_broken_modules() { - covers!(name_res_works_for_broken_modules); - let map = def_map( - " - //- /lib.rs - mod foo // no `;`, no body - - use self::foo::Baz; - - //- /foo/mod.rs - pub mod bar; - - pub use self::bar::Baz; - - //- /foo/bar.rs - pub struct Baz; - ", - ); - assert_snapshot_matches!(map, - @r###" -crate -Baz: _ -"### - ); -} - -#[test] -fn item_map_using_self() { - let map = def_map( - " - //- /lib.rs - mod foo; - use crate::foo::bar::Baz::{self}; - //- /foo/mod.rs - pub mod bar; - //- /foo/bar.rs - pub struct Baz; - ", - ); - assert_snapshot_matches!(map, - @r###" -crate -Baz: t v -foo: t - -crate::foo -bar: t - -crate::foo::bar -Baz: t v -"### - ); -} - -#[test] -fn item_map_across_crates() { - let map = def_map_with_crate_graph( - " - //- /main.rs - use test_crate::Baz; - - //- /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 extern_crate_rename() { - let map = def_map_with_crate_graph( - " - //- /main.rs - extern crate alloc as alloc_crate; - - mod alloc; - mod sync; - - //- /sync.rs - use alloc_crate::Arc; - - //- /lib.rs - struct Arc; - ", - crate_graph! { - "main": ("/main.rs", ["alloc"]), - "alloc": ("/lib.rs", []), - }, - ); - - assert_snapshot_matches!(map, - @r###" -crate -Arc: t v -"### - ); -} - -#[test] -fn extern_crate_rename_2015_edition() { - let map = def_map_with_crate_graph( - " - //- /main.rs - extern crate alloc as alloc_crate; - - mod alloc; - mod sync; - - //- /sync.rs - use alloc_crate::Arc; - - //- /lib.rs - struct Arc; - ", - crate_graph! { - "main": ("/main.rs", "2015", ["alloc"]), - "alloc": ("/lib.rs", []), - }, - ); - - assert_snapshot_matches!(map, - @r###" -crate -Arc: t v -"### - ); -} - -#[test] -fn import_across_source_roots() { - let map = def_map_with_crate_graph( - " - //- /lib.rs - pub mod a { - pub mod b { - pub struct C; - } - } - - //- root /main/ - - //- /main/main.rs - use test_crate::a::b::C; - ", - crate_graph! { - "main": ("/main/main.rs", ["test_crate"]), - "test_crate": ("/lib.rs", []), - }, - ); - - assert_snapshot_matches!(map, - @r###" -crate -C: t v -"### - ); -} - -#[test] -fn reexport_across_crates() { - let map = def_map_with_crate_graph( - " - //- /main.rs - use test_crate::Baz; - - //- /lib.rs - pub use foo::Baz; - - mod foo; - - //- /foo.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 values_dont_shadow_extern_crates() { - let map = def_map_with_crate_graph( - " - //- /main.rs - fn foo() {} - use foo::Bar; - - //- /foo/lib.rs - pub struct Bar; - ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/foo/lib.rs", []), - }, - ); - - assert_snapshot_matches!(map, - @r###" -crate -Bar: t v -foo: v -"### - ); -} diff --git a/crates/ra_hir/src/nameres/crate_def_map/tests/globs.rs b/crates/ra_hir/src/nameres/crate_def_map/tests/globs.rs deleted file mode 100644 index 6e50c7ff6..000000000 --- a/crates/ra_hir/src/nameres/crate_def_map/tests/globs.rs +++ /dev/null @@ -1,118 +0,0 @@ -use super::*; - -#[test] -fn glob_1() { - let map = def_map( - " - //- /lib.rs - mod foo; - use foo::*; - - //- /foo/mod.rs - pub mod bar; - pub use self::bar::Baz; - pub struct Foo; - - //- /foo/bar.rs - pub struct Baz; - ", - ); - assert_snapshot_matches!(map, @r###" -crate -bar: t -Foo: t v -Baz: t v -foo: t - -crate::foo -bar: t -Foo: t v -Baz: t v - -crate::foo::bar -Baz: t v -"### - ); -} - -#[test] -fn glob_2() { - let map = def_map( - " - //- /lib.rs - mod foo; - use foo::*; - - //- /foo/mod.rs - pub mod bar; - pub use self::bar::*; - pub struct Foo; - - //- /foo/bar.rs - pub struct Baz; - pub use super::*; - ", - ); - assert_snapshot_matches!(map, @r###" -crate -bar: t -Foo: t v -Baz: t v -foo: t - -crate::foo -bar: t -Foo: t v -Baz: t v - -crate::foo::bar -bar: t -Foo: 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 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/crate_def_map/tests/incremental.rs b/crates/ra_hir/src/nameres/crate_def_map/tests/incremental.rs deleted file mode 100644 index 698781923..000000000 --- a/crates/ra_hir/src/nameres/crate_def_map/tests/incremental.rs +++ /dev/null @@ -1,123 +0,0 @@ -use super::*; - -use std::sync::Arc; - -use ra_db::SourceDatabase; - -fn check_def_map_is_not_recomputed(initial: &str, file_change: &str) { - let (mut db, pos) = MockDatabase::with_position(initial); - let crate_id = db.crate_graph().iter().next().unwrap(); - let krate = Crate { crate_id }; - { - let events = db.log_executed(|| { - db.crate_def_map(krate); - }); - assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) - } - db.set_file_text(pos.file_id, Arc::new(file_change.to_string())); - - { - let events = db.log_executed(|| { - db.crate_def_map(krate); - }); - assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) - } -} - -#[test] -fn typing_inside_a_function_should_not_invalidate_def_map() { - check_def_map_is_not_recomputed( - " - //- /lib.rs - mod foo;<|> - - use crate::foo::bar::Baz; - - fn foo() -> i32 { - 1 + 1 - } - //- /foo/mod.rs - pub mod bar; - - //- /foo/bar.rs - pub struct Baz; - ", - " - mod foo; - - use crate::foo::bar::Baz; - - fn foo() -> i32 { 92 } - ", - ); -} - -#[test] -fn adding_inner_items_should_not_invalidate_def_map() { - check_def_map_is_not_recomputed( - " - //- /lib.rs - struct S { a: i32} - enum E { A } - trait T { - fn a() {} - } - mod foo;<|> - impl S { - fn a() {} - } - use crate::foo::bar::Baz; - //- /foo/mod.rs - pub mod bar; - - //- /foo/bar.rs - pub struct Baz; - ", - " - struct S { a: i32, b: () } - enum E { A, B } - trait T { - fn a() {} - fn b() {} - } - mod foo;<|> - impl S { - fn a() {} - fn b() {} - } - use crate::foo::bar::Baz; - ", - ); -} - -// It would be awesome to make this work, but it's unclear how -#[test] -#[ignore] -fn typing_inside_a_function_inside_a_macro_should_not_invalidate_def_map() { - check_def_map_is_not_recomputed( - " - //- /lib.rs - mod foo; - - use crate::foo::bar::Baz; - - //- /foo/mod.rs - pub mod bar; - - //- /foo/bar.rs - <|> - salsa::query_group! { - trait Baz { - fn foo() -> i32 { 1 + 1 } - } - } - ", - " - salsa::query_group! { - trait Baz { - fn foo() -> i32 { 92 } - } - } - ", - ); -} diff --git a/crates/ra_hir/src/nameres/crate_def_map/tests/macros.rs b/crates/ra_hir/src/nameres/crate_def_map/tests/macros.rs deleted file mode 100644 index 8781b026b..000000000 --- a/crates/ra_hir/src/nameres/crate_def_map/tests/macros.rs +++ /dev/null @@ -1,94 +0,0 @@ -use super::*; - -#[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 -nested: t -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 -n1: t - -crate::n1 -n2: t - -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 -bar: t -Foo: t v -Bar: t v - -crate::bar -bar: t -Foo: t v -Bar: t v -"###); -} diff --git a/crates/ra_hir/src/nameres/lower.rs b/crates/ra_hir/src/nameres/lower.rs deleted file mode 100644 index d4c7f2481..000000000 --- a/crates/ra_hir/src/nameres/lower.rs +++ /dev/null @@ -1,40 +0,0 @@ -use ra_syntax::{ - AstNode, SourceFile, TreeArc, AstPtr, - ast, -}; -use ra_arena::{RawId, impl_arena_id, map::ArenaMap}; - -use crate::{Path, ModuleSource, Name}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ImportId(RawId); -impl_arena_id!(ImportId); - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ImportData { - pub(super) path: Path, - pub(super) alias: Option, - pub(super) is_glob: bool, - pub(super) is_prelude: bool, - pub(super) is_extern_crate: bool, -} - -#[derive(Debug, Default, PartialEq, Eq)] -pub struct ImportSourceMap { - map: ArenaMap>, -} - -impl ImportSourceMap { - pub(crate) fn insert(&mut self, import: ImportId, segment: &ast::PathSegment) { - self.map.insert(import, AstPtr::new(segment)) - } - - pub fn get(&self, source: &ModuleSource, import: ImportId) -> TreeArc { - let file = match source { - ModuleSource::SourceFile(file) => &*file, - ModuleSource::Module(m) => m.syntax().ancestors().find_map(SourceFile::cast).unwrap(), - }; - - self.map[import].to_node(file).to_owned() - } -} diff --git a/crates/ra_hir/src/nameres/per_ns.rs b/crates/ra_hir/src/nameres/per_ns.rs new file mode 100644 index 000000000..c40a3ff9d --- /dev/null +++ b/crates/ra_hir/src/nameres/per_ns.rs @@ -0,0 +1,78 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Namespace { + Types, + Values, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct PerNs { + pub types: Option, + pub values: Option, +} + +impl Default for PerNs { + fn default() -> Self { + PerNs { types: None, values: None } + } +} + +impl PerNs { + pub fn none() -> PerNs { + PerNs { types: None, values: None } + } + + pub fn values(t: T) -> PerNs { + PerNs { types: None, values: Some(t) } + } + + pub fn types(t: T) -> PerNs { + PerNs { types: Some(t), values: None } + } + + pub fn both(types: T, values: T) -> PerNs { + PerNs { types: Some(types), values: Some(values) } + } + + pub fn is_none(&self) -> bool { + self.types.is_none() && self.values.is_none() + } + + pub fn is_both(&self) -> bool { + self.types.is_some() && self.values.is_some() + } + + pub fn take(self, namespace: Namespace) -> Option { + match namespace { + Namespace::Types => self.types, + Namespace::Values => self.values, + } + } + + pub fn take_types(self) -> Option { + self.take(Namespace::Types) + } + + pub fn take_values(self) -> Option { + self.take(Namespace::Values) + } + + pub fn get(&self, namespace: Namespace) -> Option<&T> { + self.as_ref().take(namespace) + } + + pub fn as_ref(&self) -> PerNs<&T> { + PerNs { types: self.types.as_ref(), values: self.values.as_ref() } + } + + pub fn or(self, other: PerNs) -> PerNs { + PerNs { types: self.types.or(other.types), values: self.values.or(other.values) } + } + + pub fn and_then(self, f: impl Fn(T) -> Option) -> PerNs { + PerNs { types: self.types.and_then(&f), values: self.values.and_then(&f) } + } + + pub fn map(self, f: impl Fn(T) -> U) -> PerNs { + PerNs { types: self.types.map(&f), values: self.values.map(&f) } + } +} diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir/src/nameres/raw.rs new file mode 100644 index 000000000..3226bbf0d --- /dev/null +++ b/crates/ra_hir/src/nameres/raw.rs @@ -0,0 +1,322 @@ +use std::{ + sync::Arc, + ops::Index, +}; + +use test_utils::tested_by; +use ra_db::FileId; +use ra_arena::{Arena, impl_arena_id, RawId, map::ArenaMap}; +use ra_syntax::{ + AstNode, SourceFile, AstPtr, TreeArc, + ast::{self, NameOwner, AttrsOwner}, +}; + +use crate::{ + PersistentHirDatabase, Name, AsName, Path, HirFileId, ModuleSource, + ids::{SourceFileItemId, SourceFileItems}, +}; + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct RawItems { + modules: Arena, + imports: Arena, + defs: Arena, + macros: Arena, + /// items for top-level module + items: Vec, +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct ImportSourceMap { + map: ArenaMap>, +} + +impl ImportSourceMap { + pub(crate) fn insert(&mut self, import: ImportId, segment: &ast::PathSegment) { + self.map.insert(import, AstPtr::new(segment)) + } + + pub fn get(&self, source: &ModuleSource, import: ImportId) -> TreeArc { + let file = match source { + ModuleSource::SourceFile(file) => &*file, + ModuleSource::Module(m) => m.syntax().ancestors().find_map(SourceFile::cast).unwrap(), + }; + + self.map[import].to_node(file).to_owned() + } +} + +impl RawItems { + pub(crate) fn raw_items_query( + db: &impl PersistentHirDatabase, + file_id: FileId, + ) -> Arc { + db.raw_items_with_source_map(file_id).0 + } + + pub(crate) fn raw_items_with_source_map_query( + db: &impl PersistentHirDatabase, + file_id: FileId, + ) -> (Arc, Arc) { + let mut collector = RawItemsCollector { + raw_items: RawItems::default(), + source_file_items: db.file_items(file_id.into()), + source_map: ImportSourceMap::default(), + }; + let source_file = db.parse(file_id); + collector.process_module(None, &*source_file); + (Arc::new(collector.raw_items), Arc::new(collector.source_map)) + } + + pub(crate) fn items(&self) -> &[RawItem] { + &self.items + } + + // We can't use queries during name resolution for fear of cycles, so this + // is a query-less variant of the above function. + pub(crate) fn from_source_file(source_file: &SourceFile, file_id: HirFileId) -> RawItems { + let source_file_items = SourceFileItems::from_source_file(source_file, file_id); + let mut collector = RawItemsCollector { + raw_items: RawItems::default(), + source_file_items: Arc::new(source_file_items), + source_map: ImportSourceMap::default(), + }; + collector.process_module(None, &*source_file); + collector.raw_items + } +} + +impl Index for RawItems { + type Output = ModuleData; + fn index(&self, idx: Module) -> &ModuleData { + &self.modules[idx] + } +} + +impl Index for RawItems { + type Output = ImportData; + fn index(&self, idx: ImportId) -> &ImportData { + &self.imports[idx] + } +} + +impl Index for RawItems { + type Output = DefData; + fn index(&self, idx: Def) -> &DefData { + &self.defs[idx] + } +} + +impl Index for RawItems { + type Output = MacroData; + fn index(&self, idx: Macro) -> &MacroData { + &self.macros[idx] + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub(crate) enum RawItem { + Module(Module), + Import(ImportId), + Def(Def), + Macro(Macro), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Module(RawId); +impl_arena_id!(Module); + +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum ModuleData { + Declaration { name: Name, source_item_id: SourceFileItemId }, + Definition { name: Name, source_item_id: SourceFileItemId, items: Vec }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ImportId(RawId); +impl_arena_id!(ImportId); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ImportData { + pub(crate) path: Path, + pub(crate) alias: Option, + pub(crate) is_glob: bool, + pub(crate) is_prelude: bool, + pub(crate) is_extern_crate: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Def(RawId); +impl_arena_id!(Def); + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct DefData { + pub(crate) source_item_id: SourceFileItemId, + pub(crate) name: Name, + pub(crate) kind: DefKind, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub(crate) enum DefKind { + Function, + Struct, + Enum, + Const, + Static, + Trait, + TypeAlias, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Macro(RawId); +impl_arena_id!(Macro); + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct MacroData { + pub(crate) source_item_id: SourceFileItemId, + pub(crate) path: Path, + pub(crate) name: Option, + pub(crate) arg: tt::Subtree, + pub(crate) export: bool, +} + +struct RawItemsCollector { + raw_items: RawItems, + source_file_items: Arc, + source_map: ImportSourceMap, +} + +impl RawItemsCollector { + fn process_module(&mut self, current_module: Option, body: &impl ast::ModuleItemOwner) { + for item_or_macro in body.items_with_macros() { + match item_or_macro { + ast::ItemOrMacro::Macro(m) => self.add_macro(current_module, m), + ast::ItemOrMacro::Item(item) => self.add_item(current_module, item), + } + } + } + + fn add_item(&mut self, current_module: Option, item: &ast::ModuleItem) { + let (kind, name) = match item.kind() { + ast::ModuleItemKind::Module(module) => { + self.add_module(current_module, module); + return; + } + ast::ModuleItemKind::UseItem(use_item) => { + self.add_use_item(current_module, use_item); + return; + } + ast::ModuleItemKind::ExternCrateItem(extern_crate) => { + self.add_extern_crate_item(current_module, extern_crate); + return; + } + ast::ModuleItemKind::ImplBlock(_) => { + // impls don't participate in name resolution + return; + } + ast::ModuleItemKind::StructDef(it) => (DefKind::Struct, it.name()), + ast::ModuleItemKind::EnumDef(it) => (DefKind::Enum, it.name()), + ast::ModuleItemKind::FnDef(it) => (DefKind::Function, it.name()), + ast::ModuleItemKind::TraitDef(it) => (DefKind::Trait, it.name()), + ast::ModuleItemKind::TypeAliasDef(it) => (DefKind::TypeAlias, it.name()), + ast::ModuleItemKind::ConstDef(it) => (DefKind::Const, it.name()), + ast::ModuleItemKind::StaticDef(it) => (DefKind::Static, it.name()), + }; + if let Some(name) = name { + let name = name.as_name(); + let source_item_id = self.source_file_items.id_of_unchecked(item.syntax()); + let def = self.raw_items.defs.alloc(DefData { name, kind, source_item_id }); + self.push_item(current_module, RawItem::Def(def)) + } + } + + fn add_module(&mut self, current_module: Option, module: &ast::Module) { + let name = match module.name() { + Some(it) => it.as_name(), + None => return, + }; + let source_item_id = self.source_file_items.id_of_unchecked(module.syntax()); + if module.has_semi() { + let item = + self.raw_items.modules.alloc(ModuleData::Declaration { name, source_item_id }); + self.push_item(current_module, RawItem::Module(item)); + return; + } + + if let Some(item_list) = module.item_list() { + let item = self.raw_items.modules.alloc(ModuleData::Definition { + name, + source_item_id, + items: Vec::new(), + }); + self.process_module(Some(item), item_list); + self.push_item(current_module, RawItem::Module(item)); + return; + } + tested_by!(name_res_works_for_broken_modules); + } + + fn add_use_item(&mut self, current_module: Option, use_item: &ast::UseItem) { + 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 { + path, + alias, + is_glob: segment.is_none(), + is_prelude, + is_extern_crate: false, + }); + if let Some(segment) = segment { + self.source_map.insert(import, segment) + } + self.push_item(current_module, RawItem::Import(import)) + }) + } + + fn add_extern_crate_item( + &mut self, + current_module: Option, + extern_crate: &ast::ExternCrateItem, + ) { + if let Some(name_ref) = extern_crate.name_ref() { + let path = Path::from_name_ref(name_ref); + let alias = extern_crate.alias().and_then(|a| a.name()).map(AsName::as_name); + let import = self.raw_items.imports.alloc(ImportData { + path, + alias, + is_glob: false, + is_prelude: false, + is_extern_crate: true, + }); + self.push_item(current_module, RawItem::Import(import)) + } + } + + fn add_macro(&mut self, current_module: Option, m: &ast::MacroCall) { + let (path, arg) = match ( + m.path().and_then(Path::from_ast), + m.token_tree().and_then(mbe::ast_to_token_tree), + ) { + (Some(path), Some((token_tree, _token_map))) => (path, token_tree), + _ => return, + }; + + let name = m.name().map(|it| it.as_name()); + let source_item_id = self.source_file_items.id_of_unchecked(m.syntax()); + 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)); + } + + fn push_item(&mut self, current_module: Option, item: RawItem) { + match current_module { + Some(module) => match &mut self.raw_items.modules[module] { + ModuleData::Definition { items, .. } => items, + ModuleData::Declaration { .. } => unreachable!(), + }, + None => &mut self.raw_items.items, + } + .push(item) + } +} diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs new file mode 100644 index 000000000..36c1d74ce --- /dev/null +++ b/crates/ra_hir/src/nameres/tests.rs @@ -0,0 +1,528 @@ +mod macros; +mod globs; +mod incremental; + +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 }; + db.crate_def_map(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; + use crate::foo::bar::E; + use self::E::V; + + //- /foo/mod.rs + pub mod bar; + fn f() {} + + //- /foo/bar.rs + pub struct Baz; + enum E { V } + ", + ); + assert_snapshot_matches!(map, @r###" +crate +V: t v +E: t +foo: t +S: t v + +crate::foo +bar: t +f: v + +crate::foo::bar +Baz: t v +E: t +"### + ) +} + +#[test] +fn use_as() { + let map = def_map( + " + //- /lib.rs + mod foo; + + use crate::foo::Baz as Foo; + + //- /foo/mod.rs + pub struct Baz; + ", + ); + assert_snapshot_matches!(map, + @r###" +crate +Foo: t v +foo: t + +crate::foo +Baz: t v +"### + ); +} + +#[test] +fn use_trees() { + let map = def_map( + " + //- /lib.rs + mod foo; + + use crate::foo::bar::{Baz, Quux}; + + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + pub struct Baz; + pub enum Quux {}; + ", + ); + assert_snapshot_matches!(map, + @r###" +crate +Quux: t +Baz: t v +foo: t + +crate::foo +bar: t + +crate::foo::bar +Quux: t +Baz: t v +"### + ); +} + +#[test] +fn re_exports() { + let map = def_map( + " + //- /lib.rs + mod foo; + + use self::foo::Baz; + + //- /foo/mod.rs + pub mod bar; + + pub use self::bar::Baz; + + //- /foo/bar.rs + pub struct Baz; + ", + ); + assert_snapshot_matches!(map, + @r###" +crate +Baz: t v +foo: t + +crate::foo +bar: t +Baz: t v + +crate::foo::bar +Baz: 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 can_import_enum_variant() { + covers!(can_import_enum_variant); + 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 edition_2015_imports() { + let map = def_map_with_crate_graph( + " + //- /main.rs + mod foo; + mod bar; + + //- /bar.rs + struct Bar; + + //- /foo.rs + use bar::Bar; + use other_crate::FromLib; + + //- /lib.rs + struct FromLib; + ", + crate_graph! { + "main": ("/main.rs", "2015", ["other_crate"]), + "other_crate": ("/lib.rs", "2018", []), + }, + ); + + assert_snapshot_matches!(map, + @r###" +crate +bar: t +foo: t + +crate::bar +Bar: t v + +crate::foo +FromLib: t v +Bar: t v +"### + ); +} + +#[test] +fn module_resolution_works_for_non_standard_filenames() { + let map = def_map_with_crate_graph( + " + //- /my_library.rs + mod foo; + use self::foo::Bar; + + //- /foo/mod.rs + pub struct Bar; + ", + crate_graph! { + "my_library": ("/my_library.rs", []), + }, + ); + + assert_snapshot_matches!(map, + @r###" +crate +Bar: t v +foo: t + +crate::foo +Bar: t v +"### + ); +} + +#[test] +fn name_res_works_for_broken_modules() { + covers!(name_res_works_for_broken_modules); + let map = def_map( + " + //- /lib.rs + mod foo // no `;`, no body + + use self::foo::Baz; + + //- /foo/mod.rs + pub mod bar; + + pub use self::bar::Baz; + + //- /foo/bar.rs + pub struct Baz; + ", + ); + assert_snapshot_matches!(map, + @r###" +crate +Baz: _ +"### + ); +} + +#[test] +fn item_map_using_self() { + let map = def_map( + " + //- /lib.rs + mod foo; + use crate::foo::bar::Baz::{self}; + //- /foo/mod.rs + pub mod bar; + //- /foo/bar.rs + pub struct Baz; + ", + ); + assert_snapshot_matches!(map, + @r###" +crate +Baz: t v +foo: t + +crate::foo +bar: t + +crate::foo::bar +Baz: t v +"### + ); +} + +#[test] +fn item_map_across_crates() { + let map = def_map_with_crate_graph( + " + //- /main.rs + use test_crate::Baz; + + //- /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 extern_crate_rename() { + let map = def_map_with_crate_graph( + " + //- /main.rs + extern crate alloc as alloc_crate; + + mod alloc; + mod sync; + + //- /sync.rs + use alloc_crate::Arc; + + //- /lib.rs + struct Arc; + ", + crate_graph! { + "main": ("/main.rs", ["alloc"]), + "alloc": ("/lib.rs", []), + }, + ); + + assert_snapshot_matches!(map, + @r###" +crate +Arc: t v +"### + ); +} + +#[test] +fn extern_crate_rename_2015_edition() { + let map = def_map_with_crate_graph( + " + //- /main.rs + extern crate alloc as alloc_crate; + + mod alloc; + mod sync; + + //- /sync.rs + use alloc_crate::Arc; + + //- /lib.rs + struct Arc; + ", + crate_graph! { + "main": ("/main.rs", "2015", ["alloc"]), + "alloc": ("/lib.rs", []), + }, + ); + + assert_snapshot_matches!(map, + @r###" +crate +Arc: t v +"### + ); +} + +#[test] +fn import_across_source_roots() { + let map = def_map_with_crate_graph( + " + //- /lib.rs + pub mod a { + pub mod b { + pub struct C; + } + } + + //- root /main/ + + //- /main/main.rs + use test_crate::a::b::C; + ", + crate_graph! { + "main": ("/main/main.rs", ["test_crate"]), + "test_crate": ("/lib.rs", []), + }, + ); + + assert_snapshot_matches!(map, + @r###" +crate +C: t v +"### + ); +} + +#[test] +fn reexport_across_crates() { + let map = def_map_with_crate_graph( + " + //- /main.rs + use test_crate::Baz; + + //- /lib.rs + pub use foo::Baz; + + mod foo; + + //- /foo.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 values_dont_shadow_extern_crates() { + let map = def_map_with_crate_graph( + " + //- /main.rs + fn foo() {} + use foo::Bar; + + //- /foo/lib.rs + pub struct Bar; + ", + crate_graph! { + "main": ("/main.rs", ["foo"]), + "foo": ("/foo/lib.rs", []), + }, + ); + + assert_snapshot_matches!(map, + @r###" +crate +Bar: t v +foo: v +"### + ); +} diff --git a/crates/ra_hir/src/nameres/tests/globs.rs b/crates/ra_hir/src/nameres/tests/globs.rs new file mode 100644 index 000000000..6e50c7ff6 --- /dev/null +++ b/crates/ra_hir/src/nameres/tests/globs.rs @@ -0,0 +1,118 @@ +use super::*; + +#[test] +fn glob_1() { + let map = def_map( + " + //- /lib.rs + mod foo; + use foo::*; + + //- /foo/mod.rs + pub mod bar; + pub use self::bar::Baz; + pub struct Foo; + + //- /foo/bar.rs + pub struct Baz; + ", + ); + assert_snapshot_matches!(map, @r###" +crate +bar: t +Foo: t v +Baz: t v +foo: t + +crate::foo +bar: t +Foo: t v +Baz: t v + +crate::foo::bar +Baz: t v +"### + ); +} + +#[test] +fn glob_2() { + let map = def_map( + " + //- /lib.rs + mod foo; + use foo::*; + + //- /foo/mod.rs + pub mod bar; + pub use self::bar::*; + pub struct Foo; + + //- /foo/bar.rs + pub struct Baz; + pub use super::*; + ", + ); + assert_snapshot_matches!(map, @r###" +crate +bar: t +Foo: t v +Baz: t v +foo: t + +crate::foo +bar: t +Foo: t v +Baz: t v + +crate::foo::bar +bar: t +Foo: 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 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/tests/incremental.rs b/crates/ra_hir/src/nameres/tests/incremental.rs new file mode 100644 index 000000000..698781923 --- /dev/null +++ b/crates/ra_hir/src/nameres/tests/incremental.rs @@ -0,0 +1,123 @@ +use super::*; + +use std::sync::Arc; + +use ra_db::SourceDatabase; + +fn check_def_map_is_not_recomputed(initial: &str, file_change: &str) { + let (mut db, pos) = MockDatabase::with_position(initial); + let crate_id = db.crate_graph().iter().next().unwrap(); + let krate = Crate { crate_id }; + { + let events = db.log_executed(|| { + db.crate_def_map(krate); + }); + assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } + db.set_file_text(pos.file_id, Arc::new(file_change.to_string())); + + { + let events = db.log_executed(|| { + db.crate_def_map(krate); + }); + assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } +} + +#[test] +fn typing_inside_a_function_should_not_invalidate_def_map() { + check_def_map_is_not_recomputed( + " + //- /lib.rs + mod foo;<|> + + use crate::foo::bar::Baz; + + fn foo() -> i32 { + 1 + 1 + } + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + pub struct Baz; + ", + " + mod foo; + + use crate::foo::bar::Baz; + + fn foo() -> i32 { 92 } + ", + ); +} + +#[test] +fn adding_inner_items_should_not_invalidate_def_map() { + check_def_map_is_not_recomputed( + " + //- /lib.rs + struct S { a: i32} + enum E { A } + trait T { + fn a() {} + } + mod foo;<|> + impl S { + fn a() {} + } + use crate::foo::bar::Baz; + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + pub struct Baz; + ", + " + struct S { a: i32, b: () } + enum E { A, B } + trait T { + fn a() {} + fn b() {} + } + mod foo;<|> + impl S { + fn a() {} + fn b() {} + } + use crate::foo::bar::Baz; + ", + ); +} + +// It would be awesome to make this work, but it's unclear how +#[test] +#[ignore] +fn typing_inside_a_function_inside_a_macro_should_not_invalidate_def_map() { + check_def_map_is_not_recomputed( + " + //- /lib.rs + mod foo; + + use crate::foo::bar::Baz; + + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + <|> + salsa::query_group! { + trait Baz { + fn foo() -> i32 { 1 + 1 } + } + } + ", + " + salsa::query_group! { + trait Baz { + fn foo() -> i32 { 92 } + } + } + ", + ); +} diff --git a/crates/ra_hir/src/nameres/tests/macros.rs b/crates/ra_hir/src/nameres/tests/macros.rs new file mode 100644 index 000000000..8781b026b --- /dev/null +++ b/crates/ra_hir/src/nameres/tests/macros.rs @@ -0,0 +1,94 @@ +use super::*; + +#[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 +nested: t +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 +n1: t + +crate::n1 +n2: t + +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 +bar: t +Foo: t v +Bar: t v + +crate::bar +bar: t +Foo: t v +Bar: t v +"###); +} -- cgit v1.2.3