Repository: rust-analyzer/rust-analyzer
Pull request #6673 (2020-11-29 23:57:53)

Show type of Self on hover


Patch:
diff --git a/crates/completion/src/completions/postfix.rs b/crates/completion/src/completions/postfix.rs
index 1785794cc5..7fbda7a6bd 100644
--- a/crates/completion/src/completions/postfix.rs
+++ b/crates/completion/src/completions/postfix.rs
@@ -5,7 +5,6 @@ mod format_like;
 use ide_db::ty_filter::TryEnum;
 use syntax::{
     ast::{self, AstNode, AstToken},
-    SyntaxKind::BLOCK_EXPR,
     TextRange, TextSize,
 };
 use text_edit::TextEdit;
@@ -221,30 +220,6 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
     )
     .add_to(acc);
 
-    let parent_node = dot_receiver.syntax().parent().and_then(|p| p.parent());
-    if let Some(parent) = parent_node {
-        if parent.kind() == BLOCK_EXPR {
-            postfix_snippet(
-                ctx,
-                cap,
-                &dot_receiver,
-                "let",
-                "let",
-                &format!("let $0 = {};", receiver_text),
-            )
-            .add_to(acc);
-            postfix_snippet(
-                ctx,
-                cap,
-                &dot_receiver,
-                "letm",
-                "let mut",
-                &format!("let mut $0 = {};", receiver_text),
-            )
-            .add_to(acc);
-        }
-    }
-
     if let ast::Expr::Literal(literal) = dot_receiver.clone() {
         if let Some(literal_text) = ast::String::cast(literal.token()) {
             add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
@@ -314,38 +289,6 @@ fn main() {
     let bar = true;
     bar.<|>
 }
-"#,
-            expect![[r#"
-                sn box   Box::new(expr)
-                sn call  function(expr)
-                sn dbg   dbg!(expr)
-                sn dbgr  dbg!(&expr)
-                sn if    if expr {}
-                sn let   let
-                sn letm  let mut
-                sn match match expr {}
-                sn not   !expr
-                sn ok    Ok(expr)
-                sn ref   &expr
-                sn refm  &mut expr
-                sn some  Some(expr)
-                sn while while expr {}
-            "#]],
-        );
-    }
-
-    #[test]
-    fn postfix_completion_works_for_function_calln() {
-        check(
-            r#"
-fn foo(elt: bool) -> bool {
-    !elt
-}
-
-fn main() {
-    let bar = true;
-    foo(bar.<|>)
-}
 "#,
             expect![[r#"
                 sn box   Box::new(expr)
@@ -378,8 +321,6 @@ fn main() {
                 sn call  function(expr)
                 sn dbg   dbg!(expr)
                 sn dbgr  dbg!(&expr)
-                sn let   let
-                sn letm  let mut
                 sn match match expr {}
                 sn ok    Ok(expr)
                 sn ref   &expr
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs
index 1ec2e9be72..aecc1378b1 100644
--- a/crates/completion/src/lib.rs
+++ b/crates/completion/src/lib.rs
@@ -44,8 +44,6 @@ pub use crate::{
 // - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
 // - `expr.ref` -> `&expr`
 // - `expr.refm` -> `&mut expr`
-// - `expr.let` -> `let <|> = expr;`
-// - `expr.letm` -> `let mut <|> = expr;`
 // - `expr.not` -> `!expr`
 // - `expr.dbg` -> `dbg!(expr)`
 // - `expr.dbgr` -> `dbg!(&expr)`
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 94ad800a08..462f5c2b8b 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -295,7 +295,6 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
             ModuleDef::EnumVariant(e) => Some(e.parent_enum(db).name(db)),
             _ => None,
         },
-        Definition::SelfType(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
         _ => None,
     }
     .map(|name| name.to_string())
@@ -357,7 +356,14 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
             ModuleDef::BuiltinType(it) => return Some(it.to_string().into()),
         },
         Definition::Local(it) => return Some(Markup::fenced_block(&it.ty(db).display(db))),
-        Definition::TypeParam(_) | Definition::SelfType(_) => {
+        Definition::SelfType(impl_def) => {
+            impl_def.target_ty(db).as_adt().and_then(|adt| match adt {
+                Adt::Struct(it) => from_def_source(db, it, mod_path),
+                Adt::Union(it) => from_def_source(db, it, mod_path),
+                Adt::Enum(it) => from_def_source(db, it, mod_path),
+            })
+        }
+        Definition::TypeParam(_) => {
             // FIXME: Hover for generic param
             None
         }
@@ -1025,52 +1031,75 @@ impl Thing {
 }
 "#,
             expect![[r#"
-                *Self { x: 0 }*
+                *Self*
+
                 ```rust
-                Thing
+                test
+                ```
+
+                ```rust
+                struct Thing
                 ```
             "#]],
-        )
-    } /* FIXME: revive these tests
-              let (analysis, position) = fixture::position(
-                  "
-                  struct Thing { x: u32 }
-                  impl Thing {
-                      fn new() -> Self<|> {
-                          Self { x: 0 }
-                      }
-                  }
-                  ",
-              );
-
-              let hover = analysis.hover(position).unwrap().unwrap();
-              assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Thing"));
-
-              let (analysis, position) = fixture::position(
-                  "
-                  enum Thing { A }
-                  impl Thing {
-                      pub fn new() -> Self<|> {
-                          Thing::A
-                      }
-                  }
-                  ",
-              );
-              let hover = analysis.hover(position).unwrap().unwrap();
-              assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing"));
-
-              let (analysis, position) = fixture::position(
-                  "
-                  enum Thing { A }
-                  impl Thing {
-                      pub fn thing(a: Self<|>) {
-                      }
-                  }
-                  ",
-              );
-              let hover = analysis.hover(position).unwrap().unwrap();
-              assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing"));
-      */
+        );
+        check(
+            r#"
+struct Thing { x: u32 }
+impl Thing {
+    fn new() -> Self<|> { Self { x: 0 } }
+}
+"#,
+            expect![[r#"
+                *Self*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                struct Thing
+                ```
+            "#]],
+        );
+        check(
+            r#"
+enum Thing { A }
+impl Thing {
+    pub fn new() -> Self<|> { Thing::A }
+}
+"#,
+            expect![[r#"
+                *Self*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                enum Thing
+                ```
+            "#]],
+        );
+        check(
+            r#"
+        enum Thing { A }
+        impl Thing {
+            pub fn thing(a: Self<|>) {}
+        }
+        "#,
+            expect![[r#"
+                *Self*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                enum Thing
+                ```
+            "#]],
+        );
+    }
 
     #[test]
     fn test_hover_shadowing_pat() {
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 7395b81bd2..5693dd400e 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -21,7 +21,7 @@ use ide_db::{
 use syntax::{
     algo::find_node_at_offset,
     ast::{self, NameOwner},
-    match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
+    AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
 };
 
 use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
@@ -89,10 +89,6 @@ pub(crate) fn find_all_refs(
     let _p = profile::span("find_all_refs");
     let syntax = sema.parse(position.file_id).syntax().clone();
 
-    if let Some(res) = try_find_self_references(&syntax, position) {
-        return Some(res);
-    }
-
     let (opt_name, search_kind) = if let Some(name) =
         get_struct_def_name_for_struct_literal_search(&sema, &syntax, position)
     {
@@ -198,77 +194,6 @@ fn get_struct_def_name_for_struct_literal_search(
     None
 }
 
-fn try_find_self_references(
-    syntax: &SyntaxNode,
-    position: FilePosition,
-) -> Option<RangeInfo<ReferenceSearchResult>> {
-    let self_token =
-        syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)?;
-    let parent = self_token.parent();
-    match_ast! {
-        match parent {
-            ast::SelfParam(it) => (),
-            ast::PathSegment(segment) => {
-                segment.self_token()?;
-                let path = segment.parent_path();
-                if path.qualifier().is_some() && !ast::PathExpr::can_cast(path.syntax().parent()?.kind()) {
-                    return None;
-                }
-            },
-            _ => return None,
-        }
-    };
-    let function = parent.ancestors().find_map(ast::Fn::cast)?;
-    let self_param = function.param_list()?.self_param()?;
-    let param_self_token = self_param.self_token()?;
-
-    let declaration = Declaration {
-        nav: NavigationTarget {
-            file_id: position.file_id,
-            full_range: self_param.syntax().text_range(),
-            focus_range: Some(param_self_token.text_range()),
-            name: param_self_token.text().clone(),
-            kind: param_self_token.kind(),
-            container_name: None,
-            description: None,
-            docs: None,
-        },
-        kind: ReferenceKind::SelfKw,
-        access: Some(if self_param.mut_token().is_some() {
-            ReferenceAccess::Write
-        } else {
-            ReferenceAccess::Read
-        }),
-    };
-    let references = function
-        .body()
-        .map(|body| {
-            body.syntax()
-                .descendants()
-                .filter_map(ast::PathExpr::cast)
-                .filter_map(|expr| {
-                    let path = expr.path()?;
-                    if path.qualifier().is_none() {
-                        path.segment()?.self_token()
-                    } else {
-                        None
-                    }
-                })
-                .map(|token| Reference {
-                    file_range: FileRange { file_id: position.file_id, range: token.text_range() },
-                    kind: ReferenceKind::SelfKw,
-                    access: declaration.access, // FIXME: properly check access kind here instead of copying it from the declaration
-                })
-                .collect()
-        })
-        .unwrap_or_default();
-
-    Some(RangeInfo::new(
-        param_self_token.text_range(),
-        ReferenceSearchResult { declaration, references },
-    ))
-}
-
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
@@ -837,32 +762,6 @@ fn f() -> m::En {
         );
     }
 
-    #[test]
-    fn test_find_self_refs() {
-        check(
-            r#"
-struct Foo { bar: i32 }
-
-impl Foo {
-    fn foo(self) {
-        let x = self<|>.bar;
-        if true {
-            let _ = match () {
-                () => self,
-            };
-        }
-    }
-}
-"#,
-            expect![[r#"
-                self SELF_KW FileId(0) 47..51 47..51 SelfKw Read
-
-                FileId(0) 71..75 SelfKw Read
-                FileId(0) 152..156 SelfKw Read
-            "#]],
-        );
-    }
-
     fn check(ra_fixture: &str, expect: Expect) {
         check_with_scope(ra_fixture, None, expect)
     }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 7314576967..91c64bd4a1 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -221,47 +221,24 @@ fn rename_to_self(
     let source_file = sema.parse(position.file_id);
     let syn = source_file.syntax();
 
-    let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
-        .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
+    let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
         .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
-    let param_range = fn_ast
-        .param_list()
-        .and_then(|p| p.params().next())
-        .ok_or_else(|| RenameError("Method has no parameters".to_string()))?
-        .syntax()
-        .text_range();
-    if !param_range.contains(position.offset) {
-        return Err(RenameError("Only the first parameter can be self".to_string()));
-    }
-
-    let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
-        .and_then(|def| sema.to_def(&def))
-        .ok_or_else(|| RenameError("No impl block found for function".to_string()))?;
-    if fn_def.self_param(sema.db).is_some() {
+    let params =
+        fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
+    if params.self_param().is_some() {
         return Err(RenameError("Method already has a self parameter".to_string()));
     }
-
-    let params = fn_def.params(sema.db);
     let first_param =
-        params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
-    let first_param_ty = first_param.ty();
-    let impl_ty = impl_block.target_ty(sema.db);
-    let (ty, self_param) = if impl_ty.remove_ref().is_some() {
-        // if the impl is a ref to the type we can just match the `&T` with self directly
-        (first_param_ty.clone(), "self")
-    } else {
-        first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| {
-            (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })
-        })
+        params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
+    let mutable = match first_param.ty() {
+        Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
+        _ => return Err(RenameError("Not renaming other types".to_string())),
     };
 
-    if ty != impl_ty {
-        return Err(RenameError("Parameter type differs from impl block type".to_string()));
-    }
-
     let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
         .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
 
+    let param_range = first_param.syntax().text_range();
     let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
         .into_iter()
         .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
@@ -277,7 +254,10 @@ fn rename_to_self(
 
     edits.push(SourceFileEdit {
         file_id: position.file_id,
-        edit: TextEdit::replace(param_range, String::from(self_param)),
+        edit: TextEdit::replace(
+            param_range,
+            String::from(if mutable { "&mut self" } else { "&self" }),
+        ),
     });
 
     Ok(RangeInfo::new(range, SourceChange::from(edits)))
@@ -300,11 +280,7 @@ fn text_edit_from_self_param(
 
     let mut replacement_text = String::from(new_name);
     replacement_text.push_str(": ");
-    match (self_param.amp_token(), self_param.mut_token()) {
-        (None, None) => (),
-        (Some(_), None) => replacement_text.push('&'),
-        (_, Some(_)) => replacement_text.push_str("&mut "),
-    };
+    replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut "));
     replacement_text.push_str(type_name.as_str());
 
     Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
@@ -1104,95 +1080,6 @@ impl Foo {
         self.i
     }
 }
-"#,
-        );
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-
-impl Foo {
-    fn f(foo<|>: Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-            r#"
-struct Foo { i: i32 }
-
-impl Foo {
-    fn f(self) -> i32 {
-        self.i
-    }
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn test_parameter_to_self_error_no_impl() {
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-
-fn f(foo<|>: &mut Foo) -> i32 {
-    foo.i
-}
-"#,
-            "error: No impl block found for function",
-        );
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-struct Bar;
-
-impl Bar {
-    fn f(foo<|>: &mut Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-            "error: Parameter type differs from impl block type",
-        );
-    }
-
-    #[test]
-    fn test_parameter_to_self_error_not_first() {
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-impl Foo {
-    fn f(x: (), foo<|>: &mut Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-            "error: Only the first parameter can be self",
-        );
-    }
-
-    #[test]
-    fn test_parameter_to_self_impl_ref() {
-        check(
-            "self",
-            r#"
-struct Foo { i: i32 }
-impl &Foo {
-    fn f(foo<|>: &Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-            r#"
-struct Foo { i: i32 }
-impl &Foo {
-    fn f(self) -> i32 {
-        self.i
-    }
-}
 "#,
         );
     }
@@ -1222,31 +1109,6 @@ impl Foo {
         );
     }
 
-    #[test]
-    fn test_owned_self_to_parameter() {
-        check(
-            "foo",
-            r#"
-struct Foo { i: i32 }
-
-impl Foo {
-    fn f(<|>self) -> i32 {
-        self.i
-    }
-}
-"#,
-            r#"
-struct Foo { i: i32 }
-
-impl Foo {
-    fn f(foo: Foo) -> i32 {
-        foo.i
-    }
-}
-"#,
-        );
-    }
-
     #[test]
     fn test_self_in_path_to_parameter() {
         check(
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 5150a970c1..1ed77b40b0 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -76,7 +76,6 @@ pub(crate) fn highlight(
     let mut current_macro_call: Option<ast::MacroCall> = None;
     let mut format_string_highlighter = FormatStringHighlighter::default();
     let mut macro_rules_highlighter = MacroRulesHighlighter::default();
-    let mut inside_attribute = false;
 
     // Walk all nodes, keeping track of whether we are inside a macro or not.
     // If in macro, expand it first and highlight the expanded code.
@@ -133,12 +132,9 @@ pub(crate) fn highlight(
             _ => (),
         }
 
+        // Check for Rust code in documentation
         match &event {
-            // Check for Rust code in documentation
             WalkEvent::Leave(NodeOrToken::Node(node)) => {
-                if ast::Attr::can_cast(node.kind()) {
-                    inside_attribute = false
-                }
                 if let Some((doctest, range_mapping, new_comments)) =
                     injection::extract_doc_comments(node)
                 {
@@ -150,9 +146,6 @@ pub(crate) fn highlight(
                     );
                 }
             }
-            WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
-                inside_attribute = true
-            }
             _ => (),
         }
 
@@ -195,16 +188,12 @@ pub(crate) fn highlight(
             }
         }
 
-        if let Some((mut highlight, binding_hash)) = highlight_element(
+        if let Some((highlight, binding_hash)) = highlight_element(
             &sema,
             &mut bindings_shadow_count,
             syntactic_name_ref_highlighting,
             element_to_highlight.clone(),
         ) {
-            if inside_attribute {
-                highlight = highlight | HighlightModifier::Attribute;
-            }
-
             if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() {
                 stack.add(HighlightedRange { range, highlight, binding_hash });
             }
@@ -485,9 +474,7 @@ fn highlight_element(
 
         // Highlight references like the definitions they resolve to
         NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
-            // even though we track whether we are in an attribute or not we still need this special case
-            // as otherwise we would emit unresolved references for name refs inside attributes
-            Highlight::from(HighlightTag::Function)
+            Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute
         }
         NAME_REF => {
             let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index 607185ca97..a3e765d059 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -31,7 +31,6 @@ pub enum ReferenceKind {
     FieldShorthandForLocal,
     StructLiteral,
     RecordFieldExprOrPat,
-    SelfKw,
     Other,
 }
 


Test patch:
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index d79fa6dca8..6be88f856b 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -50,7 +50,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="comment documentation">/// # Examples</span>
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// ```</span>
-    <span class="comment documentation">/// #</span><span class="generic injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="punctuation attribute injected">)</span><span class="attribute attribute injected">]</span>
+    <span class="comment documentation">/// #</span><span class="generic injected"> </span><span class="attribute injected">#</span><span class="attribute injected">!</span><span class="attribute injected">[</span><span class="function attribute injected">allow</span><span class="punctuation injected">(</span><span class="attribute injected">unused_mut</span><span class="punctuation injected">)</span><span class="attribute injected">]</span>
     <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="generic injected"> </span><span class="keyword injected">mut</span><span class="generic injected"> </span><span class="variable declaration injected mutable">foo</span><span class="punctuation injected">:</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="generic injected"> </span><span class="operator injected">=</span><span class="generic injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="punctuation injected">(</span><span class="punctuation injected">)</span><span class="punctuation injected">;</span><span class="punctuation injected">
 </span>    <span class="comment documentation">/// ```</span>
     <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static">new</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 1d05b77131..4b6d6adc96 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -54,7 +54,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 <span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">global_mut</span><span class="punctuation">:</span> <span class="struct">TypeForStaticMut</span> <span class="operator">=</span> <span class="struct">TypeForStaticMut</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span>
 
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">repr</span><span class="punctuation attribute">(</span><span class="attribute attribute">packed</span><span class="punctuation attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">repr</span><span class="punctuation">(</span><span class="attribute">packed</span><span class="punctuation">)</span><span class="attribute">]</span>
 <span class="keyword">struct</span> <span class="struct declaration">Packed</span> <span class="punctuation">{</span>
     <span class="field declaration">a</span><span class="punctuation">:</span> <span class="builtin_type">u16</span><span class="punctuation">,</span>
 <span class="punctuation">}</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 15fbf2ce39..6a10a9dcd0 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -40,18 +40,18 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 <span class="comment">// Needed for function consuming vs normal</span>
 <span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">marker</span> <span class="punctuation">{</span>
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"copy"</span><span class="attribute attribute">]</span>
+    <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"copy"</span><span class="attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 <span class="punctuation">}</span>
 
 <span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">ops</span> <span class="punctuation">{</span>
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn_once"</span><span class="attribute attribute">]</span>
+    <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_once"</span><span class="attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn_mut"</span><span class="attribute attribute">]</span>
+    <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn_mut"</span><span class="attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">FnMut</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnOnce</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 
-    <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"fn"</span><span class="attribute attribute">]</span>
+    <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">lang</span><span class="attribute"> </span><span class="operator">=</span><span class="attribute"> </span><span class="string_literal">"fn"</span><span class="attribute">]</span>
     <span class="keyword">pub</span> <span class="keyword">trait</span> <span class="trait declaration">Fn</span><span class="punctuation">&lt;</span><span class="type_param declaration">Args</span><span class="punctuation">&gt;</span><span class="punctuation">:</span> <span class="trait">FnMut</span><span class="punctuation">&lt;</span><span class="type_param">Args</span><span class="punctuation">&gt;</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 <span class="punctuation">}</span>
 
@@ -85,7 +85,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="punctuation">}</span>
 <span class="punctuation">}</span>
 
-<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">derive</span><span class="punctuation attribute">(</span><span class="attribute attribute">Copy</span><span class="punctuation attribute">)</span><span class="attribute attribute">]</span>
+<span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">derive</span><span class="punctuation">(</span><span class="attribute">Copy</span><span class="punctuation">)</span><span class="attribute">]</span>
 <span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="punctuation">{</span>
     <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">u32</span><span class="punctuation">,</span>
 <span class="punctuation">}</span>