Repository: rust-analyzer/rust-analyzer
Pull request #6514 (2020-11-09 19:09:54)

Fix extract_struct_from_enum_variant not updating record references
Related to #6510


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 4940110684..1a4a63550d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -139,9 +139,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.62"
+version = "1.0.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40"
+checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
 
 [[package]]
 name = "cfg"
@@ -398,11 +398,11 @@ dependencies = [
 
 [[package]]
 name = "filetime"
-version = "0.2.13"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c122a393ea57648015bf06fbd3d372378992e86b9ff5a7a497b076a28c79efe"
+checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if 0.1.10",
  "libc",
  "redox_syscall",
  "winapi 0.3.9",
@@ -438,16 +438,6 @@ dependencies = [
  "toolchain",
 ]
 
-[[package]]
-name = "form_urlencoded"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
-dependencies = [
- "matches",
- "percent-encoding",
-]
-
 [[package]]
 name = "fsevent"
 version = "2.0.2"
@@ -622,7 +612,6 @@ dependencies = [
  "hir_expand",
  "itertools",
  "log",
- "once_cell",
  "profile",
  "rustc-hash",
  "scoped-tls",
@@ -725,9 +714,9 @@ dependencies = [
 
 [[package]]
 name = "inotify-sys"
-version = "0.1.4"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4563555856585ab3180a5bf0b2f9f8d301a728462afffc8195b3f5394229c55"
+checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
 dependencies = [
  "libc",
 ]
@@ -1064,9 +1053,9 @@ checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
 
 [[package]]
 name = "once_cell"
-version = "1.5.1"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f53cef67919d7d247eb9a2f128ca9e522789967ef1eb4ccd8c71a95a8aedf596"
+checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
 
 [[package]]
 name = "oorandom"
@@ -1394,9 +1383,9 @@ dependencies = [
 
 [[package]]
 name = "rustc-ap-rustc_lexer"
-version = "688.0.0"
+version = "686.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebbdcc99bd015349093fcbae4780fda21416fec5d8843acfb3d1733e130cd4db"
+checksum = "a5b04cd2159495584d976d501c5394498470c2e94e4f0cebb8186562d407a678"
 dependencies = [
  "unicode-xid",
 ]
@@ -1899,11 +1888,10 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
 
 [[package]]
 name = "url"
-version = "2.2.0"
+version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
+checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
 dependencies = [
- "form_urlencoded",
  "idna",
  "matches",
  "percent-encoding",
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 84662d832d..067afabf2e 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -5,10 +5,9 @@ use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
 use ide_db::{defs::Definition, search::Reference, RootDatabase};
 use rustc_hash::{FxHashMap, FxHashSet};
 use syntax::{
-    algo::find_node_at_offset,
-    algo::SyntaxRewriter,
-    ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
-    SourceFile, SyntaxElement,
+    algo::{find_node_at_offset, SyntaxRewriter},
+    ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
+    SourceFile, SyntaxElement, SyntaxNode, T,
 };
 
 use crate::{
@@ -130,17 +129,21 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &En
 fn insert_import(
     ctx: &AssistContext,
     rewriter: &mut SyntaxRewriter,
-    path: &ast::PathExpr,
+    scope_node: &SyntaxNode,
     module: &Module,
     enum_module_def: &ModuleDef,
     variant_hir_name: &Name,
 ) -> Option<()> {
     let db = ctx.db();
-    let mod_path = module.find_use_path(db, enum_module_def.clone());
+    let mod_path = module.find_use_path_prefixed(
+        db,
+        enum_module_def.clone(),
+        ctx.config.insert_use.prefix_kind,
+    );
     if let Some(mut mod_path) = mod_path {
         mod_path.segments.pop();
         mod_path.segments.push(variant_hir_name.clone());
-        let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
+        let scope = ImportScope::find_insert_use_container(scope_node, ctx)?;
 
         *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
     }
@@ -204,27 +207,31 @@ fn update_reference(
     variant_hir_name: &Name,
     visited_modules_set: &mut FxHashSet<Module>,
 ) -> Option<()> {
-    let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
-        source_file.syntax(),
-        reference.file_range.range.start(),
-    )?;
-    let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
-    let list = call.arg_list()?;
-    let segment = path_expr.path()?.segment()?;
-    let module = ctx.sema.scope(&path_expr.syntax()).module()?;
+    let offset = reference.file_range.range.start();
+    let (segment, expr) = if let Some(path_expr) =
+        find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
+    {
+        // tuple variant
+        (path_expr.path()?.segment()?, path_expr.syntax().parent()?.clone())
+    } else if let Some(record_expr) =
+        find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
+    {
+        // record variant
+        (record_expr.path()?.segment()?, record_expr.syntax().clone())
+    } else {
+        return None;
+    };
+
+    let module = ctx.sema.scope(&expr).module()?;
     if !visited_modules_set.contains(&module) {
-        if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name)
-            .is_some()
+        if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
         {
             visited_modules_set.insert(module);
         }
     }
-
-    let lparen = syntax::SyntaxElement::from(list.l_paren_token()?);
-    let rparen = syntax::SyntaxElement::from(list.r_paren_token()?);
-    rewriter.insert_after(&lparen, segment.syntax());
-    rewriter.insert_after(&lparen, &lparen);
-    rewriter.insert_before(&rparen, &rparen);
+    rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
+    rewriter.insert_after(segment.syntax(), segment.syntax());
+    rewriter.insert_after(&expr, &make::token(T![')']));
     Some(())
 }
 
@@ -320,7 +327,7 @@ fn another_fn() {
             r#"use my_mod::my_other_mod::MyField;
 
 mod my_mod {
-    use my_other_mod::MyField;
+    use self::my_other_mod::MyField;
 
     fn another_fn() {
         let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
@@ -345,6 +352,33 @@ fn another_fn() {
         );
     }
 
+    #[test]
+    fn extract_record_fix_references() {
+        check_assist(
+            extract_struct_from_enum_variant,
+            r#"
+enum E {
+    <|>V { i: i32, j: i32 }
+}
+
+fn f() {
+    let e = E::V { i: 9, j: 2 };
+}
+"#,
+            r#"
+struct V{ pub i: i32, pub j: i32 }
+
+enum E {
+    V(V)
+}
+
+fn f() {
+    let e = E::V(V { i: 9, j: 2 });
+}
+"#,
+        )
+    }
+
     #[test]
     fn test_several_files() {
         check_assist(
@@ -372,9 +406,7 @@ enum E {
 mod foo;
 
 //- /foo.rs
-use V;
-
-use crate::E;
+use crate::{E, V};
 fn f() {
     let e = E::V(V(9, 2));
 }
@@ -384,7 +416,6 @@ fn f() {
 
     #[test]
     fn test_several_files_record() {
-        // FIXME: this should fix the usage as well!
         check_assist(
             extract_struct_from_enum_variant,
             r#"
@@ -401,6 +432,7 @@ fn f() {
 }
 "#,
             r#"
+//- /main.rs
 struct V{ pub i: i32, pub j: i32 }
 
 enum E {
@@ -408,10 +440,42 @@ enum E {
 }
 mod foo;
 
+//- /foo.rs
+use crate::{E, V};
+fn f() {
+    let e = E::V(V { i: 9, j: 2 });
+}
 "#,
         )
     }
 
+    #[test]
+    fn test_extract_struct_record_nested_call_exp() {
+        check_assist(
+            extract_struct_from_enum_variant,
+            r#"
+enum A { <|>One { a: u32, b: u32 } }
+
+struct B(A);
+
+fn foo() {
+    let _ = B(A::One { a: 1, b: 2 });
+}
+"#,
+            r#"
+struct One{ pub a: u32, pub b: u32 }
+
+enum A { One(One) }
+
+struct B(A);
+
+fn foo() {
+    let _ = B(A::One(One { a: 1, b: 2 }));
+}
+"#,
+        );
+    }
+
     fn check_not_applicable(ra_fixture: &str) {
         let fixture =
             format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
diff --git a/crates/assists/src/handlers/remove_unused_param.rs b/crates/assists/src/handlers/remove_unused_param.rs
index 1ff5e92b04..5fccca54b8 100644
--- a/crates/assists/src/handlers/remove_unused_param.rs
+++ b/crates/assists/src/handlers/remove_unused_param.rs
@@ -73,8 +73,7 @@ fn process_usage(
     let source_file = ctx.sema.parse(usage.file_range.file_id);
     let call_expr: ast::CallExpr =
         find_node_at_range(source_file.syntax(), usage.file_range.range)?;
-    let call_expr_range = call_expr.expr()?.syntax().text_range();
-    if !call_expr_range.contains_range(usage.file_range.range) {
+    if call_expr.expr()?.syntax().text_range() != usage.file_range.range {
         return None;
     }
     let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
@@ -118,53 +117,6 @@ fn b() { foo(9, ) }
         );
     }
 
-    #[test]
-    fn remove_unused_qualified_call() {
-        check_assist(
-            remove_unused_param,
-            r#"
-mod bar { pub fn foo(x: i32, <|>y: i32) { x; } }
-fn b() { bar::foo(9, 2) }
-"#,
-            r#"
-mod bar { pub fn foo(x: i32) { x; } }
-fn b() { bar::foo(9) }
-"#,
-        );
-    }
-
-    #[test]
-    fn remove_unused_turbofished_func() {
-        check_assist(
-            remove_unused_param,
-            r#"
-pub fn foo<T>(x: T, <|>y: i32) { x; }
-fn b() { foo::<i32>(9, 2) }
-"#,
-            r#"
-pub fn foo<T>(x: T) { x; }
-fn b() { foo::<i32>(9) }
-"#,
-        );
-    }
-
-    #[test]
-    fn remove_unused_generic_unused_param_func() {
-        check_assist(
-            remove_unused_param,
-            r#"
-pub fn foo<T>(x: i32, <|>y: T) { x; }
-fn b() { foo::<i32>(9, 2) }
-fn b2() { foo(9, 2) }
-"#,
-            r#"
-pub fn foo<T>(x: i32) { x; }
-fn b() { foo::<i32>(9) }
-fn b2() { foo(9) }
-"#,
-        );
-    }
-
     #[test]
     fn keep_used() {
         mark::check!(keep_used);
@@ -176,37 +128,4 @@ fn main() { foo(9, 2) }
 "#,
         );
     }
-
-    #[test]
-    fn remove_across_files() {
-        check_assist(
-            remove_unused_param,
-            r#"
-//- /main.rs
-fn foo(x: i32, <|>y: i32) { x; }
-
-mod foo;
-
-//- /foo.rs
-use super::foo;
-
-fn bar() {
-    let _ = foo(1, 2);
-}
-"#,
-            r#"
-//- /main.rs
-fn foo(x: i32) { x; }
-
-mod foo;
-
-//- /foo.rs
-use super::foo;
-
-fn bar() {
-    let _ = foo(1);
-}
-"#,
-        )
-    }
 }
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index af3fc96b6c..84a0dffdd4 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -9,7 +9,7 @@ use syntax::{
         edit::{AstNodeEdit, IndentLevel},
         make, AstNode, PathSegmentKind, VisibilityOwner,
     },
-    AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
+    InsertPosition, SyntaxElement, SyntaxNode,
 };
 use test_utils::mark;
 
@@ -63,30 +63,27 @@ impl ImportScope {
         }
     }
 
-    fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
-        self.as_syntax_node()
-            .children_with_tokens()
-            .filter(|child| match child {
-                NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
-                NodeOrToken::Token(token) => is_inner_comment(token.clone()),
+    fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
+        // check if the scope has inner attributes, we dont want to insert in front of them
+        match self
+            .as_syntax_node()
+            .children()
+            // no flat_map here cause we want to short circuit the iterator
+            .map(ast::Attr::cast)
+            .take_while(|attr| {
+                attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false)
             })
             .last()
-            .map(|last_inner_element| {
-                (InsertPosition::After(last_inner_element.into()), AddBlankLine::BeforeTwice)
-            })
-            .unwrap_or_else(|| self.first_insert_pos())
+            .flatten()
+        {
+            Some(attr) => {
+                (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice)
+            }
+            None => self.first_insert_pos(),
+        }
     }
 }
 
-fn is_inner_attribute(node: SyntaxNode) -> bool {
-    ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
-}
-
-fn is_inner_comment(token: SyntaxToken) -> bool {
-    ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
-        == Some(ast::CommentPlacement::Inner)
-}
-
 /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
 pub(crate) fn insert_use<'a>(
     scope: &ImportScope,
@@ -561,7 +558,7 @@ fn find_insert_position(
                             (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
                         }
                         // there are no imports in this file at all
-                        None => scope.insert_pos_after_last_inner_element(),
+                        None => scope.insert_pos_after_inner_attribute(),
                     },
                 }
             }
@@ -833,67 +830,12 @@ use foo::bar;",
             "foo::bar",
             r"#![allow(unused_imports)]
 
-#![no_std]
 fn main() {}",
             r"#![allow(unused_imports)]
 
-#![no_std]
-
 use foo::bar;
-fn main() {}",
-        );
-    }
-
-    #[test]
-    fn inserts_after_single_line_inner_comments() {
-        check_none(
-            "foo::bar::Baz",
-            "//! Single line inner comments do not allow any code before them.",
-            r#"//! Single line inner comments do not allow any code before them.
-
-use foo::bar::Baz;"#,
-        );
-    }
 
-    #[test]
-    fn inserts_after_multiline_inner_comments() {
-        check_none(
-            "foo::bar::Baz",
-            r#"/*! Multiline inner comments do not allow any code before them. */
-
-/*! Still an inner comment, cannot place any code before. */
-fn main() {}"#,
-            r#"/*! Multiline inner comments do not allow any code before them. */
-
-/*! Still an inner comment, cannot place any code before. */
-
-use foo::bar::Baz;
-fn main() {}"#,
-        )
-    }
-
-    #[test]
-    fn inserts_after_all_inner_items() {
-        check_none(
-            "foo::bar::Baz",
-            r#"#![allow(unused_imports)]
-/*! Multiline line comment 2 */
-
-
-//! Single line comment 1
-#![no_std]
-//! Single line comment 2
-fn main() {}"#,
-            r#"#![allow(unused_imports)]
-/*! Multiline line comment 2 */
-
-
-//! Single line comment 1
-#![no_std]
-//! Single line comment 2
-
-use foo::bar::Baz;
-fn main() {}"#,
+fn main() {}",
         )
     }
 
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index cf5c38a235..fdc65a5c32 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -35,4 +35,3 @@ expect-test = "1.0"
 tracing = "0.1"
 tracing-subscriber = { version = "0.2", default-features = false, features = ["env-filter", "registry"] }
 tracing-tree = { version = "0.1.4" }
-once_cell = { version = "1.5.0", features = ["unstable"] }
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 1ed77b40b0..37ab7a8f67 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -6,7 +6,7 @@ pub(crate) mod tags;
 #[cfg(test)]
 mod tests;
 
-use hir::{AsAssocItem, Local, Name, Semantics, VariantDef};
+use hir::{Local, Name, Semantics, VariantDef};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     RootDatabase,
@@ -746,9 +746,6 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
                 if func.is_unsafe(db) {
                     h |= HighlightModifier::Unsafe;
                 }
-                if func.as_assoc_item(db).is_some() && func.self_param(db).is_none() {
-                    h |= HighlightModifier::Static;
-                }
                 return h;
             }
             hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index 65e0671a5a..e8f78ad525 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -65,8 +65,6 @@ pub enum HighlightModifier {
     Consuming,
     Unsafe,
     Callable,
-    /// Used for associated functions
-    Static,
 }
 
 impl HighlightTag {
@@ -126,7 +124,6 @@ impl HighlightModifier {
         HighlightModifier::Consuming,
         HighlightModifier::Unsafe,
         HighlightModifier::Callable,
-        HighlightModifier::Static,
     ];
 
     fn as_str(self) -> &'static str {
@@ -140,7 +137,6 @@ impl HighlightModifier {
             HighlightModifier::Consuming => "consuming",
             HighlightModifier::Unsafe => "unsafe",
             HighlightModifier::Callable => "callable",
-            HighlightModifier::Static => "static",
         }
     }
 
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index 9a88700534..ff1ae9575f 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -16,6 +16,8 @@ use serde_json::json;
 use crate::semantic_tokens;
 
 pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabilities {
+    let code_action_provider = code_action_capabilities(client_caps);
+
     ServerCapabilities {
         text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
             open_close: Some(true),
@@ -47,7 +49,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
         document_highlight_provider: Some(OneOf::Left(true)),
         document_symbol_provider: Some(OneOf::Left(true)),
         workspace_symbol_provider: Some(OneOf::Left(true)),
-        code_action_provider: Some(code_action_capabilities(client_caps)),
+        code_action_provider: Some(code_action_provider),
         code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
         document_formatting_provider: Some(OneOf::Left(true)),
         document_range_formatting_provider: None,
@@ -111,7 +113,7 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi
                     CodeActionKind::REFACTOR_INLINE,
                     CodeActionKind::REFACTOR_REWRITE,
                 ]),
-                resolve_provider: Some(true),
+                resolve_provider: None,
                 work_done_progress_options: Default::default(),
             })
         })
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index b4c738272b..2ed6a0d82e 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -144,7 +144,7 @@ pub struct ClientCapsConfig {
     pub code_action_literals: bool,
     pub work_done_progress: bool,
     pub code_action_group: bool,
-    pub code_action_resolve: bool,
+    pub resolve_code_action: bool,
     pub hover_actions: bool,
     pub status_notification: bool,
     pub signature_help_label_offsets: bool,
@@ -383,17 +383,6 @@ impl Config {
                     }
                 }
             }
