Skip to content

Commit 9ca7352

Browse files
committed
generate method assist
1 parent b2b2425 commit 9ca7352

File tree

3 files changed

+154
-0
lines changed

3 files changed

+154
-0
lines changed

crates/ide_assists/src/handlers/generate_function.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,35 @@ pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Optio
7979
)
8080
}
8181

82+
pub(crate) fn generate_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
83+
let fn_name: ast::NameRef = ctx.find_node_at_offset()?;
84+
let call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
85+
let module = ctx.sema.scope(call.syntax()).module();
86+
let ty = ctx.sema.type_of_expr(&call.receiver()?)?.as_adt()?;
87+
88+
let function_builder = FunctionBuilder::from_method_call(ctx, &call, &fn_name, module)?;
89+
let target = call.syntax().text_range();
90+
91+
acc.add(
92+
AssistId("generate_method", AssistKind::Generate),
93+
format!("Generate `{}` method", function_builder.fn_name),
94+
target,
95+
|builder| {
96+
let function_template = function_builder.render();
97+
builder.edit_file(function_template.file);
98+
let new_fn = format!(
99+
"impl {} {{{}}}",
100+
ty.name(ctx.sema.db),
101+
function_template.to_string(ctx.config.snippet_cap)
102+
);
103+
match ctx.config.snippet_cap {
104+
Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
105+
None => builder.insert(function_template.insert_offset, new_fn),
106+
}
107+
},
108+
)
109+
}
110+
82111
struct FunctionTemplate {
83112
insert_offset: TextSize,
84113
leading_ws: String,
@@ -181,6 +210,70 @@ impl FunctionBuilder {
181210
})
182211
}
183212

213+
fn from_method_call(
214+
ctx: &AssistContext,
215+
call: &ast::MethodCallExpr,
216+
name: &ast::NameRef,
217+
target_module: Option<hir::Module>,
218+
) -> Option<Self> {
219+
let mut file = ctx.frange.file_id;
220+
let target = match &target_module {
221+
Some(target_module) => {
222+
let module_source = target_module.definition_source(ctx.db());
223+
let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
224+
file = in_file;
225+
target
226+
}
227+
None => next_space_for_fn_after_method_call_site(call)?,
228+
};
229+
let needs_pub = false;
230+
let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?;
231+
let fn_name = make::name(&name.text());
232+
let (type_params, params) = method_args(ctx, target_module, call)?;
233+
234+
let await_expr = call.syntax().parent().and_then(ast::AwaitExpr::cast);
235+
let is_async = await_expr.is_some();
236+
237+
// should_render_snippet intends to express a rough level of confidence about
238+
// the correctness of the return type.
239+
//
240+
// If we are able to infer some return type, and that return type is not unit, we
241+
// don't want to render the snippet. The assumption here is in this situation the
242+
// return type is just as likely to be correct as any other part of the generated
243+
// function.
244+
//
245+
// In the case where the return type is inferred as unit it is likely that the
246+
// user does in fact intend for this generated function to return some non unit
247+
// type, but that the current state of their code doesn't allow that return type
248+
// to be accurately inferred.
249+
let (ret_ty, should_render_snippet) = {
250+
match ctx.sema.type_of_expr(&ast::Expr::MethodCallExpr(call.clone())) {
251+
Some(ty) if ty.is_unknown() || ty.is_unit() => (make::ty_unit(), true),
252+
Some(ty) => {
253+
let rendered = ty.display_source_code(ctx.db(), target_module.into());
254+
match rendered {
255+
Ok(rendered) => (make::ty(&rendered), false),
256+
Err(_) => (make::ty_unit(), true),
257+
}
258+
}
259+
None => (make::ty_unit(), true),
260+
}
261+
};
262+
let ret_type = make::ret_type(ret_ty);
263+
264+
Some(Self {
265+
target,
266+
fn_name,
267+
type_params,
268+
params,
269+
ret_type,
270+
should_render_snippet,
271+
file,
272+
needs_pub,
273+
is_async,
274+
})
275+
}
276+
184277
fn render(self) -> FunctionTemplate {
185278
let placeholder_expr = make::ext::expr_todo();
186279
let fn_body = make::block_expr(vec![], Some(placeholder_expr));
@@ -280,6 +373,40 @@ fn fn_args(
280373
Some((None, make::param_list(None, params)))
281374
}
282375

376+
fn method_args(
377+
ctx: &AssistContext,
378+
target_module: hir::Module,
379+
call: &ast::MethodCallExpr,
380+
) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
381+
let mut arg_names = Vec::new();
382+
let mut arg_types = Vec::new();
383+
for arg in call.arg_list()?.args() {
384+
arg_names.push(match fn_arg_name(&arg) {
385+
Some(name) => name,
386+
None => String::from("arg"),
387+
});
388+
arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
389+
Some(ty) => {
390+
if ty.len() > 0 && ty.starts_with('&') {
391+
if let Some((new_ty, _)) = useless_type_special_case("", &ty[1..].to_owned()) {
392+
new_ty
393+
} else {
394+
ty
395+
}
396+
} else {
397+
ty
398+
}
399+
}
400+
None => String::from("()"),
401+
});
402+
}
403+
deduplicate_arg_names(&mut arg_names);
404+
let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| {
405+
make::param(make::ext::simple_ident_pat(make::name(&name)).into(), make::ty(&ty))
406+
});
407+
Some((None, make::param_list(Some(make::self_param()), params)))
408+
}
409+
283410
/// Makes duplicate argument names unique by appending incrementing numbers.
284411
///
285412
/// ```
@@ -368,6 +495,28 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu
368495
last_ancestor.map(GeneratedFunctionTarget::BehindItem)
369496
}
370497

498+
fn next_space_for_fn_after_method_call_site(
499+
expr: &ast::MethodCallExpr,
500+
) -> Option<GeneratedFunctionTarget> {
501+
let mut ancestors = expr.syntax().ancestors().peekable();
502+
let mut last_ancestor: Option<SyntaxNode> = None;
503+
while let Some(next_ancestor) = ancestors.next() {
504+
match next_ancestor.kind() {
505+
SyntaxKind::SOURCE_FILE => {
506+
break;
507+
}
508+
SyntaxKind::ITEM_LIST => {
509+
if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
510+
break;
511+
}
512+
}
513+
_ => {}
514+
}
515+
last_ancestor = Some(next_ancestor);
516+
}
517+
last_ancestor.map(GeneratedFunctionTarget::BehindItem)
518+
}
519+
371520
fn next_space_for_fn_in_module(
372521
db: &dyn hir::db::AstDatabase,
373522
module_source: &hir::InFile<hir::ModuleSource>,

crates/ide_assists/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ mod handlers {
151151
generate_enum_projection_method::generate_enum_try_into_method,
152152
generate_from_impl_for_enum::generate_from_impl_for_enum,
153153
generate_function::generate_function,
154+
generate_function::generate_method,
154155
generate_getter::generate_getter,
155156
generate_getter::generate_getter_mut,
156157
generate_impl::generate_impl,

crates/syntax/src/ast/make.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,10 @@ pub fn param(pat: ast::Pat, ty: ast::Type) -> ast::Param {
531531
ast_from_text(&format!("fn f({}: {}) {{ }}", pat, ty))
532532
}
533533

534+
pub fn self_param() -> ast::SelfParam {
535+
ast_from_text(&format!("fn f(&self) {{ }}"))
536+
}
537+
534538
pub fn ret_type(ty: ast::Type) -> ast::RetType {
535539
ast_from_text(&format!("fn f() -> {} {{ }}", ty))
536540
}

0 commit comments

Comments
 (0)