Implement basic type checking

This commit is contained in:
Jeff 2024-03-06 12:15:03 -05:00
parent 76be50eab3
commit 28efa78db1
3 changed files with 222 additions and 24 deletions

View File

@ -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<Value, RuntimeError> {
@ -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
}))
)
}
}

View File

@ -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<Type>),
ListExact(Vec<Type>),
Map,
None,
Range,
String,
}
impl AbstractTree for Type {
fn expected_type(
&self,
context: &crate::context::Context,
) -> Result<Type, crate::error::ValidationError> {
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<crate::Value, crate::error::RuntimeError> {
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<Type, ValidationError> {
Ok(Type::None)
}
fn validate(&self, _: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn run(self, _: &Context) -> Result<Value, RuntimeError> {
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(()));
}
}

View File

@ -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<ValidationError> for RuntimeError {
#[derive(Debug, PartialEq)]
pub enum ValidationError {
RwLockPoison(RwLockPoisonError),
ExpectedBoolean,
RwLockPoison(RwLockPoisonError),
TypeCheck(TypeCheckError),
}
impl From<RwLockPoisonError> for ValidationError {
@ -59,6 +60,12 @@ impl From<RwLockPoisonError> for ValidationError {
}
}
impl From<TypeCheckError> for ValidationError {
fn from(error: TypeCheckError) -> Self {
ValidationError::TypeCheck(error)
}
}
#[derive(Debug, PartialEq)]
pub struct RwLockPoisonError;
@ -67,3 +74,9 @@ impl<T> From<PoisonError<T>> for RwLockPoisonError {
RwLockPoisonError
}
}
#[derive(Debug, PartialEq)]
pub struct TypeCheckError {
pub actual: Type,
pub expected: Type,
}