-
-            if let Some(code_action) = &doc_caps.code_action {
-                match (code_action.data_support, &code_action.resolve_support) {
-                    (Some(true), Some(resolve_support)) => {
-                        if resolve_support.properties.iter().any(|it| it == "edit") {
-                            self.client_caps.code_action_resolve = true;
-                        }
-                    }
-                    _ => (),
-                }
-            }
         }
 
         if let Some(window_caps) = caps.window.as_ref() {
@@ -411,6 +400,7 @@ impl Config {
             self.assist.allow_snippets(snippet_text_edit);
 
             self.client_caps.code_action_group = get_bool("codeActionGroup");
+            self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
             self.client_caps.hover_actions = get_bool("hoverActions");
             self.client_caps.status_notification = get_bool("statusNotification");
         }
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 15145415b3..b949577c13 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -110,6 +110,7 @@ fn map_rust_child_diagnostic(
     } else {
         MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction {
             title: rd.message.clone(),
+            id: None,
             group: None,
             kind: Some(lsp_types::CodeActionKind::QUICKFIX),
             edit: Some(lsp_ext::SnippetWorkspaceEdit {
@@ -118,7 +119,6 @@ fn map_rust_child_diagnostic(
                 document_changes: None,
             }),
             is_preferred: Some(true),
-            data: None,
         })
     }
 }
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 95659b0db9..049c583a42 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -806,11 +806,11 @@ fn handle_fixes(
         let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
         let action = lsp_ext::CodeAction {
             title: fix.label.to_string(),
+            id: None,
             group: None,
             kind: Some(CodeActionKind::QUICKFIX),
             edit: Some(edit),
             is_preferred: Some(false),
-            data: None,
         };
         res.push(action);
     }
