1 use crate::ast_types::{ImplHeader, PathAlias, PathAliasKind, PathSegment};
2 use crate::core::MatchType::{
3     self, Const, Enum, EnumVariant, For, Function, IfLet, Let, Macro, Module, Static, Struct,
4     Trait, Type, WhileLet,
5 };
6 use crate::core::Namespace;
7 use crate::core::SearchType::{self, ExactMatch, StartsWith};
8 use crate::core::{BytePos, ByteRange, Coordinate, Match, Session, SessionExt, Src};
9 use crate::fileres::{get_crate_file, get_module_file};
10 use crate::nameres::resolve_path;
11 use crate::util::*;
12 use crate::{ast, scopes, typeinf};
13 use std::path::Path;
14 use std::{str, vec};
15 
16 /// The location of an import (`use` item) currently being resolved.
17 #[derive(PartialEq, Eq)]
18 struct PendingImport<'fp> {
19     filepath: &'fp Path,
20     range: ByteRange,
21 }
22 
23 /// A stack of imports (`use` items) currently being resolved.
24 type PendingImports<'stack, 'fp> = StackLinkedListNode<'stack, PendingImport<'fp>>;
25 
26 const GLOB_LIMIT: usize = 2;
27 /// Import information(pending imports, glob, and etc.)
28 pub struct ImportInfo<'stack, 'fp> {
29     /// A stack of imports currently being resolved
30     imports: PendingImports<'stack, 'fp>,
31     /// the max number of times where we can go through glob continuously
32     /// if current search path isn't constructed via glob, it's none
33     glob_limit: Option<usize>,
34 }
35 
36 impl<'stack, 'fp: 'stack> Default for ImportInfo<'stack, 'fp> {
default() -> Self37     fn default() -> Self {
38         ImportInfo {
39             imports: PendingImports::empty(),
40             glob_limit: None,
41         }
42     }
43 }
44 
45 #[derive(Clone, Debug, Eq, PartialEq)]
46 pub struct MatchCxt<'s, 'p> {
47     pub filepath: &'p Path,
48     pub search_str: &'s str,
49     pub range: ByteRange,
50     pub search_type: SearchType,
51     pub is_local: bool,
52 }
53 
54 impl<'s, 'p> MatchCxt<'s, 'p> {
get_key_ident( &self, blob: &str, keyword: &str, ignore: &[&str], ) -> Option<(BytePos, String)>55     fn get_key_ident(
56         &self,
57         blob: &str,
58         keyword: &str,
59         ignore: &[&str],
60     ) -> Option<(BytePos, String)> {
61         find_keyword(blob, keyword, ignore, self).map(|start| {
62             let s = match self.search_type {
63                 ExactMatch => self.search_str.to_owned(),
64                 StartsWith => {
65                     let end = find_ident_end(blob, start + BytePos(self.search_str.len()));
66                     blob[start.0..end.0].to_owned()
67                 }
68             };
69             (start, s)
70         })
71     }
72 }
73 
find_keyword( src: &str, pattern: &str, ignore: &[&str], context: &MatchCxt<'_, '_>, ) -> Option<BytePos>74 pub(crate) fn find_keyword(
75     src: &str,
76     pattern: &str,
77     ignore: &[&str],
78     context: &MatchCxt<'_, '_>,
79 ) -> Option<BytePos> {
80     find_keyword_impl(
81         src,
82         pattern,
83         context.search_str,
84         ignore,
85         context.search_type,
86         context.is_local,
87     )
88 }
89 
find_keyword_impl( src: &str, pattern: &str, search_str: &str, ignore: &[&str], search_type: SearchType, is_local: bool, ) -> Option<BytePos>90 fn find_keyword_impl(
91     src: &str,
92     pattern: &str,
93     search_str: &str,
94     ignore: &[&str],
95     search_type: SearchType,
96     is_local: bool,
97 ) -> Option<BytePos> {
98     let mut start = BytePos::ZERO;
99 
100     if let Some(offset) = strip_visibility(&src[..]) {
101         start += offset;
102     } else if !is_local {
103         // TODO: too about
104         return None;
105     }
106 
107     if ignore.len() > 0 {
108         start += strip_words(&src[start.0..], ignore);
109     }
110     // mandatory pattern\s+
111     if !src[start.0..].starts_with(pattern) {
112         return None;
113     }
114     // remove whitespaces ... must have one at least
115     start += pattern.len().into();
116     let oldstart = start;
117     for &b in src[start.0..].as_bytes() {
118         match b {
119             b if is_whitespace_byte(b) => start = start.increment(),
120             _ => break,
121         }
122     }
123     if start == oldstart {
124         return None;
125     }
126 
127     let search_str_len = search_str.len();
128     if src[start.0..].starts_with(search_str) {
129         match search_type {
130             StartsWith => Some(start),
131             ExactMatch => {
132                 if src.len() > start.0 + search_str_len
133                     && !is_ident_char(char_at(src, start.0 + search_str_len))
134                 {
135                     Some(start)
136                 } else {
137                     None
138                 }
139             }
140         }
141     } else {
142         None
143     }
144 }
145 
is_const_fn(src: &str, blob_range: ByteRange) -> bool146 fn is_const_fn(src: &str, blob_range: ByteRange) -> bool {
147     if let Some(b) = strip_word(&src[blob_range.to_range()], "const") {
148         let s = src[(blob_range.start + b).0..].trim_start();
149         s.starts_with("fn") || s.starts_with("unsafe")
150     } else {
151         false
152     }
153 }
154 
match_pattern_start( src: &str, context: &MatchCxt<'_, '_>, pattern: &str, ignore: &[&str], mtype: MatchType, ) -> Option<Match>155 fn match_pattern_start(
156     src: &str,
157     context: &MatchCxt<'_, '_>,
158     pattern: &str,
159     ignore: &[&str],
160     mtype: MatchType,
161 ) -> Option<Match> {
162     // ast currently doesn't contain the ident coords, so match them with a hacky
163     // string search
164 
165     let blob = &src[context.range.to_range()];
166     if let Some(start) = find_keyword(blob, pattern, ignore, context) {
167         if let Some(end) = blob[start.0..].find(|c: char| c == ':' || c.is_whitespace()) {
168             if blob[start.0 + end..].trim_start().chars().next() == Some(':') {
169                 let s = &blob[start.0..start.0 + end];
170                 return Some(Match {
171                     matchstr: s.to_owned(),
172                     filepath: context.filepath.to_path_buf(),
173                     point: context.range.start + start,
174                     coords: None,
175                     local: context.is_local,
176                     mtype: mtype,
177                     contextstr: first_line(blob),
178                     docs: String::new(),
179                 });
180             }
181         }
182     }
183     None
184 }
185 
match_const(msrc: &str, context: &MatchCxt<'_, '_>) -> Option<Match>186 pub fn match_const(msrc: &str, context: &MatchCxt<'_, '_>) -> Option<Match> {
187     if is_const_fn(msrc, context.range) {
188         return None;
189     }
190     // Here we don't have to ignore "unsafe"
191     match_pattern_start(msrc, context, "const", &[], Const)
192 }
193 
match_static(msrc: &str, context: &MatchCxt<'_, '_>) -> Option<Match>194 pub fn match_static(msrc: &str, context: &MatchCxt<'_, '_>) -> Option<Match> {
195     // Here we don't have to ignore "unsafe"
196     match_pattern_start(msrc, context, "static", &[], Static)
197 }
198 
match_let_impl(msrc: &str, context: &MatchCxt<'_, '_>, mtype: MatchType) -> Vec<Match>199 fn match_let_impl(msrc: &str, context: &MatchCxt<'_, '_>, mtype: MatchType) -> Vec<Match> {
200     let mut out = Vec::new();
201     let coords = ast::parse_pat_bind_stmt(msrc.to_owned());
202     for pat_range in coords {
203         let s = &msrc[pat_range.to_range()];
204         if symbol_matches(context.search_type, context.search_str, s) {
205             let start = context.range.start + pat_range.start;
206             debug!("match_pattern_let point is {:?}", start);
207             out.push(Match {
208                 matchstr: s.to_owned(),
209                 filepath: context.filepath.to_path_buf(),
210                 point: start,
211                 coords: None,
212                 local: context.is_local,
213                 mtype: mtype.clone(),
214                 contextstr: msrc.to_owned(),
215                 docs: String::new(),
216             });
217             if context.search_type == ExactMatch {
218                 break;
219             }
220         }
221     }
222     out
223 }
224 
match_if_let(msrc: &str, start: BytePos, context: &MatchCxt<'_, '_>) -> Vec<Match>225 pub fn match_if_let(msrc: &str, start: BytePos, context: &MatchCxt<'_, '_>) -> Vec<Match> {
226     match_let_impl(msrc, context, IfLet(start))
227 }
228 
match_while_let(msrc: &str, start: BytePos, context: &MatchCxt<'_, '_>) -> Vec<Match>229 pub fn match_while_let(msrc: &str, start: BytePos, context: &MatchCxt<'_, '_>) -> Vec<Match> {
230     match_let_impl(msrc, context, WhileLet(start))
231 }
232 
match_let(msrc: &str, start: BytePos, context: &MatchCxt<'_, '_>) -> Vec<Match>233 pub fn match_let(msrc: &str, start: BytePos, context: &MatchCxt<'_, '_>) -> Vec<Match> {
234     let blob = &msrc[context.range.to_range()];
235     if blob.starts_with("let ") && txt_matches(context.search_type, context.search_str, blob) {
236         match_let_impl(blob, context, Let(start))
237     } else {
238         Vec::new()
239     }
240 }
241 
match_for(msrc: &str, for_start: BytePos, context: &MatchCxt<'_, '_>) -> Vec<Match>242 pub fn match_for(msrc: &str, for_start: BytePos, context: &MatchCxt<'_, '_>) -> Vec<Match> {
243     let mut out = Vec::new();
244     let blob = &msrc[context.range.to_range()];
245     let coords = ast::parse_pat_bind_stmt(blob.to_owned());
246     for pat_range in coords {
247         let s = &blob[pat_range.to_range()];
248         if symbol_matches(context.search_type, context.search_str, s) {
249             let start = pat_range.start + context.range.start;
250             debug!("match_for point is {:?}, found ident {}", start, s);
251             out.push(Match {
252                 matchstr: s.to_owned(),
253                 filepath: context.filepath.to_path_buf(),
254                 point: start, // it's 'for ~' start
255                 coords: None,
256                 local: context.is_local,
257                 mtype: For(for_start),
258                 contextstr: blob.to_owned(),
259                 docs: String::new(),
260             });
261         }
262     }
263     out
264 }
265 
first_line(blob: &str) -> String266 pub fn first_line(blob: &str) -> String {
267     blob[..blob.find('\n').unwrap_or(blob.len())].to_owned()
268 }
269 
270 /// Get the match's cleaned up context string
271 ///
272 /// Strip all whitespace, including newlines in order to have a single line
273 /// context string.
get_context(blob: &str, context_end: &str) -> String274 pub fn get_context(blob: &str, context_end: &str) -> String {
275     blob[..blob.find(context_end).unwrap_or(blob.len())]
276         .split_whitespace()
277         .collect::<Vec<_>>()
278         .join(" ")
279 }
280 
match_extern_crate( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>281 pub fn match_extern_crate(
282     msrc: Src<'_>,
283     context: &MatchCxt<'_, '_>,
284     session: &Session<'_>,
285 ) -> Option<Match> {
286     let mut res = None;
287     let mut blob = &msrc[context.range.to_range()];
288 
289     // Temporary fix to parse reexported crates by skipping pub
290     // keyword until racer understands crate visibility.
291     if let Some(offset) = strip_visibility(blob) {
292         blob = &blob[offset.0..];
293     }
294 
295     if txt_matches(
296         context.search_type,
297         &format!("extern crate {}", context.search_str),
298         blob,
299     ) && !(txt_matches(
300         context.search_type,
301         &format!("extern crate {} as", context.search_str),
302         blob,
303     )) || (blob.starts_with("extern crate")
304         && txt_matches(
305             context.search_type,
306             &format!("as {}", context.search_str),
307             blob,
308         ))
309     {
310         debug!("found an extern crate: |{}|", blob);
311 
312         let extern_crate = ast::parse_extern_crate(blob.to_owned());
313 
314         if let Some(ref name) = extern_crate.name {
315             let realname = extern_crate.realname.as_ref().unwrap_or(name);
316             if let Some(cratepath) = get_crate_file(realname, context.filepath, session) {
317                 let raw_src = session.load_raw_file(&cratepath);
318                 res = Some(Match {
319                     matchstr: name.clone(),
320                     filepath: cratepath.to_path_buf(),
321                     point: BytePos::ZERO,
322                     coords: Some(Coordinate::start()),
323                     local: false,
324                     mtype: Module,
325                     contextstr: cratepath.to_str().unwrap().to_owned(),
326                     docs: find_mod_doc(&raw_src, BytePos::ZERO),
327                 });
328             }
329         }
330     }
331     res
332 }
333 
match_mod( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>334 pub fn match_mod(
335     msrc: Src<'_>,
336     context: &MatchCxt<'_, '_>,
337     session: &Session<'_>,
338 ) -> Option<Match> {
339     let blob = &msrc[context.range.to_range()];
340     let (start, s) = context.get_key_ident(blob, "mod", &[])?;
341     if blob.find('{').is_some() {
342         debug!("found a module inline: |{}|", blob);
343         return Some(Match {
344             matchstr: s,
345             filepath: context.filepath.to_path_buf(),
346             point: context.range.start + start,
347             coords: None,
348             local: false,
349             mtype: Module,
350             contextstr: context.filepath.to_str().unwrap().to_owned(),
351             docs: String::new(),
352         });
353     } else {
354         debug!("found a module declaration: |{}|", blob);
355         // the name of the file where we found the module declaration (foo.rs)
356         // without its extension!
357         let filename = context.filepath.file_stem()?;
358         let parent_path = context.filepath.parent()?;
359         // if we found the declaration in `src/foo.rs`, then let's look for the
360         // submodule in `src/foo/` as well!
361         let filename_subdir = parent_path.join(filename);
362         // if we are looking for "foo::bar", we have two cases:
363         //   1. we found `pub mod bar;` in either `src/foo/mod.rs`
364         // (or `src/lib.rs`). As such we are going to search for `bar.rs` in
365         // the same directory (`src/foo/`, or `src/` respectively).
366         //   2. we found `pub mod bar;` in `src/foo.rs`. This means that we also
367         // need to seach in `src/foo/` if it exists!
368         let search_path = if filename_subdir.exists() {
369             filename_subdir.as_path()
370         } else {
371             parent_path
372         };
373         match_mod_inner(msrc, context, session, search_path, s)
374     }
375 }
376 
match_mod_inner( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, search_path: &Path, s: String, ) -> Option<Match>377 fn match_mod_inner(
378     msrc: Src<'_>,
379     context: &MatchCxt<'_, '_>,
380     session: &Session<'_>,
381     search_path: &Path,
382     s: String,
383 ) -> Option<Match> {
384     let ranged_raw = session.load_raw_src_ranged(&msrc, context.filepath);
385     // get module from path attribute
386     if let Some(modpath) =
387         scopes::get_module_file_from_path(msrc, context.range.start, search_path, ranged_raw)
388     {
389         let doc_src = session.load_raw_file(&modpath);
390         return Some(Match {
391             matchstr: s,
392             filepath: modpath.to_path_buf(),
393             point: BytePos::ZERO,
394             coords: Some(Coordinate::start()),
395             local: false,
396             mtype: Module,
397             contextstr: modpath.to_str().unwrap().to_owned(),
398             docs: find_mod_doc(&doc_src, BytePos::ZERO),
399         });
400     }
401     // get internal module nesting
402     // e.g. is this in an inline submodule?  mod foo{ mod bar; }
403     // because if it is then we need to search further down the
404     // directory hierarchy - e.g. <cwd>/foo/bar.rs
405     let internalpath = scopes::get_local_module_path(msrc, context.range.start);
406     let mut searchdir = (*search_path).to_owned();
407     for s in internalpath {
408         searchdir.push(&s);
409     }
410     if let Some(modpath) = get_module_file(&s, &searchdir, session) {
411         let doc_src = session.load_raw_file(&modpath);
412         let context = modpath.to_str().unwrap().to_owned();
413         return Some(Match {
414             matchstr: s,
415             filepath: modpath,
416             point: BytePos::ZERO,
417             coords: Some(Coordinate::start()),
418             local: false,
419             mtype: Module,
420             contextstr: context,
421             docs: find_mod_doc(&doc_src, BytePos::ZERO),
422         });
423     }
424     None
425 }
426 
find_generics_end(blob: &str) -> Option<BytePos>427 fn find_generics_end(blob: &str) -> Option<BytePos> {
428     // Naive version that attempts to skip over attributes
429     let mut in_attr = false;
430     let mut attr_level = 0;
431 
432     let mut level = 0;
433     for (i, b) in blob.as_bytes().into_iter().enumerate() {
434         // Naively skip attributes `#[...]`
435         if in_attr {
436             match b {
437                 b'[' => attr_level += 1,
438                 b']' => {
439                     attr_level -=1;
440                     if attr_level == 0 {
441                         in_attr = false;
442                         continue;
443                     }
444                 },
445                 _ => continue,
446             }
447         }
448         // ...otherwise just try to find the last `>`
449         match b {
450             b'{' | b'(' | b';' => return None,
451             b'<' => level += 1,
452             b'>' => {
453                 level -= 1;
454                 if level == 0 {
455                     return Some(i.into());
456                 }
457             }
458             b'#' if blob.bytes().nth(i + 1) == Some(b'[') => in_attr = true,
459             _ => {}
460         }
461     }
462     None
463 }
464 
match_struct( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>465 pub fn match_struct(
466     msrc: Src<'_>,
467     context: &MatchCxt<'_, '_>,
468     session: &Session<'_>,
469 ) -> Option<Match> {
470     let blob = &msrc[context.range.to_range()];
471     let (start, s) = context.get_key_ident(blob, "struct", &[])?;
472 
473     debug!("found a struct |{}|", s);
474     let generics =
475         find_generics_end(&blob[start.0..]).map_or_else(Default::default, |generics_end| {
476             let header = format!("struct {}();", &blob[start.0..=(start + generics_end).0]);
477             ast::parse_generics(header, context.filepath)
478         });
479     let start = context.range.start + start;
480     let doc_src = session.load_raw_src_ranged(&msrc, context.filepath);
481     Some(Match {
482         matchstr: s,
483         filepath: context.filepath.to_path_buf(),
484         point: start,
485         coords: None,
486         local: context.is_local,
487         mtype: Struct(Box::new(generics)),
488         contextstr: get_context(blob, "{"),
489         docs: find_doc(&doc_src, start),
490     })
491 }
492 
match_union( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>493 pub fn match_union(
494     msrc: Src<'_>,
495     context: &MatchCxt<'_, '_>,
496     session: &Session<'_>,
497 ) -> Option<Match> {
498     let blob = &msrc[context.range.to_range()];
499     let (start, s) = context.get_key_ident(blob, "union", &[])?;
500 
501     debug!("found a union |{}|", s);
502     let generics =
503         find_generics_end(&blob[start.0..]).map_or_else(Default::default, |generics_end| {
504             let header = format!("union {}();", &blob[start.0..=(start + generics_end).0]);
505             ast::parse_generics(header, context.filepath)
506         });
507     let start = context.range.start + start;
508     let doc_src = session.load_raw_src_ranged(&msrc, context.filepath);
509     Some(Match {
510         matchstr: s,
511         filepath: context.filepath.to_path_buf(),
512         point: start,
513         coords: None,
514         local: context.is_local,
515         mtype: MatchType::Union(Box::new(generics)),
516         contextstr: get_context(blob, "{"),
517         docs: find_doc(&doc_src, start),
518     })
519 }
520 
match_type( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>521 pub fn match_type(
522     msrc: Src<'_>,
523     context: &MatchCxt<'_, '_>,
524     session: &Session<'_>,
525 ) -> Option<Match> {
526     let blob = &msrc[context.range.to_range()];
527     let (start, s) = context.get_key_ident(blob, "type", &[])?;
528     debug!("found!! a type {}", s);
529     // parse type here
530     let start = context.range.start + start;
531     let doc_src = session.load_raw_src_ranged(&msrc, context.filepath);
532     Some(Match {
533         matchstr: s,
534         filepath: context.filepath.to_path_buf(),
535         point: start,
536         coords: None,
537         local: context.is_local,
538         mtype: Type,
539         contextstr: first_line(blob),
540         docs: find_doc(&doc_src, start),
541     })
542 }
543 
match_trait( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>544 pub fn match_trait(
545     msrc: Src<'_>,
546     context: &MatchCxt<'_, '_>,
547     session: &Session<'_>,
548 ) -> Option<Match> {
549     let blob = &msrc[context.range.to_range()];
550     let (start, s) = context.get_key_ident(blob, "trait", &["unsafe"])?;
551     debug!("found!! a trait {}", s);
552     let start = context.range.start + start;
553     let doc_src = session.load_raw_src_ranged(&msrc, context.filepath);
554     Some(Match {
555         matchstr: s,
556         filepath: context.filepath.to_path_buf(),
557         point: start,
558         coords: None,
559         local: context.is_local,
560         mtype: Trait,
561         contextstr: get_context(blob, "{"),
562         docs: find_doc(&doc_src, start),
563     })
564 }
565 
match_enum_variants(msrc: &str, context: &MatchCxt<'_, '_>) -> Vec<Match>566 pub fn match_enum_variants(msrc: &str, context: &MatchCxt<'_, '_>) -> Vec<Match> {
567     let blob = &msrc[context.range.to_range()];
568     let mut out = Vec::new();
569     let parsed_enum = ast::parse_enum(blob.to_owned());
570     for (name, offset) in parsed_enum.values {
571         if name.starts_with(context.search_str) {
572             let start = context.range.start + offset;
573             let m = Match {
574                 matchstr: name,
575                 filepath: context.filepath.to_path_buf(),
576                 point: start,
577                 coords: None,
578                 local: context.is_local,
579                 mtype: EnumVariant(None),
580                 contextstr: first_line(&blob[offset.0..]),
581                 docs: find_doc(msrc, start),
582             };
583             out.push(m);
584         }
585     }
586     out
587 }
588 
match_enum( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>589 pub fn match_enum(
590     msrc: Src<'_>,
591     context: &MatchCxt<'_, '_>,
592     session: &Session<'_>,
593 ) -> Option<Match> {
594     let blob = &msrc[context.range.to_range()];
595     let (start, s) = context.get_key_ident(blob, "enum", &[])?;
596 
597     debug!("found!! an enum |{}|", s);
598 
599     let generics =
600         find_generics_end(&blob[start.0..]).map_or_else(Default::default, |generics_end| {
601             let header = format!("enum {}{{}}", &blob[start.0..=(start + generics_end).0]);
602             ast::parse_generics(header, context.filepath)
603         });
604     let start = context.range.start + start;
605     let doc_src = session.load_raw_src_ranged(&msrc, context.filepath);
606     Some(Match {
607         matchstr: s,
608         filepath: context.filepath.to_path_buf(),
609         point: start,
610         coords: None,
611         local: context.is_local,
612         mtype: Enum(Box::new(generics)),
613         contextstr: first_line(blob),
614         docs: find_doc(&doc_src, start),
615     })
616 }
617 
match_use( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, import_info: &ImportInfo<'_, '_>, ) -> Vec<Match>618 pub fn match_use(
619     msrc: Src<'_>,
620     context: &MatchCxt<'_, '_>,
621     session: &Session<'_>,
622     import_info: &ImportInfo<'_, '_>,
623 ) -> Vec<Match> {
624     let import = PendingImport {
625         filepath: context.filepath,
626         range: context.range,
627     };
628 
629     let blob = &msrc[context.range.to_range()];
630 
631     // If we're trying to resolve the same import recursively,
632     // do not return any matches this time.
633     if import_info.imports.contains(&import) {
634         debug!("import {} involved in a cycle; ignoring", blob);
635         return Vec::new();
636     }
637 
638     // Push this import on the stack of pending imports.
639     let pending_imports = import_info.imports.push(import);
640 
641     let mut out = Vec::new();
642 
643     if find_keyword_impl(blob, "use", "", &[], StartsWith, context.is_local).is_none() {
644         return out;
645     }
646 
647     let use_item = ast::parse_use(blob.to_owned());
648     debug!(
649         "[match_use] found item: {:?}, searchstr: {}",
650         use_item, context.search_str
651     );
652     // for speed up!
653     if !use_item.contains_glob && !txt_matches(context.search_type, context.search_str, blob) {
654         return out;
655     }
656     let mut import_info = ImportInfo {
657         imports: pending_imports,
658         glob_limit: import_info.glob_limit,
659     };
660     let alias_match = |ident, start, inner, cstr| Match {
661         matchstr: ident,
662         filepath: context.filepath.to_owned(),
663         point: context.range.start + start,
664         coords: None,
665         local: context.is_local,
666         mtype: MatchType::UseAlias(Box::new(inner)),
667         contextstr: cstr,
668         docs: String::new(),
669     };
670     // common utilities
671     macro_rules! with_match {
672         ($path:expr, $ns: expr, $f:expr) => {
673             let path_iter = resolve_path(
674                 $path,
675                 context.filepath,
676                 context.range.start,
677                 ExactMatch,
678                 $ns,
679                 session,
680                 &import_info,
681             );
682             for m in path_iter {
683                 out.push($f(m));
684                 if context.search_type == ExactMatch {
685                     return out;
686                 }
687             }
688         };
689     }
690     // let's find searchstr using path_aliases
691     for path_alias in use_item.path_list {
692         let PathAlias {
693             path: mut alias_path,
694             kind: alias_kind,
695             range: alias_range,
696         } = path_alias;
697         alias_path.set_prefix();
698         match alias_kind {
699             PathAliasKind::Ident(ref ident, rename_start) => {
700                 if !symbol_matches(context.search_type, context.search_str, &ident) {
701                     continue;
702                 }
703                 with_match!(&alias_path, Namespace::Path, |m: Match| {
704                     debug!("[match_use] PathAliasKind::Ident {:?} was found", ident);
705                     let rename_start = match rename_start {
706                         Some(r) => r,
707                         None => return m,
708                     };
709                     // if use A as B found, we treat this type as type alias
710                     let context_str = &msrc[alias_range.shift(context.range.start).to_range()];
711                     alias_match(ident.clone(), rename_start, m, context_str.to_owned())
712                 });
713             }
714             PathAliasKind::Self_(ref ident, rename_start) => {
715                 if let Some(last_seg) = alias_path.segments.last() {
716                     let search_name = if rename_start.is_some() {
717                         ident
718                     } else {
719                         &last_seg.name
720                     };
721                     if !symbol_matches(context.search_type, context.search_str, search_name) {
722                         continue;
723                     }
724                     with_match!(&alias_path, Namespace::PathParen, |m: Match| {
725                         debug!("[match_use] PathAliasKind::Self_ {:?} was found", ident);
726                         let rename_start = match rename_start {
727                             Some(r) => r,
728                             None => return m,
729                         };
730                         // if use A as B found, we treat this type as type alias
731                         let context_str = &msrc[alias_range.shift(context.range.start).to_range()];
732                         alias_match(ident.clone(), rename_start, m, context_str.to_owned())
733                     });
734                 }
735             }
736             PathAliasKind::Glob => {
737                 let glob_depth_reserved = if let Some(ref mut d) = import_info.glob_limit {
738                     if *d == 0 {
739                         continue;
740                     }
741                     *d -= 1;
742                     Some(*d + 1)
743                 } else {
744                     // heuristics for issue #844
745                     import_info.glob_limit = Some(GLOB_LIMIT - 1);
746                     None
747                 };
748                 let mut search_path = alias_path;
749                 search_path.segments.push(PathSegment::new(
750                     context.search_str.to_owned(),
751                     vec![],
752                     None,
753                 ));
754                 let path_iter = resolve_path(
755                     &search_path,
756                     context.filepath,
757                     context.range.start,
758                     context.search_type,
759                     Namespace::Path,
760                     session,
761                     &import_info,
762                 );
763                 import_info.glob_limit = glob_depth_reserved;
764                 debug!("[match_use] resolve_path returned {:?} for Glob", path_iter,);
765                 out.extend(path_iter);
766             }
767         }
768     }
769     out
770 }
771 
772 /// TODO: Handle `extern` functions
match_fn(msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>) -> Option<Match>773 pub fn match_fn(msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>) -> Option<Match> {
774     let blob = &msrc[context.range.to_range()];
775     if typeinf::first_param_is_self(blob) {
776         return None;
777     }
778     match_fn_common(blob, msrc, context, session)
779 }
780 
match_method( msrc: Src<'_>, context: &MatchCxt<'_, '_>, include_assoc_fn: bool, session: &Session<'_>, ) -> Option<Match>781 pub fn match_method(
782     msrc: Src<'_>,
783     context: &MatchCxt<'_, '_>,
784     include_assoc_fn: bool,
785     session: &Session<'_>,
786 ) -> Option<Match> {
787     let blob = &msrc[context.range.to_range()];
788     if !include_assoc_fn && !typeinf::first_param_is_self(blob) {
789         return None;
790     }
791     match_fn_common(blob, msrc, context, session)
792 }
793 
match_fn_common( blob: &str, msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>794 fn match_fn_common(
795     blob: &str,
796     msrc: Src<'_>,
797     context: &MatchCxt<'_, '_>,
798     session: &Session<'_>,
799 ) -> Option<Match> {
800     let (start, s) = context.get_key_ident(blob, "fn", &["const", "unsafe", "async"])?;
801     let start = context.range.start + start;
802     let doc_src = session.load_raw_src_ranged(&msrc, context.filepath);
803     Some(Match {
804         matchstr: s,
805         filepath: context.filepath.to_path_buf(),
806         point: start,
807         coords: None,
808         local: context.is_local,
809         mtype: Function,
810         contextstr: get_context(blob, "{"),
811         docs: find_doc(&doc_src, start),
812     })
813 }
814 
match_macro( msrc: Src<'_>, context: &MatchCxt<'_, '_>, session: &Session<'_>, ) -> Option<Match>815 pub fn match_macro(
816     msrc: Src<'_>,
817     context: &MatchCxt<'_, '_>,
818     session: &Session<'_>,
819 ) -> Option<Match> {
820     let trimed = context.search_str.trim_end_matches('!');
821     let mut context = context.clone();
822     context.search_str = trimed;
823     let blob = &msrc[context.range.to_range()];
824     let (start, mut s) = context.get_key_ident(blob, "macro_rules!", &[])?;
825     s.push('!');
826     debug!("found a macro {}", s);
827     let doc_src = session.load_raw_src_ranged(&msrc, context.filepath);
828     Some(Match {
829         matchstr: s,
830         filepath: context.filepath.to_owned(),
831         point: context.range.start + start,
832         coords: None,
833         local: context.is_local,
834         mtype: Macro,
835         contextstr: first_line(blob),
836         docs: find_doc(&doc_src, context.range.start),
837     })
838 }
839 
find_doc(msrc: &str, match_point: BytePos) -> String840 pub fn find_doc(msrc: &str, match_point: BytePos) -> String {
841     let blob = &msrc[0..match_point.0];
842     blob.lines()
843         .rev()
844         .skip(1) // skip the line that the match is on
845         .map(|line| line.trim())
846         .take_while(|line| line.starts_with("///") || line.starts_with("#[") || line.is_empty())
847         .filter(|line| !(line.trim().starts_with("#[") || line.is_empty())) // remove the #[flags]
848         .collect::<Vec<_>>() // These are needed because
849         .iter() // you cannot `rev`an `iter` that
850         .rev() // has already been `rev`ed.
851         .map(|line| if line.len() >= 4 { &line[4..] } else { "" }) // Remove "/// "
852         .collect::<Vec<_>>()
853         .join("\n")
854 }
855 
find_mod_doc(msrc: &str, blobstart: BytePos) -> String856 pub(crate) fn find_mod_doc(msrc: &str, blobstart: BytePos) -> String {
857     let blob = &msrc[blobstart.0..];
858     let mut doc = String::new();
859 
860     let mut iter = blob
861         .lines()
862         .map(|line| line.trim())
863         .take_while(|line| line.starts_with("//") || line.is_empty())
864         // Skip over the copyright notice and empty lines until you find
865         // the module's documentation (it will go until the end of the
866         // file if the module doesn't have any docs).
867         .filter(|line| line.starts_with("//!"))
868         .peekable();
869 
870     // Use a loop to avoid unnecessary collect and String allocation
871     while let Some(line) = iter.next() {
872         // Remove "//! " and push to doc string to be returned
873         doc.push_str(if line.len() >= 4 { &line[4..] } else { "" });
874         if iter.peek() != None {
875             doc.push_str("\n");
876         }
877     }
878     doc
879 }
880 
881 // DON'T USE MatchCxt's range
match_impl(decl: String, context: &MatchCxt<'_, '_>, offset: BytePos) -> Option<Vec<Match>>882 pub fn match_impl(decl: String, context: &MatchCxt<'_, '_>, offset: BytePos) -> Option<Vec<Match>> {
883     let ImplHeader { generics, .. } =
884         ast::parse_impl(decl, context.filepath, offset, true, offset)?;
885     let mut out = Vec::new();
886     for type_param in generics.0 {
887         if !symbol_matches(context.search_type, context.search_str, &type_param.name) {
888             continue;
889         }
890         out.push(type_param.into_match());
891     }
892     Some(out)
893 }
894 
895 #[cfg(test)]
896 mod tests {
897     use super::*;
898     #[test]
find_generics_end()899     fn find_generics_end() {
900         use super::find_generics_end;
901         assert_eq!(
902             find_generics_end("Vec<T, #[unstable(feature = \"\", issue = \"\"] A: AllocRef = Global>"),
903             Some(BytePos(64))
904         );
905         assert_eq!(
906             find_generics_end("Vec<T, A: AllocRef = Global>"),
907             Some(BytePos(27))
908         );
909         assert_eq!(
910             find_generics_end("Result<Vec<String>, Option<&str>>"),
911             Some(BytePos(32))
912         );
913     }
914 }
915