1 use hir::{HirDisplay, ModuleDef, PathResolution, Semantics};
3 assists::{AssistId, AssistKind},
5 helpers::node_ext::preorder_expr,
8 use stdx::to_upper_snake_case;
10 ast::{self, make, HasName},
15 assist_context::{AssistContext, Assists},
16 utils::{render_snippet, Cursor},
19 // Assist: promote_local_to_const
21 // Promotes a local variable to a const item changing its name to a `SCREAMING_SNAKE_CASE` variant
22 // if the local uses no non-const expressions.
29 // println!("It's true");
31 // println!("It's false");
38 // const $0FOO: bool = true;
41 // println!("It's true");
43 // println!("It's false");
47 pub(crate) fn promote_local_to_const(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
48 let pat = ctx.find_node_at_offset::<ast::IdentPat>()?;
49 let name = pat.name()?;
50 if !pat.is_simple_ident() {
51 cov_mark::hit!(promote_local_non_simple_ident);
54 let let_stmt = pat.syntax().parent().and_then(ast::LetStmt::cast)?;
56 let module = ctx.sema.scope(pat.syntax()).module()?;
57 let local = ctx.sema.to_def(&pat)?;
58 let ty = ctx.sema.type_of_pat(&pat.into())?.original;
60 if ty.contains_unknown() || ty.is_closure() {
61 cov_mark::hit!(promote_lcoal_not_applicable_if_ty_not_inferred);
64 let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
66 let initializer = let_stmt.initializer()?;
67 if !is_body_const(&ctx.sema, &initializer) {
68 cov_mark::hit!(promote_local_non_const);
71 let target = let_stmt.syntax().text_range();
73 AssistId("promote_local_to_const", AssistKind::Refactor),
74 "Promote local to constant",
77 let name = to_upper_snake_case(&name.to_string());
78 let usages = Definition::Local(local).usages(&ctx.sema).all();
79 if let Some(usages) = usages.references.get(&ctx.file_id()) {
81 builder.replace(usage.range, &name);
85 let item = make::item_const(None, make::name(&name), make::ty(&ty), initializer);
86 match ctx.config.snippet_cap.zip(item.name()) {
87 Some((cap, name)) => builder.replace_snippet(
90 render_snippet(cap, item.syntax(), Cursor::Before(name.syntax())),
92 None => builder.replace(target, item.to_string()),
98 fn is_body_const(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> bool {
99 let mut is_const = true;
100 preorder_expr(expr, &mut |ev| {
101 let expr = match ev {
102 WalkEvent::Enter(_) if !is_const => return true,
103 WalkEvent::Enter(expr) => expr,
104 WalkEvent::Leave(_) => return false,
107 ast::Expr::CallExpr(call) => {
108 if let Some(ast::Expr::PathExpr(path_expr)) = call.expr() {
109 if let Some(PathResolution::Def(ModuleDef::Function(func))) =
110 path_expr.path().and_then(|path| sema.resolve_path(&path))
112 is_const &= func.is_const(sema.db);
116 ast::Expr::MethodCallExpr(call) => {
118 sema.resolve_method_call(&call).map(|it| it.is_const(sema.db)).unwrap_or(true)
120 ast::Expr::BoxExpr(_)
121 | ast::Expr::ForExpr(_)
122 | ast::Expr::ReturnExpr(_)
123 | ast::Expr::TryExpr(_)
124 | ast::Expr::YieldExpr(_)
125 | ast::Expr::AwaitExpr(_) => is_const = false,
135 use crate::tests::{check_assist, check_assist_not_applicable};
142 promote_local_to_const,
159 fn not_applicable_non_const_meth_call() {
160 cov_mark::check!(promote_local_non_const);
161 check_assist_not_applicable(
162 promote_local_to_const,
176 fn not_applicable_non_const_call() {
177 check_assist_not_applicable(
178 promote_local_to_const,
189 fn not_applicable_unknown_ty() {
190 cov_mark::check!(promote_lcoal_not_applicable_if_ty_not_inferred);
191 check_assist_not_applicable(
192 promote_local_to_const,
202 fn not_applicable_non_simple_ident() {
203 cov_mark::check!(promote_local_non_simple_ident);
204 check_assist_not_applicable(
205 promote_local_to_const,
212 check_assist_not_applicable(
213 promote_local_to_const,