about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2024-12-12 13:22:05 +0000
committerGitHub <noreply@github.com>2024-12-12 13:22:05 +0000
commit05f9d44656e123cc709d5a9f85fd9bfb0d4eca82 (patch)
tree12c258c3e2a809974f98b383d4d63912a719df60
parent508ce7cfc88185b838552b1fff4ba9d34952bfe0 (diff)
parentd16909ca9120df2272083840c6b664c448cfdab4 (diff)
downloadrust-05f9d44656e123cc709d5a9f85fd9bfb0d4eca82.tar.gz
rust-05f9d44656e123cc709d5a9f85fd9bfb0d4eca82.zip
Merge pull request #18652 from Giga-Bowser/extract-constant
feat: Add an assist to extract an expression into a constant
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs1402
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs44
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests.rs319
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs36
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs44
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/make.rs24
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs67
8 files changed, 1634 insertions, 306 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs
index 6937d33ebc1..2e363b0b62c 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs
@@ -7,6 +7,7 @@ use hir::{
     TypeInfo, TypeParam,
 };
 use ide_db::{
+    assists::GroupLabel,
     defs::{Definition, NameRefClass},
     famous_defs::FamousDefs,
     helpers::mod_path_to_ast,
@@ -104,7 +105,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
 
     let scope = ImportScope::find_insert_use_container(&node, &ctx.sema)?;
 
-    acc.add(
+    acc.add_group(
+        &GroupLabel("Extract into...".to_owned()),
         AssistId("extract_function", crate::AssistKind::RefactorExtract),
         "Extract into function",
         target_range,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
index 61dc72e0b33..a8d71ed7f4d 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs
@@ -1,17 +1,15 @@
-use hir::TypeInfo;
-use ide_db::syntax_helpers::suggest_name;
+use hir::{HirDisplay, TypeInfo};
+use ide_db::{assists::GroupLabel, syntax_helpers::suggest_name};
 use syntax::{
     ast::{
         self, edit::IndentLevel, edit_in_place::Indent, make, syntax_factory::SyntaxFactory,
         AstNode,
     },
     syntax_editor::Position,
-    NodeOrToken,
-    SyntaxKind::{BLOCK_EXPR, BREAK_EXPR, COMMENT, LOOP_EXPR, MATCH_GUARD, PATH_EXPR, RETURN_EXPR},
-    SyntaxNode, T,
+    NodeOrToken, SyntaxKind, SyntaxNode, T,
 };
 
-use crate::{AssistContext, AssistId, AssistKind, Assists};
+use crate::{utils::is_body_const, AssistContext, AssistId, AssistKind, Assists};
 
 // Assist: extract_variable
 //
@@ -29,6 +27,40 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
 //     var_name * 4;
 // }
 // ```
+
+// Assist: extract_constant
+//
+// Extracts subexpression into a constant.
+//
+// ```
+// fn main() {
+//     $0(1 + 2)$0 * 4;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+//     const $0VAR_NAME: i32 = 1 + 2;
+//     VAR_NAME * 4;
+// }
+// ```
+
+// Assist: extract_static
+//
+// Extracts subexpression into a static.
+//
+// ```
+// fn main() {
+//     $0(1 + 2)$0 * 4;
+// }
+// ```
+// ->
+// ```
+// fn main() {
+//     static $0VAR_NAME: i32 = 1 + 2;
+//     VAR_NAME * 4;
+// }
+// ```
 pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
     let node = if ctx.has_empty_selection() {
         if let Some(t) = ctx.token_at_offset().find(|it| it.kind() == T![;]) {
@@ -41,7 +73,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
     } else {
         match ctx.covering_element() {
             NodeOrToken::Node(it) => it,
-            NodeOrToken::Token(it) if it.kind() == COMMENT => {
+            NodeOrToken::Token(it) if it.kind() == SyntaxKind::COMMENT => {
                 cov_mark::hit!(extract_var_in_comment_is_not_applicable);
                 return None;
             }
@@ -87,116 +119,150 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
             }
             _ => false,
         };
-
-    let anchor = Anchor::from(&to_extract)?;
+    let module = ctx.sema.scope(to_extract.syntax())?.module();
     let target = to_extract.syntax().text_range();
-    acc.add(
-        AssistId("extract_variable", AssistKind::RefactorExtract),
-        "Extract into variable",
-        target,
-        move |edit| {
-            let field_shorthand = to_extract
-                .syntax()
-                .parent()
-                .and_then(ast::RecordExprField::cast)
-                .filter(|field| field.name_ref().is_some());
-
-            let (var_name, expr_replace) = match field_shorthand {
-                Some(field) => (field.to_string(), field.syntax().clone()),
-                None => (
-                    suggest_name::for_variable(&to_extract, &ctx.sema),
-                    to_extract.syntax().clone(),
-                ),
-            };
-
-            let make = SyntaxFactory::new();
-            let mut editor = edit.make_editor(&expr_replace);
-
-            let pat_name = make.name(&var_name);
-            let name_expr = make.expr_path(make::ext::ident_path(&var_name));
-
-            if let Some(cap) = ctx.config.snippet_cap {
-                let tabstop = edit.make_tabstop_before(cap);
-                editor.add_annotation(pat_name.syntax().clone(), tabstop);
-            }
+    let needs_mut = match &parent {
+        Some(ast::Expr::RefExpr(expr)) => expr.mut_token().is_some(),
+        _ => needs_adjust && !needs_ref && ty.as_ref().is_some_and(|ty| ty.is_mutable_reference()),
+    };
+    for kind in ExtractionKind::ALL {
+        let Some(anchor) = Anchor::from(&to_extract, kind) else {
+            continue;
+        };
 
-            let ident_pat = match parent {
-                Some(ast::Expr::RefExpr(expr)) if expr.mut_token().is_some() => {
-                    make.ident_pat(false, true, pat_name)
-                }
-                _ if needs_adjust
-                    && !needs_ref
-                    && ty.as_ref().is_some_and(|ty| ty.is_mutable_reference()) =>
+        let ty_string = match kind {
+            ExtractionKind::Constant | ExtractionKind::Static => {
+                let Some(ty) = ty.clone() else {
+                    continue;
+                };
+
+                // We can't mutably reference a const, nor can we define
+                // one using a non-const expression or one of unknown type
+                if needs_mut
+                    || !is_body_const(&ctx.sema, &to_extract_no_ref)
+                    || ty.is_unknown()
+                    || ty.is_mutable_reference()
                 {
-                    make.ident_pat(false, true, pat_name)
+                    continue;
                 }
-                _ => make.ident_pat(false, false, pat_name),
-            };
 
-            let to_extract_no_ref = match ty.as_ref().filter(|_| needs_ref) {
-                Some(receiver_type) if receiver_type.is_mutable_reference() => {
-                    make.expr_ref(to_extract_no_ref, true)
-                }
-                Some(receiver_type) if receiver_type.is_reference() => {
-                    make.expr_ref(to_extract_no_ref, false)
-                }
-                _ => to_extract_no_ref,
-            };
-
-            let let_stmt = make.let_stmt(ident_pat.into(), None, Some(to_extract_no_ref));
-
-            match anchor {
-                Anchor::Before(place) => {
-                    let prev_ws = place.prev_sibling_or_token().and_then(|it| it.into_token());
-                    let indent_to = IndentLevel::from_node(&place);
-
-                    // Adjust ws to insert depending on if this is all inline or on separate lines
-                    let trailing_ws = if prev_ws.is_some_and(|it| it.text().starts_with('\n')) {
-                        format!("\n{indent_to}")
-                    } else {
-                        " ".to_owned()
-                    };
-
-                    editor.insert_all(
-                        Position::before(place),
-                        vec![
-                            let_stmt.syntax().clone().into(),
-                            make::tokens::whitespace(&trailing_ws).into(),
-                        ],
-                    );
-
-                    editor.replace(expr_replace, name_expr.syntax());
-                }
-                Anchor::Replace(stmt) => {
-                    cov_mark::hit!(test_extract_var_expr_stmt);
+                let Ok(type_string) = ty.display_source_code(ctx.db(), module.into(), false) else {
+                    continue;
+                };
+
+                type_string
+            }
+            _ => "".to_owned(),
+        };
+
+        acc.add_group(
+            &GroupLabel("Extract into...".to_owned()),
+            kind.assist_id(),
+            kind.label(),
+            target,
+            |edit| {
+                let (var_name, expr_replace) = kind.get_name_and_expr(ctx, &to_extract);
+
+                let make = SyntaxFactory::new();
+                let mut editor = edit.make_editor(&expr_replace);
 
-                    editor.replace(stmt.syntax(), let_stmt.syntax());
+                let pat_name = make.name(&var_name);
+                let name_expr = make.expr_path(make::ext::ident_path(&var_name));
+
+                if let Some(cap) = ctx.config.snippet_cap {
+                    let tabstop = edit.make_tabstop_before(cap);
+                    editor.add_annotation(pat_name.syntax().clone(), tabstop);
                 }
-                Anchor::WrapInBlock(to_wrap) => {
-                    let indent_to = to_wrap.indent_level();
-
-                    let block = if to_wrap.syntax() == &expr_replace {
-                        // Since `expr_replace` is the same that needs to be wrapped in a block,
-                        // we can just directly replace it with a block
-                        make.block_expr([let_stmt.into()], Some(name_expr))
-                    } else {
-                        // `expr_replace` is a descendant of `to_wrap`, so we just replace it with `name_expr`.
+
+                let initializer = match ty.as_ref().filter(|_| needs_ref) {
+                    Some(receiver_type) if receiver_type.is_mutable_reference() => {
+                        make.expr_ref(to_extract_no_ref.clone(), true)
+                    }
+                    Some(receiver_type) if receiver_type.is_reference() => {
+                        make.expr_ref(to_extract_no_ref.clone(), false)
+                    }
+                    _ => to_extract_no_ref.clone(),
+                };
+
+                let new_stmt: ast::Stmt = match kind {
+                    ExtractionKind::Variable => {
+                        let ident_pat = make.ident_pat(false, needs_mut, pat_name);
+                        make.let_stmt(ident_pat.into(), None, Some(initializer)).into()
+                    }
+                    ExtractionKind::Constant => {
+                        let ast_ty = make.ty(&ty_string);
+                        ast::Item::Const(make.item_const(None, pat_name, ast_ty, initializer))
+                            .into()
+                    }
+                    ExtractionKind::Static => {
+                        let ast_ty = make.ty(&ty_string);
+                        ast::Item::Static(make.item_static(
+                            None,
+                            false,
+                            false,
+                            pat_name,
+                            ast_ty,
+                            Some(initializer),
+                        ))
+                        .into()
+                    }
+                };
+
+                match &anchor {
+                    Anchor::Before(place) => {
+                        let prev_ws = place.prev_sibling_or_token().and_then(|it| it.into_token());
+                        let indent_to = IndentLevel::from_node(place);
+
+                        // Adjust ws to insert depending on if this is all inline or on separate lines
+                        let trailing_ws = if prev_ws.is_some_and(|it| it.text().starts_with('\n')) {
+                            format!("\n{indent_to}")
+                        } else {
+                            " ".to_owned()
+                        };
+
+                        editor.insert_all(
+                            Position::before(place),
+                            vec![
+                                new_stmt.syntax().clone().into(),
+                                make::tokens::whitespace(&trailing_ws).into(),
+                            ],
+                        );
+
                         editor.replace(expr_replace, name_expr.syntax());
-                        make.block_expr([let_stmt.into()], Some(to_wrap.clone()))
-                    };
+                    }
+                    Anchor::Replace(stmt) => {
+                        cov_mark::hit!(test_extract_var_expr_stmt);
+
+                        editor.replace(stmt.syntax(), new_stmt.syntax());
+                    }
+                    Anchor::WrapInBlock(to_wrap) => {
+                        let indent_to = to_wrap.indent_level();
+
+                        let block = if to_wrap.syntax() == &expr_replace {
+                            // Since `expr_replace` is the same that needs to be wrapped in a block,
+                            // we can just directly replace it with a block
+                            make.block_expr([new_stmt], Some(name_expr))
+                        } else {
+                            // `expr_replace` is a descendant of `to_wrap`, so we just replace it with `name_expr`.
+                            editor.replace(expr_replace, name_expr.syntax());
+                            make.block_expr([new_stmt], Some(to_wrap.clone()))
+                        };
 
-                    editor.replace(to_wrap.syntax(), block.syntax());
+                        editor.replace(to_wrap.syntax(), block.syntax());
 
-                    // fixup indentation of block
-                    block.indent(indent_to);
+                        // fixup indentation of block
+                        block.indent(indent_to);
+                    }
                 }
-            }
 
-            editor.add_mappings(make.finish_with_mappings());
-            edit.add_file_edits(ctx.file_id(), editor);
-            edit.rename();
-        },
-    )
+                editor.add_mappings(make.finish_with_mappings());
+                edit.add_file_edits(ctx.file_id(), editor);
+                edit.rename();
+            },
+        );
+    }
+
+    Some(())
 }
 
 fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
@@ -211,17 +277,71 @@ fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
 /// In general that's true for any expression, but in some cases that would produce invalid code.
 fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
     match node.kind() {
-        PATH_EXPR | LOOP_EXPR => None,
-        BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
-        RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
-        BLOCK_EXPR => {
+        SyntaxKind::PATH_EXPR | SyntaxKind::LOOP_EXPR => None,
+        SyntaxKind::BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
+        SyntaxKind::RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
+        SyntaxKind::BLOCK_EXPR => {
             ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
         }
         _ => ast::Expr::cast(node),
     }
 }
 
-#[derive(Debug)]
+enum ExtractionKind {
+    Variable,
+    Constant,
+    Static,
+}
+
+impl ExtractionKind {
+    const ALL: &'static [ExtractionKind] =
+        &[ExtractionKind::Variable, ExtractionKind::Constant, ExtractionKind::Static];
+
+    fn assist_id(&self) -> AssistId {
+        let s = match self {
+            ExtractionKind::Variable => "extract_variable",
+            ExtractionKind::Constant => "extract_constant",
+            ExtractionKind::Static => "extract_static",
+        };
+
+        AssistId(s, AssistKind::RefactorExtract)
+    }
+
+    fn label(&self) -> &'static str {
+        match self {
+            ExtractionKind::Variable => "Extract into variable",
+            ExtractionKind::Constant => "Extract into constant",
+            ExtractionKind::Static => "Extract into static",
+        }
+    }
+
+    fn get_name_and_expr(
+        &self,
+        ctx: &AssistContext<'_>,
+        to_extract: &ast::Expr,
+    ) -> (String, SyntaxNode) {
+        let field_shorthand = to_extract
+            .syntax()
+            .parent()
+            .and_then(ast::RecordExprField::cast)
+            .filter(|field| field.name_ref().is_some());
+        let (var_name, expr_replace) = match field_shorthand {
+            Some(field) => (field.to_string(), field.syntax().clone()),
+            None => {
+                (suggest_name::for_variable(to_extract, &ctx.sema), to_extract.syntax().clone())
+            }
+        };
+
+        let var_name = match self {
+            ExtractionKind::Variable => var_name,
+            ExtractionKind::Constant | ExtractionKind::Static => var_name.to_uppercase(),
+        };
+
+        (var_name, expr_replace)
+    }
+}
+
+#[derive(Debug, Clone)]
 enum Anchor {
     Before(SyntaxNode),
     Replace(ast::ExprStmt),
@@ -229,8 +349,8 @@ enum Anchor {
 }
 
 impl Anchor {
-    fn from(to_extract: &ast::Expr) -> Option<Anchor> {
-        to_extract
+    fn from(to_extract: &ast::Expr, kind: &ExtractionKind) -> Option<Anchor> {
+        let result = to_extract
             .syntax()
             .ancestors()
             .take_while(|it| !ast::Item::can_cast(it.kind()) || ast::MacroCall::can_cast(it.kind()))
@@ -253,7 +373,7 @@ impl Anchor {
                         return parent.body().map(Anchor::WrapInBlock);
                     }
                     if let Some(parent) = ast::MatchArm::cast(parent) {
-                        if node.kind() == MATCH_GUARD {
+                        if node.kind() == SyntaxKind::MATCH_GUARD {
                             cov_mark::hit!(test_extract_var_in_match_guard);
                         } else {
                             cov_mark::hit!(test_extract_var_in_match_arm_no_block);
@@ -271,19 +391,42 @@ impl Anchor {
                     return Some(Anchor::Before(node));
                 }
                 None
-            })
+            });
+
+        match kind {
+            ExtractionKind::Constant | ExtractionKind::Static if result.is_none() => {
+                to_extract.syntax().ancestors().find_map(|node| {
+                    let item = ast::Item::cast(node.clone())?;
+                    let parent = item.syntax().parent()?;
+                    match parent.kind() {
+                        SyntaxKind::ITEM_LIST
+                        | SyntaxKind::SOURCE_FILE
+                        | SyntaxKind::ASSOC_ITEM_LIST
+                        | SyntaxKind::STMT_LIST => Some(Anchor::Before(node)),
+                        _ => None,
+                    }
+                })
+            }
+            _ => result,
+        }
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+    // NOTE: We use check_assist_by_label, but not check_assist_not_applicable_by_label
+    // because all of our not-applicable tests should behave that way for both assists
+    // extract_variable offers, and check_assist_not_applicable ensures neither is offered
+    use crate::tests::{
+        check_assist_by_label, check_assist_not_applicable, check_assist_not_applicable_by_label,
+        check_assist_target,
+    };
 
     use super::*;
 
     #[test]
-    fn test_extract_var_simple_without_select() {
-        check_assist(
+    fn extract_var_simple_without_select() {
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() -> i32 {
@@ -304,9 +447,10 @@ fn main() -> i32 {
     var_name
 }
 "#,
+            "Extract into variable",
         );
 
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn foo() -> i32 { 1 }
@@ -320,9 +464,10 @@ fn main() {
     let $0foo = foo();
 }
 "#,
+            "Extract into variable",
         );
 
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -336,9 +481,10 @@ fn main() {
     let $0is_some = a.is_some();
 }
 "#,
+            "Extract into variable",
         );
 
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -350,9 +496,10 @@ fn main() {
     let $0var_name = "hello";
 }
 "#,
+            "Extract into variable",
         );
 
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -364,9 +511,10 @@ fn main() {
     let $0var_name = 1  + 2;
 }
 "#,
+            "Extract into variable",
         );
 
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -384,11 +532,202 @@ fn main() {
     };
 }
 "#,
+            "Extract into variable",
+        );
+    }
+
+    #[test]
+    fn extract_const_simple_without_select() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn main() -> i32 {
+    if true {
+        1
+    } else {
+        2
+    }$0
+}
+"#,
+            r#"
+fn main() -> i32 {
+    const $0VAR_NAME: i32 = if true {
+        1
+    } else {
+        2
+    };
+    VAR_NAME
+}
+"#,
+            "Extract into constant",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+const fn foo() -> i32 { 1 }
+fn main() {
+    foo();$0
+}
+"#,
+            r#"
+const fn foo() -> i32 { 1 }
+fn main() {
+    const $0FOO: i32 = foo();
+}
+"#,
+            "Extract into constant",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn main() {
+    "hello"$0;
+}
+"#,
+            r#"
+fn main() {
+    const $0VAR_NAME: &str = "hello";
+}
+"#,
+            "Extract into constant",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn main() {
+    1  + 2$0;
+}
+"#,
+            r#"
+fn main() {
+    const $0VAR_NAME: i32 = 1  + 2;
+}
+"#,
+            "Extract into constant",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn main() {
+    match () {
+        () if true => 1,
+        _ => 2,
+    };$0
+}
+"#,
+            r#"
+fn main() {
+    const $0VAR_NAME: i32 = match () {
+        () if true => 1,
+        _ => 2,
+    };
+}
+"#,
+            "Extract into constant",
+        );
+    }
+
+    #[test]
+    fn extract_static_simple_without_select() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn main() -> i32 {
+    if true {
+        1
+    } else {
+        2
+    }$0
+}
+"#,
+            r#"
+fn main() -> i32 {
+    static $0VAR_NAME: i32 = if true {
+        1
+    } else {
+        2
+    };
+    VAR_NAME
+}
+"#,
+            "Extract into static",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+const fn foo() -> i32 { 1 }
+fn main() {
+    foo();$0
+}
+"#,
+            r#"
+const fn foo() -> i32 { 1 }
+fn main() {
+    static $0FOO: i32 = foo();
+}
+"#,
+            "Extract into static",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn main() {
+    "hello"$0;
+}
+"#,
+            r#"
+fn main() {
+    static $0VAR_NAME: &str = "hello";
+}
+"#,
+            "Extract into static",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn main() {
+    1  + 2$0;
+}
+"#,
+            r#"
+fn main() {
+    static $0VAR_NAME: i32 = 1  + 2;
+}
+"#,
+            "Extract into static",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn main() {
+    match () {
+        () if true => 1,
+        _ => 2,
+    };$0
+}
+"#,
+            r#"
+fn main() {
+    static $0VAR_NAME: i32 = match () {
+        () if true => 1,
+        _ => 2,
+    };
+}
+"#,
+            "Extract into static",
         );
     }
 
     #[test]
