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:
bors 2023-12-22 10:42:38 +00:00
commit 23a1280106
7 changed files with 194 additions and 1 deletions

View File

@ -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(())

View 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);
}
}
}

View File

@ -371,6 +371,7 @@ pub(super) enum CompletionAnalysis {
UnexpandedAttrTT {
colon_prefix: bool,
fake_attribute_under_caret: Option<ast::Attr>,
extern_crate: Option<ast::ExternCrate>,
},
}

View File

@ -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;

View File

@ -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 { .. } => (),

View File

@ -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
"#]],
)
}
}

View File

@ -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 {
() => {};
}
"#,
);
}
}