From 06bd80aafefa7b44b0831f3bbad1226e6d000722 Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Mon, 7 Aug 2017 12:43:45 -0700 Subject: [PATCH 1/3] Fix #775 by providing completions for associated types This fix is very similar to the one for trait functions: 1. Look to see if cursor is after a declaration keyword 2. If so, try to find the enclosing trait 3. If trait found, look for trait items of the right type --- CHANGELOG.md | 2 +- src/racer/core.rs | 11 ++++ src/racer/nameres.rs | 138 ++++++++++++++++++++++++++++++++++--------- src/racer/util.rs | 31 ++++++++-- tests/system.rs | 47 +++++++++++++++ 5 files changed, 196 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e624a8d4..4e1a3f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ project adheres to [Semantic Versioning](http://semver.org/). ## HEAD -No changes, yet! +- Add completions for associated type names in `impl for` blocks #784 ## 2.0.10 diff --git a/src/racer/core.rs b/src/racer/core.rs index b0ca570f..3050117f 100644 --- a/src/racer/core.rs +++ b/src/racer/core.rs @@ -1016,6 +1016,17 @@ fn complete_from_file_( SearchType::StartsWith, session, &PendingImports::empty()); + } else if util::in_type_name(line) { + trace!("Path is in type declaration: `{}`", expr); + + return nameres::resolve_associated_type( + pos, + src.as_src(), + expr, + filepath, + SearchType::StartsWith, + session, + &PendingImports::empty()); } let v = (if is_use { diff --git a/src/racer/nameres.rs b/src/racer/nameres.rs index a11c2553..207e26b8 100644 --- a/src/racer/nameres.rs +++ b/src/racer/nameres.rs @@ -3,7 +3,7 @@ use {core, ast, matchers, scopes, typeinf}; use core::SearchType::{self, ExactMatch, StartsWith}; use core::{Match, Src, Session, Coordinate, SessionExt, Ty, Point}; -use core::MatchType::{Module, Function, Struct, Enum, FnArg, Trait, StructField, Impl, TraitImpl, MatchArm, Builtin}; +use core::MatchType::{Module, Function, Struct, Enum, FnArg, Trait, StructField, Impl, TraitImpl, MatchArm, Builtin, Type}; use core::Namespace; use util::{self, closure_valid_arg_scope, symbol_matches, txt_matches, find_ident_end}; @@ -233,6 +233,43 @@ fn search_scope_for_static_trait_fns(point: Point, src: Src, searchstr: &str, fi out.into_iter() } +fn search_scope_for_associated_types(point: Point, src: Src, searchstr: &str, filepath: &Path, + search_type: SearchType) -> vec::IntoIter { + debug!("searching scope for associated type declarations {} |{}| {:?}", point, searchstr, filepath.display()); + + let scopesrc = src.from(point); + let mut out = Vec::new(); + for (blobstart,blobend) in scopesrc.iter_stmts() { + let blob = &scopesrc[blobstart..blobend]; + blob.find(|c| c == '=' || c == ';').map(|n| { + let signature = blob[..n].trim_right(); + + if txt_matches(search_type, &format!("type {}", searchstr), signature) { + debug!("found associated type starting |{}| |{}|", searchstr, blob); + // TODO: parse this properly + let start = blob.find(&format!("type {}", searchstr)).unwrap() + 5; + let end = find_ident_end(blob, start); + let l = &blob[start..end]; + + let m = Match { + matchstr: l.to_owned(), + filepath: filepath.to_path_buf(), + point: point + blobstart + start, + coords: None, + local: true, + mtype: Type, + contextstr: signature.to_owned(), + generic_args: Vec::new(), + generic_types: Vec::new(), + docs: find_doc(&scopesrc, blobstart + start), + }; + out.push(m); + } + }); + } + out.into_iter() +} + pub fn search_for_impls(pos: Point, searchstr: &str, filepath: &Path, local: bool, include_traits: bool, session: &Session, pending_imports: &PendingImports) -> vec::IntoIter { @@ -1401,10 +1438,9 @@ pub fn resolve_path(path: &core::Path, filepath: &Path, pos: Point, } } -pub(crate) fn resolve_method(point: Point, msrc: Src, searchstr: &str, - filepath: &Path, search_type: SearchType, session: &Session, - pending_imports: &PendingImports) -> Vec { - +fn find_implemented_trait(point: Point, msrc: Src, searchstr: &str, + filepath: &Path, session: &Session, + pending_imports: &PendingImports) -> Option { let scopestart = scopes::scope_start(msrc, point); debug!("resolve_method for |{}| pt: {} ({:?}); scopestart: {} ({:?})", searchstr, @@ -1424,7 +1460,8 @@ pub(crate) fn resolve_method(point: Point, msrc: Src, searchstr: &str, debug!("found impl of trait : expr is |{}|", expr); let path = core::Path::from_vec(false, expr.split("::").collect::>()); - let m = resolve_path(&path, + + return resolve_path(&path, filepath, stmtstart + n - 1, SearchType::ExactMatch, @@ -1433,36 +1470,83 @@ pub(crate) fn resolve_method(point: Point, msrc: Src, searchstr: &str, pending_imports) .filter(|m| m.mtype == Trait) .nth(0); - if let Some(m) = m { - debug!("found trait : match is |{:?}|", m); - let mut out = Vec::new(); - let src = session.load_file(&m.filepath); - src[m.point..].find('{').map(|n| { - let point = m.point + n + 1; - for m in search_scope_for_static_trait_fns(point, src.as_src(), searchstr, &m.filepath, search_type) { - out.push(m); - } - for m in search_scope_for_methods(point, src.as_src(), searchstr, &m.filepath, search_type) { - out.push(m); - } - }); + } + } + } - trace!( - "Found {} methods matching `{}` for trait `{}`", - out.len(), - searchstr, - m.matchstr); + None +} - return out; - } +pub fn resolve_method(point: Point, msrc: Src, searchstr: &str, + filepath: &Path, search_type: SearchType, session: &Session, + pending_imports: &PendingImports) -> Vec { + + let scopestart = scopes::scope_start(msrc, point); + debug!("resolve_method for |{}| pt: {} ({:?}); scopestart: {} ({:?})", + searchstr, + point, + msrc.src.point_to_coords(point), + scopestart, + msrc.src.point_to_coords(scopestart)); + let trayt = find_implemented_trait(point, msrc, searchstr, filepath, session, pending_imports); + + if let Some(m) = trayt { + debug!("found trait : match is |{:?}|", m); + let mut out = Vec::new(); + let src = session.load_file(&m.filepath); + src[m.point..].find('{').map(|n| { + let point = m.point + n + 1; + for m in search_scope_for_static_trait_fns(point, src.as_src(), searchstr, &m.filepath, search_type) { + out.push(m); } - } + for m in search_scope_for_methods(point, src.as_src(), searchstr, &m.filepath, search_type) { + out.push(m); + } + }); + + trace!( + "Found {} methods matching `{}` for trait `{}`", + out.len(), + searchstr, + m.matchstr); + + return out; } Vec::new() } +pub fn resolve_associated_type(point: Point, msrc: Src, searchstr: &str, + filepath: &Path, search_type: SearchType, session: &Session, + pending_imports: &PendingImports) -> Vec { + + let scopestart = scopes::scope_start(msrc, point); + debug!("resolve_associated_type for |{}| pt: {} ({:?}); scopestart: {} ({:?})", + searchstr, + point, + msrc.src.point_to_coords(point), + scopestart, + msrc.src.point_to_coords(scopestart)); + + let trayt = find_implemented_trait(point, msrc, searchstr, filepath, session, pending_imports); + if let Some(m) = trayt { + let src = session.load_file(&m.filepath); + let mut out = Vec::new(); + + src[m.point..].find('{').map(|n| { + let point = m.point + n + 1; + for at in search_scope_for_associated_types(point, src.as_src(), searchstr, &m.filepath, search_type) { + out.push(at); + } + }); + + out + } else { + Vec::new() + } +} + pub fn do_external_search(path: &[&str], filepath: &Path, pos: Point, search_type: SearchType, namespace: Namespace, session: &Session) -> vec::IntoIter { debug!("do_external_search path {:?} {:?}", path, filepath.display()); diff --git a/src/racer/util.rs b/src/racer/util.rs index 855f7088..79268c5f 100644 --- a/src/racer/util.rs +++ b/src/racer/util.rs @@ -480,10 +480,8 @@ fn test_trim_visibility() { assert_eq!(trim_visibility("pub (in super) const fn"), "const fn"); } -/// Checks if the completion point is in a function declaration by looking -/// to see if the second-to-last word is `fn`. -pub fn in_fn_name(line_before_point: &str) -> bool { - /// Determine if the cursor is sitting in the whitespace after typing `fn ` before +fn is_after_keyword(keyword: &str, line_before_point: &str) -> bool { + /// Determine if the cursor is sitting in the whitespace after typing `[keyword] ` before /// typing a name. let has_started_name = !line_before_point.ends_with(|c: char| c.is_whitespace()); @@ -500,10 +498,16 @@ pub fn in_fn_name(line_before_point: &str) -> bool { words .next() - .map(|word| word == "fn") + .map(|word| word == keyword) .unwrap_or_default() } +/// Checks if the completion point is in a function declaration by looking +/// to see if the second-to-last word is `fn`. +pub fn in_fn_name(line_before_point: &str) -> bool { + is_after_keyword("fn", line_before_point) +} + #[test] fn test_in_fn_name() { assert!(in_fn_name("fn foo")); @@ -511,4 +515,21 @@ fn test_in_fn_name() { assert!(in_fn_name("fn ")); assert!(!in_fn_name("fn foo(b")); assert!(!in_fn_name("fn")); +} + +/// Checks if the completion point is in a type or associated type declaration +/// by looking to see if the second-to-last word is `type`. +pub fn in_type_name(line_before_point: &str) -> bool { + is_after_keyword("type", line_before_point) +} + +#[test] +fn test_in_type_name() { + assert!(in_type_name("type Er")); + assert!(in_type_name(" type Err")); + + + assert!(!in_type_name("type Foo Date: Mon, 7 Aug 2017 18:11:32 -0700 Subject: [PATCH 2/3] Improve logging statements for #775 and add doc comments --- src/racer/nameres.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/racer/nameres.rs b/src/racer/nameres.rs index 207e26b8..1fd3408f 100644 --- a/src/racer/nameres.rs +++ b/src/racer/nameres.rs @@ -1438,11 +1438,12 @@ pub fn resolve_path(path: &core::Path, filepath: &Path, pos: Point, } } +/// If the cursor is inside a trait implementation, try to find the definition of that trait. fn find_implemented_trait(point: Point, msrc: Src, searchstr: &str, filepath: &Path, session: &Session, pending_imports: &PendingImports) -> Option { let scopestart = scopes::scope_start(msrc, point); - debug!("resolve_method for |{}| pt: {} ({:?}); scopestart: {} ({:?})", + debug!("find implemented trait for |{}| pt: {} ({:?}); scopestart: {} ({:?})", searchstr, point, msrc.src.point_to_coords(point), @@ -1477,24 +1478,25 @@ fn find_implemented_trait(point: Point, msrc: Src, searchstr: &str, None } +/// Find required or optional trait functions declared by the trait currently being implemented +/// if the cursor is in a trait implementation. pub fn resolve_method(point: Point, msrc: Src, searchstr: &str, filepath: &Path, search_type: SearchType, session: &Session, pending_imports: &PendingImports) -> Vec { - let scopestart = scopes::scope_start(msrc, point); - debug!("resolve_method for |{}| pt: {} ({:?}); scopestart: {} ({:?})", + debug!("resolve_method for |{}| pt: {} ({:?})", searchstr, point, - msrc.src.point_to_coords(point), - scopestart, - msrc.src.point_to_coords(scopestart)); + msrc.src.point_to_coords(point)); let trayt = find_implemented_trait(point, msrc, searchstr, filepath, session, pending_imports); if let Some(m) = trayt { debug!("found trait : match is |{:?}|", m); - let mut out = Vec::new(); + let src = session.load_file(&m.filepath); + let mut out = Vec::new(); + src[m.point..].find('{').map(|n| { let point = m.point + n + 1; for m in search_scope_for_static_trait_fns(point, src.as_src(), searchstr, &m.filepath, search_type) { @@ -1517,20 +1519,21 @@ pub fn resolve_method(point: Point, msrc: Src, searchstr: &str, Vec::new() } +/// Find associated types declared by the trait being implemented if the cursor is inside +/// a trait implementation. pub fn resolve_associated_type(point: Point, msrc: Src, searchstr: &str, filepath: &Path, search_type: SearchType, session: &Session, pending_imports: &PendingImports) -> Vec { - let scopestart = scopes::scope_start(msrc, point); - debug!("resolve_associated_type for |{}| pt: {} ({:?}); scopestart: {} ({:?})", + debug!("resolve_associated_type for |{}| pt: {} ({:?})", searchstr, point, - msrc.src.point_to_coords(point), - scopestart, - msrc.src.point_to_coords(scopestart)); + msrc.src.point_to_coords(point)); let trayt = find_implemented_trait(point, msrc, searchstr, filepath, session, pending_imports); if let Some(m) = trayt { + debug!("found trait: match is |{:?}|", m); + let src = session.load_file(&m.filepath); let mut out = Vec::new(); From 67a84424912a44bac9626f1ff30b47a8d95f22be Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Mon, 7 Aug 2017 18:14:49 -0700 Subject: [PATCH 3/3] Replace doc comments on `let` with regular comments to fix unused_doc_comment warnings --- src/racer/ast.rs | 6 +++--- src/racer/core.rs | 6 +++--- src/racer/matchers.rs | 4 ++-- src/racer/nameres.rs | 2 +- src/racer/typeinf.rs | 12 ++++++------ src/racer/util.rs | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/racer/ast.rs b/src/racer/ast.rs index bbee3f7e..74238e1b 100644 --- a/src/racer/ast.rs +++ b/src/racer/ast.rs @@ -114,9 +114,9 @@ impl visit::Visitor for UseVisitor { ast::ViewPathList(ref pth, ref paths) => { let basepath = to_racer_path(pth); for path in paths { - /// Figure out the identifier being introduced to the local - /// namespace. This will differ from the import name if an `as` - /// was used. + // Figure out the identifier being introduced to the local + // namespace. This will differ from the import name if an `as` + // was used. let ident = path.node.rename.unwrap_or(path.node.name).name.to_string(); let name = path.node.name.name.to_string(); diff --git a/src/racer/core.rs b/src/racer/core.rs index 3050117f..8261b640 100644 --- a/src/racer/core.rs +++ b/src/racer/core.rs @@ -997,9 +997,9 @@ fn complete_from_file_( let line = src_text[linestart..pos].trim().rsplit(';').nth(0).unwrap(); debug!("Complete path with line: {:?}", line); - /// Test if the **path expression** starts with `::`, in which case the path - /// should be checked against the global namespace rather than the items currently - /// in scope. + // Test if the **path expression** starts with `::`, in which case the path + // should be checked against the global namespace rather than the items currently + // in scope. let is_global = expr.starts_with("::"); let is_use = line.starts_with("use "); diff --git a/src/racer/matchers.rs b/src/racer/matchers.rs index b773ea88..e5507c06 100644 --- a/src/racer/matchers.rs +++ b/src/racer/matchers.rs @@ -59,8 +59,8 @@ fn find_keyword(src: &str, pattern: &str, search: &str, search_type: SearchType, // optional (pub\s+)?(unsafe\s+)? for pat in ["pub", "unsafe"].into_iter() { if src[start..].starts_with(pat) { - /// Rust added support for `pub(in codegen)`; we need to consume the visibility - /// specifier for the rest of the code to keep working. + // Rust added support for `pub(in codegen)`; we need to consume the visibility + // specifier for the rest of the code to keep working. let allow_scope = pat == &"pub"; let mut levels = 0; diff --git a/src/racer/nameres.rs b/src/racer/nameres.rs index 1fd3408f..ac78ca20 100644 --- a/src/racer/nameres.rs +++ b/src/racer/nameres.rs @@ -539,7 +539,7 @@ fn preblock_is_fn(preblock: &str) -> bool { return true; } - /// Remove visibility declarations, such as restricted visibility + // Remove visibility declarations, such as restricted visibility let trimmed = if preblock.starts_with("pub") { util::trim_visibility(preblock) } else { diff --git a/src/racer/typeinf.rs b/src/racer/typeinf.rs index 28d29fb5..9d8dc969 100644 --- a/src/racer/typeinf.rs +++ b/src/racer/typeinf.rs @@ -28,12 +28,12 @@ pub fn generate_skeleton_for_parsing(src: &str) -> String { } pub fn first_param_is_self(blob: &str) -> bool { - /// Restricted visibility introduces the possibility of `pub(in ...)` at the start - /// of a method declaration. To counteract this, we restrict the search to only - /// look at text _after_ the visibility declaration. - /// - /// Having found the end of the visibility declaration, we now start the search - /// for method parameters. + // Restricted visibility introduces the possibility of `pub(in ...)` at the start + // of a method declaration. To counteract this, we restrict the search to only + // look at text _after_ the visibility declaration. + // + // Having found the end of the visibility declaration, we now start the search + // for method parameters. let blob = util::trim_visibility(blob); // skip generic arg diff --git a/src/racer/util.rs b/src/racer/util.rs index 79268c5f..9f031c01 100644 --- a/src/racer/util.rs +++ b/src/racer/util.rs @@ -481,8 +481,8 @@ fn test_trim_visibility() { } fn is_after_keyword(keyword: &str, line_before_point: &str) -> bool { - /// Determine if the cursor is sitting in the whitespace after typing `[keyword] ` before - /// typing a name. + // Determine if the cursor is sitting in the whitespace after typing `[keyword] ` before + // typing a name. let has_started_name = !line_before_point.ends_with(|c: char| c.is_whitespace()); let mut words = line_before_point.split_whitespace().rev();