Repository: racer-rust/racer (language: rust)

Issue:
Completion not working with Rust 2018 `no more mod.rs`
Racer isn't able to complete modules that looks like `my_module.rs`, `my_module/sub_mod.rs` (https://doc.rust-lang.org/nightly/edition-guide/rust-2018/module-system/path-clarity.html#no-more-modrs).
Changing to `my_module/mod.rs`, `my_module/sub_mod.rs` makes it work again.


Discussion / hints:
Oops, I didn't know that :thinking: 
This seems to affect for example [futures_channel](https://docs.rs/futures-channel/0.3.1/futures_channel/) where Racer isn't able to find the "mpsc" and "oneshot" modules.

Fix patch:
diff --git a/src/racer/matchers.rs b/src/racer/matchers.rs
index b803e779..6868fba8 100644
--- a/src/racer/matchers.rs
+++ b/src/racer/matchers.rs
@@ -352,48 +352,74 @@ pub fn match_mod(
         });
     } else {
         debug!("found a module declaration: |{}|", blob);
-
+        // the name of the file where we found the module declaration (foo.rs)
+        // without its extension!
+        let filename = context.filepath.file_stem()?;
         let parent_path = context.filepath.parent()?;
-        let ranged_raw = session.load_raw_src_ranged(&msrc, context.filepath);
-        // get module from path attribute
-        if let Some(modpath) =
-            scopes::get_module_file_from_path(msrc, context.range.start, parent_path, ranged_raw)
-        {
-            let doc_src = session.load_raw_file(&modpath);
-            return Some(Match {
-                matchstr: s,
-                filepath: modpath.to_path_buf(),
-                point: BytePos::ZERO,
-                coords: Some(Coordinate::start()),
-                local: false,
-                mtype: Module,
-                contextstr: modpath.to_str().unwrap().to_owned(),
-                docs: find_mod_doc(&doc_src, BytePos::ZERO),
-            });
-        }
-        // get internal module nesting
-        // e.g. is this in an inline submodule?  mod foo{ mod bar; }
-        // because if it is then we need to search further down the
-        // directory hierarchy - e.g. <cwd>/foo/bar.rs
-        let internalpath = scopes::get_local_module_path(msrc, context.range.start);
-        let mut searchdir = parent_path.to_owned();
-        for s in internalpath {
-            searchdir.push(&s);
-        }
-        if let Some(modpath) = get_module_file(&s, &searchdir, session) {
-            let doc_src = session.load_raw_file(&modpath);
-            let context = modpath.to_str().unwrap().to_owned();
-            return Some(Match {
-                matchstr: s,
-                filepath: modpath,
-                point: BytePos::ZERO,
-                coords: Some(Coordinate::start()),
-                local: false,
-                mtype: Module,
-                contextstr: context,
-                docs: find_mod_doc(&doc_src, BytePos::ZERO),
-            });
-        }
+        // if we found the declaration in `src/foo.rs`, then let's look for the
+        // submodule in `src/foo/` as well!
+        let filename_subdir = parent_path.join(filename);
+        // if we are looking for "foo::bar", we have two cases:
+        //   1. we found `pub mod bar;` in either `src/foo/mod.rs`
+        // (or `src/lib.rs`). As such we are going to search for `bar.rs` in
+        // the same directory (`src/foo/`, or `src/` respectively).
+        //   2. we found `pub mod bar;` in `src/foo.rs`. This means that we also
+        // need to seach in `src/foo/` if it exists!
+        let search_path = if filename_subdir.exists() {
+            filename_subdir.as_path()
+        } else {
+            parent_path
+        };
+        match_mod_inner(msrc, context, session, search_path, s)
+    }
+}
+
+fn match_mod_inner(
+    msrc: Src<'_>,
+    context: &MatchCxt<'_, '_>,
+    session: &Session<'_>,
+    search_path: &Path,
+    s: String,
+) -> Option<Match> {
+    let ranged_raw = session.load_raw_src_ranged(&msrc, context.filepath);
+    // get module from path attribute
+    if let Some(modpath) =
+        scopes::get_module_file_from_path(msrc, context.range.start, search_path, ranged_raw)
+    {
+        let doc_src = session.load_raw_file(&modpath);
+        return Some(Match {
+            matchstr: s,
+            filepath: modpath.to_path_buf(),
+            point: BytePos::ZERO,
+            coords: Some(Coordinate::start()),
+            local: false,
+            mtype: Module,
+            contextstr: modpath.to_str().unwrap().to_owned(),
+            docs: find_mod_doc(&doc_src, BytePos::ZERO),
+        });
+    }
+    // get internal module nesting
+    // e.g. is this in an inline submodule?  mod foo{ mod bar; }
+    // because if it is then we need to search further down the
+    // directory hierarchy - e.g. <cwd>/foo/bar.rs
+    let internalpath = scopes::get_local_module_path(msrc, context.range.start);
+    let mut searchdir = (*search_path).to_owned();
+    for s in internalpath {
+        searchdir.push(&s);
+    }
+    if let Some(modpath) = get_module_file(&s, &searchdir, session) {
+        let doc_src = session.load_raw_file(&modpath);
+        let context = modpath.to_str().unwrap().to_owned();
+        return Some(Match {
+            matchstr: s,
+            filepath: modpath,
+            point: BytePos::ZERO,
+            coords: Some(Coordinate::start()),
+            local: false,
+            mtype: Module,
+            contextstr: context,
+            docs: find_mod_doc(&doc_src, BytePos::ZERO),
+        });
     }
     None
 }


