Skip to content

Commit

Permalink
Fix racer-rust#775 by providing completions for associated types
Browse files Browse the repository at this point in the history
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
  • Loading branch information
TedDriggs committed Aug 7, 2017
1 parent 607a647 commit 06bd80a
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 33 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions src/racer/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
138 changes: 111 additions & 27 deletions src/racer/nameres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<Match> {
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<Match> {
Expand Down Expand Up @@ -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<Match> {

fn find_implemented_trait(point: Point, msrc: Src, searchstr: &str,
filepath: &Path, session: &Session,
pending_imports: &PendingImports) -> Option<Match> {
let scopestart = scopes::scope_start(msrc, point);
debug!("resolve_method for |{}| pt: {} ({:?}); scopestart: {} ({:?})",
searchstr,
Expand All @@ -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::<Vec<_>>());
let m = resolve_path(&path,

return resolve_path(&path,
filepath,
stmtstart + n - 1,
SearchType::ExactMatch,
Expand All @@ -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<Match> {

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<Match> {

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<Match> {
debug!("do_external_search path {:?} {:?}", path, filepath.display());
Expand Down
31 changes: 26 additions & 5 deletions src/racer/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -500,15 +498,38 @@ 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"));
assert!(in_fn_name(" fn foo"));
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<T"));
assert!(!in_type_name("type Foo=String"));
assert!(!in_type_name("type Foo = String"));
}
47 changes: 47 additions & 0 deletions tests/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3982,3 +3982,50 @@ fn completes_for_global_path_in_trait_impl_decl() {
assert_eq!(got.matchstr, "Bar");
assert_eq!(got.mtype, MatchType::Trait);
}

/// Addresses https://github.com/racer-rust/racer/issues/775
#[test]
fn completes_associated_type() {
let _lock = sync!();

let src = "
struct Test;
impl IntoIterator for Test {
type Item = u64;
type IntoIt~er = ::std::iter::Once<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
::std::iter::once(0)
}
}
";

let got = get_only_completion(src, None);
assert_eq!(got.matchstr, "IntoIter");
assert_eq!(got.mtype, MatchType::Type);
}

/// Verifies the fix for https://github.com/racer-rust/racer/issues/775 doesn't
/// break completions on the RHS for associated types.
#[test]
fn completes_types_on_rhs_for_associated_type() {
let _lock = sync!();

let src = "
struct Test;
impl IntoIterator for Test {
type Item = u64;
type IntoIter = ::std::iter::On~ce<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
::std::iter::once(0)
}
}
";

let got = get_only_completion(src, None);
assert_eq!(got.matchstr, "Once");
assert_eq!(got.mtype, MatchType::Struct);
}

0 comments on commit 06bd80a

Please sign in to comment.