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 { match res {
Either::Left(path) => resolve_hir_path( Either::Left(path) => {
self.db, let len = path.len();
&self.scope(derive.syntax()).resolver, resolve_hir_path(
&Path::from_known_path(path, []), self.db,
) &self.scope(derive.syntax()).resolver,
.filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))), &Path::from_known_path(path, vec![None; len]),
)
.filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_))))
}
Either::Right(derive) => derive Either::Right(derive) => derive
.map(|call| MacroDef { id: self.db.lookup_intern_macro_call(call).def }) .map(|call| MacroDef { id: self.db.lookup_intern_macro_call(call).def })
.map(PathResolution::Macro), .map(PathResolution::Macro),

View File

@ -92,7 +92,9 @@ pub fn from_known_path(
path: ModPath, path: ModPath,
generic_args: impl Into<Box<[Option<Interned<GenericArgs>>]>>, generic_args: impl Into<Box<[Option<Interned<GenericArgs>>]>>,
) -> Path { ) -> 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 { 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. /// Go from a mut raw pointer to a const raw pointer.
MutToConstPointer, MutToConstPointer,
#[allow(dead_code)]
/// Go from `*const [T; N]` to `*const T` /// Go from `*const [T; N]` to `*const T`
ArrayToPointer, ArrayToPointer,

View File

@ -16,62 +16,95 @@
use itertools::Itertools; use itertools::Itertools;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rustc_hash::FxHashMap; 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 cfg;
mod derive; mod derive;
mod lint; mod lint;
mod repr; 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 attribute = ctx.fake_attribute_under_caret.as_ref()?;
let name_ref = match attribute.path() { let name_ref = match attribute.path() {
Some(p) => Some(p.as_single_name_ref()?), Some(p) => Some(p.as_single_name_ref()?),
None => None, None => None,
}; };
match (name_ref, attribute.token_tree()) { let (path, tt) = name_ref.zip(attribute.token_tree())?;
(Some(path), Some(tt)) if tt.l_paren_token().is_some() => match path.text().as_str() { if tt.l_paren_token().is_none() {
"repr" => repr::complete_repr(acc, ctx, tt), return None;
"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 lints: Vec<Lint> = CLIPPY_LINT_GROUPS match path.text().as_str() {
.iter() "repr" => repr::complete_repr(acc, ctx, tt),
.map(|g| &g.lint) "derive" => derive::complete_derive(acc, ctx, ctx.attr.as_ref()?),
.chain(DEFAULT_LINTS.iter()) "feature" => lint::complete_lint(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?, FEATURES),
.chain(CLIPPY_LINTS.iter()) "allow" | "warn" | "deny" | "forbid" => {
.chain(RUSTDOC_LINTS) let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
.cloned()
.collect();
lint::complete_lint(acc, ctx, &existing_lints, &lints); let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
} .iter()
"cfg" => { .map(|g| &g.lint)
cfg::complete_cfg(acc, ctx); .chain(DEFAULT_LINTS)
} .chain(CLIPPY_LINTS)
_ => (), .chain(RUSTDOC_LINTS)
}, .cloned()
(_, Some(_)) => (), .collect();
(_, None) if attribute.expr().is_some() => (),
(_, None) => complete_new_attribute(acc, ctx, attribute), lint::complete_lint(acc, ctx, &existing_lints, &lints);
}
"cfg" => {
cfg::complete_cfg(acc, ctx);
}
_ => (),
} }
Some(()) Some(())
} }
// FIXME?: Move this functionality to (un)qualified_path, make this module work solely for builtin/known attributes for their inputs? pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) {
fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { let (is_trivial_path, qualifier, is_inner, annotated_item_kind) = match ctx.path_context {
let is_inner = attribute.kind() == ast::AttrKind::Inner; Some(PathCompletionContext {
let attribute_annotated_item_kind = kind: Some(PathKind::Attr { kind, annotated_item_kind }),
attribute.syntax().parent().map(|it| it.kind()).filter(|_| { is_trivial_path,
is_inner ref qualifier,
// 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() }) => (is_trivial_path, qualifier, kind == AttrKind::Inner, annotated_item_kind),
}); _ => return,
let attributes = attribute_annotated_item_kind.and_then(|kind| { };
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) { if ast::Expr::can_cast(kind) {
Some(EXPR_ATTRIBUTES) Some(EXPR_ATTRIBUTES)
} else { } 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::Types(_)) => true,
(PathKind::Type, ItemInNs::Values(_)) => false, (PathKind::Type, ItemInNs::Values(_)) => false,
(PathKind::Attr, ItemInNs::Macros(mac)) => mac.is_attr(), (PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(),
(PathKind::Attr, _) => false, (PathKind::Attr { .. }, _) => false,
} }
}; };

View File

@ -7,7 +7,7 @@
use syntax::{ast, AstNode}; use syntax::{ast, AstNode};
use crate::{ use crate::{
completions::{module_or_attr, module_or_fn_macro}, completions::module_or_fn_macro,
context::{PathCompletionContext, PathKind}, context::{PathCompletionContext, PathKind},
patterns::ImmediateLocation, patterns::ImmediateLocation,
CompletionContext, Completions, 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() { if ctx.is_path_disallowed() || ctx.has_impl_or_trait_prev_sibling() {
return; 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 // let ... else, syntax would come in really handy here right now
Some(PathCompletionContext { Some(PathCompletionContext {
qualifier: Some(ref qualifier), 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, Some(res) => res,
None => return, None => return,
}; };
let context_module = ctx.module;
match ctx.completion_location { match ctx.completion_location {
Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => { Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => {
if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { 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) { if let Some(def) = module_or_fn_macro(def) {
acc.add_resolution(ctx, name, 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 let next_towards_current = current_module
.path_to_root(ctx.db) .path_to_root(ctx.db)
.into_iter() .into_iter()
.take_while(|&it| it != module) .take_while(|it| it != module)
.next(); .next();
if let Some(next) = next_towards_current { if let Some(next) = next_towards_current {
if let Some(name) = next.name(ctx.db) { if let Some(name) = next.name(ctx.db) {
@ -88,14 +86,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
} }
return; return;
} }
Some(PathKind::Attr) => { 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);
}
}
}
return; return;
} }
Some(PathKind::Use) => { Some(PathKind::Use) => {
@ -127,7 +118,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
match resolution { match resolution {
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => { 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 { for (name, def) in module_scope {
if let Some(PathKind::Use) = kind { if let Some(PathKind::Use) = kind {
if let ScopeDef::Unknown = def { 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::TypeAlias(_)
| hir::ModuleDef::BuiltinType(_)), | 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); add_enum_variants(acc, ctx, e);
} }
let ty = match def { 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] #[test]
fn completes_variant_through_self() { fn completes_variant_through_self() {
check( check(

View File

@ -4,7 +4,7 @@
use syntax::{ast, AstNode}; use syntax::{ast, AstNode};
use crate::{ use crate::{
completions::{module_or_attr, module_or_fn_macro}, completions::module_or_fn_macro,
context::{PathCompletionContext, PathKind}, context::{PathCompletionContext, PathKind},
patterns::ImmediateLocation, patterns::ImmediateLocation,
CompletionContext, Completions, CompletionContext, Completions,
@ -36,14 +36,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
match kind { match kind {
Some(PathKind::Vis { .. }) => return, Some(PathKind::Vis { .. }) => return,
Some(PathKind::Attr) => { Some(PathKind::Attr { .. }) => return,
ctx.process_all_names(&mut |name, def| {
if let Some(def) = module_or_attr(def) {
acc.add_resolution(ctx, name, def);
}
});
return;
}
_ => (), _ => (),
} }

View File

@ -3,7 +3,9 @@
use std::iter; use std::iter;
use base_db::SourceDatabaseExt; 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::{ use ide_db::{
active_parameter::ActiveParameter, active_parameter::ActiveParameter,
base_db::{FilePosition, SourceDatabase}, base_db::{FilePosition, SourceDatabase},
@ -11,8 +13,8 @@
RootDatabase, RootDatabase,
}; };
use syntax::{ use syntax::{
algo::find_node_at_offset, algo::{find_node_at_offset, non_trivia_sibling},
ast::{self, HasName, NameOrNameRef}, ast::{self, AttrKind, HasName, NameOrNameRef},
match_ast, AstNode, NodeOrToken, match_ast, AstNode, NodeOrToken,
SyntaxKind::{self, *}, SyntaxKind::{self, *},
SyntaxNode, SyntaxToken, TextRange, TextSize, T, SyntaxNode, SyntaxToken, TextRange, TextSize, T,
@ -44,7 +46,7 @@ pub(crate) enum Visible {
pub(super) enum PathKind { pub(super) enum PathKind {
Expr, Expr,
Type, Type,
Attr, Attr { kind: AttrKind, annotated_item_kind: Option<SyntaxKind> },
Mac, Mac,
Pat, Pat,
Vis { has_in_token: bool }, 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. /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
pub(super) is_trivial_path: bool, pub(super) is_trivial_path: bool,
/// If not a trivial path, the prefix (qualifier). /// 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)] #[allow(dead_code)]
/// If not a trivial path, the suffix (parent). /// If not a trivial path, the suffix (parent).
pub(super) parent: Option<ast::Path>, 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> { 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> { pub(crate) fn path_kind(&self) -> Option<PathKind> {
@ -786,7 +788,7 @@ fn classify_name(
} }
fn classify_name_ref( fn classify_name_ref(
_sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
original_file: &SyntaxNode, original_file: &SyntaxNode,
name_ref: ast::NameRef, name_ref: ast::NameRef,
) -> Option<(PathCompletionContext, Option<PatternContext>)> { ) -> Option<(PathCompletionContext, Option<PatternContext>)> {
@ -808,8 +810,9 @@ fn classify_name_ref(
let mut pat_ctx = None; let mut pat_ctx = None;
path_ctx.in_loop_body = is_in_loop_body(name_ref.syntax()); path_ctx.in_loop_body = is_in_loop_body(name_ref.syntax());
path_ctx.kind = path.syntax().ancestors().find_map(|it| { path_ctx.kind = path.syntax().ancestors().find_map(|it| {
match_ast! { // using Option<Option<PathKind>> as extra controlflow
let kind = match_ast! {
match it { match it {
ast::PathType(_) => Some(PathKind::Type), ast::PathType(_) => Some(PathKind::Type),
ast::PathExpr(it) => { ast::PathExpr(it) => {
@ -830,21 +833,41 @@ fn classify_name_ref(
Some(PathKind::Pat) Some(PathKind::Pat)
}, },
ast::MacroCall(it) => it.excl_token().and(Some(PathKind::Mac)), 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::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }),
ast::UseTree(_) => Some(PathKind::Use), ast::UseTree(_) => Some(PathKind::Use),
_ => None, _ => return None,
} }
} };
}); Some(kind)
}).flatten();
path_ctx.has_type_args = segment.generic_arg_list().is_some(); path_ctx.has_type_args = segment.generic_arg_list().is_some();
if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) { if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
path_ctx.use_tree_parent = use_tree_parent; path_ctx.use_tree_parent = use_tree_parent;
path_ctx.qualifier = path let path = path
.segment() .segment()
.and_then(|it| find_node_in_file(original_file, &it)) .and_then(|it| find_node_in_file(original_file, &it))
.map(|it| it.parent_path()); .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)); return Some((path_ctx, pat_ctx));
} }

View File

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

View File

@ -17,6 +17,7 @@ fn proc_macros() {
struct Foo; struct Foo;
"#, "#,
expect![[r#" expect![[r#"
md proc_macros
at allow() at allow()
at cfg() at cfg()
at cfg_attr() at cfg_attr()
@ -35,7 +36,6 @@ fn proc_macros() {
kw self kw self
kw super kw super
kw crate kw crate
md proc_macros
"#]], "#]],
) )
} }
@ -61,10 +61,7 @@ fn proc_macros_qualified() {
#[proc_macros::$0] #[proc_macros::$0]
struct Foo; struct Foo;
"#, "#,
expect![[r#" expect![[r#""#]],
at input_replace pub macro input_replace
at identity pub macro identity
"#]],
) )
} }
@ -302,6 +299,8 @@ fn attr_on_struct() {
struct Foo; struct Foo;
"#, "#,
expect![[r#" expect![[r#"
md core
at derive pub macro derive
at allow() at allow()
at cfg() at cfg()
at cfg_attr() at cfg_attr()
@ -320,8 +319,6 @@ fn attr_on_struct() {
kw self kw self
kw super kw super
kw crate 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 { pub enum AttrKind {
Inner, Inner,
Outer, Outer,