Repository: rust-analyzer/rust-analyzer
Pull request #5002 (2020-06-23 02:32:05)

Fix underflow panic when doctests are at top of file
While debugging a comment at the top of a test string, I discovered that the offset calculations could underflow and panic. This only seemed to occur in tests, I assume because it's running a debug mode. The wrapping is quickly fixed later on in release mode, which is why this seems to have gone unnoticed. The new checks ensure the value is always positive or zero.


Patch:
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs
index 32267f2e6e..190c509130 100644
--- a/crates/paths/src/lib.rs
+++ b/crates/paths/src/lib.rs
@@ -95,80 +95,6 @@ impl AbsPath {
     pub fn normalize(&self) -> AbsPathBuf {
         AbsPathBuf(normalize_path(&self.0))
     }
-    pub fn to_path_buf(&self) -> AbsPathBuf {
-        AbsPathBuf::try_from(self.0.to_path_buf()).unwrap()
-    }
-    pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> {
-        self.0.strip_prefix(base).ok().map(RelPath::new_unchecked)
-    }
-}
-
-#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
-pub struct RelPathBuf(PathBuf);
-
-impl From<RelPathBuf> for PathBuf {
-    fn from(RelPathBuf(path_buf): RelPathBuf) -> PathBuf {
-        path_buf
-    }
-}
-
-impl ops::Deref for RelPathBuf {
-    type Target = RelPath;
-    fn deref(&self) -> &RelPath {
-        self.as_path()
-    }
-}
-
-impl AsRef<Path> for RelPathBuf {
-    fn as_ref(&self) -> &Path {
-        self.0.as_path()
-    }
-}
-
-impl TryFrom<PathBuf> for RelPathBuf {
-    type Error = PathBuf;
-    fn try_from(path_buf: PathBuf) -> Result<RelPathBuf, PathBuf> {
-        if !path_buf.is_relative() {
-            return Err(path_buf);
-        }
-        Ok(RelPathBuf(path_buf))
-    }
-}
-
-impl TryFrom<&str> for RelPathBuf {
-    type Error = PathBuf;
-    fn try_from(path: &str) -> Result<RelPathBuf, PathBuf> {
-        RelPathBuf::try_from(PathBuf::from(path))
-    }
-}
-
-impl RelPathBuf {
-    pub fn as_path(&self) -> &RelPath {
-        RelPath::new_unchecked(self.0.as_path())
-    }
-}
-
-#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
-#[repr(transparent)]
-pub struct RelPath(Path);
-
-impl ops::Deref for RelPath {
-    type Target = Path;
-    fn deref(&self) -> &Path {
-        &self.0
-    }
-}
-
-impl AsRef<Path> for RelPath {
-    fn as_ref(&self) -> &Path {
-        &self.0
-    }
-}
-
-impl RelPath {
-    pub fn new_unchecked(path: &Path) -> &RelPath {
-        unsafe { &*(path as *const Path as *const RelPath) }
-    }
 }
 
 // https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
index b0e56e1b5a..776bddf918 100644
--- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
+++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
@@ -128,7 +128,7 @@ impl From<foo::bar::baz::Boo> for A {
 
     fn check_not_applicable(ra_fixture: &str) {
         let fixture =
-            format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
+            format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
         check_assist_not_applicable(add_from_impl_for_enum, &fixture)
     }
 
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
index 43b4584b45..44db7917a6 100644
--- a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -301,7 +301,7 @@ fn another_fn() {
 
     fn check_not_applicable(ra_fixture: &str) {
         let fixture =
-            format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
+            format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
         check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
     }
 
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 3c12c1daa4..569efb7687 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -765,7 +765,7 @@ fn foo(opt: Option<i32>) {
     }
 }"#;
         let before =
-            &format!("//- /main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE);
+            &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE);
 
         check_assist(
             fill_match_arms,
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index af8fe11ec6..482a2f3e65 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -164,7 +164,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
 
     let mut source_root = SourceRoot::new_local();
     let mut source_root_id = WORKSPACE;
-    let mut source_root_prefix = "/".to_string();
+    let mut source_root_prefix: RelativePathBuf = "/".into();
     let mut file_id = FileId(0);
 
     let mut file_position = None;
@@ -212,9 +212,9 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
         };
 
         db.set_file_text(file_id, Arc::new(text));
-        db.set_file_relative_path(file_id, meta.path.clone().into());
+        db.set_file_relative_path(file_id, meta.path.clone());
         db.set_file_source_root(file_id, source_root_id);
-        source_root.insert_file(meta.path.into(), file_id);
+        source_root.insert_file(meta.path, file_id);
 
         file_id.0 += 1;
     }