-    fn test_extract_var_unit_expr_without_select_not_applicable() {
+    fn dont_extract_unit_expr_without_select() {
         check_assist_not_applicable(
             extract_variable,
             r#"
@@ -414,8 +753,8 @@ fn foo() {
     }
 
     #[test]
-    fn test_extract_var_simple() {
-        check_assist(
+    fn extract_var_simple() {
+        check_assist_by_label(
             extract_variable,
             r#"
 fn foo() {
@@ -426,19 +765,54 @@ fn foo() {
     let $0var_name = 1 + 1;
     foo(var_name);
 }"#,
+            "Extract into variable",
+        );
+    }
+
+    #[test]
+    fn extract_const_simple() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    foo($01 + 1$0);
+}"#,
+            r#"
+fn foo() {
+    const $0VAR_NAME: i32 = 1 + 1;
+    foo(VAR_NAME);
+}"#,
+            "Extract into constant",
         );
     }
 
     #[test]
-    fn extract_var_in_comment_is_not_applicable() {
+    fn extract_static_simple() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    foo($01 + 1$0);
+}"#,
+            r#"
+fn foo() {
+    static $0VAR_NAME: i32 = 1 + 1;
+    foo(VAR_NAME);
+}"#,
+            "Extract into static",
+        );
+    }
+
+    #[test]
+    fn dont_extract_in_comment() {
         cov_mark::check!(extract_var_in_comment_is_not_applicable);
-        check_assist_not_applicable(extract_variable, "fn main() { 1 + /* $0comment$0 */ 1; }");
+        check_assist_not_applicable(extract_variable, r#"fn main() { 1 + /* $0comment$0 */ 1; }"#);
     }
 
     #[test]
-    fn test_extract_var_expr_stmt() {
+    fn extract_var_expr_stmt() {
         cov_mark::check!(test_extract_var_expr_stmt);
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn foo() {
@@ -448,42 +822,143 @@ fn foo() {
 fn foo() {
     let $0var_name = 1 + 1;
 }"#,
+            "Extract into variable",
         );
-        check_assist(
+        check_assist_by_label(
             extract_variable,
-            r"
+            r#"
 fn foo() {
     $0{ let x = 0; x }$0;
     something_else();
-}",
-            r"
+}"#,
+            r#"
 fn foo() {
     let $0var_name = { let x = 0; x };
     something_else();
-}",
+}"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_part_of_expr_stmt() {
-        check_assist(
+    fn extract_const_expr_stmt() {
+        cov_mark::check!(test_extract_var_expr_stmt);
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+  $0  1 + 1$0;
+}"#,
+            r#"
+fn foo() {
+    const $0VAR_NAME: i32 = 1 + 1;
+}"#,
+            "Extract into constant",
+        );
+        // This is hilarious but as far as I know, it's valid
+        check_assist_by_label(
             extract_variable,
-            r"
+            r#"
+fn foo() {
+    $0{ let x = 0; x }$0;
+    something_else();
+}"#,
+            r#"
+fn foo() {
+    const $0VAR_NAME: i32 = { let x = 0; x };
+    something_else();
+}"#,
+            "Extract into constant",
+        );
+    }
+
+    #[test]
+    fn extract_static_expr_stmt() {
+        cov_mark::check!(test_extract_var_expr_stmt);
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+  $0  1 + 1$0;
+}"#,
+            r#"
+fn foo() {
+    static $0VAR_NAME: i32 = 1 + 1;
+}"#,
+            "Extract into static",
+        );
+        // This is hilarious but as far as I know, it's valid
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    $0{ let x = 0; x }$0;
+    something_else();
+}"#,
+            r#"
+fn foo() {
+    static $0VAR_NAME: i32 = { let x = 0; x };
+    something_else();
+}"#,
+            "Extract into static",
+        );
+    }
+
+    #[test]
+    fn extract_var_part_of_expr_stmt() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
 fn foo() {
     $01$0 + 1;
-}",
-            r"
+}"#,
+            r#"
 fn foo() {
     let $0var_name = 1;
     var_name + 1;
-}",
+}"#,
+            "Extract into variable",
+        );
+    }
+
+    #[test]
+    fn extract_const_part_of_expr_stmt() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    $01$0 + 1;
+}"#,
+            r#"
+fn foo() {
+    const $0VAR_NAME: i32 = 1;
+    VAR_NAME + 1;
+}"#,
+            "Extract into constant",
         );
     }
 
     #[test]
