diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index e19d641..6729a87 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -323,7 +323,7 @@ impl<'a> Analyzer<'a> { ))) = literal_type { if integer >= length as i64 { - self.errors.push(AnalysisError::IndexOutOfBounds { + self.errors.push(AnalysisError::ListIndexOutOfBounds { index: index.clone(), length, list: list.clone(), @@ -358,7 +358,7 @@ impl<'a> Analyzer<'a> { ))) = literal_type { if integer >= length as i64 { - self.errors.push(AnalysisError::IndexOutOfBounds { + self.errors.push(AnalysisError::ListIndexOutOfBounds { index: index.clone(), length, list: list.clone(), @@ -634,7 +634,42 @@ impl<'a> Analyzer<'a> { } }, Expression::TupleAccess(tuple_access) => { - let TupleAccessExpression { tuple, .. } = tuple_access.inner.as_ref(); + let TupleAccessExpression { tuple, index } = tuple_access.inner.as_ref(); + + let tuple_type = match tuple.return_type(&self.context) { + Ok(Some(tuple_type)) => tuple_type, + Ok(None) => { + self.errors + .push(AnalysisError::ExpectedValueFromExpression { + expression: tuple.clone(), + }); + return; + } + Err(ast_error) => { + self.errors.push(AnalysisError::AstError(ast_error)); + return; + } + }; + + if let Type::Tuple { + fields: Some(fields), + } = tuple_type + { + if index.inner >= fields.len() { + self.errors.push(AnalysisError::TupleIndexOutOfBounds { + index: expression.clone(), + tuple: tuple.clone(), + index_value: index.inner as i64, + length: fields.len(), + }); + } + } else { + self.errors.push(AnalysisError::ExpectedType { + expected: Type::Tuple { fields: None }, + actual: tuple_type, + actual_expression: tuple.clone(), + }); + } self.analyze_expression(tuple, statement_position); } @@ -720,12 +755,18 @@ pub enum AnalysisError { actual: usize, position: Span, }, - IndexOutOfBounds { + ListIndexOutOfBounds { list: Expression, index: Expression, index_value: i64, length: usize, }, + TupleIndexOutOfBounds { + tuple: Expression, + index: Expression, + index_value: i64, + length: usize, + }, NegativeIndex { list: Expression, index: Expression, @@ -775,7 +816,8 @@ impl AnalysisError { AnalysisError::ExpectedIdentifierOrString { actual } => actual.position(), AnalysisError::ExpectedValueFromExpression { expression, .. } => expression.position(), AnalysisError::ExpectedValueArgumentCount { position, .. } => *position, - AnalysisError::IndexOutOfBounds { index, .. } => index.position(), + AnalysisError::ListIndexOutOfBounds { index, .. } => index.position(), + AnalysisError::TupleIndexOutOfBounds { index, .. } => index.position(), AnalysisError::LetExpectedValueFromStatement { actual } => actual.position(), AnalysisError::NegativeIndex { index, .. } => index.position(), AnalysisError::TypeConflict { @@ -840,7 +882,7 @@ impl Display for AnalysisError { AnalysisError::ExpectedValueArgumentCount { expected, actual, .. } => write!(f, "Expected {} value arguments, found {}", expected, actual), - AnalysisError::IndexOutOfBounds { + AnalysisError::ListIndexOutOfBounds { list, index_value, length, @@ -860,6 +902,16 @@ impl Display for AnalysisError { AnalysisError::NegativeIndex { list, index_value, .. } => write!(f, "Negative index {} for list {}", index_value, list), + AnalysisError::TupleIndexOutOfBounds { + tuple, + index_value, + length, + .. + } => write!( + f, + "Index {} out of bounds for tuple {} with length {}", + index_value, tuple, length + ), AnalysisError::TypeConflict { actual_expression: actual_statement, actual_type, @@ -871,6 +923,7 @@ impl Display for AnalysisError { expected, actual_statement, actual_type ) } + AnalysisError::UndefinedFieldIdentifier { identifier, container, @@ -899,6 +952,8 @@ impl Display for AnalysisError { #[cfg(test)] mod tests { + use std::collections::HashMap; + use crate::RangeableType; use super::*; @@ -994,7 +1049,7 @@ mod tests { assert_eq!( analyze(source), Err(DustError::Analysis { - analysis_errors: vec![AnalysisError::IndexOutOfBounds { + analysis_errors: vec![AnalysisError::ListIndexOutOfBounds { list: Expression::list( vec![ Expression::literal(1, (1, 2)), @@ -1062,12 +1117,26 @@ mod tests { assert_eq!( analyze(source), - Err(DustError::Analysis { - analysis_errors: vec![AnalysisError::ExpectedIdentifierOrString { - actual: Expression::literal(0, (10, 11)) + Err(DustError::analysis( + [AnalysisError::ExpectedType { + expected: Type::Tuple { fields: None }, + actual: Type::Struct(StructType::Fields { + name: Identifier::new("Foo"), + fields: HashMap::from([(Identifier::new("x"), Type::Integer)]) + }), + actual_expression: Expression::r#struct( + StructExpression::Fields { + name: Node::new(Identifier::new("Foo"), (22, 25)), + fields: vec![( + Node::new(Identifier::new("x"), (28, 29)), + Expression::literal(1, (31, 32)) + )], + }, + (22, 35) + ), }], - source, - }) + source + )) ); } diff --git a/dust-lang/src/ast/expression.rs b/dust-lang/src/ast/expression.rs index be8946f..f398a6c 100644 --- a/dust-lang/src/ast/expression.rs +++ b/dust-lang/src/ast/expression.rs @@ -476,7 +476,10 @@ impl Expression { position: tuple.position(), })?; - if let Type::Tuple(fields) = tuple_value { + if let Type::Tuple { + fields: Some(fields), + } = tuple_value + { fields.get(index.inner).cloned() } else { Err(AstError::ExpectedTupleType { diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs index bbcbd23..bb178a3 100644 --- a/dust-lang/src/dust_error.rs +++ b/dust-lang/src/dust_error.rs @@ -34,9 +34,9 @@ impl<'src> DustError<'src> { } } - pub fn analysis(analysis_errors: Vec, source: &'src str) -> Self { + pub fn analysis>>(analysis_errors: T, source: &'src str) -> Self { DustError::Analysis { - analysis_errors, + analysis_errors: analysis_errors.into(), source, } } diff --git a/dust-lang/src/type.rs b/dust-lang/src/type.rs index 13726c3..87f22d6 100644 --- a/dust-lang/src/type.rs +++ b/dust-lang/src/type.rs @@ -55,7 +55,9 @@ pub enum Type { length: Option, }, Struct(StructType), - Tuple(Vec), + Tuple { + fields: Option>, + }, } impl Type { @@ -295,18 +297,22 @@ impl Display for Type { Type::Range { r#type } => write!(f, "{type} range"), Type::String { .. } => write!(f, "str"), Type::Struct(struct_type) => write!(f, "{struct_type}"), - Type::Tuple(fields) => { - write!(f, "(")?; + Type::Tuple { fields } => { + if let Some(fields) = fields { + write!(f, "(")?; - for (index, r#type) in fields.iter().enumerate() { - write!(f, "{type}")?; + for (index, r#type) in fields.iter().enumerate() { + write!(f, "{type}")?; - if index != fields.len() - 1 { - write!(f, ", ")?; + if index != fields.len() - 1 { + write!(f, ", ")?; + } } - } - write!(f, ")") + write!(f, ")") + } else { + write!(f, "tuple") + } } } } @@ -385,8 +391,9 @@ impl Ord for Type { left_struct.cmp(right_struct) } (Type::Struct(_), _) => Ordering::Greater, - (Type::Tuple(left_tuple), Type::Tuple(right_tuple)) => left_tuple.cmp(right_tuple), - (Type::Tuple(_), _) => Ordering::Greater, + + (Type::Tuple { fields: left }, Type::Tuple { fields: right }) => left.cmp(right), + (Type::Tuple { .. }, _) => Ordering::Greater, } } } diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index a153d1a..bc016ef 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -939,7 +939,9 @@ impl ValueData { ValueData::Tuple(values) => { let fields = values.iter().map(|value| value.r#type()).collect(); - Type::Tuple(fields) + Type::Tuple { + fields: Some(fields), + } } } }