diff --git a/Cargo.lock b/Cargo.lock index 29f77044e83..889820c9961 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -985,6 +985,7 @@ dependencies = [ "relative-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "salsa 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "test_utils 0.1.0", ] [[package]] @@ -1027,6 +1028,7 @@ dependencies = [ name = "ra_hir_def" version = "0.1.0" dependencies = [ + "insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "ra_arena 0.1.0", diff --git a/crates/ra_db/Cargo.toml b/crates/ra_db/Cargo.toml index 3394ae8ce6c..bf1f7920c58 100644 --- a/crates/ra_db/Cargo.toml +++ b/crates/ra_db/Cargo.toml @@ -12,3 +12,4 @@ rustc-hash = "1.0" ra_syntax = { path = "../ra_syntax" } ra_cfg = { path = "../ra_cfg" } ra_prof = { path = "../ra_prof" } +test_utils = { path = "../test_utils" } diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs new file mode 100644 index 00000000000..f5dd59f8401 --- /dev/null +++ b/crates/ra_db/src/fixture.rs @@ -0,0 +1,186 @@ +//! FIXME: write short doc here + +use std::sync::Arc; + +use ra_cfg::CfgOptions; +use rustc_hash::FxHashMap; +use test_utils::{extract_offset, parse_fixture, CURSOR_MARKER}; + +use crate::{ + CrateGraph, Edition, FileId, FilePosition, RelativePathBuf, SourceDatabaseExt, SourceRoot, + SourceRootId, +}; + +pub const WORKSPACE: SourceRootId = SourceRootId(0); + +pub trait WithFixture: Default + SourceDatabaseExt + 'static { + fn with_single_file(text: &str) -> (Self, FileId) { + let mut db = Self::default(); + let file_id = with_single_file(&mut db, text); + (db, file_id) + } + + fn with_files(fixture: &str) -> Self { + let mut db = Self::default(); + let pos = with_files(&mut db, fixture); + assert!(pos.is_none()); + db + } + + fn with_position(fixture: &str) -> (Self, FilePosition) { + let mut db = Self::default(); + let pos = with_files(&mut db, fixture); + (db, pos.unwrap()) + } +} + +impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {} + +fn with_single_file(db: &mut dyn SourceDatabaseExt, text: &str) -> FileId { + let file_id = FileId(0); + let rel_path: RelativePathBuf = "/main.rs".into(); + + let mut source_root = SourceRoot::default(); + source_root.insert_file(rel_path.clone(), file_id); + + let mut crate_graph = CrateGraph::default(); + crate_graph.add_crate_root(file_id, Edition::Edition2018, CfgOptions::default()); + + db.set_file_text(file_id, Arc::new(text.to_string())); + db.set_file_relative_path(file_id, rel_path); + db.set_file_source_root(file_id, WORKSPACE); + db.set_source_root(WORKSPACE, Arc::new(source_root)); + db.set_crate_graph(Arc::new(crate_graph)); + + file_id +} + +fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosition> { + let fixture = parse_fixture(fixture); + + let mut crate_graph = CrateGraph::default(); + let mut crates = FxHashMap::default(); + let mut crate_deps = Vec::new(); + let mut default_crate_root: Option<FileId> = None; + + let mut source_root = SourceRoot::default(); + let mut source_root_id = WORKSPACE; + let mut source_root_prefix: RelativePathBuf = "/".into(); + let mut file_id = FileId(0); + + let mut file_position = None; + + for entry in fixture.iter() { + let meta = match parse_meta(&entry.meta) { + ParsedMeta::Root { path } => { + let source_root = std::mem::replace(&mut source_root, SourceRoot::default()); + db.set_source_root(source_root_id, Arc::new(source_root)); + source_root_id.0 += 1; + source_root_prefix = path; + continue; + } + ParsedMeta::File(it) => it, + }; + assert!(meta.path.starts_with(&source_root_prefix)); + + if let Some(krate) = meta.krate { + let crate_id = crate_graph.add_crate_root(file_id, meta.edition, meta.cfg); + let prev = crates.insert(krate.clone(), crate_id); + assert!(prev.is_none()); + for dep in meta.deps { + crate_deps.push((krate.clone(), dep)) + } + } else if meta.path == "/main.rs" || meta.path == "/lib.rs" { + assert!(default_crate_root.is_none()); + default_crate_root = Some(file_id); + } + + let text = if entry.text.contains(CURSOR_MARKER) { + let (offset, text) = extract_offset(&entry.text); + assert!(file_position.is_none()); + file_position = Some(FilePosition { file_id, offset }); + text.to_string() + } else { + entry.text.to_string() + }; + + db.set_file_text(file_id, Arc::new(text)); + db.set_file_relative_path(file_id, meta.path.clone()); + db.set_file_source_root(file_id, source_root_id); + source_root.insert_file(meta.path, file_id); + + file_id.0 += 1; + } + + if crates.is_empty() { + let crate_root = default_crate_root.unwrap(); + crate_graph.add_crate_root(crate_root, Edition::Edition2018, CfgOptions::default()); + } else { + for (from, to) in crate_deps { + let from_id = crates[&from]; + let to_id = crates[&to]; + crate_graph.add_dep(from_id, to.into(), to_id).unwrap(); + } + } + + db.set_source_root(source_root_id, Arc::new(source_root)); + db.set_crate_graph(Arc::new(crate_graph)); + + file_position +} + +enum ParsedMeta { + Root { path: RelativePathBuf }, + File(FileMeta), +} + +struct FileMeta { + path: RelativePathBuf, + krate: Option<String>, + deps: Vec<String>, + cfg: CfgOptions, + edition: Edition, +} + +//- /lib.rs crate:foo deps:bar,baz +fn parse_meta(meta: &str) -> ParsedMeta { + let components = meta.split_ascii_whitespace().collect::<Vec<_>>(); + + if components[0] == "root" { + let path: RelativePathBuf = components[1].into(); + assert!(path.starts_with("/") && path.ends_with("/")); + return ParsedMeta::Root { path }; + } + + let path: RelativePathBuf = components[0].into(); + assert!(path.starts_with("/")); + + let mut krate = None; + let mut deps = Vec::new(); + let mut edition = Edition::Edition2018; + let mut cfg = CfgOptions::default(); + for component in components[1..].iter() { + let (key, value) = split1(component, ':').unwrap(); + match key { + "crate" => krate = Some(value.to_string()), + "deps" => deps = value.split(',').map(|it| it.to_string()).collect(), + "edition" => edition = Edition::from_string(&value), + "cfg" => { + for key in value.split(',') { + match split1(key, '=') { + None => cfg.insert_atom(key.into()), + Some((k, v)) => cfg.insert_key_value(k.into(), v.into()), + } + } + } + _ => panic!("bad component: {:?}", component), + } + } + + ParsedMeta::File(FileMeta { path, krate, deps, edition, cfg }) +} + +fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> { + let idx = haystack.find(delim)?; + Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..])) +} diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs index eafa95921cd..7c8dac1d38d 100644 --- a/crates/ra_db/src/input.rs +++ b/crates/ra_db/src/input.rs @@ -97,6 +97,7 @@ pub enum Edition { } impl Edition { + //FIXME: replace with FromStr with proper error handling pub fn from_string(s: &str) -> Edition { match s { "2015" => Edition::Edition2015, diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs index 0d1ab48438d..b6bfd531de9 100644 --- a/crates/ra_db/src/lib.rs +++ b/crates/ra_db/src/lib.rs @@ -1,17 +1,18 @@ //! ra_db defines basic database traits. The concrete DB is defined by ra_ide_api. mod cancellation; mod input; +pub mod fixture; use std::{panic, sync::Arc}; use ra_prof::profile; use ra_syntax::{ast, Parse, SourceFile, TextRange, TextUnit}; -use relative_path::{RelativePath, RelativePathBuf}; pub use crate::{ cancellation::Canceled, input::{CrateGraph, CrateId, Dependency, Edition, FileId, SourceRoot, SourceRootId}, }; +pub use relative_path::{RelativePath, RelativePathBuf}; pub use salsa; pub trait CheckCanceled { diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 5b78bdfef38..181c5d47afe 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -9,7 +9,7 @@ use hir_def::{ adt::VariantData, builtin_type::BuiltinType, type_ref::{Mutability, TypeRef}, - CrateModuleId, LocalEnumVariantId, LocalStructFieldId, ModuleId, + CrateModuleId, LocalEnumVariantId, LocalStructFieldId, ModuleId, UnionId, }; use hir_expand::{ diagnostics::DiagnosticSink, @@ -28,11 +28,10 @@ use crate::{ TypeAliasId, }, impl_block::ImplBlock, - nameres::{ImportId, ModuleScope, Namespace}, resolve::{Resolver, Scope, TypeNs}, traits::TraitData, ty::{InferenceResult, TraitRef}, - Either, HasSource, Name, Ty, + Either, HasSource, Name, ScopeDef, Ty, {ImportId, Namespace}, }; /// hir::Crate describes a single crate. It's the main interface with which @@ -66,7 +65,7 @@ impl Crate { } pub fn root_module(self, db: &impl DefDatabase) -> Option<Module> { - let module_id = db.crate_def_map(self).root(); + let module_id = db.crate_def_map(self.crate_id).root(); Some(Module::new(self, module_id)) } @@ -120,7 +119,7 @@ impl Module { /// Name of this module. pub fn name(self, db: &impl DefDatabase) -> Option<Name> { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); let parent = def_map[self.id.module_id].parent?; def_map[parent].children.iter().find_map(|(name, module_id)| { if *module_id == self.id.module_id { @@ -151,20 +150,20 @@ impl Module { /// might be missing `krate`. This can happen if a module's file is not included /// in the module tree of any target in `Cargo.toml`. pub fn crate_root(self, db: &impl DefDatabase) -> Module { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); self.with_module_id(def_map.root()) } /// Finds a child module with the specified name. pub fn child(self, db: &impl HirDatabase, name: &Name) -> Option<Module> { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); let child_id = def_map[self.id.module_id].children.get(name)?; Some(self.with_module_id(*child_id)) } /// Iterates over all child modules. pub fn children(self, db: &impl DefDatabase) -> impl Iterator<Item = Module> { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); let children = def_map[self.id.module_id] .children .iter() @@ -175,7 +174,7 @@ impl Module { /// Finds a parent module. pub fn parent(self, db: &impl DefDatabase) -> Option<Module> { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); let parent_id = def_map[self.id.module_id].parent?; Some(self.with_module_id(parent_id)) } @@ -191,12 +190,16 @@ impl Module { } /// Returns a `ModuleScope`: a set of items, visible in this module. - pub fn scope(self, db: &impl HirDatabase) -> ModuleScope { - db.crate_def_map(self.krate())[self.id.module_id].scope.clone() + pub fn scope(self, db: &impl HirDatabase) -> Vec<(Name, ScopeDef, Option<ImportId>)> { + db.crate_def_map(self.id.krate)[self.id.module_id] + .scope + .entries() + .map(|(name, res)| (name.clone(), res.def.into(), res.import)) + .collect() } pub fn diagnostics(self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { - db.crate_def_map(self.krate()).add_diagnostics(db, self.id.module_id, sink); + db.crate_def_map(self.id.krate).add_diagnostics(db, self.id.module_id, sink); for decl in self.declarations(db) { match decl { crate::ModuleDef::Function(f) => f.diagnostics(db, sink), @@ -220,12 +223,12 @@ impl Module { } pub(crate) fn resolver(self, db: &impl DefDatabase) -> Resolver { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); Resolver::default().push_module_scope(def_map, self.id.module_id) } pub fn declarations(self, db: &impl DefDatabase) -> Vec<ModuleDef> { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); def_map[self.id.module_id] .scope .entries() @@ -233,6 +236,7 @@ impl Module { .flat_map(|per_ns| { per_ns.take_types().into_iter().chain(per_ns.take_values().into_iter()) }) + .map(ModuleDef::from) .collect() } @@ -336,12 +340,12 @@ impl Struct { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Union { - pub(crate) id: StructId, + pub(crate) id: UnionId, } impl Union { pub fn name(self, db: &impl DefDatabase) -> Option<Name> { - db.struct_data(self.id).name.clone() + db.union_data(self.id).name.clone() } pub fn module(self, db: &impl HirDatabase) -> Module { diff --git a/crates/ra_hir/src/code_model/src.rs b/crates/ra_hir/src/code_model/src.rs index bd0c3c226aa..6d116ee75c9 100644 --- a/crates/ra_hir/src/code_model/src.rs +++ b/crates/ra_hir/src/code_model/src.rs @@ -22,7 +22,7 @@ pub trait HasSource { impl Module { /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items. pub fn definition_source(self, db: &(impl DefDatabase + AstDatabase)) -> Source<ModuleSource> { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); let decl_id = def_map[self.id.module_id].declaration; let file_id = def_map[self.id.module_id].definition; let ast = ModuleSource::new(db, file_id, decl_id); @@ -36,7 +36,7 @@ impl Module { self, db: &(impl DefDatabase + AstDatabase), ) -> Option<Source<ast::Module>> { - let def_map = db.crate_def_map(self.krate()); + let def_map = db.crate_def_map(self.id.krate); let decl = def_map[self.id.module_id].declaration?; let ast = decl.to_node(db); Some(Source { file_id: decl.file_id(), ast }) diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 89ca4e39f0a..eb66325f7f1 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -11,20 +11,19 @@ use crate::{ ids, impl_block::{ImplBlock, ImplSourceMap, ModuleImplBlocks}, lang_item::{LangItemTarget, LangItems}, - nameres::{CrateDefMap, Namespace}, traits::TraitData, ty::{ method_resolution::CrateImplBlocks, traits::Impl, CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TypableDef, TypeCtor, }, type_alias::TypeAliasData, - Const, ConstData, Crate, DefWithBody, ExprScopes, FnData, Function, Module, Static, + Const, ConstData, Crate, DefWithBody, ExprScopes, FnData, Function, Module, Namespace, Static, StructField, Trait, TypeAlias, }; pub use hir_def::db::{ - DefDatabase2, DefDatabase2Storage, EnumDataQuery, InternDatabase, InternDatabaseStorage, - RawItemsQuery, RawItemsWithSourceMapQuery, StructDataQuery, + CrateDefMapQuery, DefDatabase2, DefDatabase2Storage, EnumDataQuery, InternDatabase, + InternDatabaseStorage, RawItemsQuery, RawItemsWithSourceMapQuery, StructDataQuery, }; pub use hir_expand::db::{ AstDatabase, AstDatabaseStorage, AstIdMapQuery, MacroArgQuery, MacroDefQuery, MacroExpandQuery, @@ -41,9 +40,6 @@ pub trait DefDatabase: HirDebugDatabase + DefDatabase2 { #[salsa::invoke(crate::traits::TraitItemsIndex::trait_items_index)] fn trait_items_index(&self, module: Module) -> crate::traits::TraitItemsIndex; - #[salsa::invoke(CrateDefMap::crate_def_map_query)] - fn crate_def_map(&self, krate: Crate) -> Arc<CrateDefMap>; - #[salsa::invoke(ModuleImplBlocks::impls_in_module_with_source_map_query)] fn impls_in_module_with_source_map( &self, diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs index a33af8f4675..1751e7be33a 100644 --- a/crates/ra_hir/src/diagnostics.rs +++ b/crates/ra_hir/src/diagnostics.rs @@ -3,10 +3,10 @@ use std::any::Any; use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; -use relative_path::RelativePathBuf; use crate::{db::AstDatabase, HirFileId, Name, Source}; +pub use hir_def::diagnostics::UnresolvedModule; pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; #[derive(Debug)] @@ -29,25 +29,6 @@ impl Diagnostic for NoSuchField { } } -#[derive(Debug)] -pub struct UnresolvedModule { - pub file: HirFileId, - pub decl: AstPtr<ast::Module>, - pub candidate: RelativePathBuf, -} - -impl Diagnostic for UnresolvedModule { - fn message(&self) -> String { - "unresolved module".to_string() - } - fn source(&self) -> Source<SyntaxNodePtr> { - Source { file_id: self.file, ast: self.decl.into() } - } - fn as_any(&self) -> &(dyn Any + Send + 'static) { - self - } -} - #[derive(Debug)] pub struct MissingFields { pub file: HirFileId, diff --git a/crates/ra_hir/src/from_id.rs b/crates/ra_hir/src/from_id.rs new file mode 100644 index 00000000000..089dbc9084a --- /dev/null +++ b/crates/ra_hir/src/from_id.rs @@ -0,0 +1,63 @@ +//! Utility module for converting between hir_def ids and code_model wrappers. +//! +//! It's unclear if we need this long-term, but it's definitelly useful while we +//! are splitting the hir. + +use hir_def::{AdtId, EnumVariantId, ModuleDefId}; + +use crate::{Adt, EnumVariant, ModuleDef}; + +macro_rules! from_id { + ($(($id:path, $ty:path)),*) => {$( + impl From<$id> for $ty { + fn from(id: $id) -> $ty { + $ty { id } + } + } + )*} +} + +from_id![ + (hir_def::ModuleId, crate::Module), + (hir_def::StructId, crate::Struct), + (hir_def::UnionId, crate::Union), + (hir_def::EnumId, crate::Enum), + (hir_def::TypeAliasId, crate::TypeAlias), + (hir_def::TraitId, crate::Trait), + (hir_def::StaticId, crate::Static), + (hir_def::ConstId, crate::Const), + (hir_def::FunctionId, crate::Function), + (hir_expand::MacroDefId, crate::MacroDef) +]; + +impl From<AdtId> for Adt { + fn from(id: AdtId) -> Self { + match id { + AdtId::StructId(it) => Adt::Struct(it.into()), + AdtId::UnionId(it) => Adt::Union(it.into()), + AdtId::EnumId(it) => Adt::Enum(it.into()), + } + } +} + +impl From<EnumVariantId> for EnumVariant { + fn from(id: EnumVariantId) -> Self { + EnumVariant { parent: id.parent.into(), id: id.local_id } + } +} + +impl From<ModuleDefId> for ModuleDef { + fn from(id: ModuleDefId) -> Self { + match id { + ModuleDefId::ModuleId(it) => ModuleDef::Module(it.into()), + ModuleDefId::FunctionId(it) => ModuleDef::Function(it.into()), + ModuleDefId::AdtId(it) => ModuleDef::Adt(it.into()), + ModuleDefId::EnumVariantId(it) => ModuleDef::EnumVariant(it.into()), + ModuleDefId::ConstId(it) => ModuleDef::Const(it.into()), + ModuleDefId::StaticId(it) => ModuleDef::Static(it.into()), + ModuleDefId::TraitId(it) => ModuleDef::Trait(it.into()), + ModuleDefId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()), + ModuleDefId::BuiltinType(it) => ModuleDef::BuiltinType(it), + } + } +} diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs index a9de0145538..9899bdbbce2 100644 --- a/crates/ra_hir/src/from_source.rs +++ b/crates/ra_hir/src/from_source.rs @@ -149,14 +149,20 @@ impl Module { ModuleSource::SourceFile(_) => None, }; - db.relevant_crates(src.file_id.original_file(db)) - .iter() - .map(|&crate_id| Crate { crate_id }) - .find_map(|krate| { - let def_map = db.crate_def_map(krate); - let module_id = def_map.find_module_by_source(src.file_id, decl_id)?; - Some(Module::new(krate, module_id)) - }) + db.relevant_crates(src.file_id.original_file(db)).iter().find_map(|&crate_id| { + let def_map = db.crate_def_map(crate_id); + + let (module_id, _module_data) = + def_map.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(src.file_id) + } + })?; + + Some(Module::new(Crate { crate_id }, module_id)) + }) } } diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 0ba17e5710d..3ba99d92d70 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -34,7 +34,6 @@ pub mod mock; pub mod source_binder; mod ids; -mod nameres; mod adt; mod traits; mod type_alias; @@ -47,6 +46,7 @@ mod resolve; pub mod diagnostics; mod util; +mod from_id; mod code_model; pub mod from_source; @@ -72,7 +72,6 @@ pub use crate::{ generics::{GenericDef, GenericParam, GenericParams, HasGenericParams}, ids::{HirFileId, MacroCallId, MacroCallLoc, MacroDefId, MacroFile}, impl_block::ImplBlock, - nameres::{ImportId, Namespace, PerNs}, resolve::ScopeDef, source_binder::{PathResolution, ScopeEntryWithSyntax, SourceAnalyzer}, ty::{ @@ -82,6 +81,10 @@ pub use crate::{ pub use hir_def::{ builtin_type::BuiltinType, + nameres::{ + per_ns::{Namespace, PerNs}, + raw::ImportId, + }, path::{Path, PathKind}, type_ref::Mutability, }; diff --git a/crates/ra_hir/src/nameres.rs b/crates/ra_hir/src/nameres.rs deleted file mode 100644 index 32a6ab47495..00000000000 --- a/crates/ra_hir/src/nameres.rs +++ /dev/null @@ -1,558 +0,0 @@ -//! This module implements import-resolution/macro expansion algorithm. -//! -//! The result of this module is `CrateDefMap`: a data structure 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 -//! strict ordering. -//! -//! ## Collecting RawItems -//! -//! This happens in the `raw` module, which parses a single source file into a -//! set of top-level items. Nested imports are desugared to flat imports in -//! this phase. Macro calls are represented as a triple of (Path, Option<Name>, -//! 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 unresolved imports and macros. -//! -//! While we walk tree of modules, we also record macro_rules definitions and -//! expand calls to macro_rules defined macros. -//! -//! ## Resolving Imports -//! -//! We maintain a list of currently unresolved imports. On every iteration, we -//! try to resolve some imports from this list. If the import is resolved, we -//! record it, by adding an item to current module scope and, if necessary, by -//! recursively populating glob imports. -//! -//! ## Resolving Macros -//! -//! macro_rules from the same crate use a global mutable namespace. We expand -//! them immediately, when we collect modules. -//! -//! Macros from other crates (including proc-macros) can be used with -//! `foo::bar!` syntax. We handle them similarly to imports. There's a list of -//! unexpanded macros. On every iteration, we try to resolve each macro call -//! path and, upon success, we run macro expansion and "collect module" phase -//! on the result - -mod per_ns; -mod collector; -#[cfg(test)] -mod tests; - -use std::sync::Arc; - -use hir_def::{builtin_type::BuiltinType, CrateModuleId}; -use hir_expand::diagnostics::DiagnosticSink; -use once_cell::sync::Lazy; -use ra_arena::Arena; -use ra_db::{Edition, FileId}; -use ra_prof::profile; -use ra_syntax::ast; -use rustc_hash::{FxHashMap, FxHashSet}; -use test_utils::tested_by; - -use crate::{ - db::{AstDatabase, DefDatabase}, - ids::MacroDefId, - nameres::diagnostics::DefDiagnostic, - Adt, AstId, Crate, HirFileId, MacroDef, Module, ModuleDef, Name, Path, PathKind, Trait, -}; - -pub use self::per_ns::{Namespace, PerNs}; - -pub use hir_def::nameres::raw::ImportId; - -/// Contains 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<Module>, - extern_prelude: FxHashMap<Name, ModuleDef>, - root: CrateModuleId, - modules: Arena<CrateModuleId, ModuleData>, - - /// Some macros are not well-behavior, which leads to infinite loop - /// e.g. macro_rules! foo { ($ty:ty) => { foo!($ty); } } - /// We mark it down and skip it in collector - /// - /// FIXME: - /// Right now it only handle a poison macro in a single crate, - /// such that if other crate try to call that macro, - /// the whole process will do again until it became poisoned in that crate. - /// We should handle this macro set globally - /// However, do we want to put it as a global variable? - poison_macros: FxHashSet<MacroDefId>, - - diagnostics: Vec<DefDiagnostic>, -} - -impl std::ops::Index<CrateModuleId> for CrateDefMap { - type Output = ModuleData; - fn index(&self, id: CrateModuleId) -> &ModuleData { - &self.modules[id] - } -} - -#[derive(Default, Debug, PartialEq, Eq)] -pub struct ModuleData { - pub(crate) parent: Option<CrateModuleId>, - pub(crate) children: FxHashMap<Name, CrateModuleId>, - pub(crate) scope: ModuleScope, - /// None for root - pub(crate) declaration: Option<AstId<ast::Module>>, - /// None for inline modules. - /// - /// Note that non-inline modules, by definition, live inside non-macro file. - pub(crate) definition: Option<FileId>, -} - -#[derive(Debug, Default, PartialEq, Eq, Clone)] -pub struct ModuleScope { - items: FxHashMap<Name, Resolution>, - /// Macros visable in current module in legacy textual scope - /// - /// For macros invoked by an unquatified identifier like `bar!()`, `legacy_macros` will be searched in first. - /// If it yields no result, then it turns to module scoped `macros`. - /// It macros with name quatified with a path like `crate::foo::bar!()`, `legacy_macros` will be skipped, - /// and only normal scoped `macros` will be searched in. - /// - /// Note that this automatically inherit macros defined textually before the definition of module itself. - /// - /// Module scoped macros will be inserted into `items` instead of here. - // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will - // be all resolved to the last one defined if shadowing happens. - legacy_macros: FxHashMap<Name, MacroDef>, -} - -static BUILTIN_SCOPE: Lazy<FxHashMap<Name, Resolution>> = Lazy::new(|| { - BuiltinType::ALL - .iter() - .map(|(name, ty)| { - (name.clone(), Resolution { def: PerNs::types(ty.clone().into()), import: None }) - }) - .collect() -}); - -/// Legacy macros can only be accessed through special methods like `get_legacy_macros`. -/// Other methods will only resolve values, types and module scoped macros only. -impl ModuleScope { - pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a Name, &'a Resolution)> + 'a { - //FIXME: shadowing - self.items.iter().chain(BUILTIN_SCOPE.iter()) - } - - /// Iterate over all module scoped macros - pub fn macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDef)> + 'a { - self.items - .iter() - .filter_map(|(name, res)| res.def.get_macros().map(|macro_| (name, macro_))) - } - - /// Iterate over all legacy textual scoped macros visable at the end of the module - pub fn legacy_macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDef)> + 'a { - self.legacy_macros.iter().map(|(name, def)| (name, *def)) - } - - /// Get a name from current module scope, legacy macros are not included - pub fn get(&self, name: &Name) -> Option<&Resolution> { - self.items.get(name).or_else(|| BUILTIN_SCOPE.get(name)) - } - - pub fn traits<'a>(&'a self) -> impl Iterator<Item = Trait> + 'a { - self.items.values().filter_map(|r| match r.def.take_types() { - Some(ModuleDef::Trait(t)) => Some(t), - _ => None, - }) - } - - fn get_legacy_macro(&self, name: &Name) -> Option<MacroDef> { - self.legacy_macros.get(name).copied() - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct Resolution { - /// None for unresolved - pub def: PerNs, - /// ident by which this is imported into local scope. - pub import: Option<ImportId>, -} - -impl Resolution { - pub(crate) fn from_macro(macro_: MacroDef) -> Self { - Resolution { def: PerNs::macros(macro_), import: None } - } -} - -#[derive(Debug, Clone)] -struct ResolvePathResult { - resolved_def: PerNs, - segment_index: Option<usize>, - reached_fixedpoint: ReachedFixedPoint, -} - -impl ResolvePathResult { - fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult { - ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None) - } - - fn with( - resolved_def: PerNs, - reached_fixedpoint: ReachedFixedPoint, - segment_index: Option<usize>, - ) -> ResolvePathResult { - ResolvePathResult { resolved_def, reached_fixedpoint, segment_index } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ResolveMode { - Import, - Other, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ReachedFixedPoint { - Yes, - No, -} - -impl CrateDefMap { - pub(crate) fn crate_def_map_query( - // Note that this doesn't have `+ AstDatabase`! - // This gurantess that `CrateDefMap` is stable across reparses. - db: &impl DefDatabase, - krate: Crate, - ) -> Arc<CrateDefMap> { - let _p = profile("crate_def_map_query"); - let def_map = { - let edition = krate.edition(db); - let mut modules: Arena<CrateModuleId, ModuleData> = Arena::default(); - let root = modules.alloc(ModuleData::default()); - CrateDefMap { - krate, - edition, - extern_prelude: FxHashMap::default(), - prelude: None, - root, - modules, - poison_macros: FxHashSet::default(), - diagnostics: Vec::new(), - } - }; - let def_map = collector::collect_defs(db, def_map); - Arc::new(def_map) - } - - pub(crate) fn krate(&self) -> Crate { - self.krate - } - - pub(crate) fn root(&self) -> CrateModuleId { - self.root - } - - pub(crate) fn prelude(&self) -> Option<Module> { - self.prelude - } - - pub(crate) fn extern_prelude(&self) -> &FxHashMap<Name, ModuleDef> { - &self.extern_prelude - } - - pub(crate) fn add_diagnostics( - &self, - db: &(impl DefDatabase + AstDatabase), - module: CrateModuleId, - sink: &mut DiagnosticSink, - ) { - self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink)) - } - - pub(crate) fn find_module_by_source( - &self, - file_id: HirFileId, - decl_id: Option<AstId<ast::Module>>, - ) -> Option<CrateModuleId> { - 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 DefDatabase, - original_module: CrateModuleId, - path: &Path, - ) -> (PerNs, Option<usize>) { - let res = self.resolve_path_fp_with_macro(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_with_macro( - &self, - db: &impl DefDatabase, - mode: ResolveMode, - original_module: CrateModuleId, - path: &Path, - ) -> ResolvePathResult { - let mut segments = path.segments.iter().enumerate(); - let mut curr_per_ns: PerNs = match path.kind { - PathKind::DollarCrate(crate_id) => { - let krate = Crate { crate_id }; - if krate == self.krate { - tested_by!(macro_dollar_crate_self); - PerNs::types(Module::new(self.krate, self.root).into()) - } else { - match krate.root_module(db) { - Some(module) => { - tested_by!(macro_dollar_crate_other); - PerNs::types(module.into()) - } - None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), - } - } - } - PathKind::Crate => PerNs::types(Module::new(self.krate, self.root).into()), - PathKind::Self_ => PerNs::types(Module::new(self.krate, 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) - // FIXME 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::new(self.krate, 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 - } - } - PathKind::Type(_) => { - // This is handled in `infer::infer_path_expr` - // The result returned here does not matter - return ResolvePathResult::empty(ReachedFixedPoint::Yes); - } - }; - - for (i, segment) in segments { - let curr = match curr_per_ns.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..].to_vec(), 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.id.module_id, &path); - return ResolvePathResult::with( - def, - ReachedFixedPoint::Yes, - s.map(|s| s + i), - ); - } - - // Since it is a qualified path here, it should not contains legacy macros - match self[module.id.module_id].scope.get(&segment.name) { - Some(res) => res.def, - _ => { - log::debug!("path segment {:?} not found", segment.name); - return ResolvePathResult::empty(ReachedFixedPoint::No); - } - } - } - ModuleDef::Adt(Adt::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), - 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.get(name).map_or_else(PerNs::none, |res| res.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 DefDatabase, - module: CrateModuleId, - name: &Name, - ) -> PerNs { - // Resolve in: - // - legacy scope of macro - // - current module / scope - // - extern prelude - // - std prelude - let from_legacy_macro = - self[module].scope.get_legacy_macro(name).map_or_else(PerNs::none, PerNs::macros); - let from_scope = self[module].scope.get(name).map_or_else(PerNs::none, |res| res.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_legacy_macro.or(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 DefDatabase, name: &Name) -> PerNs { - if let Some(prelude) = self.prelude { - let keep; - let def_map = if prelude.krate() == self.krate { - self - } else { - // Extend lifetime - keep = db.crate_def_map(prelude.krate()); - &keep - }; - def_map[prelude.id.module_id].scope.get(name).map_or_else(PerNs::none, |res| res.def) - } else { - PerNs::none() - } - } -} - -mod diagnostics { - use hir_expand::diagnostics::DiagnosticSink; - use ra_syntax::{ast, AstPtr}; - use relative_path::RelativePathBuf; - - use crate::{ - db::{AstDatabase, DefDatabase}, - diagnostics::UnresolvedModule, - nameres::CrateModuleId, - AstId, - }; - - #[derive(Debug, PartialEq, Eq)] - pub(super) enum DefDiagnostic { - UnresolvedModule { - module: CrateModuleId, - declaration: AstId<ast::Module>, - candidate: RelativePathBuf, - }, - } - - impl DefDiagnostic { - pub(super) fn add_to( - &self, - db: &(impl DefDatabase + AstDatabase), - target_module: CrateModuleId, - sink: &mut DiagnosticSink, - ) { - match self { - DefDiagnostic::UnresolvedModule { module, declaration, candidate } => { - if *module != target_module { - return; - } - let decl = declaration.to_node(db); - sink.push(UnresolvedModule { - file: declaration.file_id(), - decl: AstPtr::new(&decl), - candidate: candidate.clone(), - }) - } - } - } - } -} diff --git a/crates/ra_hir/src/resolve.rs b/crates/ra_hir/src/resolve.rs index 75b24d386f1..b932b0c8cdb 100644 --- a/crates/ra_hir/src/resolve.rs +++ b/crates/ra_hir/src/resolve.rs @@ -3,8 +3,9 @@ use std::sync::Arc; use hir_def::{ builtin_type::BuiltinType, + nameres::CrateDefMap, path::{Path, PathKind}, - CrateModuleId, + AdtId, CrateModuleId, ModuleDefId, }; use hir_expand::name::{self, Name}; use rustc_hash::FxHashSet; @@ -18,8 +19,8 @@ use crate::{ }, generics::GenericParams, impl_block::ImplBlock, - nameres::{CrateDefMap, PerNs}, - Adt, Const, Enum, EnumVariant, Function, MacroDef, ModuleDef, Static, Struct, Trait, TypeAlias, + Adt, Const, Enum, EnumVariant, Function, MacroDef, ModuleDef, PerNs, Static, Struct, Trait, + TypeAlias, }; #[derive(Debug, Clone, Default)] @@ -90,7 +91,7 @@ impl Resolver { pub(crate) fn resolve_known_trait(&self, db: &impl HirDatabase, path: &Path) -> Option<Trait> { let res = self.resolve_module_path(db, path).take_types()?; match res { - ModuleDef::Trait(it) => Some(it), + ModuleDefId::TraitId(it) => Some(it.into()), _ => None, } } @@ -103,7 +104,7 @@ impl Resolver { ) -> Option<Struct> { let res = self.resolve_module_path(db, path).take_types()?; match res { - ModuleDef::Adt(Adt::Struct(it)) => Some(it), + ModuleDefId::AdtId(AdtId::StructId(it)) => Some(it.into()), _ => None, } } @@ -112,7 +113,7 @@ impl Resolver { pub(crate) fn resolve_known_enum(&self, db: &impl HirDatabase, path: &Path) -> Option<Enum> { let res = self.resolve_module_path(db, path).take_types()?; match res { - ModuleDef::Adt(Adt::Enum(it)) => Some(it), + ModuleDefId::AdtId(AdtId::EnumId(it)) => Some(it.into()), _ => None, } } @@ -166,18 +167,18 @@ impl Resolver { Scope::ModuleScope(m) => { let (module_def, idx) = m.crate_def_map.resolve_path(db, m.module_id, path); let res = match module_def.take_types()? { - ModuleDef::Adt(it) => TypeNs::Adt(it), - ModuleDef::EnumVariant(it) => TypeNs::EnumVariant(it), + ModuleDefId::AdtId(it) => TypeNs::Adt(it.into()), + ModuleDefId::EnumVariantId(it) => TypeNs::EnumVariant(it.into()), - ModuleDef::TypeAlias(it) => TypeNs::TypeAlias(it), - ModuleDef::BuiltinType(it) => TypeNs::BuiltinType(it), + ModuleDefId::TypeAliasId(it) => TypeNs::TypeAlias(it.into()), + ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it), - ModuleDef::Trait(it) => TypeNs::Trait(it), + ModuleDefId::TraitId(it) => TypeNs::Trait(it.into()), - ModuleDef::Function(_) - | ModuleDef::Const(_) - | ModuleDef::Static(_) - | ModuleDef::Module(_) => return None, + ModuleDefId::FunctionId(_) + | ModuleDefId::ConstId(_) + | ModuleDefId::StaticId(_) + | ModuleDefId::ModuleId(_) => return None, }; return Some((res, idx)); } @@ -261,33 +262,35 @@ impl Resolver { return match idx { None => { let value = match module_def.take_values()? { - ModuleDef::Function(it) => ValueNs::Function(it), - ModuleDef::Adt(Adt::Struct(it)) => ValueNs::Struct(it), - ModuleDef::EnumVariant(it) => ValueNs::EnumVariant(it), - ModuleDef::Const(it) => ValueNs::Const(it), - ModuleDef::Static(it) => ValueNs::Static(it), + ModuleDefId::FunctionId(it) => ValueNs::Function(it.into()), + ModuleDefId::AdtId(AdtId::StructId(it)) => { + ValueNs::Struct(it.into()) + } + ModuleDefId::EnumVariantId(it) => ValueNs::EnumVariant(it.into()), + ModuleDefId::ConstId(it) => ValueNs::Const(it.into()), + ModuleDefId::StaticId(it) => ValueNs::Static(it.into()), - ModuleDef::Adt(Adt::Enum(_)) - | ModuleDef::Adt(Adt::Union(_)) - | ModuleDef::Trait(_) - | ModuleDef::TypeAlias(_) - | ModuleDef::BuiltinType(_) - | ModuleDef::Module(_) => return None, + ModuleDefId::AdtId(AdtId::EnumId(_)) + | ModuleDefId::AdtId(AdtId::UnionId(_)) + | ModuleDefId::TraitId(_) + | ModuleDefId::TypeAliasId(_) + | ModuleDefId::BuiltinType(_) + | ModuleDefId::ModuleId(_) => return None, }; Some(ResolveValueResult::ValueNs(value)) } Some(idx) => { let ty = match module_def.take_types()? { - ModuleDef::Adt(it) => TypeNs::Adt(it), - ModuleDef::Trait(it) => TypeNs::Trait(it), - ModuleDef::TypeAlias(it) => TypeNs::TypeAlias(it), - ModuleDef::BuiltinType(it) => TypeNs::BuiltinType(it), + ModuleDefId::AdtId(it) => TypeNs::Adt(it.into()), + ModuleDefId::TraitId(it) => TypeNs::Trait(it.into()), + ModuleDefId::TypeAliasId(it) => TypeNs::TypeAlias(it.into()), + ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it), - ModuleDef::Module(_) - | ModuleDef::Function(_) - | ModuleDef::EnumVariant(_) - | ModuleDef::Const(_) - | ModuleDef::Static(_) => return None, + ModuleDefId::ModuleId(_) + | ModuleDefId::FunctionId(_) + | ModuleDefId::EnumVariantId(_) + | ModuleDefId::ConstId(_) + | ModuleDefId::StaticId(_) => return None, }; Some(ResolveValueResult::Partial(ty, idx)) } @@ -315,7 +318,7 @@ impl Resolver { path: &Path, ) -> Option<MacroDef> { let (item_map, module) = self.module()?; - item_map.resolve_path(db, module, path).0.get_macros() + item_map.resolve_path(db, module, path).0.get_macros().map(MacroDef::from) } pub(crate) fn process_all_names( @@ -333,10 +336,11 @@ impl Resolver { for scope in &self.scopes { if let Scope::ModuleScope(m) = scope { if let Some(prelude) = m.crate_def_map.prelude() { - let prelude_def_map = db.crate_def_map(prelude.krate()); - traits.extend(prelude_def_map[prelude.id.module_id].scope.traits()); + let prelude_def_map = db.crate_def_map(prelude.krate); + traits + .extend(prelude_def_map[prelude.module_id].scope.traits().map(Trait::from)); } - traits.extend(m.crate_def_map[m.module_id].scope.traits()); + traits.extend(m.crate_def_map[m.module_id].scope.traits().map(Trait::from)); } } traits @@ -351,7 +355,7 @@ impl Resolver { } pub(crate) fn krate(&self) -> Option<Crate> { - self.module().map(|t| t.0.krate()) + self.module().map(|t| Crate { crate_id: t.0.krate() }) } pub(crate) fn where_predicates_in_scope<'a>( @@ -420,8 +424,10 @@ impl From<PerNs> for ScopeDef { fn from(def: PerNs) -> Self { def.take_types() .or_else(|| def.take_values()) - .map(ScopeDef::ModuleDef) - .or_else(|| def.get_macros().map(ScopeDef::MacroDef)) + .map(|module_def_id| ScopeDef::ModuleDef(module_def_id.into())) + .or_else(|| { + def.get_macros().map(|macro_def_id| ScopeDef::MacroDef(macro_def_id.into())) + }) .unwrap_or(ScopeDef::Unknown) } } @@ -441,18 +447,16 @@ impl Scope { f(name.clone(), res.def.into()); }); m.crate_def_map[m.module_id].scope.legacy_macros().for_each(|(name, macro_)| { - f(name.clone(), ScopeDef::MacroDef(macro_)); + f(name.clone(), ScopeDef::MacroDef(macro_.into())); }); - m.crate_def_map.extern_prelude().iter().for_each(|(name, def)| { - f(name.clone(), ScopeDef::ModuleDef(*def)); + m.crate_def_map.extern_prelude().iter().for_each(|(name, &def)| { + f(name.clone(), ScopeDef::ModuleDef(def.into())); }); if let Some(prelude) = m.crate_def_map.prelude() { - let prelude_def_map = db.crate_def_map(prelude.krate()); - prelude_def_map[prelude.id.module_id].scope.entries().for_each( - |(name, res)| { - f(name.clone(), res.def.into()); - }, - ); + let prelude_def_map = db.crate_def_map(prelude.krate); + prelude_def_map[prelude.module_id].scope.entries().for_each(|(name, res)| { + f(name.clone(), res.def.into()); + }); } } Scope::GenericParams(gp) => { diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index a4ca59bba1b..66cb4b357f8 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -253,8 +253,11 @@ impl SourceAnalyzer { Some(res) }); - let items = - self.resolver.resolve_module_path(db, &path).take_types().map(PathResolution::Def); + let items = self + .resolver + .resolve_module_path(db, &path) + .take_types() + .map(|it| PathResolution::Def(it.into())); types.or(values).or(items).or_else(|| { self.resolver.resolve_path_as_macro(db, &path).map(|def| PathResolution::Macro(def)) }) diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index fed52df39ce..a09ef5c5d3a 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -11,12 +11,11 @@ use crate::{ db::HirDatabase, expr::{self, Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, generics::{GenericParams, HasGenericParams}, - nameres::Namespace, ty::{ autoderef, method_resolution, op, primitive, CallableDef, InferTy, Mutability, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk, }, - Adt, Name, + Adt, Name, Namespace, }; impl<'a, D: HirDatabase> InferenceContext<'a, D> { diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 8e28343071a..e29ab8492a5 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -23,15 +23,14 @@ use crate::{ db::HirDatabase, generics::HasGenericParams, generics::{GenericDef, WherePredicate}, - nameres::Namespace, resolve::{Resolver, TypeNs}, ty::{ primitive::{FloatTy, IntTy}, Adt, }, util::make_mut_slice, - Const, Enum, EnumVariant, Function, ModuleDef, Path, Static, Struct, StructField, Trait, - TypeAlias, Union, + Const, Enum, EnumVariant, Function, ModuleDef, Namespace, Path, Static, Struct, StructField, + Trait, TypeAlias, Union, }; impl Ty { diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index bfef48b1613..f271557377d 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -3403,7 +3403,7 @@ fn test() { S.foo()<|>; } #[test] fn infer_macro_with_dollar_crate_is_correct_in_expr() { - covers!(macro_dollar_crate_other); + // covers!(macro_dollar_crate_other); let (mut db, pos) = MockDatabase::with_position( r#" //- /main.rs diff --git a/crates/ra_hir_def/Cargo.toml b/crates/ra_hir_def/Cargo.toml index 746c907e80d..15055db64e5 100644 --- a/crates/ra_hir_def/Cargo.toml +++ b/crates/ra_hir_def/Cargo.toml @@ -19,3 +19,7 @@ test_utils = { path = "../test_utils" } mbe = { path = "../ra_mbe", package = "ra_mbe" } ra_cfg = { path = "../ra_cfg" } tt = { path = "../ra_tt", package = "ra_tt" } + +[dev-dependencies] +insta = "0.12.0" + diff --git a/crates/ra_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs index 22bd469f0a3..8f41e55d26f 100644 --- a/crates/ra_hir_def/src/adt.rs +++ b/crates/ra_hir_def/src/adt.rs @@ -8,7 +8,7 @@ use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; use crate::{ db::DefDatabase2, type_ref::TypeRef, AstItemDef, EnumId, LocalEnumVariantId, - LocalStructFieldId, StructId, + LocalStructFieldId, StructId, UnionId, }; /// Note that we use `StructData` for unions as well! @@ -56,6 +56,13 @@ impl StructData { let variant_data = Arc::new(variant_data); Arc::new(StructData { name, variant_data }) } + pub(crate) fn union_data_query(db: &impl DefDatabase2, struct_: UnionId) -> Arc<StructData> { + let src = struct_.source(db); + let name = src.ast.name().map(|n| n.as_name()); + let variant_data = VariantData::new(src.ast.kind()); + let variant_data = Arc::new(variant_data); + Arc::new(StructData { name, variant_data }) + } } impl EnumData { @@ -74,6 +81,11 @@ impl EnumData { .collect(); Arc::new(EnumData { name, variants }) } + + pub(crate) fn variant(&self, name: &Name) -> Option<LocalEnumVariantId> { + let (id, _) = self.variants.iter().find(|(_id, data)| data.name.as_ref() == Some(name))?; + Some(id) + } } impl VariantData { diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs index f6027013f2a..a42348101b4 100644 --- a/crates/ra_hir_def/src/db.rs +++ b/crates/ra_hir_def/src/db.rs @@ -2,13 +2,16 @@ use std::sync::Arc; use hir_expand::{db::AstDatabase, HirFileId}; -use ra_db::{salsa, SourceDatabase}; +use ra_db::{salsa, CrateId, SourceDatabase}; use ra_syntax::ast; use crate::{ adt::{EnumData, StructData}, - nameres::raw::{ImportSourceMap, RawItems}, - EnumId, StructId, + nameres::{ + raw::{ImportSourceMap, RawItems}, + CrateDefMap, + }, + EnumId, StructId, UnionId, }; #[salsa::query_group(InternDatabaseStorage)] @@ -42,9 +45,15 @@ pub trait DefDatabase2: InternDatabase + AstDatabase { #[salsa::invoke(RawItems::raw_items_query)] fn raw_items(&self, file_id: HirFileId) -> Arc<RawItems>; + #[salsa::invoke(CrateDefMap::crate_def_map_query)] + fn crate_def_map(&self, krate: CrateId) -> Arc<CrateDefMap>; + #[salsa::invoke(StructData::struct_data_query)] fn struct_data(&self, s: StructId) -> Arc<StructData>; + #[salsa::invoke(StructData::union_data_query)] + fn union_data(&self, s: UnionId) -> Arc<StructData>; + #[salsa::invoke(EnumData::enum_data_query)] fn enum_data(&self, e: EnumId) -> Arc<EnumData>; } diff --git a/crates/ra_hir_def/src/diagnostics.rs b/crates/ra_hir_def/src/diagnostics.rs new file mode 100644 index 00000000000..1c1ccdecb72 --- /dev/null +++ b/crates/ra_hir_def/src/diagnostics.rs @@ -0,0 +1,28 @@ +//! Diagnostics produced by `hir_def`. + +use std::any::Any; + +use hir_expand::diagnostics::Diagnostic; +use ra_syntax::{ast, AstPtr, SyntaxNodePtr}; +use relative_path::RelativePathBuf; + +use hir_expand::{HirFileId, Source}; + +#[derive(Debug)] +pub struct UnresolvedModule { + pub file: HirFileId, + pub decl: AstPtr<ast::Module>, + pub candidate: RelativePathBuf, +} + +impl Diagnostic for UnresolvedModule { + fn message(&self) -> String { + "unresolved module".to_string() + } + fn source(&self) -> Source<SyntaxNodePtr> { + Source { file_id: self.file, ast: self.decl.into() } + } + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index 6d66f481d3f..63ed2a09833 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -13,6 +13,12 @@ pub mod path; pub mod type_ref; pub mod builtin_type; pub mod adt; +pub mod diagnostics; + +#[cfg(test)] +mod test_db; +#[cfg(test)] +mod marks; // FIXME: this should be private pub mod nameres; @@ -237,8 +243,8 @@ impl AstItemDef<ast::EnumDef> for EnumId { // FIXME: rename to `VariantId`, only enums can ave variants #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct EnumVariantId { - parent: EnumId, - local_id: LocalEnumVariantId, + pub parent: EnumId, + pub local_id: LocalEnumVariantId, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] diff --git a/crates/ra_hir_def/src/marks.rs b/crates/ra_hir_def/src/marks.rs new file mode 100644 index 00000000000..0b99eac7174 --- /dev/null +++ b/crates/ra_hir_def/src/marks.rs @@ -0,0 +1,14 @@ +//! See test_utils/src/marks.rs + +test_utils::marks!( + bogus_paths + name_res_works_for_broken_modules + can_import_enum_variant + glob_enum + glob_across_crates + std_prelude + macro_rules_from_other_crates_are_visible_with_macro_use + prelude_is_macro_use + macro_dollar_crate_self + macro_dollar_crate_other +); diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs index 11ba8a7770d..fbd4248e6a7 100644 --- a/crates/ra_hir_def/src/nameres.rs +++ b/crates/ra_hir_def/src/nameres.rs @@ -1,5 +1,543 @@ -//! FIXME: write short doc here +//! This module implements import-resolution/macro expansion algorithm. +//! +//! The result of this module is `CrateDefMap`: a data structure 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 +//! strict ordering. +//! +//! ## Collecting RawItems +//! +//! This happens in the `raw` module, which parses a single source file into a +//! set of top-level items. Nested imports are desugared to flat imports in +//! this phase. Macro calls are represented as a triple of (Path, Option<Name>, +//! 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 unresolved imports and macros. +//! +//! While we walk tree of modules, we also record macro_rules definitions and +//! expand calls to macro_rules defined macros. +//! +//! ## Resolving Imports +//! +//! We maintain a list of currently unresolved imports. On every iteration, we +//! try to resolve some imports from this list. If the import is resolved, we +//! record it, by adding an item to current module scope and, if necessary, by +//! recursively populating glob imports. +//! +//! ## Resolving Macros +//! +//! macro_rules from the same crate use a global mutable namespace. We expand +//! them immediately, when we collect modules. +//! +//! Macros from other crates (including proc-macros) can be used with +//! `foo::bar!` syntax. We handle them similarly to imports. There's a list of +//! unexpanded macros. On every iteration, we try to resolve each macro call +//! path and, upon success, we run macro expansion and "collect module" phase +//! on the result // FIXME: review privacy of submodules pub mod raw; +pub mod per_ns; +pub mod collector; pub mod mod_resolution; + +#[cfg(test)] +mod tests; + +use std::sync::Arc; + +use hir_expand::{diagnostics::DiagnosticSink, name::Name, MacroDefId}; +use once_cell::sync::Lazy; +use ra_arena::Arena; +use ra_db::{CrateId, Edition, FileId}; +use ra_prof::profile; +use ra_syntax::ast; +use rustc_hash::{FxHashMap, FxHashSet}; +use test_utils::tested_by; + +use crate::{ + builtin_type::BuiltinType, + db::DefDatabase2, + nameres::{diagnostics::DefDiagnostic, per_ns::PerNs, raw::ImportId}, + path::{Path, PathKind}, + AdtId, AstId, CrateModuleId, EnumVariantId, ModuleDefId, ModuleId, TraitId, +}; + +/// Contains all top-level defs from a macro-expanded crate +#[derive(Debug, PartialEq, Eq)] +pub struct CrateDefMap { + krate: CrateId, + 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<ModuleId>, + extern_prelude: FxHashMap<Name, ModuleDefId>, + root: CrateModuleId, + pub modules: Arena<CrateModuleId, ModuleData>, + + /// Some macros are not well-behavior, which leads to infinite loop + /// e.g. macro_rules! foo { ($ty:ty) => { foo!($ty); } } + /// We mark it down and skip it in collector + /// + /// FIXME: + /// Right now it only handle a poison macro in a single crate, + /// such that if other crate try to call that macro, + /// the whole process will do again until it became poisoned in that crate. + /// We should handle this macro set globally + /// However, do we want to put it as a global variable? + poison_macros: FxHashSet<MacroDefId>, + + diagnostics: Vec<DefDiagnostic>, +} + +impl std::ops::Index<CrateModuleId> for CrateDefMap { + type Output = ModuleData; + fn index(&self, id: CrateModuleId) -> &ModuleData { + &self.modules[id] + } +} + +#[derive(Default, Debug, PartialEq, Eq)] +pub struct ModuleData { + pub parent: Option<CrateModuleId>, + pub children: FxHashMap<Name, CrateModuleId>, + pub scope: ModuleScope, + /// None for root + pub declaration: Option<AstId<ast::Module>>, + /// None for inline modules. + /// + /// Note that non-inline modules, by definition, live inside non-macro file. + pub definition: Option<FileId>, +} + +#[derive(Debug, Default, PartialEq, Eq, Clone)] +pub struct ModuleScope { + pub items: FxHashMap<Name, Resolution>, + /// Macros visable in current module in legacy textual scope + /// + /// For macros invoked by an unquatified identifier like `bar!()`, `legacy_macros` will be searched in first. + /// If it yields no result, then it turns to module scoped `macros`. + /// It macros with name quatified with a path like `crate::foo::bar!()`, `legacy_macros` will be skipped, + /// and only normal scoped `macros` will be searched in. + /// + /// Note that this automatically inherit macros defined textually before the definition of module itself. + /// + /// Module scoped macros will be inserted into `items` instead of here. + // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will + // be all resolved to the last one defined if shadowing happens. + legacy_macros: FxHashMap<Name, MacroDefId>, +} + +static BUILTIN_SCOPE: Lazy<FxHashMap<Name, Resolution>> = Lazy::new(|| { + BuiltinType::ALL + .iter() + .map(|(name, ty)| { + (name.clone(), Resolution { def: PerNs::types(ty.clone().into()), import: None }) + }) + .collect() +}); + +/// Legacy macros can only be accessed through special methods like `get_legacy_macros`. +/// Other methods will only resolve values, types and module scoped macros only. +impl ModuleScope { + pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a Name, &'a Resolution)> + 'a { + //FIXME: shadowing + self.items.iter().chain(BUILTIN_SCOPE.iter()) + } + + /// Iterate over all module scoped macros + pub fn macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a { + self.items + .iter() + .filter_map(|(name, res)| res.def.get_macros().map(|macro_| (name, macro_))) + } + + /// Iterate over all legacy textual scoped macros visable at the end of the module + pub fn legacy_macros<'a>(&'a self) -> impl Iterator<Item = (&'a Name, MacroDefId)> + 'a { + self.legacy_macros.iter().map(|(name, def)| (name, *def)) + } + + /// Get a name from current module scope, legacy macros are not included + pub fn get(&self, name: &Name) -> Option<&Resolution> { + self.items.get(name).or_else(|| BUILTIN_SCOPE.get(name)) + } + + pub fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a { + self.items.values().filter_map(|r| match r.def.take_types() { + Some(ModuleDefId::TraitId(t)) => Some(t), + _ => None, + }) + } + + fn get_legacy_macro(&self, name: &Name) -> Option<MacroDefId> { + self.legacy_macros.get(name).copied() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Resolution { + /// None for unresolved + pub def: PerNs, + /// ident by which this is imported into local scope. + pub import: Option<ImportId>, +} + +impl Resolution { + pub(crate) fn from_macro(macro_: MacroDefId) -> Self { + Resolution { def: PerNs::macros(macro_), import: None } + } +} + +#[derive(Debug, Clone)] +struct ResolvePathResult { + resolved_def: PerNs, + segment_index: Option<usize>, + reached_fixedpoint: ReachedFixedPoint, +} + +impl ResolvePathResult { + fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult { + ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None) + } + + fn with( + resolved_def: PerNs, + reached_fixedpoint: ReachedFixedPoint, + segment_index: Option<usize>, + ) -> ResolvePathResult { + ResolvePathResult { resolved_def, reached_fixedpoint, segment_index } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ResolveMode { + Import, + Other, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ReachedFixedPoint { + Yes, + No, +} + +impl CrateDefMap { + pub(crate) fn crate_def_map_query( + // Note that this doesn't have `+ AstDatabase`! + // This gurantess that `CrateDefMap` is stable across reparses. + db: &impl DefDatabase2, + krate: CrateId, + ) -> Arc<CrateDefMap> { + let _p = profile("crate_def_map_query"); + let def_map = { + let crate_graph = db.crate_graph(); + let edition = crate_graph.edition(krate); + let mut modules: Arena<CrateModuleId, ModuleData> = Arena::default(); + let root = modules.alloc(ModuleData::default()); + CrateDefMap { + krate, + edition, + extern_prelude: FxHashMap::default(), + prelude: None, + root, + modules, + poison_macros: FxHashSet::default(), + diagnostics: Vec::new(), + } + }; + let def_map = collector::collect_defs(db, def_map); + Arc::new(def_map) + } + + pub fn krate(&self) -> CrateId { + self.krate + } + + pub fn root(&self) -> CrateModuleId { + self.root + } + + pub fn prelude(&self) -> Option<ModuleId> { + self.prelude + } + + pub fn extern_prelude(&self) -> &FxHashMap<Name, ModuleDefId> { + &self.extern_prelude + } + + pub fn add_diagnostics( + &self, + db: &impl DefDatabase2, + module: CrateModuleId, + sink: &mut DiagnosticSink, + ) { + self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink)) + } + + pub fn resolve_path( + &self, + db: &impl DefDatabase2, + original_module: CrateModuleId, + path: &Path, + ) -> (PerNs, Option<usize>) { + let res = self.resolve_path_fp_with_macro(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_with_macro( + &self, + db: &impl DefDatabase2, + mode: ResolveMode, + original_module: CrateModuleId, + path: &Path, + ) -> ResolvePathResult { + let mut segments = path.segments.iter().enumerate(); + let mut curr_per_ns: PerNs = match path.kind { + PathKind::DollarCrate(krate) => { + if krate == self.krate { + tested_by!(macro_dollar_crate_self); + PerNs::types(ModuleId { krate: self.krate, module_id: self.root }.into()) + } else { + let def_map = db.crate_def_map(krate); + let module = ModuleId { krate, module_id: def_map.root }; + tested_by!(macro_dollar_crate_other); + PerNs::types(module.into()) + } + } + PathKind::Crate => { + PerNs::types(ModuleId { krate: self.krate, module_id: self.root }.into()) + } + PathKind::Self_ => { + PerNs::types(ModuleId { 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) + // FIXME 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(ModuleId { 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 + } + } + PathKind::Type(_) => { + // This is handled in `infer::infer_path_expr` + // The result returned here does not matter + return ResolvePathResult::empty(ReachedFixedPoint::Yes); + } + }; + + for (i, segment) in segments { + let curr = match curr_per_ns.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 { + ModuleDefId::ModuleId(module) => { + if module.krate != self.krate { + let path = + Path { segments: path.segments[i..].to_vec(), 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), + ); + } + + // Since it is a qualified path here, it should not contains legacy macros + match self[module.module_id].scope.get(&segment.name) { + Some(res) => res.def, + _ => { + log::debug!("path segment {:?} not found", segment.name); + return ResolvePathResult::empty(ReachedFixedPoint::No); + } + } + } + ModuleDefId::AdtId(AdtId::EnumId(e)) => { + // enum variant + tested_by!(can_import_enum_variant); + let enum_data = db.enum_data(e); + match enum_data.variant(&segment.name) { + Some(local_id) => { + let variant = EnumVariantId { parent: e, local_id }; + 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), + 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.get(name).map_or_else(PerNs::none, |res| res.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 DefDatabase2, + module: CrateModuleId, + name: &Name, + ) -> PerNs { + // Resolve in: + // - legacy scope of macro + // - current module / scope + // - extern prelude + // - std prelude + let from_legacy_macro = + self[module].scope.get_legacy_macro(name).map_or_else(PerNs::none, PerNs::macros); + let from_scope = self[module].scope.get(name).map_or_else(PerNs::none, |res| res.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_legacy_macro.or(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 DefDatabase2, name: &Name) -> PerNs { + if let Some(prelude) = self.prelude { + let keep; + let def_map = if prelude.krate == self.krate { + self + } else { + // Extend lifetime + keep = db.crate_def_map(prelude.krate); + &keep + }; + def_map[prelude.module_id].scope.get(name).map_or_else(PerNs::none, |res| res.def) + } else { + PerNs::none() + } + } +} + +mod diagnostics { + use hir_expand::diagnostics::DiagnosticSink; + use ra_syntax::{ast, AstPtr}; + use relative_path::RelativePathBuf; + + use crate::{db::DefDatabase2, diagnostics::UnresolvedModule, nameres::CrateModuleId, AstId}; + + #[derive(Debug, PartialEq, Eq)] + pub(super) enum DefDiagnostic { + UnresolvedModule { + module: CrateModuleId, + declaration: AstId<ast::Module>, + candidate: RelativePathBuf, + }, + } + + impl DefDiagnostic { + pub(super) fn add_to( + &self, + db: &impl DefDatabase2, + target_module: CrateModuleId, + sink: &mut DiagnosticSink, + ) { + match self { + DefDiagnostic::UnresolvedModule { module, declaration, candidate } => { + if *module != target_module { + return; + } + let decl = declaration.to_node(db); + sink.push(UnresolvedModule { + file: declaration.file_id(), + decl: AstPtr::new(&decl), + candidate: candidate.clone(), + }) + } + } + } + } +} diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs similarity index 86% rename from crates/ra_hir/src/nameres/collector.rs rename to crates/ra_hir_def/src/nameres/collector.rs index ee0a4c99fe0..3b61d9895bc 100644 --- a/crates/ra_hir/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs @@ -1,45 +1,49 @@ //! FIXME: write short doc here -use hir_def::{ - attr::Attr, - nameres::{mod_resolution::ModDir, raw}, +use hir_expand::{ + name::{self, AsName, Name}, + HirFileId, MacroCallId, MacroCallLoc, MacroDefId, MacroFileKind, }; -use hir_expand::name; use ra_cfg::CfgOptions; -use ra_db::FileId; +use ra_db::{CrateId, FileId}; use ra_syntax::{ast, SmolStr}; use rustc_hash::FxHashMap; use test_utils::tested_by; use crate::{ - db::DefDatabase, - ids::{AstItemDef, LocationCtx, MacroCallId, MacroCallLoc, MacroDefId, MacroFileKind}, + attr::Attr, + db::DefDatabase2, nameres::{ - diagnostics::DefDiagnostic, Crate, CrateDefMap, CrateModuleId, ModuleData, ModuleDef, - PerNs, ReachedFixedPoint, Resolution, ResolveMode, + diagnostics::DefDiagnostic, mod_resolution::ModDir, per_ns::PerNs, raw, CrateDefMap, + ModuleData, ReachedFixedPoint, Resolution, ResolveMode, }, - Adt, AstId, Const, Enum, Function, HirFileId, MacroDef, Module, Name, Path, PathKind, Static, - Struct, Trait, TypeAlias, Union, + path::{Path, PathKind}, + AdtId, AstId, AstItemDef, ConstId, CrateModuleId, EnumId, EnumVariantId, FunctionId, + LocationCtx, ModuleDefId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, UnionId, }; -pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { +pub(super) fn collect_defs(db: &impl DefDatabase2, mut def_map: CrateDefMap) -> CrateDefMap { + let crate_graph = db.crate_graph(); + // 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()); - } + for dep in crate_graph.dependencies(def_map.krate) { + let dep_def_map = db.crate_def_map(dep.crate_id); + log::debug!("crate dep {:?} -> {:?}", dep.name, dep.crate_id); + def_map.extern_prelude.insert( + dep.as_name(), + ModuleId { krate: dep.crate_id, module_id: dep_def_map.root }.into(), + ); + // look for the prelude if def_map.prelude.is_none() { - let map = db.crate_def_map(dep.krate); + let map = db.crate_def_map(dep.crate_id); if map.prelude.is_some() { def_map.prelude = map.prelude; } } } - let crate_graph = db.crate_graph(); - let cfg_options = crate_graph.cfg_options(def_map.krate().crate_id()); + let cfg_options = crate_graph.cfg_options(def_map.krate); let mut collector = DefCollector { db, @@ -101,11 +105,11 @@ struct DefCollector<'a, DB> { impl<DB> DefCollector<'_, DB> where - DB: DefDatabase, + DB: DefDatabase2, { 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 file_id = crate_graph.crate_root(self.def_map.krate); let raw_items = self.db.raw_items(file_id.into()); let module_id = self.def_map.root; self.def_map.modules[module_id].definition = Some(file_id); @@ -168,7 +172,7 @@ where &mut self, module_id: CrateModuleId, name: Name, - macro_: MacroDef, + macro_: MacroDefId, export: bool, ) { // Textual scoping @@ -189,7 +193,7 @@ where /// the definition of current module. /// And also, `macro_use` on a module will import all legacy macros visable inside to /// current legacy scope, with possible shadowing. - fn define_legacy_macro(&mut self, module_id: CrateModuleId, name: Name, macro_: MacroDef) { + fn define_legacy_macro(&mut self, module_id: CrateModuleId, name: Name, macro_: MacroDefId) { // Always shadowing self.def_map.modules[module_id].scope.legacy_macros.insert(name, macro_); } @@ -213,9 +217,9 @@ where .expect("extern crate should have been desugared to one-element path"), ); - if let Some(ModuleDef::Module(m)) = res.take_types() { + if let Some(ModuleDefId::ModuleId(m)) = res.take_types() { tested_by!(macro_rules_from_other_crates_are_visible_with_macro_use); - self.import_all_macros_exported(current_module_id, m.krate()); + self.import_all_macros_exported(current_module_id, m.krate); } } @@ -224,7 +228,7 @@ where /// Exported macros are just all macros in the root module scope. /// Note that it contains not only all `#[macro_export]` macros, but also all aliases /// created by `use` in the root module, ignoring the visibility of `use`. - fn import_all_macros_exported(&mut self, current_module_id: CrateModuleId, krate: Crate) { + fn import_all_macros_exported(&mut self, current_module_id: CrateModuleId, krate: CrateId) { let def_map = self.db.crate_def_map(krate); for (name, def) in def_map[def_map.root].scope.macros() { // `macro_use` only bring things into legacy scope. @@ -288,15 +292,15 @@ where if import.is_glob { log::debug!("glob import: {:?}", import); match def.take_types() { - Some(ModuleDef::Module(m)) => { + Some(ModuleDefId::ModuleId(m)) => { if import.is_prelude { tested_by!(std_prelude); self.def_map.prelude = Some(m); - } else if m.krate() != self.def_map.krate { + } 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.id.module_id].scope; + let item_map = self.db.crate_def_map(m.krate); + let scope = &item_map[m.module_id].scope; // Module scoped macros is included let items = scope @@ -310,7 +314,7 @@ where // glob import from same crate => we do an initial // import, and then need to propagate any further // additions - let scope = &self.def_map[m.id.module_id].scope; + let scope = &self.def_map[m.module_id].scope; // Module scoped macros is included let items = scope @@ -322,23 +326,25 @@ where self.update(module_id, Some(import_id), &items); // record the glob import in case we add further items self.glob_imports - .entry(m.id.module_id) + .entry(m.module_id) .or_default() .push((module_id, import_id)); } } - Some(ModuleDef::Adt(Adt::Enum(e))) => { + Some(ModuleDefId::AdtId(AdtId::EnumId(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 enum_data = self.db.enum_data(e); + let resolutions = enum_data + .variants + .iter() + .filter_map(|(local_id, variant_data)| { + let name = variant_data.name.clone()?; + let variant = EnumVariantId { parent: e, local_id }; let res = Resolution { def: PerNs::both(variant.into(), variant.into()), import: Some(import_id), }; - let name = variant.name(self.db)?; Some((name, res)) }) .collect::<Vec<_>>(); @@ -451,8 +457,8 @@ where ); if let Some(def) = resolved_res.resolved_def.get_macros() { - let call_id = self.db.intern_macro(MacroCallLoc { def: def.id, ast_id: *ast_id }); - resolved.push((*module_id, call_id, def.id)); + let call_id = self.db.intern_macro(MacroCallLoc { def, ast_id: *ast_id }); + resolved.push((*module_id, call_id, def)); res = ReachedFixedPoint::No; return false; } @@ -517,7 +523,7 @@ struct ModCollector<'a, D> { impl<DB> ModCollector<'_, &'_ mut DefCollector<'_, DB>> where - DB: DefDatabase, + DB: DefDatabase2, { fn collect(&mut self, items: &[raw::RawItem]) { // Note: don't assert that inserted value is fresh: it's simply not true @@ -526,10 +532,9 @@ where // Prelude module is always considered to be `#[macro_use]`. if let Some(prelude_module) = self.def_collector.def_map.prelude { - if prelude_module.krate() != self.def_collector.def_map.krate { + if prelude_module.krate != self.def_collector.def_map.krate { tested_by!(prelude_is_macro_use); - self.def_collector - .import_all_macros_exported(self.module_id, prelude_module.krate()); + self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); } } @@ -635,7 +640,9 @@ where modules[res].scope.legacy_macros = modules[self.module_id].scope.legacy_macros.clone(); modules[self.module_id].children.insert(name.clone(), res); let resolution = Resolution { - def: PerNs::types(Module::new(self.def_collector.def_map.krate, res).into()), + def: PerNs::types( + ModuleId { krate: self.def_collector.def_map.krate, module_id: res }.into(), + ), import: None, }; self.def_collector.update(self.module_id, None, &[(name, resolution)]); @@ -643,30 +650,32 @@ where } fn define_def(&mut self, def: &raw::DefData) { - let module = Module::new(self.def_collector.def_map.krate, self.module_id); - let ctx = LocationCtx::new(self.def_collector.db, module.id, self.file_id); + let module = + ModuleId { krate: self.def_collector.def_map.krate, module_id: self.module_id }; + let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id); - macro_rules! def { - ($kind:ident, $ast_id:ident) => { - $kind { id: AstItemDef::from_ast_id(ctx, $ast_id) }.into() - }; - } let name = def.name.clone(); let def: PerNs = match def.kind { - raw::DefKind::Function(ast_id) => PerNs::values(def!(Function, ast_id)), + raw::DefKind::Function(ast_id) => { + PerNs::values(FunctionId::from_ast_id(ctx, ast_id).into()) + } raw::DefKind::Struct(ast_id) => { - let s = def!(Struct, ast_id); + let s = StructId::from_ast_id(ctx, ast_id).into(); PerNs::both(s, s) } raw::DefKind::Union(ast_id) => { - let s = def!(Union, ast_id); + let s = UnionId::from_ast_id(ctx, ast_id).into(); PerNs::both(s, s) } - raw::DefKind::Enum(ast_id) => PerNs::types(def!(Enum, ast_id)), - raw::DefKind::Const(ast_id) => PerNs::values(def!(Const, ast_id)), - raw::DefKind::Static(ast_id) => PerNs::values(def!(Static, ast_id)), - raw::DefKind::Trait(ast_id) => PerNs::types(def!(Trait, ast_id)), - raw::DefKind::TypeAlias(ast_id) => PerNs::types(def!(TypeAlias, ast_id)), + raw::DefKind::Enum(ast_id) => PerNs::types(EnumId::from_ast_id(ctx, ast_id).into()), + raw::DefKind::Const(ast_id) => PerNs::values(ConstId::from_ast_id(ctx, ast_id).into()), + raw::DefKind::Static(ast_id) => { + PerNs::values(StaticId::from_ast_id(ctx, ast_id).into()) + } + raw::DefKind::Trait(ast_id) => PerNs::types(TraitId::from_ast_id(ctx, ast_id).into()), + raw::DefKind::TypeAlias(ast_id) => { + PerNs::types(TypeAliasId::from_ast_id(ctx, ast_id).into()) + } }; let resolution = Resolution { def, import: None }; self.def_collector.update(self.module_id, None, &[(name, resolution)]) @@ -678,10 +687,8 @@ where // Case 1: macro rules, define a macro in crate-global mutable scope if is_macro_rules(&mac.path) { if let Some(name) = &mac.name { - let macro_id = - MacroDefId { ast_id, krate: self.def_collector.def_map.krate.crate_id }; - let macro_ = MacroDef { id: macro_id }; - self.def_collector.define_macro(self.module_id, name.clone(), macro_, mac.export); + let macro_id = MacroDefId { ast_id, krate: self.def_collector.def_map.krate }; + self.def_collector.define_macro(self.module_id, name.clone(), macro_id, mac.export); } return; } @@ -691,10 +698,10 @@ where if let Some(macro_def) = mac.path.as_ident().and_then(|name| { self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) }) { - let def = macro_def.id; - let macro_call_id = self.def_collector.db.intern_macro(MacroCallLoc { def, ast_id }); + let macro_call_id = + self.def_collector.db.intern_macro(MacroCallLoc { def: macro_def, ast_id }); - self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, def); + self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, macro_def); return; } @@ -733,15 +740,16 @@ fn is_macro_rules(path: &Path) -> bool { #[cfg(test)] mod tests { - use ra_db::SourceDatabase; - - use super::*; - use crate::{db::DefDatabase, mock::MockDatabase, Crate}; use ra_arena::Arena; + use ra_db::{fixture::WithFixture, SourceDatabase}; use rustc_hash::FxHashSet; + use crate::{db::DefDatabase2, test_db::TestDB}; + + use super::*; + fn do_collect_defs( - db: &impl DefDatabase, + db: &impl DefDatabase2, def_map: CrateDefMap, monitor: MacroStackMonitor, ) -> CrateDefMap { @@ -760,12 +768,11 @@ mod tests { } fn do_limited_resolve(code: &str, limit: u32, poison_limit: u32) -> CrateDefMap { - let (db, _source_root, _) = MockDatabase::with_single_file(&code); - let crate_id = db.crate_graph().iter().next().unwrap(); - let krate = Crate { crate_id }; + let (db, _file_id) = TestDB::with_single_file(&code); + let krate = db.crate_graph().iter().next().unwrap(); let def_map = { - let edition = krate.edition(&db); + let edition = db.crate_graph().edition(krate); let mut modules: Arena<CrateModuleId, ModuleData> = Arena::default(); let root = modules.alloc(ModuleData::default()); CrateDefMap { diff --git a/crates/ra_hir/src/nameres/per_ns.rs b/crates/ra_hir_def/src/nameres/per_ns.rs similarity index 75% rename from crates/ra_hir/src/nameres/per_ns.rs rename to crates/ra_hir_def/src/nameres/per_ns.rs index 0da6789de60..298b0b0c724 100644 --- a/crates/ra_hir/src/nameres/per_ns.rs +++ b/crates/ra_hir_def/src/nameres/per_ns.rs @@ -1,6 +1,8 @@ //! FIXME: write short doc here -use crate::{MacroDef, ModuleDef}; +use hir_expand::MacroDefId; + +use crate::ModuleDefId; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Namespace { @@ -12,11 +14,11 @@ pub enum Namespace { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PerNs { - pub types: Option<ModuleDef>, - pub values: Option<ModuleDef>, + pub types: Option<ModuleDefId>, + pub values: Option<ModuleDefId>, /// Since macros has different type, many methods simply ignore it. /// We can only use special method like `get_macros` to access it. - pub macros: Option<MacroDef>, + pub macros: Option<MacroDefId>, } impl Default for PerNs { @@ -30,19 +32,19 @@ impl PerNs { PerNs { types: None, values: None, macros: None } } - pub fn values(t: ModuleDef) -> PerNs { + pub fn values(t: ModuleDefId) -> PerNs { PerNs { types: None, values: Some(t), macros: None } } - pub fn types(t: ModuleDef) -> PerNs { + pub fn types(t: ModuleDefId) -> PerNs { PerNs { types: Some(t), values: None, macros: None } } - pub fn both(types: ModuleDef, values: ModuleDef) -> PerNs { + pub fn both(types: ModuleDefId, values: ModuleDefId) -> PerNs { PerNs { types: Some(types), values: Some(values), macros: None } } - pub fn macros(macro_: MacroDef) -> PerNs { + pub fn macros(macro_: MacroDefId) -> PerNs { PerNs { types: None, values: None, macros: Some(macro_) } } @@ -54,15 +56,15 @@ impl PerNs { self.types.is_some() && self.values.is_some() && self.macros.is_some() } - pub fn take_types(self) -> Option<ModuleDef> { + pub fn take_types(self) -> Option<ModuleDefId> { self.types } - pub fn take_values(self) -> Option<ModuleDef> { + pub fn take_values(self) -> Option<ModuleDefId> { self.values } - pub fn get_macros(&self) -> Option<MacroDef> { + pub fn get_macros(&self) -> Option<MacroDefId> { self.macros } diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs index 86c05d6028e..cb47fa317d2 100644 --- a/crates/ra_hir_def/src/nameres/raw.rs +++ b/crates/ra_hir_def/src/nameres/raw.rs @@ -14,6 +14,7 @@ use ra_syntax::{ ast::{self, AttrsOwner, NameOwner}, AstNode, AstPtr, SourceFile, }; +use test_utils::tested_by; use crate::{attr::Attr, db::DefDatabase2, path::Path, FileAstId, HirFileId, ModuleSource, Source}; @@ -297,8 +298,7 @@ impl RawItemsCollector { self.push_item(current_module, attrs, RawItemKind::Module(item)); return; } - // FIXME: restore this mark once we complete hir splitting - // tested_by!(name_res_works_for_broken_modules); + tested_by!(name_res_works_for_broken_modules); } fn add_use_item(&mut self, current_module: Option<Module>, use_item: ast::UseItem) { diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir_def/src/nameres/tests.rs similarity index 73% rename from crates/ra_hir/src/nameres/tests.rs rename to crates/ra_hir_def/src/nameres/tests.rs index 8c6b40aaf2a..52bd0aa91e8 100644 --- a/crates/ra_hir/src/nameres/tests.rs +++ b/crates/ra_hir_def/src/nameres/tests.rs @@ -1,35 +1,31 @@ -mod macros; mod globs; mod incremental; -mod primitives; +mod macros; mod mod_resolution; +mod primitives; use std::sync::Arc; use insta::assert_snapshot; -use ra_db::SourceDatabase; +use ra_db::{fixture::WithFixture, SourceDatabase}; use test_utils::covers; -use crate::{ - mock::{CrateGraphFixture, MockDatabase}, - Crate, -}; +use crate::{db::DefDatabase2, nameres::*, test_db::TestDB, CrateModuleId}; -use super::*; +fn def_map(fixtute: &str) -> String { + let dm = compute_crate_def_map(fixtute); + render_crate_def_map(&dm) +} -fn compute_crate_def_map(fixture: &str, graph: Option<CrateGraphFixture>) -> Arc<CrateDefMap> { - 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 }; +fn compute_crate_def_map(fixture: &str) -> Arc<CrateDefMap> { + let db = TestDB::with_files(fixture); + let krate = db.crate_graph().iter().next().unwrap(); 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); + go(&mut buf, map, "\ncrate", map.root()); return buf.trim().to_string(); fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: CrateModuleId) { @@ -70,16 +66,6 @@ fn render_crate_def_map(map: &CrateDefMap) -> String { } } -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(fixture: &str, graph: CrateGraphFixture) -> String { - let dm = compute_crate_def_map(fixture, Some(graph)); - render_crate_def_map(&dm) -} - #[test] fn crate_def_map_smoke_test() { let map = def_map( @@ -234,12 +220,12 @@ fn re_exports() { #[test] fn std_prelude() { covers!(std_prelude); - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:test_crate use Foo::*; - //- /lib.rs + //- /lib.rs crate:test_crate mod prelude; #[prelude_import] use prelude::*; @@ -247,10 +233,6 @@ fn std_prelude() { //- /prelude.rs pub enum Foo { Bar, Baz }; ", - crate_graph! { - "main": ("/main.rs", ["test_crate"]), - "test_crate": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate @@ -279,9 +261,9 @@ fn can_import_enum_variant() { #[test] fn edition_2015_imports() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:other_crate edition:2015 mod foo; mod bar; @@ -292,13 +274,9 @@ fn edition_2015_imports() { use bar::Bar; use other_crate::FromLib; - //- /lib.rs + //- /lib.rs crate:other_crate edition:2018 struct FromLib; ", - crate_graph! { - "main": ("/main.rs", "2015", ["other_crate"]), - "other_crate": ("/lib.rs", "2018", []), - }, ); assert_snapshot!(map, @r###" @@ -343,18 +321,14 @@ fn item_map_using_self() { #[test] fn item_map_across_crates() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:test_crate use test_crate::Baz; - //- /lib.rs + //- /lib.rs crate:test_crate pub struct Baz; ", - crate_graph! { - "main": ("/main.rs", ["test_crate"]), - "test_crate": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" @@ -365,9 +339,9 @@ fn item_map_across_crates() { #[test] fn extern_crate_rename() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:alloc extern crate alloc as alloc_crate; mod alloc; @@ -376,13 +350,9 @@ fn extern_crate_rename() { //- /sync.rs use alloc_crate::Arc; - //- /lib.rs + //- /lib.rs crate:alloc struct Arc; ", - crate_graph! { - "main": ("/main.rs", ["alloc"]), - "alloc": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" @@ -397,9 +367,9 @@ fn extern_crate_rename() { #[test] fn extern_crate_rename_2015_edition() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:alloc edition:2015 extern crate alloc as alloc_crate; mod alloc; @@ -408,13 +378,9 @@ fn extern_crate_rename_2015_edition() { //- /sync.rs use alloc_crate::Arc; - //- /lib.rs + //- /lib.rs crate:alloc struct Arc; ", - crate_graph! { - "main": ("/main.rs", "2015", ["alloc"]), - "alloc": ("/lib.rs", []), - }, ); assert_snapshot!(map, @@ -431,24 +397,21 @@ fn extern_crate_rename_2015_edition() { #[test] fn import_across_source_roots() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /lib.rs + //- /main.rs crate:main deps:test_crate + use test_crate::a::b::C; + + //- root /test_crate/ + + //- /test_crate/lib.rs crate:test_crate 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!(map, @r###" @@ -459,12 +422,12 @@ fn import_across_source_roots() { #[test] fn reexport_across_crates() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:test_crate use test_crate::Baz; - //- /lib.rs + //- /lib.rs crate:test_crate pub use foo::Baz; mod foo; @@ -472,10 +435,6 @@ fn reexport_across_crates() { //- /foo.rs pub struct Baz; ", - crate_graph! { - "main": ("/main.rs", ["test_crate"]), - "test_crate": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" @@ -486,19 +445,15 @@ fn reexport_across_crates() { #[test] fn values_dont_shadow_extern_crates() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:foo fn foo() {} use foo::Bar; - //- /foo/lib.rs + //- /foo/lib.rs crate:foo pub struct Bar; ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/foo/lib.rs", []), - }, ); assert_snapshot!(map, @r###" @@ -510,11 +465,12 @@ fn values_dont_shadow_extern_crates() { #[test] fn cfg_not_test() { - let map = def_map_with_crate_graph( + let map = def_map( r#" - //- /main.rs + //- /main.rs crate:main deps:std use {Foo, Bar, Baz}; - //- /lib.rs + + //- /lib.rs crate:std #[prelude_import] pub use self::prelude::*; mod prelude { @@ -526,10 +482,6 @@ fn cfg_not_test() { pub struct Baz; } "#, - crate_graph! { - "main": ("/main.rs", ["std"]), - "std": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" @@ -542,11 +494,12 @@ fn cfg_not_test() { #[test] fn cfg_test() { - let map = def_map_with_crate_graph( + let map = def_map( r#" - //- /main.rs + //- /main.rs crate:main deps:std use {Foo, Bar, Baz}; - //- /lib.rs + + //- /lib.rs crate:std cfg:test,feature=foo,feature=bar,opt=42 #[prelude_import] pub use self::prelude::*; mod prelude { @@ -558,15 +511,6 @@ fn cfg_test() { pub struct Baz; } "#, - crate_graph! { - "main": ("/main.rs", ["std"]), - "std": ("/lib.rs", [], cfg = { - "test", - "feature" = "foo", - "feature" = "bar", - "opt" = "42", - }), - }, ); assert_snapshot!(map, @r###" diff --git a/crates/ra_hir/src/nameres/tests/globs.rs b/crates/ra_hir_def/src/nameres/tests/globs.rs similarity index 88% rename from crates/ra_hir/src/nameres/tests/globs.rs rename to crates/ra_hir_def/src/nameres/tests/globs.rs index 7ac22b47b80..5b03fe36504 100644 --- a/crates/ra_hir/src/nameres/tests/globs.rs +++ b/crates/ra_hir_def/src/nameres/tests/globs.rs @@ -76,18 +76,14 @@ fn glob_2() { #[test] fn glob_across_crates() { covers!(glob_across_crates); - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:test_crate use test_crate::*; - //- /lib.rs + //- /lib.rs crate:test_crate pub struct Baz; ", - crate_graph! { - "main": ("/main.rs", ["test_crate"]), - "test_crate": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate diff --git a/crates/ra_hir/src/nameres/tests/incremental.rs b/crates/ra_hir_def/src/nameres/tests/incremental.rs similarity index 74% rename from crates/ra_hir/src/nameres/tests/incremental.rs rename to crates/ra_hir_def/src/nameres/tests/incremental.rs index af9c3976026..80dcec62f0f 100644 --- a/crates/ra_hir/src/nameres/tests/incremental.rs +++ b/crates/ra_hir_def/src/nameres/tests/incremental.rs @@ -1,13 +1,12 @@ -use super::*; - use std::sync::Arc; use ra_db::{SourceDatabase, SourceDatabaseExt}; +use super::*; + 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 (mut db, pos) = TestDB::with_position(initial); + let krate = db.crate_graph().iter().next().unwrap(); { let events = db.log_executed(|| { db.crate_def_map(krate); @@ -92,7 +91,7 @@ fn adding_inner_items_should_not_invalidate_def_map() { #[test] fn typing_inside_a_macro_should_not_invalidate_def_map() { - let (mut db, pos) = MockDatabase::with_position( + let (mut db, pos) = TestDB::with_position( " //- /lib.rs macro_rules! m { @@ -112,15 +111,12 @@ fn typing_inside_a_macro_should_not_invalidate_def_map() { m!(X); ", ); + let krate = db.crate_graph().iter().next().unwrap(); { let events = db.log_executed(|| { - let src = crate::Source { - file_id: pos.file_id.into(), - ast: crate::ModuleSource::new(&db, Some(pos.file_id), None), - }; - let module = crate::Module::from_definition(&db, src).unwrap(); - let decls = module.declarations(&db); - assert_eq!(decls.len(), 18); + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.items.len(), 1); }); assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) } @@ -128,13 +124,9 @@ fn typing_inside_a_macro_should_not_invalidate_def_map() { { let events = db.log_executed(|| { - let src = crate::Source { - file_id: pos.file_id.into(), - ast: crate::ModuleSource::new(&db, Some(pos.file_id), None), - }; - let module = crate::Module::from_definition(&db, src).unwrap(); - let decls = module.declarations(&db); - assert_eq!(decls.len(), 18); + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.items.len(), 1); }); assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) } diff --git a/crates/ra_hir/src/nameres/tests/macros.rs b/crates/ra_hir_def/src/nameres/tests/macros.rs similarity index 86% rename from crates/ra_hir/src/nameres/tests/macros.rs rename to crates/ra_hir_def/src/nameres/tests/macros.rs index 4f52ad2c543..704065633a5 100644 --- a/crates/ra_hir/src/nameres/tests/macros.rs +++ b/crates/ra_hir_def/src/nameres/tests/macros.rs @@ -71,16 +71,16 @@ fn macro_rules_can_define_modules() { #[test] fn macro_rules_from_other_crates_are_visible() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:foo foo::structs!(Foo, Bar) mod bar; //- /bar.rs use crate::*; - //- /lib.rs + //- /lib.rs crate:foo #[macro_export] macro_rules! structs { ($($i:ident),*) => { @@ -88,10 +88,6 @@ fn macro_rules_from_other_crates_are_visible() { } } ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate @@ -108,16 +104,16 @@ fn macro_rules_from_other_crates_are_visible() { #[test] fn macro_rules_export_with_local_inner_macros_are_visible() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:foo foo::structs!(Foo, Bar) mod bar; //- /bar.rs use crate::*; - //- /lib.rs + //- /lib.rs crate:foo #[macro_export(local_inner_macros)] macro_rules! structs { ($($i:ident),*) => { @@ -125,10 +121,6 @@ fn macro_rules_export_with_local_inner_macros_are_visible() { } } ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate @@ -145,9 +137,9 @@ fn macro_rules_export_with_local_inner_macros_are_visible() { #[test] fn unexpanded_macro_should_expand_by_fixedpoint_loop() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:foo macro_rules! baz { () => { use foo::bar; @@ -158,7 +150,7 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() { bar!(); baz!(); - //- /lib.rs + //- /lib.rs crate:foo #[macro_export] macro_rules! foo { () => { @@ -172,10 +164,6 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() { } } ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate @@ -188,9 +176,9 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() { #[test] fn macro_rules_from_other_crates_are_visible_with_macro_use() { covers!(macro_rules_from_other_crates_are_visible_with_macro_use); - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:foo structs!(Foo); structs_priv!(Bar); structs_not_exported!(MacroNotResolved1); @@ -205,7 +193,7 @@ fn macro_rules_from_other_crates_are_visible_with_macro_use() { structs!(Baz); crate::structs!(MacroNotResolved3); - //- /lib.rs + //- /lib.rs crate:foo #[macro_export] macro_rules! structs { ($i:ident) => { struct $i; } @@ -222,10 +210,6 @@ fn macro_rules_from_other_crates_are_visible_with_macro_use() { } } ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate @@ -242,9 +226,9 @@ fn macro_rules_from_other_crates_are_visible_with_macro_use() { #[test] fn prelude_is_macro_use() { covers!(prelude_is_macro_use); - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:foo structs!(Foo); structs_priv!(Bar); structs_outside!(Out); @@ -256,7 +240,7 @@ fn prelude_is_macro_use() { structs!(Baz); crate::structs!(MacroNotResolved3); - //- /lib.rs + //- /lib.rs crate:foo #[prelude_import] use self::prelude::*; @@ -279,10 +263,6 @@ fn prelude_is_macro_use() { ($i:ident) => { struct $i; } } ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate @@ -447,16 +427,16 @@ fn type_value_macro_live_in_different_scopes() { #[test] fn macro_use_can_be_aliased() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:foo #[macro_use] extern crate foo; foo!(Direct); bar!(Alias); - //- /lib.rs + //- /lib.rs crate:foo use crate::foo as bar; mod m { @@ -466,10 +446,6 @@ fn macro_use_can_be_aliased() { } } ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate @@ -533,9 +509,9 @@ fn path_qualified_macros() { fn macro_dollar_crate_is_correct_in_item() { covers!(macro_dollar_crate_self); covers!(macro_dollar_crate_other); - let map = def_map_with_crate_graph( + let map = def_map( " - //- /main.rs + //- /main.rs crate:main deps:foo #[macro_use] extern crate foo; @@ -554,7 +530,7 @@ fn macro_dollar_crate_is_correct_in_item() { not_current1!(); foo::not_current2!(); - //- /lib.rs + //- /lib.rs crate:foo mod m { #[macro_export] macro_rules! not_current1 { @@ -574,10 +550,6 @@ fn macro_dollar_crate_is_correct_in_item() { struct Bar; struct Baz; ", - crate_graph! { - "main": ("/main.rs", ["foo"]), - "foo": ("/lib.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate @@ -596,12 +568,12 @@ fn macro_dollar_crate_is_correct_in_item() { fn macro_dollar_crate_is_correct_in_indirect_deps() { covers!(macro_dollar_crate_other); // From std - let map = def_map_with_crate_graph( + let map = def_map( r#" - //- /main.rs + //- /main.rs crate:main deps:std foo!(); - //- /std.rs + //- /std.rs crate:std deps:core #[prelude_import] use self::prelude::*; @@ -612,7 +584,7 @@ fn macro_dollar_crate_is_correct_in_indirect_deps() { #[macro_use] mod std_macros; - //- /core.rs + //- /core.rs crate:core #[macro_export] macro_rules! foo { () => { @@ -622,11 +594,6 @@ fn macro_dollar_crate_is_correct_in_indirect_deps() { pub struct bar; "#, - crate_graph! { - "main": ("/main.rs", ["std"]), - "std": ("/std.rs", ["core"]), - "core": ("/core.rs", []), - }, ); assert_snapshot!(map, @r###" ⋮crate diff --git a/crates/ra_hir/src/nameres/tests/mod_resolution.rs b/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs similarity index 93% rename from crates/ra_hir/src/nameres/tests/mod_resolution.rs rename to crates/ra_hir_def/src/nameres/tests/mod_resolution.rs index abfe8b1c346..dee364a1422 100644 --- a/crates/ra_hir/src/nameres/tests/mod_resolution.rs +++ b/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs @@ -2,7 +2,7 @@ use super::*; #[test] fn name_res_works_for_broken_modules() { - // covers!(name_res_works_for_broken_modules); + covers!(name_res_works_for_broken_modules); let map = def_map( " //- /lib.rs @@ -54,18 +54,15 @@ fn nested_module_resolution() { #[test] fn module_resolution_works_for_non_standard_filenames() { - let map = def_map_with_crate_graph( + let map = def_map( " - //- /my_library.rs + //- /my_library.rs crate:my_library mod foo; use self::foo::Bar; //- /foo/mod.rs pub struct Bar; ", - crate_graph! { - "my_library": ("/my_library.rs", []), - }, ); assert_snapshot!(map, @r###" @@ -650,7 +647,7 @@ fn module_resolution_decl_inside_inline_module_in_non_crate_root_2() { #[test] fn unresolved_module_diagnostics() { - let diagnostics = MockDatabase::with_files( + let db = TestDB::with_files( r" //- /lib.rs mod foo; @@ -658,11 +655,37 @@ fn unresolved_module_diagnostics() { mod baz {} //- /foo.rs ", - ) - .diagnostics(); + ); + let krate = db.crate_graph().iter().next().unwrap(); - assert_snapshot!(diagnostics, @r###" - "mod bar;": unresolved module + let crate_def_map = db.crate_def_map(krate); + + insta::assert_debug_snapshot!( + crate_def_map.diagnostics, + @r###" + [ + UnresolvedModule { + module: CrateModuleId( + 0, + ), + declaration: AstId { + file_id: HirFileId( + FileId( + FileId( + 0, + ), + ), + ), + file_ast_id: FileAstId { + raw: ErasedFileAstId( + 1, + ), + _ty: PhantomData, + }, + }, + candidate: "bar.rs", + }, + ] "### ); } diff --git a/crates/ra_hir/src/nameres/tests/primitives.rs b/crates/ra_hir_def/src/nameres/tests/primitives.rs similarity index 100% rename from crates/ra_hir/src/nameres/tests/primitives.rs rename to crates/ra_hir_def/src/nameres/tests/primitives.rs diff --git a/crates/ra_hir_def/src/test_db.rs b/crates/ra_hir_def/src/test_db.rs new file mode 100644 index 00000000000..f91a5b41df2 --- /dev/null +++ b/crates/ra_hir_def/src/test_db.rs @@ -0,0 +1,76 @@ +//! Database used for testing `hir_def`. + +use std::{ + panic, + sync::{Arc, Mutex}, +}; + +use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate}; +use relative_path::RelativePath; + +#[salsa::database( + ra_db::SourceDatabaseExtStorage, + ra_db::SourceDatabaseStorage, + hir_expand::db::AstDatabaseStorage, + crate::db::InternDatabaseStorage, + crate::db::DefDatabase2Storage +)] +#[derive(Debug, Default)] +pub struct TestDB { + runtime: salsa::Runtime<TestDB>, + events: Mutex<Option<Vec<salsa::Event<TestDB>>>>, +} + +impl salsa::Database for TestDB { + fn salsa_runtime(&self) -> &salsa::Runtime<Self> { + &self.runtime + } + + fn salsa_event(&self, event: impl Fn() -> salsa::Event<TestDB>) { + let mut events = self.events.lock().unwrap(); + if let Some(events) = &mut *events { + events.push(event()); + } + } +} + +impl panic::RefUnwindSafe for TestDB {} + +impl FileLoader for TestDB { + fn file_text(&self, file_id: FileId) -> Arc<String> { + FileLoaderDelegate(self).file_text(file_id) + } + fn resolve_relative_path( + &self, + anchor: FileId, + relative_path: &RelativePath, + ) -> Option<FileId> { + FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) + } + fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { + FileLoaderDelegate(self).relevant_crates(file_id) + } +} + +impl TestDB { + pub fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<TestDB>> { + *self.events.lock().unwrap() = Some(Vec::new()); + f(); + self.events.lock().unwrap().take().unwrap() + } + + pub fn log_executed(&self, f: impl FnOnce()) -> Vec<String> { + let events = self.log(f); + events + .into_iter() + .filter_map(|e| match e.kind { + // This pretty horrible, but `Debug` is the only way to inspect + // QueryDescriptor at the moment. + salsa::EventKind::WillExecute { database_key } => { + Some(format!("{:?}", database_key)) + } + _ => None, + }) + .collect() + } +} diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs index 9ac9768afb2..09ca401794e 100644 --- a/crates/ra_ide_api/src/completion/complete_path.rs +++ b/crates/ra_ide_api/src/completion/complete_path.rs @@ -18,15 +18,15 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { match def { hir::ModuleDef::Module(module) => { let module_scope = module.scope(ctx.db); - for (name, res) in module_scope.entries() { - if let Some(hir::ModuleDef::BuiltinType(..)) = res.def.take_types() { + for (name, def, import) in module_scope { + if let hir::ScopeDef::ModuleDef(hir::ModuleDef::BuiltinType(..)) = def { if ctx.use_item_syntax.is_some() { tested_by!(dont_complete_primitive_in_use); continue; } } if Some(module) == ctx.module { - if let Some(import) = res.import { + if let Some(import) = import { if let Either::A(use_tree) = module.import_source(ctx.db, import) { if use_tree.syntax().text_range().contains_inclusive(ctx.offset) { // for `use self::foo<|>`, don't suggest `foo` as a completion @@ -36,7 +36,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { } } } - acc.add_resolution(ctx, name.to_string(), &res.def.into()); + acc.add_resolution(ctx, name.to_string(), &def); } } hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => { diff --git a/xtask/tests/tidy-tests/docs.rs b/xtask/tests/tidy-tests/docs.rs index 6a629ce63e8..227937f462a 100644 --- a/xtask/tests/tidy-tests/docs.rs +++ b/xtask/tests/tidy-tests/docs.rs @@ -36,6 +36,7 @@ fn is_hidden(entry: &DirEntry) -> bool { fn no_docs_comments() { let crates = project_root().join("crates"); let iter = WalkDir::new(crates); + let mut missing_docs = Vec::new(); for f in iter.into_iter().filter_entry(|e| !is_hidden(e)) { let f = f.unwrap(); if f.file_type().is_dir() { @@ -54,12 +55,14 @@ fn no_docs_comments() { let mut line = String::new(); reader.read_line(&mut line).unwrap(); if !line.starts_with("//!") { - panic!( - "\nMissing docs strings\n\ - module: {}\n\ - Need add doc for module\n", - f.path().display() - ) + missing_docs.push(f.path().display().to_string()); } } + if !missing_docs.is_empty() { + panic!( + "\nMissing docs strings\n\n\ + modules:\n{}\n\n", + missing_docs.join("\n") + ) + } }