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()
{
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![::]) {
}
};
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,
}
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) {
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");
}
-//! 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 {
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,
};
}
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
}
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 {
}
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() {
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| {
}
["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| {
});
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;
- }
_ => (),
}
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>,
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,
)
}
- 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,
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 {
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);
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) {
)
})
.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));
}
}
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)
}
/// from which file the nodes are.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ImmediateLocation {
- Use,
- UseTree,
Rename,
Impl,
Trait,
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>,
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,
return None;
}
},
- ast::Attr(it) => ImmediateLocation::Attribute(it),
ast::FieldExpr(it) => {
let receiver = it
.expr()
.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,
}
};
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);
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 {
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, .. })
};
use crate::{
- context::CallKind,
+ context::PathKind,
item::{CompletionItem, ImportEdit},
render::RenderContext,
};
}
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 => {
}
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 {
);
}
+#[test]
+fn flyimport_item_name() {
+ check(
+ r#"
+mod module {
+ pub struct Struct;
+}
+struct Str$0
+ "#,
+ expect![[r#""#]],
+ );
+}
+
#[test]
fn flyimport_rename() {
check(