Skip to content

Commit 23a1280

Browse files
committed
Auto merge of rust-lang#16137 - unexge:complete-macros-in-macro-use, r=Veykril
Complete exported macros in `#[macro_use($0)]` Closes rust-lang/rust-analyzer#15657. Originally added a test case for incomplete input: ```rust #[test] fn completes_incomplete_syntax() { check( r#" //- /dep.rs crate:dep #[macro_export] macro_rules! foo { () => {}; } //- /main.rs crate:main deps:dep #[macro_use($0 extern crate dep; "#, expect![[r#" ma foo "#]], ) } ``` but couldn't make it pass and removed it 😅 Our current recovering logic doesn't work for token trees and for this code: ```rust #[macro_use( extern crate lazy_static; fn main() {} ``` we ended up with this syntax tree: ``` SOURCE_FILE@0..53 ATTR@0..52 POUND@0..1 "#" L_BRACK@1..2 "[" META@2..52 PATH@2..11 PATH_SEGMENT@2..11 NAME_REF@2..11 IDENT@2..11 "macro_use" TOKEN_TREE@11..52 L_PAREN@11..12 "(" WHITESPACE@12..13 "\n" EXTERN_KW@13..19 "extern" WHITESPACE@19..20 " " CRATE_KW@20..25 "crate" WHITESPACE@25..26 " " IDENT@26..37 "lazy_static" SEMICOLON@37..38 ";" WHITESPACE@38..40 "\n\n" FN_KW@40..42 "fn" WHITESPACE@42..43 " " IDENT@43..47 "main" TOKEN_TREE@47..49 L_PAREN@47..48 "(" R_PAREN@48..49 ")" WHITESPACE@49..50 " " TOKEN_TREE@50..52 L_CURLY@50..51 "{" R_CURLY@51..52 "}" WHITESPACE@52..53 "\n" ``` Maybe we can try to parse the token tree in `crates/ide-completion/src/context/analysis.rs` but I'm not sure what's the best way forward.
2 parents 20e09c6 + 3b8801c commit 23a1280

File tree

7 files changed

+194
-1
lines changed

7 files changed

+194
-1
lines changed

crates/ide-completion/src/completions/attribute.rs

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod cfg;
2626
mod derive;
2727
mod lint;
2828
mod repr;
29+
mod macro_use;
2930

3031
pub(crate) use self::derive::complete_derive_path;
3132

@@ -35,6 +36,7 @@ pub(crate) fn complete_known_attribute_input(
3536
ctx: &CompletionContext<'_>,
3637
&colon_prefix: &bool,
3738
fake_attribute_under_caret: &ast::Attr,
39+
extern_crate: Option<&ast::ExternCrate>,
3840
) -> Option<()> {
3941
let attribute = fake_attribute_under_caret;
4042
let name_ref = match attribute.path() {
@@ -66,6 +68,9 @@ pub(crate) fn complete_known_attribute_input(
6668
lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints);
6769
}
6870
"cfg" => cfg::complete_cfg(acc, ctx),
71+
"macro_use" => {
72+
macro_use::complete_macro_use(acc, ctx, extern_crate, &parse_tt_as_comma_sep_paths(tt)?)
73+
}
6974
_ => (),
7075
}
7176
Some(())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//! Completion for macros in `#[macro_use(...)]`
2+
use hir::ModuleDef;
3+
use ide_db::SymbolKind;
4+
use syntax::ast;
5+
6+
use crate::{context::CompletionContext, item::CompletionItem, Completions};
7+
8+
pub(super) fn complete_macro_use(
9+
acc: &mut Completions,
10+
ctx: &CompletionContext<'_>,
11+
extern_crate: Option<&ast::ExternCrate>,
12+
existing_imports: &[ast::Path],
13+
) {
14+
let Some(extern_crate) = extern_crate else { return };
15+
let Some(extern_crate) = ctx.sema.to_def(extern_crate) else { return };
16+
let Some(krate) = extern_crate.resolved_crate(ctx.db) else { return };
17+
18+
for mod_def in krate.root_module().declarations(ctx.db) {
19+
if let ModuleDef::Macro(mac) = mod_def {
20+
let mac_name = mac.name(ctx.db);
21+
let Some(mac_name) = mac_name.as_str() else { continue };
22+
23+
let existing_import = existing_imports
24+
.iter()
25+
.filter_map(|p| p.as_single_name_ref())
26+
.find(|n| n.text() == mac_name);
27+
if existing_import.is_some() {
28+
continue;
29+
}
30+
31+
let item = CompletionItem::new(SymbolKind::Macro, ctx.source_range(), mac_name);
32+
item.add_to(acc, ctx.db);
33+
}
34+
}
35+
}

