Add completion module tailored towards use trees

This commit is contained in:
Lukas Wirth 2022-02-02 15:03:46 +01:00
parent 6940cca760
commit 661d721e20
12 changed files with 270 additions and 165 deletions

View file

@ -481,7 +481,7 @@ impl HirDisplay for Module {
// FIXME: Module doesn't have visibility saved in data.
match self.name(f.db) {
Some(name) => write!(f, "mod {}", name),
None if self.crate_root(f.db) == *self => match self.krate().display_name(f.db) {
None if self.is_crate_root(f.db) => match self.krate().display_name(f.db) {
Some(name) => write!(f, "extern crate {}", name),
None => write!(f, "extern crate {{unknown}}"),
},

View file

@ -452,6 +452,11 @@ impl Module {
Module { id: def_map.module_id(def_map.root()) }
}
pub fn is_crate_root(self, db: &dyn HirDatabase) -> bool {
let def_map = db.crate_def_map(self.id.krate());
def_map.root() == self.id.local_id
}
/// Iterates over all child modules.
pub fn children(self, db: &dyn HirDatabase) -> impl Iterator<Item = Module> {
let def_map = self.id.def_map(db.upcast());

View file

@ -4,9 +4,11 @@ pub(crate) mod attribute;
pub(crate) mod dot;
pub(crate) mod flyimport;
pub(crate) mod fn_param;
pub(crate) mod format_string;
pub(crate) mod keyword;
pub(crate) mod lifetime;
pub(crate) mod mod_;
pub(crate) mod use_;
pub(crate) mod pattern;
pub(crate) mod postfix;
pub(crate) mod qualified_path;
@ -14,7 +16,6 @@ pub(crate) mod record;
pub(crate) mod snippet;
pub(crate) mod trait_impl;
pub(crate) mod unqualified_path;
pub(crate) mod format_string;
use std::iter;

View file

@ -1,9 +1,10 @@
//! Completion for attributes
//! Completion for (built-in) attributes, derives and lints.
//!
//! This module uses a bit of static metadata to provide completions
//! for built-in attributes.
//! Non-built-in attribute (excluding derives attributes) completions are done in [`super::unqualified_path`].
//! This module uses a bit of static metadata to provide completions for builtin-in attributes and lints.
use std::iter;
use hir::ScopeDef;
use ide_db::{
helpers::{
generated_lints::{
@ -85,24 +86,45 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
_ => return,
};
if !is_trivial_path {
return;
}
if let Some((_, Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))))) = qualifier {
for (name, def) in module.scope(ctx.db, ctx.module) {
if let Some(def) = module_or_attr(def) {
acc.add_resolution(ctx, name, def);
match qualifier {
Some((path, qualifier)) => {
let is_super_chain = iter::successors(Some(path.clone()), |p| p.qualifier())
.all(|p| p.segment().and_then(|s| s.super_token()).is_some());
if is_super_chain {
acc.add_keyword(ctx, "super::");
}
}
return;
}
ctx.process_all_names(&mut |name, def| {
if let Some(def) = module_or_attr(def) {
acc.add_resolution(ctx, name, def);
let module = match qualifier {
Some(hir::PathResolution::Def(hir::ModuleDef::Module(it))) => it,
_ => return,
};
for (name, def) in module.scope(ctx.db, ctx.module) {
if let Some(def) = module_or_attr(def) {
acc.add_resolution(ctx, name, def);
}
}
return;
}
});
// fresh use tree with leading colon2, only show crate roots
None if !is_trivial_path => {
ctx.process_all_names(&mut |name, res| match res {
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
acc.add_resolution(ctx, name, res);
}
_ => (),
});
}
// only show modules in a fresh UseTree
None => {
ctx.process_all_names(&mut |name, def| {
if let Some(def) = module_or_attr(def) {
acc.add_resolution(ctx, name, def);
}
});
["self::", "super::", "crate::"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
}
}
let attributes = annotated_item_kind.and_then(|kind| {
if ast::Expr::can_cast(kind) {

View file

@ -1,10 +1,8 @@
//! Completion of paths, i.e. `some::prefix::$0`.
use std::iter;
use hir::{ScopeDef, Trait};
use rustc_hash::FxHashSet;
use syntax::{ast, AstNode};
use syntax::ast;
use crate::{
completions::module_or_fn_macro,
@ -17,14 +15,11 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
if ctx.is_path_disallowed() || ctx.has_impl_or_trait_prev_sibling() {
return;
}
let ((path, resolution), use_tree_parent, kind) = match ctx.path_context {
let ((path, resolution), kind) = match ctx.path_context {
// let ... else, syntax would come in really handy here right now
Some(PathCompletionContext {
qualifier: Some(ref qualifier),
use_tree_parent,
kind,
..
}) => (qualifier, use_tree_parent, kind),
Some(PathCompletionContext { qualifier: Some(ref qualifier), kind, .. }) => {
(qualifier, kind)
}
_ => return,
};
@ -86,52 +81,23 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
return;
}
Some(PathKind::Attr { .. }) => {
Some(PathKind::Attr { .. } | PathKind::Use) => {
return;
}
Some(PathKind::Use) => {
if iter::successors(Some(path.clone()), |p| p.qualifier())
.all(|p| p.segment().and_then(|s| s.super_token()).is_some())
{
acc.add_keyword(ctx, "super::");
}
// only show `self` in a new use-tree when the qualifier doesn't end in self
if use_tree_parent
&& !matches!(
path.segment().and_then(|it| it.kind()),
Some(ast::PathSegmentKind::SelfKw)
)
{
acc.add_keyword(ctx, "self");
}
Some(PathKind::Pat) => (),
_ => {
// Add associated types on type parameters and `Self`.
ctx.scope.assoc_type_shorthand_candidates(&resolution, |_, alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
}
_ => (),
}
if !matches!(kind, Some(PathKind::Pat)) {
// Add associated types on type parameters and `Self`.
ctx.scope.assoc_type_shorthand_candidates(&resolution, |_, alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
}
match resolution {
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
let module_scope = module.scope(ctx.db, ctx.module);
for (name, def) in module_scope {
if let Some(PathKind::Use) = kind {
if let ScopeDef::Unknown = def {
if let Some(ast::NameLike::NameRef(name_ref)) = ctx.name_syntax.as_ref() {
if name_ref.syntax().text() == name.to_smol_str().as_str() {
// for `use self::foo$0`, don't suggest `foo` as a completion
cov_mark::hit!(dont_complete_current_use);
continue;
}
}
}
}
let add_resolution = match def {
// Don't suggest attribute macros and derives.
ScopeDef::MacroDef(mac) => mac.is_fn_like(),

View file

@ -20,25 +20,11 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
_ => return,
};
if let Some(PathKind::Use) = kind {
// only show modules in a fresh UseTree
cov_mark::hit!(unqualified_path_only_modules_in_import);
ctx.process_all_names(&mut |name, res| {
if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res {
acc.add_resolution(ctx, name, res);
}
});
["self::", "super::", "crate::"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
return;
}
["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
match kind {
Some(PathKind::Vis { .. }) => return,
Some(PathKind::Attr { .. }) => return,
Some(PathKind::Vis { .. } | PathKind::Attr { .. } | PathKind::Use { .. }) => return,
_ => (),
}
["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
match &ctx.completion_location {
Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => {

View file

@ -0,0 +1,109 @@
//! Completion for use trees
//!
//! This module uses a bit of static metadata to provide completions
//! for built-in attributes.
//! Non-built-in attribute (excluding derives attributes) completions are done in [`super::unqualified_path`].
use std::iter;
use hir::ScopeDef;
use syntax::{ast, AstNode};
use crate::{
context::{CompletionContext, PathCompletionContext, PathKind},
Completions,
};
pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) {
let (is_trivial_path, qualifier, use_tree_parent) = match ctx.path_context {
Some(PathCompletionContext {
kind: Some(PathKind::Use),
is_trivial_path,
use_tree_parent,
ref qualifier,
..
}) => (is_trivial_path, qualifier, use_tree_parent),
_ => return,
};
match qualifier {
Some((path, qualifier)) => {
let is_super_chain = iter::successors(Some(path.clone()), |p| p.qualifier())
.all(|p| p.segment().and_then(|s| s.super_token()).is_some());
if is_super_chain {
acc.add_keyword(ctx, "super::");
}
// only show `self` in a new use-tree when the qualifier doesn't end in self
let not_preceded_by_self = use_tree_parent
&& !matches!(
path.segment().and_then(|it| it.kind()),
Some(ast::PathSegmentKind::SelfKw)
);
if not_preceded_by_self {
acc.add_keyword(ctx, "self");
}
let qualifier = match qualifier {
Some(it) => it,
None => return,
};
match qualifier {
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
let module_scope = module.scope(ctx.db, ctx.module);
let unknown_is_current = |name: &hir::Name| {
matches!(
ctx.name_syntax.as_ref(),
Some(ast::NameLike::NameRef(name_ref))
if name_ref.syntax().text() == name.to_smol_str().as_str()
)
};
for (name, def) in module_scope {
let add_resolution = match def {
ScopeDef::Unknown if unknown_is_current(&name) => {
// for `use self::foo$0`, don't suggest `foo` as a completion
cov_mark::hit!(dont_complete_current_use);
continue;
}
ScopeDef::ModuleDef(_) | ScopeDef::MacroDef(_) | ScopeDef::Unknown => {
true
}
_ => false,
};
if add_resolution {
acc.add_resolution(ctx, name, def);
}
}
}
hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
cov_mark::hit!(enum_plain_qualified_use_tree);
e.variants(ctx.db)
.into_iter()
.for_each(|variant| acc.add_enum_variant(ctx, variant, None));
}
_ => {}
}
}
// fresh use tree with leading colon2, only show crate roots
None if !is_trivial_path => {
cov_mark::hit!(use_tree_crate_roots_only);
ctx.process_all_names(&mut |name, res| match res {
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
acc.add_resolution(ctx, name, res);
}
_ => (),
});
}
// only show modules in a fresh UseTree
None => {
cov_mark::hit!(unqualified_path_only_modules_in_import);
ctx.process_all_names(&mut |name, res| {
if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res {
acc.add_resolution(ctx, name, res);
}
});
["self::", "super::", "crate::"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
}
}
}

View file

@ -153,6 +153,7 @@ pub fn completions(
let mut acc = Completions::default();
completions::attribute::complete_known_attribute_input(&mut acc, &ctx);
completions::attribute::complete_attribute(&mut acc, &ctx);
completions::use_::complete_use_tree(&mut acc, &ctx);
completions::fn_param::complete_fn_param(&mut acc, &ctx);
completions::keyword::complete_expr_keyword(&mut acc, &ctx);
completions::snippet::complete_expr_snippet(&mut acc, &ctx);

View file

@ -18,6 +18,9 @@ struct Foo;
"#,
expect![[r#"
md proc_macros
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -33,9 +36,6 @@ struct Foo;
at derive()
at repr()
at non_exhaustive
kw self
kw super
kw crate
"#]],
)
}
@ -61,7 +61,10 @@ fn proc_macros_qualified() {
#[proc_macros::$0]
struct Foo;
"#,
expect![[r#""#]],
expect![[r#"
at input_replace pub macro input_replace
at identity pub macro identity
"#]],
)
}
@ -75,15 +78,15 @@ fn with_existing_attr() {
check(
r#"#[no_mangle] #[$0] mcall!();"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
at deny()
at forbid()
at warn()
kw self
kw super
kw crate
"#]],
)
}
@ -93,6 +96,9 @@ fn attr_on_source_file() {
check(
r#"#![$0]"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -113,9 +119,6 @@ fn attr_on_source_file() {
at recursion_limit = ""
at type_length_limit =
at windows_subsystem = ""
kw self
kw super
kw crate
"#]],
);
}
@ -125,6 +128,9 @@ fn attr_on_module() {
check(
r#"#[$0] mod foo;"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -139,14 +145,14 @@ fn attr_on_module() {
at no_mangle
at macro_use
at path = ""
kw self
kw super
kw crate
"#]],
);
check(
r#"mod foo {#![$0]}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -160,9 +166,6 @@ fn attr_on_module() {
at must_use
at no_mangle
at no_implicit_prelude
kw self
kw super
kw crate
"#]],
);
}
@ -172,6 +175,9 @@ fn attr_on_macro_rules() {
check(
r#"#[$0] macro_rules! foo {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -186,9 +192,6 @@ fn attr_on_macro_rules() {
at no_mangle
at macro_export
at macro_use
kw self
kw super
kw crate
"#]],
);
}
@ -198,6 +201,9 @@ fn attr_on_macro_def() {
check(
r#"#[$0] macro foo {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -210,9 +216,6 @@ fn attr_on_macro_def() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -222,6 +225,9 @@ fn attr_on_extern_crate() {
check(
r#"#[$0] extern crate foo;"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -235,9 +241,6 @@ fn attr_on_extern_crate() {
at must_use
at no_mangle
at macro_use
kw self
kw super
kw crate
"#]],
);
}
@ -247,6 +250,9 @@ fn attr_on_use() {
check(
r#"#[$0] use foo;"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -259,9 +265,6 @@ fn attr_on_use() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -271,6 +274,9 @@ fn attr_on_type_alias() {
check(
r#"#[$0] type foo = ();"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -283,9 +289,6 @@ fn attr_on_type_alias() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -301,6 +304,9 @@ struct Foo;
expect![[r#"
md core
at derive pub macro derive
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -316,9 +322,6 @@ struct Foo;
at derive()
at repr()
at non_exhaustive
kw self
kw super
kw crate
"#]],
);
}
@ -328,6 +331,9 @@ fn attr_on_enum() {
check(
r#"#[$0] enum Foo {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -343,9 +349,6 @@ fn attr_on_enum() {
at derive()
at repr()
at non_exhaustive
kw self
kw super
kw crate
"#]],
);
}
@ -355,6 +358,9 @@ fn attr_on_const() {
check(
r#"#[$0] const FOO: () = ();"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -367,9 +373,6 @@ fn attr_on_const() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -379,6 +382,9 @@ fn attr_on_static() {
check(
r#"#[$0] static FOO: () = ()"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -396,9 +402,6 @@ fn attr_on_static() {
at link_section = ""
at global_allocator
at used
kw self
kw super
kw crate
"#]],
);
}
@ -408,6 +411,9 @@ fn attr_on_trait() {
check(
r#"#[$0] trait Foo {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -421,9 +427,6 @@ fn attr_on_trait() {
at must_use
at no_mangle
at must_use
kw self
kw super
kw crate
"#]],
);
}
@ -433,6 +436,9 @@ fn attr_on_impl() {
check(
r#"#[$0] impl () {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -446,14 +452,14 @@ fn attr_on_impl() {
at must_use
at no_mangle
at automatically_derived
kw self
kw super
kw crate
"#]],
);
check(
r#"impl () {#![$0]}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -466,9 +472,6 @@ fn attr_on_impl() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -478,6 +481,9 @@ fn attr_on_extern_block() {
check(
r#"#[$0] extern {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -491,14 +497,14 @@ fn attr_on_extern_block() {
at must_use
at no_mangle
at link
kw self
kw super
kw crate
"#]],
);
check(
r#"extern {#![$0]}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -512,9 +518,6 @@ fn attr_on_extern_block() {
at must_use
at no_mangle
at link
kw self
kw super
kw crate
"#]],
);
}
@ -524,6 +527,9 @@ fn attr_on_variant() {
check(
r#"enum Foo { #[$0] Bar }"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -531,9 +537,6 @@ fn attr_on_variant() {
at forbid()
at warn()
at non_exhaustive
kw self
kw super
kw crate
"#]],
);
}
@ -543,6 +546,9 @@ fn attr_on_fn() {
check(
r#"#[$0] fn main() {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -570,9 +576,6 @@ fn attr_on_fn() {
at target_feature = ""
at test
at track_caller
kw self
kw super
kw crate
"#]],
);
}
@ -583,15 +586,15 @@ fn attr_on_expr() {
check(
r#"fn main() { #[$0] foo() }"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
at deny()
at forbid()
at warn()
kw self
kw super
kw crate
"#]],
);
}
@ -601,6 +604,9 @@ fn attr_in_source_file_end() {
check(
r#"#[$0]"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at automatically_derived
at cfg()
@ -637,9 +643,6 @@ fn attr_in_source_file_end() {
at track_caller
at used
at warn()
kw self
kw super
kw crate
"#]],
);
}

View file

@ -134,6 +134,25 @@ struct Bar;
);
}
#[test]
fn enum_plain_qualified_use_tree() {
cov_mark::check!(enum_plain_qualified_use_tree);
check(
r#"
use Foo::$0
enum Foo { Variant }
impl Foo {
const CONST: () = ()
fn func() {}
}
"#,
expect![[r#"
ev Variant ()
"#]],
);
}
#[test]
fn self_qualified_use_tree() {
check(

View file

@ -17,9 +17,6 @@ pub($0)
"#,
expect![[r#"
kw in
kw self
kw super
kw crate
"#]],
);
}
@ -30,11 +27,7 @@ fn after_in_kw() {
r#"
pub(in $0)
"#,
expect![[r#"
kw self
kw super
kw crate
"#]],
expect![[r#""#]],
);
}

View file

@ -223,7 +223,7 @@ impl Definition {
// def is crate root
// FIXME: We don't do searches for crates currently, as a crate does not actually have a single name
if let &Definition::Module(module) = self {
if module.crate_root(db) == module {
if module.is_crate_root(db) {
return SearchScope::reverse_dependencies(db, module.krate());
}
}
@ -378,7 +378,7 @@ impl<'a> FindUsages<'a> {
let name = match self.def {
// special case crate modules as these do not have a proper name
Definition::Module(module) if module.crate_root(self.sema.db) == module => {
Definition::Module(module) if module.is_crate_root(self.sema.db) => {
// FIXME: This assumes the crate name is always equal to its display name when it really isn't
module
.krate()
@ -460,7 +460,7 @@ impl<'a> FindUsages<'a> {
Definition::Module(module) => {
let scope = search_scope.intersection(&SearchScope::module(self.sema.db, module));
let is_crate_root = module.crate_root(self.sema.db) == module;
let is_crate_root = module.is_crate_root(self.sema.db);
for (text, file_id, search_range) in scope_files(sema, &scope) {
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());