use std::sync::Arc; use relative_path::RelativePathBuf; use rustc_hash::{FxHashMap, FxHashSet}; use ra_syntax::{ SmolStr, ast::{self, NameOwner}, }; use crate::{ FileId, Cancelable, FileResolverImp, db, }; use super::{ ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase }; pub(super) fn submodules(db: &impl ModulesDatabase, file_id: FileId) -> Cancelable>> { db::check_canceled(db)?; let file = db.file_syntax(file_id); let root = file.ast(); let submodules = modules(root).map(|(name, _)| name).collect(); Ok(Arc::new(submodules)) } pub(super) fn modules(root: ast::Root<'_>) -> impl Iterator)> { root.modules().filter_map(|module| { let name = module.name()?.text(); if !module.has_semi() { return None; } Some((name, module)) }) } pub(super) fn module_tree(db: &impl ModulesDatabase) -> Cancelable> { db::check_canceled(db)?; let res = create_module_tree(db)?; Ok(Arc::new(res)) } #[derive(Clone, Hash, PartialEq, Eq, Debug)] pub struct Submodule { pub name: SmolStr, } fn create_module_tree<'a>( db: &impl ModulesDatabase, ) -> Cancelable { let mut tree = ModuleTree { mods: Vec::new(), links: Vec::new(), }; let mut roots = FxHashMap::default(); let mut visited = FxHashSet::default(); for &file_id in db.file_set().files.iter() { if visited.contains(&file_id) { continue; // TODO: use explicit crate_roots here } assert!(!roots.contains_key(&file_id)); let module_id = build_subtree(db, &mut tree, &mut visited, &mut roots, None, file_id)?; roots.insert(file_id, module_id); } Ok(tree) } fn build_subtree( db: &impl ModulesDatabase, tree: &mut ModuleTree, visited: &mut FxHashSet, roots: &mut FxHashMap, parent: Option, file_id: FileId, ) -> Cancelable { visited.insert(file_id); let id = tree.push_mod(ModuleData { file_id, parent, children: Vec::new(), }); let file_set = db.file_set(); let file_resolver = &file_set.resolver; for name in db.submodules(file_id)?.iter() { let (points_to, problem) = resolve_submodule(file_id, name, file_resolver); let link = tree.push_link(LinkData { name: name.clone(), owner: id, points_to: Vec::new(), problem: None, }); let points_to = points_to .into_iter() .map(|file_id| match roots.remove(&file_id) { Some(module_id) => { tree.module_mut(module_id).parent = Some(link); Ok(module_id) } None => build_subtree(db, tree, visited, roots, Some(link), file_id), }) .collect::>>()?; tree.link_mut(link).points_to = points_to; tree.link_mut(link).problem = problem; } Ok(id) } fn resolve_submodule( file_id: FileId, name: &SmolStr, file_resolver: &FileResolverImp, ) -> (Vec, Option) { let mod_name = file_resolver.file_stem(file_id); let is_dir_owner = mod_name == "mod" || mod_name == "lib" || mod_name == "main"; let file_mod = RelativePathBuf::from(format!("../{}.rs", name)); let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name)); let points_to: Vec; let problem: Option; if is_dir_owner { points_to = [&file_mod, &dir_mod] .iter() .filter_map(|path| file_resolver.resolve(file_id, path)) .collect(); problem = if points_to.is_empty() { Some(Problem::UnresolvedModule { candidate: file_mod, }) } else { None } } else { points_to = Vec::new(); problem = Some(Problem::NotDirOwner { move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)), candidate: file_mod, }); } (points_to, problem) }