2 use syntax::{ast, AstNode, TextRange, TextSize};
4 use crate::{AssistContext, AssistId, AssistKind, Assists};
6 // Assist: add_return_type
8 // Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
9 // type specified. This assists is useable in a functions or closures tail expression or return type position.
12 // fn foo() { 4$02i32 }
16 // fn foo() -> i32 { 42i32 }
18 pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
19 let (fn_type, tail_expr, builder_edit_pos) = extract_tail(ctx)?;
20 let module = ctx.sema.scope(tail_expr.syntax()).module()?;
21 let ty = ctx.sema.type_of_expr(&tail_expr)?.adjusted();
25 let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
28 AssistId("add_return_type", AssistKind::RefactorRewrite),
30 FnType::Function => "Add this function's return type",
31 FnType::Closure { .. } => "Add this closure's return type",
33 tail_expr.syntax().text_range(),
35 match builder_edit_pos {
36 InsertOrReplace::Insert(insert_pos) => {
37 builder.insert(insert_pos, &format!("-> {} ", ty))
39 InsertOrReplace::Replace(text_range) => {
40 builder.replace(text_range, &format!("-> {}", ty))
43 if let FnType::Closure { wrap_expr: true } = fn_type {
44 cov_mark::hit!(wrap_closure_non_block_expr);
45 // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
46 builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
52 enum InsertOrReplace {
57 /// Check the potentially already specified return type and reject it or turn it into a builder command
59 fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
61 Some(ret_ty) => match ret_ty.ty() {
62 Some(ast::Type::InferType(_)) | None => {
63 cov_mark::hit!(existing_infer_ret_type);
64 cov_mark::hit!(existing_infer_ret_type_closure);
65 Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
68 cov_mark::hit!(existing_ret_type);
69 cov_mark::hit!(existing_ret_type_closure);
73 None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
79 Closure { wrap_expr: bool },
82 fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
83 let (fn_type, tail_expr, return_type_range, action) =
84 if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
85 let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
86 let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
88 let body = closure.body()?;
89 let body_start = body.syntax().first_token()?.text_range().start();
90 let (tail_expr, wrap_expr) = match body {
91 ast::Expr::BlockExpr(block) => (block.tail_expr()?, false),
95 let ret_range = TextRange::new(rpipe_pos, body_start);
96 (FnType::Closure { wrap_expr }, tail_expr, ret_range, action)
98 let func = ctx.find_node_at_offset::<ast::Fn>()?;
99 let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
100 let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
102 let body = func.body()?;
103 let tail_expr = body.tail_expr()?;
105 let ret_range_end = body.l_curly_token()?.text_range().start();
106 let ret_range = TextRange::new(rparen_pos, ret_range_end);
107 (FnType::Function, tail_expr, ret_range, action)
109 let frange = ctx.frange.range;
110 if return_type_range.contains_range(frange) {
111 cov_mark::hit!(cursor_in_ret_position);
112 cov_mark::hit!(cursor_in_ret_position_closure);
113 } else if tail_expr.syntax().text_range().contains_range(frange) {
114 cov_mark::hit!(cursor_on_tail);
115 cov_mark::hit!(cursor_on_tail_closure);
119 Some((fn_type, tail_expr, action))
124 use crate::tests::{check_assist, check_assist_not_applicable};
129 fn infer_return_type_specified_inferred() {
130 cov_mark::check!(existing_infer_ret_type);
143 fn infer_return_type_specified_inferred_closure() {
144 cov_mark::check!(existing_infer_ret_type_closure);
157 fn infer_return_type_cursor_at_return_type_pos() {
158 cov_mark::check!(cursor_in_ret_position);
171 fn infer_return_type_cursor_at_return_type_pos_closure() {
172 cov_mark::check!(cursor_in_ret_position_closure);
185 fn infer_return_type() {
186 cov_mark::check!(cursor_on_tail);
199 fn infer_return_type_nested() {
220 fn not_applicable_ret_type_specified() {
221 cov_mark::check!(existing_ret_type);
222 check_assist_not_applicable(
231 fn not_applicable_non_tail_expr() {
232 check_assist_not_applicable(
242 fn not_applicable_unit_return_type() {
243 check_assist_not_applicable(
252 fn infer_return_type_closure_block() {
253 cov_mark::check!(cursor_on_tail_closure);
270 fn infer_return_type_closure() {
277 |x: i32| -> i32 { x };
283 fn infer_return_type_closure_wrap() {
284 cov_mark::check!(wrap_closure_non_block_expr);
297 fn infer_return_type_nested_closure() {
322 fn not_applicable_ret_type_specified_closure() {
323 cov_mark::check!(existing_ret_type_closure);
324 check_assist_not_applicable(
333 fn not_applicable_non_tail_expr_closure() {
334 check_assist_not_applicable(