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