about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-08-21 07:38:29 +0000
committerbors <bors@rust-lang.org>2023-08-21 07:38:29 +0000
commita3892f0ed9b169351495b0ac871a63bdc5b61317 (patch)
tree1c4c421f99286ff618e76eab6bf2bf99a6fd3d62
parent83b3ba1b8191c065bb0dacc79e5896f7f21e4611 (diff)
parent37e0e8af10514a2064915389f22743fb690e3f93 (diff)
downloadrust-a3892f0ed9b169351495b0ac871a63bdc5b61317.tar.gz
rust-a3892f0ed9b169351495b0ac871a63bdc5b61317.zip
Auto merge of #15374 - jmintb:extern_crate, r=Veykril
feat: Implement extern crate completion

Hi, this is a draft PR for #13002.

I have basic completion working as well as a filter for existing extern crate imports in the same file. This is based on the tests, I have not actually tried this in an editor. Before going further I think this is a good point to stop and get feedback on the
structure and approach I have taken so far. Let me know what you think :)

I will make sure to add more tests, rebase commits and align with the code style guidelines before submitting a final version.

A few specific questions :
1. Is there a better way to check for matching suggestions? right now I just test if an extern crate name starts with the current
user input.
2. Am I creating the `CompletionItem` correctly? I noticed that `use_.rs` invokes a builder where as I do not.
3. When checking for existing extern crate imports the current implementation only looks at the current source file, is that sufficient?
-rw-r--r--crates/hir-def/src/resolver.rs19
-rw-r--r--crates/hir/src/semantics.rs8
-rw-r--r--crates/ide-completion/src/completions.rs2
-rw-r--r--crates/ide-completion/src/completions/extern_crate.rs71
-rw-r--r--crates/ide-completion/src/context.rs1
-rw-r--r--crates/ide-completion/src/context/analysis.rs4
6 files changed, 105 insertions, 0 deletions
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index 62576100ea4..2f9187009e2 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -12,6 +12,7 @@ use triomphe::Arc;
 use crate::{
     body::scope::{ExprScopes, ScopeId},
     builtin_type::BuiltinType,
+    data::ExternCrateDeclData,
     db::DefDatabase,
     generics::{GenericParams, TypeOrConstParamData},
     hir::{BindingId, ExprId, LabelId},
@@ -451,6 +452,7 @@ impl Resolver {
         def_map[module_id].scope.entries().for_each(|(name, def)| {
             res.add_per_ns(name, def);
         });
+
         def_map[module_id].scope.legacy_macros().for_each(|(name, macs)| {
             macs.iter().for_each(|&mac| {
                 res.add(name, ScopeDef::ModuleDef(ModuleDefId::MacroId(mac)));
@@ -474,6 +476,23 @@ impl Resolver {
         res.map
     }
 
+    pub fn extern_crate_decls_in_scope<'a>(
+        &'a self,
+        db: &'a dyn DefDatabase,
+    ) -> impl Iterator<Item = Name> + 'a {
+        self.module_scope.def_map[self.module_scope.module_id]
+            .scope
+            .extern_crate_decls()
+            .map(|id| ExternCrateDeclData::extern_crate_decl_data_query(db, id).name.clone())
+    }
+
+    pub fn extern_crates_in_scope<'a>(&'a self) -> impl Iterator<Item = (Name, ModuleId)> + 'a {
+        self.module_scope
+            .def_map
+            .extern_prelude()
+            .map(|(name, module_id)| (name.clone(), module_id.0.into()))
+    }
+
     pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> {
         // FIXME(trait_alias): Trait alias brings aliased traits in scope! Note that supertraits of
         // aliased traits are NOT brought in scope (unless also aliased).
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 65ccb8b0733..b8d4ecd4414 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1722,6 +1722,14 @@ impl SemanticsScope<'_> {
             |name, id| cb(name, id.into()),
         )
     }
+
+    pub fn extern_crates(&self) -> impl Iterator<Item = (Name, Module)> + '_ {
+        self.resolver.extern_crates_in_scope().map(|(name, id)| (name, Module { id }))
+    }
+
+    pub fn extern_crate_decls(&self) -> impl Iterator<Item = Name> + '_ {
+        self.resolver.extern_crate_decls_in_scope(self.db.upcast())
+    }
 }
 
 #[derive(Debug)]
diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs
index 7e2ecdbb858..f60ac150164 100644
--- a/crates/ide-completion/src/completions.rs
+++ b/crates/ide-completion/src/completions.rs
@@ -20,6 +20,7 @@ pub(crate) mod r#type;
 pub(crate) mod use_;
 pub(crate) mod vis;
 pub(crate) mod env_vars;
+pub(crate) mod extern_crate;
 
 use std::iter;
 
@@ -739,6 +740,7 @@ pub(super) fn complete_name_ref(
                 }
             }
         }
+        NameRefKind::ExternCrate => extern_crate::complete_extern_crate(acc, ctx),
         NameRefKind::DotAccess(dot_access) => {
             flyimport::import_on_the_fly_dot(acc, ctx, dot_access);
             dot::complete_dot(acc, ctx, dot_access);
diff --git a/crates/ide-completion/src/completions/extern_crate.rs b/crates/ide-completion/src/completions/extern_crate.rs
new file mode 100644
index 00000000000..0d0e143f5f6
--- /dev/null
+++ b/crates/ide-completion/src/completions/extern_crate.rs
@@ -0,0 +1,71 @@
+//! Completion for extern crates
+
+use hir::{HasAttrs, Name};
+use ide_db::SymbolKind;
+
+use crate::{context::CompletionContext, CompletionItem, CompletionItemKind};
+
+use super::Completions;
+
+pub(crate) fn complete_extern_crate(acc: &mut Completions, ctx: &CompletionContext<'_>) {
+    let imported_extern_crates: Vec<Name> = ctx.scope.extern_crate_decls().collect();
+
+    for (name, module) in ctx.scope.extern_crates() {
+        if imported_extern_crates.contains(&name) {
+            continue;
+        }
+
+        let mut item = CompletionItem::new(
+            CompletionItemKind::SymbolKind(SymbolKind::Module),
+            ctx.source_range(),
+            name.to_smol_str(),
+        );
+        item.set_documentation(module.docs(ctx.db));
+
+        item.add_to(acc, ctx.db);
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::tests::completion_list_no_kw;
+
+    #[test]
+    fn can_complete_extern_crate() {
+        let case = r#"
+//- /lib.rs crate:other_crate_a
+// nothing here
+//- /other_crate_b.rs crate:other_crate_b
+pub mod good_mod{}
+//- /lib.rs crate:crate_c
+// nothing here
+//- /lib.rs crate:lib deps:other_crate_a,other_crate_b,crate_c extern-prelude:other_crate_a
+extern crate oth$0
+mod other_mod {}
+"#;
+
+        let completion_list = completion_list_no_kw(case);
+
+        assert_eq!("md other_crate_a\n".to_string(), completion_list);
+    }
+
+    #[test]
+    fn will_not_complete_existing_import() {
+        let case = r#"
+//- /lib.rs crate:other_crate_a
+// nothing here
+//- /lib.rs crate:crate_c
+// nothing here
+//- /lib.rs crate:other_crate_b
+//
+//- /lib.rs crate:lib deps:other_crate_a,other_crate_b,crate_c extern-prelude:other_crate_a,other_crate_b
+extern crate other_crate_b;
+extern crate oth$0
+mod other_mod {}
+"#;
+
+        let completion_list = completion_list_no_kw(case);
+
+        assert_eq!("md other_crate_a\n".to_string(), completion_list);
+    }
+}
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 1fd635ba2e7..0da7ba6d000 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -351,6 +351,7 @@ pub(super) enum NameRefKind {
         expr: ast::RecordExpr,
     },
     Pattern(PatternContext),
+    ExternCrate,
 }
 
 /// The identifier we are currently completing.
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index c66cb987fea..1e6b2f319aa 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -624,6 +624,10 @@ fn classify_name_ref(
                 });
                 return Some(make_res(kind));
             },
+            ast::ExternCrate(_) => {
+                let kind = NameRefKind::ExternCrate;
+                return Some(make_res(kind));
+            },
             ast::MethodCallExpr(method) => {
                 let receiver = find_opt_node_in_file(original_file, method.receiver());
                 let kind = NameRefKind::DotAccess(DotAccess {