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 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 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 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 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 [] => (),
315 &[_item] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
317 [..] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
319 }
320 }
321 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 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 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 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}