Repository: rust-analyzer/rust-analyzer
Pull request #3746 (2020-03-27 17:09:58)

Add create_function assist
The function part of #3639, creating methods will come later

- [X] Function arguments
     - [X] Function call arguments
     - [x] Method call arguments
     - [x] Literal arguments
     - [x] Variable reference arguments
- [X] Migrate to `ast::make` API
    Done, but there are some ugly spots.

Issues to handle in another PR:
- function reference arguments: Their type isn't printed properly right now.
    The "insert explicit type" assist has the same issue and this is probably a relatively rare usecase.

- generating proper names for all kinds of argument expressions (if, loop, ...?)
    Without this, it's totally possible for the assist to generate invalid argument names.
    I think the assist it's already helpful enough to be shipped as it is, at least for me the main usecase involves passing in named references.
    Besides, the Rust tooling ecosystem is immature enough that some janky behaviour in a new assist probably won't scare anyone off.

- select the generated placeholder body so it's a bit easier to overwrite it

- create method (`self.foo<|>(..)` or `some_foo.foo<|>(..)`) instead of create_function.
    The main difference would be finding (or creating) the impl block and inserting the `self` argument correctly

- more specific default arg names for literals.
    So far, every generated argument whose name can't be taken from the call site is called `arg` (with a number suffix if necessary).

- creating functions in another module of the same crate.
    E.g. when typing `some_mod::foo<|>(...)` when in `lib.rs`, I'd want to have `foo` generated in `some_mod.rs` and jump there.
    Issues: the mod could exist in `some_mod.rs`, in `lib.rs` as `mod some_mod`, or inside another mod but be imported via `use other_mod::some_mod`.

