[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 display_path = path.display().to_string();
|
||||
|
||||
let mut concrete_stuff: Option<String> = None;
|
||||
|
||||
// Start iterating over lines and processing directives....
|
||||
let mut type_assertions = Vec::new();
|
||||
let mut assertions = Vec::new();
|
||||
let mut lines = contents.lines();
|
||||
while let Some(line) = lines.next() {
|
||||
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("\n");
|
||||
}
|
||||
concrete_stuff = Some(concrete);
|
||||
|
||||
assertions.push(quote! {
|
||||
crate::assert_concrete(&_tree, #concrete, #display_path);
|
||||
});
|
||||
} else if let Some(line) = line.strip_prefix("type:") {
|
||||
let (pos, expected) = line
|
||||
.trim()
|
||||
|
|
@ -41,26 +42,30 @@ fn generate_test_for_file(path: PathBuf) -> String {
|
|||
.parse()
|
||||
.expect(&format!("Unable to parse position '{pos}'"));
|
||||
let expected = expected.trim();
|
||||
type_assertions.push(quote! {
|
||||
assertions.push(quote! {
|
||||
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 test_method = quote! {
|
||||
fn #name() {
|
||||
let (_tree, _lines) = fine::parser::parse(#contents);
|
||||
#concrete_comparison;
|
||||
#(#type_assertions)*
|
||||
#(#assertions)*
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@ impl<'a> Semantics<'a> {
|
|||
semantics
|
||||
}
|
||||
|
||||
pub fn tree(&self) -> &SyntaxTree<'a> {
|
||||
&self.syntax_tree
|
||||
}
|
||||
|
||||
pub fn snapshot_errors(&self) -> Vec<Error> {
|
||||
(*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(
|
||||
tree: &SyntaxTree,
|
||||
lines: &Lines,
|
||||
|
|
@ -92,46 +144,49 @@ fn assert_type_at(
|
|||
expected: &str,
|
||||
_source_path: &str,
|
||||
) {
|
||||
let semantics = Semantics::new(tree, lines);
|
||||
let tree_ref = match tree.find_tree_at(pos) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
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}");
|
||||
}
|
||||
None => semantic_panic!(&semantics, "Unable to find the subtee at position {pos}"),
|
||||
};
|
||||
|
||||
let semantics = Semantics::new(tree, lines);
|
||||
let tree_type = semantics.type_of(tree_ref, true);
|
||||
|
||||
let actual = format!("{}", tree_type.unwrap_or(Type::Error));
|
||||
if actual != expected {
|
||||
println!(
|
||||
"The type of the {:?} tree at position {pos} had the wrong type! Parsed the tree as:",
|
||||
tree[tree_ref].kind
|
||||
);
|
||||
println!("\n{}", tree.dump(true));
|
||||
semantic_assert_eq!(
|
||||
&semantics,
|
||||
expected,
|
||||
actual,
|
||||
"The type of the tree at position {pos} was incorrect"
|
||||
);
|
||||
}
|
||||
|
||||
let errors = semantics.snapshot_errors();
|
||||
if errors.len() == 0 {
|
||||
println!("There were no errors reported during type checking.\n");
|
||||
} else {
|
||||
println!(
|
||||
"{} error{} reported during type checking:",
|
||||
errors.len(),
|
||||
if errors.len() == 1 { "" } else { "s" }
|
||||
);
|
||||
for error in &errors {
|
||||
println!(" Error: {error}");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
fn assert_type_error_at(
|
||||
tree: &SyntaxTree,
|
||||
lines: &Lines,
|
||||
pos: usize,
|
||||
expected: &str,
|
||||
_source_path: &str,
|
||||
) {
|
||||
let semantics = Semantics::new(tree, lines);
|
||||
let tree_ref = match tree.find_tree_at(pos) {
|
||||
Some(t) => t,
|
||||
None => semantic_panic!(&semantics, "Unable to find the subtee at position {pos}"),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
expected, actual,
|
||||
"The type of the tree at position {pos} was incorrect"
|
||||
);
|
||||
}
|
||||
let tree_type = semantics.type_of(tree_ref, true);
|
||||
semantic_assert!(
|
||||
&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"));
|
||||
|
|
|
|||
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