(T): make typification tests more data driven

This commit is contained in:
Aleksey Kladov 2020-06-29 14:21:57 +02:00
parent 82ce5792ab
commit e805e8c1d5
5 changed files with 137 additions and 133 deletions

View file

@ -154,6 +154,19 @@ impl TestDB {
}); });
(buf, count) (buf, count)
} }
pub fn all_files(&self) -> Vec<FileId> {
let mut res = Vec::new();
let crate_graph = self.crate_graph();
for krate in crate_graph.iter() {
let crate_def_map = self.crate_def_map(krate);
for (module_id, _) in crate_def_map.modules.iter() {
let file_id = crate_def_map[module_id].origin.file_id();
res.extend(file_id)
}
}
res
}
} }
impl TestDB { impl TestDB {

View file

@ -28,6 +28,7 @@ use ra_syntax::{
SyntaxNode, SyntaxNode,
}; };
use stdx::format_to; use stdx::format_to;
use test_utils::extract_annotations;
use crate::{ use crate::{
db::HirDatabase, display::HirDisplay, infer::TypeMismatch, test_db::TestDB, InferenceResult, Ty, db::HirDatabase, display::HirDisplay, infer::TypeMismatch, test_db::TestDB, InferenceResult, Ty,
@ -37,6 +38,21 @@ use crate::{
// against snapshots of the expected results using insta. Use cargo-insta to // against snapshots of the expected results using insta. Use cargo-insta to
// update the snapshots. // update the snapshots.
fn check_types(ra_fixture: &str) {
let db = TestDB::with_files(ra_fixture);
let mut checked_one = false;
for file_id in db.all_files() {
let text = db.parse(file_id).syntax_node().to_string();
let annotations = extract_annotations(&text);
for (offset, expected) in annotations {
let actual = type_at_pos(&db, FilePosition { file_id, offset });
assert_eq!(expected, actual);
checked_one = true;
}
}
assert!(checked_one, "no `//^` annotations found");
}
fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { fn type_at_pos(db: &TestDB, pos: FilePosition) -> String {
type_at_pos_displayed(db, pos, |ty, _| ty.display(db).to_string()) type_at_pos_displayed(db, pos, |ty, _| ty.display(db).to_string())
} }

View file

@ -1,99 +1,91 @@
use insta::assert_snapshot; use insta::assert_snapshot;
use super::{infer_with_mismatches, type_at}; use super::{check_types, infer_with_mismatches};
#[test] #[test]
fn infer_never1() { fn infer_never1() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn test() { fn test() {
let t = return; let t = return;
t<|>; t;
} } //^ !
"#, "#,
); );
assert_eq!(t, "!");
} }
#[test] #[test]
fn infer_never2() { fn infer_never2() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn gen<T>() -> T { loop {} } fn gen<T>() -> T { loop {} }
fn test() { fn test() {
let a = gen(); let a = gen();
if false { a } else { loop {} }; if false { a } else { loop {} };
a<|>; a;
} } //^ !
"#, "#,
); );
assert_eq!(t, "!");
} }
#[test] #[test]
fn infer_never3() { fn infer_never3() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn gen<T>() -> T { loop {} } fn gen<T>() -> T { loop {} }
fn test() { fn test() {
let a = gen(); let a = gen();
if false { loop {} } else { a }; if false { loop {} } else { a };
a<|>; a;
//^ !
} }
"#, "#,
); );
assert_eq!(t, "!");
} }
#[test] #[test]
fn never_type_in_generic_args() { fn never_type_in_generic_args() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
enum Option<T> { None, Some(T) } enum Option<T> { None, Some(T) }
fn test() { fn test() {
let a = if true { Option::None } else { Option::Some(return) }; let a = if true { Option::None } else { Option::Some(return) };
a<|>; a;
} } //^ Option<!>
"#, "#,
); );
assert_eq!(t, "Option<!>");
} }
#[test] #[test]
fn never_type_can_be_reinferred1() { fn never_type_can_be_reinferred1() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn gen<T>() -> T { loop {} } fn gen<T>() -> T { loop {} }
fn test() { fn test() {
let a = gen(); let a = gen();
if false { loop {} } else { a }; if false { loop {} } else { a };
a<|>; a;
//^ ()
if false { a }; if false { a };
} }
"#, "#,
); );
assert_eq!(t, "()");
} }
#[test] #[test]
fn never_type_can_be_reinferred2() { fn never_type_can_be_reinferred2() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
enum Option<T> { None, Some(T) } enum Option<T> { None, Some(T) }
fn test() { fn test() {
let a = if true { Option::None } else { Option::Some(return) }; let a = if true { Option::None } else { Option::Some(return) };
a<|>; a;
//^ Option<i32>
match 42 { match 42 {
42 => a, 42 => a,
_ => Option::Some(42), _ => Option::Some(42),
@ -101,19 +93,18 @@ fn test() {
} }
"#, "#,
); );
assert_eq!(t, "Option<i32>");
} }
#[test] #[test]
fn never_type_can_be_reinferred3() { fn never_type_can_be_reinferred3() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
enum Option<T> { None, Some(T) } enum Option<T> { None, Some(T) }
fn test() { fn test() {
let a = if true { Option::None } else { Option::Some(return) }; let a = if true { Option::None } else { Option::Some(return) };
a<|>; a;
//^ Option<&str>
match 42 { match 42 {
42 => a, 42 => a,
_ => Option::Some("str"), _ => Option::Some("str"),
@ -121,82 +112,72 @@ fn test() {
} }
"#, "#,
); );
assert_eq!(t, "Option<&str>");
} }
#[test] #[test]
fn match_no_arm() { fn match_no_arm() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
enum Void {} enum Void {}
fn test(a: Void) { fn test(a: Void) {
let t = match a {}; let t = match a {};
t<|>; t;
} } //^ !
"#, "#,
); );
assert_eq!(t, "!");
} }
#[test] #[test]
fn match_unknown_arm() { fn match_unknown_arm() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn test(a: Option) { fn test(a: Option) {
let t = match 0 { let t = match 0 {
_ => unknown, _ => unknown,
}; };
t<|>; t;
} } //^ {unknown}
"#, "#,
); );
assert_eq!(t, "{unknown}");
} }
#[test] #[test]
fn if_never() { fn if_never() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn test() { fn test() {
let i = if true { let i = if true {
loop {} loop {}
} else { } else {
3.0 3.0
}; };
i<|>; i;
} } //^ f64
"#, "#,
); );
assert_eq!(t, "f64");
} }
#[test] #[test]
fn if_else_never() { fn if_else_never() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn test(input: bool) { fn test(input: bool) {
let i = if input { let i = if input {
2.0 2.0
} else { } else {
return return
}; };
i<|>; i;
} } //^ f64
"#, "#,
); );
assert_eq!(t, "f64");
} }
#[test] #[test]
fn match_first_arm_never() { fn match_first_arm_never() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn test(a: i32) { fn test(a: i32) {
let i = match a { let i = match a {
1 => return, 1 => return,
@ -204,18 +185,16 @@ fn test(a: i32) {
3 => loop {}, 3 => loop {},
_ => 3.0, _ => 3.0,
}; };
i<|>; i;
} } //^ f64
"#, "#,
); );
assert_eq!(t, "f64");
} }
#[test] #[test]
fn match_second_arm_never() { fn match_second_arm_never() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn test(a: i32) { fn test(a: i32) {
let i = match a { let i = match a {
1 => 3.0, 1 => 3.0,
@ -223,45 +202,40 @@ fn test(a: i32) {
3 => 3.0, 3 => 3.0,
_ => return, _ => return,
}; };
i<|>; i;
} } //^ f64
"#, "#,
); );
assert_eq!(t, "f64");
} }
#[test] #[test]
fn match_all_arms_never() { fn match_all_arms_never() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn test(a: i32) { fn test(a: i32) {
let i = match a { let i = match a {
2 => return, 2 => return,
_ => loop {}, _ => loop {},
}; };
i<|>; i;
} } //^ !
"#, "#,
); );
assert_eq!(t, "!");
} }
#[test] #[test]
fn match_no_never_arms() { fn match_no_never_arms() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
fn test(a: i32) { fn test(a: i32) {
let i = match a { let i = match a {
2 => 2.0, 2 => 2.0,
_ => 3.0, _ => 3.0,
}; };
i<|>; i;
} } //^ f64
"#, "#,
); );
assert_eq!(t, "f64");
} }
#[test] #[test]

