- move dir_tests to test_utils for that.
use ra_editor::{self, find_node_at_offset, FileSymbol, LineIndex, LocalEdit};
use ra_syntax::{
- ast::{self, ArgListOwner, Expr, NameOwner},
+ ast::{self, ArgListOwner, Expr, NameOwner, FnDef},
+ algo::find_covering_node,
AstNode, SourceFileNode,
SyntaxKind::*,
SyntaxNodeRef, TextRange, TextUnit,
Ok(None)
}
+ pub fn type_of(&self, file_id: FileId, range: TextRange) -> Cancelable<Option<String>> {
+ let file = self.db.source_file(file_id);
+ let syntax = file.syntax();
+ let node = find_covering_node(syntax, range);
+ let parent_fn = node.ancestors().filter_map(FnDef::cast).next();
+ let parent_fn = if let Some(p) = parent_fn { p } else { return Ok(None) };
+ let function = ctry!(source_binder::function_from_source(&*self.db, file_id, parent_fn)?);
+ let infer = function.infer(&*self.db);
+ Ok(infer.type_of_node(node).map(|t| t.to_string()))
+ }
+
fn index_resolve(&self, name_ref: ast::NameRef) -> Cancelable<Vec<(FileId, FileSymbol)>> {
let name = name_ref.text();
let mut query = Query::new(name.to_string());
) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> {
self.imp.resolve_callable(position)
}
+ pub fn type_of(&self, file_id: FileId, range: TextRange) -> Cancelable<Option<String>> {
+ self.imp.type_of(file_id, range)
+ }
}
pub struct LibraryData {
(db, source_root)
}
+ pub(crate) fn with_single_file(text: &str) -> (MockDatabase, SourceRoot, FileId) {
+ let mut db = MockDatabase::default();
+ let mut source_root = SourceRoot::default();
+ let file_id = db.add_file(&mut source_root, "/main.rs", text);
+ db.query_mut(ra_db::SourceRootQuery)
+ .set(WORKSPACE, Arc::new(source_root.clone()));
+ (db, source_root, file_id)
+ }
+
pub(crate) fn with_position(fixture: &str) -> (MockDatabase, FilePosition) {
let (db, _, position) = MockDatabase::from_fixture(fixture);
let position = position.expect("expected a marker ( <|> )");
use std::sync::Arc;
use std::collections::HashMap;
+use std::fmt;
use ra_db::LocalSyntaxPtr;
use ra_syntax::{
}
}
+impl fmt::Display for Ty {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Ty::Bool => write!(f, "bool"),
+ Ty::Char => write!(f, "char"),
+ Ty::Int(t) => write!(f, "{}", t.ty_to_string()),
+ Ty::Uint(t) => write!(f, "{}", t.ty_to_string()),
+ Ty::Float(t) => write!(f, "{}", t.ty_to_string()),
+ Ty::Str => write!(f, "str"),
+ Ty::Slice(t) => write!(f, "[{}]", t),
+ Ty::Never => write!(f, "!"),
+ Ty::Tuple(ts) => {
+ write!(f, "(")?;
+ for t in ts {
+ write!(f, "{},", t)?;
+ }
+ write!(f, ")")
+ }
+ Ty::Unknown => write!(f, "[unknown]")
+ }
+ }
+}
+
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct InferenceResult {
type_for: FxHashMap<LocalSyntaxPtr, Ty>,
}
+impl InferenceResult {
+ pub fn type_of_node(&self, node: SyntaxNodeRef) -> Option<Ty> {
+ self.type_for.get(&LocalSyntaxPtr::new(node)).cloned()
+ }
+}
+
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct InferenceContext {
scopes: Arc<FnScopes>,
+use std::fmt::Write;
use std::sync::Arc;
+use std::path::{Path, PathBuf};
use salsa::Database;
use ra_db::{FilesDatabase, CrateGraph, SyntaxDatabase};
use ra_syntax::{SmolStr, algo::visit::{visitor, Visitor}, ast::{self, AstNode}};
+use test_utils::{project_dir, dir_tests};
use relative_path::RelativePath;
use crate::{source_binder, mock::WORKSPACE, module::ModuleSourceNode};
mock::MockDatabase,
};
-fn infer_all_fns(fixture: &str) -> () {
- let (db, source_root) = MockDatabase::with_files(fixture);
- for &file_id in source_root.files.values() {
- let source_file = db.source_file(file_id);
- for fn_def in source_file.syntax().descendants().filter_map(ast::FnDef::cast) {
- let func = source_binder::function_from_source(&db, file_id, fn_def).unwrap().unwrap();
- let inference_result = func.infer(&db);
- for (syntax_ptr, ty) in &inference_result.type_for {
- let node = syntax_ptr.resolve(&source_file);
- eprintln!("{} '{}': {:?}", syntax_ptr.range(), node.text(), ty);
- }
+fn infer_file(content: &str) -> String {
+ let (db, source_root, file_id) = MockDatabase::with_single_file(content);
+ let source_file = db.source_file(file_id);
+ let mut acc = String::new();
+ for fn_def in source_file.syntax().descendants().filter_map(ast::FnDef::cast) {
+ let func = source_binder::function_from_source(&db, file_id, fn_def).unwrap().unwrap();
+ let inference_result = func.infer(&db);
+ for (syntax_ptr, ty) in &inference_result.type_for {
+ let node = syntax_ptr.resolve(&source_file);
+ write!(acc, "{} '{}': {}\n", syntax_ptr.range(), ellipsize(node.text().to_string().replace("\n", " "), 15), ty);
}
}
+ acc
+}
+
+fn ellipsize(mut text: String, max_len: usize) -> String {
+ if text.len() <= max_len {
+ return text;
+ }
+ let ellipsis = "...";
+ let e_len = ellipsis.len();
+ let mut prefix_len = (max_len - e_len) / 2;
+ while !text.is_char_boundary(prefix_len) {
+ prefix_len += 1;
+ }
+ let mut suffix_len = max_len - e_len - prefix_len;
+ while !text.is_char_boundary(text.len() - suffix_len) {
+ suffix_len += 1;
+ }
+ text.replace_range(prefix_len..text.len() - suffix_len, ellipsis);
+ text
}
#[test]
-fn infer_smoke_test() {
- let text = "
- //- /lib.rs
- fn foo(x: u32, y: !) -> i128 {
- x;
- y;
- return 1;
- \"hello\";
- 0
- }
- ";
+pub fn infer_tests() {
+ dir_tests(&test_data_dir(), &["."], |text, _path| {
+ infer_file(text)
+ });
+}
- infer_all_fns(text);
+fn test_data_dir() -> PathBuf {
+ project_dir().join("crates/ra_hir/src/ty/tests/data")
}
--- /dev/null
+
+fn test(a: u32, b: isize, c: !, d: &str) {
+ a;
+ b;
+ c;
+ d;
+ 1usize;
+ 1isize;
+ "test";
+ 1.0f32;
+}
--- /dev/null
+[33; 34) 'd': [unknown]
+[88; 94) '1isize': [unknown]
+[48; 49) 'a': u32
+[55; 56) 'b': isize
+[112; 118) '1.0f32': [unknown]
+[76; 82) '1usize': [unknown]
+[9; 10) 'a': u32
+[27; 28) 'c': !
+[62; 63) 'c': !
+[17; 18) 'b': isize
+[100; 106) '"test"': [unknown]
+[42; 121) '{ ...f32; }': ()
+[69; 70) 'd': [unknown]
path::{Path, PathBuf, Component},
};
+use test_utils::{project_dir, dir_tests, read_text, collect_tests};
use ra_syntax::{
utils::{check_fuzz_invariants, dump_tree},
SourceFileNode,
#[test]
fn lexer_tests() {
- dir_tests(&["lexer"], |text, _| {
+ dir_tests(&test_data_dir(), &["lexer"], |text, _| {
let tokens = ra_syntax::tokenize(text);
dump_tokens(&tokens, text)
})
#[test]
fn parser_tests() {
- dir_tests(&["parser/inline/ok", "parser/ok"], |text, path| {
+ dir_tests(&test_data_dir(), &["parser/inline/ok", "parser/ok"], |text, path| {
let file = SourceFileNode::parse(text);
let errors = file.errors();
assert_eq!(
);
dump_tree(file.syntax())
});
- dir_tests(&["parser/err", "parser/inline/err"], |text, path| {
+ dir_tests(&test_data_dir(), &["parser/err", "parser/inline/err"], |text, path| {
let file = SourceFileNode::parse(text);
let errors = file.errors();
assert_ne!(
#[test]
fn parser_fuzz_tests() {
- for (_, text) in collect_tests(&["parser/fuzz-failures"]) {
+ for (_, text) in collect_tests(&test_data_dir(), &["parser/fuzz-failures"]) {
check_fuzz_invariants(&text)
}
}
"self_hosting_parsing found too few files - is it running in the right directory?"
)
}
-/// Read file and normalize newlines.
-///
-/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
-///
-/// ```
-/// let s = "
-/// ";
-/// assert_eq!(s.as_bytes(), &[10]);
-/// ```
-///
-/// so this should always be correct.
-fn read_text(path: &Path) -> String {
- fs::read_to_string(path)
- .expect(&format!("File at {:?} should be valid", path))
- .replace("\r\n", "\n")
-}
-
-fn dir_tests<F>(paths: &[&str], f: F)
-where
- F: Fn(&str, &Path) -> String,
-{
- for (path, input_code) in collect_tests(paths) {
- let parse_tree = f(&input_code, &path);
- let path = path.with_extension("txt");
- if !path.exists() {
- println!("\nfile: {}", path.display());
- println!("No .txt file with expected result, creating...\n");
- println!("{}\n{}", input_code, parse_tree);
- fs::write(&path, &parse_tree).unwrap();
- panic!("No expected result")
- }
- let expected = read_text(&path);
- let expected = expected.as_str();
- let parse_tree = parse_tree.as_str();
- assert_equal_text(expected, parse_tree, &path);
- }
-}
-
-const REWRITE: bool = false;
-
-fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
- if expected == actual {
- return;
- }
- let dir = project_dir();
- let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path);
- if expected.trim() == actual.trim() {
- println!("whitespace difference, rewriting");
- println!("file: {}\n", pretty_path.display());
- fs::write(path, actual).unwrap();
- return;
- }
- if REWRITE {
- println!("rewriting {}", pretty_path.display());
- fs::write(path, actual).unwrap();
- return;
- }
- assert_eq_text!(expected, actual, "file: {}", pretty_path.display());
-}
-
-fn collect_tests(paths: &[&str]) -> Vec<(PathBuf, String)> {
- paths
- .iter()
- .flat_map(|path| {
- let path = test_data_dir().join(path);
- test_from_dir(&path).into_iter()
- })
- .map(|path| {
- let text = read_text(&path);
- (path, text)
- })
- .collect()
-}
-
-fn test_from_dir(dir: &Path) -> Vec<PathBuf> {
- let mut acc = Vec::new();
- for file in fs::read_dir(&dir).unwrap() {
- let file = file.unwrap();
- let path = file.path();
- if path.extension().unwrap_or_default() == "rs" {
- acc.push(path);
- }
- }
- acc.sort();
- acc
-}
-
-fn project_dir() -> PathBuf {
- let dir = env!("CARGO_MANIFEST_DIR");
- PathBuf::from(dir)
- .parent()
- .unwrap()
- .parent()
- .unwrap()
- .to_owned()
-}
fn test_data_dir() -> PathBuf {
project_dir().join("crates/ra_syntax/tests/data")
use std::fmt;
+use std::fs;
+use std::path::{Path, PathBuf};
use itertools::Itertools;
use text_unit::{TextRange, TextUnit};
_ => Some((expected, actual)),
}
}
+
+pub fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], f: F)
+where
+ F: Fn(&str, &Path) -> String,
+{
+ for (path, input_code) in collect_tests(test_data_dir, paths) {
+ let parse_tree = f(&input_code, &path);
+ let path = path.with_extension("txt");
+ if !path.exists() {
+ println!("\nfile: {}", path.display());
+ println!("No .txt file with expected result, creating...\n");
+ println!("{}\n{}", input_code, parse_tree);
+ fs::write(&path, &parse_tree).unwrap();
+ panic!("No expected result")
+ }
+ let expected = read_text(&path);
+ let expected = expected.as_str();
+ let parse_tree = parse_tree.as_str();
+ assert_equal_text(expected, parse_tree, &path);
+ }
+}
+
+pub fn collect_tests(test_data_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> {
+ paths
+ .iter()
+ .flat_map(|path| {
+ let path = test_data_dir.to_owned().join(path);
+ test_from_dir(&path).into_iter()
+ })
+ .map(|path| {
+ let text = read_text(&path);
+ (path, text)
+ })
+ .collect()
+}
+
+fn test_from_dir(dir: &Path) -> Vec<PathBuf> {
+ let mut acc = Vec::new();
+ for file in fs::read_dir(&dir).unwrap() {
+ let file = file.unwrap();
+ let path = file.path();
+ if path.extension().unwrap_or_default() == "rs" {
+ acc.push(path);
+ }
+ }
+ acc.sort();
+ acc
+}
+
+pub fn project_dir() -> PathBuf {
+ let dir = env!("CARGO_MANIFEST_DIR");
+ PathBuf::from(dir)
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .to_owned()
+}
+
+/// Read file and normalize newlines.
+///
+/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
+///
+/// ```
+/// let s = "
+/// ";
+/// assert_eq!(s.as_bytes(), &[10]);
+/// ```
+///
+/// so this should always be correct.
+pub fn read_text(path: &Path) -> String {
+ fs::read_to_string(path)
+ .expect(&format!("File at {:?} should be valid", path))
+ .replace("\r\n", "\n")
+}
+
+const REWRITE: bool = false;
+
+fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
+ if expected == actual {
+ return;
+ }
+ let dir = project_dir();
+ let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path);
+ if expected.trim() == actual.trim() {
+ println!("whitespace difference, rewriting");
+ println!("file: {}\n", pretty_path.display());
+ fs::write(path, actual).unwrap();
+ return;
+ }
+ if REWRITE {
+ println!("rewriting {}", pretty_path.display());
+ fs::write(path, actual).unwrap();
+ return;
+ }
+ assert_eq_text!(expected, actual, "file: {}", pretty_path.display());
+}