From 28efa78db1cadf86ea4b7c22f6621a89f0e43d58 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 6 Mar 2024 12:15:03 -0500 Subject: [PATCH] Implement basic type checking --- src/abstract_tree/assignment.rs | 33 +++++- src/abstract_tree/type.rs | 196 ++++++++++++++++++++++++++++---- src/error.rs | 17 ++- 3 files changed, 222 insertions(+), 24 deletions(-) diff --git a/src/abstract_tree/assignment.rs b/src/abstract_tree/assignment.rs index ba36c5d..fee6b18 100644 --- a/src/abstract_tree/assignment.rs +++ b/src/abstract_tree/assignment.rs @@ -28,8 +28,14 @@ impl<'src> AbstractTree for Assignment<'src> { todo!() } - fn validate(&self, _context: &Context) -> Result<(), ValidationError> { - todo!() + fn validate(&self, context: &Context) -> Result<(), ValidationError> { + if let Some(expected) = &self.r#type { + let statement_type = self.statement.expected_type(context)?; + + expected.check(&statement_type)?; + } + + Ok(()) } fn run(self, context: &Context) -> Result { @@ -43,7 +49,10 @@ impl<'src> AbstractTree for Assignment<'src> { #[cfg(test)] mod tests { - use crate::abstract_tree::{Expression, ValueNode}; + use crate::{ + abstract_tree::{Expression, ValueNode}, + error::TypeCheckError, + }; use super::*; @@ -64,4 +73,22 @@ mod tests { Ok(Some(Value::integer(42))) ) } + + #[test] + fn type_check() { + let validation = Assignment::new( + Identifier::new("foobar"), + Some(Type::Boolean), + Statement::Expression(Expression::Value(ValueNode::Integer(42))), + ) + .validate(&Context::new()); + + assert_eq!( + validation, + Err(ValidationError::TypeCheck(TypeCheckError { + actual: Type::Integer, + expected: Type::Boolean + })) + ) + } } diff --git a/src/abstract_tree/type.rs b/src/abstract_tree/type.rs index a87b26c..e792b2f 100644 --- a/src/abstract_tree/type.rs +++ b/src/abstract_tree/type.rs @@ -1,9 +1,15 @@ -use crate::abstract_tree::Identifier; +use crate::{ + abstract_tree::Identifier, + context::Context, + error::{RuntimeError, TypeCheckError, ValidationError}, + Value, +}; use super::AbstractTree; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Type { + Any, Boolean, Custom(Identifier), Float, @@ -12,29 +18,181 @@ pub enum Type { ListOf(Box), ListExact(Vec), Map, + None, Range, String, } -impl AbstractTree for Type { - fn expected_type( - &self, - context: &crate::context::Context, - ) -> Result { - todo!() - } +impl Type { + pub fn check(&self, other: &Type) -> Result<(), TypeCheckError> { + match (self, other) { + (Type::Any, _) + | (_, Type::Any) + | (Type::Boolean, Type::Boolean) + | (Type::Float, Type::Float) + | (Type::Integer, Type::Integer) + | (Type::List, Type::List) + | (Type::List, Type::ListOf(_)) + | (Type::List, Type::ListExact(_)) + | (Type::ListOf(_), Type::List) + | (Type::ListExact(_), Type::List) + | (Type::Map, Type::Map) + | (Type::None, Type::None) + | (Type::Range, Type::Range) + | (Type::String, Type::String) => Ok(()), + (Type::Custom(left), Type::Custom(right)) => { + if left == right { + Ok(()) + } else { + Err(TypeCheckError { + actual: other.clone(), + expected: self.clone(), + }) + } + } + (Type::ListOf(left), Type::ListOf(right)) => { + if let Ok(()) = left.check(right) { + Ok(()) + } else { + Err(TypeCheckError { + actual: left.as_ref().clone(), + expected: right.as_ref().clone(), + }) + } + } + (Type::ListOf(list_of), Type::ListExact(list_exact)) => { + for r#type in list_exact { + list_of.check(r#type)?; + } - fn validate( - &self, - context: &crate::context::Context, - ) -> Result<(), crate::error::ValidationError> { - todo!() - } + Ok(()) + } + (Type::ListExact(list_exact), Type::ListOf(list_of)) => { + for r#type in list_exact { + r#type.check(&list_of)?; + } - fn run( - self, - context: &crate::context::Context, - ) -> Result { - todo!() + Ok(()) + } + (Type::ListExact(left), Type::ListExact(right)) => { + for (left, right) in left.iter().zip(right.iter()) { + left.check(right)?; + } + + Ok(()) + } + _ => Err(TypeCheckError { + actual: other.clone(), + expected: self.clone(), + }), + } + } +} + +impl AbstractTree for Type { + fn expected_type(&self, _: &Context) -> Result { + Ok(Type::None) + } + + fn validate(&self, _: &Context) -> Result<(), ValidationError> { + Ok(()) + } + + fn run(self, _: &Context) -> Result { + Ok(Value::none()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_same_types() { + assert_eq!(Type::Any.check(&Type::Any), Ok(())); + assert_eq!(Type::Boolean.check(&Type::Boolean), Ok(())); + assert_eq!( + Type::Custom(Identifier::new("foo")).check(&Type::Custom(Identifier::new("foo"))), + Ok(()) + ); + assert_eq!(Type::Float.check(&Type::Float), Ok(())); + assert_eq!(Type::Integer.check(&Type::Integer), Ok(())); + assert_eq!(Type::List.check(&Type::List), Ok(())); + assert_eq!( + Type::ListOf(Box::new(Type::Integer)).check(&Type::ListOf(Box::new(Type::Integer))), + Ok(()) + ); + + assert_eq!( + Type::ListExact(vec![Type::Float]).check(&Type::ListExact(vec![Type::Float])), + Ok(()) + ); + assert_eq!(Type::Map.check(&Type::Map), Ok(())); + assert_eq!(Type::None.check(&Type::None), Ok(())); + assert_eq!(Type::Range.check(&Type::Range), Ok(())); + assert_eq!(Type::String.check(&Type::String), Ok(())); + } + + #[test] + fn errors() { + let foo = Type::Custom(Identifier::new("foo")); + let bar = Type::Custom(Identifier::new("bar")); + + assert_eq!( + foo.check(&bar), + Err(TypeCheckError { + actual: bar.clone(), + expected: foo.clone() + }) + ); + assert_eq!( + bar.check(&foo), + Err(TypeCheckError { + actual: foo.clone(), + expected: bar.clone() + }) + ); + + let types = [ + Type::Any, + Type::Boolean, + Type::Float, + Type::Integer, + Type::List, + Type::ListOf(Box::new(Type::Boolean)), + Type::ListExact(vec![Type::Integer]), + Type::Map, + Type::None, + Type::Range, + Type::String, + ]; + + for (left, right) in types.iter().zip(types.iter()) { + if left == right { + continue; + } + + assert_eq!( + left.check(right), + Err(TypeCheckError { + actual: right.clone(), + expected: left.clone() + }) + ); + } + } + + #[test] + fn check_list_types() { + let list = Type::List; + let list_exact = Type::ListExact(vec![Type::Integer, Type::Integer]); + let list_of = Type::ListOf(Box::new(Type::Integer)); + + assert_eq!(list.check(&list_exact), Ok(())); + assert_eq!(list.check(&list_of), Ok(())); + assert_eq!(list_exact.check(&list), Ok(())); + assert_eq!(list_exact.check(&list_of), Ok(())); + assert_eq!(list_of.check(&list), Ok(())); + assert_eq!(list_of.check(&list_exact), Ok(())); } } diff --git a/src/error.rs b/src/error.rs index e33a738..9b55da2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,7 @@ use std::sync::PoisonError; use chumsky::prelude::Rich; -use crate::lexer::Token; +use crate::{abstract_tree::Type, lexer::Token}; #[derive(Debug, PartialEq)] pub enum Error<'src> { @@ -49,8 +49,9 @@ impl From for RuntimeError { #[derive(Debug, PartialEq)] pub enum ValidationError { - RwLockPoison(RwLockPoisonError), ExpectedBoolean, + RwLockPoison(RwLockPoisonError), + TypeCheck(TypeCheckError), } impl From for ValidationError { @@ -59,6 +60,12 @@ impl From for ValidationError { } } +impl From for ValidationError { + fn from(error: TypeCheckError) -> Self { + ValidationError::TypeCheck(error) + } +} + #[derive(Debug, PartialEq)] pub struct RwLockPoisonError; @@ -67,3 +74,9 @@ impl From> for RwLockPoisonError { RwLockPoisonError } } + +#[derive(Debug, PartialEq)] +pub struct TypeCheckError { + pub actual: Type, + pub expected: Type, +}