@@ -852,11 +852,11 @@ pub(crate) fn handle_code_action(
 
     handle_fixes(&snap, &params, &mut res)?;
 
-    if snap.config.client_caps.code_action_resolve {
+    if snap.config.client_caps.resolve_code_action {
         for (index, assist) in
             snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate()
         {
-            res.push(to_proto::unresolved_code_action(&snap, params.clone(), assist, index)?);
+            res.push(to_proto::unresolved_code_action(&snap, assist, index)?);
         }
     } else {
         for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() {
@@ -867,16 +867,11 @@ pub(crate) fn handle_code_action(
     Ok(Some(res))
 }
 
-pub(crate) fn handle_code_action_resolve(
+pub(crate) fn handle_resolve_code_action(
     mut snap: GlobalStateSnapshot,
-    mut code_action: lsp_ext::CodeAction,
-) -> Result<lsp_ext::CodeAction> {
-    let _p = profile::span("handle_code_action_resolve");
-    let params = match code_action.data.take() {
-        Some(it) => it,
-        None => Err("can't resolve code action without data")?,
-    };
-
+    params: lsp_ext::ResolveCodeActionParams,
+) -> Result<Option<lsp_ext::SnippetWorkspaceEdit>> {
+    let _p = profile::span("handle_resolve_code_action");
     let file_id = from_proto::file_id(&snap, &params.code_action_params.text_document.uri)?;
     let line_index = snap.analysis.file_line_index(file_id)?;
     let range = from_proto::text_range(&line_index, params.code_action_params.range);
@@ -893,9 +888,7 @@ pub(crate) fn handle_code_action_resolve(
     let index = index.parse::<usize>().unwrap();
     let assist = &assists[index];
     assert!(assist.assist.id.0 == id);
-    let edit = to_proto::resolved_code_action(&snap, assist.clone())?.edit;
-    code_action.edit = edit;
-    Ok(code_action)
+    Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit)
 }
 
 pub(crate) fn handle_code_lens(
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index a7c3028e49..f31f8d9001 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -113,6 +113,22 @@ pub struct JoinLinesParams {
     pub ranges: Vec<Range>,
 }
 
+pub enum ResolveCodeActionRequest {}
+
+impl Request for ResolveCodeActionRequest {
+    type Params = ResolveCodeActionParams;
+    type Result = Option<SnippetWorkspaceEdit>;
+    const METHOD: &'static str = "experimental/resolveCodeAction";
+}
+
+/// Params for the ResolveCodeActionRequest
+#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ResolveCodeActionParams {
+    pub code_action_params: lsp_types::CodeActionParams,
+    pub id: String,
+}
+
 pub enum OnEnter {}
 
 impl Request for OnEnter {
@@ -249,18 +265,13 @@ impl Request for CodeActionRequest {
     const METHOD: &'static str = "textDocument/codeAction";
 }
 
-pub enum CodeActionResolveRequest {}
-impl Request for CodeActionResolveRequest {
-    type Params = CodeAction;
-    type Result = CodeAction;
-    const METHOD: &'static str = "codeAction/resolve";
-}
-
 #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct CodeAction {
     pub title: String,
     #[serde(skip_serializing_if = "Option::is_none")]
+    pub id: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub group: Option<String>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub kind: Option<CodeActionKind>,
@@ -271,16 +282,6 @@ pub struct CodeAction {
     pub edit: Option<SnippetWorkspaceEdit>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub is_preferred: Option<bool>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub data: Option<CodeActionData>,
-}
-
-#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CodeActionData {
-    pub code_action_params: lsp_types::CodeActionParams,
-    pub id: String,
 }
 
 #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 6e6cac42e0..d504572a83 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -435,7 +435,7 @@ impl GlobalState {
             .on::<lsp_ext::Runnables>(handlers::handle_runnables)
             .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)
             .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)
-            .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve)
+            .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)
             .on::<lsp_ext::HoverRequest>(handlers::handle_hover)
             .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
             .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 4bdf4bf0fa..92b7c7b681 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -426,7 +426,6 @@ fn semantic_token_type_and_modifiers(
             HighlightModifier::Consuming => semantic_tokens::CONSUMING,
             HighlightModifier::Unsafe => semantic_tokens::UNSAFE,
             HighlightModifier::Callable => semantic_tokens::CALLABLE,
-            HighlightModifier::Static => lsp_types::SemanticTokenModifier::STATIC,
         };
         mods |= modifier;
     }
