From b28c54a2c239acd73f2eea80fda9ee3960d2c046 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 13 Aug 2020 16:28:27 +0200 Subject: Rename ra_hir_def -> hir_def --- crates/hir_def/src/nameres/collector.rs | 1279 ++++++++++++++++++++ crates/hir_def/src/nameres/mod_resolution.rs | 139 +++ crates/hir_def/src/nameres/path_resolution.rs | 330 +++++ crates/hir_def/src/nameres/tests.rs | 690 +++++++++++ crates/hir_def/src/nameres/tests/globs.rs | 338 ++++++ crates/hir_def/src/nameres/tests/incremental.rs | 101 ++ crates/hir_def/src/nameres/tests/macros.rs | 669 ++++++++++ crates/hir_def/src/nameres/tests/mod_resolution.rs | 796 ++++++++++++ crates/hir_def/src/nameres/tests/primitives.rs | 23 + 9 files changed, 4365 insertions(+) create mode 100644 crates/hir_def/src/nameres/collector.rs create mode 100644 crates/hir_def/src/nameres/mod_resolution.rs create mode 100644 crates/hir_def/src/nameres/path_resolution.rs create mode 100644 crates/hir_def/src/nameres/tests.rs create mode 100644 crates/hir_def/src/nameres/tests/globs.rs create mode 100644 crates/hir_def/src/nameres/tests/incremental.rs create mode 100644 crates/hir_def/src/nameres/tests/macros.rs create mode 100644 crates/hir_def/src/nameres/tests/mod_resolution.rs create mode 100644 crates/hir_def/src/nameres/tests/primitives.rs (limited to 'crates/hir_def/src/nameres') diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs new file mode 100644 index 000000000..3e99c8773 --- /dev/null +++ b/crates/hir_def/src/nameres/collector.rs @@ -0,0 +1,1279 @@ +//! The core of the module-level name resolution algorithm. +//! +//! `DefCollector::collect` contains the fixed-point iteration loop which +//! resolves imports and expands macros. + +use base_db::{CrateId, FileId, ProcMacroId}; +use cfg::CfgOptions; +use hir_expand::{ + ast_id_map::FileAstId, + builtin_derive::find_builtin_derive, + builtin_macro::find_builtin_macro, + name::{name, AsName, Name}, + proc_macro::ProcMacroExpander, + HirFileId, MacroCallId, MacroDefId, MacroDefKind, +}; +use rustc_hash::FxHashMap; +use syntax::ast; +use test_utils::mark; + +use crate::{ + attr::Attrs, + db::DefDatabase, + item_scope::{ImportType, PerNsGlobImports}, + item_tree::{ + self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind, + }, + nameres::{ + diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint, + BuiltinShadowMode, CrateDefMap, ModuleData, ModuleOrigin, ResolveMode, + }, + path::{ImportAlias, ModPath, PathKind}, + per_ns::PerNs, + visibility::{RawVisibility, Visibility}, + AdtId, AsMacroCall, AstId, AstIdWithPath, ConstLoc, ContainerId, EnumLoc, EnumVariantId, + FunctionLoc, ImplLoc, Intern, LocalModuleId, ModuleDefId, ModuleId, StaticLoc, StructLoc, + TraitLoc, TypeAliasLoc, UnionLoc, +}; + +const GLOB_RECURSION_LIMIT: usize = 100; +const EXPANSION_DEPTH_LIMIT: usize = 128; +const FIXED_POINT_LIMIT: usize = 8192; + +pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { + let crate_graph = db.crate_graph(); + + // populate external prelude + for dep in &crate_graph[def_map.krate].dependencies { + log::debug!("crate dep {:?} -> {:?}", dep.name, dep.crate_id); + let dep_def_map = db.crate_def_map(dep.crate_id); + def_map.extern_prelude.insert( + dep.as_name(), + ModuleId { krate: dep.crate_id, local_id: dep_def_map.root }.into(), + ); + + // look for the prelude + // If the dependency defines a prelude, we overwrite an already defined + // prelude. This is necessary to import the "std" prelude if a crate + // depends on both "core" and "std". + if dep_def_map.prelude.is_some() { + def_map.prelude = dep_def_map.prelude; + } + } + + let cfg_options = &crate_graph[def_map.krate].cfg_options; + let proc_macros = &crate_graph[def_map.krate].proc_macro; + let proc_macros = proc_macros + .iter() + .enumerate() + .map(|(idx, it)| { + // FIXME: a hacky way to create a Name from string. + let name = tt::Ident { text: it.name.clone(), id: tt::TokenId::unspecified() }; + (name.as_name(), ProcMacroExpander::new(def_map.krate, ProcMacroId(idx as u32))) + }) + .collect(); + + let mut collector = DefCollector { + db, + def_map, + glob_imports: FxHashMap::default(), + unresolved_imports: Vec::new(), + resolved_imports: Vec::new(), + + unexpanded_macros: Vec::new(), + unexpanded_attribute_macros: Vec::new(), + mod_dirs: FxHashMap::default(), + cfg_options, + proc_macros, + from_glob_import: Default::default(), + }; + collector.collect(); + collector.finish() +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum PartialResolvedImport { + /// None of any namespaces is resolved + Unresolved, + /// One of namespaces is resolved + Indeterminate(PerNs), + /// All namespaces are resolved, OR it is came from other crate + Resolved(PerNs), +} + +impl PartialResolvedImport { + fn namespaces(&self) -> PerNs { + match self { + PartialResolvedImport::Unresolved => PerNs::none(), + PartialResolvedImport::Indeterminate(ns) => *ns, + PartialResolvedImport::Resolved(ns) => *ns, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct Import { + pub path: ModPath, + pub alias: Option, + pub visibility: RawVisibility, + pub is_glob: bool, + pub is_prelude: bool, + pub is_extern_crate: bool, + pub is_macro_use: bool, +} + +impl Import { + fn from_use(tree: &ItemTree, id: FileItemTreeId) -> Self { + let it = &tree[id]; + let visibility = &tree[it.visibility]; + Self { + path: it.path.clone(), + alias: it.alias.clone(), + visibility: visibility.clone(), + is_glob: it.is_glob, + is_prelude: it.is_prelude, + is_extern_crate: false, + is_macro_use: false, + } + } + + fn from_extern_crate(tree: &ItemTree, id: FileItemTreeId) -> Self { + let it = &tree[id]; + let visibility = &tree[it.visibility]; + Self { + path: it.path.clone(), + alias: it.alias.clone(), + visibility: visibility.clone(), + is_glob: false, + is_prelude: false, + is_extern_crate: true, + is_macro_use: it.is_macro_use, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct ImportDirective { + module_id: LocalModuleId, + import: Import, + status: PartialResolvedImport, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct MacroDirective { + module_id: LocalModuleId, + ast_id: AstIdWithPath, + legacy: Option, + depth: usize, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct DeriveDirective { + module_id: LocalModuleId, + ast_id: AstIdWithPath, +} + +struct DefData<'a> { + id: ModuleDefId, + name: &'a Name, + visibility: &'a RawVisibility, + has_constructor: bool, +} + +/// Walks the tree of module recursively +struct DefCollector<'a> { + db: &'a dyn DefDatabase, + def_map: CrateDefMap, + glob_imports: FxHashMap>, + unresolved_imports: Vec, + resolved_imports: Vec, + unexpanded_macros: Vec, + unexpanded_attribute_macros: Vec, + mod_dirs: FxHashMap, + cfg_options: &'a CfgOptions, + proc_macros: Vec<(Name, ProcMacroExpander)>, + from_glob_import: PerNsGlobImports, +} + +impl DefCollector<'_> { + fn collect(&mut self) { + let file_id = self.db.crate_graph()[self.def_map.krate].root_file_id; + let item_tree = self.db.item_tree(file_id.into()); + let module_id = self.def_map.root; + self.def_map.modules[module_id].origin = ModuleOrigin::CrateRoot { definition: file_id }; + ModCollector { + def_collector: &mut *self, + macro_depth: 0, + module_id, + file_id: file_id.into(), + item_tree: &item_tree, + mod_dir: ModDir::root(), + } + .collect(item_tree.top_level_items()); + + // main name resolution fixed-point loop. + let mut i = 0; + loop { + self.db.check_canceled(); + self.resolve_imports(); + + match self.resolve_macros() { + ReachedFixedPoint::Yes => break, + ReachedFixedPoint::No => i += 1, + } + if i == FIXED_POINT_LIMIT { + log::error!("name resolution is stuck"); + break; + } + } + + // Resolve all indeterminate resolved imports again + // As some of the macros will expand newly import shadowing partial resolved imports + // FIXME: We maybe could skip this, if we handle the Indetermine imports in `resolve_imports` + // correctly + let partial_resolved = self.resolved_imports.iter().filter_map(|directive| { + if let PartialResolvedImport::Indeterminate(_) = directive.status { + let mut directive = directive.clone(); + directive.status = PartialResolvedImport::Unresolved; + Some(directive) + } else { + None + } + }); + self.unresolved_imports.extend(partial_resolved); + self.resolve_imports(); + + let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); + // show unresolved imports in completion, etc + for directive in unresolved_imports { + self.record_resolved_import(&directive) + } + + // Record proc-macros + self.collect_proc_macro(); + } + + fn collect_proc_macro(&mut self) { + let proc_macros = std::mem::take(&mut self.proc_macros); + for (name, expander) in proc_macros { + let krate = self.def_map.krate; + + let macro_id = MacroDefId { + ast_id: None, + krate: Some(krate), + kind: MacroDefKind::CustomDerive(expander), + local_inner: false, + }; + + self.define_proc_macro(name.clone(), macro_id); + } + } + + /// Define a macro with `macro_rules`. + /// + /// It will define the macro in legacy textual scope, and if it has `#[macro_export]`, + /// then it is also defined in the root module scope. + /// You can `use` or invoke it by `crate::macro_name` anywhere, before or after the definition. + /// + /// It is surprising that the macro will never be in the current module scope. + /// These code fails with "unresolved import/macro", + /// ```rust,compile_fail + /// mod m { macro_rules! foo { () => {} } } + /// use m::foo as bar; + /// ``` + /// + /// ```rust,compile_fail + /// macro_rules! foo { () => {} } + /// self::foo!(); + /// crate::foo!(); + /// ``` + /// + /// Well, this code compiles, because the plain path `foo` in `use` is searched + /// in the legacy textual scope only. + /// ```rust + /// macro_rules! foo { () => {} } + /// use foo as bar; + /// ``` + fn define_macro( + &mut self, + module_id: LocalModuleId, + name: Name, + macro_: MacroDefId, + export: bool, + ) { + // Textual scoping + self.define_legacy_macro(module_id, name.clone(), macro_); + + // Module scoping + // In Rust, `#[macro_export]` macros are unconditionally visible at the + // crate root, even if the parent modules is **not** visible. + if export { + self.update( + self.def_map.root, + &[(Some(name), PerNs::macros(macro_, Visibility::Public))], + Visibility::Public, + ImportType::Named, + ); + } + } + + /// Define a legacy textual scoped macro in module + /// + /// We use a map `legacy_macros` to store all legacy textual scoped macros visible per module. + /// It will clone all macros from parent legacy scope, whose definition is prior to + /// the definition of current module. + /// And also, `macro_use` on a module will import all legacy macros visible inside to + /// current legacy scope, with possible shadowing. + fn define_legacy_macro(&mut self, module_id: LocalModuleId, name: Name, mac: MacroDefId) { + // Always shadowing + self.def_map.modules[module_id].scope.define_legacy_macro(name, mac); + } + + /// Define a proc macro + /// + /// A proc macro is similar to normal macro scope, but it would not visiable in legacy textual scoped. + /// And unconditionally exported. + fn define_proc_macro(&mut self, name: Name, macro_: MacroDefId) { + self.update( + self.def_map.root, + &[(Some(name), PerNs::macros(macro_, Visibility::Public))], + Visibility::Public, + ImportType::Named, + ); + } + + /// Import macros from `#[macro_use] extern crate`. + fn import_macros_from_extern_crate( + &mut self, + current_module_id: LocalModuleId, + import: &item_tree::ExternCrate, + ) { + log::debug!( + "importing macros from extern crate: {:?} ({:?})", + import, + self.def_map.edition, + ); + + let res = self.def_map.resolve_name_in_extern_prelude( + &import + .path + .as_ident() + .expect("extern crate should have been desugared to one-element path"), + ); + + if let Some(ModuleDefId::ModuleId(m)) = res.take_types() { + mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); + self.import_all_macros_exported(current_module_id, m.krate); + } + } + + /// Import all exported macros from another crate + /// + /// 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: LocalModuleId, 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. + self.define_legacy_macro(current_module_id, name.clone(), def); + } + } + + /// Import resolution + /// + /// This is a fix point algorithm. We resolve imports until no forward + /// progress in resolving imports is made + fn resolve_imports(&mut self) { + let mut n_previous_unresolved = self.unresolved_imports.len() + 1; + + while self.unresolved_imports.len() < n_previous_unresolved { + n_previous_unresolved = self.unresolved_imports.len(); + let imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); + for mut directive in imports { + directive.status = self.resolve_import(directive.module_id, &directive.import); + match directive.status { + PartialResolvedImport::Indeterminate(_) => { + self.record_resolved_import(&directive); + // FIXME: For avoid performance regression, + // we consider an imported resolved if it is indeterminate (i.e not all namespace resolved) + self.resolved_imports.push(directive) + } + PartialResolvedImport::Resolved(_) => { + self.record_resolved_import(&directive); + self.resolved_imports.push(directive) + } + PartialResolvedImport::Unresolved => { + self.unresolved_imports.push(directive); + } + } + } + } + } + + fn resolve_import(&self, module_id: LocalModuleId, import: &Import) -> PartialResolvedImport { + log::debug!("resolving import: {:?} ({:?})", import, self.def_map.edition); + if import.is_extern_crate { + let res = self.def_map.resolve_name_in_extern_prelude( + &import + .path + .as_ident() + .expect("extern crate should have been desugared to one-element path"), + ); + PartialResolvedImport::Resolved(res) + } else { + let res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Import, + module_id, + &import.path, + BuiltinShadowMode::Module, + ); + + let def = res.resolved_def; + if res.reached_fixedpoint == ReachedFixedPoint::No || def.is_none() { + return PartialResolvedImport::Unresolved; + } + + if let Some(krate) = res.krate { + if krate != self.def_map.krate { + return PartialResolvedImport::Resolved(def); + } + } + + // Check whether all namespace is resolved + if def.take_types().is_some() + && def.take_values().is_some() + && def.take_macros().is_some() + { + PartialResolvedImport::Resolved(def) + } else { + PartialResolvedImport::Indeterminate(def) + } + } + } + + fn record_resolved_import(&mut self, directive: &ImportDirective) { + let module_id = directive.module_id; + let import = &directive.import; + let def = directive.status.namespaces(); + let vis = self + .def_map + .resolve_visibility(self.db, module_id, &directive.import.visibility) + .unwrap_or(Visibility::Public); + + if import.is_glob { + log::debug!("glob import: {:?}", import); + match def.take_types() { + Some(ModuleDefId::ModuleId(m)) => { + if import.is_prelude { + mark::hit!(std_prelude); + self.def_map.prelude = Some(m); + } else if m.krate != self.def_map.krate { + mark::hit!(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.local_id].scope; + + // Module scoped macros is included + let items = scope + .resolutions() + // only keep visible names... + .map(|(n, res)| { + (n, res.filter_visibility(|v| v.is_visible_from_other_crate())) + }) + .filter(|(_, res)| !res.is_none()) + .collect::>(); + + self.update(module_id, &items, vis, ImportType::Glob); + } else { + // glob import from same crate => we do an initial + // import, and then need to propagate any further + // additions + let scope = &self.def_map[m.local_id].scope; + + // Module scoped macros is included + let items = scope + .resolutions() + // only keep visible names... + .map(|(n, res)| { + ( + n, + res.filter_visibility(|v| { + v.is_visible_from_def_map(&self.def_map, module_id) + }), + ) + }) + .filter(|(_, res)| !res.is_none()) + .collect::>(); + + self.update(module_id, &items, vis, ImportType::Glob); + // record the glob import in case we add further items + let glob = self.glob_imports.entry(m.local_id).or_default(); + if !glob.iter().any(|(mid, _)| *mid == module_id) { + glob.push((module_id, vis)); + } + } + } + Some(ModuleDefId::AdtId(AdtId::EnumId(e))) => { + mark::hit!(glob_enum); + // glob import from enum => just import all the variants + + // XXX: urgh, so this works by accident! Here, we look at + // the enum data, and, in theory, this might require us to + // look back at the crate_def_map, creating a cycle. For + // example, `enum E { crate::some_macro!(); }`. Luckely, the + // only kind of macro that is allowed inside enum is a + // `cfg_macro`, and we don't need to run name resolution for + // it, but this is sheer luck! + let enum_data = self.db.enum_data(e); + let resolutions = enum_data + .variants + .iter() + .map(|(local_id, variant_data)| { + let name = variant_data.name.clone(); + let variant = EnumVariantId { parent: e, local_id }; + let res = PerNs::both(variant.into(), variant.into(), vis); + (Some(name), res) + }) + .collect::>(); + self.update(module_id, &resolutions, vis, ImportType::Glob); + } + Some(d) => { + log::debug!("glob import {:?} from non-module/enum {:?}", import, d); + } + None => { + log::debug!("glob import {:?} didn't resolve as type", import); + } + } + } else { + match import.path.segments.last() { + Some(last_segment) => { + let name = match &import.alias { + Some(ImportAlias::Alias(name)) => Some(name.clone()), + Some(ImportAlias::Underscore) => None, + None => Some(last_segment.clone()), + }; + log::debug!("resolved import {:?} ({:?}) to {:?}", name, import, def); + + // extern crates in the crate root are special-cased to insert entries into the extern prelude: rust-lang/rust#54658 + if import.is_extern_crate && module_id == self.def_map.root { + if let (Some(def), Some(name)) = (def.take_types(), name.as_ref()) { + self.def_map.extern_prelude.insert(name.clone(), def); + } + } + + self.update(module_id, &[(name, def)], vis, ImportType::Named); + } + None => mark::hit!(bogus_paths), + } + } + } + + fn update( + &mut self, + module_id: LocalModuleId, + resolutions: &[(Option, PerNs)], + vis: Visibility, + import_type: ImportType, + ) { + self.db.check_canceled(); + self.update_recursive(module_id, resolutions, vis, import_type, 0) + } + + fn update_recursive( + &mut self, + module_id: LocalModuleId, + resolutions: &[(Option, PerNs)], + // All resolutions are imported with this visibility; the visibilies in + // the `PerNs` values are ignored and overwritten + vis: Visibility, + import_type: ImportType, + depth: usize, + ) { + if depth > GLOB_RECURSION_LIMIT { + // prevent stack overflows (but this shouldn't be possible) + panic!("infinite recursion in glob imports!"); + } + let mut changed = false; + + for (name, res) in resolutions { + match name { + Some(name) => { + let scope = &mut self.def_map.modules[module_id].scope; + changed |= scope.push_res_with_import( + &mut self.from_glob_import, + (module_id, name.clone()), + res.with_visibility(vis), + import_type, + ); + } + None => { + let tr = match res.take_types() { + Some(ModuleDefId::TraitId(tr)) => tr, + Some(other) => { + log::debug!("non-trait `_` import of {:?}", other); + continue; + } + None => continue, + }; + let old_vis = self.def_map.modules[module_id].scope.unnamed_trait_vis(tr); + let should_update = match old_vis { + None => true, + Some(old_vis) => { + let max_vis = old_vis.max(vis, &self.def_map).unwrap_or_else(|| { + panic!("`Tr as _` imports with unrelated visibilities {:?} and {:?} (trait {:?})", old_vis, vis, tr); + }); + + if max_vis == old_vis { + false + } else { + mark::hit!(upgrade_underscore_visibility); + true + } + } + }; + + if should_update { + changed = true; + self.def_map.modules[module_id].scope.push_unnamed_trait(tr, vis); + } + } + } + } + + if !changed { + return; + } + let glob_imports = self + .glob_imports + .get(&module_id) + .into_iter() + .flat_map(|v| v.iter()) + .filter(|(glob_importing_module, _)| { + // we know all resolutions have the same visibility (`vis`), so we + // just need to check that once + vis.is_visible_from_def_map(&self.def_map, *glob_importing_module) + }) + .cloned() + .collect::>(); + + for (glob_importing_module, glob_import_vis) in glob_imports { + self.update_recursive( + glob_importing_module, + resolutions, + glob_import_vis, + ImportType::Glob, + depth + 1, + ); + } + } + + fn resolve_macros(&mut self) -> ReachedFixedPoint { + let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new()); + let mut attribute_macros = + std::mem::replace(&mut self.unexpanded_attribute_macros, Vec::new()); + let mut resolved = Vec::new(); + let mut res = ReachedFixedPoint::Yes; + macros.retain(|directive| { + if let Some(call_id) = directive.legacy { + res = ReachedFixedPoint::No; + resolved.push((directive.module_id, call_id, directive.depth)); + return false; + } + + if let Some(call_id) = + directive.ast_id.as_call_id(self.db, self.def_map.krate, |path| { + let resolved_res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Other, + directive.module_id, + &path, + BuiltinShadowMode::Module, + ); + resolved_res.resolved_def.take_macros() + }) + { + resolved.push((directive.module_id, call_id, directive.depth)); + res = ReachedFixedPoint::No; + return false; + } + + true + }); + attribute_macros.retain(|directive| { + if let Some(call_id) = + directive.ast_id.as_call_id(self.db, self.def_map.krate, |path| { + self.resolve_attribute_macro(&directive, &path) + }) + { + resolved.push((directive.module_id, call_id, 0)); + res = ReachedFixedPoint::No; + return false; + } + + true + }); + + self.unexpanded_macros = macros; + self.unexpanded_attribute_macros = attribute_macros; + + for (module_id, macro_call_id, depth) in resolved { + self.collect_macro_expansion(module_id, macro_call_id, depth); + } + + res + } + + fn resolve_attribute_macro( + &self, + directive: &DeriveDirective, + path: &ModPath, + ) -> Option { + if let Some(name) = path.as_ident() { + // FIXME this should actually be handled with the normal name + // resolution; the std lib defines built-in stubs for the derives, + // but these are new-style `macro`s, which we don't support yet + if let Some(def_id) = find_builtin_derive(name) { + return Some(def_id); + } + } + let resolved_res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Other, + directive.module_id, + &path, + BuiltinShadowMode::Module, + ); + + resolved_res.resolved_def.take_macros() + } + + fn collect_macro_expansion( + &mut self, + module_id: LocalModuleId, + macro_call_id: MacroCallId, + depth: usize, + ) { + if depth > EXPANSION_DEPTH_LIMIT { + mark::hit!(macro_expansion_overflow); + log::warn!("macro expansion is too deep"); + return; + } + let file_id: HirFileId = macro_call_id.as_file(); + let item_tree = self.db.item_tree(file_id); + let mod_dir = self.mod_dirs[&module_id].clone(); + ModCollector { + def_collector: &mut *self, + macro_depth: depth, + file_id, + module_id, + item_tree: &item_tree, + mod_dir, + } + .collect(item_tree.top_level_items()); + } + + fn finish(self) -> CrateDefMap { + self.def_map + } +} + +/// Walks a single module, populating defs, imports and macros +struct ModCollector<'a, 'b> { + def_collector: &'a mut DefCollector<'b>, + macro_depth: usize, + module_id: LocalModuleId, + file_id: HirFileId, + item_tree: &'a ItemTree, + mod_dir: ModDir, +} + +impl ModCollector<'_, '_> { + fn collect(&mut self, items: &[ModItem]) { + // Note: don't assert that inserted value is fresh: it's simply not true + // for macros. + self.def_collector.mod_dirs.insert(self.module_id, self.mod_dir.clone()); + + // 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 { + mark::hit!(prelude_is_macro_use); + self.def_collector.import_all_macros_exported(self.module_id, prelude_module.krate); + } + } + + // This should be processed eagerly instead of deferred to resolving. + // `#[macro_use] extern crate` is hoisted to imports macros before collecting + // any other items. + for item in items { + if self.is_cfg_enabled(self.item_tree.attrs((*item).into())) { + if let ModItem::ExternCrate(id) = item { + let import = self.item_tree[*id].clone(); + if import.is_macro_use { + self.def_collector.import_macros_from_extern_crate(self.module_id, &import); + } + } + } + } + + for &item in items { + let attrs = self.item_tree.attrs(item.into()); + if self.is_cfg_enabled(attrs) { + let module = + ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; + let container = ContainerId::ModuleId(module); + + let mut def = None; + match item { + ModItem::Mod(m) => self.collect_module(&self.item_tree[m], attrs), + ModItem::Import(import_id) => { + self.def_collector.unresolved_imports.push(ImportDirective { + module_id: self.module_id, + import: Import::from_use(&self.item_tree, import_id), + status: PartialResolvedImport::Unresolved, + }) + } + ModItem::ExternCrate(import_id) => { + self.def_collector.unresolved_imports.push(ImportDirective { + module_id: self.module_id, + import: Import::from_extern_crate(&self.item_tree, import_id), + status: PartialResolvedImport::Unresolved, + }) + } + ModItem::MacroCall(mac) => self.collect_macro(&self.item_tree[mac]), + ModItem::Impl(imp) => { + let module = ModuleId { + krate: self.def_collector.def_map.krate, + local_id: self.module_id, + }; + let container = ContainerId::ModuleId(module); + let impl_id = ImplLoc { container, id: ItemTreeId::new(self.file_id, imp) } + .intern(self.def_collector.db); + self.def_collector.def_map.modules[self.module_id] + .scope + .define_impl(impl_id) + } + ModItem::Function(id) => { + let func = &self.item_tree[id]; + def = Some(DefData { + id: FunctionLoc { + container: container.into(), + id: ItemTreeId::new(self.file_id, id), + } + .intern(self.def_collector.db) + .into(), + name: &func.name, + visibility: &self.item_tree[func.visibility], + has_constructor: false, + }); + } + ModItem::Struct(id) => { + let it = &self.item_tree[id]; + + // FIXME: check attrs to see if this is an attribute macro invocation; + // in which case we don't add the invocation, just a single attribute + // macro invocation + self.collect_derives(attrs, it.ast_id.upcast()); + + def = Some(DefData { + id: StructLoc { container, id: ItemTreeId::new(self.file_id, id) } + .intern(self.def_collector.db) + .into(), + name: &it.name, + visibility: &self.item_tree[it.visibility], + has_constructor: it.kind != StructDefKind::Record, + }); + } + ModItem::Union(id) => { + let it = &self.item_tree[id]; + + // FIXME: check attrs to see if this is an attribute macro invocation; + // in which case we don't add the invocation, just a single attribute + // macro invocation + self.collect_derives(attrs, it.ast_id.upcast()); + + def = Some(DefData { + id: UnionLoc { container, id: ItemTreeId::new(self.file_id, id) } + .intern(self.def_collector.db) + .into(), + name: &it.name, + visibility: &self.item_tree[it.visibility], + has_constructor: false, + }); + } + ModItem::Enum(id) => { + let it = &self.item_tree[id]; + + // FIXME: check attrs to see if this is an attribute macro invocation; + // in which case we don't add the invocation, just a single attribute + // macro invocation + self.collect_derives(attrs, it.ast_id.upcast()); + + def = Some(DefData { + id: EnumLoc { container, id: ItemTreeId::new(self.file_id, id) } + .intern(self.def_collector.db) + .into(), + name: &it.name, + visibility: &self.item_tree[it.visibility], + has_constructor: false, + }); + } + ModItem::Const(id) => { + let it = &self.item_tree[id]; + + if let Some(name) = &it.name { + def = Some(DefData { + id: ConstLoc { + container: container.into(), + id: ItemTreeId::new(self.file_id, id), + } + .intern(self.def_collector.db) + .into(), + name, + visibility: &self.item_tree[it.visibility], + has_constructor: false, + }); + } + } + ModItem::Static(id) => { + let it = &self.item_tree[id]; + + def = Some(DefData { + id: StaticLoc { container, id: ItemTreeId::new(self.file_id, id) } + .intern(self.def_collector.db) + .into(), + name: &it.name, + visibility: &self.item_tree[it.visibility], + has_constructor: false, + }); + } + ModItem::Trait(id) => { + let it = &self.item_tree[id]; + + def = Some(DefData { + id: TraitLoc { container, id: ItemTreeId::new(self.file_id, id) } + .intern(self.def_collector.db) + .into(), + name: &it.name, + visibility: &self.item_tree[it.visibility], + has_constructor: false, + }); + } + ModItem::TypeAlias(id) => { + let it = &self.item_tree[id]; + + def = Some(DefData { + id: TypeAliasLoc { + container: container.into(), + id: ItemTreeId::new(self.file_id, id), + } + .intern(self.def_collector.db) + .into(), + name: &it.name, + visibility: &self.item_tree[it.visibility], + has_constructor: false, + }); + } + } + + if let Some(DefData { id, name, visibility, has_constructor }) = def { + self.def_collector.def_map.modules[self.module_id].scope.define_def(id); + let vis = self + .def_collector + .def_map + .resolve_visibility(self.def_collector.db, self.module_id, visibility) + .unwrap_or(Visibility::Public); + self.def_collector.update( + self.module_id, + &[(Some(name.clone()), PerNs::from_def(id, vis, has_constructor))], + vis, + ImportType::Named, + ) + } + } + } + } + + fn collect_module(&mut self, module: &Mod, attrs: &Attrs) { + let path_attr = attrs.by_key("path").string_value(); + let is_macro_use = attrs.by_key("macro_use").exists(); + match &module.kind { + // inline module, just recurse + ModKind::Inline { items } => { + let module_id = self.push_child_module( + module.name.clone(), + AstId::new(self.file_id, module.ast_id), + None, + &self.item_tree[module.visibility], + ); + + ModCollector { + def_collector: &mut *self.def_collector, + macro_depth: self.macro_depth, + module_id, + file_id: self.file_id, + item_tree: self.item_tree, + mod_dir: self.mod_dir.descend_into_definition(&module.name, path_attr), + } + .collect(&*items); + if is_macro_use { + self.import_all_legacy_macros(module_id); + } + } + // out of line module, resolve, parse and recurse + ModKind::Outline {} => { + let ast_id = AstId::new(self.file_id, module.ast_id); + match self.mod_dir.resolve_declaration( + self.def_collector.db, + self.file_id, + &module.name, + path_attr, + ) { + Ok((file_id, is_mod_rs, mod_dir)) => { + let module_id = self.push_child_module( + module.name.clone(), + ast_id, + Some((file_id, is_mod_rs)), + &self.item_tree[module.visibility], + ); + let item_tree = self.def_collector.db.item_tree(file_id.into()); + ModCollector { + def_collector: &mut *self.def_collector, + macro_depth: self.macro_depth, + module_id, + file_id: file_id.into(), + item_tree: &item_tree, + mod_dir, + } + .collect(item_tree.top_level_items()); + if is_macro_use { + self.import_all_legacy_macros(module_id); + } + } + Err(candidate) => self.def_collector.def_map.diagnostics.push( + DefDiagnostic::UnresolvedModule { + module: self.module_id, + declaration: ast_id, + candidate, + }, + ), + }; + } + } + } + + fn push_child_module( + &mut self, + name: Name, + declaration: AstId, + definition: Option<(FileId, bool)>, + visibility: &crate::visibility::RawVisibility, + ) -> LocalModuleId { + let vis = self + .def_collector + .def_map + .resolve_visibility(self.def_collector.db, self.module_id, visibility) + .unwrap_or(Visibility::Public); + let modules = &mut self.def_collector.def_map.modules; + let res = modules.alloc(ModuleData::default()); + modules[res].parent = Some(self.module_id); + modules[res].origin = match definition { + None => ModuleOrigin::Inline { definition: declaration }, + Some((definition, is_mod_rs)) => { + ModuleOrigin::File { declaration, definition, is_mod_rs } + } + }; + for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() { + modules[res].scope.define_legacy_macro(name, mac) + } + modules[self.module_id].children.insert(name.clone(), res); + let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: res }; + let def: ModuleDefId = module.into(); + self.def_collector.def_map.modules[self.module_id].scope.define_def(def); + self.def_collector.update( + self.module_id, + &[(Some(name), PerNs::from_def(def, vis, false))], + vis, + ImportType::Named, + ); + res + } + + fn collect_derives(&mut self, attrs: &Attrs, ast_id: FileAstId) { + for derive_subtree in attrs.by_key("derive").tt_values() { + // for #[derive(Copy, Clone)], `derive_subtree` is the `(Copy, Clone)` subtree + for tt in &derive_subtree.token_trees { + let ident = match &tt { + tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => ident, + tt::TokenTree::Leaf(tt::Leaf::Punct(_)) => continue, // , is ok + _ => continue, // anything else would be an error (which we currently ignore) + }; + let path = ModPath::from_tt_ident(ident); + + let ast_id = AstIdWithPath::new(self.file_id, ast_id, path); + self.def_collector + .unexpanded_attribute_macros + .push(DeriveDirective { module_id: self.module_id, ast_id }); + } + } + } + + fn collect_macro(&mut self, mac: &MacroCall) { + let mut ast_id = AstIdWithPath::new(self.file_id, mac.ast_id, mac.path.clone()); + + // Case 0: builtin macros + if mac.is_builtin { + if let Some(name) = &mac.name { + let krate = self.def_collector.def_map.krate; + if let Some(macro_id) = find_builtin_macro(name, krate, ast_id.ast_id) { + self.def_collector.define_macro( + self.module_id, + name.clone(), + macro_id, + mac.is_export, + ); + return; + } + } + } + + // 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: Some(ast_id.ast_id), + krate: Some(self.def_collector.def_map.krate), + kind: MacroDefKind::Declarative, + local_inner: mac.is_local_inner, + }; + self.def_collector.define_macro( + self.module_id, + name.clone(), + macro_id, + mac.is_export, + ); + } + return; + } + + // Case 2: try to resolve in legacy scope and expand macro_rules + if let Some(macro_call_id) = + ast_id.as_call_id(self.def_collector.db, self.def_collector.def_map.krate, |path| { + path.as_ident().and_then(|name| { + self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) + }) + }) + { + self.def_collector.unexpanded_macros.push(MacroDirective { + module_id: self.module_id, + ast_id, + legacy: Some(macro_call_id), + depth: self.macro_depth + 1, + }); + + return; + } + + // Case 3: resolve in module scope, expand during name resolution. + // We rewrite simple path `macro_name` to `self::macro_name` to force resolve in module scope only. + if ast_id.path.is_ident() { + ast_id.path.kind = PathKind::Super(0); + } + + self.def_collector.unexpanded_macros.push(MacroDirective { + module_id: self.module_id, + ast_id, + legacy: None, + depth: self.macro_depth + 1, + }); + } + + fn import_all_legacy_macros(&mut self, module_id: LocalModuleId) { + let macros = self.def_collector.def_map[module_id].scope.collect_legacy_macros(); + for (name, macro_) in macros { + self.def_collector.define_legacy_macro(self.module_id, name.clone(), macro_); + } + } + + fn is_cfg_enabled(&self, attrs: &Attrs) -> bool { + attrs.is_cfg_enabled(self.def_collector.cfg_options) + } +} + +fn is_macro_rules(path: &ModPath) -> bool { + path.as_ident() == Some(&name![macro_rules]) +} + +#[cfg(test)] +mod tests { + use crate::{db::DefDatabase, test_db::TestDB}; + use arena::Arena; + use base_db::{fixture::WithFixture, SourceDatabase}; + + use super::*; + + fn do_collect_defs(db: &dyn DefDatabase, def_map: CrateDefMap) -> CrateDefMap { + let mut collector = DefCollector { + db, + def_map, + glob_imports: FxHashMap::default(), + unresolved_imports: Vec::new(), + resolved_imports: Vec::new(), + unexpanded_macros: Vec::new(), + unexpanded_attribute_macros: Vec::new(), + mod_dirs: FxHashMap::default(), + cfg_options: &CfgOptions::default(), + proc_macros: Default::default(), + from_glob_import: Default::default(), + }; + collector.collect(); + collector.def_map + } + + fn do_resolve(code: &str) -> CrateDefMap { + let (db, _file_id) = TestDB::with_single_file(&code); + let krate = db.test_crate(); + + 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(), + } + }; + do_collect_defs(&db, def_map) + } + + #[test] + fn test_macro_expand_will_stop_1() { + do_resolve( + r#" + macro_rules! foo { + ($($ty:ty)*) => { foo!($($ty)*); } + } + foo!(KABOOM); + "#, + ); + } + + #[ignore] // this test does succeed, but takes quite a while :/ + #[test] + fn test_macro_expand_will_stop_2() { + do_resolve( + r#" + macro_rules! foo { + ($($ty:ty)*) => { foo!($($ty)* $($ty)*); } + } + foo!(KABOOM); + "#, + ); + } +} diff --git a/crates/hir_def/src/nameres/mod_resolution.rs b/crates/hir_def/src/nameres/mod_resolution.rs new file mode 100644 index 000000000..e8389b484 --- /dev/null +++ b/crates/hir_def/src/nameres/mod_resolution.rs @@ -0,0 +1,139 @@ +//! This module resolves `mod foo;` declaration to file. +use base_db::FileId; +use hir_expand::name::Name; +use syntax::SmolStr; + +use crate::{db::DefDatabase, HirFileId}; + +#[derive(Clone, Debug)] +pub(super) struct ModDir { + /// `` for `mod.rs`, `lib.rs` + /// `foo/` for `foo.rs` + /// `foo/bar/` for `mod bar { mod x; }` nested in `foo.rs` + /// Invariant: path.is_empty() || path.ends_with('/') + dir_path: DirPath, + /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/` + root_non_dir_owner: bool, +} + +impl ModDir { + pub(super) fn root() -> ModDir { + ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false } + } + + pub(super) fn descend_into_definition( + &self, + name: &Name, + attr_path: Option<&SmolStr>, + ) -> ModDir { + let path = match attr_path.map(|it| it.as_str()) { + None => { + let mut path = self.dir_path.clone(); + path.push(&name.to_string()); + path + } + Some(attr_path) => { + let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner); + if !(path.is_empty() || path.ends_with('/')) { + path.push('/') + } + DirPath::new(path) + } + }; + ModDir { dir_path: path, root_non_dir_owner: false } + } + + pub(super) fn resolve_declaration( + &self, + db: &dyn DefDatabase, + file_id: HirFileId, + name: &Name, + attr_path: Option<&SmolStr>, + ) -> Result<(FileId, bool, ModDir), String> { + let file_id = file_id.original_file(db.upcast()); + + let mut candidate_files = Vec::new(); + match attr_path { + Some(attr_path) => { + candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner)) + } + None => { + candidate_files.push(format!("{}{}.rs", self.dir_path.0, name)); + candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name)); + } + }; + + for candidate in candidate_files.iter() { + if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) { + let is_mod_rs = candidate.ends_with("mod.rs"); + + let (dir_path, root_non_dir_owner) = if is_mod_rs || attr_path.is_some() { + (DirPath::empty(), false) + } else { + (DirPath::new(format!("{}/", name)), true) + }; + return Ok((file_id, is_mod_rs, ModDir { dir_path, root_non_dir_owner })); + } + } + Err(candidate_files.remove(0)) + } +} + +#[derive(Clone, Debug)] +struct DirPath(String); + +impl DirPath { + fn assert_invariant(&self) { + assert!(self.0.is_empty() || self.0.ends_with('/')); + } + fn new(repr: String) -> DirPath { + let res = DirPath(repr); + res.assert_invariant(); + res + } + fn empty() -> DirPath { + DirPath::new(String::new()) + } + fn push(&mut self, name: &str) { + self.0.push_str(name); + self.0.push('/'); + self.assert_invariant(); + } + fn parent(&self) -> Option<&str> { + if self.0.is_empty() { + return None; + }; + let idx = + self.0[..self.0.len() - '/'.len_utf8()].rfind('/').map_or(0, |it| it + '/'.len_utf8()); + Some(&self.0[..idx]) + } + /// So this is the case which doesn't really work I think if we try to be + /// 100% platform agnostic: + /// + /// ``` + /// mod a { + /// #[path="C://sad/face"] + /// mod b { mod c; } + /// } + /// ``` + /// + /// Here, we need to join logical dir path to a string path from an + /// attribute. Ideally, we should somehow losslessly communicate the whole + /// construction to `FileLoader`. + fn join_attr(&self, mut attr: &str, relative_to_parent: bool) -> String { + let base = if relative_to_parent { self.parent().unwrap() } else { &self.0 }; + + if attr.starts_with("./") { + attr = &attr["./".len()..]; + } + let tmp; + let attr = if attr.contains('\\') { + tmp = attr.replace('\\', "/"); + &tmp + } else { + attr + }; + let res = format!("{}{}", base, attr); + res + } +} diff --git a/crates/hir_def/src/nameres/path_resolution.rs b/crates/hir_def/src/nameres/path_resolution.rs new file mode 100644 index 000000000..88e10574e --- /dev/null +++ b/crates/hir_def/src/nameres/path_resolution.rs @@ -0,0 +1,330 @@ +//! This modules implements a function to resolve a path `foo::bar::baz` to a +//! def, which is used within the name resolution. +//! +//! When name resolution is finished, the result of resolving a path is either +//! `Some(def)` or `None`. However, when we are in process of resolving imports +//! or macros, there's a third possibility: +//! +//! I can't resolve this path right now, but I might be resolve this path +//! later, when more macros are expanded. +//! +//! `ReachedFixedPoint` signals about this. + +use std::iter::successors; + +use base_db::Edition; +use hir_expand::name::Name; +use test_utils::mark; + +use crate::{ + db::DefDatabase, + item_scope::BUILTIN_SCOPE, + nameres::{BuiltinShadowMode, CrateDefMap}, + path::{ModPath, PathKind}, + per_ns::PerNs, + visibility::{RawVisibility, Visibility}, + AdtId, CrateId, EnumVariantId, LocalModuleId, ModuleDefId, ModuleId, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum ResolveMode { + Import, + Other, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum ReachedFixedPoint { + Yes, + No, +} + +#[derive(Debug, Clone)] +pub(super) struct ResolvePathResult { + pub(super) resolved_def: PerNs, + pub(super) segment_index: Option, + pub(super) reached_fixedpoint: ReachedFixedPoint, + pub(super) krate: Option, +} + +impl ResolvePathResult { + fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult { + ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None, None) + } + + fn with( + resolved_def: PerNs, + reached_fixedpoint: ReachedFixedPoint, + segment_index: Option, + krate: Option, + ) -> ResolvePathResult { + ResolvePathResult { resolved_def, reached_fixedpoint, segment_index, krate } + } +} + +impl CrateDefMap { + pub(super) fn resolve_name_in_extern_prelude(&self, name: &Name) -> PerNs { + self.extern_prelude + .get(name) + .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public)) + } + + pub(crate) fn resolve_visibility( + &self, + db: &dyn DefDatabase, + original_module: LocalModuleId, + visibility: &RawVisibility, + ) -> Option { + match visibility { + RawVisibility::Module(path) => { + let (result, remaining) = + self.resolve_path(db, original_module, &path, BuiltinShadowMode::Module); + if remaining.is_some() { + return None; + } + let types = result.take_types()?; + match types { + ModuleDefId::ModuleId(m) => Some(Visibility::Module(m)), + _ => { + // error: visibility needs to refer to module + None + } + } + } + RawVisibility::Public => Some(Visibility::Public), + } + } + + // Returns Yes if we are sure that additions to `ItemMap` wouldn't change + // the result. + pub(super) fn resolve_path_fp_with_macro( + &self, + db: &dyn DefDatabase, + mode: ResolveMode, + original_module: LocalModuleId, + path: &ModPath, + shadow: BuiltinShadowMode, + ) -> ResolvePathResult { + let mut segments = path.segments.iter().enumerate(); + let mut curr_per_ns: PerNs = match path.kind { + PathKind::DollarCrate(krate) => { + if krate == self.krate { + mark::hit!(macro_dollar_crate_self); + PerNs::types( + ModuleId { krate: self.krate, local_id: self.root }.into(), + Visibility::Public, + ) + } else { + let def_map = db.crate_def_map(krate); + let module = ModuleId { krate, local_id: def_map.root }; + mark::hit!(macro_dollar_crate_other); + PerNs::types(module.into(), Visibility::Public) + } + } + PathKind::Crate => PerNs::types( + ModuleId { krate: self.krate, local_id: self.root }.into(), + Visibility::Public, + ), + // 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((idx, segment)) => (idx, 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) + } + PathKind::Plain => { + let (_, segment) = match segments.next() { + Some((idx, segment)) => (idx, segment), + None => return ResolvePathResult::empty(ReachedFixedPoint::Yes), + }; + // The first segment may be a builtin type. If the path has more + // than one segment, we first try resolving it as a module + // anyway. + // FIXME: If the next segment doesn't resolve in the module and + // BuiltinShadowMode wasn't Module, then we need to try + // resolving it as a builtin. + let prefer_module = + if path.segments.len() == 1 { shadow } else { BuiltinShadowMode::Module }; + + log::debug!("resolving {:?} in module", segment); + self.resolve_name_in_module(db, original_module, &segment, prefer_module) + } + PathKind::Super(lvl) => { + let m = successors(Some(original_module), |m| self.modules[*m].parent) + .nth(lvl as usize); + if let Some(local_id) = m { + PerNs::types( + ModuleId { krate: self.krate, local_id }.into(), + Visibility::Public, + ) + } 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) { + log::debug!("absolute path {:?} resolved to crate {:?}", path, def); + PerNs::types(*def, Visibility::Public) + } else { + return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude + } + } + }; + + for (i, segment) in segments { + let (curr, vis) = match curr_per_ns.take_types_vis() { + 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 = ModPath { + segments: path.segments[i..].to_vec(), + kind: PathKind::Super(0), + }; + 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.local_id, &path, shadow); + return ResolvePathResult::with( + def, + ReachedFixedPoint::Yes, + s.map(|s| s + i), + Some(module.krate), + ); + } + + // Since it is a qualified path here, it should not contains legacy macros + self[module.local_id].scope.get(&segment) + } + ModuleDefId::AdtId(AdtId::EnumId(e)) => { + // enum variant + mark::hit!(can_import_enum_variant); + let enum_data = db.enum_data(e); + match enum_data.variant(&segment) { + Some(local_id) => { + let variant = EnumVariantId { parent: e, local_id }; + match &*enum_data.variants[local_id].variant_data { + crate::adt::VariantData::Record(_) => { + PerNs::types(variant.into(), Visibility::Public) + } + crate::adt::VariantData::Tuple(_) + | crate::adt::VariantData::Unit => { + PerNs::both(variant.into(), variant.into(), Visibility::Public) + } + } + } + None => { + return ResolvePathResult::with( + PerNs::types(e.into(), vis), + ReachedFixedPoint::Yes, + Some(i), + Some(self.krate), + ); + } + } + } + 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, + curr, + ); + + return ResolvePathResult::with( + PerNs::types(s, vis), + ReachedFixedPoint::Yes, + Some(i), + Some(self.krate), + ); + } + }; + } + + ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None, Some(self.krate)) + } + + fn resolve_name_in_module( + &self, + db: &dyn DefDatabase, + module: LocalModuleId, + name: &Name, + shadow: BuiltinShadowMode, + ) -> 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, |m| PerNs::macros(m, Visibility::Public)); + let from_scope = self[module].scope.get(name); + let from_builtin = BUILTIN_SCOPE.get(name).copied().unwrap_or_else(PerNs::none); + let from_scope_or_builtin = match shadow { + BuiltinShadowMode::Module => from_scope.or(from_builtin), + BuiltinShadowMode::Other => { + if let Some(ModuleDefId::ModuleId(_)) = from_scope.take_types() { + from_builtin.or(from_scope) + } else { + from_scope.or(from_builtin) + } + } + }; + let from_extern_prelude = self + .extern_prelude + .get(name) + .map_or(PerNs::none(), |&it| PerNs::types(it, Visibility::Public)); + let from_prelude = self.resolve_in_prelude(db, name); + + from_legacy_macro.or(from_scope_or_builtin).or(from_extern_prelude).or(from_prelude) + } + + fn resolve_name_in_crate_root_or_extern_prelude(&self, name: &Name) -> PerNs { + let from_crate_root = self[self.root].scope.get(name); + let from_extern_prelude = self.resolve_name_in_extern_prelude(name); + + from_crate_root.or(from_extern_prelude) + } + + fn resolve_in_prelude(&self, db: &dyn 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.local_id].scope.get(name) + } else { + PerNs::none() + } + } +} diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs new file mode 100644 index 000000000..b105d56b2 --- /dev/null +++ b/crates/hir_def/src/nameres/tests.rs @@ -0,0 +1,690 @@ +mod globs; +mod incremental; +mod macros; +mod mod_resolution; +mod primitives; + +use std::sync::Arc; + +use base_db::{fixture::WithFixture, SourceDatabase}; +use expect::{expect, Expect}; +use test_utils::mark; + +use crate::{db::DefDatabase, nameres::*, test_db::TestDB}; + +fn compute_crate_def_map(fixture: &str) -> Arc { + let db = TestDB::with_files(fixture); + let krate = db.crate_graph().iter().next().unwrap(); + db.crate_def_map(krate) +} + +fn check(ra_fixture: &str, expect: Expect) { + let db = TestDB::with_files(ra_fixture); + let krate = db.crate_graph().iter().next().unwrap(); + let actual = db.crate_def_map(krate).dump(); + expect.assert_eq(&actual); +} + +#[test] +fn crate_def_map_smoke_test() { + check( + r#" +//- /lib.rs +mod foo; +struct S; +use crate::foo::bar::E; +use self::E::V; + +//- /foo/mod.rs +pub mod bar; +fn f() {} + +//- /foo/bar.rs +pub struct Baz; + +union U { to_be: bool, not_to_be: u8 } +enum E { V } + +extern { + static EXT: u8; + fn ext(); +} +"#, + expect![[r#" + crate + E: t + S: t v + V: t v + foo: t + + crate::foo + bar: t + f: v + + crate::foo::bar + Baz: t v + E: t + EXT: v + U: t + ext: v + "#]], + ); +} + +#[test] +fn crate_def_map_super_super() { + check( + r#" +mod a { + const A: usize = 0; + mod b { + const B: usize = 0; + mod c { + use super::super::*; + } + } +} +"#, + expect![[r#" + crate + a: t + + crate::a + A: v + b: t + + crate::a::b + B: v + c: t + + crate::a::b::c + A: v + b: t + "#]], + ); +} + +#[test] +fn crate_def_map_fn_mod_same_name() { + check( + r#" +mod m { + pub mod z {} + pub fn z() {} +} +"#, + expect![[r#" + crate + m: t + + crate::m + z: t v + + crate::m::z + "#]], + ); +} + +#[test] +fn bogus_paths() { + mark::check!(bogus_paths); + check( + r#" +//- /lib.rs +mod foo; +struct S; +use self; + +//- /foo/mod.rs +use super; +use crate; +"#, + expect![[r#" + crate + S: t v + foo: t + + crate::foo + "#]], + ); +} + +#[test] +fn use_as() { + check( + r#" +//- /lib.rs +mod foo; +use crate::foo::Baz as Foo; + +//- /foo/mod.rs +pub struct Baz; +"#, + expect![[r#" + crate + Foo: t v + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn use_trees() { + check( + r#" +//- /lib.rs +mod foo; +use crate::foo::bar::{Baz, Quux}; + +//- /foo/mod.rs +pub mod bar; + +//- /foo/bar.rs +pub struct Baz; +pub enum Quux {}; +"#, + expect![[r#" + crate + Baz: t v + Quux: t + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + Quux: t + "#]], + ); +} + +#[test] +fn re_exports() { + check( + r#" +//- /lib.rs +mod foo; +use self::foo::Baz; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::Baz; + +//- /foo/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn std_prelude() { + mark::check!(std_prelude); + check( + r#" +//- /main.rs crate:main deps:test_crate +use Foo::*; + +//- /lib.rs crate:test_crate +mod prelude; +#[prelude_import] +use prelude::*; + +//- /prelude.rs +pub enum Foo { Bar, Baz }; +"#, + expect![[r#" + crate + Bar: t v + Baz: t v + "#]], + ); +} + +#[test] +fn can_import_enum_variant() { + mark::check!(can_import_enum_variant); + check( + r#" +enum E { V } +use self::E::V; +"#, + expect![[r#" + crate + E: t + V: t v + "#]], + ); +} + +#[test] +fn edition_2015_imports() { + check( + r#" +//- /main.rs crate:main deps:other_crate edition:2015 +mod foo; +mod bar; + +//- /bar.rs +struct Bar; + +//- /foo.rs +use bar::Bar; +use other_crate::FromLib; + +//- /lib.rs crate:other_crate edition:2018 +struct FromLib; +"#, + expect![[r#" + crate + bar: t + foo: t + + crate::bar + Bar: t v + + crate::foo + Bar: t v + FromLib: t v + "#]], + ); +} + +#[test] +fn item_map_using_self() { + check( + r#" +//- /lib.rs +mod foo; +use crate::foo::bar::Baz::{self}; + +//- /foo/mod.rs +pub mod bar; + +//- /foo/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn item_map_across_crates() { + check( + r#" +//- /main.rs crate:main deps:test_crate +use test_crate::Baz; + +//- /lib.rs crate:test_crate +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + "#]], + ); +} + +#[test] +fn extern_crate_rename() { + check( + r#" +//- /main.rs crate:main deps:alloc +extern crate alloc as alloc_crate; +mod alloc; +mod sync; + +//- /sync.rs +use alloc_crate::Arc; + +//- /lib.rs crate:alloc +struct Arc; +"#, + expect![[r#" + crate + alloc_crate: t + sync: t + + crate::sync + Arc: t v + "#]], + ); +} + +#[test] +fn extern_crate_rename_2015_edition() { + check( + r#" +//- /main.rs crate:main deps:alloc edition:2015 +extern crate alloc as alloc_crate; +mod alloc; +mod sync; + +//- /sync.rs +use alloc_crate::Arc; + +//- /lib.rs crate:alloc +struct Arc; +"#, + expect![[r#" + crate + alloc_crate: t + sync: t + + crate::sync + Arc: t v + "#]], + ); +} + +#[test] +fn reexport_across_crates() { + check( + r#" +//- /main.rs crate:main deps:test_crate +use test_crate::Baz; + +//- /lib.rs crate:test_crate +pub use foo::Baz; +mod foo; + +//- /foo.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + "#]], + ); +} + +#[test] +fn values_dont_shadow_extern_crates() { + check( + r#" +//- /main.rs crate:main deps:foo +fn foo() {} +use foo::Bar; + +//- /foo/lib.rs crate:foo +pub struct Bar; +"#, + expect![[r#" + crate + Bar: t v + foo: v + "#]], + ); +} + +#[test] +fn std_prelude_takes_precedence_above_core_prelude() { + check( + r#" +//- /main.rs crate:main deps:core,std +use {Foo, Bar}; + +//- /std.rs crate:std deps:core +#[prelude_import] +pub use self::prelude::*; +mod prelude { + pub struct Foo; + pub use core::prelude::Bar; +} + +//- /core.rs crate:core +#[prelude_import] +pub use self::prelude::*; +mod prelude { + pub struct Bar; +} +"#, + expect![[r#" + crate + Bar: t v + Foo: t v + "#]], + ); +} + +#[test] +fn cfg_not_test() { + check( + r#" +//- /main.rs crate:main deps:std +use {Foo, Bar, Baz}; + +//- /lib.rs crate:std +#[prelude_import] +pub use self::prelude::*; +mod prelude { + #[cfg(test)] + pub struct Foo; + #[cfg(not(test))] + pub struct Bar; + #[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))] + pub struct Baz; +} +"#, + expect![[r#" + crate + Bar: t v + Baz: _ + Foo: _ + "#]], + ); +} + +#[test] +fn cfg_test() { + check( + r#" +//- /main.rs crate:main deps:std +use {Foo, Bar, Baz}; + +//- /lib.rs crate:std cfg:test,feature=foo,feature=bar,opt=42 +#[prelude_import] +pub use self::prelude::*; +mod prelude { + #[cfg(test)] + pub struct Foo; + #[cfg(not(test))] + pub struct Bar; + #[cfg(all(not(any()), feature = "foo", feature = "bar", opt = "42"))] + pub struct Baz; +} +"#, + expect![[r#" + crate + Bar: _ + Baz: t v + Foo: t v + "#]], + ); +} + +#[test] +fn infer_multiple_namespace() { + check( + r#" +//- /main.rs +mod a { + pub type T = (); + pub use crate::b::*; +} + +use crate::a::T; + +mod b { + pub const T: () = (); +} +"#, + expect![[r#" + crate + T: t v + a: t + b: t + + crate::b + T: v + + crate::a + T: t v + "#]], + ); +} + +#[test] +fn underscore_import() { + check( + r#" +//- /main.rs +use tr::Tr as _; +use tr::Tr2 as _; + +mod tr { + pub trait Tr {} + pub trait Tr2 {} +} + "#, + expect![[r#" + crate + _: t + _: t + tr: t + + crate::tr + Tr: t + Tr2: t + "#]], + ); +} + +#[test] +fn underscore_reexport() { + check( + r#" +//- /main.rs +mod tr { + pub trait PubTr {} + pub trait PrivTr {} +} +mod reex { + use crate::tr::PrivTr as _; + pub use crate::tr::PubTr as _; +} +use crate::reex::*; + "#, + expect![[r#" + crate + _: t + reex: t + tr: t + + crate::tr + PrivTr: t + PubTr: t + + crate::reex + _: t + _: t + "#]], + ); +} + +#[test] +fn underscore_pub_crate_reexport() { + mark::check!(upgrade_underscore_visibility); + check( + r#" +//- /main.rs crate:main deps:lib +use lib::*; + +//- /lib.rs crate:lib +use tr::Tr as _; +pub use tr::Tr as _; + +mod tr { + pub trait Tr {} +} + "#, + expect![[r#" + crate + _: t + "#]], + ); +} + +#[test] +fn underscore_nontrait() { + check( + r#" +//- /main.rs +mod m { + pub struct Struct; + pub enum Enum {} + pub const CONST: () = (); +} +use crate::m::{Struct as _, Enum as _, CONST as _}; + "#, + expect![[r#" + crate + m: t + + crate::m + CONST: v + Enum: t + Struct: t v + "#]], + ); +} + +#[test] +fn underscore_name_conflict() { + check( + r#" +//- /main.rs +struct Tr; + +use tr::Tr as _; + +mod tr { + pub trait Tr {} +} + "#, + expect![[r#" + crate + _: t + Tr: t v + tr: t + + crate::tr + Tr: t + "#]], + ); +} diff --git a/crates/hir_def/src/nameres/tests/globs.rs b/crates/hir_def/src/nameres/tests/globs.rs new file mode 100644 index 000000000..2ae836e3c --- /dev/null +++ b/crates/hir_def/src/nameres/tests/globs.rs @@ -0,0 +1,338 @@ +use super::*; + +#[test] +fn glob_1() { + check( + r#" +//- /lib.rs +mod foo; +use foo::*; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::Baz; +pub struct Foo; + +//- /foo/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + Foo: t v + bar: t + foo: t + + crate::foo + Baz: t v + Foo: t v + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn glob_2() { + check( + r#" +//- /lib.rs +mod foo; +use foo::*; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::*; +pub struct Foo; + +//- /foo/bar.rs +pub struct Baz; +pub use super::*; +"#, + expect![[r#" + crate + Baz: t v + Foo: t v + bar: t + foo: t + + crate::foo + Baz: t v + Foo: t v + bar: t + + crate::foo::bar + Baz: t v + Foo: t v + bar: t + "#]], + ); +} + +#[test] +fn glob_privacy_1() { + check( + r" +//- /lib.rs +mod foo; +use foo::*; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::*; +struct PrivateStructFoo; + +//- /foo/bar.rs +pub struct Baz; +struct PrivateStructBar; +pub use super::*; +", + expect![[r#" + crate + Baz: t v + bar: t + foo: t + + crate::foo + Baz: t v + PrivateStructFoo: t v + bar: t + + crate::foo::bar + Baz: t v + PrivateStructBar: t v + PrivateStructFoo: t v + bar: t + "#]], + ); +} + +#[test] +fn glob_privacy_2() { + check( + r" +//- /lib.rs +mod foo; +use foo::*; +use foo::bar::*; + +//- /foo/mod.rs +mod bar; +fn Foo() {}; +pub struct Foo {}; + +//- /foo/bar.rs +pub(super) struct PrivateBaz; +struct PrivateBar; +pub(crate) struct PubCrateStruct; +", + expect![[r#" + crate + Foo: t + PubCrateStruct: t v + foo: t + + crate::foo + Foo: t v + bar: t + + crate::foo::bar + PrivateBar: t v + PrivateBaz: t v + PubCrateStruct: t v + "#]], + ); +} + +#[test] +fn glob_across_crates() { + mark::check!(glob_across_crates); + check( + r#" +//- /main.rs crate:main deps:test_crate +use test_crate::*; + +//- /lib.rs crate:test_crate +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + "#]], + ); +} + +#[test] +fn glob_privacy_across_crates() { + check( + r#" +//- /main.rs crate:main deps:test_crate +use test_crate::*; + +//- /lib.rs crate:test_crate +pub struct Baz; +struct Foo; +"#, + expect![[r#" + crate + Baz: t v + "#]], + ); +} + +#[test] +fn glob_enum() { + mark::check!(glob_enum); + check( + r#" +enum Foo { Bar, Baz } +use self::Foo::*; +"#, + expect![[r#" + crate + Bar: t v + Baz: t v + Foo: t + "#]], + ); +} + +#[test] +fn glob_enum_group() { + mark::check!(glob_enum_group); + check( + r#" +enum Foo { Bar, Baz } +use self::Foo::{*}; +"#, + expect![[r#" + crate + Bar: t v + Baz: t v + Foo: t + "#]], + ); +} + +#[test] +fn glob_shadowed_def() { + mark::check!(import_shadowed); + check( + r#" +//- /lib.rs +mod foo; +mod bar; +use foo::*; +use bar::baz; +use baz::Bar; + +//- /foo.rs +pub mod baz { pub struct Foo; } + +//- /bar.rs +pub mod baz { pub struct Bar; } +"#, + expect![[r#" + crate + Bar: t v + bar: t + baz: t + foo: t + + crate::bar + baz: t + + crate::bar::baz + Bar: t v + + crate::foo + baz: t + + crate::foo::baz + Foo: t v + "#]], + ); +} + +#[test] +fn glob_shadowed_def_reversed() { + check( + r#" +//- /lib.rs +mod foo; +mod bar; +use bar::baz; +use foo::*; +use baz::Bar; + +//- /foo.rs +pub mod baz { pub struct Foo; } + +//- /bar.rs +pub mod baz { pub struct Bar; } +"#, + expect![[r#" + crate + Bar: t v + bar: t + baz: t + foo: t + + crate::bar + baz: t + + crate::bar::baz + Bar: t v + + crate::foo + baz: t + + crate::foo::baz + Foo: t v + "#]], + ); +} + +#[test] +fn glob_shadowed_def_dependencies() { + check( + r#" +mod a { pub mod foo { pub struct X; } } +mod b { pub use super::a::foo; } +mod c { pub mod foo { pub struct Y; } } +mod d { + use super::c::foo; + use super::b::*; + use foo::Y; +} +"#, + expect![[r#" + crate + a: t + b: t + c: t + d: t + + crate::d + Y: t v + foo: t + + crate::c + foo: t + + crate::c::foo + Y: t v + + crate::b + foo: t + + crate::a + foo: t + + crate::a::foo + X: t v + "#]], + ); +} diff --git a/crates/hir_def/src/nameres/tests/incremental.rs b/crates/hir_def/src/nameres/tests/incremental.rs new file mode 100644 index 000000000..cfbc62cc4 --- /dev/null +++ b/crates/hir_def/src/nameres/tests/incremental.rs @@ -0,0 +1,101 @@ +use std::sync::Arc; + +use base_db::SourceDatabaseExt; + +use super::*; + +fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) { + let (mut db, pos) = TestDB::with_position(ra_fixture_initial); + let krate = db.test_crate(); + { + let events = db.log_executed(|| { + db.crate_def_map(krate); + }); + assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } + db.set_file_text(pos.file_id, Arc::new(ra_fixture_change.to_string())); + + { + let events = db.log_executed(|| { + db.crate_def_map(krate); + }); + assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } +} + +#[test] +fn typing_inside_a_function_should_not_invalidate_def_map() { + check_def_map_is_not_recomputed( + r" + //- /lib.rs + mod foo;<|> + + use crate::foo::bar::Baz; + + enum E { A, B } + use E::*; + + fn foo() -> i32 { + 1 + 1 + } + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + pub struct Baz; + ", + r" + mod foo; + + use crate::foo::bar::Baz; + + enum E { A, B } + use E::*; + + fn foo() -> i32 { 92 } + ", + ); +} + +#[test] +fn typing_inside_a_macro_should_not_invalidate_def_map() { + let (mut db, pos) = TestDB::with_position( + r" + //- /lib.rs + macro_rules! m { + ($ident:ident) => { + fn f() { + $ident + $ident; + }; + } + } + mod foo; + + //- /foo/mod.rs + pub mod bar; + + //- /foo/bar.rs + <|> + m!(X); + ", + ); + let krate = db.test_crate(); + { + let events = db.log_executed(|| { + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.resolutions().count(), 1); + }); + assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } + db.set_file_text(pos.file_id, Arc::new("m!(Y);".to_string())); + + { + let events = db.log_executed(|| { + let crate_def_map = db.crate_def_map(krate); + let (_, module_data) = crate_def_map.modules.iter().last().unwrap(); + assert_eq!(module_data.scope.resolutions().count(), 1); + }); + assert!(!format!("{:?}", events).contains("crate_def_map"), "{:#?}", events) + } +} diff --git a/crates/hir_def/src/nameres/tests/macros.rs b/crates/hir_def/src/nameres/tests/macros.rs new file mode 100644 index 000000000..e0fb8bdef --- /dev/null +++ b/crates/hir_def/src/nameres/tests/macros.rs @@ -0,0 +1,669 @@ +use super::*; + +#[test] +fn macro_rules_are_globally_visible() { + check( + r#" +//- /lib.rs +macro_rules! structs { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } +} +structs!(Foo); +mod nested; + +//- /nested.rs +structs!(Bar, Baz); +"#, + expect![[r#" + crate + Foo: t + nested: t + + crate::nested + Bar: t + Baz: t + "#]], + ); +} + +#[test] +fn macro_rules_can_define_modules() { + check( + r#" +//- /lib.rs +macro_rules! m { + ($name:ident) => { mod $name; } +} +m!(n1); +mod m { m!(n3) } + +//- /n1.rs +m!(n2) +//- /n1/n2.rs +struct X; +//- /m/n3.rs +struct Y; +"#, + expect![[r#" + crate + m: t + n1: t + + crate::m + n3: t + + crate::m::n3 + Y: t v + + crate::n1 + n2: t + + crate::n1::n2 + X: t v + "#]], + ); +} + +#[test] +fn macro_rules_from_other_crates_are_visible() { + check( + r#" +//- /main.rs crate:main deps:foo +foo::structs!(Foo, Bar) +mod bar; + +//- /bar.rs +use crate::*; + +//- /lib.rs crate:foo +#[macro_export] +macro_rules! structs { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } +} +"#, + expect![[r#" + crate + Bar: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[test] +fn macro_rules_export_with_local_inner_macros_are_visible() { + check( + r#" +//- /main.rs crate:main deps:foo +foo::structs!(Foo, Bar) +mod bar; + +//- /bar.rs +use crate::*; + +//- /lib.rs crate:foo +#[macro_export(local_inner_macros)] +macro_rules! structs { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } +} +"#, + expect![[r#" + crate + Bar: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[test] +fn local_inner_macros_makes_local_macros_usable() { + check( + r#" +//- /main.rs crate:main deps:foo +foo::structs!(Foo, Bar); +mod bar; + +//- /bar.rs +use crate::*; + +//- /lib.rs crate:foo +#[macro_export(local_inner_macros)] +macro_rules! structs { + ($($i:ident),*) => { + inner!($($i),*); + } +} +#[macro_export] +macro_rules! inner { + ($($i:ident),*) => { + $(struct $i { field: u32 } )* + } +} +"#, + expect![[r#" + crate + Bar: t + Foo: t + bar: t + + crate::bar + Bar: t + Foo: t + bar: t + "#]], + ); +} + +#[test] +fn unexpanded_macro_should_expand_by_fixedpoint_loop() { + check( + r#" +//- /main.rs crate:main deps:foo +macro_rules! baz { + () => { + use foo::bar; + } +} +foo!(); +bar!(); +baz!(); + +//- /lib.rs crate:foo +#[macro_export] +macro_rules! foo { + () => { + struct Foo { field: u32 } + } +} +#[macro_export] +macro_rules! bar { + () => { + use foo::foo; + } +} +"#, + expect![[r#" + crate + Foo: t + bar: m + foo: m + "#]], + ); +} + +#[test] +fn macro_rules_from_other_crates_are_visible_with_macro_use() { + mark::check!(macro_rules_from_other_crates_are_visible_with_macro_use); + check( + r#" +//- /main.rs crate:main deps:foo +structs!(Foo); +structs_priv!(Bar); +structs_not_exported!(MacroNotResolved1); +crate::structs!(MacroNotResolved2); + +mod bar; + +#[macro_use] +extern crate foo; + +//- /bar.rs +structs!(Baz); +crate::structs!(MacroNotResolved3); + +//- /lib.rs crate:foo +#[macro_export] +macro_rules! structs { + ($i:ident) => { struct $i; } +} + +macro_rules! structs_not_exported { + ($i:ident) => { struct $i; } +} + +mod priv_mod { + #[macro_export] + macro_rules! structs_priv { + ($i:ident) => { struct $i; } + } +} +"#, + expect![[r#" + crate + Bar: t v + Foo: t v + bar: t + foo: t + + crate::bar + Baz: t v + "#]], + ); +} + +#[test] +fn prelude_is_macro_use() { + mark::check!(prelude_is_macro_use); + check( + r#" +//- /main.rs crate:main deps:foo +structs!(Foo); +structs_priv!(Bar); +structs_outside!(Out); +crate::structs!(MacroNotResolved2); + +mod bar; + +//- /bar.rs +structs!(Baz); +crate::structs!(MacroNotResolved3); + +//- /lib.rs crate:foo +#[prelude_import] +use self::prelude::*; + +mod prelude { + #[macro_export] + macro_rules! structs { + ($i:ident) => { struct $i; } + } + + mod priv_mod { + #[macro_export] + macro_rules! structs_priv { + ($i:ident) => { struct $i; } + } + } +} + +#[macro_export] +macro_rules! structs_outside { + ($i:ident) => { struct $i; } +} +"#, + expect![[r#" + crate + Bar: t v + Foo: t v + Out: t v + bar: t + + crate::bar + Baz: t v + "#]], + ); +} + +#[test] +fn prelude_cycle() { + check( + r#" +#[prelude_import] +use self::prelude::*; + +declare_mod!(); + +mod prelude { + macro_rules! declare_mod { + () => (mod foo {}) + } +} +"#, + expect![[r#" + crate + prelude: t + + crate::prelude + "#]], + ); +} + +#[test] +fn plain_macros_are_legacy_textual_scoped() { + check( + r#" +//- /main.rs +mod m1; +bar!(NotFoundNotMacroUse); + +mod m2 { foo!(NotFoundBeforeInside2); } + +macro_rules! foo { + ($x:ident) => { struct $x; } +} +foo!(Ok); + +mod m3; +foo!(OkShadowStop); +bar!(NotFoundMacroUseStop); + +#[macro_use] +mod m5 { + #[macro_use] + mod m6 { + macro_rules! foo { + ($x:ident) => { fn $x() {} } + } + } +} +foo!(ok_double_macro_use_shadow); + +baz!(NotFoundBefore); +#[macro_use] +mod m7 { + macro_rules! baz { + ($x:ident) => { struct $x; } + } +} +baz!(OkAfter); + +//- /m1.rs +foo!(NotFoundBeforeInside1); +macro_rules! bar { + ($x:ident) => { struct $x; } +} + +//- /m3/mod.rs +foo!(OkAfterInside); +macro_rules! foo { + ($x:ident) => { fn $x() {} } +} +foo!(ok_shadow); + +#[macro_use] +mod m4; +bar!(OkMacroUse); + +//- /m3/m4.rs +foo!(ok_shadow_deep); +macro_rules! bar { + ($x:ident) => { struct $x; } +} +"#, + expect![[r#" + crate + Ok: t v + OkAfter: t v + OkShadowStop: t v + m1: t + m2: t + m3: t + m5: t + m7: t + ok_double_macro_use_shadow: v + + crate::m7 + + crate::m1 + + crate::m5 + m6: t + + crate::m5::m6 + + crate::m2 + + crate::m3 + OkAfterInside: t v + OkMacroUse: t v + m4: t + ok_shadow: v + + crate::m3::m4 + ok_shadow_deep: v + "#]], + ); +} + +#[test] +fn type_value_macro_live_in_different_scopes() { + check( + r#" +#[macro_export] +macro_rules! foo { + ($x:ident) => { type $x = (); } +} + +foo!(foo); +use foo as bar; + +use self::foo as baz; +fn baz() {} +"#, + expect![[r#" + crate + bar: t m + baz: t v m + foo: t m + "#]], + ); +} + +#[test] +fn macro_use_can_be_aliased() { + check( + r#" +//- /main.rs crate:main deps:foo +#[macro_use] +extern crate foo; + +foo!(Direct); +bar!(Alias); + +//- /lib.rs crate:foo +use crate::foo as bar; + +mod m { + #[macro_export] + macro_rules! foo { + ($x:ident) => { struct $x; } + } +} +"#, + expect![[r#" + crate + Alias: t v + Direct: t v + foo: t + "#]], + ); +} + +#[test] +fn path_qualified_macros() { + check( + r#" +macro_rules! foo { + ($x:ident) => { struct $x; } +} + +crate::foo!(NotResolved); + +crate::bar!(OkCrate); +bar!(OkPlain); +alias1!(NotHere); +m::alias1!(OkAliasPlain); +m::alias2!(OkAliasSuper); +m::alias3!(OkAliasCrate); +not_found!(NotFound); + +mod m { + #[macro_export] + macro_rules! bar { + ($x:ident) => { struct $x; } + } + pub use bar as alias1; + pub use super::bar as alias2; + pub use crate::bar as alias3; + pub use self::bar as not_found; +} +"#, + expect![[r#" + crate + OkAliasCrate: t v + OkAliasPlain: t v + OkAliasSuper: t v + OkCrate: t v + OkPlain: t v + bar: m + m: t + + crate::m + alias1: m + alias2: m + alias3: m + not_found: _ + "#]], + ); +} + +#[test] +fn macro_dollar_crate_is_correct_in_item() { + mark::check!(macro_dollar_crate_self); + check( + r#" +//- /main.rs crate:main deps:foo +#[macro_use] +extern crate foo; + +#[macro_use] +mod m { + macro_rules! current { + () => { + use $crate::Foo as FooSelf; + } + } +} + +struct Foo; + +current!(); +not_current1!(); +foo::not_current2!(); + +//- /lib.rs crate:foo +mod m { + #[macro_export] + macro_rules! not_current1 { + () => { + use $crate::Bar; + } + } +} + +#[macro_export] +macro_rules! not_current2 { + () => { + use $crate::Baz; + } +} + +struct Bar; +struct Baz; +"#, + expect![[r#" + crate + Bar: t v + Baz: t v + Foo: t v + FooSelf: t v + foo: t + m: t + + crate::m + "#]], + ); +} + +#[test] +fn macro_dollar_crate_is_correct_in_indirect_deps() { + mark::check!(macro_dollar_crate_other); + // From std + check( + r#" +//- /main.rs crate:main deps:std +foo!(); + +//- /std.rs crate:std deps:core +#[prelude_import] +use self::prelude::*; + +pub use core::foo; + +mod prelude {} + +#[macro_use] +mod std_macros; + +//- /core.rs crate:core +#[macro_export] +macro_rules! foo { + () => { + use $crate::bar; + } +} + +pub struct bar; +"#, + expect![[r#" + crate + bar: t v + "#]], + ); +} + +#[test] +fn expand_derive() { + let map = compute_crate_def_map( + " + //- /main.rs + #[derive(Copy, Clone)] + struct Foo; + ", + ); + assert_eq!(map.modules[map.root].scope.impls().len(), 2); +} + +#[test] +fn macro_expansion_overflow() { + mark::check!(macro_expansion_overflow); + check( + r#" +macro_rules! a { + ($e:expr; $($t:tt)*) => { + b!($($t)*); + }; + () => {}; +} + +macro_rules! b { + (static = $e:expr; $($t:tt)*) => { + a!($e; $($t)*); + }; + () => {}; +} + +b! { static = #[] (); } +"#, + expect![[r#" + crate + "#]], + ); +} diff --git a/crates/hir_def/src/nameres/tests/mod_resolution.rs b/crates/hir_def/src/nameres/tests/mod_resolution.rs new file mode 100644 index 000000000..1f619787e --- /dev/null +++ b/crates/hir_def/src/nameres/tests/mod_resolution.rs @@ -0,0 +1,796 @@ +use super::*; + +#[test] +fn name_res_works_for_broken_modules() { + mark::check!(name_res_works_for_broken_modules); + check( + r" +//- /lib.rs +mod foo // no `;`, no body +use self::foo::Baz; + +//- /foo/mod.rs +pub mod bar; +pub use self::bar::Baz; + +//- /foo/bar.rs +pub struct Baz; +", + expect![[r#" + crate + Baz: _ + foo: t + + crate::foo + "#]], + ); +} + +#[test] +fn nested_module_resolution() { + check( + r#" +//- /lib.rs +mod n1; + +//- /n1.rs +mod n2; + +//- /n1/n2.rs +struct X; +"#, + expect![[r#" + crate + n1: t + + crate::n1 + n2: t + + crate::n1::n2 + X: t v + "#]], + ); +} + +#[test] +fn nested_module_resolution_2() { + check( + r#" +//- /lib.rs +mod prelude; +mod iter; + +//- /prelude.rs +pub use crate::iter::Iterator; + +//- /iter.rs +pub use self::traits::Iterator; +mod traits; + +//- /iter/traits.rs +pub use self::iterator::Iterator; +mod iterator; + +//- /iter/traits/iterator.rs +pub trait Iterator; +"#, + expect![[r#" + crate + iter: t + prelude: t + + crate::iter + Iterator: t + traits: t + + crate::iter::traits + Iterator: t + iterator: t + + crate::iter::traits::iterator + Iterator: t + + crate::prelude + Iterator: t + "#]], + ); +} + +#[test] +fn module_resolution_works_for_non_standard_filenames() { + check( + r#" +//- /my_library.rs crate:my_library +mod foo; +use self::foo::Bar; + +//- /foo/mod.rs +pub struct Bar; +"#, + expect![[r#" + crate + Bar: t v + foo: t + + crate::foo + Bar: t v + "#]], + ); +} + +#[test] +fn module_resolution_works_for_raw_modules() { + check( + r#" +//- /lib.rs +mod r#async; +use self::r#async::Bar; + +//- /async.rs +pub struct Bar; +"#, + expect![[r#" + crate + Bar: t v + async: t + + crate::async + Bar: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_path() { + check( + r#" +//- /lib.rs +#[path = "bar/baz/foo.rs"] +mod foo; +use self::foo::Bar; + +//- /bar/baz/foo.rs +pub struct Bar; +"#, + expect![[r#" + crate + Bar: t v + foo: t + + crate::foo + Bar: t v + "#]], + ); +} + +#[test] +fn module_resolution_module_with_path_in_mod_rs() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo/mod.rs +#[path = "baz.rs"] +pub mod bar; +use self::bar::Baz; + +//- /foo/baz.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_module_with_path_non_crate_root() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo.rs +#[path = "baz.rs"] +pub mod bar; +use self::bar::Baz; + +//- /baz.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_module_decl_path_super() { + check( + r#" +//- /main.rs +#[path = "bar/baz/module.rs"] +mod foo; +pub struct Baz; + +//- /bar/baz/module.rs +use super::Baz; +"#, + expect![[r#" + crate + Baz: t v + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_explicit_path_mod_rs() { + check( + r#" +//- /main.rs +#[path = "module/mod.rs"] +mod foo; + +//- /module/mod.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_relative_path() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo.rs +#[path = "./sub.rs"] +pub mod foo_bar; + +//- /sub.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + foo_bar: t + + crate::foo::foo_bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_relative_path_2() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo/mod.rs +#[path="../sub.rs"] +pub mod foo_bar; + +//- /sub.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + foo_bar: t + + crate::foo::foo_bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_relative_path_outside_root() { + check( + r#" +//- /main.rs +#[path="../../../../../outside.rs"] +mod foo; +"#, + expect![[r#" + crate + "#]], + ); +} + +#[test] +fn module_resolution_explicit_path_mod_rs_2() { + check( + r#" +//- /main.rs +#[path = "module/bar/mod.rs"] +mod foo; + +//- /module/bar/mod.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_explicit_path_mod_rs_with_win_separator() { + check( + r#" +//- /main.rs +#[path = "module\bar\mod.rs"] +mod foo; + +//- /module/bar/mod.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_with_path_attribute() { + check( + r#" +//- /main.rs +#[path = "models"] +mod foo { mod bar; } + +//- /models/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module() { + check( + r#" +//- /main.rs +mod foo { mod bar; } + +//- /foo/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_2_with_path_attribute() { + check( + r#" +//- /main.rs +#[path = "models/db"] +mod foo { mod bar; } + +//- /models/db/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_3() { + check( + r#" +//- /main.rs +#[path = "models/db"] +mod foo { + #[path = "users.rs"] + mod bar; +} + +//- /models/db/users.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_empty_path() { + check( + r#" +//- /main.rs +#[path = ""] +mod foo { + #[path = "users.rs"] + mod bar; +} + +//- /users.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_empty_path() { + check( + r#" +//- /main.rs +#[path = ""] // Should try to read `/` (a directory) +mod foo; + +//- /foo.rs +pub struct Baz; +"#, + expect![[r#" + crate + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_relative_path() { + check( + r#" +//- /main.rs +#[path = "./models"] +mod foo { mod bar; } + +//- /models/bar.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_in_crate_root() { + check( + r#" +//- /main.rs +mod foo { + #[path = "baz.rs"] + mod bar; +} +use self::foo::bar::Baz; + +//- /foo/baz.rs +pub struct Baz; +"#, + expect![[r#" + crate + Baz: t v + foo: t + + crate::foo + bar: t + + crate::foo::bar + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_in_mod_rs() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo/mod.rs +mod bar { + #[path = "qwe.rs"] + pub mod baz; +} +use self::bar::baz::Baz; + +//- /foo/bar/qwe.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + baz: t + + crate::foo::bar::baz + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_in_non_crate_root() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo.rs +mod bar { + #[path = "qwe.rs"] + pub mod baz; +} +use self::bar::baz::Baz; + +//- /foo/bar/qwe.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + baz: t + + crate::foo::bar::baz + Baz: t v + "#]], + ); +} + +#[test] +fn module_resolution_decl_inside_inline_module_in_non_crate_root_2() { + check( + r#" +//- /main.rs +mod foo; + +//- /foo.rs +#[path = "bar"] +mod bar { + pub mod baz; +} +use self::bar::baz::Baz; + +//- /bar/baz.rs +pub struct Baz; +"#, + expect![[r#" + crate + foo: t + + crate::foo + Baz: t v + bar: t + + crate::foo::bar + baz: t + + crate::foo::bar::baz + Baz: t v + "#]], + ); +} + +#[test] +fn unresolved_module_diagnostics() { + let db = TestDB::with_files( + r" + //- /lib.rs + mod foo; + mod bar; + mod baz {} + //- /foo.rs + ", + ); + let krate = db.test_crate(); + + let crate_def_map = db.crate_def_map(krate); + + expect![[r#" + [ + UnresolvedModule { + module: Idx::(0), + declaration: InFile { + file_id: HirFileId( + FileId( + FileId( + 0, + ), + ), + ), + value: FileAstId::(1), + }, + candidate: "bar.rs", + }, + ] + "#]] + .assert_debug_eq(&crate_def_map.diagnostics); +} + +#[test] +fn module_resolution_decl_inside_module_in_non_crate_root_2() { + check( + r#" +//- /main.rs +#[path="module/m2.rs"] +mod module; + +//- /module/m2.rs +pub mod submod; + +//- /module/submod.rs +pub struct Baz; +"#, + expect![[r#" + crate + module: t + + crate::module + submod: t + + crate::module::submod + Baz: t v + "#]], + ); +} + +#[test] +fn nested_out_of_line_module() { + check( + r#" +//- /lib.rs +mod a { + mod b { + mod c; + } +} + +//- /a/b/c.rs +struct X; +"#, + expect![[r#" + crate + a: t + + crate::a + b: t + + crate::a::b + c: t + + crate::a::b::c + X: t v + "#]], + ); +} + +#[test] +fn nested_out_of_line_module_with_path() { + check( + r#" +//- /lib.rs +mod a { + #[path = "d/e"] + mod b { + mod c; + } +} + +//- /a/d/e/c.rs +struct X; +"#, + expect![[r#" + crate + a: t + + crate::a + b: t + + crate::a::b + c: t + + crate::a::b::c + X: t v + "#]], + ); +} diff --git a/crates/hir_def/src/nameres/tests/primitives.rs b/crates/hir_def/src/nameres/tests/primitives.rs new file mode 100644 index 000000000..215e8952d --- /dev/null +++ b/crates/hir_def/src/nameres/tests/primitives.rs @@ -0,0 +1,23 @@ +use super::*; + +#[test] +fn primitive_reexport() { + check( + r#" +//- /lib.rs +mod foo; +use foo::int; + +//- /foo.rs +pub use i32 as int; +"#, + expect![[r#" + crate + foo: t + int: t + + crate::foo + int: t + "#]], + ); +} -- cgit v1.2.3