View file

@ -1,19 +1,17 @@
use super::{infer, type_at, type_at_pos};
use crate::test_db::TestDB;
use insta::assert_snapshot; use insta::assert_snapshot;
use ra_db::fixture::WithFixture;
use super::{check_types, infer};
#[test] #[test]
fn infer_box() { fn infer_box() {
let (db, pos) = TestDB::with_position( check_types(
r#" r#"
//- /main.rs crate:main deps:std //- /main.rs crate:main deps:std
fn test() { fn test() {
let x = box 1; let x = box 1;
let t = (x, box x, box &1, box [1]); let t = (x, box x, box &1, box [1]);
t<|>; t;
} } //^ (Box<i32>, Box<Box<i32>>, Box<&i32>, Box<[i32; _]>)
//- /std.rs crate:std //- /std.rs crate:std
#[prelude_import] use prelude::*; #[prelude_import] use prelude::*;
@ -25,29 +23,24 @@ mod boxed {
inner: *mut T, inner: *mut T,
} }
} }
"#, "#,
); );
assert_eq!("(Box<i32>, Box<Box<i32>>, Box<&i32>, Box<[i32; _]>)", type_at_pos(&db, pos));
} }
#[test] #[test]
fn infer_adt_self() { fn infer_adt_self() {
let (db, pos) = TestDB::with_position( check_types(
r#" r#"
//- /main.rs
enum Nat { Succ(Self), Demo(Nat), Zero } enum Nat { Succ(Self), Demo(Nat), Zero }
fn test() { fn test() {
let foo: Nat = Nat::Zero; let foo: Nat = Nat::Zero;
if let Nat::Succ(x) = foo { if let Nat::Succ(x) = foo {
x<|> x
} } //^ Nat
} }
"#, "#,
); );
assert_eq!("Nat", type_at_pos(&db, pos));
} }
#[test] #[test]
@ -93,7 +86,7 @@ fn foo() {
#[test] #[test]
fn infer_ranges() { fn infer_ranges() {
let (db, pos) = TestDB::with_position( check_types(
r#" r#"
//- /main.rs crate:main deps:core //- /main.rs crate:main deps:core
fn test() { fn test() {
@ -105,8 +98,8 @@ fn test() {
let f = 'a'..='z'; let f = 'a'..='z';
let t = (a, b, c, d, e, f); let t = (a, b, c, d, e, f);
t<|>; t;
} } //^ (RangeFull, RangeFrom<i32>, RangeTo<u32>, Range<usize>, RangeToInclusive<i32>, RangeInclusive<char>)
//- /core.rs crate:core //- /core.rs crate:core
#[prelude_import] use prelude::*; #[prelude_import] use prelude::*;
@ -135,29 +128,22 @@ pub mod ops {
} }
"#, "#,
); );
assert_eq!(
"(RangeFull, RangeFrom<i32>, RangeTo<u32>, Range<usize>, RangeToInclusive<i32>, RangeInclusive<char>)",
type_at_pos(&db, pos),
);
} }
#[test] #[test]
fn infer_while_let() { fn infer_while_let() {
let (db, pos) = TestDB::with_position( check_types(
r#" r#"
//- /main.rs
enum Option<T> { Some(T), None } enum Option<T> { Some(T), None }
fn test() { fn test() {
let foo: Option<f32> = None; let foo: Option<f32> = None;
while let Option::Some(x) = foo { while let Option::Some(x) = foo {
<|>x x
} } //^ f32
} }
"#, "#,
); );
assert_eq!("f32", type_at_pos(&db, pos));
} }
#[test] #[test]
@ -1687,9 +1673,8 @@ fn test() {
#[test] #[test]
fn shadowing_primitive() { fn shadowing_primitive() {
let t = type_at( check_types(
r#" r#"
//- /main.rs
struct i32; struct i32;
struct Foo; struct Foo;
@ -1697,15 +1682,15 @@ impl i32 { fn foo(&self) -> Foo { Foo } }
fn main() { fn main() {
let x: i32 = i32; let x: i32 = i32;
x.foo()<|>; x.foo();
//^ Foo
}"#, }"#,
); );
assert_eq!(t, "Foo");
} }
#[test] #[test]
fn not_shadowing_primitive_by_module() { fn not_shadowing_primitive_by_module() {
let t = type_at( check_types(
r#" r#"
//- /str.rs //- /str.rs
fn foo() {} fn foo() {}
@ -1715,15 +1700,15 @@ mod str;
fn foo() -> &'static str { "" } fn foo() -> &'static str { "" }
fn main() { fn main() {
foo()<|>; foo();
//^ &str
}"#, }"#,
); );
assert_eq!(t, "&str");
} }
#[test] #[test]
fn not_shadowing_module_by_primitive() { fn not_shadowing_module_by_primitive() {
let t = type_at( check_types(
r#" r#"
//- /str.rs //- /str.rs
fn foo() -> u32 {0} fn foo() -> u32 {0}
@ -1733,10 +1718,10 @@ mod str;
fn foo() -> &'static str { "" } fn foo() -> &'static str { "" }
fn main() { fn main() {
str::foo()<|>; str::foo();
//^ u32
}"#, }"#,
); );
assert_eq!(t, "u32");
} }
// This test is actually testing the shadowing behavior within ra_hir_def. It // This test is actually testing the shadowing behavior within ra_hir_def. It
@ -1744,7 +1729,7 @@ fn main() {
// capable of asserting the necessary conditions. // capable of asserting the necessary conditions.
#[test] #[test]
fn should_be_shadowing_imports() { fn should_be_shadowing_imports() {
let t = type_at( check_types(
r#" r#"
mod a { mod a {
pub fn foo() -> i8 {0} pub fn foo() -> i8 {0}
@ -1759,30 +1744,12 @@ mod d {
} }
fn main() { fn main() {
d::foo()<|>; d::foo();
//^ u8
d::foo{a:0};
//^ u8
}"#, }"#,
); );
assert_eq!(t, "u8");
let t = type_at(
r#"
mod a {
pub fn foo() -> i8 {0}
pub struct foo { a: i8 }
}
mod b { pub fn foo () -> u8 {0} }
mod c { pub struct foo { a: u8 } }
mod d {
pub use super::a::*;
pub use super::c::foo;
pub use super::b::foo;
}
fn main() {
d::foo{a:0<|>};
}"#,
);
assert_eq!(t, "u8");
} }
#[test] #[test]

View file

@ -16,6 +16,7 @@ use std::{
}; };
use serde_json::Value; use serde_json::Value;
use stdx::lines_with_ends;
use text_size::{TextRange, TextSize}; use text_size::{TextRange, TextSize};
pub use difference::Changeset as __Changeset; pub use difference::Changeset as __Changeset;
@ -159,6 +160,39 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String {
res res
} }
/// Extracts `//^ some text` annotations
pub fn extract_annotations(text: &str) -> Vec<(TextSize, String)> {
let mut res = Vec::new();
let mut prev_line_start: Option<TextSize> = None;
let mut line_start: TextSize = 0.into();
for line in lines_with_ends(text) {
if let Some(idx) = line.find("//^") {
let offset = prev_line_start.unwrap() + TextSize::of(&line[..idx + "//".len()]);
let data = line[idx + "//^".len()..].trim().to_string();
res.push((offset, data))
}
prev_line_start = Some(line_start);
line_start += TextSize::of(line);
}
res
}
#[test]
fn test_extract_annotations() {
let res = extract_annotations(&trim_indent(
r#"
fn main() {
let x = 92;
//^ def
x + 1
} //^ i32
"#,
));
assert_eq!(res, vec![(20.into(), "def".into()), (47.into(), "i32".into())]);
}
// Comparison functionality borrowed from cargo: // Comparison functionality borrowed from cargo:
/// Compare a line with an expected pattern. /// Compare a line with an expected pattern.