Skip to main content

hir/term_search/
tactics.rs

1//! Tactics for term search
2//!
3//! All the tactics take following arguments
4//! * `ctx` - Context for the term search
5//! * `defs` - Set of items in scope at term search target location
6//! * `lookup` - Lookup table for types
7//! * `should_continue` - Function that indicates when to stop iterating
8//!
9//! And they return iterator that yields type trees that unify with the `goal` type.
10
11use std::iter;
12
13use hir_ty::db::HirDatabase;
14use itertools::Itertools;
15use rustc_hash::FxHashSet;
16
17use crate::{
18    Adt, AssocItem, BuiltinType, GenericDef, GenericParam, HasAttrs, HasVisibility, Impl,
19    ModuleDef, ScopeDef, Type, TypeParam, term_search::Expr,
20};
21
22use super::{LookupTable, NewTypesKey, TermSearchCtx};
23
24/// # Trivial tactic
25///
26/// Attempts to fulfill the goal by trying items in scope
27/// Also works as a starting point to move all items in scope to lookup table.
28///
29/// # Arguments
30/// * `ctx` - Context for the term search
31/// * `defs` - Set of items in scope at term search target location
32/// * `lookup` - Lookup table for types
33///
34/// Returns iterator that yields elements that unify with `goal`.
35///
36/// _Note that there is no use of calling this tactic in every iteration as the output does not
37/// depend on the current state of `lookup`_
38pub(super) fn trivial<'a, 'lt, 'db, DB: HirDatabase>(
39    ctx: &'a TermSearchCtx<'_, 'db, DB>,
40    defs: &'a FxHashSet<ScopeDef<'db>>,
41    lookup: &'lt mut LookupTable<'db>,
42) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
43    let db = ctx.sema.db;
44    defs.iter().filter_map(|def| {
45        let expr = match def {
46            ScopeDef::ModuleDef(ModuleDef::Const(it)) => Some(Expr::Const(*it)),
47            ScopeDef::ModuleDef(ModuleDef::Static(it)) => Some(Expr::Static(*it)),
48            ScopeDef::GenericParam(GenericParam::ConstParam(it)) => Some(Expr::ConstParam(*it)),
49            ScopeDef::Local(it) => Some(Expr::Local(*it)),
50            _ => None,
51        }?;
52
53        let ty = expr.ty(db);
54        if ty.contains_unknown() {
55            return None;
56        }
57
58        lookup.insert(ty.clone(), std::iter::once(expr.clone()));
59
60        ty.instantiate_with_errors().could_unify_with_deeply(db, &ctx.goal).then_some(expr)
61    })
62}
63
64/// # Associated constant tactic
65///
66/// Attempts to fulfill the goal by trying constants defined as associated items.
67/// Only considers them on types that are in scope.
68///
69/// # Arguments
70/// * `ctx` - Context for the term search
71/// * `defs` - Set of items in scope at term search target location
72/// * `lookup` - Lookup table for types
73///
74/// Returns iterator that yields elements that unify with `goal`.
75///
76/// _Note that there is no use of calling this tactic in every iteration as the output does not
77/// depend on the current state of `lookup`_
78pub(super) fn assoc_const<'a, 'lt, 'db, DB: HirDatabase>(
79    ctx: &'a TermSearchCtx<'_, 'db, DB>,
80    defs: &'a FxHashSet<ScopeDef<'db>>,
81    lookup: &'lt mut LookupTable<'db>,
82) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
83    let db = ctx.sema.db;
84    let module = ctx.scope.module();
85
86    defs.iter()
87        .filter_map(|def| match def {
88            ScopeDef::ModuleDef(ModuleDef::Adt(it)) => Some(it),
89            _ => None,
90        })
91        .flat_map(|it| Impl::all_for_type(db, it.ty(db)))
92        .filter(|it| !it.is_unsafe(db))
93        .flat_map(|it| it.items(db))
94        .filter(move |it| it.is_visible_from(db, module))
95        .filter_map(AssocItem::as_const)
96        .filter_map(|it| {
97            if it.attrs(db).is_unstable() {
98                return None;
99            }
100
101            let expr = Expr::Const(it);
102            let ty = it.ty(db);
103
104            if ty.contains_unknown() {
105                return None;
106            }
107
108            lookup.insert(ty.clone(), std::iter::once(expr.clone()));
109
110            ty.instantiate_with_errors().could_unify_with_deeply(db, &ctx.goal).then_some(expr)
111        })
112}
113
114/// # Data constructor tactic
115///
116/// Attempts different data constructors for enums and structs in scope
117///
118/// Updates lookup by new types reached and returns iterator that yields
119/// elements that unify with `goal`.
120///
121/// # Arguments
122/// * `ctx` - Context for the term search
123/// * `defs` - Set of items in scope at term search target location
124/// * `lookup` - Lookup table for types
125/// * `should_continue` - Function that indicates when to stop iterating
126pub(super) fn data_constructor<'a, 'lt, 'db, DB: HirDatabase>(
127    ctx: &'a TermSearchCtx<'_, 'db, DB>,
128    _defs: &'a FxHashSet<ScopeDef<'db>>,
129    lookup: &'lt mut LookupTable<'db>,
130    should_continue: &'a dyn std::ops::Fn() -> bool,
131) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
132    let db = ctx.sema.db;
133    let module = ctx.scope.module();
134    lookup
135        .types_wishlist()
136        .clone()
137        .into_iter()
138        .chain(iter::once(ctx.goal.clone()))
139        .filter_map(|ty| ty.as_adt().map(|adt| (adt, ty)))
140        .filter(|_| should_continue())
141        .filter_map(move |(adt, ty)| match adt {
142            Adt::Struct(strukt) => {
143                // Ignore unstable or not visible
144                if strukt.is_unstable(db) || !strukt.is_visible_from(db, module) {
145                    return None;
146                }
147
148                let generics = GenericDef::from(strukt);
149
150                // We currently do not check lifetime bounds so ignore all types that have something to do
151                // with them
152                if !generics.lifetime_params(db).is_empty() {
153                    return None;
154                }
155
156                if ty.contains_unknown() {
157                    return None;
158                }
159
160                let fields = strukt.fields(db);
161                // Check if all fields are visible, otherwise we cannot fill them
162                if fields.iter().any(|it| !it.is_visible_from(db, module)) {
163                    return None;
164                }
165
166                let generics: Vec<_> = ty.type_arguments().collect();
167
168                // Early exit if some param cannot be filled from lookup
169                let param_exprs: Vec<Vec<Expr<'_>>> = fields
170                    .into_iter()
171                    .map(|field| {
172                        lookup.find(db, &field.ty(db).instantiate(generics.iter().cloned()))
173                    })
174                    .collect::<Option<_>>()?;
175
176                // Note that we need special case for 0 param constructors because of multi cartesian
177                // product
178                let exprs: Vec<Expr<'_>> = if param_exprs.is_empty() {
179                    vec![Expr::Struct { strukt, generics, params: Vec::new() }]
180                } else {
181                    param_exprs
182                        .into_iter()
183                        .multi_cartesian_product()
184                        .map(|params| Expr::Struct { strukt, generics: generics.clone(), params })
185                        .collect()
186                };
187
188                lookup.insert(ty.clone(), exprs.iter().cloned());
189                Some((ty, exprs))
190            }
191            Adt::Enum(enum_) => {
192                // Ignore unstable or not visible
193                if enum_.is_unstable(db) || !enum_.is_visible_from(db, module) {
194                    return None;
195                }
196
197                let generics = GenericDef::from(enum_);
198                // We currently do not check lifetime bounds so ignore all types that have something to do
199                // with them
200                if !generics.lifetime_params(db).is_empty() {
201                    return None;
202                }
203
204                if ty.contains_unknown() {
205                    return None;
206                }
207
208                let generics: Vec<_> = ty.type_arguments().collect();
209                let exprs = enum_
210                    .variants(db)
211                    .into_iter()
212                    .filter_map(|variant| {
213                        // Early exit if some param cannot be filled from lookup
214                        let param_exprs: Vec<Vec<Expr<'_>>> = variant
215                            .fields(db)
216                            .into_iter()
217                            .map(|field| {
218                                lookup.find(db, &field.ty(db).instantiate(generics.iter().cloned()))
219                            })
220                            .collect::<Option<_>>()?;
221
222                        // Note that we need special case for 0 param constructors because of multi cartesian
223                        // product
224                        let variant_exprs: Vec<Expr<'_>> = if param_exprs.is_empty() {
225                            vec![Expr::Variant {
226                                variant,
227                                generics: generics.clone(),
228                                params: Vec::new(),
229                            }]
230                        } else {
231                            param_exprs
232                                .into_iter()
233                                .multi_cartesian_product()
234                                .map(|params| Expr::Variant {
235                                    variant,
236                                    generics: generics.clone(),
237                                    params,
238                                })
239                                .collect()
240                        };
241                        lookup.insert(ty.clone(), variant_exprs.iter().cloned());
242                        Some(variant_exprs)
243                    })
244                    .flatten()
245                    .collect();
246
247                Some((ty, exprs))
248            }
249            Adt::Union(_) => None,
250        })
251        .filter_map(|(ty, exprs)| {
252            ty.instantiate_with_errors().could_unify_with_deeply(db, &ctx.goal).then_some(exprs)
253        })
254        .flatten()
255}
256
257/// # Free function tactic
258///
259/// Attempts to call different functions in scope with parameters from lookup table.
260/// Functions that include generics are not used for performance reasons.
261///
262/// Updates lookup by new types reached and returns iterator that yields
263/// elements that unify with `goal`.
264///
265/// # Arguments
266/// * `ctx` - Context for the term search
267/// * `defs` - Set of items in scope at term search target location
268/// * `lookup` - Lookup table for types
269/// * `should_continue` - Function that indicates when to stop iterating
270pub(super) fn free_function<'a, 'lt, 'db, DB: HirDatabase>(
271    ctx: &'a TermSearchCtx<'_, 'db, DB>,
272    defs: &'a FxHashSet<ScopeDef<'db>>,
273    lookup: &'lt mut LookupTable<'db>,
274    should_continue: &'a dyn std::ops::Fn() -> bool,
275) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
276    let db = ctx.sema.db;
277    let module = ctx.scope.module();
278    defs.iter()
279        .filter_map(move |def| match def {
280            ScopeDef::ModuleDef(ModuleDef::Function(it)) => {
281                let generics = GenericDef::from(*it);
282
283                // Ignore const params for now
284                let type_params = generics
285                    .type_or_const_params(db)
286                    .into_iter()
287                    .map(|it| it.as_type_param(db))
288                    .collect::<Option<Vec<TypeParam>>>()?;
289
290                // Ignore lifetimes as we do not check them
291                if !generics.lifetime_params(db).is_empty() {
292                    return None;
293                }
294
295                // Only account for stable type parameters for now, unstable params can be default
296                // tho, for example in `Box<T, #[unstable] A: Allocator>`
297                if type_params.iter().any(|it| it.is_unstable(db) && it.default(db).is_none()) {
298                    return None;
299                }
300
301                let non_default_type_params_len =
302                    type_params.iter().filter(|it| it.default(db).is_none()).count();
303
304                // Ignore bigger number of generics for now as they kill the performance
305                if non_default_type_params_len > 0 {
306                    return None;
307                }
308
309                let generic_params = lookup
310                    .iter_types()
311                    .collect::<Vec<_>>() // Force take ownership
312                    .into_iter()
313                    .permutations(non_default_type_params_len);
314
315                let exprs: Vec<_> = generic_params
316                    .filter(|_| should_continue())
317                    .filter_map(|generics| {
318                        // Insert default type params
319                        let mut g = generics.into_iter();
320                        let generics: Vec<_> = type_params
321                            .iter()
322                            .map(|it| match it.default(db) {
323                                Some(ty) => Some(ty),
324                                None => {
325                                    let generic = g.next().expect("Missing type param");
326                                    // Filter out generics that do not unify due to trait bounds
327                                    it.ty(db).could_unify_with(db, &generic).then_some(generic)
328                                }
329                            })
330                            .collect::<Option<_>>()?;
331
332                        let ret_ty = it.ret_type(db).instantiate(generics.iter().cloned());
333                        // Filter out private and unsafe functions
334                        if !it.is_visible_from(db, module)
335                            || it.is_unsafe_to_call(
336                                db,
337                                None,
338                                crate::Crate::from(ctx.scope.resolver().krate()).edition(db),
339                            )
340                            || it.is_unstable(db)
341                            || ret_ty.is_raw_ptr()
342                        {
343                            return None;
344                        }
345
346                        // Early exit if some param cannot be filled from lookup
347                        let param_exprs: Vec<Vec<Expr<'_>>> = it
348                            .params_without_self(db)
349                            .into_iter()
350                            .map(|field| {
351                                let ty = &field.ty().instantiate(&generics);
352                                match ty.is_mutable_reference() {
353                                    true => None,
354                                    false => lookup.find_autoref(db, ty),
355                                }
356                            })
357                            .collect::<Option<_>>()?;
358
359                        // Note that we need special case for 0 param constructors because of multi cartesian
360                        // product
361                        let fn_exprs: Vec<Expr<'_>> = if param_exprs.is_empty() {
362                            vec![Expr::Function { func: *it, generics, params: Vec::new() }]
363                        } else {
364                            param_exprs
365                                .into_iter()
366                                .multi_cartesian_product()
367                                .map(|params| Expr::Function {
368                                    func: *it,
369                                    generics: generics.clone(),
370
371                                    params,
372                                })
373                                .collect()
374                        };
375
376                        lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned());
377                        Some((ret_ty, fn_exprs))
378                    })
379                    .collect();
380                Some(exprs)
381            }
382            _ => None,
383        })
384        .flatten()
385        .filter_map(|(ty, exprs)| {
386            ty.instantiate_with_errors().could_unify_with_deeply(db, &ctx.goal).then_some(exprs)
387        })
388        .flatten()
389}
390
391/// # Impl method tactic
392///
393/// Attempts to call methods on types from lookup table.
394/// This includes both functions from direct impl blocks as well as functions from traits.
395/// Methods defined in impl blocks that are generic and methods that are themselves have
396/// generics are ignored for performance reasons.
397///
398/// Updates lookup by new types reached and returns iterator that yields
399/// elements that unify with `goal`.
400///
401/// # Arguments
402/// * `ctx` - Context for the term search
403/// * `defs` - Set of items in scope at term search target location
404/// * `lookup` - Lookup table for types
405/// * `should_continue` - Function that indicates when to stop iterating
406pub(super) fn impl_method<'a, 'lt, 'db, DB: HirDatabase>(
407    ctx: &'a TermSearchCtx<'_, 'db, DB>,
408    _defs: &'a FxHashSet<ScopeDef<'db>>,
409    lookup: &'lt mut LookupTable<'db>,
410    should_continue: &'a dyn std::ops::Fn() -> bool,
411) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
412    let db = ctx.sema.db;
413    let module = ctx.scope.module();
414    lookup
415        .new_types(NewTypesKey::ImplMethod)
416        .into_iter()
417        .filter(|ty| !ty.type_arguments().any(|it| it.contains_unknown()))
418        .filter(|_| should_continue())
419        .flat_map(|ty| {
420            Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp))
421        })
422        .flat_map(|(ty, imp)| imp.items(db).into_iter().map(move |item| (imp, ty.clone(), item)))
423        .filter_map(|(imp, ty, it)| match it {
424            AssocItem::Function(f) => Some((imp, ty, f)),
425            _ => None,
426        })
427        .filter(|_| should_continue())
428        .filter_map(move |(imp, ty, it)| {
429            let fn_generics = GenericDef::from(it);
430            let imp_generics = GenericDef::from(imp);
431
432            // Ignore all functions that have something to do with lifetimes as we don't check them
433            if !fn_generics.lifetime_params(db).is_empty()
434                || !imp_generics.lifetime_params(db).is_empty()
435            {
436                return None;
437            }
438
439            // Ignore functions without self param
440            if !it.has_self_param(db) {
441                return None;
442            }
443
444            // Filter out private and unsafe functions
445            if !it.is_visible_from(db, module)
446                || it.is_unsafe_to_call(
447                    db,
448                    None,
449                    crate::Crate::from(ctx.scope.resolver().krate()).edition(db),
450                )
451                || it.is_unstable(db)
452            {
453                return None;
454            }
455
456            // Ignore functions with generics for now as they kill the performance
457            // Also checking bounds for generics is problematic
458            if !fn_generics.type_or_const_params(db).is_empty() {
459                return None;
460            }
461
462            let ret_ty = it.ret_type(db).instantiate(ty.type_arguments());
463
464            // Ignore functions that do not change the type
465            if ty.instantiate_with_errors().could_unify_with_deeply(db, &ret_ty) {
466                return None;
467            }
468
469            let self_ty =
470                it.self_param(db).expect("No self param").ty(db).instantiate(ty.type_arguments());
471
472            // Ignore functions that have different self type
473            if !self_ty.autoderef(db).any(|s_ty| ty == s_ty) {
474                return None;
475            }
476
477            let target_type_exprs = lookup.find(db, &ty).expect("Type not in lookup");
478
479            // Early exit if some param cannot be filled from lookup
480            let param_exprs: Vec<Vec<Expr<'_>>> = it
481                .params_without_self(db)
482                .into_iter()
483                .map(|field| lookup.find_autoref(db, &field.ty().instantiate(ty.type_arguments())))
484                .collect::<Option<_>>()?;
485
486            let generics: Vec<_> = ty.type_arguments().collect();
487            let fn_exprs: Vec<Expr<'_>> = std::iter::once(target_type_exprs)
488                .chain(param_exprs)
489                .multi_cartesian_product()
490                .map(|params| {
491                    let mut params = params.into_iter();
492                    let target = Box::new(params.next().unwrap());
493                    Expr::Method {
494                        func: it,
495                        generics: generics.clone(),
496                        target,
497                        params: params.collect(),
498                    }
499                })
500                .collect();
501
502            Some((ret_ty, fn_exprs))
503        })
504        .filter_map(|(ty, exprs)| {
505            ty.instantiate_with_errors().could_unify_with_deeply(db, &ctx.goal).then_some(exprs)
506        })
507        .flatten()
508}
509
510/// # Struct projection tactic
511///
512/// Attempts different struct fields (`foo.bar.baz`)
513///
514/// Updates lookup by new types reached and returns iterator that yields
515/// elements that unify with `goal`.
516///
517/// # Arguments
518/// * `ctx` - Context for the term search
519/// * `defs` - Set of items in scope at term search target location
520/// * `lookup` - Lookup table for types
521/// * `should_continue` - Function that indicates when to stop iterating
522pub(super) fn struct_projection<'a, 'lt, 'db, DB: HirDatabase>(
523    ctx: &'a TermSearchCtx<'_, 'db, DB>,
524    _defs: &'a FxHashSet<ScopeDef<'db>>,
525    lookup: &'lt mut LookupTable<'db>,
526    should_continue: &'a dyn std::ops::Fn() -> bool,
527) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
528    let db = ctx.sema.db;
529    let module = ctx.scope.module();
530    lookup
531        .new_types(NewTypesKey::StructProjection)
532        .into_iter()
533        .map(|ty| (ty.clone(), lookup.find(db, &ty).expect("Expr not in lookup")))
534        .filter(|_| should_continue())
535        .flat_map(move |(ty, targets)| {
536            ty.fields(db).into_iter().filter_map(move |(field, filed_ty)| {
537                if !field.is_visible_from(db, module) {
538                    return None;
539                }
540                let exprs = targets
541                    .clone()
542                    .into_iter()
543                    .map(move |target| Expr::Field { field, expr: Box::new(target) });
544                Some((filed_ty, exprs))
545            })
546        })
547        .filter_map(|(ty, exprs)| {
548            ty.instantiate_with_errors().could_unify_with_deeply(db, &ctx.goal).then_some(exprs)
549        })
550        .flatten()
551}
552
553/// # Famous types tactic
554///
555/// Attempts different values of well known types such as `true` or `false`.
556///
557/// Updates lookup by new types reached and returns iterator that yields
558/// elements that unify with `goal`.
559///
560/// _Note that there is no point of calling it iteratively as the output is always the same_
561///
562/// # Arguments
563/// * `ctx` - Context for the term search
564/// * `defs` - Set of items in scope at term search target location
565/// * `lookup` - Lookup table for types
566pub(super) fn famous_types<'a, 'lt, 'db, DB: HirDatabase>(
567    ctx: &'a TermSearchCtx<'_, 'db, DB>,
568    _defs: &'a FxHashSet<ScopeDef<'db>>,
569    lookup: &'lt mut LookupTable<'db>,
570) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
571    let db = ctx.sema.db;
572    let bool_ty = BuiltinType::bool().ty(db);
573    let unit_ty = Type::new_unit();
574    [
575        Expr::FamousType { ty: bool_ty.clone(), value: "true" },
576        Expr::FamousType { ty: bool_ty, value: "false" },
577        Expr::FamousType { ty: unit_ty, value: "()" },
578    ]
579    .into_iter()
580    .inspect(|exprs| {
581        lookup.insert(exprs.ty(db), std::iter::once(exprs.clone()));
582    })
583    .filter(|expr| expr.ty(db).instantiate_with_errors().could_unify_with_deeply(db, &ctx.goal))
584}
585
586/// # Impl static method (without self type) tactic
587///
588/// Attempts different functions from impl blocks that take no self parameter.
589///
590/// Updates lookup by new types reached and returns iterator that yields
591/// elements that unify with `goal`.
592///
593/// # Arguments
594/// * `ctx` - Context for the term search
595/// * `defs` - Set of items in scope at term search target location
596/// * `lookup` - Lookup table for types
597/// * `should_continue` - Function that indicates when to stop iterating
598pub(super) fn impl_static_method<'a, 'lt, 'db, DB: HirDatabase>(
599    ctx: &'a TermSearchCtx<'_, 'db, DB>,
600    _defs: &'a FxHashSet<ScopeDef<'db>>,
601    lookup: &'lt mut LookupTable<'db>,
602    should_continue: &'a dyn std::ops::Fn() -> bool,
603) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
604    let db = ctx.sema.db;
605    let module = ctx.scope.module();
606    lookup
607        .types_wishlist()
608        .clone()
609        .into_iter()
610        .chain(iter::once(ctx.goal.clone()))
611        .filter(|ty| !ty.type_arguments().any(|it| it.contains_unknown()))
612        .filter(|_| should_continue())
613        .flat_map(|ty| {
614            Impl::all_for_type(db, ty.clone()).into_iter().map(move |imp| (ty.clone(), imp))
615        })
616        .filter(|(_, imp)| !imp.is_unsafe(db))
617        .flat_map(|(ty, imp)| imp.items(db).into_iter().map(move |item| (imp, ty.clone(), item)))
618        .filter_map(|(imp, ty, it)| match it {
619            AssocItem::Function(f) => Some((imp, ty, f)),
620            _ => None,
621        })
622        .filter(|_| should_continue())
623        .filter_map(move |(imp, ty, it)| {
624            let fn_generics = GenericDef::from(it);
625            let imp_generics = GenericDef::from(imp);
626
627            // Ignore all functions that have something to do with lifetimes as we don't check them
628            if !fn_generics.lifetime_params(db).is_empty()
629                || !imp_generics.lifetime_params(db).is_empty()
630            {
631                return None;
632            }
633
634            // Ignore functions with self param
635            if it.has_self_param(db) {
636                return None;
637            }
638
639            // Filter out private and unsafe functions
640            if !it.is_visible_from(db, module)
641                || it.is_unsafe_to_call(
642                    db,
643                    None,
644                    crate::Crate::from(ctx.scope.resolver().krate()).edition(db),
645                )
646                || it.is_unstable(db)
647            {
648                return None;
649            }
650
651            // Ignore functions with generics for now as they kill the performance
652            // Also checking bounds for generics is problematic
653            if !fn_generics.type_or_const_params(db).is_empty() {
654                return None;
655            }
656
657            let ret_ty = it.ret_type(db).instantiate(ty.type_arguments());
658
659            // Early exit if some param cannot be filled from lookup
660            let param_exprs: Vec<Vec<Expr<'_>>> = it
661                .params_without_self(db)
662                .into_iter()
663                .map(|field| lookup.find_autoref(db, &field.ty().instantiate(ty.type_arguments())))
664                .collect::<Option<_>>()?;
665
666            // Note that we need special case for 0 param constructors because of multi cartesian
667            // product
668            let generics = ty.type_arguments().collect();
669            let fn_exprs: Vec<Expr<'_>> = if param_exprs.is_empty() {
670                vec![Expr::Function { func: it, generics, params: Vec::new() }]
671            } else {
672                param_exprs
673                    .into_iter()
674                    .multi_cartesian_product()
675                    .map(|params| Expr::Function { func: it, generics: generics.clone(), params })
676                    .collect()
677            };
678
679            lookup.insert(ret_ty.clone(), fn_exprs.iter().cloned());
680
681            Some((ret_ty, fn_exprs))
682        })
683        .filter_map(|(ty, exprs)| {
684            ty.instantiate_with_errors().could_unify_with_deeply(db, &ctx.goal).then_some(exprs)
685        })
686        .flatten()
687}
688
689/// # Make tuple tactic
690///
691/// Attempts to create tuple types if any are listed in types wishlist
692///
693/// Updates lookup by new types reached and returns iterator that yields
694/// elements that unify with `goal`.
695///
696/// # Arguments
697/// * `ctx` - Context for the term search
698/// * `defs` - Set of items in scope at term search target location
699/// * `lookup` - Lookup table for types
700/// * `should_continue` - Function that indicates when to stop iterating
701pub(super) fn make_tuple<'a, 'lt, 'db, DB: HirDatabase>(
702    ctx: &'a TermSearchCtx<'_, 'db, DB>,
703    _defs: &'a FxHashSet<ScopeDef<'db>>,
704    lookup: &'lt mut LookupTable<'db>,
705    should_continue: &'a dyn std::ops::Fn() -> bool,
706) -> impl Iterator<Item = Expr<'db>> + use<'a, 'db, 'lt, DB> {
707    let db = ctx.sema.db;
708
709    lookup
710        .types_wishlist()
711        .clone()
712        .into_iter()
713        .filter(|_| should_continue())
714        .filter(|ty| ty.is_tuple())
715        .filter_map(move |ty| {
716            // Double check to not contain unknown
717            if ty.contains_unknown() {
718                return None;
719            }
720
721            // Early exit if some param cannot be filled from lookup
722            let param_exprs: Vec<Vec<Expr<'db>>> =
723                ty.type_arguments().map(|field| lookup.find(db, &field)).collect::<Option<_>>()?;
724
725            let exprs: Vec<Expr<'db>> = param_exprs
726                .into_iter()
727                .multi_cartesian_product()
728                .filter(|_| should_continue())
729                .map(|params| {
730                    let tys: Vec<Type<'_>> = params.iter().map(|it| it.ty(db)).collect();
731                    let tuple_ty = Type::new_tuple(db, &tys);
732
733                    let expr = Expr::Tuple { ty: tuple_ty.clone(), params };
734                    lookup.insert(tuple_ty, iter::once(expr.clone()));
735                    expr
736                })
737                .collect();
738
739            Some(exprs)
740        })
741        .flatten()
742        .filter_map(|expr| {
743            expr.ty(db)
744                .instantiate_with_errors()
745                .could_unify_with_deeply(db, &ctx.goal)
746                .then_some(expr)
747        })
748}