1use 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 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 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 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 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 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 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 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 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 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 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 [] => (),
316 &[_item] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
318 [..] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
320 }
321 }
322 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 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 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 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}