aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
authorFlorian Diebold <[email protected]>2020-03-07 14:27:03 +0000
committerFlorian Diebold <[email protected]>2020-03-07 14:48:06 +0000
commit24e98121d81b75bafcd9c6005548776c00de8401 (patch)
tree976841b2501ab4501613a5f66b1004cd67f7e369 /crates/ra_ide
parentaff82cf7ac172f213cb5dcca637cb2c5332294c1 (diff)
Try to complete within macros
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs98
-rw-r--r--crates/ra_ide/src/completion/complete_path.rs2
-rw-r--r--crates/ra_ide/src/completion/complete_scope.rs70
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs87
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
3use hir::{Adt, PathResolution, ScopeDef}; 3use hir::{Adt, PathResolution, ScopeDef};
4use ra_syntax::AstNode; 4use 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
3use crate::completion::{CompletionContext, Completions}; 3use 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;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use 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 &macro_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 }