11397: internal: Refactor completion module split r=Veykril a=Veykril

Currently our completion infra is split into several modules, each trying to do completions for something specific. This "something" is rather unstructured as it stands now, we have a module for `flyimporting path` completions, `unqualified` and `qualified path` completions, modules for `pattern position` completions that only try to complete extra things for patterns that aren't done in the path modules, `attribute` completions that again only try to add builtin attribute completions without adding the normal path completions and a bunch of other special "entity" completions like lifetimes, method call/field access, function param cloning, ... which serve a more specific purpose than the previous listed ones.

As is evident, the former mentioned ones have some decent overlap which requires extra filtering in them so that they don't collide with each other duplicating a bunch of completions(which we had happen in the past at times).

Now this overlap mostly happens with path completions(and keyword completions as well in some sense) which gives me the feeling that having `qualified` and `unqualified` path completions be separate from the rest gives us more troubles than benefits in the long run.
So this is an attempt at changing this structure to instead still go by rough entity for special cases, but when it comes to paths we instead do the module split on the "path kinds"/"locations"(think pattern, type, expr position etc) that exist. This goes hand in hand with the test refactoring I have done that moved tests to "location oriented" modules as well as the `CompletionContext` refactoring that actually already started splitting the context up for path kinds.

This PR moves some path completions out of the `qualified` and `unqualified` path modules namely attribute, visibility, use and pattern paths.

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2022-02-03 15:01:08 +00:00 committed by GitHub
commit 5a7e11f5fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 700 additions and 356 deletions

View file