@@ -245,12 +245,12 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
 }
 
 enum ParsedMeta {
-    Root { path: String },
+    Root { path: RelativePathBuf },
     File(FileMeta),
 }
 
 struct FileMeta {
-    path: String,
+    path: RelativePathBuf,
     krate: Option<String>,
     deps: Vec<String>,
     cfg: CfgOptions,
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs
index 1d6c471032..757d1e397f 100644
--- a/crates/ra_hir/src/source_analyzer.rs
+++ b/crates/ra_hir/src/source_analyzer.rs
@@ -216,43 +216,13 @@ impl SourceAnalyzer {
             if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) {
                 return Some(PathResolution::AssocItem(assoc.into()));
             }
-            if let Some(VariantId::EnumVariantId(variant)) =
-                self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
-            {
-                return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
-            }
         }
-
         if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) {
             let pat_id = self.pat_id(&path_pat.into())?;
             if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) {
                 return Some(PathResolution::AssocItem(assoc.into()));
             }
-            if let Some(VariantId::EnumVariantId(variant)) =
-                self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
-            {
-                return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
-            }
-        }
-
-        if let Some(rec_lit) = path.syntax().parent().and_then(ast::RecordLit::cast) {
-            let expr_id = self.expr_id(db, &rec_lit.into())?;
-            if let Some(VariantId::EnumVariantId(variant)) =
-                self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
-            {
-                return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
-            }
         }
-
-        if let Some(rec_pat) = path.syntax().parent().and_then(ast::RecordPat::cast) {
-            let pat_id = self.pat_id(&rec_pat.into())?;
-            if let Some(VariantId::EnumVariantId(variant)) =
-                self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
-            {
-                return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into())));
-            }
-        }
-
         // This must be a normal source file rather than macro file.
         let hir_path =
             crate::Path::from_src(path.clone(), &Hygiene::new(db.upcast(), self.file_id))?;
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 9bde1db8ec..a88a978d7c 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -167,9 +167,6 @@ fn missing_struct_field_fix(
     };
 
     let new_field_type = sema.type_of_expr(&record_expr.expr()?)?;
-    if new_field_type.is_unknown() {
-        return None;
-    }
     let new_field = make::record_field_def(
         record_expr.field_name()?,
         make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index 450ce0ba75..0798d2c361 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -908,84 +908,4 @@ mod tests {
             "x: i32|x",
         );
     }
-
-    #[test]
-    fn goto_def_for_enum_variant_self_pattern_const() {
-        check_goto(
-            "
-            //- /lib.rs
-            enum Foo {
-                Bar,
-            }
-            impl Foo {
-                fn baz(self) {
-                    match self {
-                        Self::Bar<|> => {}
-                    }
-                }
-            }
-            ",
-            "Bar ENUM_VARIANT FileId(1) 15..18 15..18",
-            "Bar|Bar",
-        );
-    }
-
-    #[test]
-    fn goto_def_for_enum_variant_self_pattern_record() {
-        check_goto(
-            "
-            //- /lib.rs
-            enum Foo {
-                Bar { val: i32 },
-            }
-            impl Foo {
-                fn baz(self) -> i32 {
-                    match self {
-                        Self::Bar<|> { val } => {}
-                    }
-                }
-            }
-            ",
-            "Bar ENUM_VARIANT FileId(1) 15..31 15..18",
-            "Bar { val: i32 }|Bar",
-        );
-    }
-
-    #[test]
-    fn goto_def_for_enum_variant_self_expr_const() {
-        check_goto(
-            "
-            //- /lib.rs
-            enum Foo {
-                Bar,
-            }
-            impl Foo {
-                fn baz(self) {
-                    Self::Bar<|>;
-                }
-            }
-            ",
-            "Bar ENUM_VARIANT FileId(1) 15..18 15..18",
-            "Bar|Bar",
-        );
-    }
-
-    #[test]
-    fn goto_def_for_enum_variant_self_expr_record() {
-        check_goto(
-            "
-            //- /lib.rs
-            enum Foo {
-                Bar { val: i32 },
-            }
-            impl Foo {
-                fn baz(self) {
-                    Self::Bar<|> {val: 4};
-                }
-            }
-            ",
-            "Bar ENUM_VARIANT FileId(1) 15..31 15..18",
-            "Bar { val: i32 }|Bar",
-        );
-    }
 }
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index 76910d09bd..ad78d2d93f 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -4,7 +4,7 @@ use std::str::FromStr;
 use std::sync::Arc;
 
 use ra_cfg::CfgOptions;
