Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide completions for associated types #784

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 3 additions & 3 deletions src/racer/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
17 changes: 14 additions & 3 deletions src/racer/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ");

Expand All @@ -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
4 changes: 2 additions & 2 deletions src/racer/matchers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
145 changes: 116 additions & 29 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since format! allocates, is kind of expensive, and this same string is generated in two places, it should probably be made a variable binding above.

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 @@ -502,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 {
Expand Down Expand Up @@ -1401,12 +1438,12 @@ 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> {

/// 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<Match> {
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),
Expand All @@ -1424,7 +1461,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 +1471,85 @@ 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;
}
/// 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<Match> {

debug!("resolve_method for |{}| pt: {} ({:?})",
searchstr,
point,
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();

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()
}

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

debug!("resolve_associated_type for |{}| pt: {} ({:?})",
searchstr,
point,
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();

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
12 changes: 6 additions & 6 deletions src/racer/typeinf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 27 additions & 6 deletions src/racer/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,11 +480,9 @@ 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
/// typing a name.
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());

let mut words = line_before_point.split_whitespace().rev();
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);
}