Repository: rust-analyzer/rust-analyzer
Pull request #10943 (2021-12-05 14:58:31)

feat: Enable completions for attributes
![2v7heEkq2A](https://user-images.githubusercontent.com/3757771/144752483-5dd868fb-b0f3-4f69-9db3-859f0414dc06.gif)



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 8a0d809a1b..df01cd74fe 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -525,6 +525,7 @@ dependencies = [
  "cfg",
  "cov-mark",
  "either",
+ "expect-test",
  "itertools",
  "la-arena",
  "limit",
@@ -638,10 +639,12 @@ dependencies = [
  "profile",
  "rustc-hash",
  "smallvec",
+ "sourcegen",
  "stdx",
  "syntax",
  "test_utils",
  "text_edit",
+ "xshell",
 ]
 
 [[package]]
@@ -899,6 +902,7 @@ name = "mbe"
 version = "0.0.0"
 dependencies = [
  "cov-mark",
+ "expect-test",
  "parser",
  "rustc-hash",
  "smallvec",
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 4d758c7df7..fe8a8018db 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1716,6 +1716,10 @@ impl MacroDef {
             MacroKind::Attr | MacroKind::Derive => false,
         }
     }
+
+    pub fn is_attr(&self) -> bool {
+        matches!(self.kind(), MacroKind::Attr)
+    }
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
diff --git a/crates/hir_expand/Cargo.toml b/crates/hir_expand/Cargo.toml
index 380b7ef877..f17e1e857b 100644
--- a/crates/hir_expand/Cargo.toml
+++ b/crates/hir_expand/Cargo.toml
@@ -24,3 +24,6 @@ profile = { path = "../profile", version = "0.0.0" }
 tt = { path = "../tt", version = "0.0.0" }
 mbe = { path = "../mbe", version = "0.0.0" }
 limit = { path = "../limit", version = "0.0.0" }
+
+[dev-dependencies]
+expect-test = "1.2.0-pre.1"
diff --git a/crates/ide_completion/Cargo.toml b/crates/ide_completion/Cargo.toml
index f9ce03bce2..8ade88fdd1 100644
--- a/crates/ide_completion/Cargo.toml
+++ b/crates/ide_completion/Cargo.toml
@@ -30,5 +30,7 @@ hir = { path = "../hir", version = "0.0.0" }
 
 [dev-dependencies]
 expect-test = "1.2.0-pre.1"
+xshell = "0.1"
 
 test_utils = { path = "../test_utils" }
+sourcegen = { path = "../sourcegen" }
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index 784a1a0637..446a808de8 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -1,13 +1,14 @@
 //! See [`import_on_the_fly`].
+use hir::ItemInNs;
 use ide_db::helpers::{
-    import_assets::{ImportAssets, ImportCandidate},
+    import_assets::{ImportAssets, ImportCandidate, LocatedImport},
     insert_use::ImportScope,
 };
 use itertools::Itertools;
 use syntax::{AstNode, SyntaxNode, T};
 
 use crate::{
-    context::CompletionContext,
+    context::{CompletionContext, PathKind},
     render::{render_resolution_with_import, RenderContext},
     ImportEdit,
 };
@@ -135,10 +136,35 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
         &ctx.sema,
     )?;
 
+    let ns_filter = |import: &LocatedImport| {
+        let kind = match ctx.path_kind() {
+            Some(kind) => kind,
+            None => {
+                return match import.original_item {
+                    ItemInNs::Macros(mac) => mac.is_fn_like(),
+                    _ => true,
+                }
+            }
+        };
+        match (kind, import.original_item) {
+            (PathKind::Expr, ItemInNs::Types(_) | ItemInNs::Values(_)) => true,
+
+            (PathKind::Type, ItemInNs::Types(_)) => true,
+            (PathKind::Type, ItemInNs::Values(_)) => false,
+
+            (PathKind::Expr | PathKind::Type, ItemInNs::Macros(mac)) => mac.is_fn_like(),
+
+            (PathKind::Attr, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
+            (PathKind::Attr, ItemInNs::Macros(mac)) => mac.is_attr(),
+            (PathKind::Attr, _) => false,
+        }
+    };
+
     acc.add_all(
         import_assets
             .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
             .into_iter()
+            .filter(ns_filter)
             .filter(|import| {
                 !ctx.is_item_hidden(&import.item_to_import)
                     && !ctx.is_item_hidden(&import.original_item)
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs
index a2662d2932..4abf7374b3 100644
--- a/crates/ide_completion/src/completions/qualified_path.rs
+++ b/crates/ide_completion/src/completions/qualified_path.rs
@@ -2,6 +2,7 @@
 
 use std::iter;
 
+use hir::ScopeDef;
 use rustc_hash::FxHashSet;
 use syntax::{ast, AstNode};
 
@@ -31,12 +32,12 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
         Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => {
             if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
                 for (name, def) in module.scope(ctx.db, context_module) {
-                    if let hir::ScopeDef::MacroDef(macro_def) = def {
+                    if let ScopeDef::MacroDef(macro_def) = def {
                         if macro_def.is_fn_like() {
                             acc.add_macro(ctx, Some(name.clone()), macro_def);
                         }
                     }
-                    if let hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = def {
+                    if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = def {
                         acc.add_resolution(ctx, name, &def);
                     }
                 }
@@ -44,22 +45,37 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
             return;
         }
         Some(ImmediateLocation::Visibility(_)) => {
-            if let hir::PathResolution::Def(hir::ModuleDef::Module(resolved)) = resolution {
+            if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
                 if let Some(current_module) = ctx.scope.module() {
                     if let Some(next) = current_module
                         .path_to_root(ctx.db)
                         .into_iter()
-                        .take_while(|&it| it != resolved)
+                        .take_while(|&it| it != module)
                         .next()
                     {
                         if let Some(name) = next.name(ctx.db) {
-                            acc.add_resolution(ctx, name, &hir::ScopeDef::ModuleDef(next.into()));
+                            acc.add_resolution(ctx, name, &ScopeDef::ModuleDef(next.into()));
                         }
                     }
                 }
             }
             return;
         }
+        Some(ImmediateLocation::Attribute(_)) => {
+            if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
+                for (name, def) in module.scope(ctx.db, context_module) {
+                    let add_resolution = match def {
+                        ScopeDef::MacroDef(mac) => mac.is_attr(),
+                        ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
+                        _ => false,
+                    };
+                    if add_resolution {
+                        acc.add_resolution(ctx, name, &def);
+                    }
+                }
+            }
+            return;
+        }
         _ => (),
     }
 
@@ -91,7 +107,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
             let module_scope = module.scope(ctx.db, context_module);
             for (name, def) in module_scope {
                 if ctx.in_use_tree() {
-                    if let hir::ScopeDef::Unknown = def {
+                    if let ScopeDef::Unknown = def {
                         if let Some(ast::NameLike::NameRef(name_ref)) = ctx.name_syntax.as_ref() {
                             if name_ref.syntax().text() == name.to_smol_str().as_str() {
                                 // for `use self::foo$0`, don't suggest `foo` as a completion
@@ -104,16 +120,16 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
 
                 let add_resolution = match def {
                     // Don't suggest attribute macros and derives.
-                    hir::ScopeDef::MacroDef(mac) => mac.is_fn_like(),
+                    ScopeDef::MacroDef(mac) => mac.is_fn_like(),
                     // no values in type places
-                    hir::ScopeDef::ModuleDef(
+                    ScopeDef::ModuleDef(
                         hir::ModuleDef::Function(_)
                         | hir::ModuleDef::Variant(_)
                         | hir::ModuleDef::Static(_),
                     )
-                    | hir::ScopeDef::Local(_) => !ctx.expects_type(),
+                    | ScopeDef::Local(_) => !ctx.expects_type(),
                     // unless its a constant in a generic arg list position
-                    hir::ScopeDef::ModuleDef(hir::ModuleDef::Const(_)) => {
+                    ScopeDef::ModuleDef(hir::ModuleDef::Const(_)) => {
                         !ctx.expects_type() || ctx.expects_generic_arg()
                     }
                     _ => true,
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs
index a84f0b334b..414c1d961b 100644
--- a/crates/ide_completion/src/completions/unqualified_path.rs
+++ b/crates/ide_completion/src/completions/unqualified_path.rs
@@ -56,6 +56,19 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
             });
             return;
         }
+        Some(ImmediateLocation::Attribute(_)) => {
+            ctx.process_all_names(&mut |name, res| {
+                let add_resolution = match res {
+                    ScopeDef::MacroDef(mac) => mac.is_attr(),
+                    ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
+                    _ => false,
+                };
+                if add_resolution {
+                    acc.add_resolution(ctx, name, &res);
+                }
+            });
+            return;
+        }
         _ => (),
     }
 
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index d9ddd9f2ae..b3ce1f8e9c 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -30,10 +30,11 @@ pub(crate) enum PatternRefutability {
     Irrefutable,
 }
 
-#[derive(Debug)]
+#[derive(Copy, Clone, Debug)]
 pub(super) enum PathKind {
     Expr,
     Type,
+    Attr,
 }
 
 #[derive(Debug)]
@@ -232,8 +233,7 @@ impl<'a> CompletionContext<'a> {
     }
 
     pub(crate) fn is_path_disallowed(&self) -> bool {
-        self.attribute_under_caret.is_some()
-            || self.previous_token_is(T![unsafe])
+        self.previous_token_is(T![unsafe])
             || matches!(
                 self.prev_sibling,
                 Some(ImmediatePrevSibling::Attribute | ImmediatePrevSibling::Visibility)
@@ -241,8 +241,7 @@ impl<'a> CompletionContext<'a> {
             || matches!(
                 self.completion_location,
                 Some(
-                    ImmediateLocation::Attribute(_)
-                        | ImmediateLocation::ModDeclaration(_)
+                    ImmediateLocation::ModDeclaration(_)
                         | ImmediateLocation::RecordPat(_)
                         | ImmediateLocation::RecordExpr(_)
                         | ImmediateLocation::Rename
@@ -274,6 +273,10 @@ impl<'a> CompletionContext<'a> {
         self.path_context.as_ref().and_then(|it| it.qualifier.as_ref())
     }
 
+    pub(crate) fn path_kind(&self) -> Option<PathKind> {
+        self.path_context.as_ref().and_then(|it| it.kind)
+    }
+
     /// Checks if an item is visible and not `doc(hidden)` at the completion site.
     pub(crate) fn is_visible<I>(&self, item: &I) -> bool
     where
@@ -785,6 +788,7 @@ impl<'a> CompletionContext<'a> {
                 match parent {
                     ast::PathType(_it) => Some(PathKind::Type),
                     ast::PathExpr(_it) => Some(PathKind::Expr),
+                    ast::Meta(_it) => Some(PathKind::Attr),
                     _ => None,
                 }
             };
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs
index 090bb5a715..22fb1f4825 100644
--- a/crates/ide_completion/src/render/macro_.rs
+++ b/crates/ide_completion/src/render/macro_.rs
@@ -1,8 +1,12 @@
 //! Renderer for macro invocations.
 
+use either::Either;
 use hir::HasSource;
 use ide_db::SymbolKind;
-use syntax::{display::macro_label, SmolStr};
+use syntax::{
+    display::{fn_as_proc_macro_label, macro_label},
+    SmolStr,
+};
 
 use crate::{
     context::CallKind,
@@ -35,7 +39,8 @@ impl<'a> MacroRender<'a> {
         let name = name.to_smol_str();
         let docs = ctx.docs(macro_);
         let docs_str = docs.as_ref().map_or("", |s| s.as_str());
-        let (bra, ket) = guess_macro_braces(&name, docs_str);
+        let (bra, ket) =
+            if macro_.is_fn_like() { guess_macro_braces(&name, docs_str) } else { ("", "") };
 
         MacroRender { ctx, name, macro_, docs, bra, ket }
     }
@@ -47,15 +52,23 @@ impl<'a> MacroRender<'a> {
         } else {
             Some(self.ctx.source_range())
         }?;
-        let mut item = CompletionItem::new(SymbolKind::Macro, source_range, self.label());
+        let kind = match self.macro_.kind() {
+            hir::MacroKind::Derive => SymbolKind::Derive,
+            hir::MacroKind::Attr => SymbolKind::Attribute,
+            hir::MacroKind::BuiltIn | hir::MacroKind::Declarative | hir::MacroKind::ProcMacro => {
+                SymbolKind::Macro
+            }
+        };
+        let mut item = CompletionItem::new(kind, source_range, self.label());
         item.set_deprecated(self.ctx.is_deprecated(self.macro_)).set_detail(self.detail());
 
         if let Some(import_to_add) = import_to_add {
             item.add_import(import_to_add);
         }
 
-        let needs_bang = !(self.ctx.completion.in_use_tree()
-            || matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac)));
+        let needs_bang = self.macro_.is_fn_like()
+            && !(self.ctx.completion.in_use_tree()
+                || matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac)));
         let has_parens = self.ctx.completion.path_call_kind().is_some();
 
         match self.ctx.snippet_cap() {
@@ -84,10 +97,10 @@ impl<'a> MacroRender<'a> {
     }
 
     fn label(&self) -> SmolStr {
-        if self.needs_bang() && self.ctx.snippet_cap().is_some() {
-            SmolStr::from_iter([&*self.name, "!", self.bra, "…", self.ket])
-        } else if self.macro_.kind() == hir::MacroKind::Derive {
+        if !self.macro_.is_fn_like() {
             self.name.clone()
+        } else if self.needs_bang() && self.ctx.snippet_cap().is_some() {
+            SmolStr::from_iter([&*self.name, "!", self.bra, "…", self.ket])
         } else {
             self.banged_name()
         }
@@ -98,8 +111,11 @@ impl<'a> MacroRender<'a> {
     }
 
     fn detail(&self) -> Option<String> {
-        let ast_node = self.macro_.source(self.ctx.db())?.value.left()?;
-        Some(macro_label(&ast_node))
+        let detail = match self.macro_.source(self.ctx.db())?.value {
+            Either::Left(node) => macro_label(&node),
+            Either::Right(node) => fn_as_proc_macro_label(&node),
+        };
+        Some(detail)
     }
 }
 
diff --git a/crates/mbe/Cargo.toml b/crates/mbe/Cargo.toml
index 651ca409c6..cff7224e02 100644
--- a/crates/mbe/Cargo.toml
+++ b/crates/mbe/Cargo.toml
@@ -13,6 +13,7 @@ doctest = false
 cov-mark = "2.0.0-pre.1"
 rustc-hash = "1.1.0"
 smallvec = "1.2.0"
+expect-test = "1.2.0-pre.1"
 tracing = "0.1"
 
 syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 0863c40716..4ad91e8da8 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -22,6 +22,7 @@ anyhow = "1.0.26"
 crossbeam-channel = "0.5.0"
 dissimilar = "1.0.2"
 itertools = "0.10.0"
+jod-thread = "0.1.0"
 lsp-types = { version = "0.91", features = ["proposed"] }
 parking_lot = "0.11.0"
 xflags = "0.2.1"
@@ -73,7 +74,6 @@ jemallocator = { version = "0.4.1", package = "tikv-jemallocator", optional = tr
 
 [dev-dependencies]
 expect-test = "1.2.0-pre.1"
-jod-thread = "0.1.0"
 xshell = "0.1"
 
 test_utils = { path = "../test_utils" }
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index 56b3afce98..2f63c26ce5 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -29,8 +29,7 @@ pub(crate) struct DiagnosticCollection {
 
 #[derive(Debug, Clone)]
 pub(crate) struct Fix {
-    // Fixes may be triggerable from multiple ranges.
-    pub(crate) ranges: Vec<lsp_types::Range>,
+    pub(crate) range: lsp_types::Range,
     pub(crate) action: lsp_ext::CodeAction,
 }
 
@@ -44,7 +43,7 @@ impl DiagnosticCollection {
         &mut self,
         file_id: FileId,
         diagnostic: lsp_types::Diagnostic,
-        fix: Option<Fix>,
+        fixes: Vec<lsp_ext::CodeAction>,
     ) {
         let diagnostics = self.check.entry(file_id).or_default();
         for existing_diagnostic in diagnostics.iter() {
@@ -54,7 +53,10 @@ impl DiagnosticCollection {
         }
 
         let check_fixes = Arc::make_mut(&mut self.check_fixes);
-        check_fixes.entry(file_id).or_default().extend(fix);
+        check_fixes
+            .entry(file_id)
+            .or_default()
+            .extend(fixes.into_iter().map(|action| Fix { range: diagnostic.range, action }));
         diagnostics.push(diagnostic);
         self.changes.insert(file_id);
     }
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 5f936ee086..bdc56b3196 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -9,7 +9,7 @@ use vfs::{AbsPath, AbsPathBuf};
 
 use crate::{lsp_ext, to_proto::url_from_abs_path};
 
-use super::{DiagnosticsMapConfig, Fix};
+use super::DiagnosticsMapConfig;
 
 /// Determines the LSP severity from a diagnostic
 fn diagnostic_severity(
@@ -124,7 +124,7 @@ fn resolve_path(
 
 struct SubDiagnostic {
     related: lsp_types::DiagnosticRelatedInformation,
-    suggested_fix: Option<Fix>,
+    suggested_fix: Option<lsp_ext::CodeAction>,
 }
 
 enum MappedRustChildDiagnostic {
@@ -181,24 +181,18 @@ fn map_rust_child_diagnostic(
                 location: location(config, workspace_root, spans[0]),
                 message: message.clone(),
             },
-            suggested_fix: Some(Fix {
-                ranges: spans
-                    .iter()
-                    .map(|&span| location(config, workspace_root, span).range)
-                    .collect(),
-                action: lsp_ext::CodeAction {
-                    title: message,
-                    group: None,
-                    kind: Some(lsp_types::CodeActionKind::QUICKFIX),
-                    edit: Some(lsp_ext::SnippetWorkspaceEdit {
-                        // FIXME: there's no good reason to use edit_map here....
-                        changes: Some(edit_map),
-                        document_changes: None,
-                        change_annotations: None,
-                    }),
-                    is_preferred: Some(true),
-                    data: None,
-                },
+            suggested_fix: Some(lsp_ext::CodeAction {
+                title: message,
+                group: None,
+                kind: Some(lsp_types::CodeActionKind::QUICKFIX),
+                edit: Some(lsp_ext::SnippetWorkspaceEdit {
+                    // FIXME: there's no good reason to use edit_map here....
+                    changes: Some(edit_map),
+                    document_changes: None,
+                    change_annotations: None,
+                }),
+                is_preferred: Some(true),
+                data: None,
             }),
         })
     }
@@ -208,7 +202,7 @@ fn map_rust_child_diagnostic(
 pub(crate) struct MappedRustDiagnostic {
     pub(crate) url: lsp_types::Url,
     pub(crate) diagnostic: lsp_types::Diagnostic,
-    pub(crate) fix: Option<Fix>,
+    pub(crate) fixes: Vec<lsp_ext::CodeAction>,
 }
 
 /// Converts a Rust root diagnostic to LSP form
@@ -365,7 +359,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
                 diagnostics.push(MappedRustDiagnostic {
                     url: secondary_location.uri,
                     diagnostic,
-                    fix: None,
+                    fixes: Vec::new(),
                 });
             }
 
@@ -394,7 +388,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
                     tags: if tags.is_empty() { None } else { Some(tags.clone()) },
                     data: None,
                 },
-                fix: None,
+                fixes: Vec::new(),
             });
 
             // Emit hint-level diagnostics for all `related_information` entries such as "help"s.
@@ -411,7 +405,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
                 }
                 diagnostics.push(MappedRustDiagnostic {
                     url: sub.related.location.uri.clone(),
-                    fix: sub.suggested_fix.clone(),
+                    fixes: sub.suggested_fix.iter().cloned().collect(),
                     diagnostic: lsp_types::Diagnostic {
                         range: sub.related.location.range,
                         severity: Some(lsp_types::DiagnosticSeverity::HINT),
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index bf153012d9..0ddddeca67 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -1081,13 +1081,8 @@ pub(crate) fn handle_code_action(
     for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() {
         // FIXME: this mapping is awkward and shouldn't exist. Refactor
         // `snap.check_fixes` to not convert to LSP prematurely.
-        let intersect_fix_range = fix
-            .ranges
-            .iter()
-            .copied()
-            .map(|range| from_proto::text_range(&line_index, range))
-            .any(|fix_range| fix_range.intersect(frange.range).is_some());
-        if intersect_fix_range {
+        let fix_range = from_proto::text_range(&line_index, fix.range);
+        if fix_range.intersect(frange.range).is_some() {
             res.push(fix.action.clone());
         }
     }
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 2d83cbee66..979b3b1522 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -370,7 +370,7 @@ impl GlobalState {
                                     Ok(file_id) => self.diagnostics.add_check_diagnostic(
                                         file_id,
                                         diag.diagnostic,
-                                        diag.fix,
+                                        diag.fixes,
                                     ),
                                     Err(err) => {
                                         tracing::error!(
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 686844132c..e260062167 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -9,7 +9,7 @@
             "version": "0.4.0-dev",
             "license": "MIT OR Apache-2.0",
             "dependencies": {
-                "d3": "^7.2.0",
+                "d3": "^7.1.0",
                 "d3-graphviz": "^4.0.0",
                 "https-proxy-agent": "^5.0.0",
                 "node-fetch": "^3.0.3",
@@ -792,9 +792,9 @@
             }
         },
         "node_modules/d3": {
-            "version": "7.2.0",
-            "resolved": "https://registry.npmjs.org/d3/-/d3-7.2.0.tgz",
-            "integrity": "sha512-0bh5lPzD6R/I/SjkcgyQlMaIsrZDgh40yMkdJaiZ/58YEbh4zz5EvtjaudzmI6ruwQNvxGm00trYUyPa992NSQ==",
+            "version": "7.1.1",
+            "resolved": "https://registry.npmjs.org/d3/-/d3-7.1.1.tgz",
+            "integrity": "sha512-8zkLMwSvUAnfN9pcJDfkuxU0Nvg4RLUD0A4BZN1KxJPtlnCGzMx3xM5cRl4m8fym/Vy8rlq52tl90UF3m91OnA==",
             "dependencies": {
                 "d3-array": "3",
                 "d3-axis": "3",
@@ -4503,9 +4503,9 @@
             "dev": true
         },
         "d3": {
-            "version": "7.2.0",
-            "resolved": "https://registry.npmjs.org/d3/-/d3-7.2.0.tgz",
-            "integrity": "sha512-0bh5lPzD6R/I/SjkcgyQlMaIsrZDgh40yMkdJaiZ/58YEbh4zz5EvtjaudzmI6ruwQNvxGm00trYUyPa992NSQ==",
+            "version": "7.1.1",
+            "resolved": "https://registry.npmjs.org/d3/-/d3-7.1.1.tgz",
+            "integrity": "sha512-8zkLMwSvUAnfN9pcJDfkuxU0Nvg4RLUD0A4BZN1KxJPtlnCGzMx3xM5cRl4m8fym/Vy8rlq52tl90UF3m91OnA==",
             "requires": {
                 "d3-array": "3",
                 "d3-axis": "3",
diff --git a/editors/code/package.json b/editors/code/package.json
index da4fa15b02..26f21f73d4 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -39,7 +39,7 @@
         "https-proxy-agent": "^5.0.0",
         "node-fetch": "^3.0.3",
         "vscode-languageclient": "8.0.0-next.2",
-        "d3": "^7.2.0",
+        "d3": "^7.1.0",
         "d3-graphviz": "^4.0.0"
     },
     "devDependencies": {
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index bd1bb5f8a8..c295716add 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -9,6 +9,7 @@ import * as zlib from "zlib";
 import * as util from "util";
 import * as path from "path";
 import { log, assert } from "./util";
+import * as url from "url";
 import * as https from "https";
 import { ProxySettings } from "./config";
 
@@ -20,7 +21,7 @@ const REPO = "rust-analyzer";
 
 function makeHttpAgent(proxy: string | null | undefined, options?: https.AgentOptions) {
     if (proxy) {
-        return new HttpsProxyAgent({ ...options, ...new URL(proxy) });
+        return new HttpsProxyAgent(proxy, { ...options, ...url.parse(proxy) });
     } else {
         return new https.Agent(options);
     }


Test patch:
diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs
index 8141fab299..c3dce61e7d 100644
--- a/crates/ide_completion/src/tests/attribute.rs
+++ b/crates/ide_completion/src/tests/attribute.rs
@@ -9,12 +9,12 @@ fn check(ra_fixture: &str, expect: Expect) {
 }
 
 #[test]
-fn doesnt_complete_items() {
+fn proc_macros() {
     check(
         r#"
-struct Foo;
+//- proc_macros: identity
 #[$0]
-use self as this;
+struct Foo;
 "#,
         expect![[r#"
             at allow(…)
@@ -29,19 +29,29 @@ use self as this;
             at doc(alias = "…")
             at must_use
             at no_mangle
+            at derive(…)
+            at repr(…)
+            at non_exhaustive
+            kw self
+            kw super
+            kw crate
+            md proc_macros
         "#]],
     )
 }
 
 #[test]
-fn doesnt_complete_qualified() {
+fn proc_macros_qualified() {
     check(
         r#"
+//- proc_macros: identity
+#[proc_macros::$0]
 struct Foo;
-#[foo::$0]
-use self as this;
 "#,
-        expect![[r#""#]],
+        expect![[r#"
+            at input_replace pub macro input_replace
+            at identity      pub macro identity
+        "#]],
     )
 }
 
@@ -61,6 +71,9 @@ fn with_existing_attr() {
             at deny(…)
             at forbid(…)
             at warn(…)
+            kw self
+            kw super
+            kw crate
         "#]],
     )
 }
@@ -90,6 +103,9 @@ fn attr_on_source_file() {
             at recursion_limit = "…"
             at type_length_limit = …
             at windows_subsystem = "…"
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -113,6 +129,9 @@ fn attr_on_module() {
             at no_mangle
             at macro_use
             at path = "…"
+            kw self
+            kw super
+            kw crate
         "#]],
     );
     check(
@@ -131,6 +150,9 @@ fn attr_on_module() {
             at must_use
             at no_mangle
             at no_implicit_prelude
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -154,6 +176,9 @@ fn attr_on_macro_rules() {
             at no_mangle
             at macro_export
             at macro_use
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -175,6 +200,9 @@ fn attr_on_macro_def() {
             at doc(alias = "…")
             at must_use
             at no_mangle
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -197,6 +225,9 @@ fn attr_on_extern_crate() {
             at must_use
             at no_mangle
             at macro_use
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -218,6 +249,9 @@ fn attr_on_use() {
             at doc(alias = "…")
             at must_use
             at no_mangle
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -239,6 +273,9 @@ fn attr_on_type_alias() {
             at doc(alias = "…")
             at must_use
             at no_mangle
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -263,6 +300,9 @@ fn attr_on_struct() {
             at derive(…)
             at repr(…)
             at non_exhaustive
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -287,6 +327,9 @@ fn attr_on_enum() {
             at derive(…)
             at repr(…)
             at non_exhaustive
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -308,6 +351,9 @@ fn attr_on_const() {
             at doc(alias = "…")
             at must_use
             at no_mangle
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -334,6 +380,9 @@ fn attr_on_static() {
             at link_section = "…"
             at global_allocator
             at used
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -356,6 +405,9 @@ fn attr_on_trait() {
             at must_use
             at no_mangle
             at must_use
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -378,6 +430,9 @@ fn attr_on_impl() {
             at must_use
             at no_mangle
             at automatically_derived
+            kw self
+            kw super
+            kw crate
         "#]],
     );
     check(
@@ -395,6 +450,9 @@ fn attr_on_impl() {
             at doc(alias = "…")
             at must_use
             at no_mangle
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -417,6 +475,9 @@ fn attr_on_extern_block() {
             at must_use
             at no_mangle
             at link
+            kw self
+            kw super
+            kw crate
         "#]],
     );
     check(
@@ -435,6 +496,9 @@ fn attr_on_extern_block() {
             at must_use
             at no_mangle
             at link
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -451,6 +515,9 @@ fn attr_on_variant() {
             at forbid(…)
             at warn(…)
             at non_exhaustive
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -487,6 +554,9 @@ fn attr_on_fn() {
             at target_feature = "…"
             at test
             at track_caller
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -503,6 +573,9 @@ fn attr_on_expr() {
             at deny(…)
             at forbid(…)
             at warn(…)
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
@@ -548,6 +621,9 @@ fn attr_in_source_file_end() {
             at track_caller
             at used
             at warn(…)
+            kw self
+            kw super
+            kw crate
         "#]],
     );
 }
diff --git a/crates/ide_completion/src/tests/flyimport.rs b/crates/ide_completion/src/tests/flyimport.rs
index 18880a67aa..ff46dda5e2 100644
--- a/crates/ide_completion/src/tests/flyimport.rs
+++ b/crates/ide_completion/src/tests/flyimport.rs
@@ -1043,3 +1043,31 @@ enum Foo {
         "#]],
     )
 }
+
+#[test]
+fn flyimport_attribute() {
+    check(
+        r#"
+//- proc_macros:identity
+#[ide$0]
+struct Foo;
+"#,
+        expect![[r#"
+            at identity (use proc_macros::identity) pub macro identity
+        "#]],
+    );
+    check_edit(
+        "identity",
+        r#"
+//- proc_macros:identity
+#[ide$0]
+struct Foo;
+"#,
+        r#"
+use proc_macros::identity;
+
+#[identity]
+struct Foo;
+"#,
+    );
+}
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 4610984acb..fd04163de9 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
@@ -114,7 +114,7 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -205,7 +205,7 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -296,69 +296,55 @@
             tags: None,
             data: None,
         },
-        fix: Some(
-            Fix {
-                ranges: [
-                    Range {
-                        start: Position {
-                            line: 41,
-                            character: 23,
-                        },
-                        end: Position {
-                            line: 41,
-                            character: 28,
-                        },
-                    },
-                ],
-                action: CodeAction {
-                    title: "consider passing by value instead: `self`",
-                    group: None,
-                    kind: Some(
-                        CodeActionKind(
-                            "quickfix",
-                        ),
+        fixes: [
+            CodeAction {
+                title: "consider passing by value instead: `self`",
+                group: None,
+                kind: Some(
+                    CodeActionKind(
+                        "quickfix",
                     ),
-                    edit: Some(
-                        SnippetWorkspaceEdit {
-                            changes: Some(
-                                {
-                                    Url {
-                                        scheme: "file",
-                                        cannot_be_a_base: false,
-                                        username: "",
-                                        password: None,
-                                        host: None,
-                                        port: None,
-                                        path: "/test/compiler/mir/tagset.rs",
-                                        query: None,
-                                        fragment: None,
-                                    }: [
-                                        TextEdit {
-                                            range: Range {
-                                                start: Position {
-                                                    line: 41,
-                                                    character: 23,
-                                                },
-                                                end: Position {
-                                                    line: 41,
-                                                    character: 28,
-                                                },
+                ),
+                edit: Some(
+                    SnippetWorkspaceEdit {
+                        changes: Some(
+                            {
+                                Url {
+                                    scheme: "file",
+                                    cannot_be_a_base: false,
+                                    username: "",
+                                    password: None,
+                                    host: None,
+                                    port: None,
+                                    path: "/test/compiler/mir/tagset.rs",
+                                    query: None,
+                                    fragment: None,
+                                }: [
+                                    TextEdit {
+                                        range: Range {
+                                            start: Position {
+                                                line: 41,
+                                                character: 23,
+                                            },
+                                            end: Position {
+                                                line: 41,
+                                                character: 28,
                                             },
-                                            new_text: "self",
                                         },
-                                    ],
-                                },
-                            ),
-                            document_changes: None,
-                            change_annotations: None,
-                        },
-                    ),
-                    is_preferred: Some(
-                        true,
-                    ),
-                    data: None,
-                },
+                                        new_text: "self",
+                                    },
+                                ],
+                            },
+                        ),
+                        document_changes: None,
+                        change_annotations: None,
+                    },
+                ),
+                is_preferred: Some(
+                    true,
+                ),
+                data: None,
             },
-        ),
+        ],
     },
 ]
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 989e5cf66d..a7f936a703 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
@@ -59,6 +59,6 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
 ]
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 fe5cf9b3be..8bee4cfe10 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
@@ -64,7 +64,7 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -131,7 +131,7 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -224,6 +224,6 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
 ]
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 dc36aa761c..afc562a0e2 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
@@ -59,6 +59,6 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
 ]
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 d557196c2b..05074a914b 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
@@ -59,6 +59,6 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
 ]
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 46d44192c5..3ded70411d 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
@@ -72,7 +72,7 @@
             ),
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -143,69 +143,55 @@
             tags: None,
             data: None,
         },
-        fix: Some(
-            Fix {
-                ranges: [
-                    Range {
-                        start: Position {
-                            line: 290,
-                            character: 8,
-                        },
-                        end: Position {
-                            line: 290,
-                            character: 11,
-                        },
-                    },
-                ],
-                action: CodeAction {
-                    title: "consider prefixing with an underscore: `_foo`",
-                    group: None,
-                    kind: Some(
-                        CodeActionKind(
-                            "quickfix",
-                        ),
+        fixes: [
+            CodeAction {
+                title: "consider prefixing with an underscore: `_foo`",
+                group: None,
+                kind: Some(
+                    CodeActionKind(
+                        "quickfix",
                     ),
-                    edit: Some(
-                        SnippetWorkspaceEdit {
-                            changes: Some(
-                                {
-                                    Url {
-                                        scheme: "file",
-                                        cannot_be_a_base: false,
-                                        username: "",
-                                        password: None,
-                                        host: None,
-                                        port: None,
-                                        path: "/test/driver/subcommand/repl.rs",
-                                        query: None,
-                                        fragment: None,
-                                    }: [
-                                        TextEdit {
-                                            range: Range {
-                                                start: Position {
-                                                    line: 290,
-                                                    character: 8,
-                                                },
-                                                end: Position {
-                                                    line: 290,
-                                                    character: 11,
-                                                },
+                ),
+                edit: Some(
+                    SnippetWorkspaceEdit {
+                        changes: Some(
+                            {
+                                Url {
+                                    scheme: "file",
+                                    cannot_be_a_base: false,
+                                    username: "",
+                                    password: None,
+                                    host: None,
+                                    port: None,
+                                    path: "/test/driver/subcommand/repl.rs",
+                                    query: None,
+                                    fragment: None,
+                                }: [
+                                    TextEdit {
+                                        range: Range {
+                                            start: Position {
+                                                line: 290,
+                                                character: 8,
+                                            },
+                                            end: Position {
+                                                line: 290,
+                                                character: 11,
                                             },
-                                            new_text: "_foo",
                                         },
-                                    ],
-                                },
-                            ),
-                            document_changes: None,
-                            change_annotations: None,
-                        },
-                    ),
-                    is_preferred: Some(
-                        true,
-                    ),
-                    data: None,
-                },
+                                        new_text: "_foo",
+                                    },
+                                ],
+                            },
+                        ),
+                        document_changes: None,
+                        change_annotations: None,
+                    },
+                ),
+                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 b595487e8f..0993aa1af1 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
@@ -72,7 +72,7 @@
             ),
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -143,69 +143,55 @@
             tags: None,
             data: None,
         },
-        fix: Some(
-            Fix {
-                ranges: [
-                    Range {
-                        start: Position {
-                            line: 290,
-                            character: 8,
-                        },
-                        end: Position {
-                            line: 290,
-                            character: 11,
-                        },
-                    },
-                ],
-                action: CodeAction {
-                    title: "consider prefixing with an underscore: `_foo`",
-                    group: None,
-                    kind: Some(
-                        CodeActionKind(
-                            "quickfix",
-                        ),
+        fixes: [
+            CodeAction {
+                title: "consider prefixing with an underscore: `_foo`",
+                group: None,
+                kind: Some(
+                    CodeActionKind(
+                        "quickfix",
                     ),
-                    edit: Some(
-                        SnippetWorkspaceEdit {
-                            changes: Some(
-                                {
-                                    Url {
-                                        scheme: "file",
-                                        cannot_be_a_base: false,
-                                        username: "",
-                                        password: None,
-                                        host: None,
-                                        port: None,
-                                        path: "/test/driver/subcommand/repl.rs",
-                                        query: None,
-                                        fragment: None,
-                                    }: [
-                                        TextEdit {
-                                            range: Range {
-                                                start: Position {
-                                                    line: 290,
-                                                    character: 8,
-                                                },
-                                                end: Position {
-                                                    line: 290,
-                                                    character: 11,
-                                                },
+                ),
+                edit: Some(
+                    SnippetWorkspaceEdit {
+                        changes: Some(
+                            {
+                                Url {
+                                    scheme: "file",
+                                    cannot_be_a_base: false,
+                                    username: "",
+                                    password: None,
+                                    host: None,
+                                    port: None,
+                                    path: "/test/driver/subcommand/repl.rs",
+                                    query: None,
+                                    fragment: None,
+                                }: [
+                                    TextEdit {
+                                        range: Range {
+                                            start: Position {
+                                                line: 290,
+                                                character: 8,
+                                            },
+                                            end: Position {
+                                                line: 290,
+                                                character: 11,
                                             },
-                                            new_text: "_foo",
                                         },
-                                    ],
-                                },
-                            ),
-                            document_changes: None,
-                            change_annotations: None,
-                        },
-                    ),
-                    is_preferred: Some(
-                        true,
-                    ),
-                    data: None,
-                },
+                                        new_text: "_foo",
+                                    },
+                                ],
+                            },
+                        ),
+                        document_changes: None,
+                        change_annotations: None,
+                    },
+                ),
+                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 3ee50392a7..d7a974ae57 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
@@ -72,7 +72,7 @@
             ),
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -143,69 +143,55 @@
             tags: None,
             data: None,
         },
-        fix: Some(
-            Fix {
-                ranges: [
-                    Range {
-                        start: Position {
-                            line: 290,
-                            character: 8,
-                        },
-                        end: Position {
-                            line: 290,
-                            character: 11,
-                        },
-                    },
-                ],
-                action: CodeAction {
-                    title: "consider prefixing with an underscore: `_foo`",
-                    group: None,
-                    kind: Some(
-                        CodeActionKind(
-                            "quickfix",
-                        ),
+        fixes: [
+            CodeAction {
+                title: "consider prefixing with an underscore: `_foo`",
+                group: None,
+                kind: Some(
+                    CodeActionKind(
+                        "quickfix",
                     ),
-                    edit: Some(
-                        SnippetWorkspaceEdit {
-                            changes: Some(
-                                {
-                                    Url {
-                                        scheme: "file",
-                                        cannot_be_a_base: false,
-                                        username: "",
-                                        password: None,
-                                        host: None,
-                                        port: None,
-                                        path: "/test/driver/subcommand/repl.rs",
-                                        query: None,
-                                        fragment: None,
-                                    }: [
-                                        TextEdit {
-                                            range: Range {
-                                                start: Position {
-                                                    line: 290,
-                                                    character: 8,
-                                                },
-                                                end: Position {
-                                                    line: 290,
-                                                    character: 11,
-                                                },
+                ),
+                edit: Some(
+                    SnippetWorkspaceEdit {
+                        changes: Some(
+                            {
+                                Url {
+                                    scheme: "file",
+                                    cannot_be_a_base: false,
+                                    username: "",
+                                    password: None,
+                                    host: None,
+                                    port: None,
+                                    path: "/test/driver/subcommand/repl.rs",
+                                    query: None,
+                                    fragment: None,
+                                }: [
+                                    TextEdit {
+                                        range: Range {
+                                            start: Position {
+                                                line: 290,
+                                                character: 8,
+                                            },
+                                            end: Position {
+                                                line: 290,
+                                                character: 11,
                                             },
-                                            new_text: "_foo",
                                         },
-                                    ],
-                                },
-                            ),
-                            document_changes: None,
-                            change_annotations: None,
-                        },
-                    ),
-                    is_preferred: Some(
-                        true,
-                    ),
-                    data: None,
-                },
+                                        new_text: "_foo",
+                                    },
+                                ],
+                            },
+                        ),
+                        document_changes: None,
+                        change_annotations: None,
+                    },
+                ),
+                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 8ec92888ce..5ea27a1527 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
@@ -88,7 +88,7 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -179,6 +179,6 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
 ]
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 d7987f65ed..f77abde515 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
@@ -114,7 +114,7 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -205,7 +205,7 @@
             tags: None,
             data: None,
         },
-        fix: None,
+        fixes: [],
     },
     MappedRustDiagnostic {
         url: Url {
@@ -296,92 +296,68 @@
             tags: None,
             data: None,
         },
-        fix: Some(
-            Fix {
-                ranges: [
-                    Range {
-                        start: Position {
-                            line: 2,
-                            character: 4,
-                        },
-                        end: Position {
-                            line: 2,
-                            character: 30,
-                        },
-                    },
-                    Range {
-                        start: Position {
-                            line: 3,
-                            character: 4,
-                        },
-                        end: Position {
-                            line: 3,
-                            character: 5,
-                        },
-                    },
-                ],
-                action: CodeAction {
-                    title: "return the expression directly: `(0..10).collect()`",
-                    group: None,
-                    kind: Some(
-                        CodeActionKind(
-                            "quickfix",
-                        ),
+        fixes: [
+            CodeAction {
+                title: "return the expression directly: `(0..10).collect()`",
+                group: None,
+                kind: Some(
+                    CodeActionKind(
+                        "quickfix",
                     ),
-                    edit: Some(
-                        SnippetWorkspaceEdit {
-                            changes: Some(
-                                {
-                                    Url {
-                                        scheme: "file",
-                                        cannot_be_a_base: false,
-                                        username: "",
-                                        password: None,
-                                        host: None,
-                                        port: None,
-                                        path: "/test/src/main.rs",
-                                        query: None,
-                                        fragment: None,
-                                    }: [
-                                        TextEdit {
-                                            range: Range {
-                                                start: Position {
-                                                    line: 2,
-                                                    character: 4,
-                                                },
-                                                end: Position {
-                                                    line: 2,
-                                                    character: 30,
-                                                },
+                ),
+                edit: Some(
+                    SnippetWorkspaceEdit {
+                        changes: Some(
+                            {
+                                Url {
+                                    scheme: "file",
+                                    cannot_be_a_base: false,
+                                    username: "",
+                                    password: None,
+                                    host: None,
+                                    port: None,
+                                    path: "/test/src/main.rs",
+                                    query: None,
+                                    fragment: None,
+                                }: [
+                                    TextEdit {
+                                        range: Range {
+                                            start: Position {
+                                                line: 2,
+                                                character: 4,
+                                            },
+                                            end: Position {
+                                                line: 2,
+                                                character: 30,
                                             },
-                                            new_text: "",
                                         },
-                                        TextEdit {
-                                            range: Range {
-                                                start: Position {
-                                                    line: 3,
-                                                    character: 4,
-                                                },
-                                                end: Position {
-                                                    line: 3,
-                                                    character: 5,
-                                                },
+                                        new_text: "",
+                                    },
+                                    TextEdit {
+                                        range: Range {
+                                            start: Position {
+                                                line: 3,
+                                                character: 4,
+                                            },
+                                            end: Position {
+                                                line: 3,
+                                                character: 5,
                                             },
-                                            new_text: "(0..10).collect()",
                                         },
-                                    ],
-                                },
-                            ),
-                            document_changes: None,
-                            change_annotations: None,
-                        },
-                    ),
-                    is_preferred: Some(
-                        true,
-                    ),
-                    data: None,
-                },
+                                        new_text: "(0..10).collect()",
+                                    },
+                                ],
+                            },
+                        ),
+                        document_changes: None,
+                        change_annotations: None,
+                    },
+                ),
+                is_preferred: Some(
+                    true,
+                ),
+                data: None,
             },
-        ),
+        ],
     },
 ]