From bcf48d0ecbb1ffd4688ce7fde9aaf01f695c6706 Mon Sep 17 00:00:00 2001 From: greg Date: Wed, 20 Feb 2019 01:33:45 -0800 Subject: [PATCH] First tests for typechecking --- schala-lang/language/src/lib.rs | 4 +- schala-lang/language/src/typechecking.rs | 62 +++++++++++++++++++++--- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/schala-lang/language/src/lib.rs b/schala-lang/language/src/lib.rs index d28cc22..aaa26dc 100644 --- a/schala-lang/language/src/lib.rs +++ b/schala-lang/language/src/lib.rs @@ -169,8 +169,8 @@ fn typechecking(input: ast::AST, handle: &mut Schala, comp: Option<&mut Unfinish comp.map(|comp| { let artifact = TraceArtifact::new("type", match result { - Ok(typestring) => typestring, - Err(err) => format!("Type error: {}", err) + Ok(ty) => ty.to_string(), + Err(err) => format!("Type error: {}", err.msg) }); comp.add_artifact(artifact); }); diff --git a/schala-lang/language/src/typechecking.rs b/schala-lang/language/src/typechecking.rs index a0a9666..e31db7a 100644 --- a/schala-lang/language/src/typechecking.rs +++ b/schala-lang/language/src/typechecking.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use crate::ast::*; use crate::util::ScopeStack; -use crate::builtin::PrefixOp; +use crate::builtin::{PrefixOp, BinOp}; pub type TypeName = Rc; @@ -19,7 +19,7 @@ pub struct TypeContext<'a> { type InferResult = Result; #[derive(Debug, Clone)] -struct TypeError { msg: String } +pub struct TypeError { pub msg: String } impl TypeError { fn new(msg: &str) -> InferResult { //TODO make these kinds of error-producing functions CoW-ready @@ -27,7 +27,7 @@ impl TypeError { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Type { Const(TypeConst), Arrow(Box, Box), @@ -57,7 +57,7 @@ macro_rules! ty { //TODO find a better way to capture the to/from string logic impl Type { - fn to_string(&self) -> String { + pub fn to_string(&self) -> String { use self::Type::*; use self::TypeConst::*; match self { @@ -193,12 +193,12 @@ impl<'a> TypeContext<'a> { } - pub fn typecheck(&mut self, ast: &AST) -> Result { + pub fn typecheck(&mut self, ast: &AST) -> Result { let mut returned_type = Type::Const(TypeConst::Unit); for statement in ast.0.iter() { - returned_type = self.statement(statement.node()).map_err(|err| { err.msg })? + returned_type = self.statement(statement.node())?; } - Ok(returned_type.to_string()) + Ok(returned_type) } fn statement(&mut self, statement: &Statement) -> InferResult { @@ -239,8 +239,10 @@ impl<'a> TypeContext<'a> { FloatLiteral(_) => ty!(Float), StringLiteral(_) => ty!(StringT), PrefixExp(op, expr) => self.prefix(op, expr.node())?, + BinExp(op, lhs, rhs) => self.binexp(op, lhs.node(), rhs.node())?, IfExpression { discriminator, body } => self.if_expr(discriminator, body)?, Value(val) => self.handle_value(val)?, + Lambda { params, type_anno, body } => self.lambda(params, type_anno, body)?, _ => ty!(Unit), }) } @@ -255,10 +257,20 @@ impl<'a> TypeContext<'a> { self.handle_apply(f, x) } - fn handle_apply(&mut self, f: Type, x: Type) -> InferResult { + fn binexp(&mut self, op: &BinOp, lhs: &Expression, rhs: &Expression) -> InferResult { Ok(ty!(Unit)) } + fn handle_apply(&mut self, tf: Type, tx: Type) -> InferResult { + Ok(match tf { + Type::Arrow(ref t1, ref t2) => { + let _ = self.unify(*t1.clone(), tx)?; + *t2.clone() + }, + _ => return TypeError::new(&format!("Not a function")) + }) + } + fn if_expr(&mut self, discriminator: &Discriminator, body: &IfExpressionBody) -> InferResult { use self::Discriminator::*; use self::IfExpressionBody::*; match (discriminator, body) { @@ -279,6 +291,10 @@ impl<'a> TypeContext<'a> { self.unify(t2, t3) } + fn lambda(&mut self, params: &Vec, type_anno: &Option, body: &Block) -> InferResult { + Ok(ty!(Unit)) + } + fn block(&mut self, block: &Block) -> InferResult { let mut output = ty!(Unit); for s in block.iter() { @@ -303,3 +319,33 @@ impl<'a> TypeContext<'a> { }) } } + +#[cfg(test)] +mod typechecking_tests { + use super::*; + use crate::ast::AST; + + fn parse(input: &str) -> AST { + let tokens = crate::tokenizing::tokenize(input); + let mut parser = crate::parsing::Parser::new(tokens); + parser.parse().unwrap() + } + + macro_rules! assert_type_in_fresh_context { + ($string:expr, $type:expr) => { + let mut tc = TypeContext::new(); + let ref ast = parse($string); + let ty = tc.typecheck(ast).unwrap(); + assert_eq!(ty, $type) + } + } + + #[test] + fn basic_test() { + assert_type_in_fresh_context!("1", ty!(Nat)); + assert_type_in_fresh_context!(r#""drugs""#, ty!(StringT)); + assert_type_in_fresh_context!("true", ty!(Bool)); + assert_type_in_fresh_context!("-1", ty!(Int)); + } + +}