[fine] Tests for type errors
WHEEEEEEE!
This commit is contained in:
parent
618e0028d3
commit
a9c1b04920
8 changed files with 123 additions and 47 deletions
|
|
@ -7,10 +7,8 @@ fn generate_test_for_file(path: PathBuf) -> String {
|
||||||
let contents = fs::read_to_string(&path).expect("Unable to read input");
|
let contents = fs::read_to_string(&path).expect("Unable to read input");
|
||||||
let display_path = path.display().to_string();
|
let display_path = path.display().to_string();
|
||||||
|
|
||||||
let mut concrete_stuff: Option<String> = None;
|
|
||||||
|
|
||||||
// Start iterating over lines and processing directives....
|
// Start iterating over lines and processing directives....
|
||||||
let mut type_assertions = Vec::new();
|
let mut assertions = Vec::new();
|
||||||
let mut lines = contents.lines();
|
let mut lines = contents.lines();
|
||||||
while let Some(line) = lines.next() {
|
while let Some(line) = lines.next() {
|
||||||
let line = match line.strip_prefix("//") {
|
let line = match line.strip_prefix("//") {
|
||||||
|
|
@ -30,7 +28,10 @@ fn generate_test_for_file(path: PathBuf) -> String {
|
||||||
concrete.push_str(line);
|
concrete.push_str(line);
|
||||||
concrete.push_str("\n");
|
concrete.push_str("\n");
|
||||||
}
|
}
|
||||||
concrete_stuff = Some(concrete);
|
|
||||||
|
assertions.push(quote! {
|
||||||
|
crate::assert_concrete(&_tree, #concrete, #display_path);
|
||||||
|
});
|
||||||
} else if let Some(line) = line.strip_prefix("type:") {
|
} else if let Some(line) = line.strip_prefix("type:") {
|
||||||
let (pos, expected) = line
|
let (pos, expected) = line
|
||||||
.trim()
|
.trim()
|
||||||
|
|
@ -41,26 +42,30 @@ fn generate_test_for_file(path: PathBuf) -> String {
|
||||||
.parse()
|
.parse()
|
||||||
.expect(&format!("Unable to parse position '{pos}'"));
|
.expect(&format!("Unable to parse position '{pos}'"));
|
||||||
let expected = expected.trim();
|
let expected = expected.trim();
|
||||||
type_assertions.push(quote! {
|
assertions.push(quote! {
|
||||||
crate::assert_type_at(&_tree, &_lines, #pos, #expected, #display_path);
|
crate::assert_type_at(&_tree, &_lines, #pos, #expected, #display_path);
|
||||||
});
|
});
|
||||||
|
} else if let Some(line) = line.strip_prefix("type-error:") {
|
||||||
|
let (pos, expected) = line
|
||||||
|
.trim()
|
||||||
|
.split_once(' ')
|
||||||
|
.expect("Mal-formed type-error expectation");
|
||||||
|
let pos: usize = pos
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.expect(&format!("Unable to parse position '{pos}'"));
|
||||||
|
let expected = expected.trim();
|
||||||
|
assertions.push(quote! {
|
||||||
|
crate::assert_type_error_at(&_tree, &_lines, #pos, #expected, #display_path);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let concrete_comparison = if let Some(concrete) = concrete_stuff {
|
|
||||||
quote! {
|
|
||||||
crate::assert_concrete(&_tree, #concrete, #display_path)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {}
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = format_ident!("{}", path.file_stem().unwrap().to_string_lossy());
|
let name = format_ident!("{}", path.file_stem().unwrap().to_string_lossy());
|
||||||
let test_method = quote! {
|
let test_method = quote! {
|
||||||
fn #name() {
|
fn #name() {
|
||||||
let (_tree, _lines) = fine::parser::parse(#contents);
|
let (_tree, _lines) = fine::parser::parse(#contents);
|
||||||
#concrete_comparison;
|
#(#assertions)*
|
||||||
#(#type_assertions)*
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,10 @@ impl<'a> Semantics<'a> {
|
||||||
semantics
|
semantics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tree(&self) -> &SyntaxTree<'a> {
|
||||||
|
&self.syntax_tree
|
||||||
|
}
|
||||||
|
|
||||||
pub fn snapshot_errors(&self) -> Vec<Error> {
|
pub fn snapshot_errors(&self) -> Vec<Error> {
|
||||||
(*self.errors.borrow()).clone()
|
(*self.errors.borrow()).clone()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,58 @@ fn assert_concrete(tree: &SyntaxTree, expected: &str, source_path: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn report_semantic_error(semantics: &Semantics, message: &str) {
|
||||||
|
let tree = semantics.tree();
|
||||||
|
|
||||||
|
println!("{message}! Parsed the tree as:");
|
||||||
|
println!("\n{}", tree.dump(true));
|
||||||
|
|
||||||
|
let errors = semantics.snapshot_errors();
|
||||||
|
if errors.len() == 0 {
|
||||||
|
println!("There were no errors reported during checking.\n");
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"{} error{} reported during checking:",
|
||||||
|
errors.len(),
|
||||||
|
if errors.len() == 1 { "" } else { "s" }
|
||||||
|
);
|
||||||
|
for error in &errors {
|
||||||
|
println!(" Error: {error}");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! semantic_panic {
|
||||||
|
($semantics:expr, $($t:tt)*) => {{
|
||||||
|
let message = format!($($t)*);
|
||||||
|
report_semantic_error($semantics, &message);
|
||||||
|
panic!("{message}");
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! semantic_assert {
|
||||||
|
($semantics:expr, $pred:expr, $($t:tt)*) => {{
|
||||||
|
if !$pred {
|
||||||
|
let message = format!($($t)*);
|
||||||
|
report_semantic_error($semantics, &message);
|
||||||
|
panic!("{message}");
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! semantic_assert_eq {
|
||||||
|
($semantics:expr, $left:expr, $right:expr, $($t:tt)*) => {{
|
||||||
|
let ll = $left;
|
||||||
|
let rr = $right;
|
||||||
|
if ll != rr {
|
||||||
|
let message = format!($($t)*);
|
||||||
|
report_semantic_error($semantics, &message);
|
||||||
|
assert_eq!(ll, rr, "{}", message);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_type_at(
|
fn assert_type_at(
|
||||||
tree: &SyntaxTree,
|
tree: &SyntaxTree,
|
||||||
lines: &Lines,
|
lines: &Lines,
|
||||||
|
|
@ -92,46 +144,49 @@ fn assert_type_at(
|
||||||
expected: &str,
|
expected: &str,
|
||||||
_source_path: &str,
|
_source_path: &str,
|
||||||
) {
|
) {
|
||||||
|
let semantics = Semantics::new(tree, lines);
|
||||||
let tree_ref = match tree.find_tree_at(pos) {
|
let tree_ref = match tree.find_tree_at(pos) {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => {
|
None => semantic_panic!(&semantics, "Unable to find the subtee at position {pos}"),
|
||||||
println!("Unable to find the subtee at position {pos}! Parsed the tree as:");
|
|
||||||
println!("\n{}", tree.dump(true));
|
|
||||||
panic!("Cannot find tree at position {pos}");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let semantics = Semantics::new(tree, lines);
|
|
||||||
let tree_type = semantics.type_of(tree_ref, true);
|
let tree_type = semantics.type_of(tree_ref, true);
|
||||||
|
|
||||||
let actual = format!("{}", tree_type.unwrap_or(Type::Error));
|
let actual = format!("{}", tree_type.unwrap_or(Type::Error));
|
||||||
if actual != expected {
|
semantic_assert_eq!(
|
||||||
println!(
|
&semantics,
|
||||||
"The type of the {:?} tree at position {pos} had the wrong type! Parsed the tree as:",
|
expected,
|
||||||
tree[tree_ref].kind
|
actual,
|
||||||
);
|
"The type of the tree at position {pos} was incorrect"
|
||||||
println!("\n{}", tree.dump(true));
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let errors = semantics.snapshot_errors();
|
fn assert_type_error_at(
|
||||||
if errors.len() == 0 {
|
tree: &SyntaxTree,
|
||||||
println!("There were no errors reported during type checking.\n");
|
lines: &Lines,
|
||||||
} else {
|
pos: usize,
|
||||||
println!(
|
expected: &str,
|
||||||
"{} error{} reported during type checking:",
|
_source_path: &str,
|
||||||
errors.len(),
|
) {
|
||||||
if errors.len() == 1 { "" } else { "s" }
|
let semantics = Semantics::new(tree, lines);
|
||||||
);
|
let tree_ref = match tree.find_tree_at(pos) {
|
||||||
for error in &errors {
|
Some(t) => t,
|
||||||
println!(" Error: {error}");
|
None => semantic_panic!(&semantics, "Unable to find the subtee at position {pos}"),
|
||||||
}
|
};
|
||||||
println!();
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(
|
let tree_type = semantics.type_of(tree_ref, true);
|
||||||
expected, actual,
|
semantic_assert!(
|
||||||
"The type of the tree at position {pos} was incorrect"
|
&semantics,
|
||||||
);
|
matches!(tree_type, Some(Type::Error)),
|
||||||
}
|
"The type of the {:?} tree at position {pos} was '{tree_type:?}', not an error",
|
||||||
|
tree[tree_ref].kind
|
||||||
|
);
|
||||||
|
|
||||||
|
let errors = semantics.snapshot_errors();
|
||||||
|
semantic_assert!(
|
||||||
|
&semantics,
|
||||||
|
errors.iter().any(|e| e.message == expected),
|
||||||
|
"Unable to find the expected error message '{expected}'"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/generated_tests.rs"));
|
include!(concat!(env!("OUT_DIR"), "/generated_tests.rs"));
|
||||||
|
|
|
||||||
4
fine/tests/expression/errors/binary_mismatch.fine
Normal file
4
fine/tests/expression/errors/binary_mismatch.fine
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
112 - "twenty five";
|
||||||
|
"twenty five" - 112;
|
||||||
|
// type-error: 4 cannot apply binary operator '-' to expressions of type 'f64' (on the left) and 'string' (on the right)
|
||||||
|
// type-error: 35 cannot apply binary operator '-' to expressions of type 'string' (on the left) and 'f64' (on the right)
|
||||||
2
fine/tests/expression/errors/if_mismatched_arms.fine
Normal file
2
fine/tests/expression/errors/if_mismatched_arms.fine
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
if true { "blarg" } else { 23 }
|
||||||
|
// type-error: 0 the type of the `then` branch (string) must match the type of the `else` branch (f64)
|
||||||
2
fine/tests/expression/errors/if_not_bool.fine
Normal file
2
fine/tests/expression/errors/if_not_bool.fine
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
if 23 { "what" } else { "the" }
|
||||||
|
// type-error: 0 conditions must yield a boolean
|
||||||
2
fine/tests/expression/errors/if_requires_else.fine
Normal file
2
fine/tests/expression/errors/if_requires_else.fine
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
if (if false { true }) { 32 } else { 23 }
|
||||||
|
// type-error: 4 this conditional expression needs an else arm to produce a value
|
||||||
2
fine/tests/expression/errors/unary_mismatch.fine
Normal file
2
fine/tests/expression/errors/unary_mismatch.fine
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
- "twenty five";
|
||||||
|
// type-error: 0 cannot apply unary operator '-' to value of type string
|
||||||
Loading…
Add table
Add a link
Reference in a new issue