1
0

Begin implementing type arguments

This commit is contained in:
Jeff 2024-03-24 12:21:08 -04:00
parent a0a9bc2fdf
commit 6b0bb0016f
16 changed files with 139 additions and 78 deletions

View File

@ -161,7 +161,7 @@ impl AbstractNode for Assignment {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
abstract_tree::{Expression, ValueNode}, abstract_tree::{Expression, ValueNode, WithPos},
error::TypeConflict, error::TypeConflict,
}; };

View File

@ -51,7 +51,7 @@ impl AbstractNode for Block {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
abstract_tree::{Expression, ValueNode}, abstract_tree::{Expression, ValueNode, WithPos},
Value, Value,
}; };

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
context::Context, context::Context,
error::{RuntimeError, ValidationError}, error::{RuntimeError, ValidationError},
value::ValueInner, value::{Function, ParsedFunction, ValueInner},
}; };
use super::{AbstractNode, Action, Expression, Type, WithPosition}; use super::{AbstractNode, Action, Expression, Type, WithPosition};
@ -32,7 +32,7 @@ impl AbstractNode for FunctionCall {
let function_node_type = self.function.node.expected_type(_context)?; let function_node_type = self.function.node.expected_type(_context)?;
if let Type::Function { return_type, .. } = function_node_type { if let Type::Function { return_type, .. } = function_node_type {
Ok(*return_type) Ok(return_type.node)
} else { } else {
Err(ValidationError::ExpectedFunction { Err(ValidationError::ExpectedFunction {
actual: function_node_type, actual: function_node_type,
@ -41,14 +41,31 @@ impl AbstractNode for FunctionCall {
} }
} }
fn validate(&self, _context: &Context) -> Result<(), ValidationError> { fn validate(&self, context: &Context) -> Result<(), ValidationError> {
for expression in &self.arguments { for expression in &self.arguments {
expression.node.validate(_context)?; expression.node.validate(context)?;
} }
let function_node_type = self.function.node.expected_type(_context)?; let function_node_type = self.function.node.expected_type(context)?;
if let Type::Function {
parameter_types,
return_type: _,
} = function_node_type
{
for (type_parameter, type_argument) in
parameter_types.iter().zip(self.type_arguments.iter())
{
type_parameter
.node
.check(&type_argument.node)
.map_err(|conflict| ValidationError::TypeCheck {
conflict,
actual_position: type_argument.position,
expected_position: type_parameter.position,
})?;
}
if let Type::Function { .. } = function_node_type {
Ok(()) Ok(())
} else { } else {
Err(ValidationError::ExpectedFunction { Err(ValidationError::ExpectedFunction {
@ -94,6 +111,21 @@ impl AbstractNode for FunctionCall {
let function_context = Context::new(); let function_context = Context::new();
if let Function::Parsed(ParsedFunction {
type_parameters, ..
}) = function
{
for (type_parameter, type_argument) in type_parameters
.iter()
.map(|r#type| r#type.node.clone())
.zip(self.type_arguments.into_iter().map(|r#type| r#type.node))
{
if let Type::Argument(identifier) = type_parameter {
function_context.set_type(identifier, type_argument)?;
}
}
};
function_context.inherit_data_from(&context)?; function_context.inherit_data_from(&context)?;
function.clone().call(arguments, function_context) function.clone().call(arguments, function_context)
} }

View File

@ -95,7 +95,7 @@ impl AbstractNode for IfElse {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
abstract_tree::{Statement, ValueNode}, abstract_tree::{Statement, ValueNode, WithPos},
Value, Value,
}; };

View File

@ -90,7 +90,7 @@ impl AbstractNode for ListIndex {
let found_item = list.get(index as usize); let found_item = list.get(index as usize);
if let Some(item) = found_item { if let Some(item) = found_item {
Ok(Action::Return(item.clone())) Ok(Action::Return(item.node.clone()))
} else { } else {
Ok(Action::None) Ok(Action::None)
} }

View File

@ -187,7 +187,7 @@ impl AbstractNode for Logic {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::abstract_tree::ValueNode; use crate::abstract_tree::{ValueNode, WithPos};
use super::*; use super::*;

View File

@ -70,7 +70,8 @@ impl Ord for Loop {
mod tests { mod tests {
use crate::{ use crate::{
abstract_tree::{ abstract_tree::{
Assignment, AssignmentOperator, Block, Expression, Identifier, IfElse, Logic, ValueNode, Assignment, AssignmentOperator, Block, Expression, Identifier, IfElse, Logic,
ValueNode, WithPos,
}, },
Value, Value,
}; };
@ -99,19 +100,19 @@ mod tests {
.with_position((0, 0)), .with_position((0, 0)),
Statement::Loop(Loop::new(vec![Statement::IfElse(IfElse::new( Statement::Loop(Loop::new(vec![Statement::IfElse(IfElse::new(
Expression::Logic(Box::new(Logic::Greater( Expression::Logic(Box::new(Logic::Greater(
Expression::Identifier(Identifier::new("i")).with_position((10, 11)), Expression::Identifier(Identifier::new("i")).with_position((0, 0)),
Expression::Value(ValueNode::Integer(2)).with_position((14, 15)), Expression::Value(ValueNode::Integer(2)).with_position((0, 0)),
))) )))
.with_position((10, 15)), .with_position((0, 0)),
Block::new(vec![Statement::Break.with_position((18, 24))]), Block::new(vec![Statement::Break.with_position((0, 0))]),
Some(Block::new(vec![Statement::Assignment(Assignment::new( Some(Block::new(vec![Statement::Assignment(Assignment::new(
Identifier::new("i").with_position((0, 0)), Identifier::new("i").with_position((0, 0)),
None, None,
AssignmentOperator::AddAssign, AssignmentOperator::AddAssign,
Statement::Expression(Expression::Value(ValueNode::Integer(1))) Statement::Expression(Expression::Value(ValueNode::Integer(1)))
.with_position((38, 39)), .with_position((0, 0)),
)) ))
.with_position((33, 39))])), .with_position((0, 0))])),
)) ))
.with_position((0, 0))])) .with_position((0, 0))]))
.with_position((0, 0)), .with_position((0, 0)),

View File

@ -52,6 +52,17 @@ pub struct WithPosition<T> {
pub position: SourcePosition, pub position: SourcePosition,
} }
pub trait WithPos: Sized {
fn with_position<T: Into<SourcePosition>>(self, span: T) -> WithPosition<Self> {
WithPosition {
node: self,
position: span.into(),
}
}
}
impl<T> WithPos for T {}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct SourcePosition(pub usize, pub usize); pub struct SourcePosition(pub usize, pub usize);
@ -164,11 +175,4 @@ pub trait AbstractNode: Sized {
fn expected_type(&self, context: &Context) -> Result<Type, ValidationError>; fn expected_type(&self, context: &Context) -> Result<Type, ValidationError>;
fn validate(&self, context: &Context) -> Result<(), ValidationError>; fn validate(&self, context: &Context) -> Result<(), ValidationError>;
fn run(self, context: &Context) -> Result<Action, RuntimeError>; fn run(self, context: &Context) -> Result<Action, RuntimeError>;
fn with_position<T: Into<SourcePosition>>(self, span: T) -> WithPosition<Self> {
WithPosition {
node: self,
position: span.into(),
}
}
} }

View File

@ -17,13 +17,13 @@ pub enum Type {
Boolean, Boolean,
Float, Float,
Function { Function {
parameter_types: Vec<Type>, parameter_types: Vec<WithPosition<Type>>,
return_type: Box<Type>, return_type: Box<WithPosition<Type>>,
}, },
Integer, Integer,
List, List,
ListOf(Box<Type>), ListOf(Box<WithPosition<Type>>),
ListExact(Vec<Type>), ListExact(Vec<WithPosition<Type>>),
Map, Map,
None, None,
Range, Range,
@ -52,27 +52,27 @@ impl Type {
| (Type::Range, Type::Range) | (Type::Range, Type::Range)
| (Type::String, Type::String) => return 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.node.check(&right.node) {
return Ok(()); return Ok(());
} }
} }
(Type::ListOf(list_of), Type::ListExact(list_exact)) => { (Type::ListOf(list_of), Type::ListExact(list_exact)) => {
for r#type in list_exact { for r#type in list_exact {
list_of.check(r#type)?; list_of.node.check(&r#type.node)?;
} }
return 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.node.check(&list_of.node)?;
} }
return 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.node.check(&right.node)?;
} }
return Ok(()); return Ok(());
@ -149,15 +149,15 @@ impl Display for Type {
Type::Float => write!(f, "float"), Type::Float => write!(f, "float"),
Type::Integer => write!(f, "int"), Type::Integer => write!(f, "int"),
Type::List => write!(f, "list"), Type::List => write!(f, "list"),
Type::ListOf(item_type) => write!(f, "list({item_type})"), Type::ListOf(item_type) => write!(f, "list({})", item_type.node),
Type::ListExact(item_types) => { Type::ListExact(item_types) => {
write!(f, "[")?; write!(f, "[")?;
for (index, item_type) in item_types.into_iter().enumerate() { for (index, item_type) in item_types.into_iter().enumerate() {
if index == item_types.len() - 1 { if index == item_types.len() - 1 {
write!(f, "{item_type}")?; write!(f, "{}", item_type.node)?;
} else { } else {
write!(f, "{item_type}, ")?; write!(f, "{}, ", item_type.node)?;
} }
} }
@ -174,10 +174,10 @@ impl Display for Type {
write!(f, "(")?; write!(f, "(")?;
for r#type in parameter_types { for r#type in parameter_types {
write!(f, "{} ", r#type)?; write!(f, "{} ", r#type.node)?;
} }
write!(f, ") : {return_type}") write!(f, ") : {}", return_type.node)
} }
Type::Structure { name, .. } => write!(f, "{name}"), Type::Structure { name, .. } => write!(f, "{name}"),
Type::Argument(_) => todo!(), Type::Argument(_) => todo!(),
@ -187,6 +187,8 @@ impl Display for Type {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::abstract_tree::WithPos;
use super::*; use super::*;
#[test] #[test]
@ -197,12 +199,14 @@ mod tests {
assert_eq!(Type::Integer.check(&Type::Integer), Ok(())); assert_eq!(Type::Integer.check(&Type::Integer), Ok(()));
assert_eq!(Type::List.check(&Type::List), Ok(())); assert_eq!(Type::List.check(&Type::List), Ok(()));
assert_eq!( assert_eq!(
Type::ListOf(Box::new(Type::Integer)).check(&Type::ListOf(Box::new(Type::Integer))), Type::ListOf(Box::new(Type::Integer.with_position((0, 0))))
.check(&Type::ListOf(Box::new(Type::Integer.with_position((0, 0))))),
Ok(()) Ok(())
); );
assert_eq!( assert_eq!(
Type::ListExact(vec![Type::Float]).check(&Type::ListExact(vec![Type::Float])), Type::ListExact(vec![Type::Float.with_position((0, 0))])
.check(&Type::ListExact(vec![Type::Float.with_position((0, 0))])),
Ok(()) Ok(())
); );
assert_eq!(Type::Map.check(&Type::Map), Ok(())); assert_eq!(Type::Map.check(&Type::Map), Ok(()));
@ -237,8 +241,8 @@ mod tests {
Type::Float, Type::Float,
Type::Integer, Type::Integer,
Type::List, Type::List,
Type::ListOf(Box::new(Type::Boolean)), Type::ListOf(Box::new(Type::Boolean.with_position((0, 0)))),
Type::ListExact(vec![Type::Integer]), Type::ListExact(vec![Type::Integer.with_position((0, 0))]),
Type::Map, Type::Map,
Type::None, Type::None,
Type::Range, Type::Range,
@ -263,8 +267,11 @@ mod tests {
#[test] #[test]
fn check_list_types() { fn check_list_types() {
let list = Type::List; let list = Type::List;
let list_exact = Type::ListExact(vec![Type::Integer, Type::Integer]); let list_exact = Type::ListExact(vec![
let list_of = Type::ListOf(Box::new(Type::Integer)); Type::Integer.with_position((0, 0)),
Type::Integer.with_position((0, 0)),
]);
let list_of = Type::ListOf(Box::new(Type::Integer.with_position((0, 0))));
assert_eq!(list.check(&list_exact), Ok(())); assert_eq!(list.check(&list_exact), Ok(()));
assert_eq!(list.check(&list_of), Ok(())); assert_eq!(list.check(&list_of), Ok(()));

View File

@ -7,7 +7,7 @@ use crate::{
Value, Value,
}; };
use super::{AbstractNode, Action, Block, Expression, Identifier, Type, WithPosition}; use super::{AbstractNode, Action, Block, Expression, Identifier, Type, WithPos, WithPosition};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ValueNode { pub enum ValueNode {
@ -47,7 +47,12 @@ impl AbstractNode for ValueNode {
let mut item_types = Vec::with_capacity(items.len()); let mut item_types = Vec::with_capacity(items.len());
for expression in items { for expression in items {
item_types.push(expression.node.expected_type(context)?); item_types.push(
expression
.node
.expected_type(context)?
.with_position(expression.position),
);
} }
Type::ListExact(item_types) Type::ListExact(item_types)
@ -61,10 +66,10 @@ impl AbstractNode for ValueNode {
.. ..
} => Type::Function { } => Type::Function {
parameter_types: parameters parameter_types: parameters
.into_iter() .iter()
.map(|(_, r#type)| r#type.node.clone()) .map(|(_, r#type)| r#type.clone())
.collect(), .collect(),
return_type: Box::new(return_type.node.clone()), return_type: Box::new(return_type.clone()),
}, },
ValueNode::Structure { ValueNode::Structure {
name, name,
@ -185,7 +190,10 @@ impl AbstractNode for ValueNode {
for expression in expression_list { for expression in expression_list {
let action = expression.node.run(_context)?; let action = expression.node.run(_context)?;
let value = if let Action::Return(value) = action { let value = if let Action::Return(value) = action {
value WithPosition {
node: value,
position: expression.position,
}
} else { } else {
return Err(RuntimeError::ValidationFailure( return Err(RuntimeError::ValidationFailure(
ValidationError::InterpreterExpectedReturn(expression.position), ValidationError::InterpreterExpectedReturn(expression.position),

View File

@ -74,7 +74,7 @@ impl AbstractNode for While {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::abstract_tree::{ use crate::abstract_tree::{
Assignment, AssignmentOperator, Block, Identifier, Logic, ValueNode, Assignment, AssignmentOperator, Block, Identifier, Logic, ValueNode, WithPos,
}; };
use super::*; use super::*;

View File

@ -6,7 +6,7 @@ use std::{
}; };
use crate::{ use crate::{
abstract_tree::{Action, Type}, abstract_tree::{Action, Type, WithPos},
context::Context, context::Context,
error::RuntimeError, error::RuntimeError,
value::ValueInner, value::ValueInner,
@ -36,8 +36,8 @@ impl BuiltInFunction {
pub fn r#type(&self) -> Type { pub fn r#type(&self) -> Type {
match self { match self {
BuiltInFunction::WriteLine => Type::Function { BuiltInFunction::WriteLine => Type::Function {
parameter_types: vec![Type::String], parameter_types: vec![Type::String.with_position((0, 0))],
return_type: Box::new(Type::None), return_type: Box::new(Type::None.with_position((0, 0))),
}, },
_ => { _ => {
todo!() todo!()

View File

@ -121,8 +121,8 @@ pub fn parser<'src>() -> impl Parser<
} }
}), }),
)) ))
}) .map_with(|r#type, state| r#type.with_position(state.span()))
.map_with(|r#type, state| r#type.with_position(state.span())); });
let type_argument = identifier let type_argument = identifier
.clone() .clone()
@ -759,7 +759,10 @@ mod tests {
parse(&lex("foobar : list(bool) = [true]").unwrap()).unwrap()[0].node, parse(&lex("foobar : list(bool) = [true]").unwrap()).unwrap()[0].node,
Statement::Assignment(Assignment::new( Statement::Assignment(Assignment::new(
Identifier::new("foobar").with_position((0, 6)), Identifier::new("foobar").with_position((0, 6)),
Some(Type::ListOf(Box::new(Type::Boolean)).with_position((9, 19))), Some(
Type::ListOf(Box::new(Type::Boolean.with_position((14, 18))))
.with_position((9, 19))
),
AssignmentOperator::Assign, AssignmentOperator::Assign,
Statement::Expression(Expression::Value(ValueNode::List(vec![Expression::Value( Statement::Expression(Expression::Value(ValueNode::List(vec![Expression::Value(
ValueNode::Boolean(true) ValueNode::Boolean(true)
@ -776,7 +779,13 @@ mod tests {
parse(&lex("foobar : [bool, str] = [true, '42']").unwrap()).unwrap()[0], parse(&lex("foobar : [bool, str] = [true, '42']").unwrap()).unwrap()[0],
Statement::Assignment(Assignment::new( Statement::Assignment(Assignment::new(
Identifier::new("foobar").with_position((0, 6)), Identifier::new("foobar").with_position((0, 6)),
Some(Type::ListExact(vec![Type::Boolean, Type::String]).with_position((9, 20))), Some(
Type::ListExact(vec![
Type::Boolean.with_position((10, 14)),
Type::String.with_position((16, 19))
])
.with_position((9, 20))
),
AssignmentOperator::Assign, AssignmentOperator::Assign,
Statement::Expression(Expression::Value(ValueNode::List(vec![ Statement::Expression(Expression::Value(ValueNode::List(vec![
Expression::Value(ValueNode::Boolean(true)).with_position((24, 28)), Expression::Value(ValueNode::Boolean(true)).with_position((24, 28)),
@ -797,7 +806,7 @@ mod tests {
Some( Some(
Type::Function { Type::Function {
parameter_types: vec![], parameter_types: vec![],
return_type: Box::new(Type::Any) return_type: Box::new(Type::Any.with_position((17, 20)))
} }
.with_position((9, 20)) .with_position((9, 20))
), ),

View File

@ -13,7 +13,7 @@ use stanza::{
}; };
use crate::{ use crate::{
abstract_tree::{AbstractNode, Action, Block, Identifier, Type, WithPosition}, abstract_tree::{AbstractNode, Action, Block, Identifier, Type, WithPos, WithPosition},
built_in_functions::BuiltInFunction, built_in_functions::BuiltInFunction,
context::Context, context::Context,
error::{RuntimeError, ValidationError}, error::{RuntimeError, ValidationError},
@ -39,7 +39,7 @@ impl Value {
Value(Arc::new(ValueInner::Integer(integer))) Value(Arc::new(ValueInner::Integer(integer)))
} }
pub fn list(list: Vec<Value>) -> Self { pub fn list(list: Vec<WithPosition<Value>>) -> Self {
Value(Arc::new(ValueInner::List(list))) Value(Arc::new(ValueInner::List(list)))
} }
@ -63,7 +63,7 @@ impl Value {
) -> Self { ) -> Self {
Value(Arc::new(ValueInner::Function(Function::Parsed( Value(Arc::new(ValueInner::Function(Function::Parsed(
ParsedFunction { ParsedFunction {
type_arguments, type_parameters: type_arguments,
parameters, parameters,
return_type, return_type,
body, body,
@ -91,7 +91,7 @@ impl Value {
} }
} }
pub fn as_list(&self) -> Option<&Vec<Value>> { pub fn as_list(&self) -> Option<&Vec<WithPosition<Value>>> {
if let ValueInner::List(list) = self.inner().as_ref() { if let ValueInner::List(list) = self.inner().as_ref() {
Some(list) Some(list)
} else { } else {
@ -122,7 +122,7 @@ impl Display for Value {
let mut table = create_table(); let mut table = create_table();
for value in list { for value in list {
table = table.with_row([value.to_string()]); table = table.with_row([value.node.to_string()]);
} }
write!(f, "{}", Console::default().render(&table)) write!(f, "{}", Console::default().render(&table))
@ -139,7 +139,7 @@ impl Display for Value {
ValueInner::Range(_) => todo!(), ValueInner::Range(_) => todo!(),
ValueInner::String(string) => write!(f, "{string}"), ValueInner::String(string) => write!(f, "{string}"),
ValueInner::Function(Function::Parsed(ParsedFunction { ValueInner::Function(Function::Parsed(ParsedFunction {
type_arguments, type_parameters: type_arguments,
parameters, parameters,
return_type, return_type,
body, body,
@ -202,7 +202,7 @@ pub enum ValueInner {
Float(f64), Float(f64),
Function(Function), Function(Function),
Integer(i64), Integer(i64),
List(Vec<Value>), List(Vec<WithPosition<Value>>),
Map(BTreeMap<Identifier, Value>), Map(BTreeMap<Identifier, Value>),
Range(Range<i64>), Range(Range<i64>),
String(String), String(String),
@ -222,7 +222,7 @@ impl ValueInner {
let mut types = Vec::with_capacity(values.len()); let mut types = Vec::with_capacity(values.len());
for value in values { for value in values {
types.push(value.r#type(context)?); types.push(value.node.r#type(context)?.with_position(value.position));
} }
Type::ListExact(types) Type::ListExact(types)
@ -235,9 +235,9 @@ impl ValueInner {
parameter_types: parsed_function parameter_types: parsed_function
.parameters .parameters
.iter() .iter()
.map(|(_, r#type)| r#type.node.clone()) .map(|(_, r#type)| r#type.clone())
.collect(), .collect(),
return_type: Box::new(parsed_function.return_type.node.clone()), return_type: Box::new(parsed_function.return_type.clone()),
}, },
Function::BuiltIn(built_in_function) => { Function::BuiltIn(built_in_function) => {
built_in_function.clone().as_value().r#type(context)? built_in_function.clone().as_value().r#type(context)?
@ -343,8 +343,8 @@ impl Function {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct ParsedFunction { pub struct ParsedFunction {
type_arguments: Vec<WithPosition<Type>>, pub type_parameters: Vec<WithPosition<Type>>,
parameters: Vec<(Identifier, WithPosition<Type>)>, pub parameters: Vec<(Identifier, WithPosition<Type>)>,
return_type: WithPosition<Type>, pub return_type: WithPosition<Type>,
body: WithPosition<Block>, pub body: WithPosition<Block>,
} }

View File

@ -1,7 +1,7 @@
use std::{collections::BTreeMap, rc::Rc}; use std::{collections::BTreeMap, rc::Rc};
use dust_lang::{ use dust_lang::{
abstract_tree::{Identifier, Type}, abstract_tree::{Identifier, Type, WithPos},
error::{Error, TypeConflict, ValidationError}, error::{Error, TypeConflict, ValidationError},
*, *,
}; };
@ -100,9 +100,9 @@ fn list() {
assert_eq!( assert_eq!(
interpret(Rc::new("test".to_string()), "[1, 2, 'foobar']"), interpret(Rc::new("test".to_string()), "[1, 2, 'foobar']"),
Ok(Some(Value::list(vec![ Ok(Some(Value::list(vec![
Value::integer(1), Value::integer(1).with_position((1, 2)),
Value::integer(2), Value::integer(2).with_position((4, 5)),
Value::string("foobar".to_string()), Value::string("foobar".to_string()).with_position((7, 15)),
]))) ])))
); );
} }

View File

@ -1,7 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use dust_lang::{ use dust_lang::{
abstract_tree::{AbstractNode, Block, Expression, Identifier, Statement, Type}, abstract_tree::{Block, Expression, Identifier, Statement, Type, WithPos},
error::{Error, TypeConflict, ValidationError}, error::{Error, TypeConflict, ValidationError},
*, *,
}; };