crates/ide-completion/src/context.rs

+1
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ pub(super) enum CompletionAnalysis {
371371
UnexpandedAttrTT {
372372
colon_prefix: bool,
373373
fake_attribute_under_caret: Option<ast::Attr>,
374+
extern_crate: Option<ast::ExternCrate>,
374375
},
375376
}
376377

crates/ide-completion/src/context/analysis.rs

+2
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,13 @@ fn analyze(
254254
{
255255
let colon_prefix = previous_non_trivia_token(self_token.clone())
256256
.map_or(false, |it| T![:] == it.kind());
257+
257258
CompletionAnalysis::UnexpandedAttrTT {
258259
fake_attribute_under_caret: fake_ident_token
259260
.parent_ancestors()
260261
.find_map(ast::Attr::cast),
261262
colon_prefix,
263+
extern_crate: p.ancestors().find_map(ast::ExternCrate::cast),
262264
}
263265
} else {
264266
return None;

crates/ide-completion/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,14 @@ pub fn completions(
211211
CompletionAnalysis::UnexpandedAttrTT {
212212
colon_prefix,
213213
fake_attribute_under_caret: Some(attr),
214+
extern_crate,
214215
} => {
215216
completions::attribute::complete_known_attribute_input(
216217
acc,
217218
ctx,
218219
colon_prefix,
219220
attr,
221+
extern_crate.as_ref(),
220222
);
221223
}
222224
CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),

crates/ide-completion/src/tests/attribute.rs

+79
Original file line numberDiff line numberDiff line change
@@ -1067,3 +1067,82 @@ mod repr {
10671067
);
10681068
}
10691069
}
1070+
1071+
mod macro_use {
1072+
use super::*;
1073+
1074+
#[test]
1075+
fn completes_macros() {
1076+
check(
1077+
r#"
1078+
//- /dep.rs crate:dep
1079+
#[macro_export]
1080+
macro_rules! foo {
1081+
() => {};
1082+
}
1083+
1084+
#[macro_export]
1085+
macro_rules! bar {
1086+
() => {};
1087+
}
1088+
1089+
//- /main.rs crate:main deps:dep
1090+
#[macro_use($0)]
1091+
extern crate dep;
1092+
"#,
1093+
expect![[r#"
1094+
ma bar
1095+
ma foo
1096+
"#]],
1097+
)
1098+
}
1099+
1100+
#[test]
1101+
fn only_completes_exported_macros() {
1102+
check(
1103+
r#"
1104+
//- /dep.rs crate:dep
1105+
#[macro_export]
1106+
macro_rules! foo {
1107+
() => {};
1108+
}
1109+
1110+
macro_rules! bar {
1111+
() => {};
1112+
}
1113+
1114+
//- /main.rs crate:main deps:dep
1115+
#[macro_use($0)]
1116+
extern crate dep;
1117+
"#,
1118+
expect![[r#"
1119+
ma foo
1120+
"#]],
1121+
)
1122+
}
1123+
1124+
#[test]
1125+
fn does_not_completes_already_imported_macros() {
1126+
check(
1127+
r#"
1128+
//- /dep.rs crate:dep
1129+
#[macro_export]
1130+
macro_rules! foo {
1131+
() => {};
1132+
}
1133+
1134+
#[macro_export]
1135+
macro_rules! bar {
1136+
() => {};
1137+
}
1138+
1139+
//- /main.rs crate:main deps:dep
1140+
#[macro_use(foo, $0)]
1141+
extern crate dep;
1142+
"#,
1143+
expect![[r#"
1144+
ma bar
1145+
"#]],
1146+
)
1147+
}
1148+
}

crates/ide/src/goto_definition.rs

+70-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
55
RangeInfo, TryToNav,
66
};
7-
use hir::{AsAssocItem, AssocItem, DescendPreference, Semantics};
7+
use hir::{AsAssocItem, AssocItem, DescendPreference, ModuleDef, Semantics};
88
use ide_db::{
99
base_db::{AnchoredPath, FileId, FileLoader},
1010
defs::{Definition, IdentClass},
@@ -73,10 +73,15 @@ pub(crate) fn goto_definition(
7373
.into_iter()
7474
.filter_map(|token| {
7575
let parent = token.parent()?;
76+
7677
if let Some(tt) = ast::TokenTree::cast(parent.clone()) {
7778
if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), file_id) {
7879
return Some(vec![x]);
7980
}
81+
82+
if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token.clone()) {
83+
return Some(vec![x]);
84+
}
8085
}
8186
Some(
8287
IdentClass::classify_node(sema, &parent)?
@@ -140,6 +145,27 @@ fn try_lookup_include_path(
140145
})
141146
}
142147

148+
fn try_lookup_macro_def_in_macro_use(
149+
sema: &Semantics<'_, RootDatabase>,
150+
token: SyntaxToken,
151+
) -> Option<NavigationTarget> {
152+
let extern_crate = token.parent()?.ancestors().find_map(ast::ExternCrate::cast)?;
153+
let extern_crate = sema.to_def(&extern_crate)?;
154+
let krate = extern_crate.resolved_crate(sema.db)?;
155+
156+
for mod_def in krate.root_module().declarations(sema.db) {
157+
if let ModuleDef::Macro(mac) = mod_def {
158+
if mac.name(sema.db).as_str() == Some(token.text()) {
159+
if let Some(nav) = mac.try_to_nav(sema.db) {
160+
return Some(nav.call_site);
161+
}
162+
}
163+
}
164+
}
165+
166+
None
167+
}
168+
143169
/// finds the trait definition of an impl'd item, except function
144170
/// e.g.
145171
/// ```rust
@@ -2081,4 +2107,47 @@ fn test() {
20812107
"#,
20822108
);
20832109
}
2110+
2111+
#[test]
2112+
fn goto_macro_def_from_macro_use() {
2113+
check(
2114+
r#"
2115+
//- /main.rs crate:main deps:mac
2116+
#[macro_use(foo$0)]
2117+
extern crate mac;
2118+
2119+
//- /mac.rs crate:mac
2120+
#[macro_export]
2121+
macro_rules! foo {
2122+
//^^^
2123+
() => {};
2124+
}
2125+
"#,
2126+
);
2127+
2128+
check(
2129+
r#"
2130+
//- /main.rs crate:main deps:mac
2131+
#[macro_use(foo, bar$0, baz)]
2132+
extern crate mac;
2133+
2134+
//- /mac.rs crate:mac
2135+
#[macro_export]
2136+
macro_rules! foo {
2137+
() => {};
2138+
}
2139+
2140+
#[macro_export]
2141+
macro_rules! bar {
2142+
//^^^
2143+
() => {};
2144+
}
2145+
2146+
#[macro_export]
2147+
macro_rules! baz {
2148+
() => {};
2149+
}
2150+
"#,
2151+
);
2152+
}
20842153
}

0 commit comments

Comments
 (0)