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