Auto merge of #17138 - Kohei316:generate-function-assist-for-new, r=Veykril

feature: Make generate function assist generate a function as a constructor if the generated function has the name "new" and is an asscociated function.

close #17050
This PR makes `generate function assist` generate a function as a constructor if the generated function has the name "new" and is an asscociated function.
If the asscociate type is a record struct, it generates the constructor like this.
```rust
impl Foo {
    fn new() -> Self {
        Self { field_1: todo!(), field_2: todo!() }
    }
}
```
If the asscociate type is a tuple struct, it generates the constructor like this.
```rust
impl Foo {
    fn new() -> Self {
        Self(todo!(), todo!())
    }
}
```
If the asscociate type is a unit struct, it generates the constructor like this.
```rust
impl Foo {
    fn new() -> Self {
        Self
    }
}
```
If the asscociate type is another adt, it generates the constructor like this.
```rust
impl Foo {
    fn new() -> Self {
        todo!()
    }
}
```
This commit is contained in:
bors 2024-04-30 12:09:34 +00:00
commit 4ce1c6cb97
2 changed files with 270 additions and 26 deletions

View File

@ -1489,6 +1489,14 @@ pub fn lifetime(&self, db: &dyn HirDatabase) -> Option<LifetimeParamData> {
.map(|arena| arena.1.clone())
}
pub fn as_struct(&self) -> Option<Struct> {
if let Self::Struct(v) = self {
Some(*v)
} else {
None
}
}
pub fn as_enum(&self) -> Option<Enum> {
if let Self::Enum(v) = self {
Some(*v)

View File

@ -1,6 +1,6 @@
use hir::{
Adt, AsAssocItem, HasSource, HirDisplay, HirFileIdExt, Module, PathResolution, Semantics, Type,
TypeInfo,
Adt, AsAssocItem, HasSource, HirDisplay, HirFileIdExt, Module, PathResolution, Semantics,
StructKind, Type, TypeInfo,
};
use ide_db::{
base_db::FileId,
@ -15,8 +15,8 @@
use stdx::to_lower_snake_case;
use syntax::{
ast::{
self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, CallExpr, HasArgList,
HasGenericParams, HasModuleItem, HasTypeBounds,
self, edit::IndentLevel, edit_in_place::Indent, make, AstNode, BlockExpr, CallExpr,
HasArgList, HasGenericParams, HasModuleItem, HasTypeBounds,
},
ted, SyntaxKind, SyntaxNode, TextRange, T,
};
@ -66,7 +66,7 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
}
let fn_name = &*name_ref.text();
let TargetInfo { target_module, adt_name, target, file } =
let TargetInfo { target_module, adt_info, target, file } =
fn_target_info(ctx, path, &call, fn_name)?;
if let Some(m) = target_module {
@ -75,15 +75,16 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
}
}
let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?;
let function_builder =
FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target, &adt_info)?;
let text_range = call.syntax().text_range();
let label = format!("Generate {} function", function_builder.fn_name);
add_func_to_accumulator(acc, ctx, text_range, function_builder, file, adt_name, label)
add_func_to_accumulator(acc, ctx, text_range, function_builder, file, adt_info, label)
}
struct TargetInfo {
target_module: Option<Module>,
adt_name: Option<hir::Name>,
adt_info: Option<AdtInfo>,
target: GeneratedFunctionTarget,
file: FileId,
}
@ -91,11 +92,11 @@ struct TargetInfo {
impl TargetInfo {
fn new(
target_module: Option<Module>,
adt_name: Option<hir::Name>,
adt_info: Option<AdtInfo>,
target: GeneratedFunctionTarget,
file: FileId,
) -> Self {
Self { target_module, adt_name, target, file }
Self { target_module, adt_info, target, file }
}
}
@ -157,9 +158,9 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
target,
)?;
let text_range = call.syntax().text_range();
let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None };
let adt_info = AdtInfo::new(adt, impl_.is_some());
let label = format!("Generate {} method", function_builder.fn_name);
add_func_to_accumulator(acc, ctx, text_range, function_builder, file, adt_name, label)
add_func_to_accumulator(acc, ctx, text_range, function_builder, file, Some(adt_info), label)
}
fn add_func_to_accumulator(
@ -168,7 +169,7 @@ fn add_func_to_accumulator(
text_range: TextRange,
function_builder: FunctionBuilder,
file: FileId,
adt_name: Option<hir::Name>,
adt_info: Option<AdtInfo>,
label: String,
) -> Option<()> {
acc.add(AssistId("generate_function", AssistKind::Generate), label, text_range, |edit| {
@ -177,8 +178,14 @@ fn add_func_to_accumulator(
let target = function_builder.target.clone();
let func = function_builder.render(ctx.config.snippet_cap, edit);
if let Some(name) = adt_name {
let name = make::ty_path(make::ext::ident_path(&format!("{}", name.display(ctx.db()))));
if let Some(adt) =
adt_info
.and_then(|adt_info| if adt_info.impl_exists { None } else { Some(adt_info.adt) })
{
let name = make::ty_path(make::ext::ident_path(&format!(
"{}",
adt.name(ctx.db()).display(ctx.db())
)));
// FIXME: adt may have generic params.
let impl_ = make::impl_(None, None, name, None, None).clone_for_update();
@ -210,6 +217,7 @@ struct FunctionBuilder {
generic_param_list: Option<ast::GenericParamList>,
where_clause: Option<ast::WhereClause>,
params: ast::ParamList,
fn_body: BlockExpr,
ret_type: Option<ast::RetType>,
should_focus_return_type: bool,
visibility: Visibility,
@ -225,6 +233,7 @@ fn from_call(
fn_name: &str,
target_module: Option<Module>,
target: GeneratedFunctionTarget,
adt_info: &Option<AdtInfo>,
) -> Option<Self> {
let target_module =
target_module.or_else(|| ctx.sema.scope(target.syntax()).map(|it| it.module()))?;
@ -243,9 +252,27 @@ fn from_call(
let await_expr = call.syntax().parent().and_then(ast::AwaitExpr::cast);
let is_async = await_expr.is_some();
let expr_for_ret_ty = await_expr.map_or_else(|| call.clone().into(), |it| it.into());
let (ret_type, should_focus_return_type) =
make_return_type(ctx, &expr_for_ret_ty, target_module, &mut necessary_generic_params);
let ret_type;
let should_focus_return_type;
let fn_body;
// If generated function has the name "new" and is an associated function, we generate fn body
// as a constructor and assume a "Self" return type.
if let Some(body) = make_fn_body_as_new_function(ctx, &fn_name.text(), adt_info) {
ret_type = Some(make::ret_type(make::ty_path(make::ext::ident_path("Self"))));
should_focus_return_type = false;
fn_body = body;
} else {
let expr_for_ret_ty = await_expr.map_or_else(|| call.clone().into(), |it| it.into());
(ret_type, should_focus_return_type) = make_return_type(
ctx,
&expr_for_ret_ty,
target_module,
&mut necessary_generic_params,
);
let placeholder_expr = make::ext::expr_todo();
fn_body = make::block_expr(vec![], Some(placeholder_expr));
};
let (generic_param_list, where_clause) =
fn_generic_params(ctx, necessary_generic_params, &target)?;
@ -256,6 +283,7 @@ fn from_call(
generic_param_list,
where_clause,
params,
fn_body,
ret_type,
should_focus_return_type,
visibility,
@ -294,12 +322,16 @@ fn from_method_call(
let (generic_param_list, where_clause) =
fn_generic_params(ctx, necessary_generic_params, &target)?;
let placeholder_expr = make::ext::expr_todo();
let fn_body = make::block_expr(vec![], Some(placeholder_expr));
Some(Self {
target,
fn_name,
generic_param_list,
where_clause,
params,
fn_body,
ret_type,
should_focus_return_type,
visibility,
@ -308,8 +340,6 @@ fn from_method_call(
}
fn render(self, cap: Option<SnippetCap>, edit: &mut SourceChangeBuilder) -> ast::Fn {
let placeholder_expr = make::ext::expr_todo();
let fn_body = make::block_expr(vec![], Some(placeholder_expr));
let visibility = match self.visibility {
Visibility::None => None,
Visibility::Crate => Some(make::visibility_pub_crate()),
@ -321,7 +351,7 @@ fn render(self, cap: Option<SnippetCap>, edit: &mut SourceChangeBuilder) -> ast:
self.generic_param_list,
self.where_clause,
self.params,
fn_body,
self.fn_body,
self.ret_type,
self.is_async,
false, // FIXME : const and unsafe are not handled yet.
@ -391,6 +421,53 @@ fn make_return_type(
(ret_type, should_focus_return_type)
}
fn make_fn_body_as_new_function(
ctx: &AssistContext<'_>,
fn_name: &str,
adt_info: &Option<AdtInfo>,
) -> Option<ast::BlockExpr> {
if fn_name != "new" {
return None;
};
let adt_info = adt_info.as_ref()?;
let path_self = make::ext::ident_path("Self");
let placeholder_expr = make::ext::expr_todo();
let tail_expr = if let Some(strukt) = adt_info.adt.as_struct() {
match strukt.kind(ctx.db()) {
StructKind::Record => {
let fields = strukt
.fields(ctx.db())
.iter()
.map(|field| {
make::record_expr_field(
make::name_ref(&format!("{}", field.name(ctx.db()).display(ctx.db()))),
Some(placeholder_expr.clone()),
)
})
.collect::<Vec<_>>();
make::record_expr(path_self, make::record_expr_field_list(fields)).into()
}
StructKind::Tuple => {
let args = strukt
.fields(ctx.db())
.iter()
.map(|_| placeholder_expr.clone())
.collect::<Vec<_>>();
make::expr_call(make::expr_path(path_self), make::arg_list(args))
}
StructKind::Unit => make::expr_path(path_self),
}
} else {
placeholder_expr
};
let fn_body = make::block_expr(vec![], Some(tail_expr));
Some(fn_body)
}
fn get_fn_target_info(
ctx: &AssistContext<'_>,
target_module: Option<Module>,
@ -443,8 +520,8 @@ fn assoc_fn_target_info(
}
let (impl_, file) = get_adt_source(ctx, &adt, fn_name)?;
let target = get_method_target(ctx, &impl_, &adt)?;
let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None };
Some(TargetInfo::new(target_module, adt_name, target, file))
let adt_info = AdtInfo::new(adt, impl_.is_some());
Some(TargetInfo::new(target_module, Some(adt_info), target, file))
}
#[derive(Clone)]
@ -560,6 +637,17 @@ fn insert_fn_at(&self, edit: &mut SourceChangeBuilder, func: ast::Fn) {
}
}
struct AdtInfo {
adt: hir::Adt,
impl_exists: bool,
}
impl AdtInfo {
fn new(adt: Adt, impl_exists: bool) -> Self {
Self { adt, impl_exists }
}
}
/// Computes parameter list for the generated function.
fn fn_args(
ctx: &AssistContext<'_>,
@ -2758,18 +2846,18 @@ fn applicable_for_enum_method() {
r"
enum Foo {}
fn main() {
Foo::new$0();
Foo::bar$0();
}
",
r"
enum Foo {}
impl Foo {
fn new() ${0:-> _} {
fn bar() ${0:-> _} {
todo!()
}
}
fn main() {
Foo::new();
Foo::bar();
}
",
)
@ -2849,4 +2937,152 @@ fn main() {
",
);
}
#[test]
fn new_function_assume_self_type() {
check_assist(
generate_function,
r"
pub struct Foo {
field_1: usize,
field_2: String,
}
fn main() {
let foo = Foo::new$0();
}
",
r"
pub struct Foo {
field_1: usize,
field_2: String,
}
impl Foo {
fn new() -> Self {
${0:Self { field_1: todo!(), field_2: todo!() }}
}
}
fn main() {
let foo = Foo::new();
}
",
)
}
#[test]
fn new_function_assume_self_type_for_tuple_struct() {
check_assist(
generate_function,
r"
pub struct Foo (usize, String);
fn main() {
let foo = Foo::new$0();
}
",
r"
pub struct Foo (usize, String);
impl Foo {
fn new() -> Self {
${0:Self(todo!(), todo!())}
}
}
fn main() {
let foo = Foo::new();
}
",
)
}
#[test]
fn new_function_assume_self_type_for_unit_struct() {
check_assist(
generate_function,
r"
pub struct Foo;
fn main() {
let foo = Foo::new$0();
}
",
r"
pub struct Foo;
impl Foo {
fn new() -> Self {
${0:Self}
}
}
fn main() {
let foo = Foo::new();
}
",
)
}
#[test]
fn new_function_assume_self_type_for_enum() {
check_assist(
generate_function,
r"
pub enum Foo {}
fn main() {
let foo = Foo::new$0();
}
",
r"
pub enum Foo {}
impl Foo {
fn new() -> Self {
${0:todo!()}
}
}
fn main() {
let foo = Foo::new();
}
",
)
}
#[test]
fn new_function_assume_self_type_with_args() {
check_assist(
generate_function,
r#"
pub struct Foo {
field_1: usize,
field_2: String,
}
struct Baz;
fn baz() -> Baz { Baz }
fn main() {
let foo = Foo::new$0(baz(), baz(), "foo", "bar");
}
"#,
r#"
pub struct Foo {
field_1: usize,
field_2: String,
}
impl Foo {
fn new(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) -> Self {
${0:Self { field_1: todo!(), field_2: todo!() }}
}
}
struct Baz;
fn baz() -> Baz { Baz }
fn main() {
let foo = Foo::new(baz(), baz(), "foo", "bar");
}
"#,
)
}
}