-    fn test_extract_var_last_expr() {
+    fn extract_static_part_of_expr_stmt() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    $01$0 + 1;
+}"#,
+            r#"
+fn foo() {
+    static $0VAR_NAME: i32 = 1;
+    VAR_NAME + 1;
+}"#,
+            "Extract into static",
+        );
+    }
+
+    #[test]
+    fn extract_var_last_expr() {
         cov_mark::check!(test_extract_var_last_expr);
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn foo() {
@@ -496,8 +971,9 @@ fn foo() {
     bar(var_name)
 }
 "#,
+            "Extract into variable",
         );
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn foo() -> i32 {
@@ -518,13 +994,100 @@ fn bar(i: i32) -> i32 {
     i
 }
 "#,
+            "Extract into variable",
+        )
+    }
+
+    #[test]
+    fn extract_const_last_expr() {
+        cov_mark::check!(test_extract_var_last_expr);
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    bar($01 + 1$0)
+}
+"#,
+            r#"
+fn foo() {
+    const $0VAR_NAME: i32 = 1 + 1;
+    bar(VAR_NAME)
+}
+"#,
+            "Extract into constant",
+        );
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() -> i32 {
+    $0bar(1 + 1)$0
+}
+
+const fn bar(i: i32) -> i32 {
+    i
+}
+"#,
+            r#"
+fn foo() -> i32 {
+    const $0BAR: i32 = bar(1 + 1);
+    BAR
+}
+
+const fn bar(i: i32) -> i32 {
+    i
+}
+"#,
+            "Extract into constant",
+        )
+    }
+
+    #[test]
+    fn extract_static_last_expr() {
+        cov_mark::check!(test_extract_var_last_expr);
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    bar($01 + 1$0)
+}
+"#,
+            r#"
+fn foo() {
+    static $0VAR_NAME: i32 = 1 + 1;
+    bar(VAR_NAME)
+}
+"#,
+            "Extract into static",
+        );
+        check_assist_by_label(
+            extract_variable,
+            r#"
+fn foo() -> i32 {
+    $0bar(1 + 1)$0
+}
+
+const fn bar(i: i32) -> i32 {
+    i
+}
+"#,
+            r#"
+fn foo() -> i32 {
+    static $0BAR: i32 = bar(1 + 1);
+    BAR
+}
+
+const fn bar(i: i32) -> i32 {
+    i
+}
+"#,
+            "Extract into static",
         )
     }
 
     #[test]
