From 6940cca76065f7871e53ef2a95019e0841ff408d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 2 Feb 2022 13:35:46 +0100 Subject: [PATCH] Move attribute path completions into attribute completion module --- crates/hir/src/semantics.rs | 15 ++- crates/hir_def/src/path.rs | 4 +- crates/hir_ty/src/infer.rs | 1 + .../src/completions/attribute.rs | 109 ++++++++++++------ .../src/completions/flyimport.rs | 4 +- .../src/completions/qualified_path.rs | 37 ++---- .../src/completions/unqualified_path.rs | 11 +- crates/ide_completion/src/context.rs | 51 +++++--- crates/ide_completion/src/lib.rs | 1 + crates/ide_completion/src/tests/attribute.rs | 11 +- crates/syntax/src/ast/node_ext.rs | 2 +- 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>]>>, ) -> 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 = 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 = 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 { @@ -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( 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 }, 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, + pub(super) qualifier: Option<(ast::Path, Option)>, #[allow(dead_code)] /// If not a trivial path, the suffix (parent). pub(super) parent: Option, @@ -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 { @@ -786,7 +788,7 @@ impl<'a> CompletionContext<'a> { } fn classify_name_ref( - _sema: &Semantics, + sema: &Semantics, original_file: &SyntaxNode, name_ref: ast::NameRef, ) -> Option<(PathCompletionContext, Option)> { @@ -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> 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 for ast::Item { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum AttrKind { Inner, Outer,