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(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<'db, DB: HirDatabase> {
218 pub sema: &'db Semantics<'db, DB>,
220 pub scope: &'db SemanticsScope<'db>,
222 pub goal: Type<'db>,
224 pub config: TermSearchConfig,
226}
227
228#[derive(Debug, Clone, Copy)]
230pub struct TermSearchConfig {
231 pub enable_borrowcheck: bool,
233 pub many_alternatives_threshold: usize,
235 pub fuel: u64,
237}
238
239impl Default for TermSearchConfig {
240 fn default() -> Self {
241 Self { enable_borrowcheck: true, many_alternatives_threshold: 1, fuel: 1200 }
242 }
243}
244
245pub fn term_search<'db, DB: HirDatabase>(ctx: &'db TermSearchCtx<'db, DB>) -> Vec<Expr<'db>> {
267 let module = ctx.scope.module();
268 let mut defs = FxHashSet::default();
269 defs.insert(ScopeDef::ModuleDef(ModuleDef::Module(module)));
270
271 ctx.scope.process_all_names(&mut |_, def| {
272 defs.insert(def);
273 });
274
275 let mut lookup = LookupTable::new(ctx.config.many_alternatives_threshold, ctx.goal.clone());
276 let fuel = std::cell::Cell::new(ctx.config.fuel);
277
278 let should_continue = &|| {
279 let remaining = fuel.get();
280 fuel.set(remaining.saturating_sub(1));
281 if remaining == 0 {
282 tracing::debug!("fuel exhausted");
283 }
284 remaining > 0
285 };
286
287 let mut solutions: Vec<Expr<'db>> = tactics::trivial(ctx, &defs, &mut lookup).collect();
289 solutions.extend(tactics::famous_types(ctx, &defs, &mut lookup));
291 solutions.extend(tactics::assoc_const(ctx, &defs, &mut lookup));
292
293 while should_continue() {
294 solutions.extend(tactics::data_constructor(ctx, &defs, &mut lookup, should_continue));
295 solutions.extend(tactics::free_function(ctx, &defs, &mut lookup, should_continue));
296 solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup, should_continue));
297 solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup, should_continue));
298 solutions.extend(tactics::impl_static_method(ctx, &defs, &mut lookup, should_continue));
299 solutions.extend(tactics::make_tuple(ctx, &defs, &mut lookup, should_continue));
300 }
301
302 solutions.into_iter().filter(|it| !it.is_many()).unique().collect()
303}