-    fn test_extract_var_in_match_arm_no_block() {
+    fn extract_var_in_match_arm_no_block() {
         cov_mark::check!(test_extract_var_in_match_arm_no_block);
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -547,12 +1110,13 @@ fn main() {
     };
 }
 "#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_in_match_arm_with_block() {
-        check_assist(
+    fn extract_var_in_match_arm_with_block() {
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -579,13 +1143,14 @@ fn main() {
     };
 }
 "#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_in_match_guard() {
+    fn extract_var_in_match_guard() {
         cov_mark::check!(test_extract_var_in_match_guard);
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -604,13 +1169,14 @@ fn main() {
     };
 }
 "#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_in_closure_no_block() {
+    fn extract_var_in_closure_no_block() {
         cov_mark::check!(test_extract_var_in_closure_no_block);
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -625,12 +1191,13 @@ fn main() {
     };
 }
 "#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_in_closure_with_block() {
-        check_assist(
+    fn extract_var_in_closure_with_block() {
+        check_assist_by_label(
             extract_variable,
             r#"
 fn main() {
@@ -642,104 +1209,110 @@ fn main() {
     let lambda = |x: u32| { let $0var_name = x * 2; var_name };
 }
 "#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_path_simple() {
-        check_assist(
+    fn extract_var_path_simple() {
+        check_assist_by_label(
             extract_variable,
-            "
+            r#"
 fn main() {
     let o = $0Some(true)$0;
 }
-",
-            "
+"#,
+            r#"
 fn main() {
     let $0var_name = Some(true);
     let o = var_name;
 }
-",
+"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_path_method() {
-        check_assist(
+    fn extract_var_path_method() {
+        check_assist_by_label(
             extract_variable,
-            "
+            r#"
 fn main() {
     let v = $0bar.foo()$0;
 }
-",
-            "
+"#,
+            r#"
 fn main() {
     let $0foo = bar.foo();
     let v = foo;
 }
-",
+"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_return() {
-        check_assist(
+    fn extract_var_return() {
+        check_assist_by_label(
             extract_variable,
-            "
+            r#"
 fn foo() -> u32 {
     $0return 2 + 2$0;
 }
-",
-            "
+"#,
+            r#"
 fn foo() -> u32 {
     let $0var_name = 2 + 2;
     return var_name;
 }
-",
+"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_does_not_add_extra_whitespace() {
-        check_assist(
+    fn extract_var_does_not_add_extra_whitespace() {
+        check_assist_by_label(
             extract_variable,
-            "
+            r#"
 fn foo() -> u32 {
 
 
     $0return 2 + 2$0;
 }
-",
-            "
+"#,
+            r#"
 fn foo() -> u32 {
 
 
     let $0var_name = 2 + 2;
     return var_name;
 }
-",
+"#,
+            "Extract into variable",
         );
 
-        check_assist(
+        check_assist_by_label(
             extract_variable,
-            "
+            r#"
 fn foo() -> u32 {
 
         $0return 2 + 2$0;
 }
-",
-            "
+"#,
+            r#"
 fn foo() -> u32 {
 
         let $0var_name = 2 + 2;
         return var_name;
 }
-",
+"#,
+            "Extract into variable",
         );
 
-        check_assist(
+        check_assist_by_label(
             extract_variable,
-            "
+            r#"
 fn foo() -> u32 {
     let foo = 1;
 
@@ -748,8 +1321,8 @@ fn foo() -> u32 {
 
     $0return 2 + 2$0;
 }
-",
-            "
+"#,
+            r#"
 fn foo() -> u32 {
     let foo = 1;
 
@@ -759,53 +1332,56 @@ fn foo() -> u32 {
     let $0var_name = 2 + 2;
     return var_name;
 }
-",
+"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_break() {
-        check_assist(
+    fn extract_var_break() {
+        check_assist_by_label(
             extract_variable,
-            "
+            r#"
 fn main() {
     let result = loop {
         $0break 2 + 2$0;
     };
 }
-",
-            "
+"#,
+            r#"
 fn main() {
     let result = loop {
         let $0var_name = 2 + 2;
         break var_name;
     };
 }
-",
+"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_for_cast() {
-        check_assist(
+    fn extract_var_for_cast() {
+        check_assist_by_label(
             extract_variable,
-            "
+            r#"
 fn main() {
     let v = $00f32 as u32$0;
 }
-",
-            "
+"#,
+            r#"
 fn main() {
     let $0var_name = 0f32 as u32;
     let v = var_name;
 }
-",
+"#,
+            "Extract into variable",
         );
     }
 
     #[test]
     fn extract_var_field_shorthand() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 struct S {
@@ -826,12 +1402,13 @@ fn main() {
     S { foo }
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_var_name_from_type() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 struct Test(i32);
@@ -848,12 +1425,13 @@ fn foo() -> Test {
     test
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_var_name_from_parameter() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn bar(test: u32, size: u32)
@@ -870,12 +1448,13 @@ fn foo() {
     bar(1, size);
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_var_parameter_name_has_precedence_over_type() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 struct TextSize(u32);
@@ -894,12 +1473,13 @@ fn foo() {
     bar(1, size);
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_var_name_from_function() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn is_required(test: u32, size: u32) -> bool
@@ -916,12 +1496,13 @@ fn foo() -> bool {
     is_required
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_var_name_from_method() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 struct S;
@@ -944,12 +1525,13 @@ fn foo() -> u32 {
     bar
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_var_name_from_method_param() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 struct S;
@@ -972,12 +1554,13 @@ fn foo() {
     S.bar(n, 2)
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_var_name_from_ufcs_method_param() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 struct S;
@@ -1000,12 +1583,13 @@ fn foo() {
     S::bar(&S, n, 2)
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_var_parameter_name_has_precedence_over_function() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 fn bar(test: u32, size: u32)
@@ -1022,14 +1606,15 @@ fn foo() {
     bar(1, size);
 }
 "#,
+            "Extract into variable",
         )
     }
 
     #[test]
     fn extract_macro_call() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
-            r"
+            r#"
 struct Vec;
 macro_rules! vec {
     () => {Vec}
@@ -1037,8 +1622,8 @@ macro_rules! vec {
 fn main() {
     let _ = $0vec![]$0;
 }
-",
-            r"
+"#,
+            r#"
 struct Vec;
 macro_rules! vec {
     () => {Vec}
@@ -1047,22 +1632,71 @@ fn main() {
     let $0vec = vec![];
     let _ = vec;
 }
-",
+"#,
+            "Extract into variable",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+struct Vec;
+macro_rules! vec {
+    () => {Vec}
+}
+fn main() {
+    let _ = $0vec![]$0;
+}
+"#,
+            r#"
+struct Vec;
+macro_rules! vec {
+    () => {Vec}
+}
+fn main() {
+    const $0VEC: Vec = vec![];
+    let _ = VEC;
+}
+"#,
+            "Extract into constant",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+struct Vec;
+macro_rules! vec {
+    () => {Vec}
+}
+fn main() {
+    let _ = $0vec![]$0;
+}
+"#,
+            r#"
+struct Vec;
+macro_rules! vec {
+    () => {Vec}
+}
+fn main() {
+    static $0VEC: Vec = vec![];
+    let _ = VEC;
+}
+"#,
+            "Extract into static",
         );
     }
 
     #[test]
-    fn test_extract_var_for_return_not_applicable() {
+    fn extract_var_for_return_not_applicable() {
         check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");
     }
 
     #[test]
-    fn test_extract_var_for_break_not_applicable() {
+    fn extract_var_for_break_not_applicable() {
         check_assist_not_applicable(extract_variable, "fn main() { loop { $0break$0; }; }");
     }
 
     #[test]
-    fn test_extract_var_unit_expr_not_applicable() {
+    fn extract_var_unit_expr_not_applicable() {
         check_assist_not_applicable(
             extract_variable,
             r#"
@@ -1080,11 +1714,11 @@ fn foo() {
     // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
     #[test]
     fn extract_var_target() {
-        check_assist_target(extract_variable, "fn foo() -> u32 { $0return 2 + 2$0; }", "2 + 2");
+        check_assist_target(extract_variable, r#"fn foo() -> u32 { $0return 2 + 2$0; }"#, "2 + 2");
 
         check_assist_target(
             extract_variable,
-            "
+            r#"
 fn main() {
     let x = true;
     let tuple = match x {
@@ -1092,24 +1726,231 @@ fn main() {
         _ => (0, false)
     };
 }
-",
+"#,
             "2 + 2",
         );
     }
 
     #[test]
     fn extract_var_no_block_body() {
-        check_assist_not_applicable(
+        check_assist_not_applicable_by_label(
             extract_variable,
-            r"
+            r#"
 const X: usize = $0100$0;
-",
+"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_mutable_reference_parameter() {
-        check_assist(
+    fn extract_const_no_block_body() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+const FOO: i32 = foo($0100$0);
+"#,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+const $0X: i32 = 100;
+const FOO: i32 = foo(X);
+"#,
+            "Extract into constant",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+mod foo {
+    enum Foo {
+        Bar,
+        Baz = $042$0,
+    }
+}
+"#,
+            r#"
+mod foo {
+    const $0VAR_NAME: isize = 42;
+    enum Foo {
+        Bar,
+        Baz = VAR_NAME,
+    }
+}
+"#,
+            "Extract into constant",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+trait Hello {
+    const World: i32;
+}
+
+struct Bar;
+impl Hello for Bar {
+    const World = foo($042$0);
+}
+"#,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+trait Hello {
+    const World: i32;
+}
+
+struct Bar;
+impl Hello for Bar {
+    const $0X: i32 = 42;
+    const World = foo(X);
+}
+"#,
+            "Extract into constant",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+fn bar() {
+    const BAZ: i32 = foo($042$0);
+}
+"#,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+fn bar() {
+    const $0X: i32 = 42;
+    const BAZ: i32 = foo(X);
+}
+"#,
+            "Extract into constant",
+        );
+    }
+
+    #[test]
+    fn extract_static_no_block_body() {
+        check_assist_by_label(
+            extract_variable,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+const FOO: i32 = foo($0100$0);
+"#,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+static $0X: i32 = 100;
+const FOO: i32 = foo(X);
+"#,
+            "Extract into static",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+mod foo {
+    enum Foo {
+        Bar,
+        Baz = $042$0,
+    }
+}
+"#,
+            r#"
+mod foo {
+    static $0VAR_NAME: isize = 42;
+    enum Foo {
+        Bar,
+        Baz = VAR_NAME,
+    }
+}
+"#,
+            "Extract into static",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+trait Hello {
+    const World: i32;
+}
+
+struct Bar;
+impl Hello for Bar {
+    const World = foo($042$0);
+}
+"#,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+trait Hello {
+    const World: i32;
+}
+
+struct Bar;
+impl Hello for Bar {
+    static $0X: i32 = 42;
+    const World = foo(X);
+}
+"#,
+            "Extract into static",
+        );
+
+        check_assist_by_label(
+            extract_variable,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+fn bar() {
+    const BAZ: i32 = foo($042$0);
+}
+"#,
+            r#"
+const fn foo(x: i32) -> i32 {
+    x
+}
+
+fn bar() {
+    static $0X: i32 = 42;
+    const BAZ: i32 = foo(X);
+}
+"#,
+            "Extract into static",
+        );
+    }
+
+    #[test]
+    fn extract_var_mutable_reference_parameter() {
+        check_assist_by_label(
             extract_variable,
             r#"
 struct S {
@@ -1138,12 +1979,55 @@ fn foo(s: &mut S) {
     let $0vec = &mut s.vec;
     vec.push(0);
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_mutable_reference_parameter_deep_nesting() {
-        check_assist(
+    fn dont_extract_const_mutable_reference_parameter() {
+        check_assist_not_applicable_by_label(
+            extract_variable,
+            r#"
+struct S {
+    vec: Vec<u8>
+}
+
+struct Vec<T>;
+impl<T> Vec<T> {
+    fn push(&mut self, _:usize) {}
+}
+
+fn foo(s: &mut S) {
+    $0s.vec$0.push(0);
+}"#,
+            "Extract into constant",
+        );
+    }
+
+    #[test]
+    fn dont_extract_static_mutable_reference_parameter() {
+        check_assist_not_applicable_by_label(
+            extract_variable,
+            r#"
+struct S {
+    vec: Vec<u8>
+}
+
+struct Vec<T>;
+impl<T> Vec<T> {
+    fn push(&mut self, _:usize) {}
+}
+
+fn foo(s: &mut S) {
+    $0s.vec$0.push(0);
+}"#,
+            "Extract into static",
+        );
+    }
+
+    #[test]
+    fn extract_var_mutable_reference_parameter_deep_nesting() {
+        check_assist_by_label(
             extract_variable,
             r#"
 struct Y {
@@ -1182,12 +2066,13 @@ fn foo(f: &mut Y) {
     let $0vec = &mut f.field.field.vec;
     vec.push(0);
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_reference_parameter() {
-        check_assist(
+    fn extract_var_reference_parameter() {
+        check_assist_by_label(
             extract_variable,
             r#"
 struct X;
@@ -1222,12 +2107,13 @@ fn foo(s: &S) {
     let $0x = &s.sub;
     x.do_thing();
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_index_deref() {
-        check_assist(
+    fn extract_var_index_deref() {
+        check_assist_by_label(
             extract_variable,
             r#"
 //- minicore: index
@@ -1261,12 +2147,13 @@ fn foo(s: &S) {
     let $0sub = &s.sub;
     sub[0];
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_reference_parameter_deep_nesting() {
-        check_assist(
+    fn extract_var_reference_parameter_deep_nesting() {
+        check_assist_by_label(
             extract_variable,
             r#"
 struct Z;
@@ -1315,12 +2202,13 @@ fn foo(s: &S) {
     let $0z = &s.sub.field.field;
     z.do_thing();
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_regular_parameter() {
-        check_assist(
+    fn extract_var_regular_parameter() {
+        check_assist_by_label(
             extract_variable,
             r#"
 struct X;
@@ -1355,12 +2243,13 @@ fn foo(s: S) {
     let $0x = &s.sub;
     x.do_thing();
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_mutable_reference_local() {
-        check_assist(
+    fn extract_var_mutable_reference_local() {
+        check_assist_by_label(
             extract_variable,
             r#"
 struct X;
@@ -1421,12 +2310,13 @@ fn foo() {
     let $0x = &local.sub;
     x.do_thing();
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_reference_local() {
-        check_assist(
+    fn extract_var_reference_local() {
+        check_assist_by_label(
             extract_variable,
             r#"
 struct X;
@@ -1487,12 +2377,13 @@ fn foo() {
     let $0x = &local.sub;
     x.do_thing();
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
-    fn test_extract_var_for_mutable_borrow() {
-        check_assist(
+    fn extract_var_for_mutable_borrow() {
+        check_assist_by_label(
             extract_variable,
             r#"
 fn foo() {
@@ -1503,12 +2394,37 @@ fn foo() {
     let mut $0var_name = 0;
     let v = &mut var_name;
 }"#,
+            "Extract into variable",
+        );
+    }
+
+    #[test]
+    fn dont_extract_const_for_mutable_borrow() {
+        check_assist_not_applicable_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    let v = &mut $00$0;
+}"#,
+            "Extract into constant",
+        );
+    }
+
+    #[test]
+    fn dont_extract_static_for_mutable_borrow() {
+        check_assist_not_applicable_by_label(
+            extract_variable,
+            r#"
+fn foo() {
+    let v = &mut $00$0;
+}"#,
+            "Extract into static",
         );
     }
 
     #[test]
     fn generates_no_ref_on_calls() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 struct S;
@@ -1529,12 +2445,13 @@ fn foo() {
     let mut $0bar = bar();
     bar.do_work();
 }"#,
+            "Extract into variable",
         );
     }
 
     #[test]
     fn generates_no_ref_for_deref() {
-        check_assist(
+        check_assist_by_label(
             extract_variable,
             r#"
 struct S;
@@ -1559,6 +2476,7 @@ fn foo() {
     s.do_work();
 }
 "#,
+            "Extract into variable",
         );
     }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs
index 7c2dc0e0c10..0cc771ff397 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/promote_local_to_const.rs
@@ -1,19 +1,17 @@
-use hir::{HirDisplay, ModuleDef, PathResolution, Semantics};
+use hir::HirDisplay;
 use ide_db::{
     assists::{AssistId, AssistKind},
     defs::Definition,
-    syntax_helpers::node_ext::preorder_expr,
-    RootDatabase,
 };
 use stdx::to_upper_snake_case;
 use syntax::{
     ast::{self, make, HasName},
-    ted, AstNode, WalkEvent,
+    ted, AstNode,
 };
 
 use crate::{
     assist_context::{AssistContext, Assists},
-    utils,
+    utils::{self},
 };
 
 // Assist: promote_local_to_const
@@ -63,7 +61,7 @@ pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>)
     };
 
     let initializer = let_stmt.initializer()?;
-    if !is_body_const(&ctx.sema, &initializer) {
+    if !utils::is_body_const(&ctx.sema, &initializer) {
         cov_mark::hit!(promote_local_non_const);
         return None;
     }
@@ -103,40 +101,6 @@ pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext<'_>)
     )
 }
 
-fn is_body_const(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> bool {
-    let mut is_const = true;
-    preorder_expr(expr, &mut |ev| {
-        let expr = match ev {
-            WalkEvent::Enter(_) if !is_const => return true,
-            WalkEvent::Enter(expr) => expr,
-            WalkEvent::Leave(_) => return false,
-        };
-        match expr {
-            ast::Expr::CallExpr(call) => {
-                if let Some(ast::Expr::PathExpr(path_expr)) = call.expr() {
-                    if let Some(PathResolution::Def(ModuleDef::Function(func))) =
-                        path_expr.path().and_then(|path| sema.resolve_path(&path))
-                    {
-                        is_const &= func.is_const(sema.db);
-                    }
-                }
-            }
-            ast::Expr::MethodCallExpr(call) => {
-                is_const &=
-                    sema.resolve_method_call(&call).map(|it| it.is_const(sema.db)).unwrap_or(true)
-            }
-            ast::Expr::ForExpr(_)
-            | ast::Expr::ReturnExpr(_)
-            | ast::Expr::TryExpr(_)
-            | ast::Expr::YieldExpr(_)
-            | ast::Expr::AwaitExpr(_) => is_const = false,
-            _ => (),
-        }
-        !is_const
-    });
-    is_const
-}
-
 #[cfg(test)]
 mod tests {
     use crate::tests::{check_assist, check_assist_not_applicable};
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
index 6469957fe16..0b1ff87c5c2 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs
@@ -362,8 +362,7 @@ pub fn test_some_range(a: int) -> bool {
 
     expect![[r#"
         Convert integer base
-        Extract into variable
-        Extract into function
+        Extract into...
         Replace if let with match
     "#]]
     .assert_eq(&expected);
@@ -391,8 +390,7 @@ pub fn test_some_range(a: int) -> bool {
 
         expect![[r#"
             Convert integer base
-            Extract into variable
-            Extract into function
+            Extract into...
             Replace if let with match
         "#]]
         .assert_eq(&expected);
@@ -405,8 +403,7 @@ pub fn test_some_range(a: int) -> bool {
         let expected = labels(&assists);
 
         expect![[r#"
-            Extract into variable
-            Extract into function
+            Extract into...
         "#]]
         .assert_eq(&expected);
     }
@@ -440,7 +437,7 @@ pub fn test_some_range(a: int) -> bool {
 
     {
         let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange.into());
-        assert_eq!(2, assists.len());
+        assert_eq!(4, assists.len());
         let mut assists = assists.into_iter();
 
         let extract_into_variable_assist = assists.next().unwrap();
@@ -451,7 +448,11 @@ pub fn test_some_range(a: int) -> bool {
                     RefactorExtract,
                 ),
                 label: "Extract into variable",
-                group: None,
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
                 target: 59..60,
                 source_change: None,
                 command: None,
@@ -459,6 +460,46 @@ pub fn test_some_range(a: int) -> bool {
         "#]]
         .assert_debug_eq(&extract_into_variable_assist);
 
+        let extract_into_constant_assist = assists.next().unwrap();
+        expect![[r#"
+            Assist {
+                id: AssistId(
+                    "extract_constant",
+                    RefactorExtract,
+                ),
+                label: "Extract into constant",
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
+                target: 59..60,
+                source_change: None,
+                command: None,
+            }
+        "#]]
+        .assert_debug_eq(&extract_into_constant_assist);
+
+        let extract_into_static_assist = assists.next().unwrap();
+        expect![[r#"
+            Assist {
+                id: AssistId(
+                    "extract_static",
+                    RefactorExtract,
+                ),
+                label: "Extract into static",
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
+                target: 59..60,
+                source_change: None,
+                command: None,
+            }
+        "#]]
+        .assert_debug_eq(&extract_into_static_assist);
+
         let extract_into_function_assist = assists.next().unwrap();
         expect![[r#"
             Assist {
@@ -467,7 +508,11 @@ pub fn test_some_range(a: int) -> bool {
                     RefactorExtract,
                 ),
                 label: "Extract into function",
-                group: None,
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
                 target: 59..60,
                 source_change: None,
                 command: None,
@@ -486,7 +531,7 @@ pub fn test_some_range(a: int) -> bool {
             }),
             frange.into(),
         );
-        assert_eq!(2, assists.len());
+        assert_eq!(4, assists.len());
         let mut assists = assists.into_iter();
 
         let extract_into_variable_assist = assists.next().unwrap();
@@ -497,7 +542,11 @@ pub fn test_some_range(a: int) -> bool {
                     RefactorExtract,
                 ),
                 label: "Extract into variable",
-                group: None,
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
                 target: 59..60,
                 source_change: None,
                 command: None,
@@ -505,6 +554,46 @@ pub fn test_some_range(a: int) -> bool {
         "#]]
         .assert_debug_eq(&extract_into_variable_assist);
 
+        let extract_into_constant_assist = assists.next().unwrap();
+        expect![[r#"
+            Assist {
+                id: AssistId(
+                    "extract_constant",
+                    RefactorExtract,
+                ),
+                label: "Extract into constant",
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
+                target: 59..60,
+                source_change: None,
+                command: None,
+            }
+        "#]]
+        .assert_debug_eq(&extract_into_constant_assist);
+
+        let extract_into_static_assist = assists.next().unwrap();
+        expect![[r#"
+            Assist {
+                id: AssistId(
+                    "extract_static",
+                    RefactorExtract,
+                ),
+                label: "Extract into static",
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
+                target: 59..60,
+                source_change: None,
+                command: None,
+            }
+        "#]]
+        .assert_debug_eq(&extract_into_static_assist);
+
         let extract_into_function_assist = assists.next().unwrap();
         expect![[r#"
             Assist {
@@ -513,7 +602,11 @@ pub fn test_some_range(a: int) -> bool {
                     RefactorExtract,
                 ),
                 label: "Extract into function",
-                group: None,
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
                 target: 59..60,
                 source_change: None,
                 command: None,
@@ -532,7 +625,7 @@ pub fn test_some_range(a: int) -> bool {
             }),
             frange.into(),
         );
-        assert_eq!(2, assists.len());
+        assert_eq!(4, assists.len());
         let mut assists = assists.into_iter();
 
         let extract_into_variable_assist = assists.next().unwrap();
@@ -543,7 +636,11 @@ pub fn test_some_range(a: int) -> bool {
                     RefactorExtract,
                 ),
                 label: "Extract into variable",
-                group: None,
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
                 target: 59..60,
                 source_change: Some(
                     SourceChange {
@@ -594,6 +691,46 @@ pub fn test_some_range(a: int) -> bool {
         "#]]
         .assert_debug_eq(&extract_into_variable_assist);
 
+        let extract_into_constant_assist = assists.next().unwrap();
+        expect![[r#"
+            Assist {
+                id: AssistId(
+                    "extract_constant",
+                    RefactorExtract,
+                ),
+                label: "Extract into constant",
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
+                target: 59..60,
+                source_change: None,
+                command: None,
+            }
+        "#]]
+        .assert_debug_eq(&extract_into_constant_assist);
+
+        let extract_into_static_assist = assists.next().unwrap();
+        expect![[r#"
+            Assist {
+                id: AssistId(
+                    "extract_static",
+                    RefactorExtract,
+                ),
+                label: "Extract into static",
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
+                target: 59..60,
+                source_change: None,
+                command: None,
+            }
+        "#]]
+        .assert_debug_eq(&extract_into_static_assist);
+
         let extract_into_function_assist = assists.next().unwrap();
         expect![[r#"
             Assist {
@@ -602,7 +739,11 @@ pub fn test_some_range(a: int) -> bool {
                     RefactorExtract,
                 ),
                 label: "Extract into function",
-                group: None,
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
                 target: 59..60,
                 source_change: None,
                 command: None,
@@ -613,7 +754,7 @@ pub fn test_some_range(a: int) -> bool {
 
     {
         let assists = assists(&db, &cfg, AssistResolveStrategy::All, frange.into());
-        assert_eq!(2, assists.len());
+        assert_eq!(4, assists.len());
         let mut assists = assists.into_iter();
 
         let extract_into_variable_assist = assists.next().unwrap();
@@ -624,7 +765,11 @@ pub fn test_some_range(a: int) -> bool {
                     RefactorExtract,
                 ),
                 label: "Extract into variable",
-                group: None,
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
                 target: 59..60,
                 source_change: Some(
                     SourceChange {
@@ -675,6 +820,140 @@ pub fn test_some_range(a: int) -> bool {
         "#]]
         .assert_debug_eq(&extract_into_variable_assist);
 
+        let extract_into_constant_assist = assists.next().unwrap();
+        expect![[r#"
+            Assist {
+                id: AssistId(
+                    "extract_constant",
+                    RefactorExtract,
+                ),
+                label: "Extract into constant",
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
+                target: 59..60,
+                source_change: Some(
+                    SourceChange {
+                        source_file_edits: {
+                            FileId(
+                                0,
+                            ): (
+                                TextEdit {
+                                    indels: [
+                                        Indel {
+                                            insert: "const",
+                                            delete: 45..47,
+                                        },
+                                        Indel {
+                                            insert: "VAR_NAME:",
+                                            delete: 48..60,
+                                        },
+                                        Indel {
+                                            insert: "i32",
+                                            delete: 61..81,
+                                        },
+                                        Indel {
+                                            insert: "=",
+                                            delete: 82..86,
+                                        },
+                                        Indel {
+                                            insert: "5;\n    if let 2..6 = VAR_NAME {\n        true\n    } else {\n        false\n    }",
+                                            delete: 87..108,
+                                        },
+                                    ],
+                                },
+                                Some(
+                                    SnippetEdit(
+                                        [
+                                            (
+                                                0,
+                                                51..51,
+                                            ),
+                                        ],
+                                    ),
+                                ),
+                            ),
+                        },
+                        file_system_edits: [],
+                        is_snippet: true,
+                    },
+                ),
+                command: Some(
+                    Rename,
+                ),
+            }
+        "#]]
+        .assert_debug_eq(&extract_into_constant_assist);
+
+        let extract_into_static_assist = assists.next().unwrap();
+        expect![[r#"
+            Assist {
+                id: AssistId(
+                    "extract_static",
+                    RefactorExtract,
+                ),
+                label: "Extract into static",
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
+                target: 59..60,
+                source_change: Some(
+                    SourceChange {
+                        source_file_edits: {
+                            FileId(
+                                0,
+                            ): (
+                                TextEdit {
+                                    indels: [
+                                        Indel {
+                                            insert: "static",
+                                            delete: 45..47,
+                                        },
+                                        Indel {
+                                            insert: "VAR_NAME:",
+                                            delete: 48..60,
+                                        },
+                                        Indel {
+                                            insert: "i32",
+                                            delete: 61..81,
+                                        },
+                                        Indel {
+                                            insert: "=",
+                                            delete: 82..86,
+                                        },
+                                        Indel {
+                                            insert: "5;\n    if let 2..6 = VAR_NAME {\n        true\n    } else {\n        false\n    }",
+                                            delete: 87..108,
+                                        },
+                                    ],
+                                },
+                                Some(
+                                    SnippetEdit(
+                                        [
+                                            (
+                                                0,
+                                                52..52,
+                                            ),
+                                        ],
+                                    ),
+                                ),
+                            ),
+                        },
+                        file_system_edits: [],
+                        is_snippet: true,
+                    },
+                ),
+                command: Some(
+                    Rename,
+                ),
+            }
+        "#]]
+        .assert_debug_eq(&extract_into_static_assist);
+
         let extract_into_function_assist = assists.next().unwrap();
         expect![[r#"
             Assist {
@@ -683,7 +962,11 @@ pub fn test_some_range(a: int) -> bool {
                     RefactorExtract,
                 ),
                 label: "Extract into function",
-                group: None,
+                group: Some(
+                    GroupLabel(
+                        "Extract into...",
+                    ),
+                ),
                 target: 59..60,
                 source_change: Some(
                     SourceChange {
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
index 69ea200db16..87c3d166ee6 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
@@ -933,6 +933,24 @@ enum TheEnum {
 }
 
 #[test]
+fn doctest_extract_constant() {
+    check_doc_test(
+        "extract_constant",
+        r#####"
+fn main() {
+    $0(1 + 2)$0 * 4;
+}
+"#####,
+        r#####"
+fn main() {
+    const $0VAR_NAME: i32 = 1 + 2;
+    VAR_NAME * 4;
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_extract_expressions_from_format_string() {
     check_doc_test(
         "extract_expressions_from_format_string",
@@ -1007,6 +1025,24 @@ fn bar(name: i32) -> i32 {
 }
 
 #[test]
+fn doctest_extract_static() {
+    check_doc_test(
+        "extract_static",
+        r#####"
+fn main() {
+    $0(1 + 2)$0 * 4;
+}
+"#####,
+        r#####"
+fn main() {
+    static $0VAR_NAME: i32 = 1 + 2;
+    VAR_NAME * 4;
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_extract_struct_from_enum_variant() {
     check_doc_test(
         "extract_struct_from_enum_variant",
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
index 0830017bd0f..3c26b043597 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -3,11 +3,13 @@
 pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
 use hir::{
     db::{ExpandDatabase, HirDatabase},
-    HasAttrs as HirHasAttrs, HirDisplay, InFile, Semantics,
+    HasAttrs as HirHasAttrs, HirDisplay, InFile, ModuleDef, PathResolution, Semantics,
 };
 use ide_db::{
-    famous_defs::FamousDefs, path_transform::PathTransform,
-    syntax_helpers::prettify_macro_expansion, RootDatabase,
+    famous_defs::FamousDefs,
+    path_transform::PathTransform,
+    syntax_helpers::{node_ext::preorder_expr, prettify_macro_expansion},
+    RootDatabase,
 };
 use stdx::format_to;
 use syntax::{
@@ -19,7 +21,7 @@ use syntax::{
     },
     ted, AstNode, AstToken, Direction, Edition, NodeOrToken, SourceFile,
     SyntaxKind::*,
-    SyntaxNode, SyntaxToken, TextRange, TextSize, T,
+    SyntaxNode, SyntaxToken, TextRange, TextSize, WalkEvent, T,
 };
 
 use crate::assist_context::{AssistContext, SourceChangeBuilder};
@@ -966,3 +968,37 @@ pub(crate) fn tt_from_syntax(node: SyntaxNode) -> Vec<NodeOrToken<ast::TokenTree
 
     tt_stack.pop().expect("parent token tree was closed before it was completed").1
 }
+
+pub fn is_body_const(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> bool {
+    let mut is_const = true;
+    preorder_expr(expr, &mut |ev| {
+        let expr = match ev {
+            WalkEvent::Enter(_) if !is_const => return true,
+            WalkEvent::Enter(expr) => expr,
+            WalkEvent::Leave(_) => return false,
+        };
+        match expr {
+            ast::Expr::CallExpr(call) => {
+                if let Some(ast::Expr::PathExpr(path_expr)) = call.expr() {
+                    if let Some(PathResolution::Def(ModuleDef::Function(func))) =
+                        path_expr.path().and_then(|path| sema.resolve_path(&path))
+                    {
+                        is_const &= func.is_const(sema.db);
+                    }
+                }
+            }
+            ast::Expr::MethodCallExpr(call) => {
+                is_const &=
+                    sema.resolve_method_call(&call).map(|it| it.is_const(sema.db)).unwrap_or(true)
+            }
+            ast::Expr::ForExpr(_)
+            | ast::Expr::ReturnExpr(_)
+            | ast::Expr::TryExpr(_)
+            | ast::Expr::YieldExpr(_)
+            | ast::Expr::AwaitExpr(_) => is_const = false,
+            _ => (),
+        }
+        !is_const
+    });
+    is_const
+}
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
index 05c2a8354da..eb96ab6ef59 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs
@@ -895,7 +895,29 @@ pub fn item_const(
         None => String::new(),
         Some(it) => format!("{it} "),
     };
-    ast_from_text(&format!("{visibility} const {name}: {ty} = {expr};"))
+    ast_from_text(&format!("{visibility}const {name}: {ty} = {expr};"))
+}
+
+pub fn item_static(
+    visibility: Option<ast::Visibility>,
+    is_unsafe: bool,
+    is_mut: bool,
+    name: ast::Name,
+    ty: ast::Type,
+    expr: Option<ast::Expr>,
+) -> ast::Static {
+    let visibility = match visibility {
+        None => String::new(),
+        Some(it) => format!("{it} "),
+    };
+    let is_unsafe = if is_unsafe { "unsafe " } else { "" };
+    let is_mut = if is_mut { "mut " } else { "" };
+    let expr = match expr {
+        Some(it) => &format!(" = {it}"),
+        None => "",
+    };
+
+    ast_from_text(&format!("{visibility}{is_unsafe}static {is_mut}{name}: {ty}{expr};"))
 }
 
 pub fn unnamed_param(ty: ast::Type) -> ast::Param {
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs
index e86c291f76c..280c5c25cb9 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs
@@ -188,6 +188,73 @@ impl SyntaxFactory {
         ast
     }
 
+    pub fn item_const(
+        &self,
+        visibility: Option<ast::Visibility>,
+        name: ast::Name,
+        ty: ast::Type,
+        expr: ast::Expr,
+    ) -> ast::Const {
+        let ast = make::item_const(visibility.clone(), name.clone(), ty.clone(), expr.clone())
+            .clone_for_update();
+
+        if let Some(mut mapping) = self.mappings() {
+            let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
+            if let Some(visibility) = visibility {
+                builder.map_node(
+                    visibility.syntax().clone(),
+                    ast.visibility().unwrap().syntax().clone(),
+                );
+            }
+            builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
+            builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone());
+            builder.map_node(expr.syntax().clone(), ast.body().unwrap().syntax().clone());
+            builder.finish(&mut mapping);
+        }
+
+        ast
+    }
+
+    pub fn item_static(
+        &self,
+        visibility: Option<ast::Visibility>,
+        is_unsafe: bool,
+        is_mut: bool,
+        name: ast::Name,
+        ty: ast::Type,
+        expr: Option<ast::Expr>,
+    ) -> ast::Static {
+        let ast = make::item_static(
+            visibility.clone(),
+            is_unsafe,
+            is_mut,
+            name.clone(),
+            ty.clone(),
+            expr.clone(),
+        )
+        .clone_for_update();
+
+        if let Some(mut mapping) = self.mappings() {
+            let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
+            if let Some(visibility) = visibility {
+                builder.map_node(
+                    visibility.syntax().clone(),
+                    ast.visibility().unwrap().syntax().clone(),
+                );
+            }
+
+            builder.map_node(name.syntax().clone(), ast.name().unwrap().syntax().clone());
+            builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone());
+
+            if let Some(expr) = expr {
+                builder.map_node(expr.syntax().clone(), ast.body().unwrap().syntax().clone());
+            }
+            builder.finish(&mut mapping);
+        }
+
+        ast
+    }
+
     pub fn turbofish_generic_arg_list(
         &self,
         args: impl IntoIterator<Item = ast::GenericArg> + Clone,