@@ -735,20 +734,16 @@ pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind {
 
 pub(crate) fn unresolved_code_action(
     snap: &GlobalStateSnapshot,
-    code_action_params: lsp_types::CodeActionParams,
     assist: Assist,
     index: usize,
 ) -> Result<lsp_ext::CodeAction> {
     let res = lsp_ext::CodeAction {
         title: assist.label.to_string(),
+        id: Some(format!("{}:{}", assist.id.0, index.to_string())),
         group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
         kind: Some(code_action_kind(assist.id.1)),
         edit: None,
         is_preferred: None,
-        data: Some(lsp_ext::CodeActionData {
-            id: format!("{}:{}", assist.id.0, index.to_string()),
-            code_action_params,
-        }),
     };
     Ok(res)
 }
@@ -758,19 +753,13 @@ pub(crate) fn resolved_code_action(
     assist: ResolvedAssist,
 ) -> Result<lsp_ext::CodeAction> {
     let change = assist.source_change;
-    let res = lsp_ext::CodeAction {
-        edit: Some(snippet_workspace_edit(snap, change)?),
-        title: assist.assist.label.to_string(),
-        group: assist
-            .assist
-            .group
-            .filter(|_| snap.config.client_caps.code_action_group)
-            .map(|gr| gr.0),
-        kind: Some(code_action_kind(assist.assist.id.1)),
-        is_preferred: None,
-        data: None,
-    };
-    Ok(res)
+    unresolved_code_action(snap, assist.assist, 0).and_then(|it| {
+        Ok(lsp_ext::CodeAction {
+            id: None,
+            edit: Some(snippet_workspace_edit(snap, change)?),
+            ..it
+        })
+    })
 }
 
 pub(crate) fn runnable(
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 374ed59105..59d89f47d1 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -1,5 +1,8 @@
 //! Missing batteries for standard libraries.
-use std::time::Instant;
+use std::{
+    sync::atomic::{AtomicUsize, Ordering},
+    time::Instant,
+};
 
 mod macros;
 pub mod panic_context;
@@ -147,6 +150,31 @@ where
     left
 }
 
+pub struct RacyFlag(AtomicUsize);
+
+impl RacyFlag {
+    pub const fn new() -> RacyFlag {
+        RacyFlag(AtomicUsize::new(!0))
+    }
+
+    pub fn get(&self, init: impl FnMut() -> bool) -> bool {
+        let mut init = Some(init);
+        self.get_impl(&mut || init.take().map_or(false, |mut f| f()))
+    }
+
+    fn get_impl(&self, init: &mut dyn FnMut() -> bool) -> bool {
+        match self.0.load(Ordering::Relaxed) {
+            0 => false,
+            1 => true,
+            _ => {
+                let res = init();
+                self.0.store(if res { 1 } else { 0 }, Ordering::Relaxed);
+                res
+            }
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 1fe907753d..61d2acb49c 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 = "688.0.0", package = "rustc-ap-rustc_lexer" }
+rustc_lexer = { version = "686.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/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index 320c430c94..30adb72176 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -111,28 +111,18 @@ pub enum InsertPosition<T> {
 
 type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>;
 
-#[derive(Debug, Hash, PartialEq, Eq)]
-enum TreeDiffInsertPos {
-    After(SyntaxElement),
-    AsFirstChild(SyntaxElement),
-}
-
 #[derive(Debug)]
 pub struct TreeDiff {
     replacements: FxHashMap<SyntaxElement, SyntaxElement>,
     deletions: Vec<SyntaxElement>,
     // the vec as well as the indexmap are both here to preserve order
-    insertions: FxIndexMap<TreeDiffInsertPos, Vec<SyntaxElement>>,
+    insertions: FxIndexMap<SyntaxElement, Vec<SyntaxElement>>,
 }
 
 impl TreeDiff {
     pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
         for (anchor, to) in self.insertions.iter() {
-            let offset = match anchor {
-                TreeDiffInsertPos::After(it) => it.text_range().end(),
-                TreeDiffInsertPos::AsFirstChild(it) => it.text_range().start(),
-            };
-            to.iter().for_each(|to| builder.insert(offset, to.to_string()));
+            to.iter().for_each(|to| builder.insert(anchor.text_range().end(), to.to_string()));
         }
         for (from, to) in self.replacements.iter() {
             builder.replace(from.text_range(), to.to_string())
@@ -198,20 +188,19 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
             let lhs_child = lhs_children.next();
             match (lhs_child.clone(), rhs_children.next()) {
                 (None, None) => break,
-                (None, Some(element)) => {
-                    let insert_pos = match last_lhs.clone() {
-                        Some(prev) => {
-                            mark::hit!(diff_insert);
-                            TreeDiffInsertPos::After(prev)
-                        }
-                        // first iteration, insert into out parent as the first child
-                        None => {
-                            mark::hit!(diff_insert_as_first_child);
-                            TreeDiffInsertPos::AsFirstChild(lhs.clone().into())
-                        }
-                    };
-                    diff.insertions.entry(insert_pos).or_insert_with(Vec::new).push(element);
-                }
+                (None, Some(element)) => match last_lhs.clone() {
+                    Some(prev) => {
+                        mark::hit!(diff_insert);
+                        diff.insertions.entry(prev).or_insert_with(Vec::new).push(element);
+                    }
+                    // first iteration, this means we got no anchor element to insert after
+                    // therefor replace the parent node instead
+                    None => {
+                        mark::hit!(diff_replace_parent);
+                        diff.replacements.insert(lhs.clone().into(), rhs.clone().into());
+                        break;
+                    }
+                },
                 (Some(element), None) => {
                     mark::hit!(diff_delete);
                     diff.deletions.push(element);
@@ -235,15 +224,8 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
                         }
                     }
                     let drain = look_ahead_scratch.drain(..);
-                    if insert {
-                        let insert_pos = if let Some(prev) = last_lhs.clone().filter(|_| insert) {
-                            TreeDiffInsertPos::After(prev)
-                        } else {
-                            mark::hit!(insert_first_child);
-                            TreeDiffInsertPos::AsFirstChild(lhs.clone().into())
-                        };
-
-                        diff.insertions.entry(insert_pos).or_insert_with(Vec::new).extend(drain);
+                    if let Some(prev) = last_lhs.clone().filter(|_| insert) {
+                        diff.insertions.entry(prev).or_insert_with(Vec::new).extend(drain);
                         rhs_children = rhs_children_clone;
                     } else {
                         go(diff, lhs_ele, rhs_ele)
@@ -650,19 +632,18 @@ mod tests {
 
     #[test]
     fn replace_parent() {
-        mark::check!(diff_insert_as_first_child);
+        mark::check!(diff_replace_parent);
         check_diff(
             r#""#,
             r#"use foo::bar;"#,
             expect![[r#"
                 insertions:
 
-                Line 0: AsFirstChild(Node(SOURCE_FILE@0..0))
-                -> use foo::bar;
 
-                replacements:
 
+                replacements:
 
+                Line 0: Node(SOURCE_FILE@0..0) -> use foo::bar;
 
                 deletions:
 
@@ -685,7 +666,7 @@ use baz;"#,
             expect![[r#"
                 insertions:
 
-                Line 2: After(Node(USE@10..18))
+                Line 2: Node(USE@10..18)
                 -> "\n"
                 -> use baz;
 
@@ -713,7 +694,7 @@ use baz;"#,
             expect![[r#"
                 insertions:
 
-                Line 2: After(Token(WHITESPACE@9..10 "\n"))
+                Line 2: Token(WHITESPACE@9..10 "\n")
                 -> use bar;
                 -> "\n"
 
@@ -741,7 +722,7 @@ use baz;"#,
             expect![[r#"
                 insertions:
 
-                Line 0: After(Token(WHITESPACE@0..1 "\n"))
+                Line 0: Token(WHITESPACE@0..1 "\n")
                 -> use foo;
                 -> "\n"
 
@@ -756,36 +737,6 @@ use baz;"#,
         )
     }
 
-    #[test]
-    fn first_child_insertion() {
-        mark::check!(insert_first_child);
-        check_diff(
-            r#"fn main() {
-        stdi
-    }"#,
-            r#"use foo::bar;
-
-    fn main() {
-        stdi
-    }"#,
-            expect![[r#"
-                insertions:
-
-                Line 0: AsFirstChild(Node(SOURCE_FILE@0..30))
-                -> use foo::bar;
-                -> "\n\n    "
-
-                replacements:
-
-
-
-                deletions:
-
-
-            "#]],
-        );
-    }
-
     #[test]
     fn delete_last() {
         mark::check!(diff_delete);
@@ -828,7 +779,7 @@ use crate::AstNode;
             expect![[r#"
                 insertions:
 
-                Line 1: After(Node(USE@1..35))
+                Line 1: Node(USE@1..35)
                 -> "\n\n"
                 -> use crate::AstNode;
 
@@ -894,10 +845,10 @@ use std::ops::{self, RangeInclusive};
             expect![[r#"
                 insertions:
 
-                Line 2: After(Node(PATH_SEGMENT@5..8))
+                Line 2: Node(PATH_SEGMENT@5..8)
                 -> ::
                 -> fmt
-                Line 6: After(Token(WHITESPACE@86..87 "\n"))
+                Line 6: Token(WHITESPACE@86..87 "\n")
                 -> use std::hash::BuildHasherDefault;
                 -> "\n"
                 -> use std::ops::{self, RangeInclusive};
@@ -941,14 +892,14 @@ fn main() {
             expect![[r#"
                 insertions:
 
-                Line 3: After(Node(BLOCK_EXPR@40..63))
+                Line 3: Node(BLOCK_EXPR@40..63)
                 -> " "
                 -> match Err(92) {
                         Ok(it) => it,
                         _ => return,
                     }
                 -> ;
-                Line 3: After(Node(IF_EXPR@17..63))
+                Line 3: Node(IF_EXPR@17..63)
                 -> "\n    "
                 -> foo(x);
 
@@ -983,18 +934,14 @@ fn main() {
             _ => format!("{}", syn),
         };
 
-        let insertions =
-            diff.insertions.iter().format_with("\n", |(k, v), f| -> Result<(), std::fmt::Error> {
-                f(&format!(
-                    "Line {}: {:?}\n-> {}",
-                    line_number(match k {
-                        super::TreeDiffInsertPos::After(syn) => syn,
-                        super::TreeDiffInsertPos::AsFirstChild(syn) => syn,
-                    }),
-                    k,
-                    v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v)))
-                ))
-            });
+        let insertions = diff.insertions.iter().format_with("\n", |(k, v), f| {
+            f(&format!(
+                "Line {}: {:?}\n-> {}",
+                line_number(k),
+                k,
+                v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v)))
+            ))
+        });
 
         let replacements = diff
             .replacements
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index 7844f9ed63..8a0e3d27b2 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -115,22 +115,7 @@ fn test_doc_comment_none() {
 }
 
 #[test]
-fn test_outer_doc_comment_of_items() {
-    let file = SourceFile::parse(
-        r#"
-        /// doc
-        // non-doc
-        mod foo {}
-        "#,
-    )
-    .ok()
-    .unwrap();
-    let module = file.syntax().descendants().find_map(Module::cast).unwrap();
-    assert_eq!("doc", module.doc_comment_text().unwrap());
-}
-
-#[test]
-fn test_inner_doc_comment_of_items() {
+fn test_doc_comment_of_items() {
     let file = SourceFile::parse(
         r#"
         //! doc
@@ -141,7 +126,7 @@ fn test_inner_doc_comment_of_items() {
     .ok()
     .unwrap();
     let module = file.syntax().descendants().find_map(Module::cast).unwrap();
-    assert!(module.doc_comment_text().is_none());
+    assert_eq!("doc", module.doc_comment_text().unwrap());
 }
 
 #[test]
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 2661c753e2..e4e512f2e0 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -14,15 +14,16 @@ use crate::{
 
 impl ast::Comment {
     pub fn kind(&self) -> CommentKind {
-        CommentKind::from_text(self.text())
+        kind_by_prefix(self.text())
     }
 
     pub fn prefix(&self) -> &'static str {
-        let &(prefix, _kind) = CommentKind::BY_PREFIX
-            .iter()
-            .find(|&(prefix, kind)| self.kind() == *kind && self.text().starts_with(prefix))
-            .unwrap();
-        prefix
+        for (prefix, k) in COMMENT_PREFIX_TO_KIND.iter() {
+            if *k == self.kind() && self.text().starts_with(prefix) {
+                return prefix;
+            }
+        }
+        unreachable!()
     }
 }
 
@@ -54,25 +55,29 @@ pub enum CommentPlacement {
     Outer,
 }
 
-impl CommentKind {
-    const BY_PREFIX: [(&'static str, CommentKind); 8] = [
-        ("/**/", CommentKind { shape: CommentShape::Block, doc: None }),
-        ("////", CommentKind { shape: CommentShape::Line, doc: None }),
-        ("///", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Outer) }),
-        ("//!", CommentKind { shape: CommentShape::Line, doc: Some(CommentPlacement::Inner) }),
-        ("/**", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Outer) }),
-        ("/*!", CommentKind { shape: CommentShape::Block, doc: Some(CommentPlacement::Inner) }),
-        ("//", CommentKind { shape: CommentShape::Line, doc: None }),
-        ("/*", CommentKind { shape: CommentShape::Block, doc: None }),
-    ];
+const COMMENT_PREFIX_TO_KIND: &[(&str, CommentKind)] = {
+    use {CommentPlacement::*, CommentShape::*};
+    &[
+        ("////", CommentKind { shape: Line, doc: None }),
+        ("///", CommentKind { shape: Line, doc: Some(Outer) }),
+        ("//!", CommentKind { shape: Line, doc: Some(Inner) }),
+        ("/**", CommentKind { shape: Block, doc: Some(Outer) }),
+        ("/*!", CommentKind { shape: Block, doc: Some(Inner) }),
+        ("//", CommentKind { shape: Line, doc: None }),
+        ("/*", CommentKind { shape: Block, doc: None }),
+    ]
+};
 
-    pub(crate) fn from_text(text: &str) -> CommentKind {
-        let &(_prefix, kind) = CommentKind::BY_PREFIX
-            .iter()
-            .find(|&(prefix, _kind)| text.starts_with(prefix))
-            .unwrap();
-        kind
+fn kind_by_prefix(text: &str) -> CommentKind {
+    if text == "/**/" {
+        return CommentKind { shape: CommentShape::Block, doc: None };
+    }
+    for (prefix, kind) in COMMENT_PREFIX_TO_KIND.iter() {
+        if text.starts_with(prefix) {
+            return *kind;
+        }
     }
+    panic!("bad comment text: {:?}", text)
 }
 
 impl ast::Whitespace {
diff --git a/crates/syntax/src/parsing/text_tree_sink.rs b/crates/syntax/src/parsing/text_tree_sink.rs
index 997bc5d288..c1b5f246d1 100644
--- a/crates/syntax/src/parsing/text_tree_sink.rs
+++ b/crates/syntax/src/parsing/text_tree_sink.rs
@@ -5,7 +5,6 @@ use std::mem;
 use parser::{ParseError, TreeSink};
 
 use crate::{
-    ast,
     parsing::Token,
     syntax_node::GreenNode,
     SmolStr, SyntaxError,
@@ -154,22 +153,24 @@ fn n_attached_trivias<'a>(
 
             while let Some((i, (kind, text))) = trivias.next() {
                 match kind {
-                    WHITESPACE if text.contains("\n\n") => {
-                        // we check whether the next token is a doc-comment
-                        // and skip the whitespace in this case
-                        if let Some((COMMENT, peek_text)) = trivias.peek().map(|(_, pair)| pair) {
-                            let comment_kind = ast::CommentKind::from_text(peek_text);
-                            if comment_kind.doc == Some(ast::CommentPlacement::Outer) {
-                                continue;
+                    WHITESPACE => {
+                        if text.contains("\n\n") {
+                            // we check whether the next token is a doc-comment
+                            // and skip the whitespace in this case
+                            if let Some((peek_kind, peek_text)) =
+                                trivias.peek().map(|(_, pair)| pair)
+                            {
+                                if *peek_kind == COMMENT
+                                    && peek_text.starts_with("///")
+                                    && !peek_text.starts_with("////")
+                                {
+                                    continue;
+                                }
                             }
+                            break;
                         }
-                        break;
                     }
                     COMMENT => {
-                        let comment_kind = ast::CommentKind::from_text(text);
-                        if comment_kind.doc == Some(ast::CommentPlacement::Inner) {
-                            break;
-                        }
                         res = i + 1;
                     }
                     _ => (),
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 77d4e0ec99..780f5cb91f 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp_ext.rs hash: 4f86fb54e4b2870e
+lsp_ext.rs hash: 286f8bbac885531a
 
 If you need to change the above hash to make the test pass, please check if you
 need to adjust this doc as well and ping this issue:
@@ -109,6 +109,30 @@ Invoking code action at this position will yield two code actions for importing
 * Is a fixed two-level structure enough?
 * Should we devise a general way to encode custom interaction protocols for GUI refactorings?
 
+## Lazy assists with `ResolveCodeAction`
+
+**Issue:** https://github.com/microsoft/language-server-protocol/issues/787
+
+**Client Capability** `{ "resolveCodeAction": boolean }`
+
+If this capability is set, the assists will be computed lazily. Thus `CodeAction` returned from the server will only contain `id` but not `edit` or `command` fields. The only exclusion from the rule is the diagnostic edits.
+
+After the client got the id, it should then call `experimental/resolveCodeAction` command on the server and provide the following payload:
+
+```typescript
+interface ResolveCodeActionParams {
+    id: string;
+    codeActionParams: lc.CodeActionParams;
+}
+```
+
+As a result of the command call the client will get the respective workspace edit (`lc.WorkspaceEdit`).
+
+### Unresolved Questions
+
+* Apply smarter filtering for ids?
+* Upon `resolveCodeAction` command only call the assits which should be resolved and not all of them?
+
 ## Parent Module
 
 **Issue:** https://github.com/microsoft/language-server-protocol/issues/1002
diff --git a/editors/code/rust.tmGrammar.json b/editors/code/rust.tmGrammar.json
index cd4775d273..b3a10795eb 100644
--- a/editors/code/rust.tmGrammar.json
+++ b/editors/code/rust.tmGrammar.json
@@ -307,14 +307,9 @@
         "block-comments": {
             "patterns": [
                 {
-                    "comment": "empty block comments",
+                    "comment": "block comments",
                     "name": "comment.block.rust",
-                    "match": "/\\*\\*/"
-                },
-                {
-                    "comment": "block documentation comments",
-                    "name": "comment.block.documentation.rust",
-                    "begin": "/\\*\\*",
+                    "begin": "/\\*(?!\\*)",
                     "end": "\\*/",
                     "patterns": [
                         {
@@ -323,9 +318,9 @@
                     ]
                 },
                 {
-                    "comment": "block comments",
-                    "name": "comment.block.rust",
-                    "begin": "/\\*(?!\\*)",
+                    "comment": "block documentation comments",
+                    "name": "comment.block.documentation.rust",
+                    "begin": "/\\*\\*",
                     "end": "\\*/",
                     "patterns": [
                         {
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index c9d032ead3..d032b45b71 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -4,7 +4,6 @@ import * as ra from '../src/lsp_ext';
 import * as Is from 'vscode-languageclient/lib/common/utils/is';
 import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens';
 import { assert } from './util';
-import { WorkspaceEdit } from 'vscode';
 
 function renderCommand(cmd: ra.CommandLink) {
     return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`;
@@ -76,8 +75,8 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
                         return Promise.resolve(null);
                     });
             },
-            // Using custom handling of CodeActions to support action groups and snippet edits.
-            // Note that this means we have to re-implement lazy edit resolving ourselves as well.
+            // Using custom handling of CodeActions where each code action is resolved lazily
+            // That's why we are not waiting for any command or edits
             async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
                 const params: lc.CodeActionParams = {
                     textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
@@ -100,15 +99,16 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
                         const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind);
                         const action = new vscode.CodeAction(item.title, kind);
                         const group = (item as any).group;
+                        const id = (item as any).id;
+                        const resolveParams: ra.ResolveCodeActionParams = {
+                            id: id,
+                            codeActionParams: params
+                        };
                         action.command = {
                             command: "rust-analyzer.resolveCodeAction",
                             title: item.title,
-                            arguments: [item],
+                            arguments: [resolveParams],
                         };
-
-                        // Set a dummy edit, so that VS Code doesn't try to resolve this.
-                        action.edit = new WorkspaceEdit();
-
                         if (group) {
                             let entry = groups.get(group);
                             if (!entry) {
@@ -134,10 +134,6 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
                                     return { label: item.title, arguments: item.command!!.arguments!![0] };
                                 })],
                             };
-
-                            // Set a dummy edit, so that VS Code doesn't try to resolve this.
-                            action.edit = new WorkspaceEdit();
-
                             result[index] = action;
                         }
                     }
@@ -168,6 +164,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
         const caps: any = capabilities.experimental ?? {};
         caps.snippetTextEdit = true;
         caps.codeActionGroup = true;
+        caps.resolveCodeAction = true;
         caps.hoverActions = true;
         caps.statusNotification = true;
         capabilities.experimental = caps;
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index cf34622c34..22509e8740 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -395,7 +395,7 @@ export function showReferences(ctx: Ctx): Cmd {
 }
 
 export function applyActionGroup(_ctx: Ctx): Cmd {
-    return async (actions: { label: string; arguments: lc.CodeAction }[]) => {
+    return async (actions: { label: string; arguments: ra.ResolveCodeActionParams }[]) => {
         const selectedAction = await vscode.window.showQuickPick(actions);
         if (!selectedAction) return;
         vscode.commands.executeCommand(
@@ -442,13 +442,12 @@ export function openDocs(ctx: Ctx): Cmd {
 
 export function resolveCodeAction(ctx: Ctx): Cmd {
     const client = ctx.client;
-    return async (params: lc.CodeAction) => {
-        params.command = undefined;
-        const item = await client.sendRequest(lc.CodeActionResolveRequest.type, params);
-        if (!item.edit) {
+    return async (params: ra.ResolveCodeActionParams) => {
+        const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params);
+        if (!item) {
             return;
         }
-        const edit = client.protocol2CodeConverter.asWorkspaceEdit(item.edit);
+        const edit = client.protocol2CodeConverter.asWorkspaceEdit(item);
         await applySnippetWorkspaceEdit(edit);
     };
 }
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index d320c3cd71..fc8e120b3f 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -43,6 +43,12 @@ export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position
 
 export const parentModule = new lc.RequestType<lc.TextDocumentPositionParams, lc.LocationLink[], void>("experimental/parentModule");
 
+export interface ResolveCodeActionParams {
+    id: string;
+    codeActionParams: lc.CodeActionParams;
+}
+export const resolveCodeAction = new lc.RequestType<ResolveCodeActionParams, lc.WorkspaceEdit, unknown>('experimental/resolveCodeAction');
+
 export interface JoinLinesParams {
     textDocument: lc.TextDocumentIdentifier;
     ranges: lc.Range[];


Test patch:
diff --git a/crates/hir_ty/src/tests.rs b/crates/hir_ty/src/tests.rs
index 0a400cb705..104ef334c6 100644
--- a/crates/hir_ty/src/tests.rs
+++ b/crates/hir_ty/src/tests.rs
@@ -22,8 +22,7 @@ use hir_def::{
     AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId,
 };
 use hir_expand::{db::AstDatabase, InFile};
-use once_cell::race::OnceBool;
-use stdx::format_to;
+use stdx::{format_to, RacyFlag};
 use syntax::{
     algo,
     ast::{self, AstNode},
@@ -41,8 +40,8 @@ use crate::{
 // `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots.
 
 fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
-    static ENABLE: OnceBool = OnceBool::new();
-    if !ENABLE.get_or_init(|| env::var("CHALK_DEBUG").is_ok()) {
+    static ENABLE: RacyFlag = RacyFlag::new();
+    if !ENABLE.get(|| env::var("CHALK_DEBUG").is_ok()) {
         return None;
     }
 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
deleted file mode 100644
index cd80d72b79..0000000000
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-<style>
-body                { margin: 0; }
-pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
-
-.lifetime           { color: #DFAF8F; font-style: italic; }
-.comment            { color: #7F9F7F; }
-.documentation      { color: #629755; }
-.injected           { opacity: 0.65 ; }
-.struct, .enum      { color: #7CB8BB; }
-.enum_variant       { color: #BDE0F3; }
-.string_literal     { color: #CC9393; }
-.field              { color: #94BFF3; }
-.function           { color: #93E0E3; }
-.function.unsafe    { color: #BC8383; }
-.operator.unsafe    { color: #BC8383; }
-.parameter          { color: #94BFF3; }
-.text               { color: #DCDCCC; }
-.type               { color: #7CB8BB; }
-.builtin_type       { color: #8CD0D3; }
-.type_param         { color: #DFAF8F; }
-.attribute          { color: #94BFF3; }
-.numeric_literal    { color: #BFEBBF; }
-.bool_literal       { color: #BFE6EB; }
-.macro              { color: #94BFF3; }
-.module             { color: #AFD8AF; }
-.value_param        { color: #DCDCCC; }
-.variable           { color: #DCDCCC; }
-.format_specifier   { color: #CC696B; }
-.mutable            { text-decoration: underline; }
-.escape_sequence    { color: #94BFF3; }
-.keyword            { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe     { color: #BC8383; font-weight: bold; }
-.control            { font-style: italic; }
-
-.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
-</style>
-<pre><code><span class="keyword">fn</span> <span class="function declaration">not_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
-
-<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="punctuation">{</span><span class="punctuation">}</span>
-
-<span class="keyword">impl</span> <span class="struct">foo</span> <span class="punctuation">{</span>
-    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static">is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
-    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
-<span class="punctuation">}</span>
-
-<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="punctuation">{</span>
-    <span class="keyword">fn</span> <span class="function declaration static">t_is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
-    <span class="keyword">fn</span> <span class="function declaration">t_is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
-<span class="punctuation">}</span>
-
-<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="punctuation">{</span>
-    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static">is_static</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
-    <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">is_not_static</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
-<span class="punctuation">}</span>
-        </code></pre>
\ No newline at end of file
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 6be88f856b..f44fe457d4 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -53,7 +53,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <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>
+    <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
         <span class="struct">Foo</span> <span class="punctuation">{</span> <span class="field">bar</span><span class="punctuation">:</span> <span class="bool_literal">true</span> <span class="punctuation">}</span>
     <span class="punctuation">}</span>
 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index 57c178916f..18addd00d2 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -40,7 +40,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 <span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
     <span class="function">fixture</span><span class="punctuation">(</span><span class="string_literal">r#"</span>
         <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="punctuation">{</span>
-            <span class="keyword">fn</span> <span class="function declaration static">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
+            <span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
                 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="punctuation">,</span> <span class="numeric_literal">4</span><span class="punctuation">)</span><span class="punctuation">;</span>
             <span class="punctuation">}</span>
         <span class="punctuation">}</span><span class="string_literal">"#</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 5c22e2fce3..2b667b0d4a 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -513,34 +513,6 @@ fn test_extern_crate() {
     );
 }
 
-#[test]
-fn test_associated_function() {
-    check_highlighting(
-        r#"
-fn not_static() {}
-
-struct foo {}
-
-impl foo {
-    pub fn is_static() {}
-    pub fn is_not_static(&self) {}
-}
-
-trait t {
-    fn t_is_static() {}
-    fn t_is_not_static(&self) {}
-}
-
-impl t for foo {
-    pub fn is_static() {}
-    pub fn is_not_static(&self) {}
-}
-        "#,
-        expect_file!["./test_data/highlight_assoc_functions.html"],
-        false,
-    )
-}
-
 /// Highlights the code given by the `ra_fixture` argument, renders the
 /// result as HTML, and compares it with the HTML file given as `snapshot`.
 /// Note that the `snapshot` file is overwritten by the rendered HTML.
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
index 5b2e5187ac..58d47d32a4 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/clippy_pass_by_ref.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/compiler/mir/tagset.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/compiler/mir/tagset.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
@@ -36,14 +29,7 @@
                 [
                     DiagnosticRelatedInformation {
                         location: Location {
-                            uri: Url {
-                                scheme: "file",
-                                host: None,
-                                port: None,
-                                path: "/test/compiler/lib.rs",
-                                query: None,
-                                fragment: None,
-                            },
+                            uri: "file:///test/compiler/lib.rs",
                             range: Range {
                                 start: Position {
                                     line: 0,
@@ -59,14 +45,7 @@
                     },
                     DiagnosticRelatedInformation {
                         location: Location {
-                            uri: Url {
-                                scheme: "file",
-                                host: None,
-                                port: None,
-                                path: "/test/compiler/mir/tagset.rs",
-                                query: None,
-                                fragment: None,
-                            },
+                            uri: "file:///test/compiler/mir/tagset.rs",
                             range: Range {
                                 start: Position {
                                     line: 41,
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt b/crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt
index 116f0ff734..6aa26bf63a 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/handles_macro_location.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/src/main.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/src/main.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
index bbec6a7964..7aaffaba25 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/crates/hir_def/src/data.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/crates/hir_def/src/data.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
@@ -32,14 +25,7 @@
                 [
                     DiagnosticRelatedInformation {
                         location: Location {
-                            uri: Url {
-                                scheme: "file",
-                                host: None,
-                                port: None,
-                                path: "/test/crates/hir_def/src/path.rs",
-                                query: None,
-                                fragment: None,
-                            },
+                            uri: "file:///test/crates/hir_def/src/path.rs",
                             range: Range {
                                 start: Position {
                                     line: 264,
@@ -61,14 +47,7 @@
         fixes: [],
     },
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/crates/hir_def/src/path.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/crates/hir_def/src/path.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
@@ -93,14 +72,7 @@
                 [
                     DiagnosticRelatedInformation {
                         location: Location {
-                            uri: Url {
-                                scheme: "file",
-                                host: None,
-                                port: None,
-                                path: "/test/crates/hir_def/src/data.rs",
-                                query: None,
-                                fragment: None,
-                            },
+                            uri: "file:///test/crates/hir_def/src/data.rs",
                             range: Range {
                                 start: Position {
                                     line: 79,
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt
index 2cbf657e59..5842134200 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_incompatible_type_for_trait.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/compiler/ty/list_iter.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/compiler/ty/list_iter.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt
index 1142dc2ac6..2610e4e209 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_mismatched_type.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/runtime/compiler_support.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/runtime/compiler_support.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
index c709de95ff..8dc53391e9 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/driver/subcommand/repl.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/driver/subcommand/repl.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
@@ -43,6 +36,7 @@
         fixes: [
             CodeAction {
                 title: "consider prefixing with an underscore",
+                id: None,
                 group: None,
                 kind: Some(
                     CodeActionKind(
@@ -53,14 +47,7 @@
                     SnippetWorkspaceEdit {
                         changes: Some(
                             {
-                                Url {
-                                    scheme: "file",
-                                    host: None,
-                                    port: None,
-                                    path: "/test/driver/subcommand/repl.rs",
-                                    query: None,
-                                    fragment: None,
-                                }: [
+                                "file:///test/driver/subcommand/repl.rs": [
                                     TextEdit {
                                         range: Range {
                                             start: Position {
@@ -83,7 +70,6 @@
                 is_preferred: Some(
                     true,
                 ),
-                data: None,
             },
         ],
     },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
index 632f438d7a..c8703194c8 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_hint.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/driver/subcommand/repl.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/driver/subcommand/repl.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
@@ -43,6 +36,7 @@
         fixes: [
             CodeAction {
                 title: "consider prefixing with an underscore",
+                id: None,
                 group: None,
                 kind: Some(
                     CodeActionKind(
@@ -53,14 +47,7 @@
                     SnippetWorkspaceEdit {
                         changes: Some(
                             {
-                                Url {
-                                    scheme: "file",
-                                    host: None,
-                                    port: None,
-                                    path: "/test/driver/subcommand/repl.rs",
-                                    query: None,
-                                    fragment: None,
-                                }: [
+                                "file:///test/driver/subcommand/repl.rs": [
                                     TextEdit {
                                         range: Range {
                                             start: Position {
@@ -83,7 +70,6 @@
                 is_preferred: Some(
                     true,
                 ),
-                data: None,
             },
         ],
     },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
index c0b79428d8..dc93227adf 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_unused_variable_as_info.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/driver/subcommand/repl.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/driver/subcommand/repl.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
@@ -43,6 +36,7 @@
         fixes: [
             CodeAction {
                 title: "consider prefixing with an underscore",
+                id: None,
                 group: None,
                 kind: Some(
                     CodeActionKind(
@@ -53,14 +47,7 @@
                     SnippetWorkspaceEdit {
                         changes: Some(
                             {
-                                Url {
-                                    scheme: "file",
-                                    host: None,
-                                    port: None,
-                                    path: "/test/driver/subcommand/repl.rs",
-                                    query: None,
-                                    fragment: None,
-                                }: [
+                                "file:///test/driver/subcommand/repl.rs": [
                                     TextEdit {
                                         range: Range {
                                             start: Position {
@@ -83,7 +70,6 @@
                 is_preferred: Some(
                     true,
                 ),
-                data: None,
             },
         ],
     },
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt b/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
index 782c72dbdc..ba1b98b335 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/rustc_wrong_number_of_parameters.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/compiler/ty/select.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/compiler/ty/select.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
@@ -36,14 +29,7 @@
                 [
                     DiagnosticRelatedInformation {
                         location: Location {
-                            uri: Url {
-                                scheme: "file",
-                                host: None,
-                                port: None,
-                                path: "/test/compiler/ty/select.rs",
-                                query: None,
-                                fragment: None,
-                            },
+                            uri: "file:///test/compiler/ty/select.rs",
                             range: Range {
                                 start: Position {
                                     line: 218,
diff --git a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
index d3f27ab6a5..81f752672f 100644
--- a/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
+++ b/crates/rust-analyzer/src/diagnostics/test_data/snap_multi_line_fix.txt
@@ -1,13 +1,6 @@
 [
     MappedRustDiagnostic {
-        url: Url {
-            scheme: "file",
-            host: None,
-            port: None,
-            path: "/test/src/main.rs",
-            query: None,
-            fragment: None,
-        },
+        url: "file:///test/src/main.rs",
         diagnostic: Diagnostic {
             range: Range {
                 start: Position {
@@ -36,14 +29,7 @@
                 [
                     DiagnosticRelatedInformation {
                         location: Location {
-                            uri: Url {
-                                scheme: "file",
-                                host: None,
-                                port: None,
-                                path: "/test/src/main.rs",
-                                query: None,
-                                fragment: None,
-                            },
+                            uri: "file:///test/src/main.rs",
                             range: Range {
                                 start: Position {
                                     line: 2,
@@ -65,6 +51,7 @@
         fixes: [
             CodeAction {
                 title: "return the expression directly",
+                id: None,
                 group: None,
                 kind: Some(
                     CodeActionKind(
@@ -75,14 +62,7 @@
                     SnippetWorkspaceEdit {
                         changes: Some(
                             {
-                                Url {
-                                    scheme: "file",
-                                    host: None,
-                                    port: None,
-                                    path: "/test/src/main.rs",
-                                    query: None,
-                                    fragment: None,
-                                }: [
+                                "file:///test/src/main.rs": [
                                     TextEdit {
                                         range: Range {
                                             start: Position {
@@ -118,7 +98,6 @@
                 is_preferred: Some(
                     true,
                 ),
-                data: None,
             },
         ],
     },
diff --git a/crates/syntax/test_data/parser/ok/0037_mod.rast b/crates/syntax/test_data/parser/ok/0037_mod.rast
index 35577272e5..1d5d94bdea 100644
--- a/crates/syntax/test_data/parser/ok/0037_mod.rast
+++ b/crates/syntax/test_data/parser/ok/0037_mod.rast
@@ -1,9 +1,9 @@
 SOURCE_FILE@0..93
   COMMENT@0..60 "// https://github.com ..."
   WHITESPACE@60..62 "\n\n"
-  COMMENT@62..70 "//! docs"
-  WHITESPACE@70..71 "\n"
-  MODULE@71..93
+  MODULE@62..93
+    COMMENT@62..70 "//! docs"
+    WHITESPACE@70..71 "\n"
     COMMENT@71..82 "// non-docs"
     WHITESPACE@82..83 "\n"
     MOD_KW@83..86 "mod"