Skip to content

Commit beb4f49

Browse files
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 <flodiebold@gmail.com>
2 parents 30062da + afdf08e commit beb4f49

File tree

14 files changed

+528
-42
lines changed

14 files changed

+528
-42
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_hir/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ log = "0.4.8"
1212
rustc-hash = "1.1.0"
1313
either = "1.5.3"
1414

15+
itertools = "0.8.2"
16+
1517
ra_syntax = { path = "../ra_syntax" }
1618
ra_db = { path = "../ra_db" }
1719
ra_prof = { path = "../ra_prof" }

crates/ra_hir/src/semantics.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{cell::RefCell, fmt, iter::successors};
66

77
use hir_def::{
88
resolver::{self, HasResolver, Resolver},
9-
TraitId,
9+
AsMacroCall, TraitId,
1010
};
1111
use hir_expand::ExpansionInfo;
1212
use ra_db::{FileId, FileRange};
@@ -70,6 +70,20 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
7070
Some(node)
7171
}
7272

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+
7387
pub fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken {
7488
let parent = token.parent();
7589
let parent = self.find_file(parent);
@@ -104,6 +118,25 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
104118
node.ancestors_with_macros(self.db).map(|it| it.value)
105119
}
106120

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+
107140
pub fn type_of_expr(&self, expr: &ast::Expr) -> Option<Type> {
108141
self.analyze(expr.syntax()).type_of(self.db, &expr)
109142
}

crates/ra_hir_expand/src/db.rs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ pub trait AstDatabase: SourceDatabase {
7272
fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
7373
}
7474

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.
79+
pub 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+
7599
pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> {
76100
let map =
77101
db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it));
@@ -129,16 +153,43 @@ pub(crate) fn macro_arg(
129153
pub(crate) fn macro_expand(
130154
db: &dyn AstDatabase,
131155
id: MacroCallId,
156+
) -> Result<Arc<tt::Subtree>, String> {
157+
macro_expand_with_arg(db, id, None)
158+
}
159+
160+
fn 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+
173+
fn macro_expand_with_arg(
174+
db: &dyn AstDatabase,
175+
id: MacroCallId,
176+
arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
132177
) -> Result<Arc<tt::Subtree>, String> {
133178
let lazy_id = match id {
134179
MacroCallId::LazyMacro(id) => id,
135180
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+
}
137188
}
138189
};
139190

140191
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")?;
142193

143194
let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
144195
let tt = macro_rules.0.expand(db, lazy_id, &macro_arg.0).map_err(|err| format!("{:?}", err))?;
@@ -162,12 +213,24 @@ pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Optio
162213
pub(crate) fn parse_macro(
163214
db: &dyn AstDatabase,
164215
macro_file: MacroFile,
216+
) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
217+
parse_macro_with_arg(db, macro_file, None)
218+
}
219+
220+
pub fn parse_macro_with_arg(
221+
db: &dyn AstDatabase,
222+
macro_file: MacroFile,
223+
arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>,
165224
) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> {
166225
let _p = profile("parse_macro_query");
167226

168227
let macro_call_id = macro_file.macro_call_id;
169-
let tt = db
170-
.macro_expand(macro_call_id)
228+
let expansion = if let Some(arg) = arg {
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
171234
.map_err(|err| {
172235
// Note:
173236
// The final goal we would like to make all parse_macro success,

crates/ra_ide/src/completion/complete_dot.rs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
3838
fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) {
3939
for receiver in receiver.autoderef(ctx.db) {
4040
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)) {
4242
// Skip private field. FIXME: If the definition location of the
4343
// field is editable, we should show the completion
4444
continue;
@@ -53,7 +53,7 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Ty
5353
}
5454

5555
fn 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 {
5757
let mut seen_methods = FxHashSet::default();
5858
let traits_in_scope = ctx.scope().traits_in_scope();
5959
receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
@@ -620,4 +620,102 @@ mod tests {
620620
"###
621621
);
622622
}
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+
}
623721
}

crates/ra_ide/src/completion/complete_keyword.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
7979
}
8080

8181
fn is_in_loop_body(leaf: &SyntaxToken) -> bool {
82+
// FIXME move this to CompletionContext and make it handle macros
8283
for node in leaf.parent().ancestors() {
8384
if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
8485
break;

crates/ra_ide/src/completion/complete_path.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Completion of paths, including when writing a single name.
1+
//! Completion of paths, i.e. `some::prefix::<|>`.
22
33
use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
44
use ra_syntax::AstNode;
@@ -48,7 +48,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
4848
};
4949
// Iterate assoc types separately
5050
// FIXME: complete T::AssocType
51-
let krate = ctx.module.map(|m| m.krate());
51+
let krate = ctx.krate;
5252
if let Some(krate) = krate {
5353
let traits_in_scope = ctx.scope().traits_in_scope();
5454
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
@@ -934,4 +934,37 @@ mod tests {
934934
"###
935935
);
936936
}
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+
}
937970
}

crates/ra_ide/src/completion/complete_pattern.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,22 @@ mod tests {
8686
]
8787
"###);
8888
}
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+
}
89107
}

0 commit comments

Comments
 (0)