//! 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, //! 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 collector; mod mod_resolution; mod path_resolution; #[cfg(test)] mod tests; use std::sync::Arc; use arena::Arena; use base_db::{CrateId, Edition, FileId}; use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile}; use rustc_hash::FxHashMap; use stdx::format_to; use syntax::ast; use crate::{ db::DefDatabase, item_scope::{BuiltinShadowMode, ItemScope}, nameres::{diagnostics::DefDiagnostic, path_resolution::ResolveMode}, path::ModPath, per_ns::PerNs, AstId, LocalModuleId, ModuleDefId, ModuleId, }; /// Contains all top-level defs from a macro-expanded crate #[derive(Debug, PartialEq, Eq)] pub struct CrateDefMap { pub root: LocalModuleId, pub modules: Arena, pub(crate) krate: CrateId, /// 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`). pub(crate) prelude: Option, pub(crate) extern_prelude: FxHashMap, edition: Edition, diagnostics: Vec, } impl std::ops::Index for CrateDefMap { type Output = ModuleData; fn index(&self, id: LocalModuleId) -> &ModuleData { &self.modules[id] } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum ModuleOrigin { CrateRoot { definition: FileId, }, /// Note that non-inline modules, by definition, live inside non-macro file. File { is_mod_rs: bool, declaration: AstId, definition: FileId, }, Inline { definition: AstId, }, } impl Default for ModuleOrigin { fn default() -> Self { ModuleOrigin::CrateRoot { definition: FileId(0) } } } impl ModuleOrigin { fn declaration(&self) -> Option> { match self { ModuleOrigin::File { declaration: module, .. } | ModuleOrigin::Inline { definition: module, .. } => Some(*module), ModuleOrigin::CrateRoot { .. } => None, } } pub fn file_id(&self) -> Option { match self { ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => { Some(*definition) } _ => None, } } pub fn is_inline(&self) -> bool { match self { ModuleOrigin::Inline { .. } => true, ModuleOrigin::CrateRoot { .. } | ModuleOrigin::File { .. } => false, } } /// Returns a node which defines this module. /// That is, a file or a `mod foo {}` with items. fn definition_source(&self, db: &dyn DefDatabase) -> InFile { match self { ModuleOrigin::File { definition, .. } | ModuleOrigin::CrateRoot { definition } => { let file_id = *definition; let sf = db.parse(file_id).tree(); InFile::new(file_id.into(), ModuleSource::SourceFile(sf)) } ModuleOrigin::Inline { definition } => InFile::new( definition.file_id, ModuleSource::Module(definition.to_node(db.upcast())), ), } } } #[derive(Default, Debug, PartialEq, Eq)] pub struct ModuleData { pub parent: Option, pub children: FxHashMap, pub scope: ItemScope, /// Where does this module come from? pub origin: ModuleOrigin, } impl CrateDefMap { pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc { let _p = profile::span("crate_def_map_query").detail(|| { db.crate_graph()[krate].display_name.as_deref().unwrap_or_default().to_string() }); let def_map = { let edition = db.crate_graph()[krate].edition; let mut modules: Arena = Arena::default(); let root = modules.alloc(ModuleData::default()); CrateDefMap { krate, edition, extern_prelude: FxHashMap::default(), prelude: None, root, modules, diagnostics: Vec::new(), } }; let def_map = collector::collect_defs(db, def_map); Arc::new(def_map) } pub fn add_diagnostics( &self, db: &dyn DefDatabase, module: LocalModuleId, sink: &mut DiagnosticSink, ) { self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink)) } pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator + '_ { self.modules .iter() .filter(move |(_id, data)| data.origin.file_id() == Some(file_id)) .map(|(id, _data)| id) } pub(crate) fn resolve_path( &self, db: &dyn DefDatabase, original_module: LocalModuleId, path: &ModPath, shadow: BuiltinShadowMode, ) -> (PerNs, Option) { let res = self.resolve_path_fp_with_macro(db, ResolveMode::Other, original_module, path, shadow); (res.resolved_def, res.segment_index) } // FIXME: this can use some more human-readable format (ideally, an IR // even), as this should be a great debugging aid. pub fn dump(&self) -> String { let mut buf = String::new(); go(&mut buf, self, "crate", self.root); return buf; fn go(buf: &mut String, map: &CrateDefMap, path: &str, module: LocalModuleId) { format_to!(buf, "{}\n", path); let mut entries: Vec<_> = map.modules[module].scope.resolutions().collect(); entries.sort_by_key(|(name, _)| name.clone()); for (name, def) in entries { format_to!(buf, "{}:", name.map_or("_".to_string(), |name| name.to_string())); if def.types.is_some() { buf.push_str(" t"); } if def.values.is_some() { buf.push_str(" v"); } if def.macros.is_some() { buf.push_str(" m"); } if def.is_none() { buf.push_str(" _"); } buf.push_str("\n"); } for (name, child) in map.modules[module].children.iter() { let path = format!("{}::{}", path, name); buf.push('\n'); go(buf, map, &path, *child); } } } } impl ModuleData { /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items. pub fn definition_source(&self, db: &dyn DefDatabase) -> InFile { self.origin.definition_source(db) } /// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`. /// `None` for the crate root or block. pub fn declaration_source(&self, db: &dyn DefDatabase) -> Option> { let decl = self.origin.declaration()?; let value = decl.to_node(db.upcast()); Some(InFile { file_id: decl.file_id, value }) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ModuleSource { SourceFile(ast::SourceFile), Module(ast::Module), } mod diagnostics { use cfg::{CfgExpr, CfgOptions}; use hir_expand::diagnostics::DiagnosticSink; use hir_expand::hygiene::Hygiene; use hir_expand::{InFile, MacroCallKind}; use syntax::{ast, AstPtr, SyntaxNodePtr}; use crate::path::ModPath; use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId}; #[derive(Debug, PartialEq, Eq)] enum DiagnosticKind { UnresolvedModule { declaration: AstId, candidate: String }, UnresolvedExternCrate { ast: AstId }, UnresolvedImport { ast: AstId, index: usize }, UnconfiguredCode { ast: AstId, cfg: CfgExpr, opts: CfgOptions }, UnresolvedProcMacro { ast: MacroCallKind }, MacroError { ast: MacroCallKind, message: String }, } #[derive(Debug, PartialEq, Eq)] pub(super) struct DefDiagnostic { in_module: LocalModuleId, kind: DiagnosticKind, } impl DefDiagnostic { pub(super) fn unresolved_module( container: LocalModuleId, declaration: AstId, candidate: String, ) -> Self { Self { in_module: container, kind: DiagnosticKind::UnresolvedModule { declaration, candidate }, } } pub(super) fn unresolved_extern_crate( container: LocalModuleId, declaration: AstId, ) -> Self { Self { in_module: container, kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration }, } } pub(super) fn unresolved_import( container: LocalModuleId, ast: AstId, index: usize, ) -> Self { Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } } } pub(super) fn unconfigured_code( container: LocalModuleId, ast: AstId, cfg: CfgExpr, opts: CfgOptions, ) -> Self { Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } } } pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self { Self { in_module: container, kind: DiagnosticKind::UnresolvedProcMacro { ast } } } pub(super) fn macro_error( container: LocalModuleId, ast: MacroCallKind, message: String, ) -> Self { Self { in_module: container, kind: DiagnosticKind::MacroError { ast, message } } } pub(super) fn add_to( &self, db: &dyn DefDatabase, target_module: LocalModuleId, sink: &mut DiagnosticSink, ) { if self.in_module != target_module { return; } match &self.kind { DiagnosticKind::UnresolvedModule { declaration, candidate } => { let decl = declaration.to_node(db.upcast()); sink.push(UnresolvedModule { file: declaration.file_id, decl: AstPtr::new(&decl), candidate: candidate.clone(), }) } DiagnosticKind::UnresolvedExternCrate { ast } => { let item = ast.to_node(db.upcast()); sink.push(UnresolvedExternCrate { file: ast.file_id, item: AstPtr::new(&item), }); } DiagnosticKind::UnresolvedImport { ast, index } => { let use_item = ast.to_node(db.upcast()); let hygiene = Hygiene::new(db.upcast(), ast.file_id); let mut cur = 0; let mut tree = None; ModPath::expand_use_item( InFile::new(ast.file_id, use_item), &hygiene, |_mod_path, use_tree, _is_glob, _alias| { if cur == *index { tree = Some(use_tree.clone()); } cur += 1; }, ); if let Some(tree) = tree { sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) }); } } DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => { let item = ast.to_node(db.upcast()); sink.push(InactiveCode { file: ast.file_id, node: AstPtr::new(&item).into(), cfg: cfg.clone(), opts: opts.clone(), }); } DiagnosticKind::UnresolvedProcMacro { ast } => { let (file, ast, name) = match ast { MacroCallKind::FnLike(ast) => { let node = ast.to_node(db.upcast()); (ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None) } MacroCallKind::Attr(ast, name) => { let node = ast.to_node(db.upcast()); ( ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), Some(name.to_string()), ) } }; sink.push(UnresolvedProcMacro { file, node: ast, macro_name: name }); } DiagnosticKind::MacroError { ast, message } => { let (file, ast) = match ast { MacroCallKind::FnLike(ast) => { let node = ast.to_node(db.upcast()); (ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) } MacroCallKind::Attr(ast, _) => { let node = ast.to_node(db.upcast()); (ast.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) } }; sink.push(MacroError { file, node: ast, message: message.clone() }); } } } } }