Unnamed repository; edit this file 'description' to name the repository.
Move attribute path completions into attribute completion module
Lukas Wirth 2022-02-03
parent 46b5089 · commit 6940cca
-rw-r--r--crates/hir/src/semantics.rs15
-rw-r--r--crates/hir_def/src/path.rs4
-rw-r--r--crates/hir_ty/src/infer.rs1
-rw-r--r--crates/ide_completion/src/completions/attribute.rs109
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs4
-rw-r--r--crates/ide_completion/src/completions/qualified_path.rs37
-rw-r--r--crates/ide_completion/src/completions/unqualified_path.rs11
-rw-r--r--crates/ide_completion/src/context.rs51
-rw-r--r--crates/ide_completion/src/lib.rs1
-rw-r--r--crates/ide_completion/src/tests/attribute.rs11
-rw-r--r--crates/syntax/src/ast/node_ext.rs2
11 files changed, 139 insertions, 107 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 9ac88e260c..243ba63b8a 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -949,12 +949,15 @@ impl<'db> SemanticsImpl<'db> {
})?;
match res {
- Either::Left(path) => resolve_hir_path(
- self.db,
- &self.scope(derive.syntax()).resolver,
- &Path::from_known_path(path, []),
- )
- .filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))),
+ Either::Left(path) => {
+ let len = path.len();
+ resolve_hir_path(
+ self.db,
+ &self.scope(derive.syntax()).resolver,
+ &Path::from_known_path(path, vec![None; len]),
+ )
+ .filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_))))
+ }
Either::Right(derive) => derive
.map(|call| MacroDef { id: self.db.lookup_intern_macro_call(call).def })
.map(PathResolution::Macro),
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index fc81b88db3..a6141174c8 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -92,7 +92,9 @@ impl Path {
path: ModPath,
generic_args: impl Into<Box<[Option<Interned<GenericArgs>>]>>,
) -> Path {
- Path { type_anchor: None, mod_path: Interned::new(path), generic_args: generic_args.into() }
+ let generic_args = generic_args.into();
+ assert_eq!(path.len(), generic_args.len());
+ Path { type_anchor: None, mod_path: Interned::new(path), generic_args }
}
pub fn kind(&self) -> &PathKind {
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index a6c91f9d27..173380654e 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -253,6 +253,7 @@ pub enum PointerCast {
/// Go from a mut raw pointer to a const raw pointer.
MutToConstPointer,
+ #[allow(dead_code)]
/// Go from `*const [T; N]` to `*const T`
ArrayToPointer,
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs
index 6b6759ebfd..8950b13d6f 100644
--- a/crates/ide_completion/src/completions/attribute.rs
+++ b/crates/ide_completion/src/completions/attribute.rs
@@ -16,62 +16,95 @@ use ide_db::{
use itertools::Itertools;
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
-use syntax::{algo::non_trivia_sibling, ast, AstNode, Direction, SyntaxKind, T};
+use syntax::{
+ ast::{self, AttrKind},
+ AstNode, SyntaxKind, T,
+};
-use crate::{context::CompletionContext, item::CompletionItem, Completions};
+use crate::{
+ completions::module_or_attr,
+ context::{CompletionContext, PathCompletionContext, PathKind},
+ item::CompletionItem,
+ Completions,
+};
mod cfg;
mod derive;
mod lint;
mod repr;
-pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
+/// Complete inputs to known builtin attributes as well as derive attributes
+pub(crate) fn complete_known_attribute_input(
+ acc: &mut Completions,
+ ctx: &CompletionContext,
+) -> Option<()> {
let attribute = ctx.fake_attribute_under_caret.as_ref()?;
let name_ref = match attribute.path() {
Some(p) => Some(p.as_single_name_ref()?),
None => None,
};
- match (name_ref, attribute.token_tree()) {
- (Some(path), Some(tt)) if tt.l_paren_token().is_some() => match path.text().as_str() {
- "repr" => repr::complete_repr(acc, ctx, tt),
- "derive" => derive::complete_derive(acc, ctx, ctx.attr.as_ref()?),
- "feature" => lint::complete_lint(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?, FEATURES),
- "allow" | "warn" | "deny" | "forbid" => {
- let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
+ let (path, tt) = name_ref.zip(attribute.token_tree())?;
+ if tt.l_paren_token().is_none() {
+ return None;
+ }
- let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
- .iter()
- .map(|g| &g.lint)
- .chain(DEFAULT_LINTS.iter())
- .chain(CLIPPY_LINTS.iter())
- .chain(RUSTDOC_LINTS)
- .cloned()
- .collect();
+ match path.text().as_str() {
+ "repr" => repr::complete_repr(acc, ctx, tt),
+ "derive" => derive::complete_derive(acc, ctx, ctx.attr.as_ref()?),
+ "feature" => lint::complete_lint(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?, FEATURES),
+ "allow" | "warn" | "deny" | "forbid" => {
+ let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
- lint::complete_lint(acc, ctx, &existing_lints, &lints);
- }
- "cfg" => {
- cfg::complete_cfg(acc, ctx);
- }
- _ => (),
- },
- (_, Some(_)) => (),
- (_, None) if attribute.expr().is_some() => (),
- (_, None) => complete_new_attribute(acc, ctx, attribute),
+ let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
+ .iter()
+ .map(|g| &g.lint)
+ .chain(DEFAULT_LINTS)
+ .chain(CLIPPY_LINTS)
+ .chain(RUSTDOC_LINTS)
+ .cloned()
+ .collect();
+
+ lint::complete_lint(acc, ctx, &existing_lints, &lints);
+ }
+ "cfg" => {
+ cfg::complete_cfg(acc, ctx);
+ }
+ _ => (),
}
Some(())
}
-// FIXME?: Move this functionality to (un)qualified_path, make this module work solely for builtin/known attributes for their inputs?
-fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
- let is_inner = attribute.kind() == ast::AttrKind::Inner;
- let attribute_annotated_item_kind =
- attribute.syntax().parent().map(|it| it.kind()).filter(|_| {
- is_inner
- // If we got nothing coming after the attribute it could be anything so filter it the kind out
- || non_trivia_sibling(attribute.syntax().clone().into(), Direction::Next).is_some()
- });
- let attributes = attribute_annotated_item_kind.and_then(|kind| {
+pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) {
+ let (is_trivial_path, qualifier, is_inner, annotated_item_kind) = match ctx.path_context {
+ Some(PathCompletionContext {
+ kind: Some(PathKind::Attr { kind, annotated_item_kind }),
+ is_trivial_path,
+ ref qualifier,
+ ..
+ }) => (is_trivial_path, qualifier, kind == AttrKind::Inner, annotated_item_kind),
+ _ => 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);
+ }
+ }
+ return;
+ }
+
+ ctx.process_all_names(&mut |name, def| {
+ if let Some(def) = module_or_attr(def) {
+ acc.add_resolution(ctx, name, def);
+ }
+ });
+
+ let attributes = annotated_item_kind.and_then(|kind| {
if ast::Expr::can_cast(kind) {
Some(EXPR_ATTRIBUTES)
} else {
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index 8ca4634be2..723abd62ae 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -171,8 +171,8 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
(PathKind::Type, ItemInNs::Types(_)) => true,
(PathKind::Type, ItemInNs::Values(_)) => false,
- (PathKind::Attr, ItemInNs::Macros(mac)) => mac.is_attr(),
- (PathKind::Attr, _) => false,
+ (PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(),
+ (PathKind::Attr { .. }, _) => false,
}
};
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs
index 2c6899ff0c..b15ec38b0a 100644
--- a/crates/ide_completion/src/completions/qualified_path.rs
+++ b/crates/ide_completion/src/completions/qualified_path.rs
@@ -7,7 +7,7 @@ use rustc_hash::FxHashSet;
use syntax::{ast, AstNode};
use crate::{
- completions::{module_or_attr, module_or_fn_macro},
+ completions::module_or_fn_macro,
context::{PathCompletionContext, PathKind},
patterns::ImmediateLocation,
CompletionContext, Completions,
@@ -17,7 +17,7 @@ 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, use_tree_parent, kind) = match ctx.path_context {
+ let ((path, resolution), use_tree_parent, kind) = match ctx.path_context {
// let ... else, syntax would come in really handy here right now
Some(PathCompletionContext {
qualifier: Some(ref qualifier),
@@ -47,17 +47,15 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
}
- let resolution = match ctx.sema.resolve_path(path) {
+ let resolution = match resolution {
Some(res) => res,
None => return,
};
- let context_module = ctx.module;
-
match ctx.completion_location {
Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => {
if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
- for (name, def) in module.scope(ctx.db, context_module) {
+ for (name, def) in module.scope(ctx.db, ctx.module) {
if let Some(def) = module_or_fn_macro(def) {
acc.add_resolution(ctx, name, def);
}
@@ -76,7 +74,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
let next_towards_current = current_module
.path_to_root(ctx.db)
.into_iter()
- .take_while(|&it| it != module)
+ .take_while(|it| it != module)
.next();
if let Some(next) = next_towards_current {
if let Some(name) = next.name(ctx.db) {
@@ -88,14 +86,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
return;
}
- Some(PathKind::Attr) => {
- if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
- for (name, def) in module.scope(ctx.db, context_module) {
- if let Some(def) = module_or_attr(def) {
- acc.add_resolution(ctx, name, def);
- }
- }
- }
+ Some(PathKind::Attr { .. }) => {
return;
}
Some(PathKind::Use) => {
@@ -127,7 +118,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
match resolution {
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
- let module_scope = module.scope(ctx.db, context_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 {
@@ -168,7 +159,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
| hir::ModuleDef::TypeAlias(_)
| hir::ModuleDef::BuiltinType(_)),
) => {
- if let hir::ModuleDef::Adt(hir::Adt::Enum(e)) = def {
+ if let &hir::ModuleDef::Adt(hir::Adt::Enum(e)) = def {
add_enum_variants(acc, ctx, e);
}
let ty = match def {
@@ -623,18 +614,6 @@ fn foo() {
}
#[test]
- fn dont_complete_attr() {
- check(
- r#"
-mod foo { pub struct Foo; }
-#[foo::$0]
-fn f() {}
-"#,
- expect![[""]],
- );
- }
-
- #[test]
fn completes_variant_through_self() {
check(
r#"
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs
index 7e06b074ce..01f3f777a1 100644
--- a/crates/ide_completion/src/completions/unqualified_path.rs
+++ b/crates/ide_completion/src/completions/unqualified_path.rs
@@ -4,7 +4,7 @@ use hir::ScopeDef;
use syntax::{ast, AstNode};
use crate::{
- completions::{module_or_attr, module_or_fn_macro},
+ completions::module_or_fn_macro,
context::{PathCompletionContext, PathKind},
patterns::ImmediateLocation,
CompletionContext, Completions,
@@ -36,14 +36,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
match kind {
Some(PathKind::Vis { .. }) => return,
- Some(PathKind::Attr) => {
- ctx.process_all_names(&mut |name, def| {
- if let Some(def) = module_or_attr(def) {
- acc.add_resolution(ctx, name, def);
- }
- });
- return;
- }
+ Some(PathKind::Attr { .. }) => return,
_ => (),
}
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index ab55d9cc04..4845427422 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -3,7 +3,9 @@
use std::iter;
use base_db::SourceDatabaseExt;
-use hir::{HasAttrs, Local, Name, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo};
+use hir::{
+ HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
+};
use ide_db::{
active_parameter::ActiveParameter,
base_db::{FilePosition, SourceDatabase},
@@ -11,8 +13,8 @@ use ide_db::{
RootDatabase,
};
use syntax::{
- algo::find_node_at_offset,
- ast::{self, HasName, NameOrNameRef},
+ algo::{find_node_at_offset, non_trivia_sibling},
+ ast::{self, AttrKind, HasName, NameOrNameRef},
match_ast, AstNode, NodeOrToken,
SyntaxKind::{self, *},
SyntaxNode, SyntaxToken, TextRange, TextSize, T,
@@ -44,7 +46,7 @@ pub(crate) enum Visible {
pub(super) enum PathKind {
Expr,
Type,
- Attr,
+ Attr { kind: AttrKind, annotated_item_kind: Option<SyntaxKind> },
Mac,
Pat,
Vis { has_in_token: bool },
@@ -58,7 +60,7 @@ pub(crate) struct PathCompletionContext {
/// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
pub(super) is_trivial_path: bool,
/// If not a trivial path, the prefix (qualifier).
- pub(super) qualifier: Option<ast::Path>,
+ pub(super) qualifier: Option<(ast::Path, Option<PathResolution>)>,
#[allow(dead_code)]
/// If not a trivial path, the suffix (parent).
pub(super) parent: Option<ast::Path>,
@@ -282,7 +284,7 @@ impl<'a> CompletionContext<'a> {
}
pub(crate) fn path_qual(&self) -> Option<&ast::Path> {
- self.path_context.as_ref().and_then(|it| it.qualifier.as_ref())
+ self.path_context.as_ref().and_then(|it| it.qualifier.as_ref().map(|(it, _)| it))
}
pub(crate) fn path_kind(&self) -> Option<PathKind> {
@@ -786,7 +788,7 @@ impl<'a> CompletionContext<'a> {
}
fn classify_name_ref(
- _sema: &Semantics<RootDatabase>,
+ sema: &Semantics<RootDatabase>,
original_file: &SyntaxNode,
name_ref: ast::NameRef,
) -> Option<(PathCompletionContext, Option<PatternContext>)> {
@@ -808,8 +810,9 @@ impl<'a> CompletionContext<'a> {
let mut pat_ctx = None;
path_ctx.in_loop_body = is_in_loop_body(name_ref.syntax());
- path_ctx.kind = path.syntax().ancestors().find_map(|it| {
- match_ast! {
+ path_ctx.kind = path.syntax().ancestors().find_map(|it| {
+ // using Option<Option<PathKind>> as extra controlflow
+ let kind = match_ast! {
match it {
ast::PathType(_) => Some(PathKind::Type),
ast::PathExpr(it) => {
@@ -830,21 +833,41 @@ impl<'a> CompletionContext<'a> {
Some(PathKind::Pat)
},
ast::MacroCall(it) => it.excl_token().and(Some(PathKind::Mac)),
- ast::Meta(_) => Some(PathKind::Attr),
+ ast::Meta(meta) => (|| {
+ let attr = meta.parent_attr()?;
+ let kind = attr.kind();
+ let attached = attr.syntax().parent()?;
+ let is_trailing_outer_attr = kind != AttrKind::Inner
+ && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none();
+ let annotated_item_kind = if is_trailing_outer_attr {
+ None
+ } else {
+ Some(attached.kind())
+ };
+ Some(PathKind::Attr {
+ kind,
+ annotated_item_kind,
+ })
+ })(),
ast::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }),
ast::UseTree(_) => Some(PathKind::Use),
- _ => None,
+ _ => return None,
}
- }
- });
+ };
+ Some(kind)
+ }).flatten();
path_ctx.has_type_args = segment.generic_arg_list().is_some();
if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
path_ctx.use_tree_parent = use_tree_parent;
- path_ctx.qualifier = path
+ let path = path
.segment()
.and_then(|it| find_node_in_file(original_file, &it))
.map(|it| it.parent_path());
+ path_ctx.qualifier = path.map(|path| {
+ let res = sema.resolve_path(&path);
+ (path, res)
+ });
return Some((path_ctx, pat_ctx));
}
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index a2217af493..5b49a49911 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -151,6 +151,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::fn_param::complete_fn_param(&mut acc, &ctx);
completions::keyword::complete_expr_keyword(&mut acc, &ctx);
diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs
index 2d2a1b867a..ac43a67bdc 100644
--- a/crates/ide_completion/src/tests/attribute.rs
+++ b/crates/ide_completion/src/tests/attribute.rs
@@ -17,6 +17,7 @@ fn proc_macros() {
struct Foo;
"#,
expect![[r#"
+ md proc_macros
at allow(…)
at cfg(…)
at cfg_attr(…)
@@ -35,7 +36,6 @@ struct Foo;
kw self
kw super
kw crate
- md proc_macros
"#]],
)
}
@@ -61,10 +61,7 @@ fn proc_macros_qualified() {
#[proc_macros::$0]
struct Foo;
"#,
- expect![[r#"
- at input_replace pub macro input_replace
- at identity pub macro identity
- "#]],
+ expect![[r#""#]],
)
}
@@ -302,6 +299,8 @@ fn attr_on_struct() {
struct Foo;
"#,
expect![[r#"
+ md core
+ at derive pub macro derive
at allow(…)
at cfg(…)
at cfg_attr(…)
@@ -320,8 +319,6 @@ struct Foo;
kw self
kw super
kw crate
- md core
- at derive pub macro derive
"#]],
);
}
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 7211c77e88..067e13ee14 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -119,7 +119,7 @@ impl From<ast::AssocItem> for ast::Item {
}
}
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AttrKind {
Inner,
Outer,