From dba7e1784e0a61b97b8b4ac4f224edb624e613a9 Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Tue, 6 Jun 2017 11:55:20 -0700 Subject: [PATCH 1/3] Ignore VS Code-specific files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 32f1cfc6..9ed6f3a5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ src/scopes *.racertmp target/ *.py[cod] +.vscode \ No newline at end of file From b5a926b986c5b19f22a763823bbae0d70d7937af Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Tue, 6 Jun 2017 14:17:13 -0700 Subject: [PATCH 2/3] Add `get-type` command The `get-type` command (requested in #620) takes a line and character number which point to a complete identifier in the input text. The command will then attempt to determine the concrete type of that identifier. --- src/bin/main.rs | 30 +++++++++ src/racer/core.rs | 51 ++++++++++++++ src/racer/lib.rs | 2 +- src/racer/scopes.rs | 12 ++++ tests/system.rs | 161 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 253 insertions(+), 3 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index c6e03467..3e9f11a0 100755 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -59,6 +59,10 @@ fn complete_by_line_coords(cfg: Config, interface.emit(Message::End); } +fn get_type(cfg: Config) { + run_get_type(&cfg); +} + #[derive(Debug)] enum CompletePrinter { Normal, @@ -121,6 +125,18 @@ fn run_the_complete_fn(cfg: &Config, print_type: CompletePrinter) { } } +fn run_get_type(cfg: &Config) { + let fn_path = cfg.fn_name.as_ref().unwrap(); + let substitute_file = cfg.substitute_file.as_ref().unwrap_or(fn_path); + + let cache = FileCache::default(); + let session = Session::new(&cache); + + load_query_file(&fn_path, &substitute_file, &session); + racer::get_type(&fn_path, cfg.coords(), &session).map(|m| match_fn(m, cfg.interface)); + cfg.interface.emit(Message::End); +} + /// Completes a fully qualified name specified on command line fn external_complete(cfg: Config, print_type: CompletePrinter) { let cwd = Path::new("."); @@ -380,6 +396,19 @@ fn build_cli<'a, 'b>() -> App<'a, 'b> { .help("An optional substitute file")) .arg(Arg::with_name("linenum") .help("The line number at which to find the match"))) + .subcommand(SubCommand::with_name("get-type") + .about("finds the type of the specified value") + .usage("racer get-type ") + .setting(AppSettings::ArgRequiredElseHelp) + .arg(Arg::with_name("linenum") + .help("The line number at which to find the match") + .required(true)) + .arg(Arg::with_name("charnum") + .help("The char number at which to find the match") + .required(true)) + .arg(Arg::with_name("path") + .help("The path to search for name to match") + .required(true))) .after_help("For more information about a specific command try 'racer --help'") } @@ -409,6 +438,7 @@ fn run(m: ArgMatches, interface: Interface) { "complete" => complete(cfg, Normal), "complete-with-snippet" => complete(cfg, WithSnippets), "find-definition" => find_definition(cfg), + "get-type" => get_type(cfg), _ => unreachable!() } } diff --git a/src/racer/core.rs b/src/racer/core.rs index 277164c7..684eda0d 100644 --- a/src/racer/core.rs +++ b/src/racer/core.rs @@ -1102,6 +1102,57 @@ pub fn find_definition_(filepath: &path::Path, cursor: Location, session: &Sessi } } +/// Gets the type of a variable or field identified by position in the input file. +/// +/// When the cursor is placed anywhere inside a fully-typed identifier, this function +/// will attempt to determine the concrete type of that value. +pub fn get_type( + filepath: P, + cursor: C, + session: &Session +) -> Option where P: AsRef, C: Into +{ + let fpath = filepath.as_ref(); + let cursor = cursor.into(); + let src = session.load_file_and_mask_comments(fpath); + let src = &src.as_src()[..]; + + // TODO return result + let pos = match cursor.to_point(&session.load_file(fpath)) { + Some(pos) => pos, + None => { + debug!("Failed to convert cursor to point"); + return None; + } + }; + + /// Backtrack to find the start of the expression, like completion does. This should + /// enable get-type to work with field accesses as well as local variables. + let start = scopes::get_start_of_search_expr(src, pos); + + /// Unlike `complete`, we are doing an exact match, so we also need to search forward + /// to find the end of the identifier. We deliberately don't look for the end of the + /// expression, as we aren't trying to find the whole expression's type. + let end = scopes::get_end_of_ident(src, pos); + + + let expr = &src[start..end]; + + ast::get_type_of(expr.to_owned(), fpath, pos, session).and_then(|ty| { + if let Ty::Match(mut m) = ty { + if m.coords.is_none() { + let point = m.point; + let src = session.load_file(m.filepath.as_path()); + m.coords = src.point_to_coords(point); + } + + Some(m) + } else { + None + } + }) +} + #[cfg(test)] mod tests { use std::path::Path; diff --git a/src/racer/lib.rs b/src/racer/lib.rs index 1a5ddfee..9eb03d28 100644 --- a/src/racer/lib.rs +++ b/src/racer/lib.rs @@ -28,7 +28,7 @@ mod matchers; mod snippets; mod cargo; -pub use core::{find_definition, complete_from_file, complete_fully_qualified_name}; +pub use core::{find_definition, complete_from_file, complete_fully_qualified_name, get_type}; pub use snippets::snippet_for_match; pub use core::{Match, MatchType, PathSearch}; pub use core::{FileCache, Session, Coordinate, Location, FileLoader}; diff --git a/src/racer/scopes.rs b/src/racer/scopes.rs index c31875f9..ff87d07b 100644 --- a/src/racer/scopes.rs +++ b/src/racer/scopes.rs @@ -202,6 +202,18 @@ pub fn get_line(src: &str, point: usize) -> usize { 0 } +/// Search forward to the end of the current ident. +/// Used by `get-type` to ensure that it is searching for the full ident rather than a prefix match. +pub fn get_end_of_ident(src: &str, point: usize) -> usize { + for (i, _) in src.as_bytes()[point..].iter().enumerate() { + if !util::is_ident_char(char_at(src, point + i)) { + return point + i; + } + } + + return point + src.as_bytes()[point..].len(); +} + /// search in reverse for the start of the current expression /// allow . and :: to be surrounded by white chars to enable multi line call chains pub fn get_start_of_search_expr(src: &str, point: usize) -> usize { diff --git a/tests/system.rs b/tests/system.rs index 56444935..7919f6a9 100644 --- a/tests/system.rs +++ b/tests/system.rs @@ -150,7 +150,7 @@ impl Drop for TmpDir { } fn get_pos_and_source(src: &str) -> (usize, String) { - let point = src.find('~').unwrap(); + let point = src.find('~').expect("Test input must contain a `~` character to represent cursor location"); (point, src.replace('~', "")) } @@ -190,7 +190,17 @@ fn get_definition(src: &str, dir: Option) -> Match { let cache = racer::FileCache::default(); let session = racer::Session::new(&cache); - find_definition(&path, completion_point, &session).unwrap() + find_definition(&path, completion_point, &session).expect("find-definition must produce a definition") +} + +fn get_type(src: &str, dir: Option) -> Match { + let dir = dir.unwrap_or_else(|| TmpDir::new()); + let (search_point, clean_src) = get_pos_and_source(src); + let path = dir.write_file("src.rs", &clean_src); + let cache = racer::FileCache::default(); + let session = racer::Session::new(&cache); + + racer::get_type(&path, search_point, &session).expect("get-type must produce a type") } @@ -3045,4 +3055,151 @@ fn closure_bracket_scope_nested_match_outside() { let got = get_definition(src, None); assert_eq!("x", got.matchstr); assert_eq!("| x: i32 |", got.contextstr); +} + +#[test] +fn get_type_finds_explicit_local_var() { + let _lock = sync!(); + let src = " + fn main() { + let value: Option = Some(5); + if val~ue.is_some() { + // do nothing + } + } + "; + + let got = get_type(src, None); + assert_eq!("Option", got.matchstr); +} + +#[test] +fn get_type_finds_fn_arg() { + let _lock = sync!(); + let src = r#" + fn say_hello(name: &str) { + if nam~e != "teddriggs" { + // do nothing + } + } + "#; + + let got = get_type(src, None); + assert_eq!("str", got.matchstr); +} + +#[test] +fn get_type_finds_tuple_field() { + let _lock = sync!(); + let src = " + struct Bar; + struct Foo(Bar); + + fn do_things(foo: Foo) -> Bar { + foo.0~ + } + "; + + let got = get_type(src, None); + assert_eq!("Bar", got.matchstr); +} + +#[test] +fn get_type_finds_struct_field() { + let _lock = sync!(); + let src = " + struct Bar; + struct Foo { value: Bar } + + fn do_things(foo: Foo) -> Bar { + foo.va~lue + } + "; + + let got = get_type(src, None); + assert_eq!("Bar", got.matchstr); +} + +#[test] +fn get_type_finds_destructured() { + let _lock = sync!(); + let src = " + struct Bar; + struct Foo { value: Bar } + + fn do_things(foo: Foo) -> Bar { + let Foo { value: value } = foo; + valu~e + } + "; + + let got = get_type(src, None); + assert_eq!("Bar", got.matchstr); +} + +#[test] +fn get_type_finds_single_character_var_at_end() { + let _lock = sync!(); + let src = " + struct Bar; + fn do_things(y: Bar) -> Bar { + y~ + } + "; + + let got = get_type(src, None); + assert_eq!("Bar", got.matchstr); +} + +#[test] +fn get_type_finds_single_character_var_at_start() { + let _lock = sync!(); + let src = " + struct Bar; + fn do_things(y: Bar) -> Bar { + ~y + } + "; + + let got = get_type(src, None); + assert_eq!("Bar", got.matchstr); +} + +#[test] +fn get_type_finds_function_return() { + let _lock = sync!(); + let src = r#" + struct Bar; + fn do_things(y: Bar) -> Bar { + y + } + + fn main() { + let z = do_things(); + println!("{:?}", ~z); + } + "#; + + let got = get_type(src, None); + assert_eq!("Bar", got.matchstr); +} + +#[test] +fn get_type_finds_function() { + let _lock = sync!(); + let src = r#" + struct Bar; + + fn do_things(y: Bar) -> Bar { + y + } + + fn main() { + let z = do_th~ings(); + println!("{:?}", ~z); + } + "#; + + let got = get_type(src, None); + assert_eq!("do_things", got.matchstr); } \ No newline at end of file From 2b0bc35c6e9ce88b316a618cd106cd67d47ac754 Mon Sep 17 00:00:00 2001 From: Ted Driggs Date: Tue, 6 Jun 2017 14:20:25 -0700 Subject: [PATCH 3/3] Add RUST_SRC_PATH to travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8d82f6a6..3e2d0985 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,8 @@ cache: cargo before_script: - pip install 'travis-cargo<0.2' --user - export PATH=$HOME/.local/bin:$PATH + - rustup component add rust-src + - export RUST_SRC_PATH=`rustc --print sysroot`/lib/rustlib/src/rust/src script: - travis-cargo --skip nightly-2016-11-25 test