mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 05:38:46 +00:00
907 lines
29 KiB
Rust
907 lines
29 KiB
Rust
//! See [`CompletionContext`] structure.
|
|
|
|
mod analysis;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
use std::{iter, ops::ControlFlow};
|
|
|
|
use hir::{
|
|
HasAttrs, Local, ModPath, ModuleDef, ModuleSource, Name, PathResolution, ScopeDef, Semantics,
|
|
SemanticsScope, Symbol, Type, TypeInfo,
|
|
};
|
|
use ide_db::{
|
|
base_db::SourceDatabase, famous_defs::FamousDefs, helpers::is_editable_crate, FilePosition,
|
|
FxHashMap, FxHashSet, RootDatabase,
|
|
};
|
|
use syntax::{
|
|
ast::{self, AttrKind, NameOrNameRef},
|
|
match_ast, AstNode, Edition, SmolStr,
|
|
SyntaxKind::{self, *},
|
|
SyntaxToken, TextRange, TextSize, T,
|
|
};
|
|
|
|
use crate::{
|
|
config::AutoImportExclusionType,
|
|
context::analysis::{expand_and_analyze, AnalysisResult},
|
|
CompletionConfig,
|
|
};
|
|
|
|
const COMPLETION_MARKER: &str = "raCompletionMarker";
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) enum PatternRefutability {
|
|
Refutable,
|
|
Irrefutable,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum Visible {
|
|
Yes,
|
|
Editable,
|
|
No,
|
|
}
|
|
|
|
/// Existing qualifiers for the thing we are currently completing.
|
|
#[derive(Debug, Default)]
|
|
pub(crate) struct QualifierCtx {
|
|
// TODO: Add try_tok and default_tok
|
|
pub(crate) async_tok: Option<SyntaxToken>,
|
|
pub(crate) unsafe_tok: Option<SyntaxToken>,
|
|
pub(crate) safe_tok: Option<SyntaxToken>,
|
|
pub(crate) vis_node: Option<ast::Visibility>,
|
|
}
|
|
|
|
impl QualifierCtx {
|
|
pub(crate) fn none(&self) -> bool {
|
|
self.async_tok.is_none()
|
|
&& self.unsafe_tok.is_none()
|
|
&& self.safe_tok.is_none()
|
|
&& self.vis_node.is_none()
|
|
}
|
|
}
|
|
|
|
/// The state of the path we are currently completing.
|
|
#[derive(Debug)]
|
|
pub(crate) struct PathCompletionCtx {
|
|
/// If this is a call with () already there (or {} in case of record patterns)
|
|
pub(crate) has_call_parens: bool,
|
|
/// If this has a macro call bang !
|
|
pub(crate) has_macro_bang: bool,
|
|
/// The qualifier of the current path.
|
|
pub(crate) qualified: Qualified,
|
|
/// The parent of the path we are completing.
|
|
pub(crate) parent: Option<ast::Path>,
|
|
#[allow(dead_code)]
|
|
/// The path of which we are completing the segment
|
|
pub(crate) path: ast::Path,
|
|
/// The path of which we are completing the segment in the original file
|
|
pub(crate) original_path: Option<ast::Path>,
|
|
pub(crate) kind: PathKind,
|
|
/// Whether the path segment has type args or not.
|
|
pub(crate) has_type_args: bool,
|
|
/// Whether the qualifier comes from a use tree parent or not
|
|
pub(crate) use_tree_parent: bool,
|
|
}
|
|
|
|
impl PathCompletionCtx {
|
|
pub(crate) fn is_trivial_path(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
PathCompletionCtx {
|
|
has_call_parens: false,
|
|
has_macro_bang: false,
|
|
qualified: Qualified::No,
|
|
parent: None,
|
|
has_type_args: false,
|
|
..
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
/// The kind of path we are completing right now.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub(crate) enum PathKind {
|
|
Expr {
|
|
expr_ctx: PathExprCtx,
|
|
},
|
|
Type {
|
|
location: TypeLocation,
|
|
},
|
|
Attr {
|
|
attr_ctx: AttrCtx,
|
|
},
|
|
Derive {
|
|
existing_derives: ExistingDerives,
|
|
},
|
|
/// Path in item position, that is inside an (Assoc)ItemList
|
|
Item {
|
|
kind: ItemListKind,
|
|
},
|
|
Pat {
|
|
pat_ctx: PatternContext,
|
|
},
|
|
Vis {
|
|
has_in_token: bool,
|
|
},
|
|
Use,
|
|
}
|
|
|
|
pub(crate) type ExistingDerives = FxHashSet<hir::Macro>;
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub(crate) struct AttrCtx {
|
|
pub(crate) kind: AttrKind,
|
|
pub(crate) annotated_item_kind: Option<SyntaxKind>,
|
|
pub(crate) derive_helpers: Vec<(Symbol, Symbol)>,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub(crate) struct PathExprCtx {
|
|
pub(crate) in_block_expr: bool,
|
|
pub(crate) in_breakable: BreakableKind,
|
|
pub(crate) after_if_expr: bool,
|
|
/// Whether this expression is the direct condition of an if or while expression
|
|
pub(crate) in_condition: bool,
|
|
pub(crate) incomplete_let: bool,
|
|
pub(crate) ref_expr_parent: Option<ast::RefExpr>,
|
|
/// The surrounding RecordExpression we are completing a functional update
|
|
pub(crate) is_func_update: Option<ast::RecordExpr>,
|
|
pub(crate) self_param: Option<hir::SelfParam>,
|
|
pub(crate) innermost_ret_ty: Option<hir::Type>,
|
|
pub(crate) impl_: Option<ast::Impl>,
|
|
/// Whether this expression occurs in match arm guard position: before the
|
|
/// fat arrow token
|
|
pub(crate) in_match_guard: bool,
|
|
}
|
|
|
|
/// Original file ast nodes
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) enum TypeLocation {
|
|
TupleField,
|
|
TypeAscription(TypeAscriptionTarget),
|
|
/// Generic argument position e.g. `Foo<$0>`
|
|
GenericArg {
|
|
/// The generic argument list containing the generic arg
|
|
args: Option<ast::GenericArgList>,
|
|
/// `Some(trait_)` if `trait_` is being instantiated with `args`
|
|
of_trait: Option<hir::Trait>,
|
|
/// The generic parameter being filled in by the generic arg
|
|
corresponding_param: Option<ast::GenericParam>,
|
|
},
|
|
/// Associated type equality constraint e.g. `Foo<Bar = $0>`
|
|
AssocTypeEq,
|
|
/// Associated constant equality constraint e.g. `Foo<X = $0>`
|
|
AssocConstEq,
|
|
TypeBound,
|
|
ImplTarget,
|
|
ImplTrait,
|
|
Other,
|
|
}
|
|
|
|
impl TypeLocation {
|
|
pub(crate) fn complete_lifetimes(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
TypeLocation::GenericArg {
|
|
corresponding_param: Some(ast::GenericParam::LifetimeParam(_)),
|
|
..
|
|
}
|
|
)
|
|
}
|
|
|
|
pub(crate) fn complete_consts(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
TypeLocation::GenericArg {
|
|
corresponding_param: Some(ast::GenericParam::ConstParam(_)),
|
|
..
|
|
} | TypeLocation::AssocConstEq
|
|
)
|
|
}
|
|
|
|
pub(crate) fn complete_types(&self) -> bool {
|
|
match self {
|
|
TypeLocation::GenericArg { corresponding_param: Some(param), .. } => {
|
|
matches!(param, ast::GenericParam::TypeParam(_))
|
|
}
|
|
TypeLocation::AssocConstEq => false,
|
|
TypeLocation::AssocTypeEq => true,
|
|
TypeLocation::ImplTrait => false,
|
|
_ => true,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn complete_self_type(&self) -> bool {
|
|
self.complete_types() && !matches!(self, TypeLocation::ImplTarget | TypeLocation::ImplTrait)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) enum TypeAscriptionTarget {
|
|
Let(Option<ast::Pat>),
|
|
FnParam(Option<ast::Pat>),
|
|
RetType(Option<ast::Expr>),
|
|
Const(Option<ast::Expr>),
|
|
}
|
|
|
|
/// The kind of item list a [`PathKind::Item`] belongs to.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub(crate) enum ItemListKind {
|
|
SourceFile,
|
|
Module,
|
|
Impl,
|
|
TraitImpl(Option<ast::Impl>),
|
|
Trait,
|
|
ExternBlock { is_unsafe: bool },
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum Qualified {
|
|
No,
|
|
With {
|
|
path: ast::Path,
|
|
resolution: Option<PathResolution>,
|
|
/// How many `super` segments are present in the path
|
|
///
|
|
/// This would be None, if path is not solely made of
|
|
/// `super` segments, e.g.
|
|
///
|
|
/// ```rust
|
|
/// use super::foo;
|
|
/// ```
|
|
///
|
|
/// Otherwise it should be Some(count of `super`)
|
|
super_chain_len: Option<usize>,
|
|
},
|
|
/// <_>::
|
|
TypeAnchor {
|
|
ty: Option<hir::Type>,
|
|
trait_: Option<hir::Trait>,
|
|
},
|
|
/// Whether the path is an absolute path
|
|
Absolute,
|
|
}
|
|
|
|
/// The state of the pattern we are completing.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub(crate) struct PatternContext {
|
|
pub(crate) refutability: PatternRefutability,
|
|
pub(crate) param_ctx: Option<ParamContext>,
|
|
pub(crate) has_type_ascription: bool,
|
|
pub(crate) should_suggest_name: bool,
|
|
pub(crate) parent_pat: Option<ast::Pat>,
|
|
pub(crate) ref_token: Option<SyntaxToken>,
|
|
pub(crate) mut_token: Option<SyntaxToken>,
|
|
/// The record pattern this name or ref is a field of
|
|
pub(crate) record_pat: Option<ast::RecordPat>,
|
|
pub(crate) impl_: Option<ast::Impl>,
|
|
/// List of missing variants in a match expr
|
|
pub(crate) missing_variants: Vec<hir::Variant>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub(crate) struct ParamContext {
|
|
pub(crate) param_list: ast::ParamList,
|
|
pub(crate) param: ast::Param,
|
|
pub(crate) kind: ParamKind,
|
|
}
|
|
|
|
/// The state of the lifetime we are completing.
|
|
#[derive(Debug)]
|
|
pub(crate) struct LifetimeContext {
|
|
pub(crate) kind: LifetimeKind,
|
|
}
|
|
|
|
/// The kind of lifetime we are completing.
|
|
#[derive(Debug)]
|
|
pub(crate) enum LifetimeKind {
|
|
LifetimeParam,
|
|
Lifetime { in_lifetime_param_bound: bool, def: Option<hir::GenericDef> },
|
|
LabelRef,
|
|
LabelDef,
|
|
}
|
|
|
|
/// The state of the name we are completing.
|
|
#[derive(Debug)]
|
|
pub(crate) struct NameContext {
|
|
#[allow(dead_code)]
|
|
pub(crate) name: Option<ast::Name>,
|
|
pub(crate) kind: NameKind,
|
|
}
|
|
|
|
/// The kind of the name we are completing.
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
pub(crate) enum NameKind {
|
|
Const,
|
|
ConstParam,
|
|
Enum,
|
|
Function,
|
|
IdentPat(PatternContext),
|
|
MacroDef,
|
|
MacroRules,
|
|
/// Fake node
|
|
Module(ast::Module),
|
|
RecordField,
|
|
Rename,
|
|
SelfParam,
|
|
Static,
|
|
Struct,
|
|
Trait,
|
|
TypeAlias,
|
|
TypeParam,
|
|
Union,
|
|
Variant,
|
|
}
|
|
|
|
/// The state of the NameRef we are completing.
|
|
#[derive(Debug)]
|
|
pub(crate) struct NameRefContext {
|
|
/// NameRef syntax in the original file
|
|
pub(crate) nameref: Option<ast::NameRef>,
|
|
pub(crate) kind: NameRefKind,
|
|
}
|
|
|
|
/// The kind of the NameRef we are completing.
|
|
#[derive(Debug)]
|
|
pub(crate) enum NameRefKind {
|
|
Path(PathCompletionCtx),
|
|
DotAccess(DotAccess),
|
|
/// Position where we are only interested in keyword completions
|
|
Keyword(ast::Item),
|
|
/// The record expression this nameref is a field of and whether a dot precedes the completion identifier.
|
|
RecordExpr {
|
|
dot_prefix: bool,
|
|
expr: ast::RecordExpr,
|
|
},
|
|
Pattern(PatternContext),
|
|
ExternCrate,
|
|
}
|
|
|
|
/// The identifier we are currently completing.
|
|
#[derive(Debug)]
|
|
pub(crate) enum CompletionAnalysis {
|
|
Name(NameContext),
|
|
NameRef(NameRefContext),
|
|
Lifetime(LifetimeContext),
|
|
/// The string the cursor is currently inside
|
|
String {
|
|
/// original token
|
|
original: ast::String,
|
|
/// fake token
|
|
expanded: Option<ast::String>,
|
|
},
|
|
/// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)`
|
|
UnexpandedAttrTT {
|
|
colon_prefix: bool,
|
|
fake_attribute_under_caret: Option<ast::Attr>,
|
|
extern_crate: Option<ast::ExternCrate>,
|
|
},
|
|
}
|
|
|
|
/// Information about the field or method access we are completing.
|
|
#[derive(Debug)]
|
|
pub(crate) struct DotAccess {
|
|
pub(crate) receiver: Option<ast::Expr>,
|
|
pub(crate) receiver_ty: Option<TypeInfo>,
|
|
pub(crate) kind: DotAccessKind,
|
|
pub(crate) ctx: DotAccessExprCtx,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum DotAccessKind {
|
|
Field {
|
|
/// True if the receiver is an integer and there is no ident in the original file after it yet
|
|
/// like `0.$0`
|
|
receiver_is_ambiguous_float_literal: bool,
|
|
},
|
|
Method {
|
|
has_parens: bool,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub(crate) struct DotAccessExprCtx {
|
|
pub(crate) in_block_expr: bool,
|
|
pub(crate) in_breakable: BreakableKind,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) enum BreakableKind {
|
|
None,
|
|
Loop,
|
|
For,
|
|
While,
|
|
Block,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub(crate) enum ParamKind {
|
|
Function(ast::Fn),
|
|
Closure(ast::ClosureExpr),
|
|
}
|
|
|
|
/// `CompletionContext` is created early during completion to figure out, where
|
|
/// exactly is the cursor, syntax-wise.
|
|
#[derive(Debug)]
|
|
pub(crate) struct CompletionContext<'a> {
|
|
pub(crate) sema: Semantics<'a, RootDatabase>,
|
|
pub(crate) scope: SemanticsScope<'a>,
|
|
pub(crate) db: &'a RootDatabase,
|
|
pub(crate) config: &'a CompletionConfig<'a>,
|
|
pub(crate) position: FilePosition,
|
|
|
|
/// The token before the cursor, in the original file.
|
|
pub(crate) original_token: SyntaxToken,
|
|
/// The token before the cursor, in the macro-expanded file.
|
|
pub(crate) token: SyntaxToken,
|
|
/// The crate of the current file.
|
|
pub(crate) krate: hir::Crate,
|
|
/// The module of the `scope`.
|
|
pub(crate) module: hir::Module,
|
|
/// Whether nightly toolchain is used. Cached since this is looked up a lot.
|
|
is_nightly: bool,
|
|
pub(crate) edition: Edition,
|
|
|
|
/// The expected name of what we are completing.
|
|
/// This is usually the parameter name of the function argument we are completing.
|
|
pub(crate) expected_name: Option<NameOrNameRef>,
|
|
/// The expected type of what we are completing.
|
|
pub(crate) expected_type: Option<Type>,
|
|
|
|
pub(crate) qualifier_ctx: QualifierCtx,
|
|
|
|
pub(crate) locals: FxHashMap<Name, Local>,
|
|
|
|
/// The module depth of the current module of the cursor position.
|
|
/// - crate-root
|
|
/// - mod foo
|
|
/// - mod bar
|
|
///
|
|
/// Here depth will be 2
|
|
pub(crate) depth_from_crate_root: usize,
|
|
|
|
/// Traits whose methods will be excluded from flyimport. Flyimport should not suggest
|
|
/// importing those traits.
|
|
///
|
|
/// Note the trait *themselves* are not excluded, only their methods are.
|
|
pub(crate) exclude_flyimport: FxHashMap<ModuleDef, AutoImportExclusionType>,
|
|
/// Traits whose methods should always be excluded, even when in scope (compare `exclude_flyimport_traits`).
|
|
/// They will *not* be excluded, however, if they are available as a generic bound.
|
|
///
|
|
/// Note the trait *themselves* are not excluded, only their methods are.
|
|
pub(crate) exclude_traits: FxHashSet<hir::Trait>,
|
|
|
|
/// Whether and how to complete semicolon for unit-returning functions.
|
|
pub(crate) complete_semicolon: CompleteSemicolon,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) enum CompleteSemicolon {
|
|
DoNotComplete,
|
|
CompleteSemi,
|
|
CompleteComma,
|
|
}
|
|
|
|
impl CompletionContext<'_> {
|
|
/// The range of the identifier that is being completed.
|
|
pub(crate) fn source_range(&self) -> TextRange {
|
|
let kind = self.original_token.kind();
|
|
match kind {
|
|
CHAR => {
|
|
// assume we are completing a lifetime but the user has only typed the '
|
|
cov_mark::hit!(completes_if_lifetime_without_idents);
|
|
TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
|
|
}
|
|
LIFETIME_IDENT | UNDERSCORE | INT_NUMBER => self.original_token.text_range(),
|
|
// We want to consider all keywords in all editions.
|
|
_ if kind.is_any_identifier() => self.original_token.text_range(),
|
|
_ => TextRange::empty(self.position.offset),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn famous_defs(&self) -> FamousDefs<'_, '_> {
|
|
FamousDefs(&self.sema, self.krate)
|
|
}
|
|
|
|
/// Checks if an item is visible and not `doc(hidden)` at the completion site.
|
|
pub(crate) fn def_is_visible(&self, item: &ScopeDef) -> Visible {
|
|
match item {
|
|
ScopeDef::ModuleDef(def) => match def {
|
|
hir::ModuleDef::Module(it) => self.is_visible(it),
|
|
hir::ModuleDef::Function(it) => self.is_visible(it),
|
|
hir::ModuleDef::Adt(it) => self.is_visible(it),
|
|
hir::ModuleDef::Variant(it) => self.is_visible(it),
|
|
hir::ModuleDef::Const(it) => self.is_visible(it),
|
|
hir::ModuleDef::Static(it) => self.is_visible(it),
|
|
hir::ModuleDef::Trait(it) => self.is_visible(it),
|
|
hir::ModuleDef::TraitAlias(it) => self.is_visible(it),
|
|
hir::ModuleDef::TypeAlias(it) => self.is_visible(it),
|
|
hir::ModuleDef::Macro(it) => self.is_visible(it),
|
|
hir::ModuleDef::BuiltinType(_) => Visible::Yes,
|
|
},
|
|
ScopeDef::GenericParam(_)
|
|
| ScopeDef::ImplSelfType(_)
|
|
| ScopeDef::AdtSelfType(_)
|
|
| ScopeDef::Local(_)
|
|
| ScopeDef::Label(_)
|
|
| ScopeDef::Unknown => Visible::Yes,
|
|
}
|
|
}
|
|
|
|
/// Checks if an item is visible and not `doc(hidden)` at the completion site.
|
|
pub(crate) fn is_visible<I>(&self, item: &I) -> Visible
|
|
where
|
|
I: hir::HasVisibility + hir::HasAttrs + hir::HasCrate + Copy,
|
|
{
|
|
let vis = item.visibility(self.db);
|
|
let attrs = item.attrs(self.db);
|
|
self.is_visible_impl(&vis, &attrs, item.krate(self.db))
|
|
}
|
|
|
|
pub(crate) fn doc_aliases<I>(&self, item: &I) -> Vec<SmolStr>
|
|
where
|
|
I: hir::HasAttrs + Copy,
|
|
{
|
|
let attrs = item.attrs(self.db);
|
|
attrs.doc_aliases().map(|it| it.as_str().into()).collect()
|
|
}
|
|
|
|
/// Check if an item is `#[doc(hidden)]`.
|
|
pub(crate) fn is_item_hidden(&self, item: &hir::ItemInNs) -> bool {
|
|
let attrs = item.attrs(self.db);
|
|
let krate = item.krate(self.db);
|
|
match (attrs, krate) {
|
|
(Some(attrs), Some(krate)) => self.is_doc_hidden(&attrs, krate),
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Checks whether this item should be listed in regards to stability. Returns `true` if we should.
|
|
pub(crate) fn check_stability(&self, attrs: Option<&hir::Attrs>) -> bool {
|
|
let Some(attrs) = attrs else {
|
|
return true;
|
|
};
|
|
!attrs.is_unstable() || self.is_nightly
|
|
}
|
|
|
|
/// Whether the given trait is an operator trait or not.
|
|
pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
|
|
match trait_.attrs(self.db).lang() {
|
|
Some(lang) => OP_TRAIT_LANG_NAMES.contains(&lang.as_str()),
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
/// Whether the given trait has `#[doc(notable_trait)]`
|
|
pub(crate) fn is_doc_notable_trait(&self, trait_: hir::Trait) -> bool {
|
|
trait_.attrs(self.db).has_doc_notable_trait()
|
|
}
|
|
|
|
/// Returns the traits in scope, with the [`Drop`] trait removed.
|
|
pub(crate) fn traits_in_scope(&self) -> hir::VisibleTraits {
|
|
let mut traits_in_scope = self.scope.visible_traits();
|
|
if let Some(drop) = self.famous_defs().core_ops_Drop() {
|
|
traits_in_scope.0.remove(&drop.into());
|
|
}
|
|
traits_in_scope
|
|
}
|
|
|
|
pub(crate) fn iterate_path_candidates(
|
|
&self,
|
|
ty: &hir::Type,
|
|
mut cb: impl FnMut(hir::AssocItem),
|
|
) {
|
|
let mut seen = FxHashSet::default();
|
|
ty.iterate_path_candidates(
|
|
self.db,
|
|
&self.scope,
|
|
&self.traits_in_scope(),
|
|
Some(self.module),
|
|
None,
|
|
|item| {
|
|
// We might iterate candidates of a trait multiple times here, so deduplicate
|
|
// them.
|
|
if seen.insert(item) {
|
|
cb(item)
|
|
}
|
|
None::<()>
|
|
},
|
|
);
|
|
}
|
|
|
|
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items and
|
|
/// passes all doc-aliases along, to funnel it into [`Completions::add_path_resolution`].
|
|
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef, Vec<SmolStr>)) {
|
|
let _p = tracing::info_span!("CompletionContext::process_all_names").entered();
|
|
self.scope.process_all_names(&mut |name, def| {
|
|
if self.is_scope_def_hidden(def) {
|
|
return;
|
|
}
|
|
let doc_aliases = self.doc_aliases_in_scope(def);
|
|
f(name, def, doc_aliases);
|
|
});
|
|
}
|
|
|
|
pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
|
|
let _p = tracing::info_span!("CompletionContext::process_all_names_raw").entered();
|
|
self.scope.process_all_names(f);
|
|
}
|
|
|
|
fn is_scope_def_hidden(&self, scope_def: ScopeDef) -> bool {
|
|
if let (Some(attrs), Some(krate)) = (scope_def.attrs(self.db), scope_def.krate(self.db)) {
|
|
return self.is_doc_hidden(&attrs, krate);
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn is_visible_impl(
|
|
&self,
|
|
vis: &hir::Visibility,
|
|
attrs: &hir::Attrs,
|
|
defining_crate: hir::Crate,
|
|
) -> Visible {
|
|
if !vis.is_visible_from(self.db, self.module.into()) {
|
|
if !self.config.enable_private_editable {
|
|
return Visible::No;
|
|
}
|
|
// If the definition location is editable, also show private items
|
|
return if is_editable_crate(defining_crate, self.db) {
|
|
Visible::Editable
|
|
} else {
|
|
Visible::No
|
|
};
|
|
}
|
|
|
|
if self.is_doc_hidden(attrs, defining_crate) {
|
|
Visible::No
|
|
} else {
|
|
Visible::Yes
|
|
}
|
|
}
|
|
|
|
fn is_doc_hidden(&self, attrs: &hir::Attrs, defining_crate: hir::Crate) -> bool {
|
|
// `doc(hidden)` items are only completed within the defining crate.
|
|
self.krate != defining_crate && attrs.has_doc_hidden()
|
|
}
|
|
|
|
pub(crate) fn doc_aliases_in_scope(&self, scope_def: ScopeDef) -> Vec<SmolStr> {
|
|
if let Some(attrs) = scope_def.attrs(self.db) {
|
|
attrs.doc_aliases().map(|it| it.as_str().into()).collect()
|
|
} else {
|
|
vec![]
|
|
}
|
|
}
|
|
}
|
|
|
|
// CompletionContext construction
|
|
impl<'a> CompletionContext<'a> {
|
|
pub(crate) fn new(
|
|
db: &'a RootDatabase,
|
|
position @ FilePosition { file_id, offset }: FilePosition,
|
|
config: &'a CompletionConfig<'a>,
|
|
) -> Option<(CompletionContext<'a>, CompletionAnalysis)> {
|
|
let _p = tracing::info_span!("CompletionContext::new").entered();
|
|
let sema = Semantics::new(db);
|
|
|
|
let file_id = sema.attach_first_edition(file_id)?;
|
|
let original_file = sema.parse(file_id);
|
|
|
|
// Insert a fake ident to get a valid parse tree. We will use this file
|
|
// to determine context, though the original_file will be used for
|
|
// actual completion.
|
|
let file_with_fake_ident = {
|
|
let parse = db.parse(file_id);
|
|
parse.reparse(TextRange::empty(offset), COMPLETION_MARKER, file_id.edition()).tree()
|
|
};
|
|
|
|
// always pick the token to the immediate left of the cursor, as that is what we are actually
|
|
// completing on
|
|
let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
|
|
|
|
// try to skip completions on path with invalid colons
|
|
// this approach works in normal path and inside token tree
|
|
if original_token.kind() == T![:] {
|
|
// return if no prev token before colon
|
|
let prev_token = original_token.prev_token()?;
|
|
|
|
// only has a single colon
|
|
if prev_token.kind() != T![:] {
|
|
return None;
|
|
}
|
|
|
|
// has 3 colon or 2 coloncolon in a row
|
|
// special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205
|
|
// and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751
|
|
if prev_token
|
|
.prev_token()
|
|
.map(|t| t.kind() == T![:] || t.kind() == T![::])
|
|
.unwrap_or(false)
|
|
{
|
|
return None;
|
|
}
|
|
}
|
|
|
|
let AnalysisResult {
|
|
analysis,
|
|
expected: (expected_type, expected_name),
|
|
qualifier_ctx,
|
|
token,
|
|
original_offset,
|
|
} = expand_and_analyze(
|
|
&sema,
|
|
original_file.syntax().clone(),
|
|
file_with_fake_ident.syntax().clone(),
|
|
offset,
|
|
&original_token,
|
|
)?;
|
|
|
|
// adjust for macro input, this still fails if there is no token written yet
|
|
let scope = sema.scope_at_offset(&token.parent()?, original_offset)?;
|
|
|
|
let krate = scope.krate();
|
|
let module = scope.module();
|
|
let edition = krate.edition(db);
|
|
|
|
let toolchain = db.toolchain_channel(krate.into());
|
|
// `toolchain == None` means we're in some detached files. Since we have no information on
|
|
// the toolchain being used, let's just allow unstable items to be listed.
|
|
let is_nightly = matches!(toolchain, Some(base_db::ReleaseChannel::Nightly) | None);
|
|
|
|
let mut locals = FxHashMap::default();
|
|
scope.process_all_names(&mut |name, scope| {
|
|
if let ScopeDef::Local(local) = scope {
|
|
// synthetic names currently leak out as we lack synthetic hygiene, so filter them
|
|
// out here
|
|
if name.as_str().starts_with('<') {
|
|
return;
|
|
}
|
|
locals.insert(name, local);
|
|
}
|
|
});
|
|
|
|
let depth_from_crate_root = iter::successors(Some(module), |m| m.parent(db))
|
|
// `BlockExpr` modules do not count towards module depth
|
|
.filter(|m| !matches!(m.definition_source(db).value, ModuleSource::BlockExpr(_)))
|
|
.count()
|
|
// exclude `m` itself
|
|
.saturating_sub(1);
|
|
|
|
let exclude_traits: FxHashSet<_> = config
|
|
.exclude_traits
|
|
.iter()
|
|
.filter_map(|path| {
|
|
scope
|
|
.resolve_mod_path(&ModPath::from_segments(
|
|
hir::PathKind::Plain,
|
|
path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
|
|
))
|
|
.find_map(|it| match it {
|
|
hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t),
|
|
_ => None,
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
let mut exclude_flyimport: FxHashMap<_, _> = config
|
|
.exclude_flyimport
|
|
.iter()
|
|
.flat_map(|(path, kind)| {
|
|
scope
|
|
.resolve_mod_path(&ModPath::from_segments(
|
|
hir::PathKind::Plain,
|
|
path.split("::").map(Symbol::intern).map(Name::new_symbol_root),
|
|
))
|
|
.map(|it| (it.into_module_def(), *kind))
|
|
})
|
|
.collect();
|
|
exclude_flyimport
|
|
.extend(exclude_traits.iter().map(|&t| (t.into(), AutoImportExclusionType::Always)));
|
|
|
|
let complete_semicolon = if config.add_semicolon_to_unit {
|
|
let inside_closure_ret = token.parent_ancestors().try_for_each(|ancestor| {
|
|
match_ast! {
|
|
match ancestor {
|
|
ast::BlockExpr(_) => ControlFlow::Break(false),
|
|
ast::ClosureExpr(_) => ControlFlow::Break(true),
|
|
_ => ControlFlow::Continue(())
|
|
}
|
|
}
|
|
});
|
|
|
|
if inside_closure_ret == ControlFlow::Break(true) {
|
|
CompleteSemicolon::DoNotComplete
|
|
} else {
|
|
let next_non_trivia_token =
|
|
std::iter::successors(token.next_token(), |it| it.next_token())
|
|
.find(|it| !it.kind().is_trivia());
|
|
let in_match_arm = token.parent_ancestors().try_for_each(|ancestor| {
|
|
if ast::MatchArm::can_cast(ancestor.kind()) {
|
|
ControlFlow::Break(true)
|
|
} else if matches!(
|
|
ancestor.kind(),
|
|
SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR
|
|
) {
|
|
ControlFlow::Break(false)
|
|
} else {
|
|
ControlFlow::Continue(())
|
|
}
|
|
});
|
|
// FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node.
|
|
let in_match_arm = match in_match_arm {
|
|
ControlFlow::Continue(()) => false,
|
|
ControlFlow::Break(it) => it,
|
|
};
|
|
let complete_token = if in_match_arm { T![,] } else { T![;] };
|
|
if next_non_trivia_token.map(|it| it.kind()) == Some(complete_token) {
|
|
CompleteSemicolon::DoNotComplete
|
|
} else if in_match_arm {
|
|
CompleteSemicolon::CompleteComma
|
|
} else {
|
|
CompleteSemicolon::CompleteSemi
|
|
}
|
|
}
|
|
} else {
|
|
CompleteSemicolon::DoNotComplete
|
|
};
|
|
|
|
let ctx = CompletionContext {
|
|
sema,
|
|
scope,
|
|
db,
|
|
config,
|
|
position,
|
|
original_token,
|
|
token,
|
|
krate,
|
|
module,
|
|
is_nightly,
|
|
edition,
|
|
expected_name,
|
|
expected_type,
|
|
qualifier_ctx,
|
|
locals,
|
|
depth_from_crate_root,
|
|
exclude_flyimport,
|
|
exclude_traits,
|
|
complete_semicolon,
|
|
};
|
|
Some((ctx, analysis))
|
|
}
|
|
}
|
|
|
|
const OP_TRAIT_LANG_NAMES: &[&str] = &[
|
|
"add_assign",
|
|
"add",
|
|
"bitand_assign",
|
|
"bitand",
|
|
"bitor_assign",
|
|
"bitor",
|
|
"bitxor_assign",
|
|
"bitxor",
|
|
"deref_mut",
|
|
"deref",
|
|
"div_assign",
|
|
"div",
|
|
"eq",
|
|
"fn_mut",
|
|
"fn_once",
|
|
"fn",
|
|
"index_mut",
|
|
"index",
|
|
"mul_assign",
|
|
"mul",
|
|
"neg",
|
|
"not",
|
|
"partial_ord",
|
|
"rem_assign",
|
|
"rem",
|
|
"shl_assign",
|
|
"shl",
|
|
"shr_assign",
|
|
"shr",
|
|
"sub",
|
|
];
|