Unnamed repository; edit this file 'description' to name the repository.
feat: inline const expr as static str
Viktor Lott 2023-06-06
parent d42d55f · commit 094c1e7
-rw-r--r--crates/ide-assists/src/handlers/raw_string.rs281
-rw-r--r--crates/ide-assists/src/lib.rs1
-rw-r--r--crates/ide-assists/src/tests/generated.rs21
3 files changed, 300 insertions, 3 deletions
diff --git a/crates/ide-assists/src/handlers/raw_string.rs b/crates/ide-assists/src/handlers/raw_string.rs
index 40ee4771d1..4852b0220e 100644
--- a/crates/ide-assists/src/handlers/raw_string.rs
+++ b/crates/ide-assists/src/handlers/raw_string.rs
@@ -1,6 +1,7 @@
use std::borrow::Cow;
-use syntax::{ast, ast::IsString, AstToken, TextRange, TextSize};
+use ide_db::syntax_helpers::insert_whitespace_into_node::insert_ws_into;
+use syntax::{ast, ast::IsString, AstNode, AstToken, TextRange, TextSize};
use crate::{utils::required_hashes, AssistContext, AssistId, AssistKind, Assists};
@@ -156,11 +157,76 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
})
}
+// Assist: inline_str_literal
+//
+// Inline const variable as static str literal.
+//
+// ```
+// const STRING: &str = "Hello, World!";
+//
+// fn something() -> &'static str {
+// STR$0ING
+// }
+// ```
+// ->
+// ```
+// const STRING: &str = "Hello, World!";
+//
+// fn something() -> &'static str {
+// "Hello, World!"
+// }
+// ```
+pub(crate) fn inline_str_literal(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let variable = ctx.find_node_at_offset::<ast::PathExpr>()?;
+
+ if let hir::PathResolution::Def(hir::ModuleDef::Const(konst)) =
+ ctx.sema.resolve_path(&variable.path()?)?
+ {
+ if !konst.ty(ctx.sema.db).as_reference()?.0.as_builtin()?.is_str() {
+ return None;
+ }
+
+ // FIXME: Make sure it's not possible to eval during diagnostic error
+ let value = match konst.value(ctx.sema.db)? {
+ ast::Expr::Literal(lit) => lit.to_string(),
+ ast::Expr::BlockExpr(_)
+ | ast::Expr::IfExpr(_)
+ | ast::Expr::MatchExpr(_)
+ | ast::Expr::CallExpr(_) => match konst.render_eval(ctx.sema.db) {
+ Ok(result) => result,
+ Err(_) => return None,
+ },
+ ast::Expr::MacroExpr(makro) => {
+ let makro_call = makro.syntax().children().find_map(ast::MacroCall::cast)?;
+ let makro_hir = ctx.sema.resolve_macro_call(&makro_call)?;
+
+ // This should not be necessary because of the `makro_call` check
+ if !makro_hir.is_fn_like(ctx.sema.db) {
+ return None;
+ }
+
+ // FIXME: Make procedural/build-in macro tests
+ insert_ws_into(ctx.sema.expand(&makro_call)?).to_string()
+ }
+ _ => return None,
+ };
+
+ let id = AssistId("inline_str_literal", AssistKind::RefactorInline);
+ let label = "Inline as static `&str` literal";
+ let target = variable.syntax().text_range();
+
+ acc.add(id, label, target, |edit| {
+ edit.replace(variable.syntax().text_range(), value);
+ });
+ }
+
+ Some(())
+}
+
#[cfg(test)]
mod tests {
- use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
-
use super::*;
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
#[test]
fn make_raw_string_target() {
@@ -483,4 +549,213 @@ string"###;
"#,
);
}
+
+ #[test]
+ fn inline_expr_as_str_lit() {
+ check_assist(
+ inline_str_literal,
+ r#"
+ const STRING: &str = "Hello, World!";
+
+ fn something() -> &'static str {
+ STR$0ING
+ }
+ "#,
+ r#"
+ const STRING: &str = "Hello, World!";
+
+ fn something() -> &'static str {
+ "Hello, World!"
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn inline_eval_const_block_expr_to_str_lit() {
+ check_assist(
+ inline_str_literal,
+ r#"
+ const STRING: &str = {
+ let x = 9;
+ if x + 10 == 21 {
+ "Hello, World!"
+ } else {
+ "World, Hello!"
+ }
+ };
+
+ fn something() -> &'static str {
+ STR$0ING
+ }
+ "#,
+ r#"
+ const STRING: &str = {
+ let x = 9;
+ if x + 10 == 21 {
+ "Hello, World!"
+ } else {
+ "World, Hello!"
+ }
+ };
+
+ fn something() -> &'static str {
+ "World, Hello!"
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn inline_eval_const_block_macro_expr_to_str_lit() {
+ check_assist(
+ inline_str_literal,
+ r#"
+ macro_rules! co {() => {"World, Hello!"};}
+ const STRING: &str = { co!() };
+
+ fn something() -> &'static str {
+ STR$0ING
+ }
+ "#,
+ r#"
+ macro_rules! co {() => {"World, Hello!"};}
+ const STRING: &str = { co!() };
+
+ fn something() -> &'static str {
+ "World, Hello!"
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn inline_eval_const_match_expr_to_str_lit() {
+ check_assist(
+ inline_str_literal,
+ r#"
+ const STRING: &str = match 9 + 10 {
+ 0..18 => "Hello, World!",
+ _ => "World, Hello!"
+ };
+
+ fn something() -> &'static str {
+ STR$0ING
+ }
+ "#,
+ r#"
+ const STRING: &str = match 9 + 10 {
+ 0..18 => "Hello, World!",
+ _ => "World, Hello!"
+ };
+
+ fn something() -> &'static str {
+ "World, Hello!"
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn inline_eval_const_if_expr_to_str_lit() {
+ check_assist(
+ inline_str_literal,
+ r#"
+ const STRING: &str = if 1 + 2 == 4 {
+ "Hello, World!"
+ } else {
+ "World, Hello!"
+ }
+
+ fn something() -> &'static str {
+ STR$0ING
+ }
+ "#,
+ r#"
+ const STRING: &str = if 1 + 2 == 4 {
+ "Hello, World!"
+ } else {
+ "World, Hello!"
+ }
+
+ fn something() -> &'static str {
+ "World, Hello!"
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn inline_eval_const_macro_expr_to_str_lit() {
+ check_assist(
+ inline_str_literal,
+ r#"
+ macro_rules! co {() => {"World, Hello!"};}
+ const STRING: &str = co!();
+
+ fn something() -> &'static str {
+ STR$0ING
+ }
+ "#,
+ r#"
+ macro_rules! co {() => {"World, Hello!"};}
+ const STRING: &str = co!();
+
+ fn something() -> &'static str {
+ "World, Hello!"
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn inline_eval_const_call_expr_to_str_lit() {
+ check_assist(
+ inline_str_literal,
+ r#"
+ const fn const_call() -> &'static str {"World, Hello!"}
+ const STRING: &str = const_call();
+
+ fn something() -> &'static str {
+ STR$0ING
+ }
+ "#,
+ r#"
+ const fn const_call() -> &'static str {"World, Hello!"}
+ const STRING: &str = const_call();
+
+ fn something() -> &'static str {
+ "World, Hello!"
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn inline_expr_as_str_lit_not_applicable() {
+ check_assist_not_applicable(
+ inline_str_literal,
+ r#"
+ const STRING: &str = "Hello, World!";
+
+ fn something() -> &'static str {
+ STRING $0
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn inline_expr_as_str_lit_not_applicable_const() {
+ check_assist_not_applicable(
+ inline_str_literal,
+ r#"
+ const STR$0ING: &str = "Hello, World!";
+
+ fn something() -> &'static str {
+ STRING
+ }
+ "#,
+ );
+ }
}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index bd282e5343..bf6b4fdf85 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -288,6 +288,7 @@ mod handlers {
raw_string::add_hash,
raw_string::make_usual_string,
raw_string::remove_hash,
+ raw_string::inline_str_literal,
remove_mut::remove_mut,
remove_unused_param::remove_unused_param,
remove_parentheses::remove_parentheses,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 8a35fd290e..3249b6a25e 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -1568,6 +1568,27 @@ fn main() {
}
#[test]
+fn doctest_inline_str_literal() {
+ check_doc_test(
+ "inline_str_literal",
+ r#####"
+const STRING: &str = "Hello, World!";
+
+fn something() -> &'static str {
+ STR$0ING
+}
+"#####,
+ r#####"
+const STRING: &str = "Hello, World!";
+
+fn something() -> &'static str {
+ "Hello, World!"
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_inline_type_alias() {
check_doc_test(
"inline_type_alias",