Repository: rust-analyzer/rust-analyzer
Pull request #6679 (2020-11-30 15:12:07)

Extract tests module to file in vfs crate
Helps with #6522 

- [x] passed `cargo test`


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 7391542e49..f50e9edd66 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -300,7 +300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
 dependencies = [
  "cfg-if 1.0.0",
- "crossbeam-utils 0.8.1",
+ "crossbeam-utils 0.8.0",
 ]
 
 [[package]]
@@ -311,18 +311,18 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
 dependencies = [
  "cfg-if 1.0.0",
  "crossbeam-epoch",
- "crossbeam-utils 0.8.1",
+ "crossbeam-utils 0.8.0",
 ]
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.9.1"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
+checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f"
 dependencies = [
  "cfg-if 1.0.0",
  "const_fn",
- "crossbeam-utils 0.8.1",
+ "crossbeam-utils 0.8.0",
  "lazy_static",
  "memoffset",
  "scopeguard",
@@ -341,12 +341,13 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.1"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
+checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5"
 dependencies = [
  "autocfg",
  "cfg-if 1.0.0",
+ "const_fn",
  "lazy_static",
 ]
 
@@ -928,9 +929,9 @@ dependencies = [
 
 [[package]]
 name = "memoffset"
-version = "0.6.1"
+version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
+checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
 dependencies = [
  "autocfg",
 ]
@@ -987,9 +988,9 @@ dependencies = [
 
 [[package]]
 name = "miow"
-version = "0.2.2"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
+checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
 dependencies = [
  "kernel32-sys",
  "net2",
@@ -999,9 +1000,9 @@ dependencies = [
 
 [[package]]
 name = "net2"
-version = "0.2.36"
+version = "0.2.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02"
+checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"
 dependencies = [
  "cfg-if 0.1.10",
  "libc",
@@ -1297,7 +1298,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
 dependencies = [
  "crossbeam-channel 0.5.0",
  "crossbeam-deque",
- "crossbeam-utils 0.8.1",
+ "crossbeam-utils 0.8.0",
  "lazy_static",
  "num_cpus",
 ]
@@ -1395,9 +1396,9 @@ dependencies = [
 
 [[package]]
 name = "rustc-ap-rustc_lexer"
-version = "691.0.0"
+version = "688.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44bc89d9ca7a78fb82e103b389362c55f03800745f8ba14e068b805cfaf783ec"
+checksum = "ebbdcc99bd015349093fcbae4780fda21416fec5d8843acfb3d1733e130cd4db"
 dependencies = [
  "unicode-xid",
 ]
@@ -1435,7 +1436,7 @@ version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d8fadca2ab5de17acf66d744f4888049ca8f1bb9b8a1ab8afd9d032cc959c5dc"
 dependencies = [
- "crossbeam-utils 0.8.1",
+ "crossbeam-utils 0.8.0",
  "indexmap",
  "lock_api",
  "log",
@@ -1626,9 +1627,9 @@ version = "0.0.0"
 
 [[package]]
 name = "syn"
-version = "1.0.53"
+version = "1.0.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68"
+checksum = "3b4f34193997d92804d359ed09953e25d5138df6bcc055a71bf68ee89fdf9223"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1898,9 +1899,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.7.1"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+checksum = "db8716a166f290ff49dabc18b44aa407cb7c6dbe1aa0971b44b8a24b0ca35aae"
 
 [[package]]
 name = "unicode-xid"
diff --git a/crates/assists/src/handlers/toggle_ignore.rs b/crates/assists/src/handlers/ignore_test.rs
similarity index 81%
rename from crates/assists/src/handlers/toggle_ignore.rs
rename to crates/assists/src/handlers/ignore_test.rs
index 14b420421e..5096a00058 100644
--- a/crates/assists/src/handlers/toggle_ignore.rs
+++ b/crates/assists/src/handlers/ignore_test.rs
@@ -5,7 +5,7 @@ use syntax::{
 
 use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists};
 
-// Assist: toggle_ignore
+// Assist: ignore_test
 //
 // Adds `#[ignore]` attribute to the test.
 //
@@ -23,20 +23,20 @@ use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind,
 //     assert_eq!(2 + 2, 5);
 // }
 // ```
-pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+pub(crate) fn ignore_test(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let attr: ast::Attr = ctx.find_node_at_offset()?;
     let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
     let attr = test_related_attribute(&func)?;
 
     match has_ignore_attribute(&func) {
         None => acc.add(
-            AssistId("toggle_ignore", AssistKind::None),
+            AssistId("ignore_test", AssistKind::None),
             "Ignore this test",
             attr.syntax().text_range(),
             |builder| builder.insert(attr.syntax().text_range().end(), &format!("\n#[ignore]")),
         ),
         Some(ignore_attr) => acc.add(
-            AssistId("toggle_ignore", AssistKind::None),
+            AssistId("unignore_test", AssistKind::None),
             "Re-enable this test",
             ignore_attr.syntax().text_range(),
             |builder| {
@@ -55,19 +55,24 @@ pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext) -> Option<()
 }
 
 fn has_ignore_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
-    fn_def.attrs().find(|attr| attr.path().map(|it| it.syntax().text() == "ignore") == Some(true))
+    fn_def.attrs().find_map(|attr| {
+        if attr.path()?.syntax().text() == "ignore" {
+            Some(attr)
+        } else {
+            None
+        }
+    })
 }
 
 #[cfg(test)]
 mod tests {
+    use super::ignore_test;
     use crate::tests::check_assist;
 
-    use super::*;
-
     #[test]
     fn test_base_case() {
         check_assist(
-            toggle_ignore,
+            ignore_test,
             r#"
             #[test<|>]
             fn test() {}
@@ -83,7 +88,7 @@ mod tests {
     #[test]
     fn test_unignore() {
         check_assist(
-            toggle_ignore,
+            ignore_test,
             r#"
             #[test<|>]
             #[ignore]
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index dfe6c27295..17e9312db2 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -141,6 +141,7 @@ mod handlers {
     mod generate_function;
     mod generate_impl;
     mod generate_new;
+    mod ignore_test;
     mod infer_function_return_type;
     mod inline_local_variable;
     mod introduce_named_lifetime;
@@ -163,7 +164,6 @@ mod handlers {
     mod replace_string_with_char;
     mod replace_unwrap_with_match;
     mod split_import;
-    mod toggle_ignore;
     mod unwrap_block;
     mod wrap_return_type_in_result;
 
@@ -190,6 +190,7 @@ mod handlers {
             generate_function::generate_function,
             generate_impl::generate_impl,
             generate_new::generate_new,
+            ignore_test::ignore_test,
             infer_function_return_type::infer_function_return_type,
             inline_local_variable::inline_local_variable,
             introduce_named_lifetime::introduce_named_lifetime,
@@ -214,7 +215,6 @@ mod handlers {
             replace_qualified_name_with_use::replace_qualified_name_with_use,
             replace_unwrap_with_match::replace_unwrap_with_match,
             split_import::split_import,
-            toggle_ignore::toggle_ignore,
             unwrap_block::unwrap_block,
             wrap_return_type_in_result::wrap_return_type_in_result,
             // These are manually sorted for better priorities
diff --git a/crates/completion/src/completions/postfix.rs b/crates/completion/src/completions/postfix.rs
index 1785794cc5..7fbda7a6bd 100644
--- a/crates/completion/src/completions/postfix.rs
+++ b/crates/completion/src/completions/postfix.rs
@@ -5,7 +5,6 @@ mod format_like;
 use ide_db::ty_filter::TryEnum;
 use syntax::{
     ast::{self, AstNode, AstToken},
-    SyntaxKind::BLOCK_EXPR,
     TextRange, TextSize,
 };
 use text_edit::TextEdit;
@@ -221,30 +220,6 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
     )
     .add_to(acc);
 
-    let parent_node = dot_receiver.syntax().parent().and_then(|p| p.parent());
-    if let Some(parent) = parent_node {
-        if parent.kind() == BLOCK_EXPR {
-            postfix_snippet(
-                ctx,
-                cap,
-                &dot_receiver,
-                "let",
-                "let",
-                &format!("let $0 = {};", receiver_text),
-            )
-            .add_to(acc);
-            postfix_snippet(
-                ctx,
-                cap,
-                &dot_receiver,
-                "letm",
-                "let mut",
-                &format!("let mut $0 = {};", receiver_text),
-            )
-            .add_to(acc);
-        }
-    }
-
     if let ast::Expr::Literal(literal) = dot_receiver.clone() {
         if let Some(literal_text) = ast::String::cast(literal.token()) {
             add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
@@ -314,38 +289,6 @@ fn main() {
     let bar = true;
     bar.<|>
 }
-"#,
-            expect![[r#"
-                sn box   Box::new(expr)
-                sn call  function(expr)
-                sn dbg   dbg!(expr)
-                sn dbgr  dbg!(&expr)
-                sn if    if expr {}
-                sn let   let
-                sn letm  let mut
-                sn match match expr {}
-                sn not   !expr
-                sn ok    Ok(expr)
-                sn ref   &expr
-                sn refm  &mut expr
-                sn some  Some(expr)
-                sn while while expr {}
-            "#]],
-        );
-    }
-
-    #[test]
-    fn postfix_completion_works_for_function_calln() {
-        check(
-            r#"
-fn foo(elt: bool) -> bool {
-    !elt
-}
-
-fn main() {
-    let bar = true;
-    foo(bar.<|>)
-}
 "#,
             expect![[r#"
                 sn box   Box::new(expr)
@@ -378,8 +321,6 @@ fn main() {
                 sn call  function(expr)
                 sn dbg   dbg!(expr)
                 sn dbgr  dbg!(&expr)
-                sn let   let
-                sn letm  let mut
                 sn match match expr {}
                 sn ok    Ok(expr)
                 sn ref   &expr
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index 1ec2e9be72..aecc1378b1 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -44,8 +44,6 @@ pub use crate::{
 // - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
 // - `expr.ref` -> `&expr`
 // - `expr.refm` -> `&mut expr`
-// - `expr.let` -> `let <|> = expr;`
-// - `expr.letm` -> `let mut <|> = expr;`
 // - `expr.not` -> `!expr`
 // - `expr.dbg` -> `dbg!(expr)`
 // - `expr.dbgr` -> `dbg!(&expr)`
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index b9810457fc..d41dd3d928 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,6 +1,5 @@
 use hir::Semantics;
 use ide_db::{
-    base_db::FileId,
     defs::{NameClass, NameRefClass},
     symbol_index, RootDatabase,
 };
@@ -41,17 +40,10 @@ pub(crate) fn goto_definition(
                 vec![nav]
             },
             ast::SelfParam(self_param) => {
-                vec![self_to_nav_target(self_param, position.file_id)?]
-            },
-            ast::PathSegment(segment) => {
-                segment.self_token()?;
-                let path = segment.parent_path();
-                if path.qualifier().is_some() && !ast::PathExpr::can_cast(path.syntax().parent()?.kind()) {
-                    return None;
-                }
-                let func = segment.syntax().ancestors().find_map(ast::Fn::cast)?;
-                let self_param = func.param_list()?.self_param()?;
-                vec![self_to_nav_target(self_param, position.file_id)?]
+                let ty = sema.type_of_self(&self_param)?;
+                let adt_def = ty.autoderef(db).filter_map(|ty| ty.as_adt()).last()?;
+                let nav = adt_def.to_nav(db);
+                vec![nav]
             },
             _ => return None,
         }
@@ -71,20 +63,6 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
     }
 }
 
-fn self_to_nav_target(self_param: ast::SelfParam, file_id: FileId) -> Option<NavigationTarget> {
-    let self_token = self_param.self_token()?;
-    Some(NavigationTarget {
-        file_id,
-        full_range: self_param.syntax().text_range(),
-        focus_range: Some(self_token.text_range()),
-        name: self_token.text().clone(),
-        kind: self_token.kind(),
-        container_name: None,
-        description: None,
-        docs: None,
-    })
-}
-
 #[derive(Debug)]
 pub(crate) enum ReferenceResult {
     Exact(NavigationTarget),
@@ -1009,31 +987,31 @@ fn g() -> <() as Iterator<A = (), B<|> = u8>>::A {}
     }
 
     #[test]
-    fn goto_self_param_ty_specified() {
+    fn todo_def_type_for_self() {
         check(
             r#"
 struct Foo {}
+     //^^^
 
 impl Foo {
-    fn bar(self: &Foo) {
-         //^^^^
-        let foo = sel<|>f;
-    }
-}"#,
-        )
+    fn bar(&self<|>) {}
+}
+"#,
+        );
     }
 
     #[test]
-    fn goto_self_param_on_decl() {
+    fn todo_def_type_for_arbitrary_self() {
         check(
             r#"
+struct Arc<T>(T);
+     //^^^
 struct Foo {}
 
 impl Foo {
-    fn bar(&self<|>) {
-          //^^^^
-    }
-}"#,
-        )
+    fn bar(self<|>: Arc<Self>) {}
+}
+"#,
+        );
     }
 }
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 7395b81bd2..5693dd400e 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -21,7 +21,7 @@ use ide_db::{
 use syntax::{
     algo::find_node_at_offset,
     ast::{self, NameOwner},
-    match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
+    AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
 };
 
 use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
@@ -89,10 +89,6 @@ pub(crate) fn find_all_refs(
     let _p = profile::span("find_all_refs");
     let syntax = sema.parse(position.file_id).syntax().clone();
 
-    if let Some(res) = try_find_self_references(&syntax, position) {
-        return Some(res);
-    }
-
     let (opt_name, search_kind) = if let Some(name) =
         get_struct_def_name_for_struct_literal_search(&sema, &syntax, position)
     {
@@ -198,77 +194,6 @@ fn get_struct_def_name_for_struct_literal_search(
     None
 }
 
-fn try_find_self_references(
-    syntax: &SyntaxNode,
-    position: FilePosition,
-) -> Option<RangeInfo<ReferenceSearchResult>> {
-    let self_token =
-        syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)?;
-    let parent = self_token.parent();
-    match_ast! {
-        match parent {
-            ast::SelfParam(it) => (),
-            ast::PathSegment(segment) => {
-                segment.self_token()?;
-                let path = segment.parent_path();
-                if path.qualifier().is_some() && !ast::PathExpr::can_cast(path.syntax().parent()?.kind()) {
-                    return None;
-                }
-            },
-            _ => return None,
-        }
-    };
-    let function = parent.ancestors().find_map(ast::Fn::cast)?;
-    let self_param = function.param_list()?.self_param()?;
-    let param_self_token = self_param.self_token()?;
-
-    let declaration = Declaration {
-        nav: NavigationTarget {
-            file_id: position.file_id,
-            full_range: self_param.syntax().text_range(),
-            focus_range: Some(param_self_token.text_range()),
-            name: param_self_token.text().clone(),
-            kind: param_self_token.kind(),
-            container_name: None,
-            description: None,
-            docs: None,
-        },
-        kind: ReferenceKind::SelfKw,
-        access: Some(if self_param.mut_token().is_some() {
-            ReferenceAccess::Write
-        } else {
-            ReferenceAccess::Read
-        }),
-    };
-    let references = function
-        .body()
-        .map(|body| {
-            body.syntax()
-                .descendants()
-                .filter_map(ast::PathExpr::cast)
-                .filter_map(|expr| {
-                    let path = expr.path()?;
-                    if path.qualifier().is_none() {
-                        path.segment()?.self_token()
-                    } else {
-                        None
-                    }
-                })
-                .map(|token| Reference {
-                    file_range: FileRange { file_id: position.file_id, range: token.text_range() },
-                    kind: ReferenceKind::SelfKw,
-                    access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration
-                })
-                .collect()
-        })
-        .unwrap_or_default();
-
-    Some(RangeInfo::new(
-        param_self_token.text_range(),
-        ReferenceSearchResult { declaration, references },
-    ))
-}
-
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
@@ -837,32 +762,6 @@ fn f() -> m::En {
         );
     }
 
-    #[test]
-    fn test_find_self_refs() {
-        check(
-            r#"
-struct Foo { bar: i32 }
-
-impl Foo {
-    fn foo(self) {
-        let x = self<|>.bar;
-        if true {
-            let _ = match () {
-                () => self,
-            };
-        }
-    }
-}
-"#,
-            expect![[r#"
-                self SELF_KW FileId(0) 47..51 47..51 SelfKw Read
-
-                FileId(0) 71..75 SelfKw Read
-                FileId(0) 152..156 SelfKw Read
-            "#]],
-        );
-    }
-
     fn check(ra_fixture: &str, expect: Expect) {
         check_with_scope(ra_fixture, None, expect)
     }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 7314576967..91c64bd4a1 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -221,47 +221,24 @@ fn rename_to_self(
     let source_file = sema.parse(position.file_id);
     let syn = source_file.syntax();
 
-    let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
-        .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
+    let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
         .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
-    let param_range = fn_ast
-        .param_list()
-        .and_then(|p| p.params().next())
-        .ok_or_else(|| RenameError("Method has no parameters".to_string()))?
-        .syntax()
-        .text_range();
-    if !param_range.contains(position.offset) {
-        return Err(RenameError("Only the first parameter can be self".to_string()));
-    }
-
-    let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
-        .and_then(|def| sema.to_def(&def))
-        .ok_or_else(|| RenameError("No impl block found for function".to_string()))?;
-    if fn_def.self_param(sema.db).is_some() {
+    let params =
+        fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
+    if params.self_param().is_some() {
         return Err(RenameError("Method already has a self parameter".to_string()));
     }
-
-    let params = fn_def.params(sema.db);
     let first_param =
-        params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
-    let first_param_ty = first_param.ty();
-    let impl_ty = impl_block.target_ty(sema.db);
-    let (ty, self_param) = if impl_ty.remove_ref().is_some() {
-        // if the impl is a ref to the type we can just match the `&T` with self directly
-        (first_param_ty.clone(), "self")
-    } else {
-        first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| {
-            (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })
-        })
+        params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
+    let mutable = match first_param.ty() {
+        Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
+        _ => return Err(RenameError("Not renaming other types".to_string())),
     };
 
-    if ty != impl_ty {
-        return Err(RenameError("Parameter type differs from impl block type".to_string()));
-    }
-
     let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
         .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
 
+    let param_range = first_param.syntax().text_range();
     let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
         .into_iter()
         .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
@@ -277,7 +254,10 @@ fn rename_to_self(
 
     edits.push(SourceFileEdit {
         file_id: position.file_id,
-        edit: TextEdit::replace(param_range, String::from(self_param)),
+        edit: TextEdit::replace(
+            param_range,
+            String::from(if mutable { "&mut self" } else { "&self" }),
+        ),
     });
 
     Ok(RangeInfo::new(range, SourceChange::from(edits)))
@@ -300,11 +280,7 @@ fn text_edit_from_self_param(
 
     let mut replacement_text = String::from(new_name);
     replacement_text.push_str(": ");
-    match (self_param.amp_token(), self_param.mut_token()) {
-        (None, None) => (),
-        (Some(_), None) => replacement_text.push('&'),
-        (_, Some(_)) => replacement_text.push_str("&mut "),
-    };
+    replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut "));
     replacement_text.push_str(type_name.as_str());
 
     Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
@@ -1104,95 +1080,6 @@ impl Foo {
         self.i
     }
 }
-"#,
-        );
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-
-impl Foo {
-    fn f(foo<|>: Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-            r#"
-struct Foo { i: i32 }
-
-impl Foo {
-    fn f(self) -> i32 {
-        self.i
-    }
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn test_parameter_to_self_error_no_impl() {
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-
-fn f(foo<|>: &mut Foo) -> i32 {
-    foo.i
-}
-"#,
-            "error: No impl block found for function",
-        );
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-struct Bar;
-
-impl Bar {
-    fn f(foo<|>: &mut Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-            "error: Parameter type differs from impl block type",
-        );
-    }
-
-    #[test]
-    fn test_parameter_to_self_error_not_first() {
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-impl Foo {
-    fn f(x: (), foo<|>: &mut Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-            "error: Only the first parameter can be self",
-        );
-    }
-
-    #[test]
-    fn test_parameter_to_self_impl_ref() {
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-impl &Foo {
-    fn f(foo<|>: &Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-            r#"
-struct Foo { i: i32 }
-impl &Foo {
-    fn f(self) -> i32 {
-        self.i
-    }
-}
 "#,
         );
     }
@@ -1222,31 +1109,6 @@ impl Foo {
         );
     }
 
-    #[test]
-    fn test_owned_self_to_parameter() {
-        check(
-            "foo",
-            r#"
-struct Foo { i: i32 }
-
-impl Foo {
-    fn f(<|>self) -> i32 {
-        self.i
-    }
-}
-"#,
-            r#"
-struct Foo { i: i32 }
-
-impl Foo {
-    fn f(foo: Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-        );
-    }
-
     #[test]
     fn test_self_in_path_to_parameter() {
         check(
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 5150a970c1..1ed77b40b0 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -76,7 +76,6 @@ pub(crate) fn highlight(
     let mut current_macro_call: Option<ast::MacroCall> = None;
     let mut format_string_highlighter = FormatStringHighlighter::default();
     let mut macro_rules_highlighter = MacroRulesHighlighter::default();
-    let mut inside_attribute = false;
 
     // Walk all nodes, keeping track of whether we are inside a macro or not.
     // If in macro, expand it first and highlight the expanded code.
@@ -133,12 +132,9 @@ pub(crate) fn highlight(
             _ => (),
         }
 
+        // Check for Rust code in documentation
         match &event {
-            // Check for Rust code in documentation
             WalkEvent::Leave(NodeOrToken::Node(node)) => {
-                if ast::Attr::can_cast(node.kind()) {
-                    inside_attribute = false
-                }
                 if let Some((doctest, range_mapping, new_comments)) =
                     injection::extract_doc_comments(node)
                 {
@@ -150,9 +146,6 @@ pub(crate) fn highlight(
                     );
                 }
             }
-            WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
-                inside_attribute = true
-            }
             _ => (),
         }
 
@@ -195,16 +188,12 @@ pub(crate) fn highlight(
             }
         }
 
-        if let Some((mut highlight, binding_hash)) = highlight_element(
+        if let Some((highlight, binding_hash)) = highlight_element(
             &sema,
             &mut bindings_shadow_count,
             syntactic_name_ref_highlighting,
             element_to_highlight.clone(),
         ) {
-            if inside_attribute {
-                highlight = highlight | HighlightModifier::Attribute;
-            }
-
             if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() {
                 stack.add(HighlightedRange { range, highlight, binding_hash });
             }
@@ -485,9 +474,7 @@ fn highlight_element(
 
         // Highlight references like the definitions they resolve to
         NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
-            // even though we track whether we are in an attribute or not we still need this special case
-            // as otherwise we would emit unresolved references for name refs inside attributes
-            Highlight::from(HighlightTag::Function)
+            Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute
         }
         NAME_REF => {
             let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index 607185ca97..a3e765d059 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -31,7 +31,6 @@ pub enum ReferenceKind {
     FieldShorthandForLocal,
     StructLiteral,
     RecordFieldExprOrPat,
-    SelfKw,
     Other,
 }
 
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 59269a74b9..30299a4659 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -480,7 +480,7 @@ macro_rules! config_data {
 
 config_data! {
     struct ConfigData {
-        assist_importMergeBehaviour: MergeBehaviourDef = MergeBehaviourDef::Full,
+        assist_importMergeBehaviour: MergeBehaviourDef = MergeBehaviourDef::None,
         assist_importPrefix: ImportPrefixDef           = ImportPrefixDef::Plain,
 
         callInfo_full: bool = true,
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index c6a6f11e13..ce62babc3f 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
 [dependencies]
 itertools = "0.9.0"
 rowan = "0.10.0"
-rustc_lexer = { version = "691.0.0", package = "rustc-ap-rustc_lexer" }
+rustc_lexer = { version = "688.0.0", package = "rustc-ap-rustc_lexer" }
 rustc-hash = "1.1.0"
 arrayvec = "0.5.1"
 once_cell = "1.3.1"
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index 4aa2d6526b..9093fbd979 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -158,53 +158,4 @@ impl fst::Automaton for PrefixOf<'_> {
 }
 
 #[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn path_prefix() {
-        let mut file_set = FileSetConfig::builder();
-        file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo".into())]);
-        file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo/bar/baz".into())]);
-        let file_set = file_set.build();
-
-        let mut vfs = Vfs::default();
-        vfs.set_file_contents(
-            VfsPath::new_virtual_path("/foo/src/lib.rs".into()),
-            Some(Vec::new()),
-        );
-        vfs.set_file_contents(
-            VfsPath::new_virtual_path("/foo/src/bar/baz/lib.rs".into()),
-            Some(Vec::new()),
-        );
-        vfs.set_file_contents(
-            VfsPath::new_virtual_path("/foo/bar/baz/lib.rs".into()),
-            Some(Vec::new()),
-        );
-        vfs.set_file_contents(VfsPath::new_virtual_path("/quux/lib.rs".into()), Some(Vec::new()));
-
-        let partition = file_set.partition(&vfs).into_iter().map(|it| it.len()).collect::<Vec<_>>();
-        assert_eq!(partition, vec![2, 1, 1]);
-    }
-
-    #[test]
-    fn name_prefix() {
-        let mut file_set = FileSetConfig::builder();
-        file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo".into())]);
-        file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo-things".into())]);
-        let file_set = file_set.build();
-
-        let mut vfs = Vfs::default();
-        vfs.set_file_contents(
-            VfsPath::new_virtual_path("/foo/src/lib.rs".into()),
-            Some(Vec::new()),
-        );
-        vfs.set_file_contents(
-            VfsPath::new_virtual_path("/foo-things/src/lib.rs".into()),
-            Some(Vec::new()),
-        );
-
-        let partition = file_set.partition(&vfs).into_iter().map(|it| it.len()).collect::<Vec<_>>();
-        assert_eq!(partition, vec![1, 1, 0]);
-    }
-}
+mod tests;
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs
index 815697597e..bd14911c96 100644
--- a/crates/vfs/src/vfs_path.rs
+++ b/crates/vfs/src/vfs_path.rs
@@ -311,35 +311,4 @@ impl VirtualPath {
 }
 
 #[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn virtual_path_extensions() {
-        assert_eq!(VirtualPath("/".to_string()).name_and_extension(), None);
-        assert_eq!(
-            VirtualPath("/directory".to_string()).name_and_extension(),
-            Some(("directory", None))
-        );
-        assert_eq!(
-            VirtualPath("/directory/".to_string()).name_and_extension(),
-            Some(("directory", None))
-        );
-        assert_eq!(
-            VirtualPath("/directory/file".to_string()).name_and_extension(),
-            Some(("file", None))
-        );
-        assert_eq!(
-            VirtualPath("/directory/.file".to_string()).name_and_extension(),
-            Some((".file", None))
-        );
-        assert_eq!(
-            VirtualPath("/directory/.file.rs".to_string()).name_and_extension(),
-            Some((".file", Some("rs")))
-        );
-        assert_eq!(
-            VirtualPath("/directory/file.rs".to_string()).name_and_extension(),
-            Some(("file", Some("rs")))
-        );
-    }
-}
+mod tests;


Test patch:
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 8d50c8791a..5a9d1a01b7 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -473,6 +473,26 @@ impl<T: Clone> Ctx<T> {
     )
 }
 
+#[test]
+fn doctest_ignore_test() {
+    check_doc_test(
+        "ignore_test",
+        r#####"
+<|>#[test]
+fn arithmetics {
+    assert_eq!(2 + 2, 5);
+}
+"#####,
+        r#####"
+#[test]
+#[ignore]
+fn arithmetics {
+    assert_eq!(2 + 2, 5);
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_infer_function_return_type() {
     check_doc_test(
@@ -958,26 +978,6 @@ use std::{collections::HashMap};
     )
 }
 
-#[test]
-fn doctest_toggle_ignore() {
-    check_doc_test(
-        "toggle_ignore",
-        r#####"
-<|>#[test]
-fn arithmetics {
-    assert_eq!(2 + 2, 5);
-}
-"#####,
-        r#####"
-#[test]
-#[ignore]
-fn arithmetics {
-    assert_eq!(2 + 2, 5);
-}
-"#####,
-    )
-}
-
 #[test]
 fn doctest_unwrap_block() {
     check_doc_test(
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index d79fa6dca8..6be88f856b 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -50,7 +50,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="comment documentation">/// # Examples</span>
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// ```</span>
-    <span class="comment documentation">/// #</span><span class="generic injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="punctuation attribute injected">)</span><span class="attribute attribute injected">]</span>
+    <span class="comment documentation">/// #</span><span class="generic injected"> </span><span class="attribute injected">#</span><span class="attribute injected">!</span><span class="attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation injected">(</span><span class="attribute injected">unused_mut</span><span class="punctuation injected">)</span><span class="attribute injected">]</span>
     <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="keyword injected">mut</span><span class="generic injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
 </span>    <span class="comment documentation">/// ```</span>
     <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 1d05b77131..4b6d6adc96 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -54,7 +54,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 <span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">global_mut</span><span class="punctuation">:</span> <span class="struct">TypeForStaticMut</span> <span class="operator">=</span> <span class="struct">TypeForStaticMut</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span>
 
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">repr</span><span class="punctuation attribute">(</span><span class="attribute attribute">packed</span><span class="punctuation attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">repr</span><span class="punctuation">(</span><span class="attribute">packed</span><span class="punctuation">)</span><span class="attribute">]</span>
 <span class="keyword">struct</span> <span class="struct declaration">Packed</span> <span class="punctuation">{</span>
     <span class="field declaration">a</span><span class="punctuation">:</span> <span class="builtin_type">u16</span><span class="punctuation">,</span>
 <span class="punctuation">}</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 15fbf2ce39..6a10a9dcd0 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -40,18 +40,18 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 <span class="comment">// Needed for function consuming vs normal</span>
 <span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">marker</span> <span class="punctuation">{</span>
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"copy"</span><span class="attribute attribute">]</span>
+    <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"copy"</span><span class="attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 <span class="punctuation">}</span>
 
 <span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">ops</span> <span class="punctuation">{</span>
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn_once"</span><span class="attribute attribute">]</span>
+    <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_once"</span><span class="attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn_mut"</span><span class="attribute attribute">]</span>
+    <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_mut"</span><span class="attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnMut</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn"</span><span class="attribute attribute">]</span>
+    <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn"</span><span class="attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Fn</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnMut</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 <span class="punctuation">}</span>
 
@@ -85,7 +85,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="punctuation">}</span>
 <span class="punctuation">}</span>
 
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">derive</span><span class="punctuation attribute">(</span><span class="attribute attribute">Copy</span><span class="punctuation attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">derive</span><span class="punctuation">(</span><span class="attribute">Copy</span><span class="punctuation">)</span><span class="attribute">]</span>
 <span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="punctuation">{</span>
     <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">u32</span><span class="punctuation">,</span>
 <span class="punctuation">}</span>
diff --git a/crates/vfs/src/file_set/tests.rs b/crates/vfs/src/file_set/tests.rs
new file mode 100644
index 0000000000..2146df185d
--- /dev/null
+++ b/crates/vfs/src/file_set/tests.rs
@@ -0,0 +1,42 @@
+use super::*;
+
+#[test]
+fn path_prefix() {
+    let mut file_set = FileSetConfig::builder();
+    file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo".into())]);
+    file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo/bar/baz".into())]);
+    let file_set = file_set.build();
+
+    let mut vfs = Vfs::default();
+    vfs.set_file_contents(VfsPath::new_virtual_path("/foo/src/lib.rs".into()), Some(Vec::new()));
+    vfs.set_file_contents(
+        VfsPath::new_virtual_path("/foo/src/bar/baz/lib.rs".into()),
+        Some(Vec::new()),
+    );
+    vfs.set_file_contents(
+        VfsPath::new_virtual_path("/foo/bar/baz/lib.rs".into()),
+        Some(Vec::new()),
+    );
+    vfs.set_file_contents(VfsPath::new_virtual_path("/quux/lib.rs".into()), Some(Vec::new()));
+
+    let partition = file_set.partition(&vfs).into_iter().map(|it| it.len()).collect::<Vec<_>>();
+    assert_eq!(partition, vec![2, 1, 1]);
+}
+
+#[test]
+fn name_prefix() {
+    let mut file_set = FileSetConfig::builder();
+    file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo".into())]);
+    file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo-things".into())]);
+    let file_set = file_set.build();
+
+    let mut vfs = Vfs::default();
+    vfs.set_file_contents(VfsPath::new_virtual_path("/foo/src/lib.rs".into()), Some(Vec::new()));
+    vfs.set_file_contents(
+        VfsPath::new_virtual_path("/foo-things/src/lib.rs".into()),
+        Some(Vec::new()),
+    );
+
+    let partition = file_set.partition(&vfs).into_iter().map(|it| it.len()).collect::<Vec<_>>();
+    assert_eq!(partition, vec![1, 1, 0]);
+}
diff --git a/crates/vfs/src/vfs_path/tests.rs b/crates/vfs/src/vfs_path/tests.rs
new file mode 100644
index 0000000000..510e021e89
--- /dev/null
+++ b/crates/vfs/src/vfs_path/tests.rs
@@ -0,0 +1,30 @@
+use super::*;
+
+#[test]
+fn virtual_path_extensions() {
+    assert_eq!(VirtualPath("/".to_string()).name_and_extension(), None);
+    assert_eq!(
+        VirtualPath("/directory".to_string()).name_and_extension(),
+        Some(("directory", None))
+    );
+    assert_eq!(
+        VirtualPath("/directory/".to_string()).name_and_extension(),
+        Some(("directory", None))
+    );
+    assert_eq!(
+        VirtualPath("/directory/file".to_string()).name_and_extension(),
+        Some(("file", None))
+    );
+    assert_eq!(
+        VirtualPath("/directory/.file".to_string()).name_and_extension(),
+        Some((".file", None))
+    );
+    assert_eq!(
+        VirtualPath("/directory/.file.rs".to_string()).name_and_extension(),
+        Some((".file", Some("rs")))
+    );
+    assert_eq!(
+        VirtualPath("/directory/file.rs".to_string()).name_and_extension(),
+        Some(("file", Some("rs")))
+    );
+}