diff options
author | Florian Diebold <[email protected]> | 2020-03-07 14:27:03 +0000 |
---|---|---|
committer | Florian Diebold <[email protected]> | 2020-03-07 14:48:06 +0000 |
commit | 24e98121d81b75bafcd9c6005548776c00de8401 (patch) | |
tree | 976841b2501ab4501613a5f66b1004cd67f7e369 /crates/ra_ide/src | |
parent | aff82cf7ac172f213cb5dcca637cb2c5332294c1 (diff) |
Try to complete within macros
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r-- | crates/ra_ide/src/completion/complete_dot.rs | 98 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_path.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_scope.rs | 70 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 87 |
4 files changed, 230 insertions, 27 deletions
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs index 9145aa183..da2c4c1ab 100644 --- a/crates/ra_ide/src/completion/complete_dot.rs +++ b/crates/ra_ide/src/completion/complete_dot.rs | |||
@@ -584,4 +584,102 @@ mod tests { | |||
584 | "### | 584 | "### |
585 | ); | 585 | ); |
586 | } | 586 | } |
587 | |||
588 | #[test] | ||
589 | fn works_in_simple_macro_1() { | ||
590 | assert_debug_snapshot!( | ||
591 | do_ref_completion( | ||
592 | r" | ||
593 | macro_rules! m { ($e:expr) => { $e } } | ||
594 | struct A { the_field: u32 } | ||
595 | fn foo(a: A) { | ||
596 | m!(a.x<|>) | ||
597 | } | ||
598 | ", | ||
599 | ), | ||
600 | @r###" | ||
601 | [ | ||
602 | CompletionItem { | ||
603 | label: "the_field", | ||
604 | source_range: [156; 157), | ||
605 | delete: [156; 157), | ||
606 | insert: "the_field", | ||
607 | kind: Field, | ||
608 | detail: "u32", | ||
609 | }, | ||
610 | ] | ||
611 | "### | ||
612 | ); | ||
613 | } | ||
614 | |||
615 | #[test] | ||
616 | fn works_in_simple_macro_recursive() { | ||
617 | assert_debug_snapshot!( | ||
618 | do_ref_completion( | ||
619 | r" | ||
620 | macro_rules! m { ($e:expr) => { $e } } | ||
621 | struct A { the_field: u32 } | ||
622 | fn foo(a: A) { | ||
623 | m!(a.x<|>) | ||
624 | } | ||
625 | ", | ||
626 | ), | ||
627 | @r###" | ||
628 | [ | ||
629 | CompletionItem { | ||
630 | label: "the_field", | ||
631 | source_range: [156; 157), | ||
632 | delete: [156; 157), | ||
633 | insert: "the_field", | ||
634 | kind: Field, | ||
635 | detail: "u32", | ||
636 | }, | ||
637 | ] | ||
638 | "### | ||
639 | ); | ||
640 | } | ||
641 | |||
642 | #[test] | ||
643 | fn works_in_simple_macro_2() { | ||
644 | // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery | ||
645 | assert_debug_snapshot!( | ||
646 | do_ref_completion( | ||
647 | r" | ||
648 | macro_rules! m { ($e:expr) => { $e } } | ||
649 | struct A { the_field: u32 } | ||
650 | fn foo(a: A) { | ||
651 | m!(a.<|>) | ||
652 | } | ||
653 | ", | ||
654 | ), | ||
655 | @r###"[]"### | ||
656 | ); | ||
657 | } | ||
658 | |||
659 | #[test] | ||
660 | fn works_in_simple_macro_recursive_1() { | ||
661 | assert_debug_snapshot!( | ||
662 | do_ref_completion( | ||
663 | r" | ||
664 | macro_rules! m { ($e:expr) => { $e } } | ||
665 | struct A { the_field: u32 } | ||
666 | fn foo(a: A) { | ||
667 | m!(m!(m!(a.x<|>))) | ||
668 | } | ||
669 | ", | ||
670 | ), | ||
671 | @r###" | ||
672 | [ | ||
673 | CompletionItem { | ||
674 | label: "the_field", | ||
675 | source_range: [162; 163), | ||
676 | delete: [162; 163), | ||
677 | insert: "the_field", | ||
678 | kind: Field, | ||
679 | detail: "u32", | ||
680 | }, | ||
681 | ] | ||
682 | "### | ||
683 | ); | ||
684 | } | ||
587 | } | 685 | } |
diff --git a/crates/ra_ide/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs index 1a9699466..4fa47951a 100644 --- a/crates/ra_ide/src/completion/complete_path.rs +++ b/crates/ra_ide/src/completion/complete_path.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! Completion of paths, including when writing a single name. | 1 | //! Completion of paths, i.e. `some::prefix::<|>`. |
2 | 2 | ||
3 | use hir::{Adt, PathResolution, ScopeDef}; | 3 | use hir::{Adt, PathResolution, ScopeDef}; |
4 | use ra_syntax::AstNode; | 4 | use ra_syntax::AstNode; |
diff --git a/crates/ra_ide/src/completion/complete_scope.rs b/crates/ra_ide/src/completion/complete_scope.rs index 2b9a0e556..eb3c8cf1b 100644 --- a/crates/ra_ide/src/completion/complete_scope.rs +++ b/crates/ra_ide/src/completion/complete_scope.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! Completion of names from the current scope, e.g. locals and imported items. |
2 | 2 | ||
3 | use crate::completion::{CompletionContext, Completions}; | 3 | use crate::completion::{CompletionContext, Completions}; |
4 | 4 | ||
@@ -797,4 +797,72 @@ mod tests { | |||
797 | "### | 797 | "### |
798 | ) | 798 | ) |
799 | } | 799 | } |
800 | |||
801 | #[test] | ||
802 | fn completes_in_simple_macro_1() { | ||
803 | assert_debug_snapshot!( | ||
804 | do_reference_completion( | ||
805 | r" | ||
806 | macro_rules! m { ($e:expr) => { $e } } | ||
807 | fn quux(x: i32) { | ||
808 | let y = 92; | ||
809 | m!(<|>); | ||
810 | } | ||
811 | " | ||
812 | ), | ||
813 | @"[]" | ||
814 | ); | ||
815 | } | ||
816 | |||
817 | #[test] | ||
818 | fn completes_in_simple_macro_2() { | ||
819 | assert_debug_snapshot!( | ||
820 | do_reference_completion( | ||
821 | r" | ||
822 | macro_rules! m { ($e:expr) => { $e } } | ||
823 | fn quux(x: i32) { | ||
824 | let y = 92; | ||
825 | m!(x<|>); | ||
826 | } | ||
827 | " | ||
828 | ), | ||
829 | @r###" | ||
830 | [ | ||
831 | CompletionItem { | ||
832 | label: "m!", | ||
833 | source_range: [145; 146), | ||
834 | delete: [145; 146), | ||
835 | insert: "m!($0)", | ||
836 | kind: Macro, | ||
837 | detail: "macro_rules! m", | ||
838 | }, | ||
839 | CompletionItem { | ||
840 | label: "quux(…)", | ||
841 | source_range: [145; 146), | ||
842 | delete: [145; 146), | ||
843 | insert: "quux(${1:x})$0", | ||
844 | kind: Function, | ||
845 | lookup: "quux", | ||
846 | detail: "fn quux(x: i32)", | ||
847 | }, | ||
848 | CompletionItem { | ||
849 | label: "x", | ||
850 | source_range: [145; 146), | ||
851 | delete: [145; 146), | ||
852 | insert: "x", | ||
853 | kind: Binding, | ||
854 | detail: "i32", | ||
855 | }, | ||
856 | CompletionItem { | ||
857 | label: "y", | ||
858 | source_range: [145; 146), | ||
859 | delete: [145; 146), | ||
860 | insert: "y", | ||
861 | kind: Binding, | ||
862 | detail: "i32", | ||
863 | }, | ||
864 | ] | ||
865 | "### | ||
866 | ); | ||
867 | } | ||
800 | } | 868 | } |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 9aa5a705d..81a033fcb 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -5,7 +5,7 @@ use ra_db::SourceDatabase; | |||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
8 | ast, AstNode, SourceFile, | 8 | ast, AstNode, |
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextUnit, | 10 | SyntaxNode, SyntaxToken, TextRange, TextUnit, |
11 | }; | 11 | }; |
@@ -20,6 +20,9 @@ pub(crate) struct CompletionContext<'a> { | |||
20 | pub(super) sema: Semantics<'a, RootDatabase>, | 20 | pub(super) sema: Semantics<'a, RootDatabase>, |
21 | pub(super) db: &'a RootDatabase, | 21 | pub(super) db: &'a RootDatabase, |
22 | pub(super) offset: TextUnit, | 22 | pub(super) offset: TextUnit, |
23 | /// The token before the cursor, in the original file. | ||
24 | pub(super) original_token: SyntaxToken, | ||
25 | /// The token before the cursor, in the macro-expanded file. | ||
23 | pub(super) token: SyntaxToken, | 26 | pub(super) token: SyntaxToken, |
24 | pub(super) module: Option<hir::Module>, | 27 | pub(super) module: Option<hir::Module>, |
25 | pub(super) name_ref_syntax: Option<ast::NameRef>, | 28 | pub(super) name_ref_syntax: Option<ast::NameRef>, |
@@ -67,12 +70,18 @@ impl<'a> CompletionContext<'a> { | |||
67 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); | 70 | let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); |
68 | parse.reparse(&edit).tree() | 71 | parse.reparse(&edit).tree() |
69 | }; | 72 | }; |
73 | let fake_ident_token = | ||
74 | file_with_fake_ident.syntax().token_at_offset(position.offset).right_biased().unwrap(); | ||
70 | 75 | ||
76 | // TODO: shouldn't this take the position into account? (in case we're inside a mod {}) | ||
71 | let module = sema.to_module_def(position.file_id); | 77 | let module = sema.to_module_def(position.file_id); |
72 | let token = original_file.syntax().token_at_offset(position.offset).left_biased()?; | 78 | let original_token = |
79 | original_file.syntax().token_at_offset(position.offset).left_biased()?; | ||
80 | let token = sema.descend_into_macros(original_token.clone()); | ||
73 | let mut ctx = CompletionContext { | 81 | let mut ctx = CompletionContext { |
74 | sema, | 82 | sema, |
75 | db, | 83 | db, |
84 | original_token, | ||
76 | token, | 85 | token, |
77 | offset: position.offset, | 86 | offset: position.offset, |
78 | module, | 87 | module, |
@@ -95,15 +104,45 @@ impl<'a> CompletionContext<'a> { | |||
95 | has_type_args: false, | 104 | has_type_args: false, |
96 | dot_receiver_is_ambiguous_float_literal: false, | 105 | dot_receiver_is_ambiguous_float_literal: false, |
97 | }; | 106 | }; |
98 | ctx.fill(&original_file, file_with_fake_ident, position.offset); | 107 | |
108 | let mut original_file = original_file.syntax().clone(); | ||
109 | let mut hypothetical_file = file_with_fake_ident.syntax().clone(); | ||
110 | let mut offset = position.offset; | ||
111 | let mut fake_ident_token = fake_ident_token; | ||
112 | |||
113 | // Are we inside a macro call? | ||
114 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( | ||
115 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), | ||
116 | find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset), | ||
117 | ) { | ||
118 | if let (Some(actual_expansion), Some(hypothetical_expansion)) = ( | ||
119 | ctx.sema.expand(&actual_macro_call), | ||
120 | ctx.sema.expand_hypothetical( | ||
121 | &actual_macro_call, | ||
122 | ¯o_call_with_fake_ident, | ||
123 | fake_ident_token, | ||
124 | ), | ||
125 | ) { | ||
126 | // TODO check that the expansions 'look the same' up to the inserted token? | ||
127 | original_file = actual_expansion; | ||
128 | hypothetical_file = hypothetical_expansion.0; | ||
129 | fake_ident_token = hypothetical_expansion.1; | ||
130 | offset = fake_ident_token.text_range().start(); | ||
131 | } else { | ||
132 | break; | ||
133 | } | ||
134 | } | ||
135 | |||
136 | ctx.fill(&original_file, hypothetical_file, offset); | ||
99 | Some(ctx) | 137 | Some(ctx) |
100 | } | 138 | } |
101 | 139 | ||
102 | // The range of the identifier that is being completed. | 140 | // The range of the identifier that is being completed. |
103 | pub(crate) fn source_range(&self) -> TextRange { | 141 | pub(crate) fn source_range(&self) -> TextRange { |
142 | // check kind of macro-expanded token, but use range of original token | ||
104 | match self.token.kind() { | 143 | match self.token.kind() { |
105 | // workaroud when completion is triggered by trigger characters. | 144 | // workaroud when completion is triggered by trigger characters. |
106 | IDENT => self.token.text_range(), | 145 | IDENT => self.original_token.text_range(), |
107 | _ => TextRange::offset_len(self.offset, 0.into()), | 146 | _ => TextRange::offset_len(self.offset, 0.into()), |
108 | } | 147 | } |
109 | } | 148 | } |
@@ -114,14 +153,12 @@ impl<'a> CompletionContext<'a> { | |||
114 | 153 | ||
115 | fn fill( | 154 | fn fill( |
116 | &mut self, | 155 | &mut self, |
117 | original_file: &ast::SourceFile, | 156 | original_file: &SyntaxNode, |
118 | file_with_fake_ident: ast::SourceFile, | 157 | file_with_fake_ident: SyntaxNode, |
119 | offset: TextUnit, | 158 | offset: TextUnit, |
120 | ) { | 159 | ) { |
121 | // First, let's try to complete a reference to some declaration. | 160 | // First, let's try to complete a reference to some declaration. |
122 | if let Some(name_ref) = | 161 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { |
123 | find_node_at_offset::<ast::NameRef>(file_with_fake_ident.syntax(), offset) | ||
124 | { | ||
125 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | 162 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. |
126 | // See RFC#1685. | 163 | // See RFC#1685. |
127 | if is_node::<ast::Param>(name_ref.syntax()) { | 164 | if is_node::<ast::Param>(name_ref.syntax()) { |
@@ -133,8 +170,7 @@ impl<'a> CompletionContext<'a> { | |||
133 | 170 | ||
134 | // Otherwise, see if this is a declaration. We can use heuristics to | 171 | // Otherwise, see if this is a declaration. We can use heuristics to |
135 | // suggest declaration names, see `CompletionKind::Magic`. | 172 | // suggest declaration names, see `CompletionKind::Magic`. |
136 | if let Some(name) = find_node_at_offset::<ast::Name>(file_with_fake_ident.syntax(), offset) | 173 | if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) { |
137 | { | ||
138 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { | 174 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { |
139 | let parent = bind_pat.syntax().parent(); | 175 | let parent = bind_pat.syntax().parent(); |
140 | if parent.clone().and_then(ast::MatchArm::cast).is_some() | 176 | if parent.clone().and_then(ast::MatchArm::cast).is_some() |
@@ -148,23 +184,24 @@ impl<'a> CompletionContext<'a> { | |||
148 | return; | 184 | return; |
149 | } | 185 | } |
150 | if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { | 186 | if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { |
151 | self.record_lit_pat = find_node_at_offset(original_file.syntax(), self.offset); | 187 | self.record_lit_pat = |
188 | self.sema.find_node_at_offset_with_macros(&original_file, self.offset); | ||
152 | } | 189 | } |
153 | } | 190 | } |
154 | } | 191 | } |
155 | 192 | ||
156 | fn classify_name_ref(&mut self, original_file: &SourceFile, name_ref: ast::NameRef) { | 193 | fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) { |
157 | self.name_ref_syntax = | 194 | self.name_ref_syntax = |
158 | find_node_at_offset(original_file.syntax(), name_ref.syntax().text_range().start()); | 195 | find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); |
159 | let name_range = name_ref.syntax().text_range(); | 196 | let name_range = name_ref.syntax().text_range(); |
160 | if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { | 197 | if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { |
161 | self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset); | 198 | self.record_lit_syntax = |
199 | self.sema.find_node_at_offset_with_macros(&original_file, self.offset); | ||
162 | } | 200 | } |
163 | 201 | ||
164 | self.impl_def = self | 202 | self.impl_def = self |
165 | .token | 203 | .sema |
166 | .parent() | 204 | .ancestors_with_macros(self.token.parent()) |
167 | .ancestors() | ||
168 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | 205 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) |
169 | .find_map(ast::ImplDef::cast); | 206 | .find_map(ast::ImplDef::cast); |
170 | 207 | ||
@@ -183,12 +220,12 @@ impl<'a> CompletionContext<'a> { | |||
183 | _ => (), | 220 | _ => (), |
184 | } | 221 | } |
185 | 222 | ||
186 | self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast); | 223 | self.use_item_syntax = |
224 | self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast); | ||
187 | 225 | ||
188 | self.function_syntax = self | 226 | self.function_syntax = self |
189 | .token | 227 | .sema |
190 | .parent() | 228 | .ancestors_with_macros(self.token.parent()) |
191 | .ancestors() | ||
192 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | 229 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) |
193 | .find_map(ast::FnDef::cast); | 230 | .find_map(ast::FnDef::cast); |
194 | 231 | ||
@@ -242,7 +279,7 @@ impl<'a> CompletionContext<'a> { | |||
242 | 279 | ||
243 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { | 280 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { |
244 | if let Some(if_expr) = | 281 | if let Some(if_expr) = |
245 | find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off) | 282 | self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off) |
246 | { | 283 | { |
247 | if if_expr.syntax().text_range().end() | 284 | if if_expr.syntax().text_range().end() |
248 | < name_ref.syntax().text_range().start() | 285 | < name_ref.syntax().text_range().start() |
@@ -259,7 +296,7 @@ impl<'a> CompletionContext<'a> { | |||
259 | self.dot_receiver = field_expr | 296 | self.dot_receiver = field_expr |
260 | .expr() | 297 | .expr() |
261 | .map(|e| e.syntax().text_range()) | 298 | .map(|e| e.syntax().text_range()) |
262 | .and_then(|r| find_node_with_range(original_file.syntax(), r)); | 299 | .and_then(|r| find_node_with_range(original_file, r)); |
263 | self.dot_receiver_is_ambiguous_float_literal = | 300 | self.dot_receiver_is_ambiguous_float_literal = |
264 | if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { | 301 | if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { |
265 | match l.kind() { | 302 | match l.kind() { |
@@ -275,7 +312,7 @@ impl<'a> CompletionContext<'a> { | |||
275 | self.dot_receiver = method_call_expr | 312 | self.dot_receiver = method_call_expr |
276 | .expr() | 313 | .expr() |
277 | .map(|e| e.syntax().text_range()) | 314 | .map(|e| e.syntax().text_range()) |
278 | .and_then(|r| find_node_with_range(original_file.syntax(), r)); | 315 | .and_then(|r| find_node_with_range(original_file, r)); |
279 | self.is_call = true; | 316 | self.is_call = true; |
280 | } | 317 | } |
281 | } | 318 | } |