plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
729d7cb
— Peter John
2026-07-19T19:17:31+05:30
test(plum-checker): add error-case tests for type checker
- plum-checker/Cargo.toml +4 -0
- plum-checker/src/lib.rs +11 -0
- plum-checker/tests/checker_tests.rs +43 -1
plum-checker/Cargo.toml
CHANGED
|
@@ -5,3 +5,7 @@ edition = "2021"
|
|
|
5
5
|
|
|
6
6
|
[dependencies]
|
|
7
7
|
plum-core = { path = "../plum-core" }
|
|
8
|
+
|
|
9
|
+
[dev-dependencies]
|
|
10
|
+
tree-sitter = "0.26"
|
|
11
|
+
tree-sitter-plum = { path = "../tooling/tree-sitter-plum" }
|
plum-checker/src/lib.rs
CHANGED
|
@@ -111,6 +111,17 @@ fn check_fn(f: &ast::Fn, global_env: &TypeEnv) -> Vec<CheckError> {
|
|
|
111
111
|
ast::FnBody::Block(block) => {
|
|
112
112
|
let mut block_errors = check_block(block, &mut env, &declared_ret, &f.name);
|
|
113
113
|
errors.append(&mut block_errors);
|
|
114
|
+
// Check the type of the last expression statement against the declared return type
|
|
115
|
+
if let Some(ast::Stmt::Expr(last_expr)) = block.stmts.last() {
|
|
116
|
+
match infer_expr(last_expr, &env) {
|
|
117
|
+
Ok(t) => {
|
|
118
|
+
if let Err(msg) = unify(&declared_ret, &t) {
|
|
119
|
+
errors.push(CheckError { message: format!("fn '{}': return type mismatch: {}", f.name, msg) });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
Err(_) => {} // already reported by check_block
|
|
123
|
+
}
|
|
124
|
+
}
|
|
114
125
|
}
|
|
115
126
|
}
|
|
116
127
|
errors
|
plum-checker/tests/checker_tests.rs
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
use plum_checker::types::*;
|
|
2
|
-
use plum_checker::{plum_type_from_ast, unify};
|
|
2
|
+
use plum_checker::{check_source, plum_type_from_ast, unify};
|
|
3
3
|
use plum_core::ast::Type as AstType;
|
|
4
|
+
use plum_core::{ast::*, AstParser};
|
|
5
|
+
|
|
6
|
+
fn parse(src: &str) -> Source {
|
|
7
|
+
let mut parser = tree_sitter::Parser::new();
|
|
8
|
+
parser.set_language(&tree_sitter_plum::LANGUAGE.into()).unwrap();
|
|
9
|
+
let tree = parser.parse(src, None).unwrap();
|
|
10
|
+
let ap = AstParser::new(src);
|
|
11
|
+
ap.parse_source(tree.root_node())
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
#[test]
|
|
6
15
|
fn ast_type_int_maps_to_tint() {
|
|
@@ -41,3 +50,36 @@ fn mono_scheme() {
|
|
|
41
50
|
assert!(scheme.vars.is_empty());
|
|
42
51
|
assert_eq!(*scheme.body, PlumType::TInt);
|
|
43
52
|
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn valid_add_fn_passes() {
|
|
56
|
+
let src = "add(a: Int, b: Int) -> Int =\n a + b\n";
|
|
57
|
+
let source = parse(src);
|
|
58
|
+
assert!(check_source(&source).is_ok(), "expected Ok");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[test]
|
|
62
|
+
fn wrong_return_type_is_error() {
|
|
63
|
+
let src = "bad() -> Int =\n True\n";
|
|
64
|
+
let source = parse(src);
|
|
65
|
+
let result = check_source(&source);
|
|
66
|
+
assert!(result.is_err());
|
|
67
|
+
let errs = result.unwrap_err();
|
|
68
|
+
assert!(errs[0].message.contains("return type mismatch"), "got: {}", errs[0].message);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[test]
|
|
72
|
+
fn undeclared_var_is_error() {
|
|
73
|
+
let src = "bad() -> Int =\n x\n";
|
|
74
|
+
let source = parse(src);
|
|
75
|
+
let result = check_source(&source);
|
|
76
|
+
assert!(result.is_err());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#[test]
|
|
80
|
+
fn type_mismatch_in_binary_op_is_error() {
|
|
81
|
+
let src = "bad() -> Int =\n 1 + 2.0\n";
|
|
82
|
+
let source = parse(src);
|
|
83
|
+
let result = check_source(&source);
|
|
84
|
+
assert!(result.is_err());
|
|
85
|
+
}
|