Repository: rust-analyzer/rust-analyzer
Pull request #6665 (2020-11-28 22:02:35)

Support self in reference search
The approach here is simply checking the descendants of the function body for `PathExpr` then checking whether it only contains a single `self` `PathSegment`, this is to prevent us from picking up `self` tokens from local `UseTree`s.


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/goto_definition.rs b/crates/ide/src/goto_definition.rs
index b9810457fc..d41dd3d928 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,6 +1,5 @@
 use hir::Semantics;
 use ide_db::{
-    base_db::FileId,
     defs::{NameClass, NameRefClass},
     symbol_index, RootDatabase,
 };
@@ -41,17 +40,10 @@ pub(crate) fn goto_definition(
                 vec![nav]
             },
             ast::SelfParam(self_param) => {
-                vec![self_to_nav_target(self_param, position.file_id)?]
-            },
-            ast::PathSegment(segment) => {
-                segment.self_token()?;
-                let path = segment.parent_path();
-                if path.qualifier().is_some() && !ast::PathExpr::can_cast(path.syntax().parent()?.kind()) {
-                    return None;
-                }
-                let func = segment.syntax().ancestors().find_map(ast::Fn::cast)?;
-                let self_param = func.param_list()?.self_param()?;
-                vec![self_to_nav_target(self_param, position.file_id)?]
+                let ty = sema.type_of_self(&self_param)?;
+                let adt_def = ty.autoderef(db).filter_map(|ty| ty.as_adt()).last()?;
+                let nav = adt_def.to_nav(db);
+                vec![nav]
             },
             _ => return None,
         }
@@ -71,20 +63,6 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
     }
 }
 
-fn self_to_nav_target(self_param: ast::SelfParam, file_id: FileId) -> Option<NavigationTarget> {
-    let self_token = self_param.self_token()?;
-    Some(NavigationTarget {
-        file_id,
-        full_range: self_param.syntax().text_range(),
-        focus_range: Some(self_token.text_range()),
-        name: self_token.text().clone(),
-        kind: self_token.kind(),
-        container_name: None,
-        description: None,
-        docs: None,
-    })
-}
-
 #[derive(Debug)]
 pub(crate) enum ReferenceResult {
     Exact(NavigationTarget),
@@ -1009,31 +987,31 @@ fn g() -> <() as Iterator<A = (), B<|> = u8>>::A {}
     }
 
     #[test]
-    fn goto_self_param_ty_specified() {
+    fn todo_def_type_for_self() {
         check(
             r#"
 struct Foo {}
+     //^^^
 
 impl Foo {
-    fn bar(self: &Foo) {
-         //^^^^
-        let foo = sel<|>f;
-    }
-}"#,
-        )
+    fn bar(&self<|>) {}
+}
+"#,
+        );
     }
 
     #[test]
-    fn goto_self_param_on_decl() {
+    fn todo_def_type_for_arbitrary_self() {
         check(
             r#"
+struct Arc<T>(T);
+     //^^^
 struct Foo {}
 
 impl Foo {
-    fn bar(&self<|>) {
-          //^^^^
-    }
-}"#,
-        )
+    fn bar(self<|>: Arc<Self>) {}
+}
+"#,
+        );
     }
 }
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 94ad800a08..8321928817 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -139,17 +139,14 @@ pub(crate) fn hover(
         }
     }
 
-    let node = token.ancestors().find(|n| {
-        ast::Expr::can_cast(n.kind())
-            || ast::Pat::can_cast(n.kind())
-            || ast::SelfParam::can_cast(n.kind())
-    })?;
+    let node = token
+        .ancestors()
+        .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
 
     let ty = match_ast! {
         match node {
             ast::Expr(it) => sema.type_of_expr(&it)?,
             ast::Pat(it) => sema.type_of_pat(&it)?,
-            ast::SelfParam(self_param) => sema.type_of_self(&self_param)?,
             // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
             // (e.g expanding a builtin macro). So we give up here.
             ast::MacroCall(_it) => return None,
@@ -3285,41 +3282,4 @@ fn main() {
             "#]],
         );
     }
-
-    #[test]
-    fn hover_self_param_shows_type() {
-        check(
-            r#"
-struct Foo {}
-impl Foo {
-    fn bar(&sel<|>f) {}
-}
-"#,
-            expect![[r#"
-                *&self*
-                ```rust
-                &Foo
-                ```
-            "#]],
-        );
-    }
-
-    #[test]
-    fn hover_self_param_shows_type_for_arbitrary_self_type() {
-        check(
-            r#"
-struct Arc<T>(T);
-struct Foo {}
-impl Foo {
-    fn bar(sel<|>f: Arc<Foo>) {}
-}
-"#,
-            expect![[r#"
-                *self: Arc<Foo>*
-                ```rust
-                Arc<Foo>
-                ```
-            "#]],
-        );
-    }
 }
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 5693dd400e..7395b81bd2 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},
-    AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
+    match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
 };
 
 use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
@@ -89,6 +89,10 @@ 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)
     {
@@ -194,6 +198,77 @@ 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};
@@ -762,6 +837,32 @@ 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 a3e765d059..607185ca97 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -31,6 +31,7 @@ pub enum ReferenceKind {
     FieldShorthandForLocal,
     StructLiteral,
     RecordFieldExprOrPat,
+    SelfKw,
     Other,
 }
 
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 59269a74b9..30299a4659 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -480,7 +480,7 @@ macro_rules! config_data {
 
 config_data! {
     struct ConfigData {
-        assist_importMergeBehaviour: MergeBehaviourDef = MergeBehaviourDef::Full,
+        assist_importMergeBehaviour: MergeBehaviourDef = MergeBehaviourDef::None,
         assist_importPrefix: ImportPrefixDef           = ImportPrefixDef::Plain,
 
         callInfo_full: bool = true,


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>