diff options
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 27 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/add_function.rs | 788 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 25 | ||||
-rw-r--r-- | docs/user/assists.md | 26 |
5 files changed, 868 insertions, 0 deletions
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index 0848ab6bc..64444ee3a 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -59,6 +59,33 @@ fn main() { | |||
59 | } | 59 | } |
60 | 60 | ||
61 | #[test] | 61 | #[test] |
62 | fn doctest_add_function() { | ||
63 | check( | ||
64 | "add_function", | ||
65 | r#####" | ||
66 | struct Baz; | ||
67 | fn baz() -> Baz { Baz } | ||
68 | fn foo() { | ||
69 | bar<|>("", baz()); | ||
70 | } | ||
71 | |||
72 | "#####, | ||
73 | r#####" | ||
74 | struct Baz; | ||
75 | fn baz() -> Baz { Baz } | ||
76 | fn foo() { | ||
77 | bar("", baz()); | ||
78 | } | ||
79 | |||
80 | fn bar(arg: &str, baz: Baz) { | ||
81 | unimplemented!() | ||
82 | } | ||
83 | |||
84 | "#####, | ||
85 | ) | ||
86 | } | ||
87 | |||
88 | #[test] | ||
62 | fn doctest_add_hash() { | 89 | fn doctest_add_hash() { |
63 | check( | 90 | check( |
64 | "add_hash", | 91 | "add_hash", |
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 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode}, | ||
3 | SyntaxKind, SyntaxNode, TextUnit, | ||
4 | }; | ||
5 | |||
6 | use crate::{Assist, AssistCtx, AssistId}; | ||
7 | use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr}; | ||
8 | use hir::HirDisplay; | ||
9 | use 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 | // ``` | ||
36 | pub(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 | |||
62 | struct FunctionTemplate { | ||
63 | insert_offset: TextUnit, | ||
64 | cursor_offset: TextUnit, | ||
65 | fn_def: ast::SourceFile, | ||
66 | } | ||
67 | |||
68 | struct FunctionBuilder { | ||
69 | append_fn_at: SyntaxNode, | ||
70 | fn_name: ast::Name, | ||
71 | type_params: Option<ast::TypeParamList>, | ||
72 | params: ast::ParamList, | ||
73 | } | ||
74 | |||
75 | impl 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 | |||
101 | fn 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 | ||
107 | fn 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 | /// ``` | ||
139 | fn 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 | |||
161 | fn 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 | |||
175 | fn 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 | ||
187 | fn 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)] | ||
208 | mod 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" | ||
218 | fn foo() { | ||
219 | bar<|>(); | ||
220 | } | ||
221 | ", | ||
222 | r" | ||
223 | fn foo() { | ||
224 | bar(); | ||
225 | } | ||
226 | |||
227 | fn 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" | ||
241 | impl Foo { | ||
242 | fn foo() { | ||
243 | bar<|>(); | ||
244 | } | ||
245 | } | ||
246 | ", | ||
247 | r" | ||
248 | impl Foo { | ||
249 | fn foo() { | ||
250 | bar(); | ||
251 | } | ||
252 | } | ||
253 | |||
254 | fn 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" | ||
267 | fn foo1() { | ||
268 | bar<|>(); | ||
269 | } | ||
270 | |||
271 | fn foo2() {} | ||
272 | ", | ||
273 | r" | ||
274 | fn foo1() { | ||
275 | bar(); | ||
276 | } | ||
277 | |||
278 | fn bar() { | ||
279 | <|>unimplemented!() | ||
280 | } | ||
281 | |||
282 | fn 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" | ||
292 | mod baz { | ||
293 | fn foo() { | ||
294 | bar<|>(); | ||
295 | } | ||
296 | } | ||
297 | ", | ||
298 | r" | ||
299 | mod 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" | ||
317 | struct Baz; | ||
318 | fn baz() -> Baz { unimplemented!() } | ||
319 | fn foo() { | ||
320 | bar<|>(baz()); | ||
321 | } | ||
322 | ", | ||
323 | r" | ||
324 | struct Baz; | ||
325 | fn baz() -> Baz { unimplemented!() } | ||
326 | fn foo() { | ||
327 | bar(baz()); | ||
328 | } | ||
329 | |||
330 | fn 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" | ||
342 | struct Baz; | ||
343 | impl Baz { | ||
344 | fn foo(&self) -> Baz { | ||
345 | ba<|>r(self.baz()) | ||
346 | } | ||
347 | fn baz(&self) -> Baz { | ||
348 | Baz | ||
349 | } | ||
350 | } | ||
351 | ", | ||
352 | r" | ||
353 | struct Baz; | ||
354 | impl Baz { | ||
355 | fn foo(&self) -> Baz { | ||
356 | bar(self.baz()) | ||
357 | } | ||
358 | fn baz(&self) -> Baz { | ||
359 | Baz | ||
360 | } | ||
361 | } | ||
362 | |||
363 | fn 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#" | ||
375 | fn foo() { | ||
376 | <|>bar("bar") | ||
377 | } | ||
378 | "#, | ||
379 | r#" | ||
380 | fn foo() { | ||
381 | bar("bar") | ||
382 | } | ||
383 | |||
384 | fn 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#" | ||
396 | fn foo() { | ||
397 | <|>bar('x') | ||
398 | } | ||
399 | "#, | ||
400 | r#" | ||
401 | fn foo() { | ||
402 | bar('x') | ||
403 | } | ||
404 | |||
405 | fn 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" | ||
417 | fn foo() { | ||
418 | <|>bar(42) | ||
419 | } | ||
420 | ", | ||
421 | r" | ||
422 | fn foo() { | ||
423 | bar(42) | ||
424 | } | ||
425 | |||
426 | fn 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" | ||
438 | fn foo() { | ||
439 | <|>bar(42 as u8) | ||
440 | } | ||
441 | ", | ||
442 | r" | ||
443 | fn foo() { | ||
444 | bar(42 as u8) | ||
445 | } | ||
446 | |||
447 | fn 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" | ||
461 | fn foo() { | ||
462 | let x = 42; | ||
463 | bar<|>(x as u8) | ||
464 | } | ||
465 | ", | ||
466 | r" | ||
467 | fn foo() { | ||
468 | let x = 42; | ||
469 | bar(x as u8) | ||
470 | } | ||
471 | |||
472 | fn 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" | ||
484 | fn foo() { | ||
485 | let worble = (); | ||
486 | <|>bar(worble) | ||
487 | } | ||
488 | ", | ||
489 | r" | ||
490 | fn foo() { | ||
491 | let worble = (); | ||
492 | bar(worble) | ||
493 | } | ||
494 | |||
495 | fn 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" | ||
507 | trait Foo {} | ||
508 | fn foo() -> impl Foo { | ||
509 | unimplemented!() | ||
510 | } | ||
511 | fn baz() { | ||
512 | <|>bar(foo()) | ||
513 | } | ||
514 | ", | ||
515 | r" | ||
516 | trait Foo {} | ||
517 | fn foo() -> impl Foo { | ||
518 | unimplemented!() | ||
519 | } | ||
520 | fn baz() { | ||
521 | bar(foo()) | ||
522 | } | ||
523 | |||
524 | fn 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" | ||
538 | mod Baz { | ||
539 | pub struct Bof; | ||
540 | pub fn baz() -> Bof { Bof } | ||
541 | } | ||
542 | mod Foo { | ||
543 | fn foo() { | ||
544 | <|>bar(super::Baz::baz()) | ||
545 | } | ||
546 | } | ||
547 | ", | ||
548 | r" | ||
549 | mod Baz { | ||
550 | pub struct Bof; | ||
551 | pub fn baz() -> Bof { Bof } | ||
552 | } | ||
553 | mod 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" | ||
573 | fn foo<T>(t: T) { | ||
574 | <|>bar(t) | ||
575 | } | ||
576 | ", | ||
577 | r" | ||
578 | fn foo<T>(t: T) { | ||
579 | bar(t) | ||
580 | } | ||
581 | |||
582 | fn 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" | ||
596 | struct Baz; | ||
597 | impl Baz { | ||
598 | fn new() -> Self { Baz } | ||
599 | } | ||
600 | fn foo() { | ||
601 | <|>bar(Baz::new); | ||
602 | } | ||
603 | ", | ||
604 | r" | ||
605 | struct Baz; | ||
606 | impl Baz { | ||
607 | fn new() -> Self { Baz } | ||
608 | } | ||
609 | fn foo() { | ||
610 | bar(Baz::new); | ||
611 | } | ||
612 | |||
613 | fn 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" | ||
627 | fn foo() { | ||
628 | let closure = |x: i64| x - 1; | ||
629 | <|>bar(closure) | ||
630 | } | ||
631 | ", | ||
632 | r" | ||
633 | fn foo() { | ||
634 | let closure = |x: i64| x - 1; | ||
635 | bar(closure) | ||
636 | } | ||
637 | |||
638 | fn 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" | ||
650 | fn foo() { | ||
651 | <|>bar(baz) | ||
652 | } | ||
653 | ", | ||
654 | r" | ||
655 | fn foo() { | ||
656 | bar(baz) | ||
657 | } | ||
658 | |||
659 | fn 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" | ||
671 | struct Baz; | ||
672 | fn baz() -> Baz { Baz } | ||
673 | fn foo() { | ||
674 | <|>bar(baz(), baz()) | ||
675 | } | ||
676 | ", | ||
677 | r" | ||
678 | struct Baz; | ||
679 | fn baz() -> Baz { Baz } | ||
680 | fn foo() { | ||
681 | bar(baz(), baz()) | ||
682 | } | ||
683 | |||
684 | fn 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#" | ||
696 | struct Baz; | ||
697 | fn baz() -> Baz { Baz } | ||
698 | fn foo() { | ||
699 | <|>bar(baz(), baz(), "foo", "bar") | ||
700 | } | ||
701 | "#, | ||
702 | r#" | ||
703 | struct Baz; | ||
704 | fn baz() -> Baz { Baz } | ||
705 | fn foo() { | ||
706 | bar(baz(), baz(), "foo", "bar") | ||
707 | } | ||
708 | |||
709 | fn 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" | ||
721 | fn foo() { | ||
722 | bar<|>(); | ||
723 | } | ||
724 | |||
725 | fn 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" | ||
738 | fn foo() { | ||
739 | bar(b<|>az); | ||
740 | } | ||
741 | |||
742 | fn 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" | ||
755 | fn 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" | ||
768 | struct Foo; | ||
769 | impl Foo { | ||
770 | fn foo(&self) { | ||
771 | self.bar()<|>; | ||
772 | } | ||
773 | } | ||
774 | ", | ||
775 | r" | ||
776 | struct Foo; | ||
777 | impl Foo { | ||
778 | fn foo(&self) { | ||
779 | self.bar(); | ||
780 | } | ||
781 | fn bar(&self) { | ||
782 | unimplemented!(); | ||
783 | } | ||
784 | } | ||
785 | ", | ||
786 | ) | ||
787 | } | ||
788 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 6b4c56dcd..fa3d3913f 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -96,6 +96,7 @@ mod handlers { | |||
96 | mod add_custom_impl; | 96 | mod add_custom_impl; |
97 | mod add_derive; | 97 | mod add_derive; |
98 | mod add_explicit_type; | 98 | mod add_explicit_type; |
99 | mod add_function; | ||
99 | mod add_impl; | 100 | mod add_impl; |
100 | mod add_missing_impl_members; | 101 | mod add_missing_impl_members; |
101 | mod add_new; | 102 | mod add_new; |
@@ -129,6 +130,7 @@ mod handlers { | |||
129 | add_custom_impl::add_custom_impl, | 130 | add_custom_impl::add_custom_impl, |
130 | add_derive::add_derive, | 131 | add_derive::add_derive, |
131 | add_explicit_type::add_explicit_type, | 132 | add_explicit_type::add_explicit_type, |
133 | add_function::add_function, | ||
132 | add_impl::add_impl, | 134 | add_impl::add_impl, |
133 | add_missing_impl_members::add_missing_default_members, | 135 | add_missing_impl_members::add_missing_default_members, |
134 | add_missing_impl_members::add_missing_impl_members, | 136 | 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 c49cf9a3b..f39559e9e 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -270,6 +270,31 @@ pub fn unreachable_macro_call() -> ast::MacroCall { | |||
270 | ast_from_text(&format!("unreachable!()")) | 270 | ast_from_text(&format!("unreachable!()")) |
271 | } | 271 | } |
272 | 272 | ||
273 | pub fn param(name: String, ty: String) -> ast::Param { | ||
274 | ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty)) | ||
275 | } | ||
276 | |||
277 | pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList { | ||
278 | let args = pats.into_iter().join(", "); | ||
279 | ast_from_text(&format!("fn f({}) {{ }}", args)) | ||
280 | } | ||
281 | |||
282 | pub fn fn_def( | ||
283 | fn_name: ast::Name, | ||
284 | type_params: Option<ast::TypeParamList>, | ||
285 | params: ast::ParamList, | ||
286 | body: ast::BlockExpr, | ||
287 | ) -> ast::FnDef { | ||
288 | let type_params = | ||
289 | if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() }; | ||
290 | ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) | ||
291 | } | ||
292 | |||
293 | pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { | ||
294 | let newlines = "\n".repeat(amount_of_newlines); | ||
295 | ast_from_text(&format!("{}{}", newlines, t.syntax())) | ||
296 | } | ||
297 | |||
273 | fn ast_from_text<N: AstNode>(text: &str) -> N { | 298 | fn ast_from_text<N: AstNode>(text: &str) -> N { |
274 | let parse = SourceFile::parse(text); | 299 | let parse = SourceFile::parse(text); |
275 | let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); | 300 | let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); |
diff --git a/docs/user/assists.md b/docs/user/assists.md index 94b5ef85d..754131f6f 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -56,6 +56,32 @@ fn main() { | |||
56 | } | 56 | } |
57 | ``` | 57 | ``` |
58 | 58 | ||
59 | ## `add_function` | ||
60 | |||
61 | Adds a stub function with a signature matching the function under the cursor. | ||
62 | |||
63 | ```rust | ||
64 | // BEFORE | ||
65 | struct Baz; | ||
66 | fn baz() -> Baz { Baz } | ||
67 | fn foo() { | ||
68 | bar┃("", baz()); | ||
69 | } | ||
70 | |||
71 | |||
72 | // AFTER | ||
73 | struct Baz; | ||
74 | fn baz() -> Baz { Baz } | ||
75 | fn foo() { | ||
76 | bar("", baz()); | ||
77 | } | ||
78 | |||
79 | fn bar(arg: &str, baz: Baz) { | ||
80 | unimplemented!() | ||
81 | } | ||
82 | |||
83 | ``` | ||
84 | |||
59 | ## `add_hash` | 85 | ## `add_hash` |
60 | 86 | ||
61 | Adds a hash to a raw string literal. | 87 | Adds a hash to a raw string literal. |