@ -481,7 +481,7 @@ impl HirDisplay for Module {
// FIXME: Module doesn't have visibility saved in data.
match self.name(f.db) {
Some(name) => write!(f, "mod {}", name),
None if self.crate_root(f.db) == *self => match self.krate().display_name(f.db) {
None if self.is_crate_root(f.db) => match self.krate().display_name(f.db) {
Some(name) => write!(f, "extern crate {}", name),
None => write!(f, "extern crate {{unknown}}"),
},

View file

@ -452,6 +452,11 @@ impl Module {
Module { id: def_map.module_id(def_map.root()) }
}
pub fn is_crate_root(self, db: &dyn HirDatabase) -> bool {
let def_map = db.crate_def_map(self.id.krate());
def_map.root() == self.id.local_id
}
/// Iterates over all child modules.
pub fn children(self, db: &dyn HirDatabase) -> impl Iterator<Item = Module> {
let def_map = self.id.def_map(db.upcast());

View file

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

View file

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

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

@ -4,6 +4,7 @@ pub(crate) mod attribute;
pub(crate) mod dot;
pub(crate) mod flyimport;
pub(crate) mod fn_param;
pub(crate) mod format_string;
pub(crate) mod keyword;
pub(crate) mod lifetime;
pub(crate) mod mod_;
@ -14,7 +15,8 @@ pub(crate) mod record;
pub(crate) mod snippet;
pub(crate) mod trait_impl;
pub(crate) mod unqualified_path;
pub(crate) mod format_string;
pub(crate) mod use_;
pub(crate) mod vis;
use std::iter;
@ -97,6 +99,19 @@ impl Completions {
item.add_to(self);
}
pub(crate) fn add_nameref_keywords(&mut self, ctx: &CompletionContext) {
["self::", "super::", "crate::"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
}
pub(crate) fn add_crate_roots(&mut self, ctx: &CompletionContext) {
ctx.process_all_names(&mut |name, res| match res {
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
self.add_resolution(ctx, name, res);
}
_ => (),
});
}
pub(crate) fn add_resolution(
&mut self,
ctx: &CompletionContext,

View file

@ -1,8 +1,6 @@
//! Completion for attributes
//! Completion for (built-in) attributes, derives and lints.
//!
//! This module uses a bit of static metadata to provide completions
//! for built-in attributes.
//! Non-built-in attribute (excluding derives attributes) completions are done in [`super::unqualified_path`].
//! This module uses a bit of static metadata to provide completions for builtin-in attributes and lints.
use ide_db::{
helpers::{
@ -16,62 +14,107 @@ 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, PathCompletionCtx, PathKind, PathQualifierCtx},
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_absolute_path, qualifier, is_inner, annotated_item_kind) = match ctx.path_context {
Some(PathCompletionCtx {
kind: Some(PathKind::Attr { kind, annotated_item_kind }),
is_absolute_path,
ref qualifier,
..
}) => (is_absolute_path, qualifier, kind == AttrKind::Inner, annotated_item_kind),
_ => return,
};
match qualifier {
Some(PathQualifierCtx { resolution, is_super_chain, .. }) => {
if *is_super_chain {
acc.add_keyword(ctx, "super::");
}
let module = match resolution {
Some(hir::PathResolution::Def(hir::ModuleDef::Module(it))) => it,
_ => return,
};
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;
}
// fresh use tree with leading colon2, only show crate roots
None if is_absolute_path => acc.add_crate_roots(ctx),
// only show modules in a fresh UseTree
None => {
ctx.process_all_names(&mut |name, def| {
if let Some(def) = module_or_attr(def) {
acc.add_resolution(ctx, name, def);
}
});
acc.add_nameref_keywords(ctx);
}
}
let attributes = annotated_item_kind.and_then(|kind| {
if ast::Expr::can_cast(kind) {
Some(EXPR_ATTRIBUTES)
} else {

View file

@ -32,7 +32,7 @@ fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) {
if !ctx.config.enable_self_on_the_fly {
return;
}
if !ctx.is_trivial_path() || ctx.is_path_disallowed() || !ctx.expects_expression() {
if ctx.is_non_trivial_path() || ctx.is_path_disallowed() || !ctx.expects_expression() {
return;
}
if let Some(func) = ctx.function_def.as_ref().and_then(|fn_| ctx.sema.to_def(fn_)) {

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

@ -5,7 +5,7 @@
use syntax::{SyntaxKind, T};
use crate::{
context::{PathCompletionContext, PathKind},
context::{PathCompletionCtx, PathKind},
patterns::ImmediateLocation,
CompletionContext, CompletionItem, CompletionItemKind, Completions,
};
@ -27,6 +27,9 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
cov_mark::hit!(no_keyword_completion_in_non_trivial_path);
return;
}
if ctx.pattern_ctx.is_some() {
return;
}
let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet);
@ -34,11 +37,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
let has_block_expr_parent = ctx.has_block_expr_parent();
let expects_item = ctx.expects_item();
if let Some(PathKind::Vis { has_in_token }) = ctx.path_kind() {
if !has_in_token {
cov_mark::hit!(kw_completion_in);
add_keyword("in", "in");
}
if let Some(PathKind::Vis { .. }) = ctx.path_kind() {
return;
}
if ctx.has_impl_or_trait_prev_sibling() {
@ -121,14 +120,14 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
add_keyword("else if", "else if $1 {\n $0\n}");
}
if ctx.expects_ident_pat_or_ref_expr() {
if ctx.expects_ident_ref_expr() {
add_keyword("mut", "mut ");
}
let (can_be_stmt, in_loop_body) = match ctx.path_context {
Some(PathCompletionContext {
is_trivial_path: true, can_be_stmt, in_loop_body, ..
}) => (can_be_stmt, in_loop_body),
Some(PathCompletionCtx { is_absolute_path: false, can_be_stmt, in_loop_body, .. }) => {
(can_be_stmt, in_loop_body)
}
_ => return,
};

View file

@ -1,20 +1,52 @@
//! Completes constants and paths in unqualified patterns.
use hir::db::DefDatabase;
use hir::{db::DefDatabase, AssocItem, ScopeDef};
use rustc_hash::FxHashSet;
use syntax::ast::Pat;
use crate::{
context::{PatternContext, PatternRefutability},
context::{PathCompletionCtx, PathQualifierCtx, PatternRefutability},
CompletionContext, Completions,
};
/// Completes constants and paths in unqualified patterns.
pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
let refutable = match ctx.pattern_ctx {
Some(PatternContext { refutability, .. }) if ctx.path_context.is_none() => {
refutability == PatternRefutability::Refutable
}
let patctx = match &ctx.pattern_ctx {
Some(ctx) => ctx,
_ => return,
};
let refutable = patctx.refutability == PatternRefutability::Refutable;
if let Some(path_ctx) = &ctx.path_context {
pattern_path_completion(acc, ctx, path_ctx);
return;
}
match patctx.parent_pat.as_ref() {
Some(Pat::RangePat(_) | Pat::BoxPat(_)) => (),
Some(Pat::RefPat(r)) => {
if r.mut_token().is_none() {
acc.add_keyword(ctx, "mut");
}
}
_ => {
let tok = ctx.token.text_range().start();
match (patctx.ref_token.as_ref(), patctx.mut_token.as_ref()) {
(None, None) => {
acc.add_keyword(ctx, "ref");
acc.add_keyword(ctx, "mut");
}
(None, Some(m)) if tok < m.text_range().start() => {
acc.add_keyword(ctx, "ref");
}
(Some(r), None) if tok > r.text_range().end() => {
acc.add_keyword(ctx, "mut");
}
_ => (),
}
}
}
let single_variant_enum = |enum_: hir::Enum| ctx.db.enum_data(enum_.into()).variants.len() == 1;
if let Some(hir::Adt::Enum(e)) =
@ -63,3 +95,92 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
}
});
}
fn pattern_path_completion(
acc: &mut Completions,
ctx: &CompletionContext,
PathCompletionCtx { qualifier, is_absolute_path, .. }: &PathCompletionCtx,
) {
match qualifier {
Some(PathQualifierCtx { resolution, is_super_chain, .. }) => {
if *is_super_chain {
acc.add_keyword(ctx, "super::");
}
let resolution = match resolution {
Some(it) => it,
None => return,
};
match resolution {
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
let module_scope = module.scope(ctx.db, ctx.module);
for (name, def) in module_scope {
let add_resolution = match def {
ScopeDef::MacroDef(m) if m.is_fn_like() => true,
ScopeDef::ModuleDef(_) => true,
_ => false,
};
if add_resolution {
acc.add_resolution(ctx, name, def);
}
}
}
hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
cov_mark::hit!(enum_plain_qualified_use_tree);
e.variants(ctx.db)
.into_iter()
.for_each(|variant| acc.add_enum_variant(ctx, variant, None));
}
res @ (hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_)) => {
if let Some(krate) = ctx.krate {
let ty = match res {
hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
_ => return,
};
if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
e.variants(ctx.db)
.into_iter()
.for_each(|variant| acc.add_enum_variant(ctx, variant, None));
}
let traits_in_scope = ctx.scope.visible_traits();
let mut seen = FxHashSet::default();
ty.iterate_path_candidates(
ctx.db,
krate,
&traits_in_scope,
ctx.module,
None,
|_ty, item| {
// Note associated consts cannot be referenced in patterns
if let AssocItem::TypeAlias(ta) = item {
// We might iterate candidates of a trait multiple times here, so deduplicate them.
if seen.insert(item) {
acc.add_type_alias(ctx, ta);
}
}
None::<()>
},
);
}
}
_ => {}
}
}
// qualifier can only be none here if we are in a TuplePat or RecordPat in which case special characters have to follow the path
None if *is_absolute_path => acc.add_crate_roots(ctx),
None => {
cov_mark::hit!(unqualified_path_only_modules_in_import);
ctx.process_all_names(&mut |name, res| {
if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res {
acc.add_resolution(ctx, name, res);
}
});
acc.add_nameref_keywords(ctx);
}
}
}

View file

@ -1,14 +1,12 @@
//! Completion of paths, i.e. `some::prefix::$0`.
use std::iter;
use hir::{ScopeDef, Trait};
use rustc_hash::FxHashSet;
use syntax::{ast, AstNode};
use syntax::ast;
use crate::{
completions::{module_or_attr, module_or_fn_macro},
context::{PathCompletionContext, PathKind},
completions::module_or_fn_macro,
context::{PathCompletionCtx, PathKind},
patterns::ImmediateLocation,
CompletionContext, Completions,
};
@ -17,21 +15,19 @@ 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 {
if ctx.pattern_ctx.is_some() {
return;
}
let (qualifier, kind) = match ctx.path_context {
// let ... else, syntax would come in really handy here right now
Some(PathCompletionContext {
qualifier: Some(ref qualifier),
use_tree_parent,
kind,
..
}) => (qualifier, use_tree_parent, kind),
Some(PathCompletionCtx { qualifier: Some(ref qualifier), kind, .. }) => (qualifier, kind),
_ => return,
};
// special case `<_>::$0` as this doesn't resolve to anything.
if path.qualifier().is_none() {
if qualifier.path.qualifier().is_none() {
if matches!(
path.segment().and_then(|it| it.kind()),
qualifier.path.segment().and_then(|it| it.kind()),
Some(ast::PathSegmentKind::Type {
type_ref: Some(ast::Type::InferType(_)),
trait_ref: None,
@ -47,17 +43,15 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
}
let resolution = match ctx.sema.resolve_path(path) {
let resolution = match &qualifier.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);
}
@ -69,78 +63,22 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
match kind {
// Complete next child module that comes after the qualified module which is still our parent
Some(PathKind::Vis { .. }) => {
if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
if let Some(current_module) = ctx.module {
let next_towards_current = current_module
.path_to_root(ctx.db)
.into_iter()
.take_while(|&it| it != module)
.next();
if let Some(next) = next_towards_current {
if let Some(name) = next.name(ctx.db) {
cov_mark::hit!(visibility_qualified);
acc.add_resolution(ctx, name, ScopeDef::ModuleDef(next.into()));
}
}
}
}
Some(PathKind::Pat | PathKind::Attr { .. } | PathKind::Vis { .. } | PathKind::Use) => {
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);
}
}
}
return;
_ => {
// Add associated types on type parameters and `Self`.
ctx.scope.assoc_type_shorthand_candidates(&resolution, |_, alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
}
Some(PathKind::Use) => {
if iter::successors(Some(path.clone()), |p| p.qualifier())
.all(|p| p.segment().and_then(|s| s.super_token()).is_some())
{
acc.add_keyword(ctx, "super::");
}
// only show `self` in a new use-tree when the qualifier doesn't end in self
if use_tree_parent
&& !matches!(
path.segment().and_then(|it| it.kind()),
Some(ast::PathSegmentKind::SelfKw)
)
{
acc.add_keyword(ctx, "self");
}
}
_ => (),
}
if !matches!(kind, Some(PathKind::Pat)) {
// Add associated types on type parameters and `Self`.
ctx.scope.assoc_type_shorthand_candidates(&resolution, |_, alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
}
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 {
if let Some(ast::NameLike::NameRef(name_ref)) = ctx.name_syntax.as_ref() {
if name_ref.syntax().text() == name.to_smol_str().as_str() {
// for `use self::foo$0`, don't suggest `foo` as a completion
cov_mark::hit!(dont_complete_current_use);
continue;
}
}
}
}
let add_resolution = match def {
// Don't suggest attribute macros and derives.
ScopeDef::MacroDef(mac) => mac.is_fn_like(),
@ -168,7 +106,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 +560,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(

View file

@ -5,7 +5,7 @@ use ide_db::helpers::{insert_use::ImportScope, SnippetCap};
use syntax::T;
use crate::{
context::PathCompletionContext, item::Builder, CompletionContext, CompletionItem,
context::PathCompletionCtx, item::Builder, CompletionContext, CompletionItem,
CompletionItemKind, Completions, SnippetScope,
};
@ -21,7 +21,9 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
}
let can_be_stmt = match ctx.path_context {
Some(PathCompletionContext { is_trivial_path: true, can_be_stmt, .. }) => can_be_stmt,
Some(PathCompletionCtx {
is_absolute_path: false, qualifier: None, can_be_stmt, ..
}) => can_be_stmt,
_ => return,
};

View file

@ -4,8 +4,8 @@ use hir::ScopeDef;
use syntax::{ast, AstNode};
use crate::{
completions::{module_or_attr, module_or_fn_macro},
context::{PathCompletionContext, PathKind},
completions::module_or_fn_macro,
context::{PathCompletionCtx, PathKind},
patterns::ImmediateLocation,
CompletionContext, Completions,
};
@ -15,38 +15,23 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
if ctx.is_path_disallowed() || ctx.has_impl_or_trait_prev_sibling() {
return;
}
let kind = match ctx.path_context {
Some(PathCompletionContext { is_trivial_path: true, kind, .. }) => kind,
match ctx.path_context {
Some(PathCompletionCtx {
kind:
Some(
PathKind::Vis { .. }
| PathKind::Attr { .. }
| PathKind::Use { .. }
| PathKind::Pat,
),
..
}) => return,
Some(PathCompletionCtx { is_absolute_path: false, qualifier: None, .. }) => (),
_ => return,
};
if let Some(PathKind::Use) = kind {
// only show modules in a fresh UseTree
cov_mark::hit!(unqualified_path_only_modules_in_import);
ctx.process_all_names(&mut |name, res| {
if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res {
acc.add_resolution(ctx, name, res);
}
});
["self::", "super::", "crate::"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
return;
}
["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
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;
}
_ => (),
}
match &ctx.completion_location {
Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => {
// only show macros in {Assoc}ItemList

View file

@ -0,0 +1,95 @@
//! Completion for use trees
use hir::ScopeDef;
use syntax::{ast, AstNode};
use crate::{
context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx},
Completions,
};
pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) {
let (is_absolute_path, qualifier) = match ctx.path_context {
Some(PathCompletionCtx {
kind: Some(PathKind::Use),
is_absolute_path,
ref qualifier,
..
}) => (is_absolute_path, qualifier),
_ => return,
};
match qualifier {
Some(PathQualifierCtx { path, resolution, is_super_chain, use_tree_parent }) => {
if *is_super_chain {
acc.add_keyword(ctx, "super::");
}
// only show `self` in a new use-tree when the qualifier doesn't end in self
let not_preceded_by_self = *use_tree_parent
&& !matches!(
path.segment().and_then(|it| it.kind()),
Some(ast::PathSegmentKind::SelfKw)
);
if not_preceded_by_self {
acc.add_keyword(ctx, "self");
}
let resolution = match resolution {
Some(it) => it,
None => return,
};
match resolution {
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
let module_scope = module.scope(ctx.db, ctx.module);
let unknown_is_current = |name: &hir::Name| {
matches!(
ctx.name_syntax.as_ref(),
Some(ast::NameLike::NameRef(name_ref))
if name_ref.syntax().text() == name.to_smol_str().as_str()
)
};
for (name, def) in module_scope {
let add_resolution = match def {
ScopeDef::Unknown if unknown_is_current(&name) => {
// for `use self::foo$0`, don't suggest `foo` as a completion
cov_mark::hit!(dont_complete_current_use);
continue;
}
ScopeDef::ModuleDef(_) | ScopeDef::MacroDef(_) | ScopeDef::Unknown => {
true
}
_ => false,
};
if add_resolution {
acc.add_resolution(ctx, name, def);
}
}
}
hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
cov_mark::hit!(enum_plain_qualified_use_tree);
e.variants(ctx.db)
.into_iter()
.for_each(|variant| acc.add_enum_variant(ctx, variant, None));
}
_ => {}
}
}
// fresh use tree with leading colon2, only show crate roots
None if is_absolute_path => {
cov_mark::hit!(use_tree_crate_roots_only);
acc.add_crate_roots(ctx);
}
// only show modules in a fresh UseTree
None => {
cov_mark::hit!(unqualified_path_only_modules_in_import);
ctx.process_all_names(&mut |name, res| {
if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res {
acc.add_resolution(ctx, name, res);
}
});
acc.add_nameref_keywords(ctx);
}
}
}

View file

@ -0,0 +1,52 @@
//! Completion for visibility specifiers.
use hir::ScopeDef;
use crate::{
context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx},
Completions,
};
pub(crate) fn complete_vis(acc: &mut Completions, ctx: &CompletionContext) {
let (is_absolute_path, qualifier, has_in_token) = match ctx.path_context {
Some(PathCompletionCtx {
kind: Some(PathKind::Vis { has_in_token }),
is_absolute_path,
ref qualifier,
..
}) => (is_absolute_path, qualifier, has_in_token),
_ => return,
};
match qualifier {
Some(PathQualifierCtx { resolution, is_super_chain, .. }) => {
if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = resolution {
if let Some(current_module) = ctx.module {
let next_towards_current = current_module
.path_to_root(ctx.db)
.into_iter()
.take_while(|it| it != module)
.next();
if let Some(next) = next_towards_current {
if let Some(name) = next.name(ctx.db) {
cov_mark::hit!(visibility_qualified);
acc.add_resolution(ctx, name, ScopeDef::ModuleDef(next.into()));
}
}
}
}
if *is_super_chain {
acc.add_keyword(ctx, "super::");
}
}
None if !is_absolute_path => {
if !has_in_token {
cov_mark::hit!(kw_completion_in);
acc.add_keyword(ctx, "in");
}
["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
}
_ => {}
}
}

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 @@ 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,
@ -34,6 +36,7 @@ pub(crate) enum PatternRefutability {
Refutable,
Irrefutable,
}
pub(crate) enum Visible {
Yes,
Editable,
@ -44,7 +47,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 },
@ -52,18 +55,13 @@ pub(super) enum PathKind {
}
#[derive(Debug)]
pub(crate) struct PathCompletionContext {
pub(crate) struct PathCompletionCtx {
/// If this is a call with () already there
has_call_parens: bool,
/// 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>,
#[allow(dead_code)]
/// If not a trivial path, the suffix (parent).
pub(super) parent: Option<ast::Path>,
/// Whether the qualifier comes from a use tree parent or not
pub(super) use_tree_parent: bool,
/// Whether this path stars with a `::`.
pub(super) is_absolute_path: bool,
/// The qualifier of the current path if it exists.
pub(super) qualifier: Option<PathQualifierCtx>,
pub(super) kind: Option<PathKind>,
/// Whether the path segment has type args or not.
pub(super) has_type_args: bool,
@ -72,11 +70,24 @@ pub(crate) struct PathCompletionContext {
pub(super) in_loop_body: bool,
}
#[derive(Debug)]
pub(crate) struct PathQualifierCtx {
pub(crate) path: ast::Path,
pub(crate) resolution: Option<PathResolution>,
/// Whether this path consists solely of `super` segments
pub(crate) is_super_chain: bool,
/// Whether the qualifier comes from a use tree parent or not
pub(crate) use_tree_parent: bool,
}
#[derive(Debug)]
pub(super) struct PatternContext {
pub(super) refutability: PatternRefutability,
pub(super) param_ctx: Option<(ast::ParamList, ast::Param, ParamKind)>,
pub(super) has_type_ascription: bool,
pub(super) parent_pat: Option<ast::Pat>,
pub(super) ref_token: Option<SyntaxToken>,
pub(super) mut_token: Option<SyntaxToken>,
}
#[derive(Debug)]
@ -129,7 +140,7 @@ pub(crate) struct CompletionContext<'a> {
pub(super) lifetime_ctx: Option<LifetimeContext>,
pub(super) pattern_ctx: Option<PatternContext>,
pub(super) path_context: Option<PathCompletionContext>,
pub(super) path_context: Option<PathCompletionCtx>,
pub(super) locals: Vec<(Name, Local)>,
@ -211,11 +222,8 @@ impl<'a> CompletionContext<'a> {
matches!(self.completion_location, Some(ImmediateLocation::StmtList))
}
pub(crate) fn expects_ident_pat_or_ref_expr(&self) -> bool {
matches!(
self.completion_location,
Some(ImmediateLocation::IdentPat | ImmediateLocation::RefExpr)
)
pub(crate) fn expects_ident_ref_expr(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::RefExpr))
}
pub(crate) fn expect_field(&self) -> bool {
@ -262,27 +270,29 @@ impl<'a> CompletionContext<'a> {
}
pub(crate) fn expects_expression(&self) -> bool {
matches!(self.path_context, Some(PathCompletionContext { kind: Some(PathKind::Expr), .. }))
matches!(self.path_context, Some(PathCompletionCtx { kind: Some(PathKind::Expr), .. }))
}
pub(crate) fn expects_type(&self) -> bool {
matches!(self.path_context, Some(PathCompletionContext { kind: Some(PathKind::Type), .. }))
matches!(self.path_context, Some(PathCompletionCtx { kind: Some(PathKind::Type), .. }))
}
pub(crate) fn path_is_call(&self) -> bool {
self.path_context.as_ref().map_or(false, |it| it.has_call_parens)
}
pub(crate) fn is_trivial_path(&self) -> bool {
matches!(self.path_context, Some(PathCompletionContext { is_trivial_path: true, .. }))
}
pub(crate) fn is_non_trivial_path(&self) -> bool {
matches!(self.path_context, Some(PathCompletionContext { is_trivial_path: false, .. }))
matches!(
self.path_context,
Some(
PathCompletionCtx { is_absolute_path: true, .. }
| PathCompletionCtx { qualifier: Some(_), .. }
)
)
}
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.path))
}
pub(crate) fn path_kind(&self) -> Option<PathKind> {
@ -779,37 +789,33 @@ impl<'a> CompletionContext<'a> {
if is_name_in_field_pat {
return None;
}
if !bind_pat.is_simple_ident() {
return None;
}
Some(pattern_context_for(original_file, bind_pat.into()))
}
fn classify_name_ref(
_sema: &Semantics<RootDatabase>,
sema: &Semantics<RootDatabase>,
original_file: &SyntaxNode,
name_ref: ast::NameRef,
) -> Option<(PathCompletionContext, Option<PatternContext>)> {
) -> Option<(PathCompletionCtx, Option<PatternContext>)> {
let parent = name_ref.syntax().parent()?;
let segment = ast::PathSegment::cast(parent)?;
let path = segment.parent_path();
let mut path_ctx = PathCompletionContext {
let mut path_ctx = PathCompletionCtx {
has_call_parens: false,
is_trivial_path: false,
is_absolute_path: false,
qualifier: None,
parent: None,
has_type_args: false,
can_be_stmt: false,
in_loop_body: false,
use_tree_parent: false,
kind: None,
};
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,32 +836,57 @@ 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
if !use_tree_parent {
path_ctx.is_absolute_path =
path.top_path().segment().map_or(false, |it| it.coloncolon_token().is_some());
}
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);
let is_super_chain = iter::successors(Some(path.clone()), |p| p.qualifier())
.all(|p| p.segment().and_then(|s| s.super_token()).is_some());
PathQualifierCtx { path, resolution: res, is_super_chain, use_tree_parent }
});
return Some((path_ctx, pat_ctx));
}
if let Some(segment) = path.segment() {
if segment.coloncolon_token().is_some() {
path_ctx.is_absolute_path = true;
return Some((path_ctx, pat_ctx));
}
}
path_ctx.is_trivial_path = true;
// Find either enclosing expr statement (thing with `;`) or a
// block. If block, check that we are the last expr.
path_ctx.can_be_stmt = name_ref
@ -915,7 +946,18 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont
};
(refutability, false)
});
PatternContext { refutability, param_ctx: is_param, has_type_ascription }
let (ref_token, mut_token) = match &pat {
ast::Pat::IdentPat(it) => (it.ref_token(), it.mut_token()),
_ => (None, None),
};
PatternContext {
refutability,
param_ctx: is_param,
has_type_ascription,
parent_pat: pat.syntax().parent().and_then(ast::Pat::cast),
mut_token,
ref_token,
}
}
fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
@ -946,7 +988,7 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
}
let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?;
let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?;
use_tree.path().zip(Some(true))
Some((use_tree.path()?, true))
}
fn has_ref(token: &SyntaxToken) -> bool {

View file

@ -151,7 +151,10 @@ 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::use_::complete_use_tree(&mut acc, &ctx);
completions::vis::complete_vis(&mut acc, &ctx);
completions::fn_param::complete_fn_param(&mut acc, &ctx);
completions::keyword::complete_expr_keyword(&mut acc, &ctx);
completions::snippet::complete_expr_snippet(&mut acc, &ctx);

View file

@ -19,7 +19,7 @@ use ide_db::{
use syntax::{SmolStr, SyntaxKind, TextRange};
use crate::{
context::{PathCompletionContext, PathKind},
context::{PathCompletionCtx, PathKind},
item::{CompletionRelevanceTypeMatch, ImportEdit},
render::{enum_variant::render_variant, function::render_fn, macro_::render_macro},
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
@ -234,7 +234,7 @@ fn render_resolution_(
// Add `<>` for generic types
let type_path_no_ty_args = matches!(
ctx.completion.path_context,
Some(PathCompletionContext { kind: Some(PathKind::Type), has_type_args: false, .. })
Some(PathCompletionCtx { kind: Some(PathKind::Type), has_type_args: false, .. })
) && ctx.completion.config.add_call_parenthesis;
if type_path_no_ty_args {
if let Some(cap) = ctx.snippet_cap() {

View file

@ -17,6 +17,10 @@ fn proc_macros() {
struct Foo;
"#,
expect![[r#"
md proc_macros
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -32,10 +36,6 @@ struct Foo;
at derive()
at repr()
at non_exhaustive
kw self
kw super
kw crate
md proc_macros
"#]],
)
}
@ -78,15 +78,15 @@ fn with_existing_attr() {
check(
r#"#[no_mangle] #[$0] mcall!();"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
at deny()
at forbid()
at warn()
kw self
kw super
kw crate
"#]],
)
}
@ -96,6 +96,9 @@ fn attr_on_source_file() {
check(
r#"#![$0]"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -116,9 +119,6 @@ fn attr_on_source_file() {
at recursion_limit = ""
at type_length_limit =
at windows_subsystem = ""
kw self
kw super
kw crate
"#]],
);
}
@ -128,6 +128,9 @@ fn attr_on_module() {
check(
r#"#[$0] mod foo;"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -142,14 +145,14 @@ fn attr_on_module() {
at no_mangle
at macro_use
at path = ""
kw self
kw super
kw crate
"#]],
);
check(
r#"mod foo {#![$0]}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -163,9 +166,6 @@ fn attr_on_module() {
at must_use
at no_mangle
at no_implicit_prelude
kw self
kw super
kw crate
"#]],
);
}
@ -175,6 +175,9 @@ fn attr_on_macro_rules() {
check(
r#"#[$0] macro_rules! foo {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -189,9 +192,6 @@ fn attr_on_macro_rules() {
at no_mangle
at macro_export
at macro_use
kw self
kw super
kw crate
"#]],
);
}
@ -201,6 +201,9 @@ fn attr_on_macro_def() {
check(
r#"#[$0] macro foo {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -213,9 +216,6 @@ fn attr_on_macro_def() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -225,6 +225,9 @@ fn attr_on_extern_crate() {
check(
r#"#[$0] extern crate foo;"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -238,9 +241,6 @@ fn attr_on_extern_crate() {
at must_use
at no_mangle
at macro_use
kw self
kw super
kw crate
"#]],
);
}
@ -250,6 +250,9 @@ fn attr_on_use() {
check(
r#"#[$0] use foo;"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -262,9 +265,6 @@ fn attr_on_use() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -274,6 +274,9 @@ fn attr_on_type_alias() {
check(
r#"#[$0] type foo = ();"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -286,9 +289,6 @@ fn attr_on_type_alias() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -302,6 +302,11 @@ fn attr_on_struct() {
struct Foo;
"#,
expect![[r#"
md core
at derive pub macro derive
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -317,11 +322,6 @@ struct Foo;
at derive()
at repr()
at non_exhaustive
kw self
kw super
kw crate
md core
at derive pub macro derive
"#]],
);
}
@ -331,6 +331,9 @@ fn attr_on_enum() {
check(
r#"#[$0] enum Foo {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -346,9 +349,6 @@ fn attr_on_enum() {
at derive()
at repr()
at non_exhaustive
kw self
kw super
kw crate
"#]],
);
}
@ -358,6 +358,9 @@ fn attr_on_const() {
check(
r#"#[$0] const FOO: () = ();"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -370,9 +373,6 @@ fn attr_on_const() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -382,6 +382,9 @@ fn attr_on_static() {
check(
r#"#[$0] static FOO: () = ()"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -399,9 +402,6 @@ fn attr_on_static() {
at link_section = ""
at global_allocator
at used
kw self
kw super
kw crate
"#]],
);
}
@ -411,6 +411,9 @@ fn attr_on_trait() {
check(
r#"#[$0] trait Foo {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -424,9 +427,6 @@ fn attr_on_trait() {
at must_use
at no_mangle
at must_use
kw self
kw super
kw crate
"#]],
);
}
@ -436,6 +436,9 @@ fn attr_on_impl() {
check(
r#"#[$0] impl () {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -449,14 +452,14 @@ fn attr_on_impl() {
at must_use
at no_mangle
at automatically_derived
kw self
kw super
kw crate
"#]],
);
check(
r#"impl () {#![$0]}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -469,9 +472,6 @@ fn attr_on_impl() {
at doc(alias = "")
at must_use
at no_mangle
kw self
kw super
kw crate
"#]],
);
}
@ -481,6 +481,9 @@ fn attr_on_extern_block() {
check(
r#"#[$0] extern {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -494,14 +497,14 @@ fn attr_on_extern_block() {
at must_use
at no_mangle
at link
kw self
kw super
kw crate
"#]],
);
check(
r#"extern {#![$0]}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -515,9 +518,6 @@ fn attr_on_extern_block() {
at must_use
at no_mangle
at link
kw self
kw super
kw crate
"#]],
);
}
@ -527,6 +527,9 @@ fn attr_on_variant() {
check(
r#"enum Foo { #[$0] Bar }"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -534,9 +537,6 @@ fn attr_on_variant() {
at forbid()
at warn()
at non_exhaustive
kw self
kw super
kw crate
"#]],
);
}
@ -546,6 +546,9 @@ fn attr_on_fn() {
check(
r#"#[$0] fn main() {}"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
@ -573,9 +576,6 @@ fn attr_on_fn() {
at target_feature = ""
at test
at track_caller
kw self
kw super
kw crate
"#]],
);
}
@ -586,15 +586,15 @@ fn attr_on_expr() {
check(
r#"fn main() { #[$0] foo() }"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at cfg()
at cfg_attr()
at deny()
at forbid()
at warn()
kw self
kw super
kw crate
"#]],
);
}
@ -604,6 +604,9 @@ fn attr_in_source_file_end() {
check(
r#"#[$0]"#,
expect![[r#"
kw self::
kw super::
kw crate::
at allow()
at automatically_derived
at cfg()
@ -640,9 +643,6 @@ fn attr_in_source_file_end() {
at track_caller
at used
at warn()
kw self
kw super
kw crate
"#]],
);
}

View file

@ -17,6 +17,7 @@ fn baz(file$0) {}
"#,
expect![[r#"
bn file_id: usize
kw ref
kw mut
"#]],
);
@ -32,6 +33,7 @@ fn baz(foo: (), file$0) {}
"#,
expect![[r#"
bn file_id: usize
kw ref
kw mut
"#]],
);
@ -47,6 +49,7 @@ fn baz(file$0 id: u32) {}
"#,
expect![[r#"
bn file_id: usize,
kw ref
kw mut
"#]],
);
@ -60,6 +63,7 @@ fn foo(file_id: usize) {}
fn bar(file_id: u32, $0) {}
"#,
expect![[r#"
kw ref
kw mut
"#]],
);
@ -76,6 +80,7 @@ pub(crate) trait SourceRoot {
"#,
expect![[r#"
bn file_id: usize
kw ref
kw mut
"#]],
);
@ -91,6 +96,7 @@ fn outer(text: &str) {
"#,
expect![[r#"
bn text: &str
kw ref
kw mut
"#]],
)
@ -106,6 +112,7 @@ fn foo2($0) {}
"#,
expect![[r#"
bn Bar { bar }: Bar
kw ref
kw mut
bn Bar Bar { bar$1 }: Bar$0
st Bar
@ -130,6 +137,7 @@ impl A {
bn mut self
bn &mut self
bn file_id: usize
kw ref
kw mut
sp Self
st A
@ -150,6 +158,7 @@ impl A {
"#,
expect![[r#"
bn file_id: usize
kw ref
kw mut
sp Self
st A
@ -178,6 +187,7 @@ fn outer() {
bn foo: i32
bn baz: i32
bn bar: i32
kw ref
kw mut
"#]],
)
@ -202,6 +212,22 @@ fn outer() {
bn baz: i32
bn bar: i32
bn foo: i32
kw ref
kw mut
"#]],
)
}
#[test]
fn completes_fully_equal() {
check(
r#"
fn foo(bar: u32) {}
fn bar(bar$0) {}
"#,
expect![[r#"
bn bar: u32
kw ref
kw mut
"#]],
)

View file

@ -22,6 +22,7 @@ fn quux() {
}
"#,
expect![[r#"
kw ref
kw mut
"#]],
);
@ -53,16 +54,13 @@ fn quux() {
#[test]
fn ident_ref_mut_pat() {
// FIXME mut is already here, don't complete it again
check_empty(
r#"
fn quux() {
let ref mut en$0
}
"#,
expect![[r#"
kw mut
"#]],
expect![[r#""#]],
);
check_empty(
r#"
@ -70,9 +68,7 @@ fn quux() {
let ref mut en$0 @ x
}
"#,
expect![[r#"
kw mut
"#]],
expect![[r#""#]],
);
}
@ -88,16 +84,13 @@ fn quux() {
kw mut
"#]],
);
// FIXME mut is already here, don't complete it again
check_empty(
r#"
fn quux() {
let &mut en$0
}
"#,
expect![[r#"
kw mut
"#]],
expect![[r#""#]],
);
}
@ -110,6 +103,7 @@ fn foo() {
}
"#,
expect![[r##"
kw ref
kw mut
en Enum
bn Record Record { field$1 }$0
@ -139,6 +133,7 @@ fn foo() {
}
"#,
expect![[r##"
kw ref
kw mut
bn Record Record { field$1 }$0
st Record
@ -160,6 +155,7 @@ fn foo(a$0) {
}
"#,
expect![[r##"
kw ref
kw mut
bn Record Record { field$1 }: Record$0
st Record
@ -175,6 +171,7 @@ fn foo(a$0: Tuple) {
}
"#,
expect![[r##"
kw ref
kw mut
bn Record Record { field$1 }$0
st Record
@ -200,6 +197,7 @@ fn foo() {
}
"#,
expect![[r#"
kw ref
kw mut
ma m!() macro_rules! m
"#]],
@ -218,6 +216,7 @@ fn foo() {
}
"#,
expect![[r#"
kw ref
kw mut
ev E::X ()
en E
@ -242,6 +241,7 @@ fn outer() {
}
"#,
expect![[r#"
kw ref
kw mut
bn Record Record { field$1, .. }$0
st Record
@ -267,6 +267,7 @@ impl Foo {
}
"#,
expect![[r#"
kw ref
kw mut
bn Self Self($1)$0
sp Self
@ -278,7 +279,6 @@ impl Foo {
#[test]
fn enum_qualified() {
// FIXME: Don't show functions, they aren't patterns
check(
r#"
impl Enum {
@ -291,12 +291,9 @@ fn func() {
}
"#,
expect![[r#"
ev TupleV() (u32)
ev RecordV {field: u32}
ev UnitV ()
ct ASSOC_CONST const ASSOC_CONST: ()
fn assoc_fn() fn()
ta AssocType type AssocType = ()
ev TupleV() (u32)
ev RecordV {field: u32}
ev UnitV ()
"#]],
);
}
@ -310,6 +307,7 @@ struct Bar(u32);
fn outer(Foo { bar: $0 }: Foo) {}
"#,
expect![[r#"
kw ref
kw mut
bn Foo Foo { bar$1 }$0
st Foo
@ -340,6 +338,7 @@ struct Bar(u32);
fn foo($0) {}
"#,
expect![[r#"
kw ref
kw mut
bn Foo Foo { bar$1 }: Foo$0
st Foo
@ -360,6 +359,7 @@ fn foo() {
}
"#,
expect![[r#"
kw ref
kw mut
bn Foo Foo { bar$1 }$0
st Foo
@ -368,17 +368,3 @@ fn foo() {
"#]],
)
}
#[test]
fn completes_fully_equal() {
check_empty(
r#"
fn foo(bar: u32) {}
fn bar(bar$0) {}
"#,
expect![[r#"
bn bar: u32
kw mut
"#]],
)
}

View file

@ -31,6 +31,25 @@ mod foo {}
);
}
#[test]
fn use_tree_start_abs() {
cov_mark::check!(use_tree_crate_roots_only);
check(
r#"
//- /lib.rs crate:main deps:other_crate
use ::f$0
struct Foo;
mod foo {}
//- /other_crate/lib.rs crate:other_crate
// nothing here
"#,
expect![[r#"
md other_crate
"#]],
);
}
#[test]
fn dont_complete_current_use() {
cov_mark::check!(dont_complete_current_use);
@ -134,6 +153,25 @@ struct Bar;
);
}
#[test]
fn enum_plain_qualified_use_tree() {
cov_mark::check!(enum_plain_qualified_use_tree);
check(
r#"
use Foo::$0
enum Foo { Variant }
impl Foo {
const CONST: () = ()
fn func() {}
}
"#,
expect![[r#"
ev Variant ()
"#]],
);
}
#[test]
fn self_qualified_use_tree() {
check(

View file

@ -223,7 +223,7 @@ impl Definition {
// def is crate root
// FIXME: We don't do searches for crates currently, as a crate does not actually have a single name
if let &Definition::Module(module) = self {
if module.crate_root(db) == module {
if module.is_crate_root(db) {
return SearchScope::reverse_dependencies(db, module.krate());
}
}
@ -378,7 +378,7 @@ impl<'a> FindUsages<'a> {
let name = match self.def {
// special case crate modules as these do not have a proper name
Definition::Module(module) if module.crate_root(self.sema.db) == module => {
Definition::Module(module) if module.is_crate_root(self.sema.db) => {
// FIXME: This assumes the crate name is always equal to its display name when it really isn't
module
.krate()
@ -460,7 +460,7 @@ impl<'a> FindUsages<'a> {
Definition::Module(module) => {
let scope = search_scope.intersection(&SearchScope::module(self.sema.db, module));
let is_crate_root = module.crate_root(self.sema.db) == module;
let is_crate_root = module.is_crate_root(self.sema.db);
for (text, file_id, search_range) in scope_files(sema, &scope) {
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());

View file

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