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, F> {
16 ctx: &'a CompletionContext<'a>,
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 if !name.as_str().starts_with('<') {
322 acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
323 }
324 }
325 _ if scope_def_applicable(def) => {
326 acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
327 }
328
329 _ => (),
330 });
331
332 match is_func_update {
333 Some(record_expr) => {
334 let ty = ctx.sema.type_of_expr(&ast::Expr::RecordExpr(record_expr.clone()));
335
336 match ty.as_ref().and_then(|t| t.original.as_adt()) {
337 Some(hir::Adt::Union(_)) => (),
338 _ => {
339 cov_mark::hit!(functional_update);
340 let missing_fields =
341 ctx.sema.record_literal_missing_fields(record_expr);
342 if !missing_fields.is_empty() {
343 add_default_update(acc, ctx, ty);
344 }
345 }
346 };
347 }
348 None => {
349 let mut add_keyword = |kw, snippet| {
350 acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet)
351 };
352
353 if !in_block_expr {
354 add_keyword("unsafe", "unsafe {\n $0\n}");
355 if !wants_const_token {
356 add_keyword("const", "const {\n $0\n}");
358 }
359 }
360 add_keyword("match", "match $1 {\n $0\n}");
361 add_keyword("while", "while $1 {\n $0\n}");
362 add_keyword("while let", "while let $1 = $2 {\n $0\n}");
363 add_keyword("loop", "loop {\n $0\n}");
364 if in_match_guard {
365 add_keyword("if", "if $0");
366 } else if in_value {
367 add_keyword("if", "if $1 {\n $2\n} else {\n $0\n}");
368 } else {
369 add_keyword("if", "if $1 {\n $0\n}");
370 }
371 if in_value {
372 add_keyword("if let", "if let $1 = $2 {\n $3\n} else {\n $0\n}");
373 } else {
374 add_keyword("if let", "if let $1 = $2 {\n $0\n}");
375 }
376 add_keyword("for", "for $1 in $2 {\n $0\n}");
377 add_keyword("true", "true");
378 add_keyword("false", "false");
379
380 if in_condition {
381 add_keyword("letm", "let mut $1 = $0");
382 add_keyword("let", "let $1 = $0");
383 }
384
385 if in_block_expr {
386 add_keyword("letm", "let mut $1 = $0;");
387 add_keyword("let", "let $1 = $0;");
388 }
389
390 if !before_else_kw && (after_if_expr || after_incomplete_let) {
391 add_keyword("else", "else {\n $0\n}");
392 }
393
394 if after_if_expr {
395 add_keyword("else if", "else if $1 {\n $0\n}");
396 }
397
398 if wants_raw_token {
399 add_keyword("raw", "raw ");
400 }
401 if wants_const_token {
402 add_keyword("const", "const ");
403 }
404 if wants_mut_token {
405 add_keyword("mut", "mut ");
406 }
407
408 if let Some(loop_ty) = innermost_breakable_ty {
409 if in_block_expr {
410 add_keyword("continue", "continue;");
411 } else {
412 add_keyword("continue", "continue");
413 }
414 add_keyword(
415 "break",
416 match (loop_ty.is_unit(), in_block_expr) {
417 (true, true) => "break;",
418 (true, false) => "break",
419 (false, true) => "break $0;",
420 (false, false) => "break $0",
421 },
422 );
423 }
424
425 if let Some(ret_ty) = innermost_ret_ty {
426 add_keyword(
427 "return",
428 match (ret_ty.is_unit(), in_block_expr) {
429 (true, true) => {
430 cov_mark::hit!(return_unit_block);
431 "return;"
432 }
433 (true, false) => {
434 cov_mark::hit!(return_unit_no_block);
435 "return"
436 }
437 (false, true) => {
438 cov_mark::hit!(return_value_block);
439 "return $0;"
440 }
441 (false, false) => {
442 cov_mark::hit!(return_value_no_block);
443 "return $0"
444 }
445 },
446 );
447 }
448 }
449 }
450 }
451 }
452}
453
454pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) {
455 let _p = tracing::info_span!("complete_expr").entered();
456
457 if !ctx.config.enable_term_search {
458 return;
459 }
460
461 if !ctx.qualifier_ctx.none() {
462 return;
463 }
464
465 if let Some(ty) = &ctx.expected_type {
466 if ty.is_unit() || ty.is_unknown() {
468 return;
469 }
470
471 let term_search_ctx = hir::term_search::TermSearchCtx {
472 sema: &ctx.sema,
473 scope: &ctx.scope,
474 goal: ty.clone(),
475 config: hir::term_search::TermSearchConfig {
476 enable_borrowcheck: false,
477 many_alternatives_threshold: 1,
478 fuel: 200,
479 },
480 };
481 let exprs = hir::term_search::term_search(&term_search_ctx);
482 for expr in exprs {
483 match expr {
485 hir::term_search::Expr::Method { func, generics, target, params }
486 if target.is_many() =>
487 {
488 let target_ty = target.ty(ctx.db);
489 let term_search_ctx =
490 hir::term_search::TermSearchCtx { goal: target_ty, ..term_search_ctx };
491 let target_exprs = hir::term_search::term_search(&term_search_ctx);
492
493 for expr in target_exprs {
494 let expanded_expr = hir::term_search::Expr::Method {
495 func,
496 generics: generics.clone(),
497 target: Box::new(expr),
498 params: params.clone(),
499 };
500
501 acc.add_expr(ctx, &expanded_expr)
502 }
503 }
504 _ => acc.add_expr(ctx, &expr),
505 }
506 }
507 }
508}