Test patch:
diff --git a/test_project/src/lib.rs b/test_project/src/lib.rs
index 578b9a43..ff1d78c7 100644
--- a/test_project/src/lib.rs
+++ b/test_project/src/lib.rs
@@ -17,3 +17,6 @@ impl TestStruct {
 }
 
 pub(crate) fn name() {}
+
+pub mod submod;
+pub mod submod2018;
diff --git a/test_project/src/submod/foo.rs b/test_project/src/submod/foo.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/test_project/src/submod/mod.rs b/test_project/src/submod/mod.rs
new file mode 100644
index 00000000..b52703b2
--- /dev/null
+++ b/test_project/src/submod/mod.rs
@@ -0,0 +1,1 @@
+pub mod foo;
diff --git a/test_project/src/submod2018.rs b/test_project/src/submod2018.rs
new file mode 100644
index 00000000..70d1668a
--- /dev/null
+++ b/test_project/src/submod2018.rs
@@ -0,0 +1,1 @@
+pub mod foo2018;
diff --git a/test_project/src/submod2018/foo2018.rs b/test_project/src/submod2018/foo2018.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/module_structure.rs b/tests/module_structure.rs
new file mode 100644
index 00000000..5c0600f1
--- /dev/null
+++ b/tests/module_structure.rs
@@ -0,0 +1,57 @@
+use racer_testutils::*;
+
+#[test]
+fn finds_old_type_of_submodule() {
+    let src = "
+    use submod::f~oo;
+    ";
+
+    with_test_project(|dir| {
+        let srcdir = dir.nested_dir("src");
+        let got = get_definition(src, Some(srcdir));
+        assert_eq!(got.matchstr, "foo");
+        assert!(got.filepath.ends_with("src/submod/foo.rs"));
+    })
+}
+
+#[test]
+fn finds_old_type_of_module() {
+    let src = "
+    use s~ubmod::foo;
+    ";
+
+    with_test_project(|dir| {
+        let srcdir = dir.nested_dir("src");
+        let got = get_definition(src, Some(srcdir));
+        assert_eq!(got.matchstr, "submod");
+        assert!(got.filepath.ends_with("src/submod/mod.rs"));
+    })
+}
+
+#[test]
+fn finds_new_type_of_module() {
+    let src = "
+    use s~ubmod2018::foo2018;
+    ";
+
+    with_test_project(|dir| {
+        let srcdir = dir.nested_dir("src");
+        let got = get_definition(src, Some(srcdir));
+        assert_eq!(got.matchstr, "submod2018");
+        assert!(got.filepath.ends_with("src/submod2018.rs"));
+    })
+}
+
+#[test]
+fn finds_new_type_of_submodule() {
+    let src = "
+    use submod2018::f~oo2018;
+    ";
+
+    with_test_project(|dir| {
+        let srcdir = dir.nested_dir("src");
+        let got = get_definition(src, Some(srcdir));
+        assert_eq!(got.matchstr, "foo2018");
+        assert!(got.filepath.ends_with("src/submod2018/foo2018.rs"));
+    })
+}