internal: completion: split out more PathKinds from ImmediateLocation

This commit is contained in:
Lukas Wirth 2021-12-06 15:51:33 +01:00
parent 49b0970970
commit ae0c7268f7
10 changed files with 206 additions and 160 deletions

View File

@ -110,7 +110,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
if !ctx.config.enable_imports_on_the_fly {
return None;
}
if ctx.in_use_tree()
if matches!(ctx.path_kind(), Some(PathKind::Vis { .. } | PathKind::Use))
|| ctx.is_path_disallowed()
|| ctx.expects_item()
|| ctx.expects_assoc_item()
@ -118,6 +118,11 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
{
return None;
}
// FIXME: This should be encoded in a different way
if ctx.pattern_ctx.is_none() && ctx.path_context.is_none() && !ctx.has_dot_receiver() {
// completion inside `ast::Name` of a item declaration
return None;
}
let potential_import_name = {
let token_kind = ctx.token.kind();
if matches!(token_kind, T![.] | T![::]) {
@ -147,14 +152,25 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
}
};
match (kind, import.original_item) {
// Aren't handled in flyimport
(PathKind::Vis { .. } | PathKind::Use, _) => false,
// modules are always fair game
(_, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
// and so are macros(except for attributes)
(
PathKind::Expr | PathKind::Type | PathKind::Mac | PathKind::Pat,
ItemInNs::Macros(mac),
) => mac.is_fn_like(),
(PathKind::Mac, _) => true,
(PathKind::Expr, ItemInNs::Types(_) | ItemInNs::Values(_)) => true,
(PathKind::Pat, ItemInNs::Types(_)) => true,
(PathKind::Pat, ItemInNs::Values(def)) => matches!(def, hir::ModuleDef::Const(_)),
(PathKind::Type, ItemInNs::Types(_)) => true,
(PathKind::Type, ItemInNs::Values(_)) => false,
(PathKind::Expr | PathKind::Type, ItemInNs::Macros(mac)) => mac.is_fn_like(),
(PathKind::Attr, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
(PathKind::Attr, ItemInNs::Macros(mac)) => mac.is_attr(),
(PathKind::Attr, _) => false,
}

View File

@ -5,8 +5,9 @@
use syntax::{SyntaxKind, T};
use crate::{
context::PathCompletionContext, patterns::ImmediateLocation, CompletionContext, CompletionItem,
CompletionItemKind, Completions,
context::{PathCompletionContext, PathKind},
patterns::ImmediateLocation,
CompletionContext, CompletionItem, CompletionItemKind, Completions,
};
pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
@ -33,8 +34,8 @@ 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(ImmediateLocation::Visibility(vis)) = &ctx.completion_location {
if vis.in_token().is_none() {
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");
}

View File

@ -1,15 +1,17 @@
//! Completes constants and paths in patterns.
//! Completes constants and paths in unqualified patterns.
use crate::{
context::{PatternContext, PatternRefutability},
CompletionContext, Completions,
};
/// Completes constants and paths in patterns.
/// 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, .. }) => refutability == PatternRefutability::Refutable,
None => return,
Some(PatternContext { refutability, .. }) if ctx.path_context.is_none() => {
refutability == PatternRefutability::Refutable
}
_ => return,
};
if refutable {

View File

@ -7,17 +7,23 @@ use rustc_hash::FxHashSet;
use syntax::{ast, AstNode};
use crate::{
context::PathCompletionContext, patterns::ImmediateLocation, CompletionContext, Completions,
context::{PathCompletionContext, PathKind},
patterns::ImmediateLocation,
CompletionContext, Completions,
};
pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
if ctx.is_path_disallowed() || ctx.has_impl_or_trait_prev_sibling() {
return;
}
let (path, use_tree_parent) = match &ctx.path_context {
Some(PathCompletionContext { qualifier: Some(qualifier), use_tree_parent, .. }) => {
(qualifier, *use_tree_parent)
}
let (path, 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),
use_tree_parent,
kind,
..
}) => (qualifier, use_tree_parent, kind),
_ => return,
};
@ -44,7 +50,11 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
return;
}
Some(ImmediateLocation::Visibility(_)) => {
_ => (),
}
match kind {
Some(PathKind::Vis { .. }) => {
if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
if let Some(current_module) = ctx.scope.module() {
if let Some(next) = current_module
@ -61,7 +71,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
return;
}
Some(ImmediateLocation::Attribute(_)) => {
Some(PathKind::Attr) => {
if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution {
for (name, def) in module.scope(ctx.db, context_module) {
let add_resolution = match def {
@ -76,37 +86,38 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
return;
}
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 ctx.in_use_tree() {
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`.
resolution.assoc_type_shorthand_candidates(ctx.db, |_, alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
}
// Add associated types on type parameters and `Self`.
resolution.assoc_type_shorthand_candidates(ctx.db, |_, 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);
for (name, def) in module_scope {
if ctx.in_use_tree() {
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() {

View File

@ -3,15 +3,23 @@
use hir::ScopeDef;
use syntax::{ast, AstNode};
use crate::{patterns::ImmediateLocation, CompletionContext, Completions};
use crate::{
context::{PathCompletionContext, PathKind},
patterns::ImmediateLocation,
CompletionContext, Completions,
};
pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
let _p = profile::span("complete_unqualified_path");
if ctx.is_path_disallowed() || !ctx.is_trivial_path() || ctx.has_impl_or_trait_prev_sibling() {
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,
_ => return,
};
if ctx.in_use_tree() {
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| {
@ -25,8 +33,25 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
}
["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, res| {
let add_resolution = match res {
ScopeDef::MacroDef(mac) => mac.is_attr(),
ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
_ => false,
};
if add_resolution {
acc.add_resolution(ctx, name, &res);
}
});
return;
}
_ => (),
}
match &ctx.completion_location {
Some(ImmediateLocation::Visibility(_)) => return,
Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => {
// only show macros in {Assoc}ItemList
ctx.process_all_names(&mut |name, res| {
@ -56,19 +81,6 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
});
return;
}
Some(ImmediateLocation::Attribute(_)) => {
ctx.process_all_names(&mut |name, res| {
let add_resolution = match res {
ScopeDef::MacroDef(mac) => mac.is_attr(),
ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
_ => false,
};
if add_resolution {
acc.add_resolution(ctx, name, &res);
}
});
return;
}
_ => (),
}

View File

@ -35,16 +35,23 @@ pub(super) enum PathKind {
Expr,
Type,
Attr,
Mac,
Pat,
Vis { has_in_token: bool },
Use,
}
#[derive(Debug)]
pub(crate) struct PathCompletionContext {
/// If this is a call with () already there
call_kind: Option<CallKind>,
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,
pub(super) kind: Option<PathKind>,
@ -70,13 +77,6 @@ pub(super) enum LifetimeContext {
LabelDef,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum CallKind {
Pat,
Mac,
Expr,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum ParamKind {
Function,
@ -206,13 +206,6 @@ impl<'a> CompletionContext<'a> {
)
}
pub(crate) fn in_use_tree(&self) -> bool {
matches!(
self.completion_location,
Some(ImmediateLocation::Use | ImmediateLocation::UseTree)
)
}
pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool {
matches!(
self.prev_sibling,
@ -257,8 +250,8 @@ impl<'a> CompletionContext<'a> {
matches!(self.path_context, Some(PathCompletionContext { kind: Some(PathKind::Type), .. }))
}
pub(crate) fn path_call_kind(&self) -> Option<CallKind> {
self.path_context.as_ref().and_then(|it| it.call_kind)
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 {
@ -673,7 +666,12 @@ impl<'a> CompletionContext<'a> {
Self::classify_lifetime(&self.sema, original_file, lifetime, offset);
}
ast::NameLike::NameRef(name_ref) => {
self.path_context = Self::classify_name_ref(&self.sema, original_file, name_ref);
if let Some((path_ctx, pat_ctx)) =
Self::classify_name_ref(&self.sema, original_file, name_ref)
{
self.path_context = Some(path_ctx);
self.pattern_ctx = pat_ctx;
}
}
ast::NameLike::Name(name) => {
self.pattern_ctx = Self::classify_name(&self.sema, name);
@ -716,83 +714,61 @@ impl<'a> CompletionContext<'a> {
if !bind_pat.is_simple_ident() {
return None;
}
let mut is_param = None;
let (refutability, has_type_ascription) = bind_pat
.syntax()
.ancestors()
.skip_while(|it| ast::Pat::can_cast(it.kind()))
.next()
.map_or((PatternRefutability::Irrefutable, false), |node| {
let refutability = match_ast! {
match node {
ast::LetStmt(let_) => return (PatternRefutability::Irrefutable, let_.ty().is_some()),
ast::Param(param) => {
let is_closure_param = param
.syntax()
.ancestors()
.nth(2)
.and_then(ast::ClosureExpr::cast)
.is_some();
is_param = Some(if is_closure_param {
ParamKind::Closure
} else {
ParamKind::Function
});
return (PatternRefutability::Irrefutable, param.ty().is_some())
},
ast::MatchArm(__) => PatternRefutability::Refutable,
ast::Condition(__) => PatternRefutability::Refutable,
ast::ForExpr(__) => PatternRefutability::Irrefutable,
_ => PatternRefutability::Irrefutable,
}
};
(refutability, false)
});
Some(PatternContext { refutability, is_param, has_type_ascription })
Some(pattern_context_for(bind_pat.into()))
}
fn classify_name_ref(
_sema: &Semantics<RootDatabase>,
original_file: &SyntaxNode,
name_ref: ast::NameRef,
) -> Option<PathCompletionContext> {
) -> Option<(PathCompletionContext, Option<PatternContext>)> {
let parent = name_ref.syntax().parent()?;
let segment = ast::PathSegment::cast(parent)?;
let path = segment.parent_path();
let mut path_ctx = PathCompletionContext {
call_kind: None,
has_call_parens: false,
is_trivial_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());
let path = segment.parent_path();
if let Some(p) = path.syntax().parent() {
path_ctx.call_kind = match_ast! {
match p {
ast::PathExpr(it) => it.syntax().parent().and_then(ast::CallExpr::cast).map(|_| CallKind::Expr),
ast::MacroCall(it) => it.excl_token().and(Some(CallKind::Mac)),
ast::TupleStructPat(_it) => Some(CallKind::Pat),
_ => None
}
};
}
if let Some(parent) = path.syntax().parent() {
path_ctx.kind = match_ast! {
match parent {
path_ctx.kind = path.syntax().ancestors().find_map(|it| {
match_ast! {
match it {
ast::PathType(_it) => Some(PathKind::Type),
ast::PathExpr(_it) => Some(PathKind::Expr),
ast::PathExpr(it) => {
path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
Some(PathKind::Expr)
},
ast::TupleStructPat(it) => {
path_ctx.has_call_parens = true;
pat_ctx = Some(pattern_context_for(it.into()));
Some(PathKind::Pat)
},
ast::RecordPat(it) => {
pat_ctx = Some(pattern_context_for(it.into()));
Some(PathKind::Pat)
},
ast::PathPat(it) => {
pat_ctx = Some(pattern_context_for(it.into()));
Some(PathKind::Pat)
},
ast::MacroCall(it) => it.excl_token().and(Some(PathKind::Mac)),
ast::Meta(_it) => Some(PathKind::Attr),
ast::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }),
ast::UseTree(_it) => Some(PathKind::Use),
_ => None,
}
};
}
}
});
path_ctx.has_type_args = segment.generic_arg_list().is_some();
if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
@ -806,12 +782,12 @@ impl<'a> CompletionContext<'a> {
)
})
.map(|it| it.parent_path());
return Some(path_ctx);
return Some((path_ctx, pat_ctx));
}
if let Some(segment) = path.segment() {
if segment.coloncolon_token().is_some() {
return Some(path_ctx);
return Some((path_ctx, pat_ctx));
}
}
@ -835,10 +811,46 @@ impl<'a> CompletionContext<'a> {
None
})
.unwrap_or(false);
Some(path_ctx)
Some((path_ctx, pat_ctx))
}
}
fn pattern_context_for(pat: ast::Pat) -> PatternContext {
let mut is_param = None;
let (refutability, has_type_ascription) =
pat
.syntax()
.ancestors()
.skip_while(|it| ast::Pat::can_cast(it.kind()))
.next()
.map_or((PatternRefutability::Irrefutable, false), |node| {
let refutability = match_ast! {
match node {
ast::LetStmt(let_) => return (PatternRefutability::Irrefutable, let_.ty().is_some()),
ast::Param(param) => {
let is_closure_param = param
.syntax()
.ancestors()
.nth(2)
.and_then(ast::ClosureExpr::cast)
.is_some();
is_param = Some(if is_closure_param {
ParamKind::Closure
} else {
ParamKind::Function
});
return (PatternRefutability::Irrefutable, param.ty().is_some())
},
ast::MatchArm(__) => PatternRefutability::Refutable,
ast::Condition(__) => PatternRefutability::Refutable,
ast::ForExpr(__) => PatternRefutability::Irrefutable,
_ => PatternRefutability::Irrefutable,
}
};
(refutability, false)
});
PatternContext { refutability, is_param, has_type_ascription }
}
fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
syntax.covering_element(range).ancestors().find_map(N::cast)
}

View File

@ -33,8 +33,6 @@ pub(crate) enum ImmediatePrevSibling {
/// from which file the nodes are.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ImmediateLocation {
Use,
UseTree,
Rename,
Impl,
Trait,
@ -47,10 +45,7 @@ pub(crate) enum ImmediateLocation {
TypeBound,
Variant,
/// Fake file ast node
Attribute(ast::Attr),
/// Fake file ast node
ModDeclaration(ast::Module),
Visibility(ast::Visibility),
/// Original file ast node
MethodCall {
receiver: Option<ast::Expr>,
@ -206,9 +201,6 @@ pub(crate) fn determine_location(
let res = match_ast! {
match parent {
ast::IdentPat(_it) => ImmediateLocation::IdentPat,
ast::Use(_it) => ImmediateLocation::Use,
ast::UseTree(_it) => ImmediateLocation::UseTree,
ast::UseTreeList(_it) => ImmediateLocation::UseTree,
ast::Rename(_it) => ImmediateLocation::Rename,
ast::StmtList(_it) => ImmediateLocation::StmtList,
ast::SourceFile(_it) => ImmediateLocation::ItemList,
@ -242,7 +234,6 @@ pub(crate) fn determine_location(
return None;
}
},
ast::Attr(it) => ImmediateLocation::Attribute(it),
ast::FieldExpr(it) => {
let receiver = it
.expr()
@ -268,8 +259,6 @@ pub(crate) fn determine_location(
.and_then(|r| find_node_with_range(original_file, r)),
has_parens: it.arg_list().map_or(false, |it| it.l_paren_token().is_some())
},
ast::Visibility(it) => it.pub_token()
.and_then(|t| (t.text_range().end() < offset).then(|| ImmediateLocation::Visibility(it)))?,
_ => return None,
}
};
@ -417,14 +406,6 @@ mod tests {
check_location(r"impl A { fn f$0 }", None);
}
#[test]
fn test_use_loc() {
check_location(r"use f$0", ImmediateLocation::Use);
check_location(r"use f$0;", ImmediateLocation::Use);
check_location(r"use f::{f$0}", ImmediateLocation::UseTree);
check_location(r"use {f$0}", ImmediateLocation::UseTree);
}
#[test]
fn test_record_field_loc() {
check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);

View File

@ -4,7 +4,7 @@ use either::Either;
use itertools::Itertools;
use syntax::ast::{self, HasName};
use crate::{context::CallKind, item::Builder, patterns::ImmediateLocation, CompletionContext};
use crate::{context::PathKind, item::Builder, patterns::ImmediateLocation, CompletionContext};
#[derive(Debug)]
pub(super) enum Params {
@ -30,11 +30,11 @@ impl Builder {
if !ctx.config.add_call_parenthesis {
return false;
}
if ctx.in_use_tree() {
if let Some(PathKind::Use) = ctx.path_kind() {
cov_mark::hit!(no_parens_in_use_item);
return false;
}
if matches!(ctx.path_call_kind(), Some(CallKind::Expr | CallKind::Pat))
if matches!(ctx.path_kind(), Some(PathKind::Expr | PathKind::Pat) if ctx.path_is_call())
| matches!(
ctx.completion_location,
Some(ImmediateLocation::MethodCall { has_parens: true, .. })

View File

@ -9,7 +9,7 @@ use syntax::{
};
use crate::{
context::CallKind,
context::PathKind,
item::{CompletionItem, ImportEdit},
render::RenderContext,
};
@ -67,9 +67,8 @@ impl<'a> MacroRender<'a> {
}
let needs_bang = self.macro_.is_fn_like()
&& !(self.ctx.completion.in_use_tree()
|| matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac)));
let has_parens = self.ctx.completion.path_call_kind().is_some();
&& !matches!(self.ctx.completion.path_kind(), Some(PathKind::Mac | PathKind::Use));
let has_parens = self.ctx.completion.path_is_call();
match self.ctx.snippet_cap() {
Some(cap) if needs_bang && !has_parens => {
@ -92,8 +91,7 @@ impl<'a> MacroRender<'a> {
}
fn needs_bang(&self) -> bool {
!self.ctx.completion.in_use_tree()
&& !matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac))
!matches!(self.ctx.completion.path_kind(), Some(PathKind::Mac | PathKind::Use))
}
fn label(&self) -> SmolStr {

View File

@ -1000,6 +1000,19 @@ fn function() {
);
}
#[test]
fn flyimport_item_name() {
check(
r#"
mod module {
pub struct Struct;
}
struct Str$0
"#,
expect![[r#""#]],
);
}
#[test]
fn flyimport_rename() {
check(