From 094c1e7b86e2280a5ca95f3cee4a2d7aa9f382d9 Mon Sep 17 00:00:00 2001 From: Viktor Lott Date: Mon, 29 May 2023 21:15:14 +0200 Subject: [PATCH 1/4] feat: inline const expr as static str --- crates/ide-assists/src/handlers/raw_string.rs | 281 +++++++++++++++++- crates/ide-assists/src/lib.rs | 1 + crates/ide-assists/src/tests/generated.rs | 21 ++ 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::()?; + + 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 @@ -1567,6 +1567,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( From 8103a10a78e065f866b5b1000468df5c43408889 Mon Sep 17 00:00:00 2001 From: Viktor Lott Date: Wed, 31 May 2023 00:14:38 +0200 Subject: [PATCH 2/4] update assist to include more literals --- crates/hir/src/lib.rs | 8 + .../src/handlers/inline_const_as_literal.rs | 695 ++++++++++++++++++ crates/ide-assists/src/handlers/raw_string.rs | 278 +------ crates/ide-assists/src/lib.rs | 3 +- crates/ide-assists/src/tests/generated.rs | 42 +- 5 files changed, 727 insertions(+), 299 deletions(-) create mode 100644 crates/ide-assists/src/handlers/inline_const_as_literal.rs diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 5926d86542..294105dc02 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3494,6 +3494,14 @@ impl Type { } } + pub fn is_scalar(&self) -> bool { + matches!(self.ty.kind(Interner), TyKind::Scalar(_)) + } + + pub fn is_tuple(&self) -> bool { + matches!(self.ty.kind(Interner), TyKind::Tuple(..)) + } + pub fn remove_ref(&self) -> Option { match &self.ty.kind(Interner) { TyKind::Ref(.., ty) => Some(self.derived(ty.clone())), diff --git a/crates/ide-assists/src/handlers/inline_const_as_literal.rs b/crates/ide-assists/src/handlers/inline_const_as_literal.rs new file mode 100644 index 0000000000..2503fb0b99 --- /dev/null +++ b/crates/ide-assists/src/handlers/inline_const_as_literal.rs @@ -0,0 +1,695 @@ +use syntax::{ast, AstNode}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: inline_const_as_literal +// +// Evaluate and inline const variable as literal. +// +// ``` +// const STRING: &str = "Hello, World!"; +// +// fn something() -> &'static str { +// STRING$0 +// } +// ``` +// -> +// ``` +// const STRING: &str = "Hello, World!"; +// +// fn something() -> &'static str { +// "Hello, World!" +// } +// ``` +pub(crate) fn inline_const_as_literal(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let variable = ctx.find_node_at_offset::()?; + + if let hir::PathResolution::Def(hir::ModuleDef::Const(konst)) = + ctx.sema.resolve_path(&variable.path()?)? + { + let konst_ty = konst.ty(ctx.sema.db); + + let fuel = 20; + + // FIXME: Add support to handle type aliases for builtin scalar types. + // + // There is no way to have a const static reference to a type that contains a interior + // mutability cell. + validate_type_recursively(ctx, Some(&konst_ty), false, fuel)?; + + let expr = konst.value(ctx.sema.db)?; + + // FIXME: Tuples and other sequence types will be inlined even though + // they might contain inner block expressions. + // e.g `(1, { 2 + 3 })` will be inline as it is. + let value = eval_and_inline_recursively(ctx, &konst, &expr, fuel)?; + + let id = AssistId("inline_const_as_literal", AssistKind::RefactorInline); + + let label = format!("Inline as literal"); + let target = variable.syntax().text_range(); + + return acc.add(id, label, target, |edit| { + edit.replace(variable.syntax().text_range(), value); + }); + } + None +} + +fn eval_and_inline_recursively( + ctx: &AssistContext<'_>, + konst: &hir::Const, + expr: &ast::Expr, + fuel: i32, +) -> Option { + match (fuel > 0, expr) { + (true, ast::Expr::BlockExpr(block)) if block.is_standalone() => { + eval_and_inline_recursively(ctx, konst, &block.tail_expr()?, fuel - 1) + } + (_, ast::Expr::Literal(lit)) => Some(lit.to_string()), + + // NOTE: For some expressions, `render_eval` will crash. + // e.g. `{ &[&[&[&[&[&[10, 20, 30]]]]]] }` will fail for `render_eval`. + // + // If these (Ref, Array or Tuple) contain evaluable expression, then we could + // visit/evaluate/walk them one by one, but I think this is enough for now. + // + // Add these if inlining without evaluation is preferred. + // (_, ast::Expr::RefExpr(expr)) => Some(expr.to_string()), + // (_, ast::Expr::ArrayExpr(expr)) => Some(expr.to_string()), + // (_, ast::Expr::TupleExpr(expr)) => Some(expr.to_string()), + + // We should fail on (false, BlockExpr) && is_standalone because we've run out of fuel. BUT + // what is the harm in trying to evaluate the block anyway? Worst case would be that it + // behaves differently when for example: fuel = 1 + // > const A: &[u32] = { &[10] }; OK because we inline ref expression. + // > const B: &[u32] = { { &[10] } }; ERROR because we evaluate block. + (_, ast::Expr::BlockExpr(_)) + | (_, ast::Expr::RefExpr(_)) + | (_, ast::Expr::ArrayExpr(_)) + | (_, ast::Expr::TupleExpr(_)) + | (_, ast::Expr::IfExpr(_)) + | (_, ast::Expr::ParenExpr(_)) + | (_, ast::Expr::MatchExpr(_)) + | (_, ast::Expr::MacroExpr(_)) + | (_, ast::Expr::BinExpr(_)) + | (_, ast::Expr::CallExpr(_)) => match konst.render_eval(ctx.sema.db) { + Ok(result) => Some(result), + Err(_) => return None, + }, + _ => return None, + } +} + +fn validate_type_recursively( + ctx: &AssistContext<'_>, + ty_hir: Option<&hir::Type>, + refed: bool, + fuel: i32, +) -> Option<()> { + match (fuel > 0, ty_hir) { + (true, Some(ty)) if ty.is_reference() => validate_type_recursively( + ctx, + ty.as_reference().map(|(ty, _)| ty).as_ref(), + true, + // FIXME: Saving fuel when `&` repeating. Might not be a good idea. + if refed { fuel } else { fuel - 1 }, + ), + (true, Some(ty)) if ty.is_array() => validate_type_recursively( + ctx, + ty.as_array(ctx.db()).map(|(ty, _)| ty).as_ref(), + false, + fuel - 1, + ), + (true, Some(ty)) if ty.is_tuple() => ty + .tuple_fields(ctx.db()) + .iter() + .all(|ty| validate_type_recursively(ctx, Some(ty), false, fuel - 1).is_some()) + .then_some(()), + (true, Some(ty)) if refed && ty.is_slice() => { + validate_type_recursively(ctx, ty.as_slice().as_ref(), false, fuel - 1) + } + (_, Some(ty)) => match ty.as_builtin() { + // `const A: str` is not correct, `const A: &builtin` is always correct. + Some(builtin) if refed || (!refed && !builtin.is_str()) => Some(()), + _ => None, + }, + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + const NUMBER: u8 = 1; + const BOOL: u8 = 2; + const STR: u8 = 4; + const CHAR: u8 = 8; + + const TEST_PAIRS: &[(&str, &str, u8)] = &[ + ("u8", "0", NUMBER), + ("u16", "0", NUMBER), + ("u32", "0", NUMBER), + ("u64", "0", NUMBER), + ("u128", "0", NUMBER), + ("usize", "0", NUMBER), + ("i8", "0", NUMBER), + ("i16", "0", NUMBER), + ("i32", "0", NUMBER), + ("i64", "0", NUMBER), + ("i128", "0", NUMBER), + ("isize", "0", NUMBER), + ("bool", "false", BOOL), + ("&str", "\"str\"", STR), + ("char", "'c'", CHAR), + ]; + + // -----------Not supported----------- + + #[test] + fn inline_const_as_literal_const_fn_call_array() { + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist_not_applicable( + inline_const_as_literal, + &format!( + r#" + const fn abc() -> [{ty}; 1] {{ [{val}] }} + const ABC: [{ty}; 1] = abc(); + fn a() {{ A$0BC }} + "# + ), + ); + }); + } + + #[test] + fn inline_const_as_literal_const_fn_call_slice() { + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist_not_applicable( + inline_const_as_literal, + &format!( + r#" + const fn abc() -> &[{ty}] {{ &[{val}] }} + const ABC: &[{ty}] = abc(); + fn a() {{ A$0BC }} + "# + ), + ); + }); + } + + #[test] + fn inline_const_as_literal_expr_as_str_lit_not_applicable_const() { + check_assist_not_applicable( + inline_const_as_literal, + r#" + const STR$0ING: &str = "Hello, World!"; + + fn something() -> &'static str { + STRING + } + "#, + ); + } + + // ---------------------------- + + #[test] + fn inline_const_as_literal_const_expr() { + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist( + inline_const_as_literal, + &format!( + r#" + const ABC: {ty} = {val}; + fn a() {{ A$0BC }} + "# + ), + &format!( + r#" + const ABC: {ty} = {val}; + fn a() {{ {val} }} + "# + ), + ); + }); + } + + #[test] + fn inline_const_as_literal_const_block_expr() { + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist( + inline_const_as_literal, + &format!( + r#" + const ABC: {ty} = {{ {val} }}; + fn a() {{ A$0BC }} + "# + ), + &format!( + r#" + const ABC: {ty} = {{ {val} }}; + fn a() {{ {val} }} + "# + ), + ); + }); + } + + #[test] + fn inline_const_as_literal_const_block_eval_expr() { + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist( + inline_const_as_literal, + &format!( + r#" + const ABC: {ty} = {{ true; {val} }}; + fn a() {{ A$0BC }} + "# + ), + &format!( + r#" + const ABC: {ty} = {{ true; {val} }}; + fn a() {{ {val} }} + "# + ), + ); + }); + } + + #[test] + fn inline_const_as_literal_const_block_eval_block_expr() { + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist( + inline_const_as_literal, + &format!( + r#" + const ABC: {ty} = {{ true; {{ {val} }} }}; + fn a() {{ A$0BC }} + "# + ), + &format!( + r#" + const ABC: {ty} = {{ true; {{ {val} }} }}; + fn a() {{ {val} }} + "# + ), + ); + }); + } + + #[test] + fn inline_const_as_literal_const_fn_call_block_nested_builtin() { + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist( + inline_const_as_literal, + &format!( + r#" + const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }} + const ABC: {ty} = abc(); + fn a() {{ A$0BC }} + "# + ), + &format!( + r#" + const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }} + const ABC: {ty} = abc(); + fn a() {{ {val} }} + "# + ), + ); + }); + } + + #[test] + fn inline_const_as_literal_const_fn_call_tuple() { + // FIXME: + // const fn abc() -> (i32) { (1) } + // - results in `1` instead of `(1)`. It handles it as a paren expr + // + // const fn abc() -> (&str, &str) { ("a", "a") } + // - results in `("", "")` instead of `("a", "a")`. + TEST_PAIRS.into_iter().for_each(|(ty, val, bit)| { + // Everything except `&str` + if bit & STR == 0 { + check_assist( + inline_const_as_literal, + &format!( + r#" + const fn abc() -> ({ty}, {ty}) {{ ({val}, {val}) }} + const ABC: ({ty}, {ty}) = abc(); + fn a() {{ A$0BC }} + "# + ), + &format!( + r#" + const fn abc() -> ({ty}, {ty}) {{ ({val}, {val}) }} + const ABC: ({ty}, {ty}) = abc(); + fn a() {{ ({val}, {val}) }} + "# + ), + ); + } + }); + } + + #[test] + fn inline_const_as_literal_const_fn_call_builtin() { + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist( + inline_const_as_literal, + &format!( + r#" + const fn abc() -> {ty} {{ {val} }} + const ABC: {ty} = abc(); + fn a() {{ A$0BC }} + "# + ), + &format!( + r#" + const fn abc() -> {ty} {{ {val} }} + const ABC: {ty} = abc(); + fn a() {{ {val} }} + "# + ), + ); + }); + } + + #[test] + fn inline_const_as_literal_scalar_operators() { + check_assist( + inline_const_as_literal, + r#" + const ABC: i32 = 1 + 2 + 3; + fn a() { A$0BC } + "#, + r#" + const ABC: i32 = 1 + 2 + 3; + fn a() { 6 } + "#, + ); + } + #[test] + fn inline_const_as_literal_block_scalar_calculate_expr() { + check_assist( + inline_const_as_literal, + r#" + const ABC: i32 = { 1 + 2 + 3 }; + fn a() { A$0BC } + "#, + r#" + const ABC: i32 = { 1 + 2 + 3 }; + fn a() { 6 } + "#, + ); + } + + #[test] + fn inline_const_as_literal_block_scalar_calculate_param_expr() { + check_assist( + inline_const_as_literal, + r#" + const ABC: i32 = { (1 + 2 + 3) }; + fn a() { A$0BC } + "#, + r#" + const ABC: i32 = { (1 + 2 + 3) }; + fn a() { 6 } + "#, + ); + } + + #[test] + fn inline_const_as_literal_block_tuple_scalar_calculate_block_expr() { + check_assist( + inline_const_as_literal, + r#" + const ABC: (i32, i32) = { (1, { 2 + 3 }) }; + fn a() { A$0BC } + "#, + r#" + const ABC: (i32, i32) = { (1, { 2 + 3 }) }; + fn a() { (1, 5) } + "#, + ); + } + + // FIXME: These won't work with `konst.render_eval(ctx.sema.db)` + // #[test] + // fn inline_const_as_literal_block_slice() { + // check_assist( + // inline_const_as_literal, + // r#" + // const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] }; + // fn a() { A$0BC } + // "#, + // r#" + // const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] }; + // fn a() { &[&[&[&[&[&[10, 20, 30]]]]]] } + // "#, + // ); + // } + + // #[test] + // fn inline_const_as_literal_block_array() { + // check_assist( + // inline_const_as_literal, + // r#" + // const ABC: [[[i32; 1]; 1]; 1] = { [[[10]]] }; + // fn a() { A$0BC } + // "#, + // r#" + // const ABC: [[[i32; 1]; 1]; 1] = { [[[10]]] }; + // fn a() { [[[10]]] } + // "#, + // ); + // } + + // #[test] + // fn inline_const_as_literal_block_tuple() { + // check_assist( + // inline_const_as_literal, + // r#" + // const ABC: (([i32; 3]), (i32), ((&str, i32), i32), i32) = { (([1, 2, 3]), (10), (("hello", 10), 20), 30) }; + // fn a() { A$0BC } + // "#, + // r#" + // const ABC: (([i32; 3]), (i32), ((&str, i32), i32), i32) = { (([1, 2, 3]), (10), (("hello", 10), 20), 30) }; + // fn a() { (([1, 2, 3]), (10), (("hello", 10), 20), 30) } + // "#, + // ); + // } + + #[test] + fn inline_const_as_literal_block_recursive() { + check_assist( + inline_const_as_literal, + r#" + const ABC: &str = { { { { "hello" } } } }; + fn a() { A$0BC } + "#, + r#" + const ABC: &str = { { { { "hello" } } } }; + fn a() { "hello" } + "#, + ); + } + + #[test] + fn inline_const_as_literal_expr_as_str_lit() { + check_assist( + inline_const_as_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_const_as_literal_eval_const_block_expr_to_str_lit() { + check_assist( + inline_const_as_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_const_as_literal_eval_const_block_macro_expr_to_str_lit() { + check_assist( + inline_const_as_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_const_as_literal_eval_const_match_expr_to_str_lit() { + check_assist( + inline_const_as_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_const_as_literal_eval_const_if_expr_to_str_lit() { + check_assist( + inline_const_as_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_const_as_literal_eval_const_macro_expr_to_str_lit() { + check_assist( + inline_const_as_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_const_as_literal_eval_const_call_expr_to_str_lit() { + check_assist( + inline_const_as_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_const_as_literal_expr_as_str_lit_not_applicable() { + check_assist_not_applicable( + inline_const_as_literal, + r#" + const STRING: &str = "Hello, World!"; + + fn something() -> &'static str { + STRING $0 + } + "#, + ); + } +} diff --git a/crates/ide-assists/src/handlers/raw_string.rs b/crates/ide-assists/src/handlers/raw_string.rs index 4852b0220e..63db606336 100644 --- a/crates/ide-assists/src/handlers/raw_string.rs +++ b/crates/ide-assists/src/handlers/raw_string.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; -use ide_db::syntax_helpers::insert_whitespace_into_node::insert_ws_into; -use syntax::{ast, ast::IsString, AstNode, AstToken, TextRange, TextSize}; +use syntax::{ast, ast::IsString, AstToken, TextRange, TextSize}; use crate::{utils::required_hashes, AssistContext, AssistId, AssistKind, Assists}; @@ -157,72 +156,6 @@ 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::()?; - - 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 super::*; @@ -549,213 +482,4 @@ 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 bf6b4fdf85..111753bf30 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -161,6 +161,7 @@ mod handlers { mod generate_delegate_methods; mod add_return_type; mod inline_call; + mod inline_const_as_literal; mod inline_local_variable; mod inline_macro; mod inline_type_alias; @@ -265,6 +266,7 @@ mod handlers { generate_new::generate_new, inline_call::inline_call, inline_call::inline_into_callers, + inline_const_as_literal::inline_const_as_literal, inline_local_variable::inline_local_variable, inline_type_alias::inline_type_alias, inline_type_alias::inline_type_alias_uses, @@ -288,7 +290,6 @@ 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 3249b6a25e..c097e07398 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1479,6 +1479,27 @@ fn foo(name: Option<&str>) { ) } +#[test] +fn doctest_inline_const_as_literal() { + check_doc_test( + "inline_const_as_literal", + r#####" +const STRING: &str = "Hello, World!"; + +fn something() -> &'static str { + STRING$0 +} +"#####, + r#####" +const STRING: &str = "Hello, World!"; + +fn something() -> &'static str { + "Hello, World!" +} +"#####, + ) +} + #[test] fn doctest_inline_into_callers() { check_doc_test( @@ -1567,27 +1588,6 @@ 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( From 2d4cb780b69e4c80ec45b7f160985a8857b41b79 Mon Sep 17 00:00:00 2001 From: Viktor Lott Date: Sun, 4 Jun 2023 21:34:25 +0200 Subject: [PATCH 3/4] fix: Use render_eval for all builtins --- .../src/handlers/inline_const_as_literal.rs | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/crates/ide-assists/src/handlers/inline_const_as_literal.rs b/crates/ide-assists/src/handlers/inline_const_as_literal.rs index 2503fb0b99..c2b537c742 100644 --- a/crates/ide-assists/src/handlers/inline_const_as_literal.rs +++ b/crates/ide-assists/src/handlers/inline_const_as_literal.rs @@ -66,8 +66,6 @@ fn eval_and_inline_recursively( (true, ast::Expr::BlockExpr(block)) if block.is_standalone() => { eval_and_inline_recursively(ctx, konst, &block.tail_expr()?, fuel - 1) } - (_, ast::Expr::Literal(lit)) => Some(lit.to_string()), - // NOTE: For some expressions, `render_eval` will crash. // e.g. `{ &[&[&[&[&[&[10, 20, 30]]]]]] }` will fail for `render_eval`. // @@ -85,6 +83,7 @@ fn eval_and_inline_recursively( // > const A: &[u32] = { &[10] }; OK because we inline ref expression. // > const B: &[u32] = { { &[10] } }; ERROR because we evaluate block. (_, ast::Expr::BlockExpr(_)) + | (_, ast::Expr::Literal(_)) | (_, ast::Expr::RefExpr(_)) | (_, ast::Expr::ArrayExpr(_)) | (_, ast::Expr::TupleExpr(_)) @@ -130,7 +129,7 @@ fn validate_type_recursively( validate_type_recursively(ctx, ty.as_slice().as_ref(), false, fuel - 1) } (_, Some(ty)) => match ty.as_builtin() { - // `const A: str` is not correct, `const A: &builtin` is always correct. + // `const A: str` is not correct, but `const A: &builtin` is. Some(builtin) if refed || (!refed && !builtin.is_str()) => Some(()), _ => None, }, @@ -438,20 +437,35 @@ mod tests { } // FIXME: These won't work with `konst.render_eval(ctx.sema.db)` - // #[test] - // fn inline_const_as_literal_block_slice() { - // check_assist( - // inline_const_as_literal, - // r#" - // const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] }; - // fn a() { A$0BC } - // "#, - // r#" - // const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] }; - // fn a() { &[&[&[&[&[&[10, 20, 30]]]]]] } - // "#, - // ); - // } + #[test] + fn inline_const_as_literal_block_slice() { + check_assist( + inline_const_as_literal, + r#" + const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] }; + fn a() { A$0BC } + "#, + r#" + const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] }; + fn a() { &[&[&[&[&[&[10, 20, 30]]]]]] } + "#, + ); + } + + #[test] + fn inline_const_as_literal_block_slice_single() { + check_assist( + inline_const_as_literal, + r#" + const ABC: [i32; 1] = { [10] }; + fn a() { A$0BC } + "#, + r#" + const ABC: [i32; 1] = { [10] }; + fn a() { [10] } + "#, + ); + } // #[test] // fn inline_const_as_literal_block_array() { From eef716d5f2a0536a77f59ee7ec1150b811682f7a Mon Sep 17 00:00:00 2001 From: Viktor Lott Date: Tue, 6 Jun 2023 00:33:55 +0200 Subject: [PATCH 4/4] fix: use render_eval instead of inlined expr --- .../src/handlers/inline_const_as_literal.rs | 343 +++++++++--------- 1 file changed, 178 insertions(+), 165 deletions(-) diff --git a/crates/ide-assists/src/handlers/inline_const_as_literal.rs b/crates/ide-assists/src/handlers/inline_const_as_literal.rs index c2b537c742..5b1540b50c 100644 --- a/crates/ide-assists/src/handlers/inline_const_as_literal.rs +++ b/crates/ide-assists/src/handlers/inline_const_as_literal.rs @@ -29,24 +29,38 @@ pub(crate) fn inline_const_as_literal(acc: &mut Assists, ctx: &AssistContext<'_> { let konst_ty = konst.ty(ctx.sema.db); + // Used as the upper limit for recursive calls if no TCO is available let fuel = 20; - // FIXME: Add support to handle type aliases for builtin scalar types. - // // There is no way to have a const static reference to a type that contains a interior // mutability cell. + + // FIXME: Add support to handle type aliases for builtin scalar types. validate_type_recursively(ctx, Some(&konst_ty), false, fuel)?; let expr = konst.value(ctx.sema.db)?; - // FIXME: Tuples and other sequence types will be inlined even though - // they might contain inner block expressions. - // e.g `(1, { 2 + 3 })` will be inline as it is. - let value = eval_and_inline_recursively(ctx, &konst, &expr, fuel)?; + let value = match expr { + ast::Expr::BlockExpr(_) + | ast::Expr::Literal(_) + | ast::Expr::RefExpr(_) + | ast::Expr::ArrayExpr(_) + | ast::Expr::TupleExpr(_) + | ast::Expr::IfExpr(_) + | ast::Expr::ParenExpr(_) + | ast::Expr::MatchExpr(_) + | ast::Expr::MacroExpr(_) + | ast::Expr::BinExpr(_) + | ast::Expr::CallExpr(_) => match konst.render_eval(ctx.sema.db) { + Ok(result) => result, + Err(_) => return None, + }, + _ => return None, + }; let id = AssistId("inline_const_as_literal", AssistKind::RefactorInline); - let label = format!("Inline as literal"); + let label = format!("Inline const as literal"); let target = variable.syntax().text_range(); return acc.add(id, label, target, |edit| { @@ -56,50 +70,6 @@ pub(crate) fn inline_const_as_literal(acc: &mut Assists, ctx: &AssistContext<'_> None } -fn eval_and_inline_recursively( - ctx: &AssistContext<'_>, - konst: &hir::Const, - expr: &ast::Expr, - fuel: i32, -) -> Option { - match (fuel > 0, expr) { - (true, ast::Expr::BlockExpr(block)) if block.is_standalone() => { - eval_and_inline_recursively(ctx, konst, &block.tail_expr()?, fuel - 1) - } - // NOTE: For some expressions, `render_eval` will crash. - // e.g. `{ &[&[&[&[&[&[10, 20, 30]]]]]] }` will fail for `render_eval`. - // - // If these (Ref, Array or Tuple) contain evaluable expression, then we could - // visit/evaluate/walk them one by one, but I think this is enough for now. - // - // Add these if inlining without evaluation is preferred. - // (_, ast::Expr::RefExpr(expr)) => Some(expr.to_string()), - // (_, ast::Expr::ArrayExpr(expr)) => Some(expr.to_string()), - // (_, ast::Expr::TupleExpr(expr)) => Some(expr.to_string()), - - // We should fail on (false, BlockExpr) && is_standalone because we've run out of fuel. BUT - // what is the harm in trying to evaluate the block anyway? Worst case would be that it - // behaves differently when for example: fuel = 1 - // > const A: &[u32] = { &[10] }; OK because we inline ref expression. - // > const B: &[u32] = { { &[10] } }; ERROR because we evaluate block. - (_, ast::Expr::BlockExpr(_)) - | (_, ast::Expr::Literal(_)) - | (_, ast::Expr::RefExpr(_)) - | (_, ast::Expr::ArrayExpr(_)) - | (_, ast::Expr::TupleExpr(_)) - | (_, ast::Expr::IfExpr(_)) - | (_, ast::Expr::ParenExpr(_)) - | (_, ast::Expr::MatchExpr(_)) - | (_, ast::Expr::MacroExpr(_)) - | (_, ast::Expr::BinExpr(_)) - | (_, ast::Expr::CallExpr(_)) => match konst.render_eval(ctx.sema.db) { - Ok(result) => Some(result), - Err(_) => return None, - }, - _ => return None, - } -} - fn validate_type_recursively( ctx: &AssistContext<'_>, ty_hir: Option<&hir::Type>, @@ -111,7 +81,7 @@ fn validate_type_recursively( ctx, ty.as_reference().map(|(ty, _)| ty).as_ref(), true, - // FIXME: Saving fuel when `&` repeating. Might not be a good idea. + // FIXME: Saving fuel when `&` repeating might not be a good idea if there's no TCO. if refed { fuel } else { fuel - 1 }, ), (true, Some(ty)) if ty.is_array() => validate_type_recursively( @@ -166,23 +136,6 @@ mod tests { ]; // -----------Not supported----------- - - #[test] - fn inline_const_as_literal_const_fn_call_array() { - TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { - check_assist_not_applicable( - inline_const_as_literal, - &format!( - r#" - const fn abc() -> [{ty}; 1] {{ [{val}] }} - const ABC: [{ty}; 1] = abc(); - fn a() {{ A$0BC }} - "# - ), - ); - }); - } - #[test] fn inline_const_as_literal_const_fn_call_slice() { TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { @@ -190,10 +143,10 @@ mod tests { inline_const_as_literal, &format!( r#" - const fn abc() -> &[{ty}] {{ &[{val}] }} - const ABC: &[{ty}] = abc(); - fn a() {{ A$0BC }} - "# + const fn abc() -> &[{ty}] {{ &[{val}] }} + const ABC: &[{ty}] = abc(); + fn a() {{ A$0BC }} + "# ), ); }); @@ -213,6 +166,76 @@ mod tests { ); } + #[test] + fn inline_const_as_struct_() { + check_assist_not_applicable( + inline_const_as_literal, + r#" + struct A; + const STRUKT: A = A; + + fn something() -> A { + STRU$0KT + } + "#, + ); + } + + #[test] + fn inline_const_as_enum_() { + check_assist_not_applicable( + inline_const_as_literal, + r#" + enum A { A, B, C } + const ENUM: A = A::A; + + fn something() -> A { + EN$0UM + } + "#, + ); + } + + #[test] + fn inline_const_as_tuple_closure() { + check_assist_not_applicable( + inline_const_as_literal, + r#" + const CLOSURE: (&dyn Fn(i32) -> i32) = (&|num| -> i32 { num }); + fn something() -> (&dyn Fn(i32) -> i32) { + STRU$0KT + } + "#, + ); + } + + #[test] + fn inline_const_as_closure_() { + check_assist_not_applicable( + inline_const_as_literal, + r#" + const CLOSURE: &dyn Fn(i32) -> i32 = &|num| -> i32 { num }; + fn something() -> &dyn Fn(i32) -> i32 { + STRU$0KT + } + "#, + ); + } + + #[test] + fn inline_const_as_fn_() { + check_assist_not_applicable( + inline_const_as_literal, + r#" + struct S(i32); + const CON: fn(i32) -> S = S; + fn something() { + let x = CO$0N; + } + "#, + ); + } + // ---------------------------- #[test] @@ -222,15 +245,15 @@ mod tests { inline_const_as_literal, &format!( r#" - const ABC: {ty} = {val}; - fn a() {{ A$0BC }} - "# + const ABC: {ty} = {val}; + fn a() {{ A$0BC }} + "# ), &format!( r#" - const ABC: {ty} = {val}; - fn a() {{ {val} }} - "# + const ABC: {ty} = {val}; + fn a() {{ {val} }} + "# ), ); }); @@ -243,15 +266,15 @@ mod tests { inline_const_as_literal, &format!( r#" - const ABC: {ty} = {{ {val} }}; - fn a() {{ A$0BC }} - "# + const ABC: {ty} = {{ {val} }}; + fn a() {{ A$0BC }} + "# ), &format!( r#" - const ABC: {ty} = {{ {val} }}; - fn a() {{ {val} }} - "# + const ABC: {ty} = {{ {val} }}; + fn a() {{ {val} }} + "# ), ); }); @@ -264,15 +287,15 @@ mod tests { inline_const_as_literal, &format!( r#" - const ABC: {ty} = {{ true; {val} }}; - fn a() {{ A$0BC }} - "# + const ABC: {ty} = {{ true; {val} }}; + fn a() {{ A$0BC }} + "# ), &format!( r#" - const ABC: {ty} = {{ true; {val} }}; - fn a() {{ {val} }} - "# + const ABC: {ty} = {{ true; {val} }}; + fn a() {{ {val} }} + "# ), ); }); @@ -285,15 +308,15 @@ mod tests { inline_const_as_literal, &format!( r#" - const ABC: {ty} = {{ true; {{ {val} }} }}; - fn a() {{ A$0BC }} - "# + const ABC: {ty} = {{ true; {{ {val} }} }}; + fn a() {{ A$0BC }} + "# ), &format!( r#" - const ABC: {ty} = {{ true; {{ {val} }} }}; - fn a() {{ {val} }} - "# + const ABC: {ty} = {{ true; {{ {val} }} }}; + fn a() {{ {val} }} + "# ), ); }); @@ -306,17 +329,17 @@ mod tests { inline_const_as_literal, &format!( r#" - const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }} - const ABC: {ty} = abc(); - fn a() {{ A$0BC }} - "# + const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }} + const ABC: {ty} = abc(); + fn a() {{ A$0BC }} + "# ), &format!( r#" - const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }} - const ABC: {ty} = abc(); - fn a() {{ {val} }} - "# + const fn abc() -> {ty} {{ {{ {{ {{ {val} }} }} }} }} + const ABC: {ty} = abc(); + fn a() {{ {val} }} + "# ), ); }); @@ -324,33 +347,24 @@ mod tests { #[test] fn inline_const_as_literal_const_fn_call_tuple() { - // FIXME: - // const fn abc() -> (i32) { (1) } - // - results in `1` instead of `(1)`. It handles it as a paren expr - // - // const fn abc() -> (&str, &str) { ("a", "a") } - // - results in `("", "")` instead of `("a", "a")`. - TEST_PAIRS.into_iter().for_each(|(ty, val, bit)| { - // Everything except `&str` - if bit & STR == 0 { - check_assist( - inline_const_as_literal, - &format!( - r#" - const fn abc() -> ({ty}, {ty}) {{ ({val}, {val}) }} - const ABC: ({ty}, {ty}) = abc(); - fn a() {{ A$0BC }} - "# - ), - &format!( - r#" - const fn abc() -> ({ty}, {ty}) {{ ({val}, {val}) }} - const ABC: ({ty}, {ty}) = abc(); - fn a() {{ ({val}, {val}) }} - "# - ), - ); - } + TEST_PAIRS.into_iter().for_each(|(ty, val, _)| { + check_assist( + inline_const_as_literal, + &format!( + r#" + const fn abc() -> ({ty}, {ty}) {{ ({val}, {val}) }} + const ABC: ({ty}, {ty}) = abc(); + fn a() {{ A$0BC }} + "# + ), + &format!( + r#" + const fn abc() -> ({ty}, {ty}) {{ ({val}, {val}) }} + const ABC: ({ty}, {ty}) = abc(); + fn a() {{ ({val}, {val}) }} + "# + ), + ); }); } @@ -436,18 +450,32 @@ mod tests { ); } - // FIXME: These won't work with `konst.render_eval(ctx.sema.db)` + // FIXME: Add support for nested ref slices when using `render_eval` #[test] fn inline_const_as_literal_block_slice() { - check_assist( + check_assist_not_applicable( inline_const_as_literal, r#" const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] }; fn a() { A$0BC } "#, + ); + } + + // FIXME: Add support for unary tuple expressions when using `render_eval`. + // `const fn abc() -> (i32) { (1) }` will results in `1` instead of `(1)` because it's evaluated + // as a paren expr. + #[test] + fn inline_const_as_literal_block_tuple() { + check_assist( + inline_const_as_literal, r#" - const ABC: &[&[&[&[&[&[i32]]]]]] = { &[&[&[&[&[&[10, 20, 30]]]]]] }; - fn a() { &[&[&[&[&[&[10, 20, 30]]]]]] } + const ABC: (([i32; 3]), (i32), ((&str, i32), i32), i32) = { (([1, 2, 3]), (10), (("hello", 10), 20), 30) }; + fn a() { A$0BC } + "#, + r#" + const ABC: (([i32; 3]), (i32), ((&str, i32), i32), i32) = { (([1, 2, 3]), (10), (("hello", 10), 20), 30) }; + fn a() { ([1, 2, 3], 10, (("hello", 10), 20), 30) } "#, ); } @@ -467,35 +495,20 @@ mod tests { ); } - // #[test] - // fn inline_const_as_literal_block_array() { - // check_assist( - // inline_const_as_literal, - // r#" - // const ABC: [[[i32; 1]; 1]; 1] = { [[[10]]] }; - // fn a() { A$0BC } - // "#, - // r#" - // const ABC: [[[i32; 1]; 1]; 1] = { [[[10]]] }; - // fn a() { [[[10]]] } - // "#, - // ); - // } - - // #[test] - // fn inline_const_as_literal_block_tuple() { - // check_assist( - // inline_const_as_literal, - // r#" - // const ABC: (([i32; 3]), (i32), ((&str, i32), i32), i32) = { (([1, 2, 3]), (10), (("hello", 10), 20), 30) }; - // fn a() { A$0BC } - // "#, - // r#" - // const ABC: (([i32; 3]), (i32), ((&str, i32), i32), i32) = { (([1, 2, 3]), (10), (("hello", 10), 20), 30) }; - // fn a() { (([1, 2, 3]), (10), (("hello", 10), 20), 30) } - // "#, - // ); - // } + #[test] + fn inline_const_as_literal_block_array() { + check_assist( + inline_const_as_literal, + r#" + const ABC: [[[i32; 1]; 1]; 1] = { [[[10]]] }; + fn a() { A$0BC } + "#, + r#" + const ABC: [[[i32; 1]; 1]; 1] = { [[[10]]] }; + fn a() { [[[10]]] } + "#, + ); + } #[test] fn inline_const_as_literal_block_recursive() {