Add structure errors

This commit is contained in:
Jeff 2024-03-20 01:29:07 -04:00
parent 896a0855e0
commit 413add3ba8
6 changed files with 132 additions and 25 deletions

View File

@ -48,15 +48,10 @@ impl Type {
| (Type::Map, Type::Map) | (Type::Map, Type::Map)
| (Type::None, Type::None) | (Type::None, Type::None)
| (Type::Range, Type::Range) | (Type::Range, Type::Range)
| (Type::String, Type::String) => Ok(()), | (Type::String, Type::String) => return Ok(()),
(Type::ListOf(left), Type::ListOf(right)) => { (Type::ListOf(left), Type::ListOf(right)) => {
if let Ok(()) = left.check(right) { if let Ok(()) = left.check(right) {
Ok(()) return Ok(());
} else {
Err(TypeConflict {
actual: left.as_ref().clone(),
expected: right.as_ref().clone(),
})
} }
} }
(Type::ListOf(list_of), Type::ListExact(list_exact)) => { (Type::ListOf(list_of), Type::ListExact(list_exact)) => {
@ -64,27 +59,50 @@ impl Type {
list_of.check(r#type)?; list_of.check(r#type)?;
} }
Ok(()) return Ok(());
} }
(Type::ListExact(list_exact), Type::ListOf(list_of)) => { (Type::ListExact(list_exact), Type::ListOf(list_of)) => {
for r#type in list_exact { for r#type in list_exact {
r#type.check(&list_of)?; r#type.check(&list_of)?;
} }
Ok(()) return Ok(());
} }
(Type::ListExact(left), Type::ListExact(right)) => { (Type::ListExact(left), Type::ListExact(right)) => {
for (left, right) in left.iter().zip(right.iter()) { for (left, right) in left.iter().zip(right.iter()) {
left.check(right)?; left.check(right)?;
} }
Ok(()) return Ok(());
} }
_ => Err(TypeConflict { (Type::Named(left), Type::Named(right)) => {
if left == right {
return Ok(());
}
}
(
Type::Named(named),
Type::Structure {
name: struct_name, ..
},
)
| (
Type::Structure {
name: struct_name, ..
},
Type::Named(named),
) => {
if named == struct_name {
return Ok(());
}
}
_ => {}
}
Err(TypeConflict {
actual: other.clone(), actual: other.clone(),
expected: self.clone(), expected: self.clone(),
}), })
}
} }
} }

View File

@ -1,5 +1,7 @@
use std::{cmp::Ordering, collections::BTreeMap, ops::Range}; use std::{cmp::Ordering, collections::BTreeMap, ops::Range};
use chumsky::container::Container;
use crate::{ use crate::{
context::Context, context::Context,
error::{RuntimeError, ValidationError}, error::{RuntimeError, ValidationError},
@ -63,7 +65,29 @@ impl AbstractNode for ValueNode {
.collect(), .collect(),
return_type: Box::new(return_type.node.clone()), return_type: Box::new(return_type.node.clone()),
}, },
ValueNode::Structure { name, fields } => todo!(), ValueNode::Structure {
name,
fields: expressions,
} => {
let mut types = Vec::with_capacity(expressions.len());
for (identifier, expression) in expressions {
let r#type = expression.node.expected_type(_context)?;
types.push((
identifier.clone(),
WithPosition {
node: r#type,
position: expression.position,
},
));
}
Type::Structure {
name: name.clone(),
fields: types,
}
}
}; };
Ok(r#type) Ok(r#type)
@ -114,12 +138,36 @@ impl AbstractNode for ValueNode {
})?; })?;
} }
if let ValueNode::Structure { name, fields } = self { if let ValueNode::Structure {
let r#type = if let Some(r#type) = context.get_type(name)? { name,
r#type fields: expressions,
} = self
{
let types = if let Some(r#type) = context.get_type(name)? {
if let Type::Structure {
name,
fields: types,
} = r#type
{
types
} else {
todo!()
}
} else { } else {
return Err(ValidationError::TypeNotFound(name.clone())); return Err(ValidationError::TypeNotFound(name.clone()));
}; };
for ((_, expression), (_, expected_type)) in expressions.iter().zip(types.iter()) {
let actual_type = expression.node.expected_type(context)?;
expected_type.node.check(&actual_type).map_err(|conflict| {
ValidationError::TypeCheck {
conflict,
actual_position: expression.position,
expected_position: expected_type.position,
}
})?
}
} }
Ok(()) Ok(())

