]> git.lizzy.rs Git - rust.git/blob - crates/ide-completion/src/context.rs
internal: Split flyimport into its 3 applicable contexts
[rust.git] / crates / ide-completion / src / context.rs
1 //! See `CompletionContext` structure.
2
3 mod analysis;
4 #[cfg(test)]
5 mod tests;
6
7 use base_db::SourceDatabaseExt;
8 use hir::{
9     HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
10 };
11 use ide_db::{
12     base_db::{FilePosition, SourceDatabase},
13     famous_defs::FamousDefs,
14     FxHashMap, FxHashSet, RootDatabase,
15 };
16 use syntax::{
17     ast::{self, AttrKind, NameOrNameRef},
18     AstNode,
19     SyntaxKind::{self, *},
20     SyntaxToken, TextRange, TextSize,
21 };
22 use text_edit::Indel;
23
24 use crate::CompletionConfig;
25
26 const COMPLETION_MARKER: &str = "intellijRulezz";
27
28 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
29 pub(crate) enum PatternRefutability {
30     Refutable,
31     Irrefutable,
32 }
33
34 pub(crate) enum Visible {
35     Yes,
36     Editable,
37     No,
38 }
39
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>,
45 }
46
47 impl QualifierCtx {
48     pub(super) fn none(&self) -> bool {
49         self.unsafe_tok.is_none() && self.vis_node.is_none()
50     }
51 }
52
53 /// The state of the path we are currently completing.
54 #[derive(Debug)]
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,
69 }
70
71 impl PathCompletionCtx {
72     pub(super) fn is_trivial_path(&self) -> bool {
73         matches!(
74             self,
75             PathCompletionCtx {
76                 has_call_parens: false,
77                 has_macro_bang: false,
78                 qualified: Qualified::No,
79                 parent: None,
80                 has_type_args: false,
81                 ..
82             }
83         )
84     }
85 }
86
87 /// The kind of path we are completing right now.
88 #[derive(Clone, Debug, PartialEq, Eq)]
89 pub(super) enum PathKind {
90     Expr {
91         in_block_expr: bool,
92         in_loop_body: bool,
93         after_if_expr: bool,
94         /// Whether this expression is the direct condition of an if or while expression
95         in_condition: bool,
96         ref_expr_parent: Option<ast::RefExpr>,
97         is_func_update: Option<ast::RecordExpr>,
98     },
99     Type {
100         location: TypeLocation,
101     },
102     Attr {
103         kind: AttrKind,
104         annotated_item_kind: Option<SyntaxKind>,
105     },
106     Derive {
107         existing_derives: FxHashSet<hir::Macro>,
108     },
109     /// Path in item position, that is inside an (Assoc)ItemList
110     Item {
111         kind: ItemListKind,
112     },
113     Pat,
114     Vis {
115         has_in_token: bool,
116     },
117     Use,
118 }
119
120 /// Original file ast nodes
121 #[derive(Clone, Debug, PartialEq, Eq)]
122 pub(crate) enum TypeLocation {
123     TupleField,
124     TypeAscription(TypeAscriptionTarget),
125     GenericArgList(Option<ast::GenericArgList>),
126     TypeBound,
127     ImplTarget,
128     ImplTrait,
129     Other,
130 }
131
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>),
138 }
139
140 /// The kind of item list a [`PathKind::Item`] belongs to.
141 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
142 pub(super) enum ItemListKind {
143     SourceFile,
144     Module,
145     Impl,
146     TraitImpl,
147     Trait,
148     ExternBlock,
149 }
150
151 #[derive(Debug)]
152 pub(super) enum Qualified {
153     No,
154     With {
155         path: ast::Path,
156         resolution: Option<PathResolution>,
157         /// Whether this path consists solely of `super` segments
158         is_super_chain: bool,
159     },
160     /// <_>::
161     Infer,
162     /// Whether the path is an absolute path
163     Absolute,
164 }
165
166 /// The state of the pattern we are completing.
167 #[derive(Debug)]
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>,
177 }
178
179 /// The state of the lifetime we are completing.
180 #[derive(Debug)]
181 pub(super) struct LifetimeContext {
182     pub(super) lifetime: Option<ast::Lifetime>,
183     pub(super) kind: LifetimeKind,
184 }
185
186 /// The kind of lifetime we are completing.
187 #[derive(Debug)]
188 pub(super) enum LifetimeKind {
189     LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
190     Lifetime,
191     LabelRef,
192     LabelDef,
193 }
194
195 /// The state of the name we are completing.
196 #[derive(Debug)]
197 pub(super) struct NameContext {
198     #[allow(dead_code)]
199     pub(super) name: Option<ast::Name>,
200     pub(super) kind: NameKind,
201 }
202
203 /// The kind of the name we are completing.
204 #[derive(Debug)]
205 #[allow(dead_code)]
206 pub(super) enum NameKind {
207     Const,
208     ConstParam,
209     Enum,
210     Function,
211     IdentPat,
212     MacroDef,
213     MacroRules,
214     /// Fake node
215     Module(ast::Module),
216     RecordField,
217     Rename,
218     SelfParam,
219     Static,
220     Struct,
221     Trait,
222     TypeAlias,
223     TypeParam,
224     Union,
225     Variant,
226 }
227
228 /// The state of the NameRef we are completing.
229 #[derive(Debug)]
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>,
235 }
236
237 /// The kind of the NameRef we are completing.
238 #[derive(Debug)]
239 pub(super) enum NameRefKind {
240     Path(PathCompletionCtx),
241     DotAccess(DotAccess),
242     /// Position where we are only interested in keyword completions
243     Keyword(ast::Item),
244     /// The record expression this nameref is a field of
245     RecordExpr(ast::RecordExpr),
246 }
247
248 /// The identifier we are currently completing.
249 #[derive(Debug)]
250 pub(super) enum IdentContext {
251     Name(NameContext),
252     NameRef(NameRefContext),
253     Lifetime(LifetimeContext),
254     /// The string the cursor is currently inside
255     String {
256         /// original token
257         original: ast::String,
258         /// fake token
259         expanded: Option<ast::String>,
260     },
261     /// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)`
262     UnexpandedAttrTT {
263         fake_attribute_under_caret: Option<ast::Attr>,
264     },
265 }
266
267 /// Information about the field or method access we are completing.
268 #[derive(Debug)]
269 pub(super) struct DotAccess {
270     pub(super) receiver: Option<ast::Expr>,
271     pub(super) receiver_ty: Option<TypeInfo>,
272     pub(super) kind: DotAccessKind,
273 }
274
275 #[derive(Debug)]
276 pub(super) enum DotAccessKind {
277     Field {
278         /// True if the receiver is an integer and there is no ident in the original file after it yet
279         /// like `0.$0`
280         receiver_is_ambiguous_float_literal: bool,
281     },
282     Method {
283         has_parens: bool,
284     },
285 }
286
287 #[derive(Clone, Debug, PartialEq, Eq)]
288 pub(crate) enum ParamKind {
289     Function(ast::Fn),
290     Closure(ast::ClosureExpr),
291 }
292
293 /// `CompletionContext` is created early during completion to figure out, where
294 /// exactly is the cursor, syntax-wise.
295 #[derive(Debug)]
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,
302
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,
311
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>,
317
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,
327
328     // FIXME: This shouldn't exist
329     pub(super) previous_token: Option<SyntaxToken>,
330
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,
335
336     pub(super) locals: FxHashMap<Name, Local>,
337 }
338
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();
344         match kind {
345             CHAR => {
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))
349             }
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),
353         }
354     }
355
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)
359     }
360
361     pub(crate) fn famous_defs(&self) -> FamousDefs {
362         FamousDefs(&self.sema, self.krate)
363     }
364
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
367     where
368         I: hir::HasVisibility + hir::HasAttrs + hir::HasCrate + Copy,
369     {
370         self.is_visible_impl(&item.visibility(self.db), &item.attrs(self.db), item.krate(self.db))
371     }
372
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);
376         }
377
378         false
379     }
380
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),
387             _ => false,
388         }
389     }
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()),
394             None => false,
395         }
396     }
397
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());
403         }
404         traits_in_scope
405     }
406
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) {
412                 return;
413             }
414
415             f(name, def);
416         });
417     }
418
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));
422     }
423
424     fn is_visible_impl(
425         &self,
426         vis: &hir::Visibility,
427         attrs: &hir::Attrs,
428         defining_crate: hir::Crate,
429     ) -> Visible {
430         if !vis.is_visible_from(self.db, self.module.into()) {
431             if !self.config.enable_private_editable {
432                 return Visible::No;
433             }
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 };
439         }
440
441         if self.is_doc_hidden(attrs, defining_crate) {
442             Visible::No
443         } else {
444             Visible::Yes
445         }
446     }
447
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()
451     }
452 }
453
454 // CompletionContext construction
455 impl<'a> CompletionContext<'a> {
456     pub(super) fn new(
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);
463
464         let original_file = sema.parse(file_id);
465
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()
473         };
474         let fake_ident_token =
475             file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?;
476
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();
482
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);
487             }
488         });
489
490         let mut ctx = CompletionContext {
491             sema,
492             scope,
493             db,
494             config,
495             position,
496             original_token,
497             token,
498             krate,
499             module,
500             expected_name: None,
501             expected_type: None,
502             function_def: None,
503             impl_def: None,
504             incomplete_let: false,
505             previous_token: None,
506             // dummy value, will be overwritten
507             ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None },
508             pattern_ctx: None,
509             qualifier_ctx: Default::default(),
510             locals,
511         };
512         ctx.expand_and_fill(
513             original_file.syntax().clone(),
514             file_with_fake_ident.syntax().clone(),
515             offset,
516             fake_ident_token,
517         )?;
518         Some(ctx)
519     }
520 }
521
522 const OP_TRAIT_LANG_NAMES: &[&str] = &[
523     "add_assign",
524     "add",
525     "bitand_assign",
526     "bitand",
527     "bitor_assign",
528     "bitor",
529     "bitxor_assign",
530     "bitxor",
531     "deref_mut",
532     "deref",
533     "div_assign",
534     "div",
535     "eq",
536     "fn_mut",
537     "fn_once",
538     "fn",
539     "index_mut",
540     "index",
541     "mul_assign",
542     "mul",
543     "neg",
544     "not",
545     "partial_ord",
546     "rem_assign",
547     "rem",
548     "shl_assign",
549     "shl",
550     "shr_assign",
551     "shr",
552     "sub",
553 ];