- refer to arguments of the generated function with a qualified path if the types aren't imported yet
    (alternative: run autoimport. i think starting with a qualified path is cleaner and there's already an assist to replace a qualified path with an import and an unqualified path)

- add type arguments of the arguments to the generated function

- Autocomplete functions with information from unresolved calls (see https://github.com/rust-analyzer/rust-analyzer/pull/3746#issuecomment-605281323)
    Issues: see https://github.com/rust-analyzer/rust-analyzer/pull/3746#issuecomment-605282542. The unresolved call could be anywhere. But just offering this autocompletion for unresolved calls in the same module would already be cool.

matklad: It just occured to me that another plausible UI here is completion:

```
fn foo() {
     bar("", baz());
}

fn ba<|> // completes to bar(_: &str, _: ())
```
lnicola: > It just occured to me that another plausible UI here is completion:

That seems a bit non-local. Instead of adding the function under the cursor to its proper scope, you need to look for unresolved function calls in all other scopes when typing a new function. It seems harder.
TimoFreiberg: I'd really like to get qualified paths and generics working in this PR, but I'm not sure how much is missing.
I might need some help (or another few days of digging through the codebase)

For what it's worth, I find the existing state to be very helpful already, so I'd be easily convinced to handle those two issues in another PR as well ;)
flodiebold: I think it's always better to keep PRs small. These two issues won't be trivial to solve completely.
TimoFreiberg: About the `ast::make` API: do I understand it correctly that it uses a roundtrip through the parsers to ensure that no invalid syntax is generated?

Trying to use it instead of building the string directly made it a bit harder for me to place the cursor in the function body though. The only way I see is to search through the AST descendants of the `FnDef` again, which seems quite convoluted.

I'm now also struggling with the two newlines that need to be inserted before the `FnDef`, I'm still simply inserting those as I haven't found a way to add that information to my `FnDef` somehow, but now that I'm using `IndentLevel::from_node`, the first line is not indented properly anymore (see the test failure of `add_function_with_no_args_in_same_module`).
If I'm using it wrong, I'd be glad for some help.
Otherwise... well, the tests run in the first commit that doesn't use the `ast::make` API :grin: 

Patch:
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
new file mode 100644
index 0000000000..488bae08f1
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -0,0 +1,788 @@
+use ra_syntax::{
+    ast::{self, AstNode},
+    SyntaxKind, SyntaxNode, TextUnit,
+};
+
+use crate::{Assist, AssistCtx, AssistId};
+use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr};
+use hir::HirDisplay;
+use rustc_hash::{FxHashMap, FxHashSet};
+
+// Assist: add_function
+//
+// Adds a stub function with a signature matching the function under the cursor.
+//
+// ```
+// struct Baz;
+// fn baz() -> Baz { Baz }
+// fn foo() {
+//      bar<|>("", baz());
+// }
+//
+// ```
+// ->
+// ```
+// struct Baz;
+// fn baz() -> Baz { Baz }
+// fn foo() {
+//      bar("", baz());
+// }
+//
+// fn bar(arg: &str, baz: Baz) {
+//     unimplemented!()
+// }
+//
+// ```
+pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
+    let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
+    let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
+    let path = path_expr.path()?;
+
+    if path.qualifier().is_some() {
+        return None;
+    }
+
+    if ctx.sema.resolve_path(&path).is_some() {
+        // The function call already resolves, no need to add a function
+        return None;
+    }
+
+    let function_builder = FunctionBuilder::from_call(&ctx, &call)?;
+
+    ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
+        edit.target(call.syntax().text_range());
+
+        if let Some(function_template) = function_builder.render() {
+            edit.set_cursor(function_template.cursor_offset);
+            edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
+        }
+    })
+}
+
+struct FunctionTemplate {
+    insert_offset: TextUnit,
+    cursor_offset: TextUnit,
+    fn_def: ast::SourceFile,
+}
+
+struct FunctionBuilder {
+    append_fn_at: SyntaxNode,
+    fn_name: ast::Name,
+    type_params: Option<ast::TypeParamList>,
+    params: ast::ParamList,
+}
+
+impl FunctionBuilder {
+    fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> {
+        let append_fn_at = next_space_for_fn(&call)?;
+        let fn_name = fn_name(&call)?;
+        let (type_params, params) = fn_args(ctx, &call)?;
+        Some(Self { append_fn_at, fn_name, type_params, params })
+    }
+    fn render(self) -> Option<FunctionTemplate> {
+        let placeholder_expr = ast::make::expr_unimplemented();
+        let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
+        let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
+        let fn_def = ast::make::add_newlines(2, fn_def);
+        let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def);
+        let insert_offset = self.append_fn_at.text_range().end();
+        let cursor_offset_from_fn_start = fn_def
+            .syntax()
+            .descendants()
+            .find_map(ast::MacroCall::cast)?
+            .syntax()
+            .text_range()
+            .start();
+        let cursor_offset = insert_offset + cursor_offset_from_fn_start;
+        Some(FunctionTemplate { insert_offset, cursor_offset, fn_def })
+    }
+}
+
+fn fn_name(call: &CallExpr) -> Option<ast::Name> {
+    let name = call.expr()?.syntax().to_string();
+    Some(ast::make::name(&name))
+}
+
+/// Computes the type variables and arguments required for the generated function
+fn fn_args(
+    ctx: &AssistCtx,
+    call: &CallExpr,
+) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
+    let mut arg_names = Vec::new();
+    let mut arg_types = Vec::new();
+    for arg in call.arg_list()?.args() {
+        let arg_name = match fn_arg_name(&arg) {
+            Some(name) => name,
+            None => String::from("arg"),
+        };
+        arg_names.push(arg_name);
+        arg_types.push(match fn_arg_type(ctx, &arg) {
+            Some(ty) => ty,
+            None => String::from("()"),
+        });
+    }
+    deduplicate_arg_names(&mut arg_names);
+    let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty));
+    Some((None, ast::make::param_list(params)))
+}
+
+/// Makes duplicate argument names unique by appending incrementing numbers.
+///
+/// ```
+/// let mut names: Vec<String> =
+///     vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()];
+/// deduplicate_arg_names(&mut names);
+/// let expected: Vec<String> =
+///     vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()];
+/// assert_eq!(names, expected);
+/// ```
+fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
+    let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| {
+        *m.entry(name).or_insert(0) += 1;
+        m
+    });
+    let duplicate_arg_names: FxHashSet<String> = arg_name_counts
+        .into_iter()
+        .filter(|(_, count)| *count >= 2)
+        .map(|(name, _)| name.clone())
+        .collect();
+
+    let mut counter_per_name = FxHashMap::default();
+    for arg_name in arg_names.iter_mut() {
+        if duplicate_arg_names.contains(arg_name) {
+            let counter = counter_per_name.entry(arg_name.clone()).or_insert(1);
+            arg_name.push('_');
+            arg_name.push_str(&counter.to_string());
+            *counter += 1;
+        }
+    }
+}
+
+fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
+    match fn_arg {
+        Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
+        _ => Some(
+            fn_arg
+                .syntax()
+                .descendants()
+                .filter(|d| ast::NameRef::can_cast(d.kind()))
+                .last()?
+                .to_string(),
+        ),
+    }
+}
+
+fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
+    let ty = ctx.sema.type_of_expr(fn_arg)?;
+    if ty.is_unknown() {
+        return None;
+    }
+    Some(ty.display(ctx.sema.db).to_string())
+}
+
+/// Returns the position inside the current mod or file
+/// directly after the current block
+/// We want to write the generated function directly after
+/// fns, impls or macro calls, but inside mods
+fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
+    let mut ancestors = expr.syntax().ancestors().peekable();
+    let mut last_ancestor: Option<SyntaxNode> = None;
+    while let Some(next_ancestor) = ancestors.next() {
+        match next_ancestor.kind() {
+            SyntaxKind::SOURCE_FILE => {
+                break;
+            }
+            SyntaxKind::ITEM_LIST => {
+                if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
+                    break;
+                }
+            }
+            _ => {}
+        }
+        last_ancestor = Some(next_ancestor);
+    }
+    last_ancestor
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::helpers::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn add_function_with_no_args() {
+        check_assist(
+            add_function,
+            r"
+fn foo() {
+    bar<|>();
+}
+",
+            r"
+fn foo() {
+    bar();
+}
+
+fn bar() {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_from_method() {
+        // This ensures that the function is correctly generated
+        // in the next outer mod or file
+        check_assist(
+            add_function,
+            r"
+impl Foo {
+    fn foo() {
+        bar<|>();
+    }
+}
+",
+            r"
+impl Foo {
+    fn foo() {
+        bar();
+    }
+}
+
+fn bar() {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_directly_after_current_block() {
+        // The new fn should not be created at the end of the file or module
+        check_assist(
+            add_function,
+            r"
+fn foo1() {
+    bar<|>();
+}
+
+fn foo2() {}
+",
+            r"
+fn foo1() {
+    bar();
+}
+
+fn bar() {
+    <|>unimplemented!()
+}
+
+fn foo2() {}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_with_no_args_in_same_module() {
+        check_assist(
+            add_function,
+            r"
+mod baz {
+    fn foo() {
+        bar<|>();
+    }
+}
+",
+            r"
+mod baz {
+    fn foo() {
+        bar();
+    }
+
+    fn bar() {
+        <|>unimplemented!()
+    }
+}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_with_function_call_arg() {
+        check_assist(
+            add_function,
+            r"
+struct Baz;
+fn baz() -> Baz { unimplemented!() }
+fn foo() {
+    bar<|>(baz());
+}
+",
+            r"
+struct Baz;
+fn baz() -> Baz { unimplemented!() }
+fn foo() {
+    bar(baz());
+}
+
+fn bar(baz: Baz) {
+    <|>unimplemented!()
+}
+",
+        );
+    }
+
+    #[test]
+    fn add_function_with_method_call_arg() {
+        check_assist(
+            add_function,
+            r"
+struct Baz;
+impl Baz {
+    fn foo(&self) -> Baz {
+        ba<|>r(self.baz())
+    }
+    fn baz(&self) -> Baz {
+        Baz
+    }
+}
+",
+            r"
+struct Baz;
+impl Baz {
+    fn foo(&self) -> Baz {
+        bar(self.baz())
+    }
+    fn baz(&self) -> Baz {
+        Baz
+    }
+}
+
+fn bar(baz: Baz) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_with_string_literal_arg() {
+        check_assist(
+            add_function,
+            r#"
+fn foo() {
+    <|>bar("bar")
+}
+"#,
+            r#"
+fn foo() {
+    bar("bar")
+}
+
+fn bar(arg: &str) {
+    <|>unimplemented!()
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn add_function_with_char_literal_arg() {
+        check_assist(
+            add_function,
+            r#"
+fn foo() {
+    <|>bar('x')
+}
+"#,
+            r#"
+fn foo() {
+    bar('x')
+}
+
+fn bar(arg: char) {
+    <|>unimplemented!()
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn add_function_with_int_literal_arg() {
+        check_assist(
+            add_function,
+            r"
+fn foo() {
+    <|>bar(42)
+}
+",
+            r"
+fn foo() {
+    bar(42)
+}
+
+fn bar(arg: i32) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_with_cast_int_literal_arg() {
+        check_assist(
+            add_function,
+            r"
+fn foo() {
+    <|>bar(42 as u8)
+}
+",
+            r"
+fn foo() {
+    bar(42 as u8)
+}
+
+fn bar(arg: u8) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn name_of_cast_variable_is_used() {
+        // Ensures that the name of the cast type isn't used
+        // in the generated function signature.
+        check_assist(
+            add_function,
+            r"
+fn foo() {
+    let x = 42;
+    bar<|>(x as u8)
+}
+",
+            r"
+fn foo() {
+    let x = 42;
+    bar(x as u8)
+}
+
+fn bar(x: u8) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_with_variable_arg() {
+        check_assist(
+            add_function,
+            r"
+fn foo() {
+    let worble = ();
+    <|>bar(worble)
+}
+",
+            r"
+fn foo() {
+    let worble = ();
+    bar(worble)
+}
+
+fn bar(worble: ()) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_with_impl_trait_arg() {
+        check_assist(
+            add_function,
+            r"
+trait Foo {}
+fn foo() -> impl Foo {
+    unimplemented!()
+}
+fn baz() {
+    <|>bar(foo())
+}
+",
+            r"
+trait Foo {}
+fn foo() -> impl Foo {
+    unimplemented!()
+}
+fn baz() {
+    bar(foo())
+}
+
+fn bar(foo: impl Foo) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    #[ignore]
+    // FIXME print paths properly to make this test pass
+    fn add_function_with_qualified_path_arg() {
+        check_assist(
+            add_function,
+            r"
+mod Baz {
+    pub struct Bof;
+    pub fn baz() -> Bof { Bof }
+}
+mod Foo {
+    fn foo() {
+        <|>bar(super::Baz::baz())
+    }
+}
+",
+            r"
+mod Baz {
+    pub struct Bof;
+    pub fn baz() -> Bof { Bof }
+}
+mod Foo {
+    fn foo() {
+        bar(super::Baz::baz())
+    }
+
+    fn bar(baz: super::Baz::Bof) {
+        <|>unimplemented!()
+    }
+}
+",
+        )
+    }
+
+    #[test]
+    #[ignore]
+    // FIXME fix printing the generics of a `Ty` to make this test pass
+    fn add_function_with_generic_arg() {
+        check_assist(
+            add_function,
+            r"
+fn foo<T>(t: T) {
+    <|>bar(t)
+}
+",
+            r"
+fn foo<T>(t: T) {
+    bar(t)
+}
+
+fn bar<T>(t: T) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    #[ignore]
+    // FIXME Fix function type printing to make this test pass
+    fn add_function_with_fn_arg() {
+        check_assist(
+            add_function,
+            r"
+struct Baz;
+impl Baz {
+    fn new() -> Self { Baz }
+}
+fn foo() {
+    <|>bar(Baz::new);
+}
+",
+            r"
+struct Baz;
+impl Baz {
+    fn new() -> Self { Baz }
+}
+fn foo() {
+    bar(Baz::new);
+}
+
+fn bar(arg: fn() -> Baz) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    #[ignore]
+    // FIXME Fix closure type printing to make this test pass
+    fn add_function_with_closure_arg() {
+        check_assist(
+            add_function,
+            r"
+fn foo() {
+    let closure = |x: i64| x - 1;
+    <|>bar(closure)
+}
+",
+            r"
+fn foo() {
+    let closure = |x: i64| x - 1;
+    bar(closure)
+}
+
+fn bar(closure: impl Fn(i64) -> i64) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn unresolveable_types_default_to_unit() {
+        check_assist(
+            add_function,
+            r"
+fn foo() {
+    <|>bar(baz)
+}
+",
+            r"
+fn foo() {
+    bar(baz)
+}
+
+fn bar(baz: ()) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn arg_names_dont_overlap() {
+        check_assist(
+            add_function,
+            r"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+    <|>bar(baz(), baz())
+}
+",
+            r"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+    bar(baz(), baz())
+}
+
+fn bar(baz_1: Baz, baz_2: Baz) {
+    <|>unimplemented!()
+}
+",
+        )
+    }
+
+    #[test]
+    fn arg_name_counters_start_at_1_per_name() {
+        check_assist(
+            add_function,
+            r#"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+    <|>bar(baz(), baz(), "foo", "bar")
+}
+"#,
+            r#"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+    bar(baz(), baz(), "foo", "bar")
+}
+
+fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
+    <|>unimplemented!()
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn add_function_not_applicable_if_function_already_exists() {
+        check_assist_not_applicable(
+            add_function,
+            r"
+fn foo() {
+    bar<|>();
+}
+
+fn bar() {}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() {
+        check_assist_not_applicable(
+            // bar is resolved, but baz isn't.
+            // The assist is only active if the cursor is on an unresolved path,
+            // but the assist should only be offered if the path is a function call.
+            add_function,
+            r"
+fn foo() {
+    bar(b<|>az);
+}
+
+fn bar(baz: ()) {}
+",
+        )
+    }
+
+    #[test]
+    fn add_function_not_applicable_if_function_path_not_singleton() {
+        // In the future this assist could be extended to generate functions
+        // if the path is in the same crate (or even the same workspace).
+        // For the beginning, I think this is fine.
+        check_assist_not_applicable(
+            add_function,
+            r"
+fn foo() {
+    other_crate::bar<|>();
+}
+        ",
+        )
+    }
+
+    #[test]
+    #[ignore]
+    fn create_method_with_no_args() {
+        check_assist(
+            add_function,
+            r"
+struct Foo;
+impl Foo {
+    fn foo(&self) {
+        self.bar()<|>;
+    }
+}
+        ",
+            r"
+struct Foo;
+impl Foo {
+    fn foo(&self) {
+        self.bar();
+    }
+    fn bar(&self) {
+        unimplemented!();
+    }
+}
+        ",
+        )
+    }
+}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index fa1f3dd26f..66854cb5a4 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -96,6 +96,7 @@ mod handlers {
     mod add_custom_impl;
     mod add_derive;
     mod add_explicit_type;
+    mod add_function;
     mod add_impl;
     mod add_missing_impl_members;
     mod add_new;
@@ -128,6 +129,7 @@ mod handlers {
             add_custom_impl::add_custom_impl,
             add_derive::add_derive,
             add_explicit_type::add_explicit_type,
+            add_function::add_function,
             add_impl::add_impl,
             add_missing_impl_members::add_missing_default_members,
             add_missing_impl_members::add_missing_impl_members,
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 0c908573d9..dbb9c8a9b3 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -269,6 +269,31 @@ pub fn unreachable_macro_call() -> ast::MacroCall {
     ast_from_text(&format!("unreachable!()"))
 }
 
+pub fn param(name: String, ty: String) -> ast::Param {
+    ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty))
+}
+
+pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList {
+    let args = pats.into_iter().join(", ");
+    ast_from_text(&format!("fn f({}) {{ }}", args))
+}
+
+pub fn fn_def(
+    fn_name: ast::Name,
+    type_params: Option<ast::TypeParamList>,
+    params: ast::ParamList,
+    body: ast::BlockExpr,
+) -> ast::FnDef {
+    let type_params =
+        if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() };
+    ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body))
+}
+
+pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
+    let newlines = "\n".repeat(amount_of_newlines);
+    ast_from_text(&format!("{}{}", newlines, t.syntax()))
+}
+
 fn ast_from_text<N: AstNode>(text: &str) -> N {
     let parse = SourceFile::parse(text);
     let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
diff --git a/docs/user/assists.md b/docs/user/assists.md
index 94b5ef85d6..754131f6fe 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -56,6 +56,32 @@ fn main() {
 }
 ```
 
+## `add_function`
+
+Adds a stub function with a signature matching the function under the cursor.
+
+```rust
+// BEFORE
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+     bar┃("", baz());
+}
+
+
+// AFTER
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+     bar("", baz());
+}
+
+fn bar(arg: &str, baz: Baz) {
+    unimplemented!()
+}
+
+```
+
 ## `add_hash`
 
 Adds a hash to a raw string literal.


Test patch:
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 0848ab6bc2..64444ee3aa 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -58,6 +58,33 @@ fn main() {
     )
 }
 
+#[test]
+fn doctest_add_function() {
+    check(
+        "add_function",
+        r#####"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+     bar<|>("", baz());
+}
+
+"#####,
+        r#####"
+struct Baz;
+fn baz() -> Baz { Baz }
+fn foo() {
+     bar("", baz());
+}
+
+fn bar(arg: &str, baz: Baz) {
+    unimplemented!()
+}
+
+"#####,
+    )
+}
+
 #[test]
 fn doctest_add_hash() {
     check(