View File

@ -73,7 +73,13 @@ impl Context {
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ValidationError> { pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ValidationError> {
if let Some(value_data) = self.inner.read()?.get(identifier) { if let Some(value_data) = self.inner.read()?.get(identifier) {
let r#type = match value_data { let r#type = match value_data {
ValueData::Type(r#type) => r#type.clone(), ValueData::Type(r#type) => {
if let Type::Named(name) = r#type {
return self.get_type(name);
}
r#type.clone()
}
ValueData::Value(value) => value.r#type(self)?, ValueData::Value(value) => value.r#type(self)?,
}; };

View File

@ -33,7 +33,7 @@ impl Error {
let (mut builder, validation_error, error_position) = match self { let (mut builder, validation_error, error_position) = match self {
Error::Parse { expected, span } => { Error::Parse { expected, span } => {
let message = if expected.is_empty() { let message = if expected.is_empty() {
"Invalid token.".to_string() "Invalid character.".to_string()
} else { } else {
format!("Expected {expected}.") format!("Expected {expected}.")
}; };
@ -77,7 +77,7 @@ impl Error {
} }
Error::Runtime { error, position } => ( Error::Runtime { error, position } => (
Report::build( Report::build(
ReportKind::Custom("Dust Error", Color::White), ReportKind::Custom("Runtime Error", Color::White),
"input", "input",
position.1, position.1,
), ),
@ -90,10 +90,11 @@ impl Error {
), ),
Error::Validation { error, position } => ( Error::Validation { error, position } => (
Report::build( Report::build(
ReportKind::Custom("Dust Error", Color::White), ReportKind::Custom("Validation Error", Color::White),
"input", "input",
position.1, position.1,
), )
.with_note("This error was detected by the interpreter before running the code."),
Some(error), Some(error),
position, position,
), ),
@ -130,6 +131,8 @@ impl Error {
} => { } => {
let TypeConflict { actual, expected } = conflict; let TypeConflict { actual, expected } = conflict;
builder = builder.with_message("A type conflict was found.");
builder.add_labels([ builder.add_labels([
Label::new(("input", expected_postion.0..expected_postion.1)).with_message( Label::new(("input", expected_postion.0..expected_postion.1)).with_message(
format!("Type {} established here.", expected.fg(type_color)), format!("Type {} established here.", expected.fg(type_color)),

View File

@ -106,7 +106,7 @@ pub fn parser<'src>() -> impl Parser<
just(Token::Keyword("range")).to(Type::Range), just(Token::Keyword("range")).to(Type::Range),
just(Token::Keyword("str")).to(Type::String), just(Token::Keyword("str")).to(Type::String),
just(Token::Keyword("list")).to(Type::List), just(Token::Keyword("list")).to(Type::List),
identifier.clone().map(|name| Type::Named(name)), identifier.clone().map(|identifier| Type::Named(identifier)),
)) ))
}) })
.map_with(|r#type, state| r#type.with_position(state.span())); .map_with(|r#type, state| r#type.with_position(state.span()));

View File

@ -1,4 +1,8 @@
use dust_lang::{abstract_tree::Identifier, error::Error, *}; use dust_lang::{
abstract_tree::{Identifier, Type},
error::{Error, TypeConflict, ValidationError},
*,
};
#[test] #[test]
fn simple_structure() { fn simple_structure() {
@ -26,6 +30,34 @@ fn simple_structure() {
) )
} }
#[test]
fn field_type_error() {
assert_eq!(
interpret(
"
struct Foo {
bar : int,
}
Foo {
bar = 'hiya',
}
"
),
Err(vec![Error::Validation {
error: ValidationError::TypeCheck {
conflict: TypeConflict {
actual: Type::String,
expected: Type::Integer
},
actual_position: (128, 134).into(),
expected_position: (56, 59).into()
},
position: (96, 166).into()
}])
)
}
#[test] #[test]
fn nested_structure() { fn nested_structure() {
assert_eq!( assert_eq!(