1use std::collections::HashMap;
2
3use rustdoc_types as types;
4use serde::Serialize;
5use thiserror::Error;
6
7use crate::{
8 compare::{Compare, Similarities},
9 query::Query,
10 Index,
11};
12
13#[derive(Debug, Clone, PartialEq, Serialize)]
14pub struct Hit {
15 pub name: String,
16 pub path: Vec<String>,
17 pub link: Vec<String>,
18 pub docs: Option<String>,
19 #[serde(skip)]
20 similarities: Similarities,
21}
22
23impl Hit {
24 pub fn similarities(&self) -> &Similarities {
25 &self.similarities
26 }
27}
28
29impl PartialOrd for Hit {
30 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
31 self.similarities.partial_cmp(&other.similarities)
32 }
33}
34
35#[derive(Error, Debug)]
36pub enum SearchError {
37 #[error("crate `{0}` is not present in the index")]
38 CrateNotFound(String),
39
40 #[error("item with id `{0}` is not present in crate `{1}`")]
41 ItemNotFound(String, String),
42}
43
44pub type Result<T> = std::result::Result<T, SearchError>;
45
46#[derive(Debug, Clone)]
48pub enum Scope {
49 Crate(String),
51
52 Set(Vec<String>),
58}
59
60impl Scope {
61 pub fn flatten(self) -> Vec<String> {
62 match self {
63 Scope::Crate(krate) => vec![krate],
64 Scope::Set(krates) => krates,
65 }
66 }
67}
68
69impl Index {
70 pub fn search(&self, query: &Query, scope: Scope, threshold: f32) -> Result<Vec<Hit>> {
74 let mut hits = vec![];
75
76 let krates = scope.flatten();
77 for krate_name in krates {
78 let krate = self
79 .crates
80 .get(&krate_name)
81 .ok_or(SearchError::CrateNotFound(krate_name.clone()))?;
82 for item in krate.index.values() {
83 match item.inner {
84 types::ItemEnum::Function(_) => {
85 let (path, link) = Self::path_and_link(krate, &krate_name, item, None)?;
86 let sims = self.compare(query, item, krate, None);
87
88 if sims.score() < threshold {
89 hits.push(Hit {
90 name: item.name.clone().unwrap(), path,
92 link,
93 docs: item.docs.clone(),
94 similarities: sims,
95 });
96 }
97 }
98 types::ItemEnum::Impl(ref impl_) if impl_.trait_.is_none() => {
99 let assoc_items = impl_
100 .items
101 .iter()
102 .map(|id| {
103 krate.index.get(id).ok_or(SearchError::ItemNotFound(
104 id.0.clone(),
105 krate_name.clone(),
106 ))
107 })
108 .collect::<Result<Vec<_>>>()?;
109 for assoc_item in assoc_items {
110 if let types::ItemEnum::Method(_) = assoc_item.inner {
111 let (path, link) = Self::path_and_link(
112 krate,
113 &krate_name,
114 assoc_item,
115 Some(impl_),
116 )?;
117 let sims = self.compare(query, assoc_item, krate, Some(impl_));
118
119 if sims.score() < threshold {
120 hits.push(Hit {
121 name: assoc_item.name.clone().unwrap(), path,
123 link,
124 docs: assoc_item.docs.clone(),
125 similarities: sims,
126 })
127 }
128 }
129 }
130 }
131 _ => {}
133 }
134 }
135 }
136
137 hits.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
138 Ok(hits)
139 }
140
141 #[tracing::instrument(skip(self, krate))]
142 fn compare(
143 &self,
144 query: &Query,
145 item: &types::Item,
146 krate: &types::Crate,
147 impl_: Option<&types::Impl>,
148 ) -> Similarities {
149 let mut generics;
150 if let Some(impl_) = impl_ {
151 generics = impl_.generics.clone();
152 generics
153 .where_predicates
154 .push(types::WherePredicate::EqPredicate {
155 lhs: types::Type::Generic("Self".to_owned()),
156 rhs: impl_.for_.clone(),
157 });
158 } else {
159 generics = types::Generics::default()
160 }
161 let mut substs = HashMap::default();
162
163 let sims = query.compare(item, krate, &mut generics, &mut substs);
164 Similarities(sims)
165 }
166
167 fn path_and_link(
171 krate: &types::Crate,
172 krate_name: &str,
173 item: &types::Item,
174 impl_: Option<&types::Impl>,
175 ) -> Result<(Vec<String>, Vec<String>)> {
176 assert!(matches!(
177 item.inner,
178 types::ItemEnum::Function(_) | types::ItemEnum::Method(_)
179 ));
180
181 use types::Type;
182
183 let get_path = |id: &types::Id| -> Result<Vec<String>> {
184 let path = krate
185 .paths
186 .get(id)
187 .ok_or(SearchError::ItemNotFound(
188 id.0.clone(),
189 krate_name.to_owned(),
190 ))?
191 .path
192 .clone();
193
194 Ok(path)
195 };
196
197 let mut path;
200 let mut link;
201 if let Some(impl_) = impl_ {
202 let recv;
203 match (&impl_.for_, &impl_.trait_) {
204 (_, Some(ref t)) => {
205 if let Type::ResolvedPath { name, id, .. } = t {
206 path = get_path(id)?;
207 recv = format!("trait.{}.html", name);
208 } else {
209 unreachable!()
211 }
212 }
213 (
214 Type::ResolvedPath {
215 ref name, ref id, ..
216 },
217 _,
218 ) => {
219 path = get_path(id)?;
220 let summary = krate.paths.get(id).ok_or(SearchError::ItemNotFound(
221 id.0.clone(),
222 krate_name.to_owned(),
223 ))?;
224 match summary.kind {
225 types::ItemKind::Union => recv = format!("union.{}.html", name),
226 types::ItemKind::Enum => recv = format!("enum.{}.html", name),
227 types::ItemKind::Struct => recv = format!("struct.{}.html", name),
228 _ => unreachable!(),
230 }
231 }
232 (Type::Primitive(ref prim), _) => {
233 path = vec![prim.clone()];
234 recv = format!("primitive.{}.html", prim);
235 }
236 (Type::Tuple(_), _) => {
237 path = vec!["tuple".to_owned()];
238 recv = "primitive.tuple.html".to_owned();
239 }
240 (Type::Slice(_), _) => {
241 path = vec!["slice".to_owned()];
242 recv = "primitive.slice.html".to_owned();
243 }
244 (Type::Array { .. }, _) => {
245 path = vec!["array".to_owned()];
246 recv = "primitive.array.html".to_owned();
247 }
248 (Type::RawPointer { .. }, _) => {
249 path = vec!["pointer".to_owned()];
250 recv = "primitive.pointer.html".to_owned();
251 }
252 (Type::BorrowedRef { .. }, _) => {
253 path = vec!["reference".to_owned()];
254 recv = "primitive.reference.html".to_owned();
255 }
256 _ => unreachable!(),
257 }
258 link = path.clone();
259 if let Some(l) = link.last_mut() {
260 *l = recv;
261 }
262 } else {
263 path = get_path(&item.id)?;
264 link = path.clone();
265 }
266
267 match item.inner {
268 types::ItemEnum::Function(_) => {
269 if let Some(l) = link.last_mut() {
270 *l = format!("fn.{}.html", l);
271 }
272 Ok((path.clone(), link))
273 }
274 types::ItemEnum::Method(_) => {
275 path.push(item.name.clone().unwrap()); link.push(item.name.clone().unwrap());
277 if let Some(l) = link.last_mut() {
278 *l = format!("#method.{}", l);
279 }
280 Ok((path.clone(), link))
281 }
282 _ => unreachable!(),
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use std::collections::HashSet;
291
292 use super::*;
293 use crate::compare::{DiscreteSimilarity::*, Similarity::*};
294 use crate::query::{FnDecl, FnRetTy, Function};
295
296 fn krate() -> types::Crate {
297 types::Crate {
298 root: types::Id("0:0".to_owned()),
299 crate_version: Some("0.0.0".to_owned()),
300 includes_private: false,
301 index: Default::default(),
302 paths: Default::default(),
303 external_crates: Default::default(),
304 format_version: 0,
305 }
306 }
307
308 fn item(name: String, inner: types::ItemEnum) -> types::Item {
309 types::Item {
310 id: types::Id("test".to_owned()),
311 crate_id: 0,
312 name: Some(name),
313 span: None,
314 visibility: types::Visibility::Public,
315 docs: None,
316 links: HashMap::default(),
317 attrs: vec![],
318 deprecation: None,
319 inner,
320 }
321 }
322
323 fn foo() -> types::Function {
325 types::Function {
326 decl: types::FnDecl {
327 inputs: vec![],
328 output: None,
329 c_variadic: false,
330 },
331 generics: types::Generics {
332 params: vec![],
333 where_predicates: vec![],
334 },
335 header: HashSet::default(),
336 abi: "rust".to_owned(),
337 }
338 }
339
340 #[test]
341 fn compare_symbol() {
342 let query = Query {
343 name: Some("foo".to_owned()),
344 kind: None,
345 };
346
347 let function = foo();
348 let item = item("foo".to_owned(), types::ItemEnum::Function(function));
349 let krate = krate();
350 let mut generics = types::Generics::default();
351 let mut substs = HashMap::default();
352
353 assert_eq!(
354 query.compare(&item, &krate, &mut generics, &mut substs),
355 vec![Continuous(0.0)]
356 )
357 }
358
359 #[test]
360 fn compare_function() {
361 let q = Function {
362 decl: FnDecl {
363 inputs: Some(vec![]),
364 output: Some(FnRetTy::DefaultReturn),
365 },
366 };
367
368 let i = foo();
369
370 let krate = krate();
371 let mut generics = types::Generics::default();
372 let mut substs = HashMap::default();
373
374 assert_eq!(
375 q.compare(&i, &krate, &mut generics, &mut substs),
376 vec![Discrete(Equivalent), Discrete(Equivalent)]
377 )
378 }
379}