Implement basic type checking
This commit is contained in:
parent
76be50eab3
commit
28efa78db1
@ -28,8 +28,14 @@ impl<'src> AbstractTree for Assignment<'src> {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _context: &Context) -> Result<(), ValidationError> {
|
fn validate(&self, context: &Context) -> Result<(), ValidationError> {
|
||||||
todo!()
|
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> {
|
fn run(self, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
@ -43,7 +49,10 @@ impl<'src> AbstractTree for Assignment<'src> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::abstract_tree::{Expression, ValueNode};
|
use crate::{
|
||||||
|
abstract_tree::{Expression, ValueNode},
|
||||||
|
error::TypeCheckError,
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -64,4 +73,22 @@ mod tests {
|
|||||||
Ok(Some(Value::integer(42)))
|
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
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
use crate::abstract_tree::Identifier;
|
use crate::{
|
||||||
|
abstract_tree::Identifier,
|
||||||
|
context::Context,
|
||||||
|
error::{RuntimeError, TypeCheckError, ValidationError},
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
use super::AbstractTree;
|
use super::AbstractTree;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub enum Type {
|
pub enum Type {
|
||||||
|
Any,
|
||||||
Boolean,
|
Boolean,
|
||||||
Custom(Identifier),
|
Custom(Identifier),
|
||||||
Float,
|
Float,
|
||||||
@ -12,29 +18,181 @@ pub enum Type {
|
|||||||
ListOf(Box<Type>),
|
ListOf(Box<Type>),
|
||||||
ListExact(Vec<Type>),
|
ListExact(Vec<Type>),
|
||||||
Map,
|
Map,
|
||||||
|
None,
|
||||||
Range,
|
Range,
|
||||||
String,
|
String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AbstractTree for Type {
|
impl Type {
|
||||||
fn expected_type(
|
pub fn check(&self, other: &Type) -> Result<(), TypeCheckError> {
|
||||||
&self,
|
match (self, other) {
|
||||||
context: &crate::context::Context,
|
(Type::Any, _)
|
||||||
) -> Result<Type, crate::error::ValidationError> {
|
| (_, Type::Any)
|
||||||
todo!()
|
| (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(
|
Ok(())
|
||||||
&self,
|
}
|
||||||
context: &crate::context::Context,
|
(Type::ListExact(list_exact), Type::ListOf(list_of)) => {
|
||||||
) -> Result<(), crate::error::ValidationError> {
|
for r#type in list_exact {
|
||||||
todo!()
|
r#type.check(&list_of)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
Ok(())
|
||||||
self,
|
}
|
||||||
context: &crate::context::Context,
|
(Type::ListExact(left), Type::ListExact(right)) => {
|
||||||
) -> Result<crate::Value, crate::error::RuntimeError> {
|
for (left, right) in left.iter().zip(right.iter()) {
|
||||||
todo!()
|
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(()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
src/error.rs
17
src/error.rs
@ -2,7 +2,7 @@ use std::sync::PoisonError;
|
|||||||
|
|
||||||
use chumsky::prelude::Rich;
|
use chumsky::prelude::Rich;
|
||||||
|
|
||||||
use crate::lexer::Token;
|
use crate::{abstract_tree::Type, lexer::Token};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error<'src> {
|
pub enum Error<'src> {
|
||||||
@ -49,8 +49,9 @@ impl From<ValidationError> for RuntimeError {
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
RwLockPoison(RwLockPoisonError),
|
|
||||||
ExpectedBoolean,
|
ExpectedBoolean,
|
||||||
|
RwLockPoison(RwLockPoisonError),
|
||||||
|
TypeCheck(TypeCheckError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RwLockPoisonError> for ValidationError {
|
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)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct RwLockPoisonError;
|
pub struct RwLockPoisonError;
|
||||||
|
|
||||||
@ -67,3 +74,9 @@ impl<T> From<PoisonError<T>> for RwLockPoisonError {
|
|||||||
RwLockPoisonError
|
RwLockPoisonError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct TypeCheckError {
|
||||||
|
pub actual: Type,
|
||||||
|
pub expected: Type,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user