Auto merge of #16137 - unexge:complete-macros-in-macro-use, r=Veykril
Complete exported macros in `#[macro_use($0)]` Closes https://github.com/rust-lang/rust-analyzer/issues/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.
This commit is contained in:
commit
23a1280106
@ -26,6 +26,7 @@ mod cfg;
|
||||
mod derive;
|
||||
mod lint;
|
||||
mod repr;
|
||||
mod macro_use;
|
||||
|
||||
pub(crate) use self::derive::complete_derive_path;
|
||||
|
||||
@ -35,6 +36,7 @@ pub(crate) fn complete_known_attribute_input(
|
||||
ctx: &CompletionContext<'_>,
|
||||
&colon_prefix: &bool,
|
||||
fake_attribute_under_caret: &ast::Attr,
|
||||
extern_crate: Option<&ast::ExternCrate>,
|
||||
) -> Option<()> {
|
||||
let attribute = fake_attribute_under_caret;
|
||||
let name_ref = match attribute.path() {
|
||||
@ -66,6 +68,9 @@ pub(crate) fn complete_known_attribute_input(
|
||||
lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints);
|
||||
}
|
||||
"cfg" => cfg::complete_cfg(acc, ctx),
|
||||
"macro_use" => {
|
||||
macro_use::complete_macro_use(acc, ctx, extern_crate, &parse_tt_as_comma_sep_paths(tt)?)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Some(())
|
||||
|
35
crates/ide-completion/src/completions/attribute/macro_use.rs
Normal file
35
crates/ide-completion/src/completions/attribute/macro_use.rs
Normal file
@ -0,0 +1,35 @@
|
||||
//! Completion for macros in `#[macro_use(...)]`
|
||||
use hir::ModuleDef;
|
||||
use ide_db::SymbolKind;
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{context::CompletionContext, item::CompletionItem, Completions};
|
||||
|
||||
pub(super) fn complete_macro_use(
|
||||
acc: &mut Completions,
|
||||
ctx: &CompletionContext<'_>,
|
||||
extern_crate: Option<&ast::ExternCrate>,
|
||||
existing_imports: &[ast::Path],
|
||||
) {
|
||||
let Some(extern_crate) = extern_crate else { return };
|
||||
let Some(extern_crate) = ctx.sema.to_def(extern_crate) else { return };
|
||||
let Some(krate) = extern_crate.resolved_crate(ctx.db) else { return };
|
||||
|
||||
for mod_def in krate.root_module().declarations(ctx.db) {
|
||||
if let ModuleDef::Macro(mac) = mod_def {
|
||||
let mac_name = mac.name(ctx.db);
|
||||
let Some(mac_name) = mac_name.as_str() else { continue };
|
||||
|
||||
let existing_import = existing_imports
|
||||
.iter()
|
||||
.filter_map(|p| p.as_single_name_ref())
|
||||
.find(|n| n.text() == mac_name);
|
||||
if existing_import.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let item = CompletionItem::new(SymbolKind::Macro, ctx.source_range(), mac_name);
|
||||
item.add_to(acc, ctx.db);
|
||||
}
|
||||
}
|
||||
}
|
@ -371,6 +371,7 @@ pub(super) enum CompletionAnalysis {
|
||||
UnexpandedAttrTT {
|
||||
colon_prefix: bool,
|
||||
fake_attribute_under_caret: Option<ast::Attr>,
|
||||
extern_crate: Option<ast::ExternCrate>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -254,11 +254,13 @@ fn analyze(
|
||||
{
|
||||
let colon_prefix = previous_non_trivia_token(self_token.clone())
|
||||
.map_or(false, |it| T![:] == it.kind());
|
||||
|
||||
CompletionAnalysis::UnexpandedAttrTT {
|
||||
fake_attribute_under_caret: fake_ident_token
|
||||
.parent_ancestors()
|
||||
.find_map(ast::Attr::cast),
|
||||
colon_prefix,
|
||||
extern_crate: p.ancestors().find_map(ast::ExternCrate::cast),
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
|
@ -211,12 +211,14 @@ pub fn completions(
|
||||
CompletionAnalysis::UnexpandedAttrTT {
|
||||
colon_prefix,
|
||||
fake_attribute_under_caret: Some(attr),
|
||||
extern_crate,
|
||||
} => {
|
||||
completions::attribute::complete_known_attribute_input(
|
||||
acc,
|
||||
ctx,
|
||||
colon_prefix,
|
||||
attr,
|
||||
extern_crate.as_ref(),
|
||||
);
|
||||
}
|
||||
CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),
|
||||
|
@ -1067,3 +1067,82 @@ mod repr {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod macro_use {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn completes_macros() {
|
||||
check(
|
||||
r#"
|
||||
//- /dep.rs crate:dep
|
||||
#[macro_export]
|
||||
macro_rules! foo {
|
||||
() => {};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bar {
|
||||
() => {};
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
#[macro_use($0)]
|
||||
extern crate dep;
|
||||
"#,
|
||||
expect![[r#"
|
||||
ma bar
|
||||
ma foo
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_completes_exported_macros() {
|
||||
check(
|
||||
r#"
|
||||
//- /dep.rs crate:dep
|
||||
#[macro_export]
|
||||
macro_rules! foo {
|
||||
() => {};
|
||||
}
|
||||
|
||||
macro_rules! bar {
|
||||
() => {};
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
#[macro_use($0)]
|
||||
extern crate dep;
|
||||
"#,
|
||||
expect![[r#"
|
||||
ma foo
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_completes_already_imported_macros() {
|
||||
check(
|
||||
r#"
|
||||
//- /dep.rs crate:dep
|
||||
#[macro_export]
|
||||
macro_rules! foo {
|
||||
() => {};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bar {
|
||||
() => {};
|
||||
}
|
||||
|
||||
//- /main.rs crate:main deps:dep
|
||||
#[macro_use(foo, $0)]
|
||||
extern crate dep;
|
||||
"#,
|
||||
expect![[r#"
|
||||
ma bar
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
|
||||
RangeInfo, TryToNav,
|
||||
};
|
||||
use hir::{AsAssocItem, AssocItem, DescendPreference, Semantics};
|
||||
use hir::{AsAssocItem, AssocItem, DescendPreference, ModuleDef, Semantics};
|
||||
use ide_db::{
|
||||
base_db::{AnchoredPath, FileId, FileLoader},
|
||||
defs::{Definition, IdentClass},
|
||||
@ -73,10 +73,15 @@ pub(crate) fn goto_definition(
|
||||
.into_iter()
|
||||
.filter_map(|token| {
|
||||
let parent = token.parent()?;
|
||||
|
||||
if let Some(tt) = ast::TokenTree::cast(parent.clone()) {
|
||||
if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), file_id) {
|
||||
return Some(vec![x]);
|
||||
}
|
||||
|
||||
if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token.clone()) {
|
||||
return Some(vec![x]);
|
||||
}
|
||||
}
|
||||
Some(
|
||||
IdentClass::classify_node(sema, &parent)?
|
||||
@ -140,6 +145,27 @@ fn try_lookup_include_path(
|
||||
})
|
||||
}
|
||||
|
||||
fn try_lookup_macro_def_in_macro_use(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
token: SyntaxToken,
|
||||
) -> Option<NavigationTarget> {
|
||||
let extern_crate = token.parent()?.ancestors().find_map(ast::ExternCrate::cast)?;
|
||||
let extern_crate = sema.to_def(&extern_crate)?;
|
||||
let krate = extern_crate.resolved_crate(sema.db)?;
|
||||
|
||||
for mod_def in krate.root_module().declarations(sema.db) {
|
||||
if let ModuleDef::Macro(mac) = mod_def {
|
||||
if mac.name(sema.db).as_str() == Some(token.text()) {
|
||||
if let Some(nav) = mac.try_to_nav(sema.db) {
|
||||
return Some(nav.call_site);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// finds the trait definition of an impl'd item, except function
|
||||
/// e.g.
|
||||
/// ```rust
|
||||
@ -2081,4 +2107,47 @@ fn test() {
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_macro_def_from_macro_use() {
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:mac
|
||||
#[macro_use(foo$0)]
|
||||
extern crate mac;
|
||||
|
||||
//- /mac.rs crate:mac
|
||||
#[macro_export]
|
||||
macro_rules! foo {
|
||||
//^^^
|
||||
() => {};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:mac
|
||||
#[macro_use(foo, bar$0, baz)]
|
||||
extern crate mac;
|
||||
|
||||
//- /mac.rs crate:mac
|
||||
#[macro_export]
|
||||
macro_rules! foo {
|
||||
() => {};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bar {
|
||||
//^^^
|
||||
() => {};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! baz {
|
||||
() => {};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user