Add new tests for type checking
This commit is contained in:
parent
ce4d366bab
commit
fa7fb57600
@ -68,17 +68,30 @@ impl AbstractTree for Assignment {
|
|||||||
|
|
||||||
match operator {
|
match operator {
|
||||||
AssignmentOperator::Equal => {
|
AssignmentOperator::Equal => {
|
||||||
type_definition.inner().check(&statement_type)?;
|
type_definition
|
||||||
|
.inner()
|
||||||
|
.check(&statement_type)
|
||||||
|
.map_err(|error| error.at_node(statement_node, source))?;
|
||||||
}
|
}
|
||||||
AssignmentOperator::PlusEqual => {
|
AssignmentOperator::PlusEqual => {
|
||||||
let identifier_type = identifier.expected_type(context)?;
|
let identifier_type = identifier.expected_type(context)?;
|
||||||
|
|
||||||
if let Type::List(item_type) = type_definition.inner() {
|
if let Type::List(item_type) = type_definition.inner() {
|
||||||
item_type.check(&identifier_type)?;
|
item_type
|
||||||
item_type.check(&statement_type)?;
|
.check(&identifier_type)
|
||||||
|
.map_err(|error| error.at_node(identifier_node, source))?;
|
||||||
|
item_type
|
||||||
|
.check(&statement_type)
|
||||||
|
.map_err(|error| error.at_node(statement_node, source))?;
|
||||||
} else {
|
} else {
|
||||||
type_definition.inner().check(&identifier_type)?;
|
type_definition
|
||||||
type_definition.inner().check(&statement_type)?;
|
.inner()
|
||||||
|
.check(&identifier_type)
|
||||||
|
.map_err(|error| error.at_node(identifier_node, source))?;
|
||||||
|
type_definition
|
||||||
|
.inner()
|
||||||
|
.check(&statement_type)
|
||||||
|
.map_err(|error| error.at_node(statement_node, source))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssignmentOperator::MinusEqual => todo!(),
|
AssignmentOperator::MinusEqual => todo!(),
|
||||||
@ -118,8 +131,6 @@ impl AbstractTree for Assignment {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(type_defintion) = &self.type_definition {
|
if let Some(type_defintion) = &self.type_definition {
|
||||||
type_defintion.inner().check(&new_value.r#type())?;
|
|
||||||
|
|
||||||
context.set(key.clone(), new_value, Some(type_defintion.inner().clone()))?;
|
context.set(key.clone(), new_value, Some(type_defintion.inner().clone()))?;
|
||||||
} else {
|
} else {
|
||||||
context.set(key.clone(), new_value, None)?;
|
context.set(key.clone(), new_value, None)?;
|
||||||
@ -135,7 +146,7 @@ impl AbstractTree for Assignment {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{evaluate, Error, List, Value};
|
use crate::{evaluate, Error, List, Type, Value};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_assignment() {
|
fn simple_assignment() {
|
||||||
@ -167,18 +178,19 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_add_wrong_type() {
|
fn list_add_wrong_type() {
|
||||||
let test = evaluate(
|
let result = evaluate(
|
||||||
"
|
"
|
||||||
x <[str]> = []
|
x <[str]> = []
|
||||||
x += 1
|
x += 1
|
||||||
",
|
",
|
||||||
)
|
);
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
match test {
|
assert_eq!(
|
||||||
Error::WithContext { .. } => {}
|
Err(Error::TypeCheck {
|
||||||
Error::TypeCheck { .. } => {}
|
expected: Type::String,
|
||||||
_ => panic!(),
|
actual: Type::Integer
|
||||||
}
|
}),
|
||||||
|
result
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,11 +49,7 @@ impl AbstractTree for FunctionCall {
|
|||||||
|
|
||||||
expected_type
|
expected_type
|
||||||
.check(&expression_type)
|
.check(&expression_type)
|
||||||
.map_err(|error| Error::WithContext {
|
.map_err(|error| error.at_node(child, source))?;
|
||||||
error: Box::new(error),
|
|
||||||
location: child.start_position(),
|
|
||||||
source: source[child.byte_range()].to_string(),
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments.push(expression);
|
arguments.push(expression);
|
||||||
|
@ -83,7 +83,14 @@ impl Type {
|
|||||||
| (Type::Float, Type::Number)
|
| (Type::Float, Type::Number)
|
||||||
| (Type::String, Type::String) => Ok(()),
|
| (Type::String, Type::String) => Ok(()),
|
||||||
(Type::List(self_item_type), Type::List(other_item_type)) => {
|
(Type::List(self_item_type), Type::List(other_item_type)) => {
|
||||||
self_item_type.check(&other_item_type)
|
if self_item_type.check(&other_item_type).is_err() {
|
||||||
|
Err(Error::TypeCheck {
|
||||||
|
expected: self.clone(),
|
||||||
|
actual: other.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
Type::Function {
|
Type::Function {
|
||||||
@ -100,9 +107,7 @@ impl Type {
|
|||||||
.zip(other_parameter_types.iter());
|
.zip(other_parameter_types.iter());
|
||||||
|
|
||||||
for (self_parameter_type, other_parameter_type) in parameter_type_pairs {
|
for (self_parameter_type, other_parameter_type) in parameter_type_pairs {
|
||||||
let check = self_parameter_type.check(&other_parameter_type);
|
if self_parameter_type.check(&other_parameter_type).is_err() {
|
||||||
|
|
||||||
if let Err(Error::TypeCheck { .. }) = check {
|
|
||||||
return Err(Error::TypeCheck {
|
return Err(Error::TypeCheck {
|
||||||
expected: self.clone(),
|
expected: self.clone(),
|
||||||
actual: other.clone(),
|
actual: other.clone(),
|
||||||
@ -110,16 +115,14 @@ impl Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let check = self_return_type.check(other_return_type);
|
if self_return_type.check(other_return_type).is_err() {
|
||||||
|
Err(Error::TypeCheck {
|
||||||
if let Err(Error::TypeCheck { .. }) = check {
|
|
||||||
return Err(Error::TypeCheck {
|
|
||||||
expected: self.clone(),
|
expected: self.clone(),
|
||||||
actual: other.clone(),
|
actual: other.clone(),
|
||||||
});
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
_ => Err(Error::TypeCheck {
|
_ => Err(Error::TypeCheck {
|
||||||
expected: self.clone(),
|
expected: self.clone(),
|
||||||
@ -229,3 +232,49 @@ impl Display for Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::evaluate;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_type_check() {
|
||||||
|
let result = evaluate("x <bool> = 1");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Err(Error::TypeCheck {
|
||||||
|
expected: Type::Boolean,
|
||||||
|
actual: Type::Integer
|
||||||
|
}),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn callback_type_check() {
|
||||||
|
let result = evaluate(
|
||||||
|
"
|
||||||
|
x = (fn cb <() -> bool>) <bool> {
|
||||||
|
(cb)
|
||||||
|
}
|
||||||
|
(x (fn) <int> { 1 })
|
||||||
|
",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Err(Error::TypeCheck {
|
||||||
|
expected: Type::Function {
|
||||||
|
parameter_types: vec![],
|
||||||
|
return_type: Box::new(Type::Boolean),
|
||||||
|
},
|
||||||
|
actual: Type::Function {
|
||||||
|
parameter_types: vec![],
|
||||||
|
return_type: Box::new(Type::Integer),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -156,11 +156,11 @@ pub enum Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn with_context(self, location: Point, source: String) -> Self {
|
pub fn at_node(self, node: Node, source: &str) -> Self {
|
||||||
Error::WithContext {
|
Error::WithContext {
|
||||||
error: Box::new(self),
|
error: Box::new(self),
|
||||||
location,
|
location: node.start_position(),
|
||||||
source,
|
source: source[node.byte_range()].to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,16 +50,6 @@ impl Function {
|
|||||||
pub fn call(&self, arguments: &[Expression], source: &str, context: &Map) -> Result<Value> {
|
pub fn call(&self, arguments: &[Expression], source: &str, context: &Map) -> Result<Value> {
|
||||||
let function_context = Map::clone_from(context)?;
|
let function_context = Map::clone_from(context)?;
|
||||||
|
|
||||||
let (parameter_types, return_type) = if let Type::Function {
|
|
||||||
parameter_types,
|
|
||||||
return_type,
|
|
||||||
} = &self.r#type
|
|
||||||
{
|
|
||||||
(parameter_types, return_type)
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.parameters.len() != arguments.len() {
|
if self.parameters.len() != arguments.len() {
|
||||||
return Err(Error::ExpectedArgumentAmount {
|
return Err(Error::ExpectedArgumentAmount {
|
||||||
function_name: "",
|
function_name: "",
|
||||||
@ -68,27 +58,18 @@ impl Function {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let parameter_argument_pairs = self
|
let parameter_argument_pairs = self.parameters.iter().zip(arguments.iter());
|
||||||
.parameters
|
|
||||||
.iter()
|
|
||||||
.zip(parameter_types.iter())
|
|
||||||
.zip(arguments.iter());
|
|
||||||
|
|
||||||
for ((identifier, argument_type), expression) in parameter_argument_pairs {
|
for (identifier, expression) in parameter_argument_pairs {
|
||||||
let value = expression.run(source, context)?;
|
let value = expression.run(source, context)?;
|
||||||
let value_type = value.r#type();
|
|
||||||
|
|
||||||
argument_type.check(&value_type)?;
|
|
||||||
|
|
||||||
let key = identifier.inner().clone();
|
let key = identifier.inner().clone();
|
||||||
|
|
||||||
function_context.set(key, value, Some(value_type))?;
|
function_context.set(key, value, None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let return_value = self.body.run(source, &function_context)?;
|
let return_value = self.body.run(source, &function_context)?;
|
||||||
|
|
||||||
return_type.check(&return_value.r#type())?;
|
|
||||||
|
|
||||||
Ok(return_value)
|
Ok(return_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,10 +53,6 @@ impl Map {
|
|||||||
.write()?
|
.write()?
|
||||||
.insert(key, (value, value_type.clone()));
|
.insert(key, (value, value_type.clone()));
|
||||||
|
|
||||||
if let Some((_previous_value, previous_type)) = previous.clone() {
|
|
||||||
previous_type.check(&value_type)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(previous)
|
Ok(previous)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,3 +114,52 @@ Complex Function Call
|
|||||||
(expression
|
(expression
|
||||||
(value
|
(value
|
||||||
(integer)))))))))))
|
(integer)))))))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Callback Function Call
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
x = (fn cb <() -> bool>) <bool> {
|
||||||
|
(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
(x (fn) <bool> { true })
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(root
|
||||||
|
(statement
|
||||||
|
(assignment
|
||||||
|
(identifier)
|
||||||
|
(assignment_operator)
|
||||||
|
(statement
|
||||||
|
(expression
|
||||||
|
(value
|
||||||
|
(function
|
||||||
|
(identifier)
|
||||||
|
(type_definition
|
||||||
|
(type
|
||||||
|
(type)))
|
||||||
|
(type_definition
|
||||||
|
(type))
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(expression
|
||||||
|
(function_call
|
||||||
|
(expression
|
||||||
|
(identifier))))))))))))
|
||||||
|
(statement
|
||||||
|
(expression
|
||||||
|
(function_call
|
||||||
|
(expression
|
||||||
|
(identifier))
|
||||||
|
(expression
|
||||||
|
(value
|
||||||
|
(function
|
||||||
|
(type_definition
|
||||||
|
(type))
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(expression
|
||||||
|
(value
|
||||||
|
(boolean))))))))))))
|
||||||
|
Loading…
Reference in New Issue
Block a user