Move attribute path completions into attribute completion module

This commit is contained in:
Lukas Wirth 2022-02-02 13:35:46 +01:00
parent 46b5089bfa
commit 6940cca760
11 changed files with 139 additions and 107 deletions

View File

@ -949,12 +949,15 @@ fn resolve_derive_ident(
})?;
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),

View File

@ -92,7 +92,9 @@ pub fn from_known_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 {

View File

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

View File

@ -16,62 +16,95 @@
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 {

View File

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

View File

@ -7,7 +7,7 @@
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 {
@ -622,18 +613,6 @@ fn new() fn() -> HashMap<K, V, RandomState>
);
}
#[test]
fn dont_complete_attr() {
check(
r#"
mod foo { pub struct Foo; }
#[foo::$0]
fn f() {}
"#,
expect![[""]],
);
}
#[test]
fn completes_variant_through_self() {
check(

View File

@ -4,7 +4,7 @@
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,
_ => (),
}

View File

@ -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 @@
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 @@ pub(crate) fn is_non_trivial_path(&self) -> bool {
}
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 @@ fn classify_name(
}
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 @@ fn classify_name_ref(
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 @@ fn classify_name_ref(
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));
}

View File

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

View File

@ -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 @@ fn proc_macros() {
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 @@ fn attr_on_struct() {
kw self
kw super
kw crate
md core
at derive pub macro derive
"#]],
);
}

View File

@ -119,7 +119,7 @@ fn from(assoc: ast::AssocItem) -> Self {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AttrKind {
Inner,
Outer,