aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-03-09 08:56:58 +0000
committerGitHub <[email protected]>2020-03-09 08:56:58 +0000
commitbeb4f4954179998e317db33e47a48a9bb7374977 (patch)
treee2af54ce707bfcdd0a0cd3fc5d547f43fc9fb7d1 /crates
parent30062da6284052deac04c759540f81d5b326689c (diff)
parentafdf08e964345ac4a884a5630772611ba81f6969 (diff)
Merge #3513
3513: Completion in macros r=matklad a=flodiebold I experimented a bit with completion in macros. It's kind of working, but there are a lot of rough edges. - I'm trying to expand the macro call with the inserted fake token. This requires some hacky additions on the HIR level to be able to do "hypothetical" expansions. There should probably be a nicer API for this, if we want to do it this way. I'm not sure whether it's worth it, because we still can't do a lot if the original macro call didn't expand in nearly the same way. E.g. if we have something like `println!("", x<|>)` the expansions will look the same and everything is fine; but in that case we could maybe have achieved the same result in a simpler way. If we have something like `m!(<|>)` where `m!()` doesn't even expand or expands to something very different, we don't really know what to do anyway. - Relatedly, there are a lot of cases where this doesn't work because either the original call or the hypothetical call doesn't expand. E.g. if we have `m!(x.<|>)` the original token tree doesn't parse as an expression; if we have `m!(match x { <|> })` the hypothetical token tree doesn't parse. It would be nice if we could have better error recovery in these cases. Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_hir/Cargo.toml2
-rw-r--r--crates/ra_hir/src/semantics.rs35
-rw-r--r--crates/ra_hir_expand/src/db.rs71
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs102
-rw-r--r--crates/ra_ide/src/completion/complete_keyword.rs1
-rw-r--r--crates/ra_ide/src/completion/complete_path.rs37
-rw-r--r--crates/ra_ide/src/completion/complete_pattern.rs18
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs65
-rw-r--r--crates/ra_ide/src/completion/complete_record_literal.rs25
-rw-r--r--crates/ra_ide/src/completion/complete_record_pattern.rs28
-rw-r--r--crates/ra_ide/src/completion/complete_scope.rs70
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs111
-rw-r--r--crates/ra_mbe/src/mbe_expander/matcher.rs4
13 files changed, 527 insertions, 42 deletions
diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml
index 0555a0de7..266c4cff3 100644
--- a/crates/ra_hir/Cargo.toml
+++ b/crates/ra_hir/Cargo.toml
@@ -12,6 +12,8 @@ log = "0.4.8"
12rustc-hash = "1.1.0" 12rustc-hash = "1.1.0"
13either = "1.5.3" 13either = "1.5.3"
14 14
15itertools = "0.8.2"
16
15ra_syntax = { path = "../ra_syntax" } 17ra_syntax = { path = "../ra_syntax" }
16ra_db = { path = "../ra_db" } 18ra_db = { path = "../ra_db" }
17ra_prof = { path = "../ra_prof" } 19ra_prof = { path = "../ra_prof" }
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs
index 965d185a4..3782a9984 100644
--- a/crates/ra_hir/src/semantics.rs
+++ b/crates/ra_hir/src/semantics.rs
@@ -6,7 +6,7 @@ use std::{cell::RefCell, fmt, iter::successors};
6 6
7use hir_def::{ 7use hir_def::{
8 resolver::{self, HasResolver, Resolver}, 8 resolver::{self, HasResolver, Resolver},
9 TraitId, 9 AsMacroCall, TraitId,
10}; 10};
11use hir_expand::ExpansionInfo; 11use hir_expand::ExpansionInfo;
12use ra_db::{FileId, FileRange}; 12use ra_db::{FileId, FileRange};
@@ -70,6 +70,20 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
70 Some(node) 70 Some(node)
71 } 71 }
72 72
73 pub fn expand_hypothetical(
74 &self,
75 actual_macro_call: &ast::MacroCall,
76 hypothetical_args: &ast::TokenTree,
77 token_to_map: SyntaxToken,
78 ) -> Option<(SyntaxNode, SyntaxToken)> {
79 let macro_call =
80 self.find_file(actual_macro_call.syntax().clone()).with_value(actual_macro_call);
81 let sa = self.analyze2(macro_call.map(|it| it.syntax()), None);
82 let macro_call_id = macro_call
83 .as_call_id(self.db, |path| sa.resolver.resolve_path_as_macro(self.db, &path))?;
84 hir_expand::db::expand_hypothetical(self.db, macro_call_id, hypothetical_args, token_to_map)
85 }
86
73 pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken { 87 pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
74 let parent = token.parent(); 88 let parent = token.parent();
75 let parent = self.find_file(parent); 89 let parent = self.find_file(parent);
@@ -104,6 +118,25 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
104 node.ancestors_with_macros(self.db).map(|it| it.value) 118 node.ancestors_with_macros(self.db).map(|it| it.value)
105 } 119 }
106 120
121 pub fn ancestors_at_offset_with_macros(
122 &self,
123 node: &SyntaxNode,
124 offset: TextUnit,
125 ) -> impl Iterator<Item = SyntaxNode> + '_ {
126 use itertools::Itertools;
127 node.token_at_offset(offset)
128 .map(|token| self.ancestors_with_macros(token.parent()))
129 .kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len())
130 }
131
132 pub fn find_node_at_offset_with_macros<N: AstNode>(
133 &self,
134 node: &SyntaxNode,
135 offset: TextUnit,
136 ) -> Option<N> {
137 self.ancestors_at_offset_with_macros(node, offset).find_map(N::cast)
138 }
139
107 pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> { 140 pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> {
108 self.analyze(expr.syntax()).type_of(self.db, &expr) 141 self.analyze(expr.syntax()).type_of(self.db, &expr)
109 } 142 }
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
index f3a84cacc..29dde3d80 100644
--- a/crates/ra_hir_expand/src/db.rs
+++ b/crates/ra_hir_expand/src/db.rs
@@ -72,6 +72,30 @@ pub trait AstDatabase: SourceDatabase {
72 fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; 72 fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
73} 73}
74 74
75/// This expands the given macro call, but with different arguments. This is
76/// used for completion, where we want to see what 'would happen' if we insert a
77/// token. The `token_to_map` mapped down into the expansion, with the mapped
78/// token returned.
79pub fn expand_hypothetical(
80 db: &impl AstDatabase,
81 actual_macro_call: MacroCallId,
82 hypothetical_args: &ra_syntax::ast::TokenTree,
83 token_to_map: ra_syntax::SyntaxToken,
84) -> Option<(SyntaxNode, ra_syntax::SyntaxToken)> {
85 let macro_file = MacroFile { macro_call_id: actual_macro_call };
86 let (tt, tmap_1) = mbe::syntax_node_to_token_tree(hypothetical_args.syntax()).unwrap();
87 let range =
88 token_to_map.text_range().checked_sub(hypothetical_args.syntax().text_range().start())?;
89 let token_id = tmap_1.token_by_range(range)?;
90 let macro_def = expander(db, actual_macro_call)?;
91 let (node, tmap_2) =
92 parse_macro_with_arg(db, macro_file, Some(std::sync::Arc::new((tt, tmap_1))))?;
93 let token_id = macro_def.0.map_id_down(token_id);
94 let range = tmap_2.range_by_token(token_id)?.by_kind(token_to_map.kind())?;
95 let token = ra_syntax::algo::find_covering_element(&node.syntax_node(), range).into_token()?;
96 Some((node.syntax_node(), token))
97}
98
75pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> { 99pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> {
76 let map = 100 let map =
77 db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it)); 101 db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it));
@@ -130,15 +154,42 @@ pub(crate) fn macro_expand(
130 db: &dyn AstDatabase, 154 db: &dyn AstDatabase,
131 id: MacroCallId, 155 id: MacroCallId,
132) -> Result<Arc<tt::Subtree>, String> { 156) -> Result<Arc<tt::Subtree>, String> {
157 macro_expand_with_arg(db, id, None)
158}
159
160fn expander(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
161 let lazy_id = match id {
162 MacroCallId::LazyMacro(id) => id,
163 MacroCallId::EagerMacro(_id) => {
164 return None;
165 }
166 };
167
168 let loc = db.lookup_intern_macro(lazy_id);
169 let macro_rules = db.macro_def(loc.def)?;
170 Some(macro_rules)
171}
172
173fn macro_expand_with_arg(
174 db: &dyn AstDatabase,
175 id: MacroCallId,
176 arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
177) -> Result<Arc<tt::Subtree>, String> {
133 let lazy_id = match id { 178 let lazy_id = match id {
134 MacroCallId::LazyMacro(id) => id, 179 MacroCallId::LazyMacro(id) => id,
135 MacroCallId::EagerMacro(id) => { 180 MacroCallId::EagerMacro(id) => {
136 return Ok(db.lookup_intern_eager_expansion(id).subtree); 181 if arg.is_some() {
182 return Err(
183 "hypothetical macro expansion not implemented for eager macro".to_owned()
184 );
185 } else {
186 return Ok(db.lookup_intern_eager_expansion(id).subtree);
187 }
137 } 188 }
138 }; 189 };
139 190
140 let loc = db.lookup_intern_macro(lazy_id); 191 let loc = db.lookup_intern_macro(lazy_id);
141 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?; 192 let macro_arg = arg.or_else(|| db.macro_arg(id)).ok_or("Fail to args in to tt::TokenTree")?;
142 193
143 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; 194 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
144 let tt = macro_rules.0.expand(db, lazy_id, &macro_arg.0).map_err(|err| format!("{:?}", err))?; 195 let tt = macro_rules.0.expand(db, lazy_id, &macro_arg.0).map_err(|err| format!("{:?}", err))?;
@@ -163,11 +214,23 @@ pub(crate) fn parse_macro(
163 db: &dyn AstDatabase, 214 db: &dyn AstDatabase,
164 macro_file: MacroFile, 215 macro_file: MacroFile,
165) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { 216) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
217 parse_macro_with_arg(db, macro_file, None)
218}
219
220pub fn parse_macro_with_arg(
221 db: &dyn AstDatabase,
222 macro_file: MacroFile,
223 arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
224) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
166 let _p = profile("parse_macro_query"); 225 let _p = profile("parse_macro_query");
167 226
168 let macro_call_id = macro_file.macro_call_id; 227 let macro_call_id = macro_file.macro_call_id;
169 let tt = db 228 let expansion = if let Some(arg) = arg {
170 .macro_expand(macro_call_id) 229 macro_expand_with_arg(db, macro_call_id, Some(arg))
230 } else {
231 db.macro_expand(macro_call_id)
232 };
233 let tt = expansion
171 .map_err(|err| { 234 .map_err(|err| {
172 // Note: 235 // Note:
173 // The final goal we would like to make all parse_macro success, 236 // The final goal we would like to make all parse_macro success,
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs
index acada48ae..f275305e2 100644
--- a/crates/ra_ide/src/completion/complete_dot.rs
+++ b/crates/ra_ide/src/completion/complete_dot.rs
@@ -38,7 +38,7 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { 38fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
39 for receiver in receiver.autoderef(ctx.db) { 39 for receiver in receiver.autoderef(ctx.db) {
40 for (field, ty) in receiver.fields(ctx.db) { 40 for (field, ty) in receiver.fields(ctx.db) {
41 if ctx.module.map_or(false, |m| !field.is_visible_from(ctx.db, m)) { 41 if ctx.scope().module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
42 // Skip private field. FIXME: If the definition location of the 42 // Skip private field. FIXME: If the definition location of the
43 // field is editable, we should show the completion 43 // field is editable, we should show the completion
44 continue; 44 continue;
@@ -53,7 +53,7 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Ty
53} 53}
54 54
55fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { 55fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
56 if let Some(krate) = ctx.module.map(|it| it.krate()) { 56 if let Some(krate) = ctx.krate {
57 let mut seen_methods = FxHashSet::default(); 57 let mut seen_methods = FxHashSet::default();
58 let traits_in_scope = ctx.scope().traits_in_scope(); 58 let traits_in_scope = ctx.scope().traits_in_scope();
59 receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { 59 receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
@@ -620,4 +620,102 @@ mod tests {
620 "### 620 "###
621 ); 621 );
622 } 622 }
623
624 #[test]
625 fn works_in_simple_macro_1() {
626 assert_debug_snapshot!(
627 do_ref_completion(
628 r"
629 macro_rules! m { ($e:expr) => { $e } }
630 struct A { the_field: u32 }
631 fn foo(a: A) {
632 m!(a.x<|>)
633 }
634 ",
635 ),
636 @r###"
637 [
638 CompletionItem {
639 label: "the_field",
640 source_range: [156; 157),
641 delete: [156; 157),
642 insert: "the_field",
643 kind: Field,
644 detail: "u32",
645 },
646 ]
647 "###
648 );
649 }
650
651 #[test]
652 fn works_in_simple_macro_recursive() {
653 assert_debug_snapshot!(
654 do_ref_completion(
655 r"
656 macro_rules! m { ($e:expr) => { $e } }
657 struct A { the_field: u32 }
658 fn foo(a: A) {
659 m!(a.x<|>)
660 }
661 ",
662 ),
663 @r###"
664 [
665 CompletionItem {
666 label: "the_field",
667 source_range: [156; 157),
668 delete: [156; 157),
669 insert: "the_field",
670 kind: Field,
671 detail: "u32",
672 },
673 ]
674 "###
675 );
676 }
677
678 #[test]
679 fn works_in_simple_macro_2() {
680 // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery
681 assert_debug_snapshot!(
682 do_ref_completion(
683 r"
684 macro_rules! m { ($e:expr) => { $e } }
685 struct A { the_field: u32 }
686 fn foo(a: A) {
687 m!(a.<|>)
688 }
689 ",
690 ),
691 @r###"[]"###
692 );
693 }
694
695 #[test]
696 fn works_in_simple_macro_recursive_1() {
697 assert_debug_snapshot!(
698 do_ref_completion(
699 r"
700 macro_rules! m { ($e:expr) => { $e } }
701 struct A { the_field: u32 }
702 fn foo(a: A) {
703 m!(m!(m!(a.x<|>)))
704 }
705 ",
706 ),
707 @r###"
708 [
709 CompletionItem {
710 label: "the_field",
711 source_range: [162; 163),
712 delete: [162; 163),
713 insert: "the_field",
714 kind: Field,
715 detail: "u32",
716 },
717 ]
718 "###
719 );
720 }
623} 721}
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs
index eb7cd9ac2..e1c0ffb1f 100644
--- a/crates/ra_ide/src/completion/complete_keyword.rs
+++ b/crates/ra_ide/src/completion/complete_keyword.rs
@@ -79,6 +79,7 @@ pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
79} 79}
80 80
81fn is_in_loop_body(leaf: &SyntaxToken) -> bool { 81fn is_in_loop_body(leaf: &SyntaxToken) -> bool {
82 // FIXME move this to CompletionContext and make it handle macros
82 for node in leaf.parent().ancestors() { 83 for node in leaf.parent().ancestors() {
83 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { 84 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
84 break; 85 break;
diff --git a/crates/ra_ide/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs
index d2c758571..3c4a70561 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, HasVisibility, PathResolution, ScopeDef}; 3use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
4use ra_syntax::AstNode; 4use ra_syntax::AstNode;
@@ -48,7 +48,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
48 }; 48 };
49 // Iterate assoc types separately 49 // Iterate assoc types separately
50 // FIXME: complete T::AssocType 50 // FIXME: complete T::AssocType
51 let krate = ctx.module.map(|m| m.krate()); 51 let krate = ctx.krate;
52 if let Some(krate) = krate { 52 if let Some(krate) = krate {
53 let traits_in_scope = ctx.scope().traits_in_scope(); 53 let traits_in_scope = ctx.scope().traits_in_scope();
54 ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { 54 ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
@@ -934,4 +934,37 @@ mod tests {
934 "### 934 "###
935 ); 935 );
936 } 936 }
937
938 #[test]
939 fn completes_in_simple_macro_call() {
940 let completions = do_reference_completion(
941 r#"
942 macro_rules! m { ($e:expr) => { $e } }
943 fn main() { m!(self::f<|>); }
944 fn foo() {}
945 "#,
946 );
947 assert_debug_snapshot!(completions, @r###"
948 [
949 CompletionItem {
950 label: "foo()",
951 source_range: [93; 94),
952 delete: [93; 94),
953 insert: "foo()$0",
954 kind: Function,
955 lookup: "foo",
956 detail: "fn foo()",
957 },
958 CompletionItem {
959 label: "main()",
960 source_range: [93; 94),
961 delete: [93; 94),
962 insert: "main()$0",
963 kind: Function,
964 lookup: "main",
965 detail: "fn main()",
966 },
967 ]
968 "###);
969 }
937} 970}
diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs
index c2c6ca002..fa8aeceda 100644
--- a/crates/ra_ide/src/completion/complete_pattern.rs
+++ b/crates/ra_ide/src/completion/complete_pattern.rs
@@ -86,4 +86,22 @@ mod tests {
86 ] 86 ]
87 "###); 87 "###);
88 } 88 }
89
90 #[test]
91 fn completes_in_simple_macro_call() {
92 // FIXME: doesn't work yet because of missing error recovery in macro expansion
93 let completions = complete(
94 r"
95 macro_rules! m { ($e:expr) => { $e } }
96 enum E { X }
97
98 fn foo() {
99 m!(match E::X {
100 <|>
101 })
102 }
103 ",
104 );
105 assert_debug_snapshot!(completions, @r###"[]"###);
106 }
89} 107}
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs
index 8a74f993a..65ecea125 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -67,8 +67,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
67 67
68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { 68fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
69 let edit = { 69 let edit = {
70 let receiver_range = 70 let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax();
71 ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range(); 71 let receiver_range = ctx.sema.original_range(receiver_syntax).range;
72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); 72 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
73 TextEdit::replace(delete_range, snippet.to_string()) 73 TextEdit::replace(delete_range, snippet.to_string())
74 }; 74 };
@@ -279,4 +279,65 @@ mod tests {
279 "### 279 "###
280 ); 280 );
281 } 281 }
282
283 #[test]
284 fn works_in_simple_macro() {
285 assert_debug_snapshot!(
286 do_postfix_completion(
287 r#"
288 macro_rules! m { ($e:expr) => { $e } }
289 fn main() {
290 let bar: u8 = 12;
291 m!(bar.b<|>)
292 }
293 "#,
294 ),
295 @r###"
296 [
297 CompletionItem {
298 label: "box",
299 source_range: [149; 150),
300 delete: [145; 150),
301 insert: "Box::new(bar)",
302 detail: "Box::new(expr)",
303 },
304 CompletionItem {
305 label: "dbg",
306 source_range: [149; 150),
307 delete: [145; 150),
308 insert: "dbg!(bar)",
309 detail: "dbg!(expr)",
310 },
311 CompletionItem {
312 label: "match",
313 source_range: [149; 150),
314 delete: [145; 150),
315 insert: "match bar {\n ${1:_} => {$0\\},\n}",
316 detail: "match expr {}",
317 },
318 CompletionItem {
319 label: "not",
320 source_range: [149; 150),
321 delete: [145; 150),
322 insert: "!bar",
323 detail: "!expr",
324 },
325 CompletionItem {
326 label: "ref",
327 source_range: [149; 150),
328 delete: [145; 150),
329 insert: "&bar",
330 detail: "&expr",
331 },
332 CompletionItem {
333 label: "refm",
334 source_range: [149; 150),
335 delete: [145; 150),
336 insert: "&mut bar",
337 detail: "&mut expr",
338 },
339 ]
340 "###
341 );
342 }
282} 343}
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs
index f98353d76..be6e4194f 100644
--- a/crates/ra_ide/src/completion/complete_record_literal.rs
+++ b/crates/ra_ide/src/completion/complete_record_literal.rs
@@ -153,4 +153,29 @@ mod tests {
153 ] 153 ]
154 "###); 154 "###);
155 } 155 }
156
157 #[test]
158 fn test_record_literal_field_in_simple_macro() {
159 let completions = complete(
160 r"
161 macro_rules! m { ($e:expr) => { $e } }
162 struct A { the_field: u32 }
163 fn foo() {
164 m!(A { the<|> })
165 }
166 ",
167 );
168 assert_debug_snapshot!(completions, @r###"
169 [
170 CompletionItem {
171 label: "the_field",
172 source_range: [137; 140),
173 delete: [137; 140),
174 insert: "the_field",
175 kind: Field,
176 detail: "u32",
177 },
178 ]
179 "###);
180 }
156} 181}
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs
index 9bdeae49f..687c57d3e 100644
--- a/crates/ra_ide/src/completion/complete_record_pattern.rs
+++ b/crates/ra_ide/src/completion/complete_record_pattern.rs
@@ -87,4 +87,32 @@ mod tests {
87 ] 87 ]
88 "###); 88 "###);
89 } 89 }
90
91 #[test]
92 fn test_record_pattern_field_in_simple_macro() {
93 let completions = complete(
94 r"
95 macro_rules! m { ($e:expr) => { $e } }
96 struct S { foo: u32 }
97
98 fn process(f: S) {
99 m!(match f {
100 S { f<|>: 92 } => (),
101 })
102 }
103 ",
104 );
105 assert_debug_snapshot!(completions, @r###"
106 [
107 CompletionItem {
108 label: "foo",
109 source_range: [171; 172),
110 delete: [171; 172),
111 insert: "foo",
112 kind: Field,
113 detail: "u32",
114 },
115 ]
116 "###);
117 }
90} 118}
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..40535c09e 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,8 +20,11 @@ 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) krate: Option<hir::Crate>,
25 pub(super) name_ref_syntax: Option<ast::NameRef>, 28 pub(super) name_ref_syntax: Option<ast::NameRef>,
26 pub(super) function_syntax: Option<ast::FnDef>, 29 pub(super) function_syntax: Option<ast::FnDef>,
27 pub(super) use_item_syntax: Option<ast::UseItem>, 30 pub(super) use_item_syntax: Option<ast::UseItem>,
@@ -67,15 +70,20 @@ 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
71 let module = sema.to_module_def(position.file_id); 76 let krate = sema.to_module_def(position.file_id).map(|m| m.krate());
72 let token = original_file.syntax().token_at_offset(position.offset).left_biased()?; 77 let original_token =
78 original_file.syntax().token_at_offset(position.offset).left_biased()?;
79 let token = sema.descend_into_macros(original_token.clone());
73 let mut ctx = CompletionContext { 80 let mut ctx = CompletionContext {
74 sema, 81 sema,
75 db, 82 db,
83 original_token,
76 token, 84 token,
77 offset: position.offset, 85 offset: position.offset,
78 module, 86 krate,
79 name_ref_syntax: None, 87 name_ref_syntax: None,
80 function_syntax: None, 88 function_syntax: None,
81 use_item_syntax: None, 89 use_item_syntax: None,
@@ -95,15 +103,57 @@ impl<'a> CompletionContext<'a> {
95 has_type_args: false, 103 has_type_args: false,
96 dot_receiver_is_ambiguous_float_literal: false, 104 dot_receiver_is_ambiguous_float_literal: false,
97 }; 105 };
98 ctx.fill(&original_file, file_with_fake_ident, position.offset); 106
107 let mut original_file = original_file.syntax().clone();
108 let mut hypothetical_file = file_with_fake_ident.syntax().clone();
109 let mut offset = position.offset;
110 let mut fake_ident_token = fake_ident_token;
111
112 // Are we inside a macro call?
113 while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
114 find_node_at_offset::<ast::MacroCall>(&original_file, offset),
115 find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset),
116 ) {
117 if actual_macro_call.path().as_ref().map(|s| s.syntax().text())
118 != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text())
119 {
120 break;
121 }
122 let hypothetical_args = match macro_call_with_fake_ident.token_tree() {
123 Some(tt) => tt,
124 None => break,
125 };
126 if let (Some(actual_expansion), Some(hypothetical_expansion)) = (
127 ctx.sema.expand(&actual_macro_call),
128 ctx.sema.expand_hypothetical(
129 &actual_macro_call,
130 &hypothetical_args,
131 fake_ident_token,
132 ),
133 ) {
134 let new_offset = hypothetical_expansion.1.text_range().start();
135 if new_offset >= actual_expansion.text_range().end() {
136 break;
137 }
138 original_file = actual_expansion;
139 hypothetical_file = hypothetical_expansion.0;
140 fake_ident_token = hypothetical_expansion.1;
141 offset = new_offset;
142 } else {
143 break;
144 }
145 }
146
147 ctx.fill(&original_file, hypothetical_file, offset);
99 Some(ctx) 148 Some(ctx)
100 } 149 }
101 150
102 // The range of the identifier that is being completed. 151 // The range of the identifier that is being completed.
103 pub(crate) fn source_range(&self) -> TextRange { 152 pub(crate) fn source_range(&self) -> TextRange {
153 // check kind of macro-expanded token, but use range of original token
104 match self.token.kind() { 154 match self.token.kind() {
105 // workaroud when completion is triggered by trigger characters. 155 // workaroud when completion is triggered by trigger characters.
106 IDENT => self.token.text_range(), 156 IDENT => self.original_token.text_range(),
107 _ => TextRange::offset_len(self.offset, 0.into()), 157 _ => TextRange::offset_len(self.offset, 0.into()),
108 } 158 }
109 } 159 }
@@ -114,27 +164,24 @@ impl<'a> CompletionContext<'a> {
114 164
115 fn fill( 165 fn fill(
116 &mut self, 166 &mut self,
117 original_file: &ast::SourceFile, 167 original_file: &SyntaxNode,
118 file_with_fake_ident: ast::SourceFile, 168 file_with_fake_ident: SyntaxNode,
119 offset: TextUnit, 169 offset: TextUnit,
120 ) { 170 ) {
121 // First, let's try to complete a reference to some declaration. 171 // First, let's try to complete a reference to some declaration.
122 if let Some(name_ref) = 172 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) {} }`. 173 // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
126 // See RFC#1685. 174 // See RFC#1685.
127 if is_node::<ast::Param>(name_ref.syntax()) { 175 if is_node::<ast::Param>(name_ref.syntax()) {
128 self.is_param = true; 176 self.is_param = true;
129 return; 177 return;
130 } 178 }
131 self.classify_name_ref(original_file, name_ref); 179 self.classify_name_ref(original_file, name_ref, offset);
132 } 180 }
133 181
134 // Otherwise, see if this is a declaration. We can use heuristics to 182 // Otherwise, see if this is a declaration. We can use heuristics to
135 // suggest declaration names, see `CompletionKind::Magic`. 183 // suggest declaration names, see `CompletionKind::Magic`.
136 if let Some(name) = find_node_at_offset::<ast::Name>(file_with_fake_ident.syntax(), offset) 184 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) { 185 if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) {
139 let parent = bind_pat.syntax().parent(); 186 let parent = bind_pat.syntax().parent();
140 if parent.clone().and_then(ast::MatchArm::cast).is_some() 187 if parent.clone().and_then(ast::MatchArm::cast).is_some()
@@ -148,23 +195,29 @@ impl<'a> CompletionContext<'a> {
148 return; 195 return;
149 } 196 }
150 if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { 197 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); 198 self.record_lit_pat =
199 self.sema.find_node_at_offset_with_macros(&original_file, offset);
152 } 200 }
153 } 201 }
154 } 202 }
155 203
156 fn classify_name_ref(&mut self, original_file: &SourceFile, name_ref: ast::NameRef) { 204 fn classify_name_ref(
205 &mut self,
206 original_file: &SyntaxNode,
207 name_ref: ast::NameRef,
208 offset: TextUnit,
209 ) {
157 self.name_ref_syntax = 210 self.name_ref_syntax =
158 find_node_at_offset(original_file.syntax(), name_ref.syntax().text_range().start()); 211 find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
159 let name_range = name_ref.syntax().text_range(); 212 let name_range = name_ref.syntax().text_range();
160 if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { 213 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); 214 self.record_lit_syntax =
215 self.sema.find_node_at_offset_with_macros(&original_file, offset);
162 } 216 }
163 217
164 self.impl_def = self 218 self.impl_def = self
165 .token 219 .sema
166 .parent() 220 .ancestors_with_macros(self.token.parent())
167 .ancestors()
168 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 221 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
169 .find_map(ast::ImplDef::cast); 222 .find_map(ast::ImplDef::cast);
170 223
@@ -183,12 +236,12 @@ impl<'a> CompletionContext<'a> {
183 _ => (), 236 _ => (),
184 } 237 }
185 238
186 self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast); 239 self.use_item_syntax =
240 self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::UseItem::cast);
187 241
188 self.function_syntax = self 242 self.function_syntax = self
189 .token 243 .sema
190 .parent() 244 .ancestors_with_macros(self.token.parent())
191 .ancestors()
192 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) 245 .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
193 .find_map(ast::FnDef::cast); 246 .find_map(ast::FnDef::cast);
194 247
@@ -242,7 +295,7 @@ impl<'a> CompletionContext<'a> {
242 295
243 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { 296 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
244 if let Some(if_expr) = 297 if let Some(if_expr) =
245 find_node_at_offset::<ast::IfExpr>(original_file.syntax(), off) 298 self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
246 { 299 {
247 if if_expr.syntax().text_range().end() 300 if if_expr.syntax().text_range().end()
248 < name_ref.syntax().text_range().start() 301 < name_ref.syntax().text_range().start()
@@ -259,7 +312,7 @@ impl<'a> CompletionContext<'a> {
259 self.dot_receiver = field_expr 312 self.dot_receiver = field_expr
260 .expr() 313 .expr()
261 .map(|e| e.syntax().text_range()) 314 .map(|e| e.syntax().text_range())
262 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 315 .and_then(|r| find_node_with_range(original_file, r));
263 self.dot_receiver_is_ambiguous_float_literal = 316 self.dot_receiver_is_ambiguous_float_literal =
264 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver { 317 if let Some(ast::Expr::Literal(l)) = &self.dot_receiver {
265 match l.kind() { 318 match l.kind() {
@@ -275,7 +328,7 @@ impl<'a> CompletionContext<'a> {
275 self.dot_receiver = method_call_expr 328 self.dot_receiver = method_call_expr
276 .expr() 329 .expr()
277 .map(|e| e.syntax().text_range()) 330 .map(|e| e.syntax().text_range())
278 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 331 .and_then(|r| find_node_with_range(original_file, r));
279 self.is_call = true; 332 self.is_call = true;
280 } 333 }
281 } 334 }
diff --git a/crates/ra_mbe/src/mbe_expander/matcher.rs b/crates/ra_mbe/src/mbe_expander/matcher.rs
index ffba03898..49c53183a 100644
--- a/crates/ra_mbe/src/mbe_expander/matcher.rs
+++ b/crates/ra_mbe/src/mbe_expander/matcher.rs
@@ -247,6 +247,7 @@ impl<'a> TtIter<'a> {
247 ra_parser::parse_fragment(&mut src, &mut sink, fragment_kind); 247 ra_parser::parse_fragment(&mut src, &mut sink, fragment_kind);
248 248
249 if !sink.cursor.is_root() || sink.error { 249 if !sink.cursor.is_root() || sink.error {
250 // FIXME better recovery in this case would help completion inside macros immensely
250 return Err(()); 251 return Err(());
251 } 252 }
252 253
@@ -375,7 +376,8 @@ fn match_meta_var(kind: &str, input: &mut TtIter) -> Result<Option<Fragment>, Ex
375 return Ok(Some(Fragment::Tokens(tt))); 376 return Ok(Some(Fragment::Tokens(tt)));
376 } 377 }
377 }; 378 };
378 let tt = input.expect_fragment(fragment).map_err(|()| err!())?; 379 let tt =
380 input.expect_fragment(fragment).map_err(|()| err!("fragment did not parse as {}", kind))?;
379 let fragment = if kind == "expr" { Fragment::Ast(tt) } else { Fragment::Tokens(tt) }; 381 let fragment = if kind == "expr" { Fragment::Ast(tt) } else { Fragment::Tokens(tt) };
380 Ok(Some(fragment)) 382 Ok(Some(fragment))
381} 383}