mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 12:33:33 +00:00
Move attribute path completions into attribute completion module
This commit is contained in:
parent
46b5089bfa
commit
6940cca760
11 changed files with 139 additions and 107 deletions
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
@ -622,18 +613,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(
|
||||
|
|
|
@ -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,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue