aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/add_function.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-04-03 09:23:44 +0100
committerGitHub <[email protected]>2020-04-03 09:23:44 +0100
commit77462bba621b0e85f3f04bbcebd2a065a875ac02 (patch)
treec86a14f9389a183fc574a89d6364fdfa4771bb91 /crates/ra_assists/src/handlers/add_function.rs
parent2cee8531c5236ae7d66717bea604f1224c23ea56 (diff)
parent10667753c71242c75e70e7b8c46486f37685c186 (diff)
Merge #3746
3746: Add create_function assist r=flodiebold a=TimoFreiberg 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. Co-authored-by: Timo Freiberg <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/handlers/add_function.rs')
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs788
1 files changed, 788 insertions, 0 deletions
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 000000000..488bae08f
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -0,0 +1,788 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 SyntaxKind, SyntaxNode, TextUnit,
4};
5
6use crate::{Assist, AssistCtx, AssistId};
7use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr};
8use hir::HirDisplay;
9use rustc_hash::{FxHashMap, FxHashSet};
10
11// Assist: add_function
12//
13// Adds a stub function with a signature matching the function under the cursor.
14//
15// ```
16// struct Baz;
17// fn baz() -> Baz { Baz }
18// fn foo() {
19// bar<|>("", baz());
20// }
21//
22// ```
23// ->
24// ```
25// struct Baz;
26// fn baz() -> Baz { Baz }
27// fn foo() {
28// bar("", baz());
29// }
30//
31// fn bar(arg: &str, baz: Baz) {
32// unimplemented!()
33// }
34//
35// ```
36pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
37 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
38 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
39 let path = path_expr.path()?;
40
41 if path.qualifier().is_some() {
42 return None;
43 }
44
45 if ctx.sema.resolve_path(&path).is_some() {
46 // The function call already resolves, no need to add a function
47 return None;
48 }
49
50 let function_builder = FunctionBuilder::from_call(&ctx, &call)?;
51
52 ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
53 edit.target(call.syntax().text_range());
54
55 if let Some(function_template) = function_builder.render() {
56 edit.set_cursor(function_template.cursor_offset);
57 edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
58 }
59 })
60}
61
62struct FunctionTemplate {
63 insert_offset: TextUnit,
64 cursor_offset: TextUnit,
65 fn_def: ast::SourceFile,
66}
67
68struct FunctionBuilder {
69 append_fn_at: SyntaxNode,
70 fn_name: ast::Name,
71 type_params: Option<ast::TypeParamList>,
72 params: ast::ParamList,
73}
74
75impl FunctionBuilder {
76 fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> {
77 let append_fn_at = next_space_for_fn(&call)?;
78 let fn_name = fn_name(&call)?;
79 let (type_params, params) = fn_args(ctx, &call)?;
80 Some(Self { append_fn_at, fn_name, type_params, params })
81 }
82 fn render(self) -> Option<FunctionTemplate> {
83 let placeholder_expr = ast::make::expr_unimplemented();
84 let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
85 let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
86 let fn_def = ast::make::add_newlines(2, fn_def);
87 let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def);
88 let insert_offset = self.append_fn_at.text_range().end();
89 let cursor_offset_from_fn_start = fn_def
90 .syntax()
91 .descendants()
92 .find_map(ast::MacroCall::cast)?
93 .syntax()
94 .text_range()
95 .start();
96 let cursor_offset = insert_offset + cursor_offset_from_fn_start;
97 Some(FunctionTemplate { insert_offset, cursor_offset, fn_def })
98 }
99}
100
101fn fn_name(call: &CallExpr) -> Option<ast::Name> {
102 let name = call.expr()?.syntax().to_string();
103 Some(ast::make::name(&name))
104}
105
106/// Computes the type variables and arguments required for the generated function
107fn fn_args(
108 ctx: &AssistCtx,
109 call: &CallExpr,
110) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
111 let mut arg_names = Vec::new();
112 let mut arg_types = Vec::new();
113 for arg in call.arg_list()?.args() {
114 let arg_name = match fn_arg_name(&arg) {
115 Some(name) => name,
116 None => String::from("arg"),
117 };
118 arg_names.push(arg_name);
119 arg_types.push(match fn_arg_type(ctx, &arg) {
120 Some(ty) => ty,
121 None => String::from("()"),
122 });
123 }
124 deduplicate_arg_names(&mut arg_names);
125 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty));
126 Some((None, ast::make::param_list(params)))
127}
128
129/// Makes duplicate argument names unique by appending incrementing numbers.
130///
131/// ```
132/// let mut names: Vec<String> =
133/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()];
134/// deduplicate_arg_names(&mut names);
135/// let expected: Vec<String> =
136/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()];
137/// assert_eq!(names, expected);
138/// ```
139fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
140 let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| {
141 *m.entry(name).or_insert(0) += 1;
142 m
143 });
144 let duplicate_arg_names: FxHashSet<String> = arg_name_counts
145 .into_iter()
146 .filter(|(_, count)| *count >= 2)
147 .map(|(name, _)| name.clone())
148 .collect();
149
150 let mut counter_per_name = FxHashMap::default();
151 for arg_name in arg_names.iter_mut() {
152 if duplicate_arg_names.contains(arg_name) {
153 let counter = counter_per_name.entry(arg_name.clone()).or_insert(1);
154 arg_name.push('_');
155 arg_name.push_str(&counter.to_string());
156 *counter += 1;
157 }
158 }
159}
160
161fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
162 match fn_arg {
163 Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
164 _ => Some(
165 fn_arg
166 .syntax()
167 .descendants()
168 .filter(|d| ast::NameRef::can_cast(d.kind()))
169 .last()?
170 .to_string(),
171 ),
172 }
173}
174
175fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
176 let ty = ctx.sema.type_of_expr(fn_arg)?;
177 if ty.is_unknown() {
178 return None;
179 }
180 Some(ty.display(ctx.sema.db).to_string())
181}
182
183/// Returns the position inside the current mod or file
184/// directly after the current block
185/// We want to write the generated function directly after
186/// fns, impls or macro calls, but inside mods
187fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
188 let mut ancestors = expr.syntax().ancestors().peekable();
189 let mut last_ancestor: Option<SyntaxNode> = None;
190 while let Some(next_ancestor) = ancestors.next() {
191 match next_ancestor.kind() {
192 SyntaxKind::SOURCE_FILE => {
193 break;
194 }
195 SyntaxKind::ITEM_LIST => {
196 if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
197 break;
198 }
199 }
200 _ => {}
201 }
202 last_ancestor = Some(next_ancestor);
203 }
204 last_ancestor
205}
206
207#[cfg(test)]
208mod tests {
209 use crate::helpers::{check_assist, check_assist_not_applicable};
210
211 use super::*;
212
213 #[test]
214 fn add_function_with_no_args() {
215 check_assist(
216 add_function,
217 r"
218fn foo() {
219 bar<|>();
220}
221",
222 r"
223fn foo() {
224 bar();
225}
226
227fn bar() {
228 <|>unimplemented!()
229}
230",
231 )
232 }
233
234 #[test]
235 fn add_function_from_method() {
236 // This ensures that the function is correctly generated
237 // in the next outer mod or file
238 check_assist(
239 add_function,
240 r"
241impl Foo {
242 fn foo() {
243 bar<|>();
244 }
245}
246",
247 r"
248impl Foo {
249 fn foo() {
250 bar();
251 }
252}
253
254fn bar() {
255 <|>unimplemented!()
256}
257",
258 )
259 }
260
261 #[test]
262 fn add_function_directly_after_current_block() {
263 // The new fn should not be created at the end of the file or module
264 check_assist(
265 add_function,
266 r"
267fn foo1() {
268 bar<|>();
269}
270
271fn foo2() {}
272",
273 r"
274fn foo1() {
275 bar();
276}
277
278fn bar() {
279 <|>unimplemented!()
280}
281
282fn foo2() {}
283",
284 )
285 }
286
287 #[test]
288 fn add_function_with_no_args_in_same_module() {
289 check_assist(
290 add_function,
291 r"
292mod baz {
293 fn foo() {
294 bar<|>();
295 }
296}
297",
298 r"
299mod baz {
300 fn foo() {
301 bar();
302 }
303
304 fn bar() {
305 <|>unimplemented!()
306 }
307}
308",
309 )
310 }
311
312 #[test]
313 fn add_function_with_function_call_arg() {
314 check_assist(
315 add_function,
316 r"
317struct Baz;
318fn baz() -> Baz { unimplemented!() }
319fn foo() {
320 bar<|>(baz());
321}
322",
323 r"
324struct Baz;
325fn baz() -> Baz { unimplemented!() }
326fn foo() {
327 bar(baz());
328}
329
330fn bar(baz: Baz) {
331 <|>unimplemented!()
332}
333",
334 );
335 }
336
337 #[test]
338 fn add_function_with_method_call_arg() {
339 check_assist(
340 add_function,
341 r"
342struct Baz;
343impl Baz {
344 fn foo(&self) -> Baz {
345 ba<|>r(self.baz())
346 }
347 fn baz(&self) -> Baz {
348 Baz
349 }
350}
351",
352 r"
353struct Baz;
354impl Baz {
355 fn foo(&self) -> Baz {
356 bar(self.baz())
357 }
358 fn baz(&self) -> Baz {
359 Baz
360 }
361}
362
363fn bar(baz: Baz) {
364 <|>unimplemented!()
365}
366",
367 )
368 }
369
370 #[test]
371 fn add_function_with_string_literal_arg() {
372 check_assist(
373 add_function,
374 r#"
375fn foo() {
376 <|>bar("bar")
377}
378"#,
379 r#"
380fn foo() {
381 bar("bar")
382}
383
384fn bar(arg: &str) {
385 <|>unimplemented!()
386}
387"#,
388 )
389 }
390
391 #[test]
392 fn add_function_with_char_literal_arg() {
393 check_assist(
394 add_function,
395 r#"
396fn foo() {
397 <|>bar('x')
398}
399"#,
400 r#"
401fn foo() {
402 bar('x')
403}
404
405fn bar(arg: char) {
406 <|>unimplemented!()
407}
408"#,
409 )
410 }
411
412 #[test]
413 fn add_function_with_int_literal_arg() {
414 check_assist(
415 add_function,
416 r"
417fn foo() {
418 <|>bar(42)
419}
420",
421 r"
422fn foo() {
423 bar(42)
424}
425
426fn bar(arg: i32) {
427 <|>unimplemented!()
428}
429",
430 )
431 }
432
433 #[test]
434 fn add_function_with_cast_int_literal_arg() {
435 check_assist(
436 add_function,
437 r"
438fn foo() {
439 <|>bar(42 as u8)
440}
441",
442 r"
443fn foo() {
444 bar(42 as u8)
445}
446
447fn bar(arg: u8) {
448 <|>unimplemented!()
449}
450",
451 )
452 }
453
454 #[test]
455 fn name_of_cast_variable_is_used() {
456 // Ensures that the name of the cast type isn't used
457 // in the generated function signature.
458 check_assist(
459 add_function,
460 r"
461fn foo() {
462 let x = 42;
463 bar<|>(x as u8)
464}
465",
466 r"
467fn foo() {
468 let x = 42;
469 bar(x as u8)
470}
471
472fn bar(x: u8) {
473 <|>unimplemented!()
474}
475",
476 )
477 }
478
479 #[test]
480 fn add_function_with_variable_arg() {
481 check_assist(
482 add_function,
483 r"
484fn foo() {
485 let worble = ();
486 <|>bar(worble)
487}
488",
489 r"
490fn foo() {
491 let worble = ();
492 bar(worble)
493}
494
495fn bar(worble: ()) {
496 <|>unimplemented!()
497}
498",
499 )
500 }
501
502 #[test]
503 fn add_function_with_impl_trait_arg() {
504 check_assist(
505 add_function,
506 r"
507trait Foo {}
508fn foo() -> impl Foo {
509 unimplemented!()
510}
511fn baz() {
512 <|>bar(foo())
513}
514",
515 r"
516trait Foo {}
517fn foo() -> impl Foo {
518 unimplemented!()
519}
520fn baz() {
521 bar(foo())
522}
523
524fn bar(foo: impl Foo) {
525 <|>unimplemented!()
526}
527",
528 )
529 }
530
531 #[test]
532 #[ignore]
533 // FIXME print paths properly to make this test pass
534 fn add_function_with_qualified_path_arg() {
535 check_assist(
536 add_function,
537 r"
538mod Baz {
539 pub struct Bof;
540 pub fn baz() -> Bof { Bof }
541}
542mod Foo {
543 fn foo() {
544 <|>bar(super::Baz::baz())
545 }
546}
547",
548 r"
549mod Baz {
550 pub struct Bof;
551 pub fn baz() -> Bof { Bof }
552}
553mod Foo {
554 fn foo() {
555 bar(super::Baz::baz())
556 }
557
558 fn bar(baz: super::Baz::Bof) {
559 <|>unimplemented!()
560 }
561}
562",
563 )
564 }
565
566 #[test]
567 #[ignore]
568 // FIXME fix printing the generics of a `Ty` to make this test pass
569 fn add_function_with_generic_arg() {
570 check_assist(
571 add_function,
572 r"
573fn foo<T>(t: T) {
574 <|>bar(t)
575}
576",
577 r"
578fn foo<T>(t: T) {
579 bar(t)
580}
581
582fn bar<T>(t: T) {
583 <|>unimplemented!()
584}
585",
586 )
587 }
588
589 #[test]
590 #[ignore]
591 // FIXME Fix function type printing to make this test pass
592 fn add_function_with_fn_arg() {
593 check_assist(
594 add_function,
595 r"
596struct Baz;
597impl Baz {
598 fn new() -> Self { Baz }
599}
600fn foo() {
601 <|>bar(Baz::new);
602}
603",
604 r"
605struct Baz;
606impl Baz {
607 fn new() -> Self { Baz }
608}
609fn foo() {
610 bar(Baz::new);
611}
612
613fn bar(arg: fn() -> Baz) {
614 <|>unimplemented!()
615}
616",
617 )
618 }
619
620 #[test]
621 #[ignore]
622 // FIXME Fix closure type printing to make this test pass
623 fn add_function_with_closure_arg() {
624 check_assist(
625 add_function,
626 r"
627fn foo() {
628 let closure = |x: i64| x - 1;
629 <|>bar(closure)
630}
631",
632 r"
633fn foo() {
634 let closure = |x: i64| x - 1;
635 bar(closure)
636}
637
638fn bar(closure: impl Fn(i64) -> i64) {
639 <|>unimplemented!()
640}
641",
642 )
643 }
644
645 #[test]
646 fn unresolveable_types_default_to_unit() {
647 check_assist(
648 add_function,
649 r"
650fn foo() {
651 <|>bar(baz)
652}
653",
654 r"
655fn foo() {
656 bar(baz)
657}
658
659fn bar(baz: ()) {
660 <|>unimplemented!()
661}
662",
663 )
664 }
665
666 #[test]
667 fn arg_names_dont_overlap() {
668 check_assist(
669 add_function,
670 r"
671struct Baz;
672fn baz() -> Baz { Baz }
673fn foo() {
674 <|>bar(baz(), baz())
675}
676",
677 r"
678struct Baz;
679fn baz() -> Baz { Baz }
680fn foo() {
681 bar(baz(), baz())
682}
683
684fn bar(baz_1: Baz, baz_2: Baz) {
685 <|>unimplemented!()
686}
687",
688 )
689 }
690
691 #[test]
692 fn arg_name_counters_start_at_1_per_name() {
693 check_assist(
694 add_function,
695 r#"
696struct Baz;
697fn baz() -> Baz { Baz }
698fn foo() {
699 <|>bar(baz(), baz(), "foo", "bar")
700}
701"#,
702 r#"
703struct Baz;
704fn baz() -> Baz { Baz }
705fn foo() {
706 bar(baz(), baz(), "foo", "bar")
707}
708
709fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
710 <|>unimplemented!()
711}
712"#,
713 )
714 }
715
716 #[test]
717 fn add_function_not_applicable_if_function_already_exists() {
718 check_assist_not_applicable(
719 add_function,
720 r"
721fn foo() {
722 bar<|>();
723}
724
725fn bar() {}
726",
727 )
728 }
729
730 #[test]
731 fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() {
732 check_assist_not_applicable(
733 // bar is resolved, but baz isn't.
734 // The assist is only active if the cursor is on an unresolved path,
735 // but the assist should only be offered if the path is a function call.
736 add_function,
737 r"
738fn foo() {
739 bar(b<|>az);
740}
741
742fn bar(baz: ()) {}
743",
744 )
745 }
746
747 #[test]
748 fn add_function_not_applicable_if_function_path_not_singleton() {
749 // In the future this assist could be extended to generate functions
750 // if the path is in the same crate (or even the same workspace).
751 // For the beginning, I think this is fine.
752 check_assist_not_applicable(
753 add_function,
754 r"
755fn foo() {
756 other_crate::bar<|>();
757}
758 ",
759 )
760 }
761
762 #[test]
763 #[ignore]
764 fn create_method_with_no_args() {
765 check_assist(
766 add_function,
767 r"
768struct Foo;
769impl Foo {
770 fn foo(&self) {
771 self.bar()<|>;
772 }
773}
774 ",
775 r"
776struct Foo;
777impl Foo {
778 fn foo(&self) {
779 self.bar();
780 }
781 fn bar(&self) {
782 unimplemented!();
783 }
784}
785 ",
786 )
787 }
788}