rust-analyzer/crates/ide-completion/src/context.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

603 lines
17 KiB
Rust
Raw Normal View History

//! See `CompletionContext` structure.
mod analysis;
2022-06-17 13:19:09 +00:00
#[cfg(test)]
mod tests;
2021-07-23 17:57:16 +00:00
use base_db::SourceDatabaseExt;
use hir::{
HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
};
2021-05-26 19:09:27 +00:00
use ide_db::{
base_db::{FilePosition, SourceDatabase},
2022-03-06 18:01:30 +00:00
famous_defs::FamousDefs,
FxHashMap, FxHashSet, RootDatabase,
2021-05-26 19:09:27 +00:00
};
2020-08-12 16:26:51 +00:00
use syntax::{
ast::{self, AttrKind, NameOrNameRef},
AstNode,
2021-05-26 19:09:27 +00:00
SyntaxKind::{self, *},
SyntaxToken, TextRange, TextSize,
2019-01-08 19:33:36 +00:00
};
2020-08-12 15:03:06 +00:00
use text_edit::Indel;
2019-01-08 19:33:36 +00:00
use crate::CompletionConfig;
const COMPLETION_MARKER: &str = "intellijRulezz";
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum PatternRefutability {
Refutable,
Irrefutable,
}
2022-02-02 11:05:21 +00:00
pub(crate) enum Visible {
Yes,
Editable,
No,
}
/// Existing qualifiers for the thing we are currently completing.
2022-05-30 14:01:17 +00:00
#[derive(Debug, Default)]
pub(super) struct QualifierCtx {
pub(super) unsafe_tok: Option<SyntaxToken>,
pub(super) vis_node: Option<ast::Visibility>,
}
impl QualifierCtx {
pub(super) fn none(&self) -> bool {
self.unsafe_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(super) has_call_parens: bool,
/// If this has a macro call bang !
pub(super) has_macro_bang: bool,
/// The qualifier of the current path.
pub(super) qualified: Qualified,
/// The parent of the path we are completing.
pub(super) parent: Option<ast::Path>,
pub(super) kind: PathKind,
2021-06-08 14:50:10 +00:00
/// Whether the path segment has type args or not.
pub(super) has_type_args: bool,
}
impl PathCompletionCtx {
2022-06-03 17:51:08 +00:00
pub(super) 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(Clone, Debug, PartialEq, Eq)]
pub(super) enum PathKind {
Expr {
in_block_expr: bool,
in_loop_body: bool,
after_if_expr: bool,
2022-06-17 14:22:51 +00:00
/// Whether this expression is the direct condition of an if or while expression
in_condition: bool,
ref_expr_parent: Option<ast::RefExpr>,
is_func_update: Option<ast::RecordExpr>,
},
Type {
location: TypeLocation,
},
Attr {
kind: AttrKind,
annotated_item_kind: Option<SyntaxKind>,
},
Derive {
existing_derives: FxHashSet<hir::Macro>,
},
/// Path in item position, that is inside an (Assoc)ItemList
Item {
kind: ItemListKind,
},
Pat,
Vis {
has_in_token: bool,
},
Use,
}
/// Original file ast nodes
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum TypeLocation {
TupleField,
TypeAscription(TypeAscriptionTarget),
GenericArgList(Option<ast::GenericArgList>),
TypeBound,
2022-06-17 14:22:51 +00:00
ImplTarget,
ImplTrait,
Other,
}
#[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(Copy, Clone, Debug, PartialEq, Eq)]
pub(super) enum ItemListKind {
SourceFile,
Module,
Impl,
TraitImpl,
Trait,
ExternBlock,
}
#[derive(Debug)]
pub(super) enum Qualified {
No,
With(PathQualifierCtx),
/// Whether the path is an absolute path
Absolute,
}
/// The path qualifier state of the path we are completing.
#[derive(Debug)]
pub(crate) struct PathQualifierCtx {
2022-02-02 17:18:08 +00:00
pub(crate) path: ast::Path,
pub(crate) resolution: Option<PathResolution>,
/// Whether this path consists solely of `super` segments
2022-02-02 17:18:08 +00:00
pub(crate) is_super_chain: bool,
/// Whether the qualifier comes from a use tree parent or not
2022-02-02 17:18:08 +00:00
pub(crate) use_tree_parent: bool,
/// <_>
pub(crate) is_infer_qualifier: bool,
}
/// The state of the pattern we are completing.
2021-08-14 17:06:35 +00:00
#[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>,
/// The record pattern this name or ref is a field of
pub(super) record_pat: Option<ast::RecordPat>,
2021-08-14 17:06:35 +00:00
}
/// The state of the lifetime we are completing.
#[derive(Debug)]
pub(super) struct LifetimeContext {
pub(super) lifetime: Option<ast::Lifetime>,
pub(super) kind: LifetimeKind,
}
/// The kind of lifetime we are completing.
#[derive(Debug)]
2022-05-07 13:08:33 +00:00
pub(super) enum LifetimeKind {
LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
Lifetime,
LabelRef,
LabelDef,
}
/// The state of the name we are completing.
#[derive(Debug)]
2022-05-07 13:08:33 +00:00
pub(super) struct NameContext {
#[allow(dead_code)]
pub(super) name: Option<ast::Name>,
pub(super) kind: NameKind,
}
/// The kind of the name we are completing.
#[derive(Debug)]
#[allow(dead_code)]
pub(super) enum NameKind {
Const,
ConstParam,
Enum,
Function,
IdentPat,
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.
2022-05-07 11:46:43 +00:00
#[derive(Debug)]
pub(super) struct NameRefContext {
/// NameRef syntax in the original file
pub(super) nameref: Option<ast::NameRef>,
// FIXME: This shouldn't be an Option
2022-06-17 08:45:19 +00:00
pub(super) kind: Option<NameRefKind>,
}
/// The kind of the NameRef we are completing.
2022-06-17 08:45:19 +00:00
#[derive(Debug)]
pub(super) enum NameRefKind {
Path(PathCompletionCtx),
DotAccess(DotAccess),
2022-06-03 14:11:26 +00:00
/// Position where we are only interested in keyword completions
2022-06-17 08:45:19 +00:00
Keyword(ast::Item),
/// The record expression this nameref is a field of
2022-06-17 08:45:19 +00:00
RecordExpr(ast::RecordExpr),
2022-05-07 11:46:43 +00:00
}
/// The identifier we are currently completing.
#[derive(Debug)]
pub(super) enum IdentContext {
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 {
fake_attribute_under_caret: Option<ast::Attr>,
},
}
/// Information about the field or method access we are completing.
2022-05-07 11:46:43 +00:00
#[derive(Debug)]
pub(super) struct DotAccess {
pub(super) receiver: Option<ast::Expr>,
pub(super) receiver_ty: Option<TypeInfo>,
pub(super) kind: DotAccessKind,
}
#[derive(Debug)]
pub(super) enum DotAccessKind {
2022-05-07 11:46:43 +00:00
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(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ParamKind {
Function(ast::Fn),
Closure(ast::ClosureExpr),
}
2019-01-08 19:33:36 +00:00
/// `CompletionContext` is created early during completion to figure out, where
/// exactly is the cursor, syntax-wise.
#[derive(Debug)]
pub(crate) struct CompletionContext<'a> {
pub(super) sema: Semantics<'a, RootDatabase>,
2020-07-10 23:26:24 +00:00
pub(super) scope: SemanticsScope<'a>,
2020-02-06 11:52:32 +00:00
pub(super) db: &'a RootDatabase,
2020-03-31 14:02:55 +00:00
pub(super) config: &'a CompletionConfig,
2020-08-11 06:54:33 +00:00
pub(super) position: FilePosition,
2020-03-07 14:27:03 +00:00
/// The token before the cursor, in the original file.
pub(super) original_token: SyntaxToken,
/// The token before the cursor, in the macro-expanded file.
2019-07-19 09:56:47 +00:00
pub(super) token: SyntaxToken,
2021-10-11 19:49:39 +00:00
/// The crate of the current file.
pub(super) krate: hir::Crate,
2022-02-02 11:05:21 +00:00
/// The module of the `scope`.
pub(super) module: hir::Module,
/// The expected name of what we are completing.
/// This is usually the parameter name of the function argument we are completing.
pub(super) expected_name: Option<NameOrNameRef>,
/// The expected type of what we are completing.
pub(super) expected_type: Option<Type>,
/// The parent function of the cursor position if it exists.
pub(super) function_def: Option<ast::Fn>,
/// The parent impl of the cursor position if it exists.
2020-07-30 16:28:28 +00:00
pub(super) impl_def: Option<ast::Impl>,
/// Are we completing inside a let statement with a missing semicolon?
// FIXME: This should be part of PathKind::Expr
pub(super) incomplete_let: bool,
2021-06-07 17:06:03 +00:00
pub(super) previous_token: Option<SyntaxToken>,
pub(super) ident_ctx: IdentContext,
2021-08-14 17:06:35 +00:00
pub(super) pattern_ctx: Option<PatternContext>,
2022-05-30 14:01:17 +00:00
pub(super) qualifier_ctx: QualifierCtx,
2022-03-14 19:36:35 +00:00
pub(super) locals: FxHashMap<Name, Local>,
2019-01-08 19:33:36 +00:00
}
2019-01-08 19:33:36 +00:00
impl<'a> CompletionContext<'a> {
/// The range of the identifier that is being completed.
pub(crate) fn source_range(&self) -> TextRange {
2020-03-07 14:27:03 +00:00
// check kind of macro-expanded token, but use range of original token
let kind = self.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))
}
IDENT | LIFETIME_IDENT | UNDERSCORE => self.original_token.text_range(),
_ if kind.is_keyword() => self.original_token.text_range(),
_ => TextRange::empty(self.position.offset),
2019-01-20 05:34:16 +00:00
}
}
2021-05-26 19:09:27 +00:00
pub(crate) fn previous_token_is(&self, kind: SyntaxKind) -> bool {
self.previous_token.as_ref().map_or(false, |tok| tok.kind() == kind)
}
pub(crate) fn famous_defs(&self) -> FamousDefs {
FamousDefs(&self.sema, self.krate)
}
pub(super) fn nameref_ctx(&self) -> Option<&NameRefContext> {
match &self.ident_ctx {
IdentContext::NameRef(it) => Some(it),
_ => None,
}
}
pub(super) fn name_ctx(&self) -> Option<&NameContext> {
match &self.ident_ctx {
IdentContext::Name(it) => Some(it),
_ => None,
}
}
pub(super) fn lifetime_ctx(&self) -> Option<&LifetimeContext> {
match &self.ident_ctx {
IdentContext::Lifetime(it) => Some(it),
_ => None,
}
}
pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> {
match self.nameref_ctx() {
2022-06-17 08:45:19 +00:00
Some(NameRefContext {
kind: Some(NameRefKind::DotAccess(DotAccess { receiver, .. })),
..
}) => receiver.as_ref(),
_ => None,
}
}
pub(crate) fn has_dot_receiver(&self) -> bool {
2022-05-07 11:46:43 +00:00
self.dot_receiver().is_some()
}
2022-05-07 11:46:43 +00:00
pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> {
2022-06-17 08:45:19 +00:00
self.nameref_ctx().and_then(|ctx| match &ctx.kind {
Some(NameRefKind::Path(path)) => Some(path),
_ => None,
})
2022-05-07 11:46:43 +00:00
}
pub(crate) fn path_qual(&self) -> Option<&ast::Path> {
self.path_context().and_then(|it| match &it.qualified {
Qualified::With(it) => Some(&it.path),
_ => None,
})
}
/// 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,
{
self.is_visible_impl(&item.visibility(self.db), &item.attrs(self.db), item.krate(self.db))
}
2021-12-21 13:07:48 +00:00
pub(crate) 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
}
2021-10-11 19:49:39 +00:00
/// 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,
}
}
/// 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,
}
}
/// 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
}
/// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
2021-12-21 17:25:50 +00:00
let _p = profile::span("CompletionContext::process_all_names");
self.scope.process_all_names(&mut |name, def| {
2021-12-21 13:07:48 +00:00
if self.is_scope_def_hidden(def) {
return;
}
f(name, def);
2022-03-14 19:36:35 +00:00
});
}
pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
let _p = profile::span("CompletionContext::process_all_names_raw");
self.scope.process_all_names(&mut |name, def| f(name, def));
}
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;
}
2021-07-23 17:57:16 +00:00
// If the definition location is editable, also show private items
let root_file = defining_crate.root_file(self.db);
let source_root_id = self.db.file_source_root(root_file);
let is_editable = !self.db.source_root(source_root_id).is_library;
return if is_editable { 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()
}
2021-10-17 09:15:56 +00:00
}
// CompletionContext construction
impl<'a> CompletionContext<'a> {
pub(super) fn new(
db: &'a RootDatabase,
2021-11-10 16:33:35 +00:00
position @ FilePosition { file_id, offset }: FilePosition,
2021-10-17 09:15:56 +00:00
config: &'a CompletionConfig,
) -> Option<CompletionContext<'a>> {
2021-11-08 18:41:16 +00:00
let _p = profile::span("CompletionContext::new");
2021-10-17 09:15:56 +00:00
let sema = Semantics::new(db);
2021-11-10 16:33:35 +00:00
let original_file = sema.parse(file_id);
2021-10-17 09:15:56 +00:00
// 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 = {
2021-11-10 16:33:35 +00:00
let parse = db.parse(file_id);
let edit = Indel::insert(offset, COMPLETION_MARKER.to_string());
2021-10-17 09:15:56 +00:00
parse.reparse(&edit).tree()
};
let fake_ident_token =
2021-12-21 13:07:48 +00:00
file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?;
2021-10-17 09:15:56 +00:00
2021-11-10 16:33:35 +00:00
let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
2021-10-17 09:15:56 +00:00
let token = sema.descend_into_macros_single(original_token.clone());
let scope = sema.scope_at_offset(&token.parent()?, offset)?;
2021-10-17 09:15:56 +00:00
let krate = scope.krate();
let module = scope.module();
2022-03-14 19:36:35 +00:00
let mut locals = FxHashMap::default();
2021-10-17 09:15:56 +00:00
scope.process_all_names(&mut |name, scope| {
if let ScopeDef::Local(local) = scope {
2022-03-14 19:36:35 +00:00
locals.insert(name, local);
2021-10-17 09:15:56 +00:00
}
});
2021-10-17 09:15:56 +00:00
let mut ctx = CompletionContext {
sema,
scope,
db,
config,
position,
original_token,
token,
krate,
module,
2021-10-17 09:15:56 +00:00
expected_name: None,
expected_type: None,
function_def: None,
impl_def: None,
2022-05-07 11:46:43 +00:00
incomplete_let: false,
2021-10-17 09:15:56 +00:00
previous_token: None,
// dummy value, will be overwritten
ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None },
2022-05-07 11:46:43 +00:00
pattern_ctx: None,
2022-05-30 14:01:17 +00:00
qualifier_ctx: Default::default(),
2022-05-07 11:46:43 +00:00
locals,
2021-10-17 09:15:56 +00:00
};
ctx.expand_and_fill(
original_file.syntax().clone(),
file_with_fake_ident.syntax().clone(),
2021-11-10 16:33:35 +00:00
offset,
2021-10-17 09:15:56 +00:00
fake_ident_token,
)?;
2021-10-17 09:15:56 +00:00
Some(ctx)
}
}
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",
];