1use std::collections::HashMap;
2
3use crate::{
4 reconstruct_path_for_local,
5 types::{self, CrateMetadata, GenericArgs},
6 Parent,
7};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10use tracing::debug;
11
12use crate::{
13 compare::{Compare, Similarities},
14 query::Query,
15 Index,
16};
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub struct Hit {
20 pub id: types::Id,
21 pub name: String,
22 pub path: Vec<String>,
23 pub link: String,
24 pub docs: Option<String>,
25 pub signature: String,
26 #[serde(skip_serializing, skip_deserializing)]
27 similarities: Similarities,
28}
29
30impl Hit {
31 pub fn similarities(&self) -> &Similarities {
32 &self.similarities
33 }
34}
35
36impl PartialOrd for Hit {
37 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
38 self.similarities.partial_cmp(&other.similarities)
39 }
40}
41
42#[derive(Error, Debug)]
43pub enum SearchError {
44 #[error("crate `{0}` is not present in the index")]
45 CrateNotFound(CrateMetadata),
46
47 #[error("item with id `{0}` is not present in crate `{1}`")]
48 ItemNotFound(u32, CrateMetadata),
49}
50
51pub type Result<T> = std::result::Result<T, SearchError>;
52
53#[derive(Debug, Clone, Deserialize, Serialize)]
55pub enum Scope {
56 Crate(CrateMetadata),
58
59 Set(String, Vec<CrateMetadata>),
65}
66
67impl Scope {
68 pub fn url(&self) -> String {
69 match self {
70 Scope::Crate(krate) => format!(
71 "https://raw.githubusercontent.com/alpaylan/ruggle-index/main/crate/{}.bin",
72 krate
73 ),
74 Scope::Set(name, _) => format!(
75 "https://raw.githubusercontent.com/alpaylan/ruggle-index/main/set/{}.json",
76 name
77 ),
78 }
79 }
80 pub fn flatten(self) -> Vec<CrateMetadata> {
81 match self {
82 Scope::Crate(krate) => vec![krate],
83 Scope::Set(_, krates) => krates,
84 }
85 }
86}
87
88impl Index {
89 pub fn search(&self, query: &Query, scope: Scope, threshold: f32) -> Result<Vec<Hit>> {
93 tracing::debug!(
94 "searching with query: {:?}, scope: {:?}, threshold: {}",
95 query,
96 scope,
97 threshold
98 );
99 let mut hits = vec![];
100
101 let krates = scope.flatten();
102
103 for krate_metadata in krates {
104 let krate = self
105 .crates
106 .get(&krate_metadata)
107 .ok_or_else(|| SearchError::CrateNotFound(krate_metadata.clone()))?;
108
109 let parents = self
110 .parents
111 .get(&krate_metadata)
112 .expect("parent for a crate SHOULD ALWAYS be in 'parents' index");
113
114 for item in krate.index.values() {
115 tracing::trace!(?item);
116 match item.inner {
117 types::ItemEnum::Function(ref f) => {
118 let path = Self::path_and_link(krate, item, None, parents)?;
119 tracing::trace!(?path);
120 let sims = self.compare(query, item, krate, None);
121 tracing::trace!(?sims);
122
123 if sims.score() < threshold {
124 debug!(?item, ?path, ?sims, score = ?sims.score());
125 hits.push(Hit {
126 id: item.id,
127 name: item.name.clone().unwrap(), path: path.pathify(),
129 link: path.link(),
130 docs: item.docs.clone(),
131 signature: format_fn_signature(
132 item.name.as_deref().unwrap_or(""),
133 &f.sig,
134 ),
135 similarities: sims,
136 });
137 }
138 }
139 types::ItemEnum::Impl(ref impl_) if impl_.trait_.is_none() => {
140 let assoc_items = impl_
141 .items
142 .iter()
143 .map(|id| {
144 krate.index.get(id).ok_or_else(|| {
145 SearchError::ItemNotFound(id.0, krate_metadata.clone())
146 })
147 })
148 .collect::<Result<Vec<_>>>()?;
149 for assoc_item in assoc_items {
150 if let types::ItemEnum::Function(ref m) = assoc_item.inner {
151 let path =
152 Self::path_and_link(krate, assoc_item, Some(impl_), parents)?;
153 let sims = self.compare(query, assoc_item, krate, Some(impl_));
154
155 if sims.score() < threshold {
156 hits.push(Hit {
157 id: assoc_item.id,
158 name: assoc_item.name.clone().unwrap(), path: path.pathify(),
160 link: path.link(),
161 docs: assoc_item.docs.clone(),
162 signature: format_fn_signature(
163 assoc_item.name.as_deref().unwrap_or(""),
164 &m.sig,
165 ),
166 similarities: sims,
167 })
168 }
169 }
170 }
171 }
172 _ => {}
174 }
175 }
176 }
177
178 hits.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
179
180 debug!("found {} hits", hits.len());
181 Ok(hits)
182 }
183
184 #[tracing::instrument(skip(self, krate))]
185 fn compare(
186 &self,
187 query: &Query,
188 item: &types::Item,
189 krate: &types::Crate,
190 impl_: Option<&types::Impl>,
191 ) -> Similarities {
192 let mut generics;
193 if let Some(impl_) = impl_ {
194 generics = impl_.generics.clone();
195 generics
196 .where_predicates
197 .push(types::WherePredicate::EqPredicate {
198 lhs: types::Type::Generic("Self".to_owned()),
199 rhs: types::Term::Type(impl_.for_.clone()),
200 });
201 } else {
202 generics = types::Generics::default()
203 }
204 let mut substs = HashMap::default();
205 let sims = query.compare(item, krate, &mut generics, &mut substs);
206 Similarities(sims)
207 }
208
209 fn path_and_link(
213 krate: &types::Crate,
214 item: &types::Item,
215 _impl_: Option<&types::Impl>,
216 parents: &HashMap<types::Id, Parent>,
217 ) -> Result<crate::Path> {
218 assert!(matches!(item.inner, types::ItemEnum::Function(_)));
219
220 let kinfo = krate.crate_metadata();
221
222 let get_path = |id: &types::Id| -> Result<crate::Path> {
223 if let Some(segs) = reconstruct_path_for_local(krate, id, parents) {
232 return Ok(segs);
233 }
234 Err(SearchError::ItemNotFound(id.0, kinfo.to_owned()))
235 };
236
237 let path = get_path(&item.id)?;
238
239 Ok(path)
240 }
251}
252
253fn format_fn_signature(name: &str, decl: &types::FunctionSignature) -> String {
254 let args = decl
255 .inputs
256 .iter()
257 .map(|(n, t)| {
258 if n.is_empty() {
259 render_type(t)
260 } else {
261 format!("{}: {}", n, render_type(t))
262 }
263 })
264 .collect::<Vec<_>>()
265 .join(", ");
266 let ret = match &decl.output {
267 None => "".to_string(),
268 Some(t) => format!(" -> {}", render_type(t)),
269 };
270 format!("fn {}({}){}", name, args, ret)
271}
272
273fn render_type(t: &types::Type) -> String {
274 match t {
275 types::Type::Primitive(p) => p.clone(),
276 types::Type::Generic(g) => g.clone(),
277 types::Type::Tuple(ts) => {
278 let inner = ts.iter().map(render_type).collect::<Vec<_>>().join(", ");
279 format!("({})", inner)
280 }
281 types::Type::Slice(inner) => format!("[{}]", render_type(inner)),
282 types::Type::Array { type_, .. } => format!("[{}]", render_type(type_)),
283 types::Type::RawPointer { is_mutable, type_ } => {
284 let m = if *is_mutable { "mut" } else { "const" };
285 format!("*{} {}", m, render_type(type_))
286 }
287 types::Type::BorrowedRef {
288 is_mutable, type_, ..
289 } => {
290 let m = if *is_mutable { "mut " } else { "" };
291 format!("&{}{}", m, render_type(type_))
292 }
293 types::Type::ResolvedPath(path) => {
294 let mut s = path.path.clone();
295 if let Some(ga) = &path.args {
296 if let types::GenericArgs::AngleBracketed { args, .. } =
297 (ga as &Box<GenericArgs>).as_ref()
298 {
299 let inner = args
300 .iter()
301 .filter_map(|ga| match ga {
302 types::GenericArg::Type(t) => Some(render_type(t)),
303 _ => None,
304 })
305 .collect::<Vec<_>>()
306 .join(", ");
307 if !inner.is_empty() {
308 s.push('<');
309 s.push_str(&inner);
310 s.push('>');
311 }
312 }
313 }
314 s
315 }
316 types::Type::QualifiedPath { name, .. } => name.clone(),
317 _ => "_".to_string(),
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use std::collections::HashSet;
324
325 use super::*;
326 use crate::compare::{DiscreteSimilarity::*, Similarity::*};
327 use crate::query::{FnDecl, FnRetTy, Function};
328 use crate::types::{FunctionHeader, Target};
329
330 fn krate() -> types::Crate {
331 types::Crate {
332 name: Some("test-crate".to_owned()),
333 root: types::Id(0),
334 crate_version: "0.0.0".to_owned(),
335 includes_private: false,
336 index: Default::default(),
337 paths: Default::default(),
338 external_crates: Default::default(),
339 format_version: 0,
340 target: Target::default(),
341 }
342 }
343
344 fn item(name: String, inner: types::ItemEnum) -> types::Item {
345 types::Item {
346 id: types::Id(0),
347 crate_id: 0,
348 name: Some(name),
349 span: None,
350 visibility: types::Visibility::Public,
351 docs: None,
352 links: HashMap::default(),
353 attrs: vec![],
354 deprecation: None,
355 inner,
356 }
357 }
358
359 fn foo() -> types::Function {
361 types::Function {
362 generics: types::Generics {
363 params: vec![],
364 where_predicates: vec![],
365 },
366 header: FunctionHeader::default(),
367 sig: types::FunctionSignature {
368 inputs: vec![],
369 output: None,
370 is_c_variadic: false,
371 },
372 has_body: false,
373 }
374 }
375
376 #[test]
377 fn compare_symbol() {
378 let query = Query {
379 name: Some("foo".to_owned()),
380 kind: None,
381 };
382
383 let function = foo();
384 let item = item("foo".to_owned(), types::ItemEnum::Function(function));
385 let krate = krate();
386 let mut generics = types::Generics::default();
387 let mut substs = HashMap::default();
388
389 assert_eq!(
390 query.compare(&item, &krate, &mut generics, &mut substs),
391 vec![Continuous(0.0)]
392 )
393 }
394
395 #[test]
396 fn compare_function() {
397 let q = Function {
398 decl: FnDecl {
399 inputs: Some(vec![]),
400 output: Some(FnRetTy::DefaultReturn),
401 },
402 qualifiers: HashSet::new(),
403 };
404
405 let i = foo();
406
407 let krate = krate();
408 let mut generics = types::Generics::default();
409 let mut substs = HashMap::default();
410
411 assert_eq!(
412 q.compare(&i, &krate, &mut generics, &mut substs),
413 vec![Discrete(Equivalent), Discrete(Equivalent)]
414 )
415 }
416}