diff options
author | Josh Mcguigan <[email protected]> | 2021-03-14 04:30:14 +0000 |
---|---|---|
committer | Josh Mcguigan <[email protected]> | 2021-03-15 12:38:19 +0000 |
commit | d91151c3b1af6d4e4c29f5f82df8566b046266e3 (patch) | |
tree | de655e58fa1fdb4f335ac049465dd786ea71104e /crates/ide_completion | |
parent | b4446cdd06caa32e1e9e255312a15feb770d4935 (diff) |
update algorithm for determining expected type of completion
Diffstat (limited to 'crates/ide_completion')
-rw-r--r-- | crates/ide_completion/src/context.rs | 297 | ||||
-rw-r--r-- | crates/ide_completion/src/render.rs | 15 |
2 files changed, 286 insertions, 26 deletions
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 17d9a3adf..89e9bda78 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -35,6 +35,7 @@ pub(crate) struct CompletionContext<'a> { | |||
35 | /// The token before the cursor, in the macro-expanded file. | 35 | /// The token before the cursor, in the macro-expanded file. |
36 | pub(super) token: SyntaxToken, | 36 | pub(super) token: SyntaxToken, |
37 | pub(super) krate: Option<hir::Crate>, | 37 | pub(super) krate: Option<hir::Crate>, |
38 | pub(super) expected_name: Option<String>, | ||
38 | pub(super) expected_type: Option<Type>, | 39 | pub(super) expected_type: Option<Type>, |
39 | pub(super) name_ref_syntax: Option<ast::NameRef>, | 40 | pub(super) name_ref_syntax: Option<ast::NameRef>, |
40 | pub(super) function_syntax: Option<ast::Fn>, | 41 | pub(super) function_syntax: Option<ast::Fn>, |
@@ -135,6 +136,7 @@ impl<'a> CompletionContext<'a> { | |||
135 | original_token, | 136 | original_token, |
136 | token, | 137 | token, |
137 | krate, | 138 | krate, |
139 | expected_name: None, | ||
138 | expected_type: None, | 140 | expected_type: None, |
139 | name_ref_syntax: None, | 141 | name_ref_syntax: None, |
140 | function_syntax: None, | 142 | function_syntax: None, |
@@ -290,23 +292,96 @@ impl<'a> CompletionContext<'a> { | |||
290 | file_with_fake_ident: SyntaxNode, | 292 | file_with_fake_ident: SyntaxNode, |
291 | offset: TextSize, | 293 | offset: TextSize, |
292 | ) { | 294 | ) { |
293 | // FIXME: this is wrong in at least two cases: | 295 | let expected = { |
294 | // * when there's no token `foo($0)` | 296 | let mut node = self.token.parent(); |
295 | // * when there is a token, but it happens to have type of it's own | 297 | loop { |
296 | self.expected_type = self | 298 | let ret = match_ast! { |
297 | .token | ||
298 | .ancestors() | ||
299 | .find_map(|node| { | ||
300 | let ty = match_ast! { | ||
301 | match node { | 299 | match node { |
302 | ast::Pat(it) => self.sema.type_of_pat(&it), | 300 | ast::LetStmt(it) => { |
303 | ast::Expr(it) => self.sema.type_of_expr(&it), | 301 | cov_mark::hit!(expected_type_let_with_leading_char); |
304 | _ => return None, | 302 | cov_mark::hit!(expected_type_let_without_leading_char); |
303 | let ty = it.pat() | ||
304 | .and_then(|pat| self.sema.type_of_pat(&pat)); | ||
305 | let name = if let Some(ast::Pat::IdentPat(ident)) = it.pat() { | ||
306 | Some(ident.syntax().text().to_string()) | ||
307 | } else { | ||
308 | None | ||
309 | }; | ||
310 | |||
311 | (ty, name) | ||
312 | }, | ||
313 | ast::ArgList(it) => { | ||
314 | cov_mark::hit!(expected_type_fn_param_with_leading_char); | ||
315 | cov_mark::hit!(expected_type_fn_param_without_leading_char); | ||
316 | ActiveParameter::at_token( | ||
317 | &self.sema, | ||
318 | self.token.clone(), | ||
319 | ).map(|ap| (Some(ap.ty), Some(ap.name))) | ||
320 | .unwrap_or((None, None)) | ||
321 | }, | ||
322 | ast::RecordExprFieldList(it) => { | ||
323 | cov_mark::hit!(expected_type_struct_field_without_leading_char); | ||
324 | self.token.prev_sibling_or_token() | ||
325 | .and_then(|se| se.into_node()) | ||
326 | .and_then(|node| ast::RecordExprField::cast(node)) | ||
327 | .and_then(|rf| self.sema.resolve_record_field(&rf)) | ||
328 | .map(|f|( | ||
329 | Some(f.0.signature_ty(self.db)), | ||
330 | Some(f.0.name(self.db).to_string()), | ||
331 | )) | ||
332 | .unwrap_or((None, None)) | ||
333 | }, | ||
334 | ast::RecordExprField(it) => { | ||
335 | cov_mark::hit!(expected_type_struct_field_with_leading_char); | ||
336 | self.sema | ||
337 | .resolve_record_field(&it) | ||
338 | .map(|f|( | ||
339 | Some(f.0.signature_ty(self.db)), | ||
340 | Some(f.0.name(self.db).to_string()), | ||
341 | )) | ||
342 | .unwrap_or((None, None)) | ||
343 | }, | ||
344 | ast::MatchExpr(it) => { | ||
345 | cov_mark::hit!(expected_type_match_arm_without_leading_char); | ||
346 | let ty = it.expr() | ||
347 | .and_then(|e| self.sema.type_of_expr(&e)); | ||
348 | |||
349 | (ty, None) | ||
350 | }, | ||
351 | ast::IdentPat(it) => { | ||
352 | cov_mark::hit!(expected_type_if_let_with_leading_char); | ||
353 | cov_mark::hit!(expected_type_if_let_without_leading_char); | ||
354 | cov_mark::hit!(expected_type_match_arm_with_leading_char); | ||
355 | let ty = self.sema.type_of_pat(&ast::Pat::from(it)); | ||
356 | |||
357 | (ty, None) | ||
358 | }, | ||
359 | ast::Fn(it) => { | ||
360 | cov_mark::hit!(expected_type_fn_ret_with_leading_char); | ||
361 | cov_mark::hit!(expected_type_fn_ret_without_leading_char); | ||
362 | let ty = self.token.ancestors() | ||
363 | .find_map(|ancestor| ast::Expr::cast(ancestor)) | ||
364 | .and_then(|expr| self.sema.type_of_expr(&expr)); | ||
365 | |||
366 | (ty, None) | ||
367 | }, | ||
368 | _ => { | ||
369 | match node.parent() { | ||
370 | Some(n) => { | ||
371 | node = n; | ||
372 | continue; | ||
373 | }, | ||
374 | None => (None, None), | ||
375 | } | ||
376 | }, | ||
305 | } | 377 | } |
306 | }; | 378 | }; |
307 | Some(ty) | 379 | |
308 | }) | 380 | break ret; |
309 | .flatten(); | 381 | } |
382 | }; | ||
383 | self.expected_type = expected.0; | ||
384 | self.expected_name = expected.1; | ||
310 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | 385 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); |
311 | 386 | ||
312 | // First, let's try to complete a reference to some declaration. | 387 | // First, let's try to complete a reference to some declaration. |
@@ -535,3 +610,197 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<ast::Path> { | |||
535 | let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?; | 610 | let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?; |
536 | use_tree.path() | 611 | use_tree.path() |
537 | } | 612 | } |
613 | |||
614 | #[cfg(test)] | ||
615 | mod tests { | ||
616 | use expect_test::{expect, Expect}; | ||
617 | use hir::HirDisplay; | ||
618 | |||
619 | use crate::test_utils::{position, TEST_CONFIG}; | ||
620 | |||
621 | use super::CompletionContext; | ||
622 | |||
623 | fn check_expected_type_and_name(ra_fixture: &str, expect: Expect) { | ||
624 | let (db, pos) = position(ra_fixture); | ||
625 | let completion_context = CompletionContext::new(&db, pos, &TEST_CONFIG).unwrap(); | ||
626 | |||
627 | let ty = completion_context | ||
628 | .expected_type | ||
629 | .map(|t| t.display_test(&db).to_string()) | ||
630 | .unwrap_or("?".to_owned()); | ||
631 | |||
632 | let name = completion_context.expected_name.unwrap_or("?".to_owned()); | ||
633 | |||
634 | expect.assert_eq(&format!("ty: {}, name: {}", ty, name)); | ||
635 | } | ||
636 | |||
637 | #[test] | ||
638 | fn expected_type_let_without_leading_char() { | ||
639 | cov_mark::check!(expected_type_let_without_leading_char); | ||
640 | check_expected_type_and_name( | ||
641 | r#" | ||
642 | fn foo() { | ||
643 | let x: u32 = $0; | ||
644 | } | ||
645 | "#, | ||
646 | expect![[r#"ty: u32, name: x"#]], | ||
647 | ); | ||
648 | } | ||
649 | |||
650 | #[test] | ||
651 | fn expected_type_let_with_leading_char() { | ||
652 | cov_mark::check!(expected_type_let_with_leading_char); | ||
653 | check_expected_type_and_name( | ||
654 | r#" | ||
655 | fn foo() { | ||
656 | let x: u32 = c$0; | ||
657 | } | ||
658 | "#, | ||
659 | expect![[r#"ty: u32, name: x"#]], | ||
660 | ); | ||
661 | } | ||
662 | |||
663 | #[test] | ||
664 | fn expected_type_fn_param_without_leading_char() { | ||
665 | cov_mark::check!(expected_type_fn_param_without_leading_char); | ||
666 | check_expected_type_and_name( | ||
667 | r#" | ||
668 | fn foo() { | ||
669 | bar($0); | ||
670 | } | ||
671 | |||
672 | fn bar(x: u32) {} | ||
673 | "#, | ||
674 | expect![[r#"ty: u32, name: x"#]], | ||
675 | ); | ||
676 | } | ||
677 | |||
678 | #[test] | ||
679 | fn expected_type_fn_param_with_leading_char() { | ||
680 | cov_mark::check!(expected_type_fn_param_with_leading_char); | ||
681 | check_expected_type_and_name( | ||
682 | r#" | ||
683 | fn foo() { | ||
684 | bar(c$0); | ||
685 | } | ||
686 | |||
687 | fn bar(x: u32) {} | ||
688 | "#, | ||
689 | expect![[r#"ty: u32, name: x"#]], | ||
690 | ); | ||
691 | } | ||
692 | |||
693 | #[test] | ||
694 | fn expected_type_struct_field_without_leading_char() { | ||
695 | cov_mark::check!(expected_type_struct_field_without_leading_char); | ||
696 | check_expected_type_and_name( | ||
697 | r#" | ||
698 | struct Foo { a: u32 } | ||
699 | fn foo() { | ||
700 | Foo { a: $0 }; | ||
701 | } | ||
702 | "#, | ||
703 | expect![[r#"ty: u32, name: a"#]], | ||
704 | ) | ||
705 | } | ||
706 | |||
707 | #[test] | ||
708 | fn expected_type_struct_field_with_leading_char() { | ||
709 | cov_mark::check!(expected_type_struct_field_with_leading_char); | ||
710 | check_expected_type_and_name( | ||
711 | r#" | ||
712 | struct Foo { a: u32 } | ||
713 | fn foo() { | ||
714 | Foo { a: c$0 }; | ||
715 | } | ||
716 | "#, | ||
717 | expect![[r#"ty: u32, name: a"#]], | ||
718 | ); | ||
719 | } | ||
720 | |||
721 | #[test] | ||
722 | fn expected_type_match_arm_without_leading_char() { | ||
723 | cov_mark::check!(expected_type_match_arm_without_leading_char); | ||
724 | check_expected_type_and_name( | ||
725 | r#" | ||
726 | enum E { X } | ||
727 | fn foo() { | ||
728 | match E::X { $0 } | ||
729 | } | ||
730 | "#, | ||
731 | expect![[r#"ty: E, name: ?"#]], | ||
732 | ); | ||
733 | } | ||
734 | |||
735 | #[test] | ||
736 | fn expected_type_match_arm_with_leading_char() { | ||
737 | cov_mark::check!(expected_type_match_arm_with_leading_char); | ||
738 | check_expected_type_and_name( | ||
739 | r#" | ||
740 | enum E { X } | ||
741 | fn foo() { | ||
742 | match E::X { c$0 } | ||
743 | } | ||
744 | "#, | ||
745 | expect![[r#"ty: E, name: ?"#]], | ||
746 | ); | ||
747 | } | ||
748 | |||
749 | #[test] | ||
750 | fn expected_type_if_let_without_leading_char() { | ||
751 | cov_mark::check!(expected_type_if_let_without_leading_char); | ||
752 | check_expected_type_and_name( | ||
753 | r#" | ||
754 | enum Foo { Bar, Baz, Quux } | ||
755 | |||
756 | fn foo() { | ||
757 | let f = Foo::Quux; | ||
758 | if let $0 = f { } | ||
759 | } | ||
760 | "#, | ||
761 | expect![[r#"ty: (), name: ?"#]], | ||
762 | ) // FIXME should be `ty: u32, name: ?` | ||
763 | } | ||
764 | |||
765 | #[test] | ||
766 | fn expected_type_if_let_with_leading_char() { | ||
767 | cov_mark::check!(expected_type_if_let_with_leading_char); | ||
768 | check_expected_type_and_name( | ||
769 | r#" | ||
770 | enum Foo { Bar, Baz, Quux } | ||
771 | |||
772 | fn foo() { | ||
773 | let f = Foo::Quux; | ||
774 | if let c$0 = f { } | ||
775 | } | ||
776 | "#, | ||
777 | expect![[r#"ty: Foo, name: ?"#]], | ||
778 | ) | ||
779 | } | ||
780 | |||
781 | #[test] | ||
782 | fn expected_type_fn_ret_without_leading_char() { | ||
783 | cov_mark::check!(expected_type_fn_ret_without_leading_char); | ||
784 | check_expected_type_and_name( | ||
785 | r#" | ||
786 | fn foo() -> u32 { | ||
787 | $0 | ||
788 | } | ||
789 | "#, | ||
790 | expect![[r#"ty: (), name: ?"#]], | ||
791 | ) // FIXME this should be `ty: u32, name: ?` | ||
792 | } | ||
793 | |||
794 | #[test] | ||
795 | fn expected_type_fn_ret_with_leading_char() { | ||
796 | cov_mark::check!(expected_type_fn_ret_with_leading_char); | ||
797 | check_expected_type_and_name( | ||
798 | r#" | ||
799 | fn foo() -> u32 { | ||
800 | c$0 | ||
801 | } | ||
802 | "#, | ||
803 | expect![[r#"ty: u32, name: ?"#]], | ||
804 | ) | ||
805 | } | ||
806 | } | ||
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 905f0b197..3e1bff4d6 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -119,17 +119,10 @@ impl<'a> RenderContext<'a> { | |||
119 | node.docs(self.db()) | 119 | node.docs(self.db()) |
120 | } | 120 | } |
121 | 121 | ||
122 | // FIXME delete this method in favor of directly using the fields | ||
123 | // on CompletionContext | ||
122 | fn expected_name_and_type(&self) -> Option<(String, Type)> { | 124 | fn expected_name_and_type(&self) -> Option<(String, Type)> { |
123 | if let Some(record_field) = &self.completion.record_field_syntax { | 125 | Some((self.completion.expected_name.clone()?, self.completion.expected_type.clone()?)) |
124 | cov_mark::hit!(record_field_type_match); | ||
125 | let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?; | ||
126 | Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db()))) | ||
127 | } else if let Some(active_parameter) = &self.completion.active_parameter { | ||
128 | cov_mark::hit!(active_param_type_match); | ||
129 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) | ||
130 | } else { | ||
131 | None | ||
132 | } | ||
133 | } | 126 | } |
134 | } | 127 | } |
135 | 128 | ||
@@ -852,7 +845,6 @@ fn foo(xs: Vec<i128>) | |||
852 | 845 | ||
853 | #[test] | 846 | #[test] |
854 | fn active_param_relevance() { | 847 | fn active_param_relevance() { |
855 | cov_mark::check!(active_param_type_match); | ||
856 | check_relevance( | 848 | check_relevance( |
857 | r#" | 849 | r#" |
858 | struct S { foo: i64, bar: u32, baz: u32 } | 850 | struct S { foo: i64, bar: u32, baz: u32 } |
@@ -869,7 +861,6 @@ fn foo(s: S) { test(s.$0) } | |||
869 | 861 | ||
870 | #[test] | 862 | #[test] |
871 | fn record_field_relevances() { | 863 | fn record_field_relevances() { |
872 | cov_mark::check!(record_field_type_match); | ||
873 | check_relevance( | 864 | check_relevance( |
874 | r#" | 865 | r#" |
875 | struct A { foo: i64, bar: u32, baz: u32 } | 866 | struct A { foo: i64, bar: u32, baz: u32 } |