From dab7f3d2c6cd035f446fbdcda2442954da4afd3a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 8 Jul 2020 19:09:42 +0200 Subject: Remove relative_path dependency --- crates/ra_hir_def/src/nameres/mod_resolution.rs | 116 ++++++++++++++++++------ 1 file changed, 87 insertions(+), 29 deletions(-) (limited to 'crates/ra_hir_def') diff --git a/crates/ra_hir_def/src/nameres/mod_resolution.rs b/crates/ra_hir_def/src/nameres/mod_resolution.rs index 39e9a6d97..953961632 100644 --- a/crates/ra_hir_def/src/nameres/mod_resolution.rs +++ b/crates/ra_hir_def/src/nameres/mod_resolution.rs @@ -1,23 +1,24 @@ //! This module resolves `mod foo;` declaration to file. use hir_expand::name::Name; -use ra_db::{FileId, RelativePathBuf}; +use ra_db::FileId; use ra_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` - path: RelativePathBuf, + /// `` 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 { path: RelativePathBuf::default(), root_non_dir_owner: false } + ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false } } pub(super) fn descend_into_definition( @@ -25,17 +26,21 @@ impl ModDir { name: &Name, attr_path: Option<&SmolStr>, ) -> ModDir { - let mut path = self.path.clone(); - match attr_to_path(attr_path) { - None => path.push(&name.to_string()), + 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) => { - if self.root_non_dir_owner { - assert!(path.pop()); + let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner); + if !(path.is_empty() || path.ends_with('/')) { + path.push('/') } - path.push(attr_path); + DirPath::new(path) } - } - ModDir { path, root_non_dir_owner: false } + }; + ModDir { dir_path: path, root_non_dir_owner: false } } pub(super) fn resolve_declaration( @@ -48,34 +53,87 @@ impl ModDir { let file_id = file_id.original_file(db.upcast()); let mut candidate_files = Vec::new(); - match attr_to_path(attr_path) { + match attr_path { Some(attr_path) => { - let base = - if self.root_non_dir_owner { self.path.parent().unwrap() } else { &self.path }; - candidate_files.push(base.join(attr_path).to_string()) + candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner)) } None => { - candidate_files.push(self.path.join(&format!("{}.rs", name)).to_string()); - candidate_files.push(self.path.join(&format!("{}/mod.rs", name)).to_string()); + 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 mut root_non_dir_owner = false; - let mut mod_path = RelativePathBuf::new(); let is_mod_rs = candidate.ends_with("mod.rs"); - if !(is_mod_rs || attr_path.is_some()) { - root_non_dir_owner = true; - mod_path.push(&name.to_string()); - } - return Ok((file_id, is_mod_rs, ModDir { path: mod_path, root_non_dir_owner })); + + 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)) } } -fn attr_to_path(attr: Option<&SmolStr>) -> Option { - attr.and_then(|it| RelativePathBuf::from_path(&it.replace("\\", "/")).ok()) +#[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 + } } -- cgit v1.2.3