-use ra_db::{CrateName, Env};
+use ra_db::{CrateName, Env, RelativePathBuf};
 use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
 
 use crate::{
@@ -28,7 +28,7 @@ impl MockFileData {
     fn path(&self) -> &str {
         match self {
             MockFileData::Plain { path, .. } => path.as_str(),
-            MockFileData::Fixture(f) => f.meta.path(),
+            MockFileData::Fixture(f) => f.meta.path().as_str(),
         }
     }
 
@@ -167,6 +167,7 @@ impl MockAnalysis {
         for (i, data) in self.files.into_iter().enumerate() {
             let path = data.path();
             assert!(path.starts_with('/'));
+            let path = RelativePathBuf::from_path(&path[1..]).unwrap();
             let cfg_options = data.cfg_options();
             let file_id = FileId(i as u32 + 1);
             let edition = data.edition();
@@ -182,8 +183,7 @@ impl MockAnalysis {
                     Default::default(),
                 ));
             } else if path.ends_with("/lib.rs") {
-                let base = &path[..path.len() - "/lib.rs".len()];
-                let crate_name = &base[base.rfind('/').unwrap() + '/'.len_utf8()..];
+                let crate_name = path.parent().unwrap().file_name().unwrap();
                 let other_crate = crate_graph.add_crate_root(
                     file_id,
                     edition,
@@ -199,7 +199,7 @@ impl MockAnalysis {
                         .unwrap();
                 }
             }
-            change.add_file(source_root, file_id, path.into(), Arc::new(data.content().to_owned()));
+            change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned()));
         }
         change.set_crate_graph(crate_graph);
         host.apply_change(change);
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs
index 415f24a6d3..9d82b40095 100644
--- a/crates/ra_ide/src/syntax_highlighting/injection.rs
+++ b/crates/ra_ide/src/syntax_highlighting/injection.rs
@@ -155,17 +155,21 @@ pub(super) fn highlight_doc_comment(
         let mut start_offset = None;
         let mut end_offset = None;
         for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() {
+            // It's possible for orig_line_start - line_start to be negative. Add h.range.start()
+            // here and remove it from the end range after the loop below so that the values are
+            // always non-negative.
+            let offset = h.range.start() + orig_line_start - line_start;
             if line_start <= &h.range.start() {
-                start_offset.get_or_insert(orig_line_start - line_start);
+                start_offset.get_or_insert(offset);
                 break;
             } else {
-                end_offset.get_or_insert(orig_line_start - line_start);
+                end_offset.get_or_insert(offset);
             }
         }
         if let Some(start_offset) = start_offset {
             h.range = TextRange::new(
-                h.range.start() + start_offset,
-                h.range.end() + end_offset.unwrap_or(start_offset),
+                start_offset,
+                h.range.end() + end_offset.unwrap_or(start_offset) - h.range.start(),
             );
 
             stack.add(h);
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index 9debdc57bf..0e7dd29c26 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -114,16 +114,15 @@ async function downloadFile(
 
     log.debug("Downloading file of", totalBytes, "bytes size from", url, "to", destFilePath);
 
+    let readBytes = 0;
+    res.body.on("data", (chunk: Buffer) => {
+        readBytes += chunk.length;
+        onProgress(readBytes, totalBytes);
+    });
+
     // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode
     await withTempFile(async tempFilePath => {
         const destFileStream = fs.createWriteStream(tempFilePath, { mode });
-
-        let readBytes = 0;
-        res.body.on("data", (chunk: Buffer) => {
-            readBytes += chunk.length;
-            onProgress(readBytes, totalBytes);
-        });
-
         await pipeline(res.body, destFileStream);
         await new Promise<void>(resolve => {
             destFileStream.on("close", resolve);
diff --git a/xtask/src/release.rs b/xtask/src/release.rs
index b6502b952a..36c912184f 100644
--- a/xtask/src/release.rs
+++ b/xtask/src/release.rs
@@ -28,23 +28,21 @@ impl ReleaseCmd {
 
         let contents = format!(
             "\
-= Changelog #{}
-:sectanchors:
-:page-layout: post
+    = Changelog #{}
+    :sectanchors:
+    :page-layout: post
 
-Commit: commit:{}[] +
-Release: release:{}[]
+    Commit: commit:{}[] +
+    Release: release:{}[]
 
-== Sponsors
+    == New Features
 
-== New Features
+    * pr:[] .
 
-* pr:[] .
+    == Fixes
 
-== Fixes
-
-== Internal Improvements
-",
+    == Internal Improvements
+    ",
             changelog_n, commit, today
         );
 


Test patch:
diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs
index a98efb1ccd..804297315c 100644
--- a/crates/ra_hir_ty/src/tests/method_resolution.rs
+++ b/crates/ra_hir_ty/src/tests/method_resolution.rs
@@ -933,7 +933,7 @@ fn method_resolution_overloaded_method() {
     test_utils::mark::check!(impl_self_type_match_without_receiver);
     let t = type_at(
         r#"
-//- /main.rs
+//- main.rs
 struct Wrapper<T>(T);
 struct Foo<T>(T);
 struct Bar<T>(T);
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html
index 63199cdbe2..ac546806eb 100644
--- a/crates/ra_ide/src/snapshots/highlight_doctest.html
+++ b/crates/ra_ide/src/snapshots/highlight_doctest.html
@@ -32,7 +32,10 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .keyword.unsafe     { color: #BC8383; font-weight: bold; }
 .control            { font-style: italic; }
 </style>
-<pre><code><span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
+<pre><code><span class="comment documentation">/// ```</span>
+<span class="comment documentation">/// </span><span class="keyword">let</span> _ = <span class="string_literal">"early doctests should not go boom"</span>;
+<span class="comment documentation">/// ```</span>
+<span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
     <span class="field declaration">bar</span>: <span class="builtin_type">bool</span>,
 }
 
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 93a276ffe1..b1f48f03bc 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -291,6 +291,9 @@ fn main() {
 fn test_highlight_doctest() {
     check_highlighting(
         r#"
+/// ```
+/// let _ = "early doctests should not go boom";
+/// ```
 struct Foo {
     bar: bool,
 }
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 48ce831af7..0e2a83c6a0 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -29,12 +29,12 @@ fn completes_items_from_standard_library() {
     let project_start = Instant::now();
     let server = Project::with_fixture(
         r#"
-//- /Cargo.toml
+//- Cargo.toml
 [package]
 name = "foo"
 version = "0.0.0"
 
-//- /src/lib.rs
+//- src/lib.rs
 use std::collections::Spam;
 "#,
     )
@@ -63,24 +63,24 @@ fn test_runnables_project() {
     }
 
     let code = r#"
-//- /foo/Cargo.toml
+//- foo/Cargo.toml
 [package]
 name = "foo"
 version = "0.0.0"
 
-//- /foo/src/lib.rs
+//- foo/src/lib.rs
 pub fn foo() {}
 
-//- /foo/tests/spam.rs
+//- foo/tests/spam.rs
 #[test]
 fn test_eggs() {}
 
-//- /bar/Cargo.toml
+//- bar/Cargo.toml
 [package]
 name = "bar"
 version = "0.0.0"
 
-//- /bar/src/main.rs
+//- bar/src/main.rs
 fn main() {}
 "#;
 
@@ -140,12 +140,12 @@ fn test_format_document() {
 
     let server = project(
         r#"
-//- /Cargo.toml
+//- Cargo.toml
 [package]
 name = "foo"
 version = "0.0.0"
 
-//- /src/lib.rs
+//- src/lib.rs
 mod bar;
 
 fn main() {
@@ -200,13 +200,13 @@ fn test_format_document_2018() {
 
     let server = project(
         r#"
-//- /Cargo.toml
+//- Cargo.toml
 [package]
 name = "foo"
 version = "0.0.0"
 edition = "2018"
 
-//- /src/lib.rs
+//- src/lib.rs
 mod bar;
 
 async fn test() {
@@ -266,12 +266,12 @@ fn test_missing_module_code_action() {
 
     let server = project(
         r#"
-//- /Cargo.toml
+//- Cargo.toml
 [package]
 name = "foo"
 version = "0.0.0"
 
-//- /src/lib.rs
+//- src/lib.rs
 mod bar;
 
 fn main() {}
@@ -335,10 +335,10 @@ fn test_missing_module_code_action_in_json_project() {
 
     let code = format!(
         r#"
-//- /rust-project.json
+//- rust-project.json
 {PROJECT}
 
-//- /src/lib.rs
+//- src/lib.rs
 mod bar;
 
 fn main() {{}}
@@ -391,15 +391,15 @@ fn diagnostics_dont_block_typing() {
     }
 
     let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
-    let libs: String = (0..10).map(|i| format!("//- /src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
+    let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
     let server = Project::with_fixture(&format!(
         r#"
-//- /Cargo.toml
+//- Cargo.toml
 [package]
 name = "foo"
 version = "0.0.0"
 
-//- /src/lib.rs
+//- src/lib.rs
 {}
 
 {}
@@ -449,12 +449,12 @@ fn preserves_dos_line_endings() {
 
     let server = Project::with_fixture(
         &"
-//- /Cargo.toml
+//- Cargo.toml
 [package]
 name = \"foo\"
 version = \"0.0.0\"
 
-//- /src/main.rs
+//- src/main.rs
 /// Some Docs\r\nfn main() {}
 ",
     )
@@ -484,12 +484,12 @@ fn out_dirs_check() {
 
     let server = Project::with_fixture(
         r###"
-//- /Cargo.toml
+//- Cargo.toml
 [package]
 name = "foo"
 version = "0.0.0"
 
-//- /build.rs
+//- build.rs
 use std::{env, fs, path::Path};
 
 fn main() {
@@ -504,7 +504,7 @@ fn main() {
     println!("cargo:rustc-cfg=featlike=\"set\"");
     println!("cargo:rerun-if-changed=build.rs");
 }
-//- /src/main.rs
+//- src/main.rs
 #[rustc_builtin_macro] macro_rules! include {}
 #[rustc_builtin_macro] macro_rules! concat {}
 #[rustc_builtin_macro] macro_rules! env {}
@@ -599,7 +599,7 @@ fn resolve_proc_macro() {
     }
     let server = Project::with_fixture(
         r###"
-//- /foo/Cargo.toml
+//- foo/Cargo.toml
 [package]
 name = "foo"
 version = "0.0.0"
@@ -607,7 +607,7 @@ edition = "2018"
 [dependencies]
 bar = {path = "../bar"}
 
-//- /foo/src/main.rs
+//- foo/src/main.rs
 use bar::Bar;
 trait Bar {
   fn bar();
@@ -618,7 +618,7 @@ fn main() {
   Foo::bar();
 }
 
-//- /bar/Cargo.toml
+//- bar/Cargo.toml
 [package]
 name = "bar"
 version = "0.0.0"
@@ -627,7 +627,7 @@ edition = "2018"
 [lib]
 proc-macro = true
 
-//- /bar/src/lib.rs
+//- bar/src/lib.rs
 extern crate proc_macro;
 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
 macro_rules! t {
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index f58790ded7..30d03b622b 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -69,7 +69,7 @@ impl<'a> Project<'a> {
         let mut paths = vec![];
 
         for entry in parse_fixture(self.fixture) {
-            let path = tmp_dir.path().join(&entry.meta.path()['/'.len_utf8()..]);
+            let path = tmp_dir.path().join(entry.meta.path().as_str());
             fs::create_dir_all(path.parent().unwrap()).unwrap();
             fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
             paths.push((path, entry.text));
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index b1333cf157..981565cd74 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -168,13 +168,13 @@ pub struct FixtureEntry {
 
 #[derive(Debug, Eq, PartialEq)]
 pub enum FixtureMeta {
-    Root { path: String },
+    Root { path: RelativePathBuf },
     File(FileMeta),
 }
 
 #[derive(Debug, Eq, PartialEq)]
 pub struct FileMeta {
-    pub path: String,
+    pub path: RelativePathBuf,
     pub crate_name: Option<String>,
     pub deps: Vec<String>,
     pub cfg: CfgOptions,
@@ -183,7 +183,7 @@ pub struct FileMeta {
 }
 
 impl FixtureMeta {
-    pub fn path(&self) -> &str {
+    pub fn path(&self) -> &RelativePath {
         match self {
             FixtureMeta::Root { path } => &path,
             FixtureMeta::File(f) => &f.path,
@@ -292,12 +292,12 @@ fn parse_meta(meta: &str) -> FixtureMeta {
     let components = meta.split_ascii_whitespace().collect::<Vec<_>>();
 
     if components[0] == "root" {
-        let path = components[1].to_string();
+        let path: RelativePathBuf = components[1].into();
         assert!(path.starts_with("/") && path.ends_with("/"));
         return FixtureMeta::Root { path };
     }
 
-    let path = components[0].to_string();
+    let path: RelativePathBuf = components[0].into();
     assert!(path.starts_with("/"));
 
     let mut krate = None;