Skip to main content

ide_completion/completions/
expr.rs

1//! Completion of names from the current scope in expression position.
2
3use std::ops::ControlFlow;
4
5use hir::{Complete, Name, PathCandidateCallback, ScopeDef, sym};
6use ide_db::FxHashSet;
7use syntax::ast;
8
9use crate::{
10    CompletionContext, Completions,
11    completions::record::add_default_update,
12    context::{PathCompletionCtx, PathExprCtx, Qualified},
13};
14
15struct PathCallback<'a, 'db, F> {
16    ctx: &'a CompletionContext<'a, 'db>,
17    acc: &'a mut Completions,
18    add_assoc_item: F,
19    seen: FxHashSet<hir::AssocItem>,
20}
21
22impl<F> PathCandidateCallback for PathCallback<'_, '_, F>
23where
24    F: FnMut(&mut Completions, hir::AssocItem),
25{
26    fn on_inherent_item(&mut self, item: hir::AssocItem) -> ControlFlow<()> {
27        if self.seen.insert(item) {
28            (self.add_assoc_item)(self.acc, item);
29        }
30        ControlFlow::Continue(())
31    }
32
33    fn on_trait_item(&mut self, item: hir::AssocItem) -> ControlFlow<()> {
34        // The excluded check needs to come before the `seen` test, so that if we see the same method twice,
35        // once as inherent and once not, we will include it.
36        if item.container_trait(self.ctx.db).is_none_or(|trait_| {
37            !self.ctx.exclude_traits.contains(&trait_)
38                && trait_.complete(self.ctx.db) != Complete::IgnoreMethods
39        }) && self.seen.insert(item)
40        {
41            (self.add_assoc_item)(self.acc, item);
42        }
43        ControlFlow::Continue(())
44    }
45}
46
47pub(crate) fn complete_expr_path<'db>(
48    acc: &mut Completions,
49    ctx: &CompletionContext<'_, 'db>,
50    path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx<'_>,
51    expr_ctx: &PathExprCtx<'_>,
52) {
53    let _p = tracing::info_span!("complete_expr_path").entered();
54    if !ctx.qualifier_ctx.none() {
55        return;
56    }
57
58    let &PathExprCtx {
59        in_block_expr,
60        after_if_expr,
61        before_else_kw,
62        in_condition,
63        incomplete_let,
64        after_incomplete_let,
65        in_value,
66        ref ref_expr_parent,
67        after_amp,
68        ref is_func_update,
69        ref innermost_ret_ty,
70        ref innermost_breakable_ty,
71        ref impl_,
72        in_match_guard,
73        at_optional_block,
74        ..
75    } = expr_ctx;
76
77    let (has_raw_token, has_const_token, has_mut_token) = ref_expr_parent
78        .as_ref()
79        .map(|it| (it.raw_token().is_some(), it.const_token().is_some(), it.mut_token().is_some()))
80        .unwrap_or((false, false, false));
81
82    let wants_raw_token = ref_expr_parent.is_some() && !has_raw_token && after_amp;
83    let wants_const_token =
84        ref_expr_parent.is_some() && has_raw_token && !has_const_token && !has_mut_token;
85    let wants_mut_token = if ref_expr_parent.is_some() {
86        if has_raw_token { !has_const_token && !has_mut_token } else { !has_mut_token }
87    } else {
88        false
89    };
90
91    let scope_def_applicable = |def: ScopeDef<'db>| match def {
92        ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) | ScopeDef::Label(_) => false,
93        ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
94        _ => true,
95    };
96
97    let add_assoc_item = |acc: &mut Completions, item| match item {
98        hir::AssocItem::Function(func) => acc.add_function(ctx, path_ctx, func, None),
99        hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
100        hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
101    };
102
103    match qualified {
104        // We exclude associated types/consts of excluded traits here together with methods,
105        // even though we don't exclude them when completing in type position, because it's easier.
106        Qualified::TypeAnchor { ty: None, trait_: None } => ctx
107            .traits_in_scope()
108            .iter()
109            .copied()
110            .map(hir::Trait::from)
111            .filter(|it| {
112                !ctx.exclude_traits.contains(it) && it.complete(ctx.db) != Complete::IgnoreMethods
113            })
114            .flat_map(|it| it.items(ctx.sema.db))
115            .for_each(|item| add_assoc_item(acc, item)),
116        Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
117            // Don't filter excluded traits here, user requested this specific trait.
118            trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
119        }
120        Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
121            if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
122                cov_mark::hit!(completes_variant_through_alias);
123                acc.add_enum_variants(ctx, path_ctx, e);
124            }
125
126            ty.iterate_path_candidates_split_inherent(
127                ctx.db,
128                &ctx.scope,
129                &ctx.traits_in_scope(),
130                None,
131                PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
132            );
133
134            // Iterate assoc types separately
135            ty.iterate_assoc_items(ctx.db, |item| {
136                if let hir::AssocItem::TypeAlias(ty) = item {
137                    acc.add_type_alias(ctx, ty)
138                }
139                None::<()>
140            });
141        }
142        Qualified::With { resolution: None, .. } => {}
143        Qualified::With { resolution: Some(resolution), .. } => {
144            // Add associated types on type parameters and `Self`.
145            ctx.scope.assoc_type_shorthand_candidates(resolution, |alias| {
146                acc.add_type_alias(ctx, alias);
147            });
148            match resolution {
149                hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
150                    let visible_from = if ctx.config.enable_private_editable {
151                        // Set visible_from to None so private items are returned.
152                        // They will be possibly filtered out in add_path_resolution()
153                        // via def_is_visible().
154                        None
155                    } else {
156                        Some(ctx.module)
157                    };
158
159                    let module_scope = module.scope(ctx.db, visible_from);
160                    for (name, def) in module_scope {
161                        if scope_def_applicable(def) {
162                            acc.add_path_resolution(
163                                ctx,
164                                path_ctx,
165                                name,
166                                def,
167                                ctx.doc_aliases_in_scope(def),
168                            );
169                        }
170                    }
171                }
172                hir::PathResolution::Def(
173                    def @ (hir::ModuleDef::Adt(_)
174                    | hir::ModuleDef::TypeAlias(_)
175                    | hir::ModuleDef::BuiltinType(_)),
176                ) => {
177                    let ty = match def {
178                        hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
179                        hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
180                        hir::ModuleDef::BuiltinType(builtin) => {
181                            cov_mark::hit!(completes_primitive_assoc_const);
182                            builtin.ty(ctx.db)
183                        }
184                        _ => return,
185                    };
186                    // Note: this is not *required* here, we do it to also find methods that require
187                    // the type to be instantiated with specific types.
188                    let ty = ty.instantiate_with_errors();
189
190                    if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
191                        cov_mark::hit!(completes_variant_through_alias);
192                        acc.add_enum_variants(ctx, path_ctx, e);
193                    }
194
195                    // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
196                    // (where AssocType is defined on a trait, not an inherent impl)
197
198                    ty.iterate_path_candidates_split_inherent(
199                        ctx.db,
200                        &ctx.scope,
201                        &ctx.traits_in_scope(),
202                        None,
203                        PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
204                    );
205
206                    // Iterate assoc types separately
207                    ty.iterate_assoc_items(ctx.db, |item| {
208                        if let hir::AssocItem::TypeAlias(ty) = item {
209                            acc.add_type_alias(ctx, ty)
210                        }
211                        None::<()>
212                    });
213                }
214                hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
215                    // Don't filter excluded traits here, user requested this specific trait.
216                    // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
217                    for item in t.items(ctx.db) {
218                        add_assoc_item(acc, item);
219                    }
220                }
221                hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => {
222                    let ty = match resolution {
223                        hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
224                        hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
225                        _ => return,
226                    };
227
228                    if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
229                        cov_mark::hit!(completes_variant_through_self);
230                        acc.add_enum_variants(ctx, path_ctx, e);
231                    }
232
233                    ty.iterate_path_candidates_split_inherent(
234                        ctx.db,
235                        &ctx.scope,
236                        &ctx.traits_in_scope(),
237                        None,
238                        PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
239                    );
240                }
241                _ => (),
242            }
243        }
244        Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
245        Qualified::No => {
246            acc.add_nameref_keywords_with_colon(ctx);
247            if let Some(adt) =
248                ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt())
249            {
250                let self_ty = (|| ctx.sema.to_def(impl_.as_ref()?)?.self_ty(ctx.db).as_adt())();
251                let complete_self = self_ty == Some(adt);
252
253                match adt {
254                    hir::Adt::Struct(strukt) => {
255                        let path = ctx
256                            .module
257                            .find_path(
258                                ctx.db,
259                                hir::ModuleDef::from(strukt),
260                                ctx.config.find_path_config(ctx.is_nightly),
261                            )
262                            .filter(|it| it.len() > 1);
263
264                        acc.add_struct_literal(ctx, path_ctx, strukt, path, None);
265
266                        if complete_self {
267                            acc.add_struct_literal(
268                                ctx,
269                                path_ctx,
270                                strukt,
271                                None,
272                                Some(Name::new_symbol_root(sym::Self_)),
273                            );
274                        }
275                    }
276                    hir::Adt::Union(un) => {
277                        let path = ctx
278                            .module
279                            .find_path(
280                                ctx.db,
281                                hir::ModuleDef::from(un),
282                                ctx.config.find_path_config(ctx.is_nightly),
283                            )
284                            .filter(|it| it.len() > 1);
285
286                        acc.add_union_literal(ctx, un, path, None);
287                        if complete_self {
288                            acc.add_union_literal(
289                                ctx,
290                                un,
291                                None,
292                                Some(Name::new_symbol_root(sym::Self_)),
293                            );
294                        }
295                    }
296                    hir::Adt::Enum(e) => {
297                        super::enum_variants_with_paths(
298                            acc,
299                            ctx,
300                            e,
301                            impl_.as_ref(),
302                            |acc, ctx, variant, path| {
303                                acc.add_qualified_enum_variant(ctx, path_ctx, variant, path)
304                            },
305                        );
306                    }
307                }
308            }
309            ctx.process_all_names(&mut |name, def, doc_aliases| match def {
310                ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
311                    let assocs = t.items_with_supertraits(ctx.db);
312                    match &*assocs {
313                        // traits with no assoc items are unusable as expressions since
314                        // there is no associated item path that can be constructed with them
315                        [] => (),
316                        // FIXME: Render the assoc item with the trait qualified
317                        &[_item] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
318                        // FIXME: Append `::` to the thing here, since a trait on its own won't work
319                        [..] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
320                    }
321                }
322                // synthetic names currently leak out as we lack synthetic hygiene, so filter them
323                // out here
324                ScopeDef::Local(_) => {
325                    if !name.as_str().starts_with('<') {
326                        acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
327                    }
328                }
329                _ if scope_def_applicable(def) => {
330                    acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
331                }
332
333                _ => (),
334            });
335
336            match is_func_update {
337                Some(record_expr) => {
338                    let ty = ctx.sema.type_of_expr(&ast::Expr::RecordExpr(record_expr.clone()));
339
340                    match ty.as_ref().and_then(|t| t.original.as_adt()) {
341                        Some(hir::Adt::Union(_)) => (),
342                        _ => {
343                            cov_mark::hit!(functional_update);
344                            let missing_fields =
345                                ctx.sema.record_literal_missing_fields(record_expr);
346                            if !missing_fields.is_empty() {
347                                add_default_update(acc, ctx, ty.as_ref());
348                            }
349                        }
350                    };
351                }
352                None => {
353                    let mut add_keyword = |kw, snippet| {
354                        acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet)
355                    };
356
357                    if !in_block_expr {
358                        add_keyword("unsafe", "unsafe {\n    $0\n}");
359                        if !wants_const_token {
360                            // Avoid having two `const` items in `&raw $0`
361                            add_keyword("const", "const {\n    $0\n}");
362                        }
363                    }
364                    add_keyword("match", "match $1 {\n    $0\n}");
365                    add_keyword("while", "while $1 {\n    $0\n}");
366                    add_keyword("while let", "while let $1 = $2 {\n    $0\n}");
367                    add_keyword("loop", "loop {\n    $0\n}");
368                    if in_match_guard {
369                        add_keyword("if", "if $0");
370                    } else if in_value {
371                        add_keyword("if", "if $1 {\n    $2\n} else {\n    $0\n}");
372                    } else {
373                        add_keyword("if", "if $1 {\n    $0\n}");
374                    }
375                    if in_value {
376                        add_keyword("if let", "if let $1 = $2 {\n    $3\n} else {\n    $0\n}");
377                    } else {
378                        add_keyword("if let", "if let $1 = $2 {\n    $0\n}");
379                    }
380                    add_keyword("for", "for $1 in $2 {\n    $0\n}");
381                    add_keyword("true", "true");
382                    add_keyword("false", "false");
383
384                    if in_condition {
385                        add_keyword("letm", "let mut $1 = $0");
386                        add_keyword("let", "let $1 = $0");
387                    } else if at_optional_block {
388                        add_keyword("letm", "{\n    let mut $1 = $0;\n}");
389                        add_keyword("let", "{\n    let $1 = $0;\n}");
390                    } else if in_block_expr {
391                        add_keyword("letm", "let mut $1 = $0;");
392                        add_keyword("let", "let $1 = $0;");
393                    }
394
395                    if !before_else_kw && (after_if_expr || after_incomplete_let) {
396                        add_keyword("else", "else {\n    $0\n}");
397                    }
398
399                    if after_if_expr {
400                        add_keyword("else if", "else if $1 {\n    $0\n}");
401                    }
402
403                    if wants_raw_token {
404                        add_keyword("raw", "raw ");
405                    }
406                    if wants_const_token {
407                        add_keyword("const", "const ");
408                    }
409                    if wants_mut_token {
410                        add_keyword("mut", "mut ");
411                    }
412
413                    if let Some(loop_ty) = innermost_breakable_ty {
414                        if in_block_expr {
415                            add_keyword("continue", "continue;");
416                        } else {
417                            add_keyword("continue", "continue");
418                        }
419                        add_keyword(
420                            "break",
421                            match (loop_ty.is_unit(), in_block_expr) {
422                                (true, true) => "break;",
423                                (true, false) => "break",
424                                (false, true) => "break $0;",
425                                (false, false) => "break $0",
426                            },
427                        );
428                    }
429
430                    if let Some(ret_ty) = innermost_ret_ty {
431                        add_keyword(
432                            "return",
433                            match (ret_ty.is_unit(), in_block_expr) {
434                                (true, true) => {
435                                    cov_mark::hit!(return_unit_block);
436                                    "return;"
437                                }
438                                (true, false) => {
439                                    cov_mark::hit!(return_unit_no_block);
440                                    "return"
441                                }
442                                (false, true) => {
443                                    cov_mark::hit!(return_value_block);
444                                    "return $0;"
445                                }
446                                (false, false) => {
447                                    cov_mark::hit!(return_value_no_block);
448                                    "return $0"
449                                }
450                            },
451                        );
452                    }
453                }
454            }
455        }
456    }
457}
458
459pub(crate) fn complete_expr(
460    acc: &mut Completions,
461    ctx: &CompletionContext<'_, '_>,
462    PathCompletionCtx { qualified, .. }: &PathCompletionCtx<'_>,
463) {
464    let _p = tracing::info_span!("complete_expr").entered();
465
466    if !ctx.config.enable_term_search {
467        return;
468    }
469
470    if !ctx.qualifier_ctx.none() {
471        return;
472    }
473
474    if !matches!(qualified, Qualified::No) {
475        return;
476    }
477
478    if let Some(ty) = &ctx.expected_type {
479        // Ignore unit types as they are not very interesting
480        if ty.is_unit() || ty.is_unknown() {
481            return;
482        }
483
484        let term_search_ctx = hir::term_search::TermSearchCtx {
485            sema: &ctx.sema,
486            scope: &ctx.scope,
487            goal: ty.clone(),
488            config: hir::term_search::TermSearchConfig {
489                many_alternatives_threshold: 1,
490                fuel: 200,
491            },
492        };
493        let exprs = hir::term_search::term_search(&term_search_ctx);
494        for expr in exprs {
495            // Expand method calls
496            match expr {
497                hir::term_search::Expr::Method { func, generics, target, params }
498                    if target.is_many() =>
499                {
500                    let target_ty = target.ty(ctx.db);
501                    let term_search_ctx =
502                        hir::term_search::TermSearchCtx { goal: target_ty, ..term_search_ctx };
503                    let target_exprs = hir::term_search::term_search(&term_search_ctx);
504
505                    for expr in target_exprs {
506                        let expanded_expr = hir::term_search::Expr::Method {
507                            func,
508                            generics: generics.clone(),
509                            target: Box::new(expr),
510                            params: params.clone(),
511                        };
512
513                        acc.add_expr(ctx, &expanded_expr)
514                    }
515                }
516                _ => acc.add_expr(ctx, &expr),
517            }
518        }
519    }
520}