1use hir_def::type_ref::Mutability;
4use hir_ty::db::HirDatabase;
5use itertools::Itertools;
6use rustc_hash::{FxHashMap, FxHashSet};
7
8use crate::{ModuleDef, ScopeDef, Semantics, SemanticsScope, Type};
9
10mod expr;
11pub use expr::Expr;
12
13mod tactics;
14
15#[derive(Debug, Hash, PartialEq, Eq)]
17enum NewTypesKey {
18 ImplMethod,
19 StructProjection,
20}
21
22#[derive(Debug)]
25enum AlternativeExprs<'db> {
26 Few(FxHashSet<Expr<'db>>),
28 Many,
30}
31
32impl<'db> AlternativeExprs<'db> {
33 fn new(threshold: usize, exprs: impl Iterator<Item = Expr<'db>>) -> AlternativeExprs<'db> {
39 let mut it = AlternativeExprs::Few(Default::default());
40 it.extend_with_threshold(threshold, exprs);
41 it
42 }
43
44 fn exprs(&self, ty: &Type<'db>) -> Vec<Expr<'db>> {
49 match self {
50 AlternativeExprs::Few(exprs) => exprs.iter().cloned().collect(),
51 AlternativeExprs::Many => vec![Expr::Many(ty.clone())],
52 }
53 }
54
55 fn extend_with_threshold(&mut self, threshold: usize, exprs: impl Iterator<Item = Expr<'db>>) {
61 match self {
62 AlternativeExprs::Few(tts) => {
63 for it in exprs {
64 if tts.len() > threshold {
65 *self = AlternativeExprs::Many;
66 break;
67 }
68
69 tts.insert(it);
70 }
71 }
72 AlternativeExprs::Many => (),
73 }
74 }
75
76 fn is_many(&self) -> bool {
77 matches!(self, AlternativeExprs::Many)
78 }
79}
80
81#[derive(Default, Debug)]
91struct LookupTable<'db> {
92 data: FxHashMap<Type<'db>, AlternativeExprs<'db>>,
94 new_types: FxHashMap<NewTypesKey, Vec<Type<'db>>>,
96 types_wishlist: FxHashSet<Type<'db>>,
98 many_threshold: usize,
100}
101
102impl<'db> LookupTable<'db> {
103 fn new(many_threshold: usize, goal: Type<'db>) -> Self {
105 let mut res = Self { many_threshold, ..Default::default() };
106 res.new_types.insert(NewTypesKey::ImplMethod, Vec::new());
107 res.new_types.insert(NewTypesKey::StructProjection, Vec::new());
108 res.types_wishlist.insert(goal);
109 res
110 }
111
112 fn find(&mut self, db: &'db dyn HirDatabase, ty: &Type<'db>) -> Option<Vec<Expr<'db>>> {
114 let res = self
115 .data
116 .iter()
117 .find(|(t, _)| t.could_unify_with_deeply(db, ty))
118 .map(|(t, tts)| tts.exprs(t));
119
120 if res.is_none() {
121 self.types_wishlist.insert(ty.clone());
122 }
123
124 if let Some(res) = &res
126 && res.len() > self.many_threshold
127 {
128 return Some(vec![Expr::Many(ty.clone())]);
129 }
130
131 res
132 }
133
134 fn find_autoref(&mut self, db: &'db dyn HirDatabase, ty: &Type<'db>) -> Option<Vec<Expr<'db>>> {
139 let res = self
140 .data
141 .iter()
142 .find(|(t, _)| t.could_unify_with_deeply(db, ty))
143 .map(|(t, it)| it.exprs(t))
144 .or_else(|| {
145 self.data
146 .iter()
147 .find(|(t, _)| {
148 t.add_reference(db, Mutability::Shared).could_unify_with_deeply(db, ty)
149 })
150 .map(|(t, it)| {
151 it.exprs(t)
152 .into_iter()
153 .map(|expr| Expr::Reference(Box::new(expr)))
154 .collect()
155 })
156 });
157
158 if res.is_none() {
159 self.types_wishlist.insert(ty.clone());
160 }
161
162 if let Some(res) = &res
164 && res.len() > self.many_threshold
165 {
166 return Some(vec![Expr::Many(ty.clone())]);
167 }
168
169 res
170 }
171
172 fn insert(&mut self, ty: Type<'db>, exprs: impl Iterator<Item = Expr<'db>>) {
178 match self.data.get_mut(&ty) {
179 Some(it) => {
180 it.extend_with_threshold(self.many_threshold, exprs);
181 if it.is_many() {
182 self.types_wishlist.remove(&ty);
183 }
184 }
185 None => {
186 self.data.insert(ty.clone(), AlternativeExprs::new(self.many_threshold, exprs));
187 for it in self.new_types.values_mut() {
188 it.push(ty.clone());
189 }
190 }
191 }
192 }
193
194 fn iter_types(&self) -> impl Iterator<Item = Type<'db>> + '_ {
196 self.data.keys().cloned()
197 }
198
199 fn new_types(&mut self, key: NewTypesKey) -> Vec<Type<'db>> {
203 match self.new_types.get_mut(&key) {
204 Some(it) => std::mem::take(it),
205 None => Vec::new(),
206 }
207 }
208
209 fn types_wishlist(&mut self) -> &FxHashSet<Type<'db>> {
211 &self.types_wishlist
212 }
213}
214
215#[derive(Debug)]
217pub struct TermSearchCtx<'a, 'db, DB: HirDatabase> {
218 pub sema: &'a Semantics<'db, DB>,
220 pub scope: &'a SemanticsScope<'db>,
222 pub goal: Type<'db>,
224 pub config: TermSearchConfig,
226}
227
228#[derive(Debug, Clone, Copy)]
230pub struct TermSearchConfig {
231 pub many_alternatives_threshold: usize,
233 pub fuel: u64,
235}
236
237impl Default for TermSearchConfig {
238 fn default() -> Self {
239 Self { many_alternatives_threshold: 1, fuel: 1200 }
240 }
241}
242
243pub fn term_search<'db, DB: HirDatabase>(ctx: &TermSearchCtx<'_, 'db, DB>) -> Vec<Expr<'db>> {
265 let module = ctx.scope.module();
266 let mut defs = FxHashSet::default();
267 defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(module)));
268
269 ctx.scope.process_all_names(&mut |_, def| {
270 defs.insert(def);
271 });
272
273 let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold, ctx.goal.clone());
274 let fuel = std::cell::Cell::new(ctx.config.fuel);
275
276 let should_continue = &|| {
277 let remaining = fuel.get();
278 fuel.set(remaining.saturating_sub(1));
279 if remaining == 0 {
280 tracing::debug!("fuel exhausted");
281 }
282 remaining > 0
283 };
284
285 let mut solutions: Vec<Expr<'db>> = tactics::trivial(ctx, &defs, &mut lookup).collect();
287 solutions.extend(tactics::famous_types(ctx, &defs, &mut lookup));
289 solutions.extend(tactics::assoc_const(ctx, &defs, &mut lookup));
290
291 while should_continue() {
292 solutions.extend(tactics::data_constructor(ctx, &defs, &mut lookup, should_continue));
293 solutions.extend(tactics::free_function(ctx, &defs, &mut lookup, should_continue));
294 solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup, should_continue));
295 solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup, should_continue));
296 solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup, should_continue));
297 solutions.extend(tactics::make_tuple(ctx, &defs, &mut lookup, should_continue));
298 }
299
300 solutions.into_iter().filter(|it| !it.is_many()).unique().collect()
301}