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(
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 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 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 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 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 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
186 if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
187 cov_mark::hit!(completes_variant_through_alias);
188 acc.add_enum_variants(ctx, path_ctx, e);
189 }
190
191 ty.iterate_path_candidates_split_inherent(
195 ctx.db,
196 &ctx.scope,
197 &ctx.traits_in_scope(),
198 None,
199 PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
200 );
201
202 ty.iterate_assoc_items(ctx.db, |item| {
204 if let hir::AssocItem::TypeAlias(ty) = item {
205 acc.add_type_alias(ctx, ty)
206 }
207 None::<()>
208 });
209 }
210 hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
211 for item in t.items(ctx.db) {
214 add_assoc_item(acc, item);
215 }
216 }
217 hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => {
218 let ty = match resolution {
219 hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
220 hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
221 _ => return,
222 };
223
224 if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
225 cov_mark::hit!(completes_variant_through_self);
226 acc.add_enum_variants(ctx, path_ctx, e);
227 }
228
229 ty.iterate_path_candidates_split_inherent(
230 ctx.db,
231 &ctx.scope,
232 &ctx.traits_in_scope(),
233 None,
234 PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
235 );
236 }
237 _ => (),
238 }
239 }
240 Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
241 Qualified::No => {
242 acc.add_nameref_keywords_with_colon(ctx);
243 if let Some(adt) =
244 ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt())
245 {
246 let self_ty = (|| ctx.sema.to_def(impl_.as_ref()?)?.self_ty(ctx.db).as_adt())();
247 let complete_self = self_ty == Some(adt);
248
249 match adt {
250 hir::Adt::Struct(strukt) => {
251 let path = ctx
252 .module
253 .find_path(
254 ctx.db,
255 hir::ModuleDef::from(strukt),
256 ctx.config.find_path_config(ctx.is_nightly),
257 )
258 .filter(|it| it.len() > 1);
259
260 acc.add_struct_literal(ctx, path_ctx, strukt, path, None);
261
262 if complete_self {
263 acc.add_struct_literal(
264 ctx,
265 path_ctx,
266 strukt,
267 None,
268 Some(Name::new_symbol_root(sym::Self_)),
269 );
270 }
271 }
272 hir::Adt::Union(un) => {
273 let path = ctx
274 .module
275 .find_path(
276 ctx.db,
277 hir::ModuleDef::from(un),
278 ctx.config.find_path_config(ctx.is_nightly),
279 )
280 .filter(|it| it.len() > 1);
281
282 acc.add_union_literal(ctx, un, path, None);
283 if complete_self {
284 acc.add_union_literal(
285 ctx,
286 un,
287 None,
288 Some(Name::new_symbol_root(sym::Self_)),
289 );
290 }
291 }
292 hir::Adt::Enum(e) => {
293 super::enum_variants_with_paths(
294 acc,
295 ctx,
296 e,
297 impl_.as_ref(),
298 |acc, ctx, variant, path| {
299 acc.add_qualified_enum_variant(ctx, path_ctx, variant, path)
300 },
301 );
302 }
303 }
304 }
305 ctx.process_all_names(&mut |name, def, doc_aliases| match def {
306 ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
307 let assocs = t.items_with_supertraits(ctx.db);
308 match &*assocs {
309 [] => (),
312 &[_item] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
314 [..] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
316 }
317 }
318 ScopeDef::Local(_) =>
321 {
322 #[expect(
323 clippy::collapsible_match,
324 reason = "this changes meaning, causing the next arm to be selected"
325 )]
326 if !name.as_str().starts_with('<') {
327 acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
328 }
329 }
330 _ if scope_def_applicable(def) => {
331 acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
332 }
333
334 _ => (),
335 });
336
337 match is_func_update {
338 Some(record_expr) => {
339 let ty = ctx.sema.type_of_expr(&ast::Expr::RecordExpr(record_expr.clone()));
340
341 match ty.as_ref().and_then(|t| t.original.as_adt()) {
342 Some(hir::Adt::Union(_)) => (),
343 _ => {
344 cov_mark::hit!(functional_update);
345 let missing_fields =
346 ctx.sema.record_literal_missing_fields(record_expr);
347 if !missing_fields.is_empty() {
348 add_default_update(acc, ctx, ty.as_ref());
349 }
350 }
351 };
352 }
353 None => {
354 let mut add_keyword = |kw, snippet| {
355 acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet)
356 };
357
358 if !in_block_expr {
359 add_keyword("unsafe", "unsafe {\n $0\n}");
360 if !wants_const_token {
361 add_keyword("const", "const {\n $0\n}");
363 }
364 }
365 add_keyword("match", "match $1 {\n $0\n}");
366 add_keyword("while", "while $1 {\n $0\n}");
367 add_keyword("while let", "while let $1 = $2 {\n $0\n}");
368 add_keyword("loop", "loop {\n $0\n}");
369 if in_match_guard {
370 add_keyword("if", "if $0");
371 } else if in_value {
372 add_keyword("if", "if $1 {\n $2\n} else {\n $0\n}");
373 } else {
374 add_keyword("if", "if $1 {\n $0\n}");
375 }
376 if in_value {
377 add_keyword("if let", "if let $1 = $2 {\n $3\n} else {\n $0\n}");
378 } else {
379 add_keyword("if let", "if let $1 = $2 {\n $0\n}");
380 }
381 add_keyword("for", "for $1 in $2 {\n $0\n}");
382 add_keyword("true", "true");
383 add_keyword("false", "false");
384
385 if in_condition {
386 add_keyword("letm", "let mut $1 = $0");
387 add_keyword("let", "let $1 = $0");
388 }
389
390 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 enable_borrowcheck: false,
490 many_alternatives_threshold: 1,
491 fuel: 200,
492 },
493 };
494 let exprs = hir::term_search::term_search(&term_search_ctx);
495 for expr in exprs {
496 match expr {
498 hir::term_search::Expr::Method { func, generics, target, params }
499 if target.is_many() =>
500 {
501 let target_ty = target.ty(ctx.db);
502 let term_search_ctx =
503 hir::term_search::TermSearchCtx { goal: target_ty, ..term_search_ctx };
504 let target_exprs = hir::term_search::term_search(&term_search_ctx);
505
506 for expr in target_exprs {
507 let expanded_expr = hir::term_search::Expr::Method {
508 func,
509 generics: generics.clone(),
510 target: Box::new(expr),
511 params: params.clone(),
512 };
513
514 acc.add_expr(ctx, &expanded_expr)
515 }
516 }
517 _ => acc.add_expr(ctx, &expr),
518 }
519 }
520 }
521}