1 //! See `CompletionContext` structure.
7 use base_db::SourceDatabaseExt;
9 HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
12 base_db::{FilePosition, SourceDatabase},
13 famous_defs::FamousDefs,
14 FxHashMap, FxHashSet, RootDatabase,
17 ast::{self, AttrKind, NameOrNameRef},
19 SyntaxKind::{self, *},
20 SyntaxToken, TextRange, TextSize,
24 use crate::CompletionConfig;
26 const COMPLETION_MARKER: &str = "intellijRulezz";
28 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
29 pub(crate) enum PatternRefutability {
34 pub(crate) enum Visible {
40 /// Existing qualifiers for the thing we are currently completing.
41 #[derive(Debug, Default)]
42 pub(super) struct QualifierCtx {
43 pub(super) unsafe_tok: Option<SyntaxToken>,
44 pub(super) vis_node: Option<ast::Visibility>,
48 pub(super) fn none(&self) -> bool {
49 self.unsafe_tok.is_none() && self.vis_node.is_none()
53 /// The state of the path we are currently completing.
55 pub(crate) struct PathCompletionCtx {
56 /// If this is a call with () already there (or {} in case of record patterns)
57 pub(super) has_call_parens: bool,
58 /// If this has a macro call bang !
59 pub(super) has_macro_bang: bool,
60 /// The qualifier of the current path.
61 pub(super) qualified: Qualified,
62 /// The parent of the path we are completing.
63 pub(super) parent: Option<ast::Path>,
64 pub(super) kind: PathKind,
65 /// Whether the path segment has type args or not.
66 pub(super) has_type_args: bool,
67 /// Whether the qualifier comes from a use tree parent or not
68 pub(crate) use_tree_parent: bool,
71 impl PathCompletionCtx {
72 pub(super) fn is_trivial_path(&self) -> bool {
76 has_call_parens: false,
77 has_macro_bang: false,
78 qualified: Qualified::No,
87 /// The kind of path we are completing right now.
88 #[derive(Clone, Debug, PartialEq, Eq)]
89 pub(super) enum PathKind {
94 /// Whether this expression is the direct condition of an if or while expression
96 ref_expr_parent: Option<ast::RefExpr>,
97 is_func_update: Option<ast::RecordExpr>,
100 location: TypeLocation,
104 annotated_item_kind: Option<SyntaxKind>,
107 existing_derives: FxHashSet<hir::Macro>,
109 /// Path in item position, that is inside an (Assoc)ItemList
120 /// Original file ast nodes
121 #[derive(Clone, Debug, PartialEq, Eq)]
122 pub(crate) enum TypeLocation {
124 TypeAscription(TypeAscriptionTarget),
125 GenericArgList(Option<ast::GenericArgList>),
132 #[derive(Clone, Debug, PartialEq, Eq)]
133 pub(crate) enum TypeAscriptionTarget {
134 Let(Option<ast::Pat>),
135 FnParam(Option<ast::Pat>),
136 RetType(Option<ast::Expr>),
137 Const(Option<ast::Expr>),
140 /// The kind of item list a [`PathKind::Item`] belongs to.
141 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
142 pub(super) enum ItemListKind {
152 pub(super) enum Qualified {
156 resolution: Option<PathResolution>,
157 /// Whether this path consists solely of `super` segments
158 is_super_chain: bool,
162 /// Whether the path is an absolute path
166 /// The state of the pattern we are completing.
168 pub(super) struct PatternContext {
169 pub(super) refutability: PatternRefutability,
170 pub(super) param_ctx: Option<(ast::ParamList, ast::Param, ParamKind)>,
171 pub(super) has_type_ascription: bool,
172 pub(super) parent_pat: Option<ast::Pat>,
173 pub(super) ref_token: Option<SyntaxToken>,
174 pub(super) mut_token: Option<SyntaxToken>,
175 /// The record pattern this name or ref is a field of
176 pub(super) record_pat: Option<ast::RecordPat>,
179 /// The state of the lifetime we are completing.
181 pub(super) struct LifetimeContext {
182 pub(super) lifetime: Option<ast::Lifetime>,
183 pub(super) kind: LifetimeKind,
186 /// The kind of lifetime we are completing.
188 pub(super) enum LifetimeKind {
189 LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
195 /// The state of the name we are completing.
197 pub(super) struct NameContext {
199 pub(super) name: Option<ast::Name>,
200 pub(super) kind: NameKind,
203 /// The kind of the name we are completing.
206 pub(super) enum NameKind {
228 /// The state of the NameRef we are completing.
230 pub(super) struct NameRefContext {
231 /// NameRef syntax in the original file
232 pub(super) nameref: Option<ast::NameRef>,
233 // FIXME: This shouldn't be an Option
234 pub(super) kind: Option<NameRefKind>,
237 /// The kind of the NameRef we are completing.
239 pub(super) enum NameRefKind {
240 Path(PathCompletionCtx),
241 DotAccess(DotAccess),
242 /// Position where we are only interested in keyword completions
244 /// The record expression this nameref is a field of
245 RecordExpr(ast::RecordExpr),
248 /// The identifier we are currently completing.
250 pub(super) enum IdentContext {
252 NameRef(NameRefContext),
253 Lifetime(LifetimeContext),
254 /// The string the cursor is currently inside
257 original: ast::String,
259 expanded: Option<ast::String>,
261 /// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)`
263 fake_attribute_under_caret: Option<ast::Attr>,
267 /// Information about the field or method access we are completing.
269 pub(super) struct DotAccess {
270 pub(super) receiver: Option<ast::Expr>,
271 pub(super) receiver_ty: Option<TypeInfo>,
272 pub(super) kind: DotAccessKind,
276 pub(super) enum DotAccessKind {
278 /// True if the receiver is an integer and there is no ident in the original file after it yet
280 receiver_is_ambiguous_float_literal: bool,
287 #[derive(Clone, Debug, PartialEq, Eq)]
288 pub(crate) enum ParamKind {
290 Closure(ast::ClosureExpr),
293 /// `CompletionContext` is created early during completion to figure out, where
294 /// exactly is the cursor, syntax-wise.
296 pub(crate) struct CompletionContext<'a> {
297 pub(super) sema: Semantics<'a, RootDatabase>,
298 pub(super) scope: SemanticsScope<'a>,
299 pub(super) db: &'a RootDatabase,
300 pub(super) config: &'a CompletionConfig,
301 pub(super) position: FilePosition,
303 /// The token before the cursor, in the original file.
304 pub(super) original_token: SyntaxToken,
305 /// The token before the cursor, in the macro-expanded file.
306 pub(super) token: SyntaxToken,
307 /// The crate of the current file.
308 pub(super) krate: hir::Crate,
309 /// The module of the `scope`.
310 pub(super) module: hir::Module,
312 /// The expected name of what we are completing.
313 /// This is usually the parameter name of the function argument we are completing.
314 pub(super) expected_name: Option<NameOrNameRef>,
315 /// The expected type of what we are completing.
316 pub(super) expected_type: Option<Type>,
318 /// The parent function of the cursor position if it exists.
319 // FIXME: This probably doesn't belong here
320 pub(super) function_def: Option<ast::Fn>,
321 /// The parent impl of the cursor position if it exists.
322 // FIXME: This probably doesn't belong here
323 pub(super) impl_def: Option<ast::Impl>,
324 /// Are we completing inside a let statement with a missing semicolon?
325 // FIXME: This should be part of PathKind::Expr
326 pub(super) incomplete_let: bool,
328 // FIXME: This shouldn't exist
329 pub(super) previous_token: Option<SyntaxToken>,
331 // We might wanna split these out of CompletionContext
332 pub(super) ident_ctx: IdentContext,
333 pub(super) pattern_ctx: Option<PatternContext>,
334 pub(super) qualifier_ctx: QualifierCtx,
336 pub(super) locals: FxHashMap<Name, Local>,
339 impl<'a> CompletionContext<'a> {
340 /// The range of the identifier that is being completed.
341 pub(crate) fn source_range(&self) -> TextRange {
342 // check kind of macro-expanded token, but use range of original token
343 let kind = self.token.kind();
346 // assume we are completing a lifetime but the user has only typed the '
347 cov_mark::hit!(completes_if_lifetime_without_idents);
348 TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
350 IDENT | LIFETIME_IDENT | UNDERSCORE => self.original_token.text_range(),
351 _ if kind.is_keyword() => self.original_token.text_range(),
352 _ => TextRange::empty(self.position.offset),
356 // FIXME: This shouldn't exist
357 pub(crate) fn previous_token_is(&self, kind: SyntaxKind) -> bool {
358 self.previous_token.as_ref().map_or(false, |tok| tok.kind() == kind)
361 pub(crate) fn famous_defs(&self) -> FamousDefs {
362 FamousDefs(&self.sema, self.krate)
365 /// Checks if an item is visible and not `doc(hidden)` at the completion site.
366 pub(crate) fn is_visible<I>(&self, item: &I) -> Visible
368 I: hir::HasVisibility + hir::HasAttrs + hir::HasCrate + Copy,
370 self.is_visible_impl(&item.visibility(self.db), &item.attrs(self.db), item.krate(self.db))
373 pub(crate) fn is_scope_def_hidden(&self, scope_def: ScopeDef) -> bool {
374 if let (Some(attrs), Some(krate)) = (scope_def.attrs(self.db), scope_def.krate(self.db)) {
375 return self.is_doc_hidden(&attrs, krate);
381 /// Check if an item is `#[doc(hidden)]`.
382 pub(crate) fn is_item_hidden(&self, item: &hir::ItemInNs) -> bool {
383 let attrs = item.attrs(self.db);
384 let krate = item.krate(self.db);
385 match (attrs, krate) {
386 (Some(attrs), Some(krate)) => self.is_doc_hidden(&attrs, krate),
390 /// Whether the given trait is an operator trait or not.
391 pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
392 match trait_.attrs(self.db).lang() {
393 Some(lang) => OP_TRAIT_LANG_NAMES.contains(&lang.as_str()),
398 /// Returns the traits in scope, with the [`Drop`] trait removed.
399 pub(crate) fn traits_in_scope(&self) -> hir::VisibleTraits {
400 let mut traits_in_scope = self.scope.visible_traits();
401 if let Some(drop) = self.famous_defs().core_ops_Drop() {
402 traits_in_scope.0.remove(&drop.into());
407 /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
408 pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
409 let _p = profile::span("CompletionContext::process_all_names");
410 self.scope.process_all_names(&mut |name, def| {
411 if self.is_scope_def_hidden(def) {
419 pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
420 let _p = profile::span("CompletionContext::process_all_names_raw");
421 self.scope.process_all_names(&mut |name, def| f(name, def));
426 vis: &hir::Visibility,
428 defining_crate: hir::Crate,
430 if !vis.is_visible_from(self.db, self.module.into()) {
431 if !self.config.enable_private_editable {
434 // If the definition location is editable, also show private items
435 let root_file = defining_crate.root_file(self.db);
436 let source_root_id = self.db.file_source_root(root_file);
437 let is_editable = !self.db.source_root(source_root_id).is_library;
438 return if is_editable { Visible::Editable } else { Visible::No };
441 if self.is_doc_hidden(attrs, defining_crate) {
448 fn is_doc_hidden(&self, attrs: &hir::Attrs, defining_crate: hir::Crate) -> bool {
449 // `doc(hidden)` items are only completed within the defining crate.
450 self.krate != defining_crate && attrs.has_doc_hidden()
454 // CompletionContext construction
455 impl<'a> CompletionContext<'a> {
457 db: &'a RootDatabase,
458 position @ FilePosition { file_id, offset }: FilePosition,
459 config: &'a CompletionConfig,
460 ) -> Option<CompletionContext<'a>> {
461 let _p = profile::span("CompletionContext::new");
462 let sema = Semantics::new(db);
464 let original_file = sema.parse(file_id);
466 // Insert a fake ident to get a valid parse tree. We will use this file
467 // to determine context, though the original_file will be used for
468 // actual completion.
469 let file_with_fake_ident = {
470 let parse = db.parse(file_id);
471 let edit = Indel::insert(offset, COMPLETION_MARKER.to_string());
472 parse.reparse(&edit).tree()
474 let fake_ident_token =
475 file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?;
477 let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
478 let token = sema.descend_into_macros_single(original_token.clone());
479 let scope = sema.scope_at_offset(&token.parent()?, offset)?;
480 let krate = scope.krate();
481 let module = scope.module();
483 let mut locals = FxHashMap::default();
484 scope.process_all_names(&mut |name, scope| {
485 if let ScopeDef::Local(local) = scope {
486 locals.insert(name, local);
490 let mut ctx = CompletionContext {
504 incomplete_let: false,
505 previous_token: None,
506 // dummy value, will be overwritten
507 ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None },
509 qualifier_ctx: Default::default(),
513 original_file.syntax().clone(),
514 file_with_fake_ident.syntax().clone(),
522 const OP_TRAIT_LANG_NAMES: &[&str] = &[