diff --git a/crates/ast/src/const_eval/CLAUDE.md b/crates/ast/src/const_eval/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/crates/ast/src/const_eval/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/crates/ast/src/const_eval/evaluate.rs b/crates/ast/src/const_eval/evaluate.rs
index 109b452b6c8..d65ec32c2bf 100644
--- a/crates/ast/src/const_eval/evaluate.rs
+++ b/crates/ast/src/const_eval/evaluate.rs
@@ -407,6 +407,21 @@ pub fn evaluate_binary(
_ => {}
}
+ // Handle array concatenation for Add operation.
+ if matches!(op, BinaryOperation::Add)
+ && let (
+ ValueVariants::Svm(SvmValueParam::Plaintext(Plaintext::Array(x, _))),
+ ValueVariants::Svm(SvmValueParam::Plaintext(Plaintext::Array(y, _))),
+ ) = (&lhs.contents, &rhs.contents)
+ {
+ let mut elements = x.clone();
+ elements.extend(y.clone());
+ return Ok(Value::from(ValueVariants::Svm(SvmValueParam::Plaintext(Plaintext::Array(
+ elements,
+ Default::default(),
+ )))));
+ }
+
let ValueVariants::Svm(SvmValueParam::Plaintext(Plaintext::Literal(lhs, ..))) = &lhs.contents else {
halt2!(span, "Type error");
};
diff --git a/crates/ast/src/const_eval/value.rs b/crates/ast/src/const_eval/value.rs
index be3d9a067fd..9017ceea96f 100644
--- a/crates/ast/src/const_eval/value.rs
+++ b/crates/ast/src/const_eval/value.rs
@@ -610,6 +610,25 @@ impl Value {
Some(array[i].clone().into())
}
+ pub fn array_slice(&self, start: usize, end_exclusive: Option) -> Option {
+ let plaintext: &SvmPlaintext = self.try_as_ref()?;
+ let Plaintext::Array(array, ..) = plaintext else {
+ return None;
+ };
+
+ let range = match end_exclusive {
+ Some(end) if end <= start || end > array.len() => return None,
+ Some(end) => start..end,
+ None if start > array.len() => return None,
+ None => start..array.len(),
+ };
+
+ Some(Value::from(ValueVariants::Svm(SvmValue::Plaintext(SvmPlaintext::Array(
+ array[range].to_vec(),
+ Default::default(),
+ )))))
+ }
+
pub fn array_index_set(&mut self, i: usize, value: Self) -> Option<()> {
let plaintext_rhs: SvmPlaintext = value.try_into().ok()?;
diff --git a/crates/ast/src/expressions/CLAUDE.md b/crates/ast/src/expressions/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/crates/ast/src/expressions/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/crates/ast/src/expressions/mod.rs b/crates/ast/src/expressions/mod.rs
index f3b727268cb..4fd4c1604e3 100644
--- a/crates/ast/src/expressions/mod.rs
+++ b/crates/ast/src/expressions/mod.rs
@@ -71,6 +71,9 @@ pub use unit::*;
mod literal;
pub use literal::*;
+mod slice;
+pub use slice::*;
+
/// Expression that evaluates to a value.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Expression {
@@ -109,6 +112,8 @@ pub enum Expression {
TupleAccess(Box),
/// An unary expression.
Unary(Box),
+ /// An array slice expression e.g., `arr[2..5]`.
+ Slice(Box),
/// A unit expression e.g. `()`
Unit(UnitExpression),
}
@@ -139,6 +144,7 @@ impl Node for Expression {
Ternary(n) => n.span(),
Tuple(n) => n.span(),
TupleAccess(n) => n.span(),
+ Slice(n) => n.span(),
Unary(n) => n.span(),
Unit(n) => n.span(),
}
@@ -163,6 +169,7 @@ impl Node for Expression {
Ternary(n) => n.set_span(span),
Tuple(n) => n.set_span(span),
TupleAccess(n) => n.set_span(span),
+ Slice(n) => n.set_span(span),
Unary(n) => n.set_span(span),
Unit(n) => n.set_span(span),
}
@@ -187,6 +194,7 @@ impl Node for Expression {
Ternary(n) => n.id(),
Tuple(n) => n.id(),
TupleAccess(n) => n.id(),
+ Slice(n) => n.id(),
Unary(n) => n.id(),
Unit(n) => n.id(),
}
@@ -211,6 +219,7 @@ impl Node for Expression {
Ternary(n) => n.set_id(id),
Tuple(n) => n.set_id(id),
TupleAccess(n) => n.set_id(id),
+ Slice(n) => n.set_id(id),
Unary(n) => n.set_id(id),
Unit(n) => n.set_id(id),
}
@@ -237,6 +246,7 @@ impl fmt::Display for Expression {
Ternary(n) => n.fmt(f),
Tuple(n) => n.fmt(f),
TupleAccess(n) => n.fmt(f),
+ Slice(n) => n.fmt(f),
Unary(n) => n.fmt(f),
Unit(n) => n.fmt(f),
}
@@ -258,7 +268,9 @@ impl Expression {
Cast(_) => 12,
Ternary(_) => 0,
Array(_) | ArrayAccess(_) | Async(_) | Call(_) | Composite(_) | Err(_) | Intrinsic(_) | Path(_)
- | Literal(_) | MemberAccess(_) | Repeat(_) | Tuple(_) | TupleAccess(_) | Unary(_) | Unit(_) => 20,
+ | Literal(_) | MemberAccess(_) | Repeat(_) | Slice(_) | Tuple(_) | TupleAccess(_) | Unary(_) | Unit(_) => {
+ 20
+ }
}
}
@@ -341,6 +353,11 @@ impl Expression {
// Recurse
Expression::ArrayAccess(expr) => expr.array.is_pure(get_type) && expr.index.is_pure(get_type),
+ Expression::Slice(expr) => {
+ expr.array.is_pure(get_type)
+ && expr.start.as_ref().is_none_or(|e| e.is_pure(get_type))
+ && expr.end.as_ref().is_none_or(|(_, e)| e.is_pure(get_type))
+ }
Expression::MemberAccess(expr) => expr.inner.is_pure(get_type),
Expression::Repeat(expr) => expr.expr.is_pure(get_type) && expr.count.is_pure(get_type),
Expression::TupleAccess(expr) => expr.tuple.is_pure(get_type),
diff --git a/crates/ast/src/expressions/slice.rs b/crates/ast/src/expressions/slice.rs
new file mode 100644
index 00000000000..72b5ffa3dbb
--- /dev/null
+++ b/crates/ast/src/expressions/slice.rs
@@ -0,0 +1,60 @@
+// Copyright (C) 2019-2026 Provable Inc.
+// This file is part of the Leo library.
+
+// The Leo library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// The Leo library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with the Leo library. If not, see .
+
+use super::*;
+
+/// An array constructed from slicing another.
+///
+/// E.g., `arr[2..5]`, `arr[0..=3]`, `arr[1..]`, `arr[..4]`
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct SliceExpression {
+ /// The array being sliced.
+ pub array: Expression,
+ /// The optional starting index.
+ pub start: Option,
+ /// The optional ending index and whether it's inclusive.
+ pub end: Option<(bool, Expression)>,
+ /// The span.
+ pub span: Span,
+ /// The ID of the node.
+ pub id: NodeID,
+}
+
+impl fmt::Display for SliceExpression {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Format the start expression.
+ let start = match &self.start {
+ Some(expr) => expr.to_string(),
+ None => "".to_string(),
+ };
+ // Format the end expression.
+ let end = match &self.end {
+ Some((true, expr)) => format!("={expr}"),
+ Some((false, expr)) => expr.to_string(),
+ None => "".to_string(),
+ };
+
+ write!(f, "{}[{start}..{end}]", self.array)
+ }
+}
+
+impl From for Expression {
+ fn from(value: SliceExpression) -> Self {
+ Expression::Slice(Box::new(value))
+ }
+}
+
+crate::simple_node_impl!(SliceExpression);
diff --git a/crates/ast/src/passes/consumer.rs b/crates/ast/src/passes/consumer.rs
index 49f9fab651b..da0edb6e42d 100644
--- a/crates/ast/src/passes/consumer.rs
+++ b/crates/ast/src/passes/consumer.rs
@@ -40,12 +40,15 @@ pub trait ExpressionConsumer {
Expression::Ternary(ternary) => self.consume_ternary(*ternary),
Expression::Tuple(tuple) => self.consume_tuple(tuple),
Expression::TupleAccess(access) => self.consume_tuple_access(*access),
+ Expression::Slice(slice) => self.consume_slice(*slice),
Expression::Unary(unary) => self.consume_unary(*unary),
Expression::Unit(unit) => self.consume_unit(unit),
Expression::Intrinsic(intrinsic) => self.consume_intrinsic(*intrinsic),
}
}
+ fn consume_slice(&mut self, _input: SliceExpression) -> Self::Output;
+
fn consume_array_access(&mut self, _input: ArrayAccess) -> Self::Output;
fn consume_member_access(&mut self, _input: MemberAccess) -> Self::Output;
diff --git a/crates/ast/src/passes/reconstructor.rs b/crates/ast/src/passes/reconstructor.rs
index 173c64570d9..7952fd337e9 100644
--- a/crates/ast/src/passes/reconstructor.rs
+++ b/crates/ast/src/passes/reconstructor.rs
@@ -136,12 +136,30 @@ pub trait AstReconstructor {
Expression::Ternary(ternary) => self.reconstruct_ternary(*ternary, additional),
Expression::Tuple(tuple) => self.reconstruct_tuple(tuple, additional),
Expression::TupleAccess(access) => self.reconstruct_tuple_access(*access, additional),
+ Expression::Slice(slice) => self.reconstruct_slice(*slice, additional),
Expression::Unary(unary) => self.reconstruct_unary(*unary, additional),
Expression::Unit(unit) => self.reconstruct_unit(unit, additional),
Expression::Intrinsic(intr) => self.reconstruct_intrinsic(*intr, additional),
}
}
+ fn reconstruct_slice(
+ &mut self,
+ input: SliceExpression,
+ _additional: &Self::AdditionalInput,
+ ) -> (Expression, Self::AdditionalOutput) {
+ (
+ SliceExpression {
+ array: self.reconstruct_expression(input.array, &Default::default()).0,
+ start: input.start.map(|e| self.reconstruct_expression(e, &Default::default()).0),
+ end: input.end.map(|(inc, e)| (inc, self.reconstruct_expression(e, &Default::default()).0)),
+ ..input
+ }
+ .into(),
+ Default::default(),
+ )
+ }
+
fn reconstruct_array_access(
&mut self,
input: ArrayAccess,
diff --git a/crates/ast/src/passes/visitor.rs b/crates/ast/src/passes/visitor.rs
index 66c8660bf9c..f48ec00252f 100644
--- a/crates/ast/src/passes/visitor.rs
+++ b/crates/ast/src/passes/visitor.rs
@@ -122,12 +122,24 @@ pub trait AstVisitor {
Expression::Ternary(ternary) => self.visit_ternary(ternary, additional),
Expression::Tuple(tuple) => self.visit_tuple(tuple, additional),
Expression::TupleAccess(access) => self.visit_tuple_access(access, additional),
+ Expression::Slice(slice) => self.visit_slice(slice, additional),
Expression::Unary(unary) => self.visit_unary(unary, additional),
Expression::Unit(unit) => self.visit_unit(unit, additional),
Expression::Intrinsic(intr) => self.visit_intrinsic(intr, additional),
}
}
+ fn visit_slice(&mut self, input: &SliceExpression, _additional: &Self::AdditionalInput) -> Self::Output {
+ self.visit_expression(&input.array, &Default::default());
+ if let Some(start) = &input.start {
+ self.visit_expression(start, &Default::default());
+ }
+ if let Some((_, end)) = &input.end {
+ self.visit_expression(end, &Default::default());
+ }
+ Default::default()
+ }
+
fn visit_array_access(&mut self, input: &ArrayAccess, _additional: &Self::AdditionalInput) -> Self::Output {
self.visit_expression(&input.array, &Default::default());
self.visit_expression(&input.index, &Default::default());
diff --git a/crates/errors/src/errors/compiler/compiler_errors.rs b/crates/errors/src/errors/compiler/compiler_errors.rs
index 6ae4254a081..66b665f39fc 100644
--- a/crates/errors/src/errors/compiler/compiler_errors.rs
+++ b/crates/errors/src/errors/compiler/compiler_errors.rs
@@ -132,4 +132,11 @@ create_messages!(
msg: "This repeat count could not be determined at compile time.".to_string(),
help: None,
}
+
+ @formatted
+ slice_bounds_not_evaluated {
+ args: (),
+ msg: "The bounds of this slice expression could not be determined at compile time.".to_string(),
+ help: None,
+ }
);
diff --git a/crates/errors/src/errors/type_checker/type_checker_error.rs b/crates/errors/src/errors/type_checker/type_checker_error.rs
index 5772b66e457..6189f86e0f9 100644
--- a/crates/errors/src/errors/type_checker/type_checker_error.rs
+++ b/crates/errors/src/errors/type_checker/type_checker_error.rs
@@ -1424,4 +1424,25 @@ create_messages!(
msg: format!("`@no_inline` is not allowed on `final fn` functions because they must always be inlined."),
help: None,
}
+
+ @formatted
+ slice_range_invalid {
+ args: (start: impl Display, end: impl Display),
+ msg: format!("Slice range `{start}..{end}` is invalid: end must be greater than start."),
+ help: None,
+ }
+
+ @formatted
+ slice_out_of_bounds {
+ args: (start: impl Display, end: impl Display, len: impl Display),
+ msg: format!("Slice range `{start}..{end}` is out of bounds for an array of length `{len}`."),
+ help: None,
+ }
+
+ @formatted
+ array_concat_element_mismatch {
+ args: (left: impl Display, right: impl Display),
+ msg: format!("Cannot concatenate arrays with different element types `{left}` and `{right}`."),
+ help: None,
+ }
);
diff --git a/crates/fmt/src/CLAUDE.md b/crates/fmt/src/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/crates/fmt/src/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/crates/fmt/src/format.rs b/crates/fmt/src/format.rs
index ba9f2562293..9bee272a51d 100644
--- a/crates/fmt/src/format.rs
+++ b/crates/fmt/src/format.rs
@@ -75,7 +75,7 @@ pub fn format_node(node: &SyntaxNode, out: &mut Output) {
TERNARY_EXPR => format_ternary(node, out),
FIELD_EXPR => format_field_expr(node, out),
TUPLE_ACCESS_EXPR => format_tuple_access(node, out),
- INDEX_EXPR => format_index_expr(node, out),
+ INDEX_EXPR | SLICE_EXPR => format_index_expr(node, out),
CAST_EXPR => format_cast(node, out),
ARRAY_EXPR => format_array_expr(node, out),
REPEAT_EXPR => format_repeat_expr(node, out),
diff --git a/crates/fmt/tests/source/expr_slice.leo b/crates/fmt/tests/source/expr_slice.leo
new file mode 100644
index 00000000000..e6f746e9a8e
--- /dev/null
+++ b/crates/fmt/tests/source/expr_slice.leo
@@ -0,0 +1,11 @@
+program test.aleo
+ {
+ fn main( arr : [u32; 8] ){
+
+ let s1 = arr [ 2 .. 5 ] ;
+ let s2 = arr [ .. 4 ] ;
+ let s3 = arr [ 3 .. ] ;
+ let s4 = arr [ 1 ..= 6 ] ;
+
+ }
+}
diff --git a/crates/fmt/tests/target/expr_slice.leo b/crates/fmt/tests/target/expr_slice.leo
new file mode 100644
index 00000000000..9030b58ba28
--- /dev/null
+++ b/crates/fmt/tests/target/expr_slice.leo
@@ -0,0 +1,8 @@
+program test.aleo {
+ fn main(arr: [u32; 8]) {
+ let s1 = arr[2..5];
+ let s2 = arr[..4];
+ let s3 = arr[3..];
+ let s4 = arr[1..=6];
+ }
+}
diff --git a/crates/parser-rowan/src/parser/CLAUDE.md b/crates/parser-rowan/src/parser/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/crates/parser-rowan/src/parser/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/crates/parser-rowan/src/parser/expressions.rs b/crates/parser-rowan/src/parser/expressions.rs
index 43caac85d4c..626a37b9d6e 100644
--- a/crates/parser-rowan/src/parser/expressions.rs
+++ b/crates/parser-rowan/src/parser/expressions.rs
@@ -346,12 +346,36 @@ impl Parser<'_, '_> {
let m = lhs.precede(self);
self.bump_any(); // [
+ // Check for slice with no start: `arr[..end]` or `arr[..]`
+ if matches!(self.current(), DOT_DOT | DOT_DOT_EQ) {
+ self.bump_any(); // consume `..` or `..=`
+ // Parse optional end expression.
+ if !matches!(self.current(), R_BRACKET | EOF) {
+ self.parse_expr();
+ }
+ self.expect(R_BRACKET);
+ return Some(m.complete(self, SLICE_EXPR));
+ }
+
+ // Parse first expression (start index or regular index).
if self.parse_expr().is_none() {
self.error("expected index expression");
+ self.expect(R_BRACKET);
+ return Some(m.complete(self, INDEX_EXPR));
}
- self.expect(R_BRACKET);
+ // Check for slice with start: `arr[start..end]` or `arr[start..]`
+ if matches!(self.current(), DOT_DOT | DOT_DOT_EQ) {
+ self.bump_any(); // consume `..` or `..=`
+ // Parse optional end expression.
+ if !matches!(self.current(), R_BRACKET | EOF) {
+ self.parse_expr();
+ }
+ self.expect(R_BRACKET);
+ return Some(m.complete(self, SLICE_EXPR));
+ }
+ self.expect(R_BRACKET);
Some(m.complete(self, INDEX_EXPR))
}
diff --git a/crates/parser-rowan/src/syntax_kind.rs b/crates/parser-rowan/src/syntax_kind.rs
index d507081e282..d6fde273a1f 100644
--- a/crates/parser-rowan/src/syntax_kind.rs
+++ b/crates/parser-rowan/src/syntax_kind.rs
@@ -461,6 +461,8 @@ pub enum SyntaxKind {
FIELD_EXPR,
/// Array/tuple index: `a[0]`
INDEX_EXPR,
+ /// Array slice expression: `a[1..3]`, `a[..2]`, `a[1..]`, `a[..]`
+ SLICE_EXPR,
/// Cast expression: `a as u32`
CAST_EXPR,
/// Ternary expression: `a ? b : c`
@@ -680,6 +682,7 @@ impl SyntaxKind {
| FIELD_EXPR
| TUPLE_ACCESS_EXPR
| INDEX_EXPR
+ | SLICE_EXPR
| CAST_EXPR
| TERNARY_EXPR
| ARRAY_EXPR
@@ -1113,6 +1116,7 @@ const SYNTAX_KIND_TABLE: &[SyntaxKind] = &[
METHOD_CALL_EXPR,
FIELD_EXPR,
INDEX_EXPR,
+ SLICE_EXPR,
CAST_EXPR,
TERNARY_EXPR,
ARRAY_EXPR,
diff --git a/crates/parser/src/rowan.rs b/crates/parser/src/rowan.rs
index ce5b659c687..55fefcfc5b5 100644
--- a/crates/parser/src/rowan.rs
+++ b/crates/parser/src/rowan.rs
@@ -502,6 +502,7 @@ impl<'a> ConversionContext<'a> {
FIELD_EXPR => self.field_expr_to_expression(node)?,
TUPLE_ACCESS_EXPR => self.tuple_access_expr_to_expression(node)?,
INDEX_EXPR => self.index_expr_to_expression(node)?,
+ SLICE_EXPR => self.slice_expr_to_expression(node)?,
CAST_EXPR => self.cast_expr_to_expression(node)?,
TERNARY_EXPR => self.ternary_expr_to_expression(node)?,
ARRAY_EXPR => self.array_expr_to_expression(node)?,
@@ -966,6 +967,54 @@ impl<'a> ConversionContext<'a> {
Ok(leo_ast::ArrayAccess { array, index, span, id }.into())
}
+ /// Convert a SLICE_EXPR node to a SliceExpression.
+ ///
+ /// The CST structure is:
+ /// - `array_expr L_BRACKET (start_expr)? (DOT_DOT|DOT_DOT_EQ) (end_expr)? R_BRACKET`
+ fn slice_expr_to_expression(&self, node: &SyntaxNode) -> Result {
+ debug_assert_eq!(node.kind(), SLICE_EXPR);
+ let span = self.content_span(node);
+ let id = self.builder.next_id();
+
+ // Walk children to determine structure: array, optional start, range op, optional end.
+ let mut array_node: Option = None;
+ let mut start_node: Option = None;
+ let mut end_node: Option = None;
+ let mut inclusive = false;
+ let mut saw_range = false;
+
+ for child in node.children_with_tokens() {
+ match child {
+ SyntaxElement::Node(n) if n.kind().is_expression() => {
+ if array_node.is_none() {
+ array_node = Some(n);
+ } else if !saw_range {
+ start_node = Some(n);
+ } else {
+ end_node = Some(n);
+ }
+ }
+ SyntaxElement::Token(t) if matches!(t.kind(), DOT_DOT | DOT_DOT_EQ) => {
+ inclusive = t.kind() == DOT_DOT_EQ;
+ saw_range = true;
+ }
+ _ => {}
+ }
+ }
+
+ let array = match array_node {
+ Some(n) => self.to_expression(&n)?,
+ None => {
+ self.emit_unexpected_str("array in slice expression", node.text(), span);
+ return Ok(self.error_expression(span));
+ }
+ };
+ let start = start_node.map(|n| self.to_expression(&n)).transpose()?;
+ let end = end_node.map(|n| self.to_expression(&n)).transpose()?.map(|e| (inclusive, e));
+
+ Ok(leo_ast::SliceExpression { array, start, end, span, id }.into())
+ }
+
/// Convert a CAST_EXPR node to a CastExpression.
fn cast_expr_to_expression(&self, node: &SyntaxNode) -> Result {
debug_assert_eq!(node.kind(), CAST_EXPR);
diff --git a/crates/passes/src/code_generation/CLAUDE.md b/crates/passes/src/code_generation/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/crates/passes/src/code_generation/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/crates/passes/src/code_generation/expression.rs b/crates/passes/src/code_generation/expression.rs
index 02560bef6bc..b7dd4a96c2b 100644
--- a/crates/passes/src/code_generation/expression.rs
+++ b/crates/passes/src/code_generation/expression.rs
@@ -77,6 +77,9 @@ impl CodeGeneratingVisitor<'_> {
Expression::Cast(expr) => some_expr(self.visit_cast(expr)),
Expression::Composite(expr) => some_expr(self.visit_composite_init(expr)),
Expression::Repeat(expr) => some_expr(self.visit_repeat(expr)),
+ Expression::Slice(_) => {
+ unreachable!("Slice expressions must be eliminated by const propagation before code generation.")
+ }
Expression::Ternary(expr) => some_expr(self.visit_ternary(expr)),
Expression::Tuple(expr) => some_expr(self.visit_tuple(expr)),
Expression::Unary(expr) => some_expr(self.visit_unary(expr)),
@@ -188,6 +191,21 @@ impl CodeGeneratingVisitor<'_> {
}
fn visit_binary(&mut self, input: &BinaryExpression) -> (AleoExpr, Vec) {
+ // Array concatenation should be resolved during const propagation.
+ // If we reach code generation with an Add on arrays, something went wrong.
+ if matches!(input.op, BinaryOperation::Add) {
+ let left_type = self.state.type_table.get(&input.left.id());
+ let right_type = self.state.type_table.get(&input.right.id());
+ assert!(
+ !matches!(left_type, Some(Type::Array(_))),
+ "Array concatenation should be resolved before code generation"
+ );
+ assert!(
+ !matches!(right_type, Some(Type::Array(_))),
+ "Array concatenation should be resolved before code generation"
+ );
+ }
+
let (left, left_instructions) = self.visit_expression(&input.left);
let (right, right_instructions) = self.visit_expression(&input.right);
let left = left.expect("Trying to operate on an empty expression");
diff --git a/crates/passes/src/common/replacer/mod.rs b/crates/passes/src/common/replacer/mod.rs
index dd73bd1dc9b..6e63205559a 100644
--- a/crates/passes/src/common/replacer/mod.rs
+++ b/crates/passes/src/common/replacer/mod.rs
@@ -80,6 +80,7 @@ where
Expression::Literal(value) => self.reconstruct_literal(value, &()),
Expression::MemberAccess(access) => self.reconstruct_member_access(*access, &()),
Expression::Repeat(repeat) => self.reconstruct_repeat(*repeat, &()),
+ Expression::Slice(slice) => self.reconstruct_slice(*slice, &()),
Expression::Ternary(ternary) => self.reconstruct_ternary(*ternary, &()),
Expression::Tuple(tuple) => self.reconstruct_tuple(tuple, &()),
Expression::TupleAccess(access) => self.reconstruct_tuple_access(*access, &()),
diff --git a/crates/passes/src/common_subexpression_elimination/visitor.rs b/crates/passes/src/common_subexpression_elimination/visitor.rs
index 56aa439729a..0d1d5be7262 100644
--- a/crates/passes/src/common_subexpression_elimination/visitor.rs
+++ b/crates/passes/src/common_subexpression_elimination/visitor.rs
@@ -108,6 +108,7 @@ impl CommonSubexpressionEliminatingVisitor<'_> {
| Expression::Err(_)
| Expression::MemberAccess(_)
| Expression::Repeat(_)
+ | Expression::Slice(_)
| Expression::Composite(_)
| Expression::Ternary(_)
| Expression::Tuple(_)
@@ -217,6 +218,17 @@ impl CommonSubexpressionEliminatingVisitor<'_> {
return Some((expression, false));
}
+ Expression::Slice(slice_expression) => {
+ self.try_atom(&mut slice_expression.array)?;
+ if let Some(start) = &mut slice_expression.start {
+ self.try_atom(start)?;
+ }
+ if let Some((_, end)) = &mut slice_expression.end {
+ self.try_atom(end)?;
+ }
+ return Some((expression, false));
+ }
+
Expression::Tuple(tuple_expression) => {
// Tuple expressions only exist in return statements at this point in
// compilation, so we need only visit each member.
diff --git a/crates/passes/src/const_prop_unroll_and_morphing.rs b/crates/passes/src/const_prop_unroll_and_morphing.rs
index 80425a52693..38ecb603d9c 100644
--- a/crates/passes/src/const_prop_unroll_and_morphing.rs
+++ b/crates/passes/src/const_prop_unroll_and_morphing.rs
@@ -82,6 +82,10 @@ impl Pass for ConstPropUnrollAndMorphing {
return Err(CompilerError::repeat_count_not_evaluated(not_evaluated_span).into());
}
+ if let Some(not_evaluated_span) = const_prop_output.slice_bounds_not_evaluated {
+ return Err(CompilerError::slice_bounds_not_evaluated(not_evaluated_span).into());
+ }
+
if let Some(not_evaluated_span) = const_prop_output.array_length_not_evaluated {
return Err(CompilerError::array_length_not_evaluated(not_evaluated_span).into());
}
diff --git a/crates/passes/src/const_propagation/CLAUDE.md b/crates/passes/src/const_propagation/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/crates/passes/src/const_propagation/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/crates/passes/src/const_propagation/ast.rs b/crates/passes/src/const_propagation/ast.rs
index 86bba77b857..98c7b68482f 100644
--- a/crates/passes/src/const_propagation/ast.rs
+++ b/crates/passes/src/const_propagation/ast.rs
@@ -63,6 +63,7 @@ impl AstReconstructor for ConstPropagationVisitor<'_> {
Expression::Literal(value) => self.reconstruct_literal(value, &()),
Expression::MemberAccess(access) => self.reconstruct_member_access(*access, &()),
Expression::Repeat(repeat) => self.reconstruct_repeat(*repeat, &()),
+ Expression::Slice(slice) => self.reconstruct_slice(*slice, &()),
Expression::Ternary(ternary) => self.reconstruct_ternary(*ternary, &()),
Expression::Tuple(tuple) => self.reconstruct_tuple(tuple, &()),
Expression::TupleAccess(access) => self.reconstruct_tuple_access(*access, &()),
@@ -262,6 +263,77 @@ impl AstReconstructor for ConstPropagationVisitor<'_> {
}
}
+ fn reconstruct_slice(
+ &mut self,
+ input: leo_ast::SliceExpression,
+ _additional: &(),
+ ) -> (Expression, Self::AdditionalOutput) {
+ let span = input.span();
+ let id = input.id();
+ let array_id = input.array.id();
+ let (array, array_opt) = self.reconstruct_expression(input.array, &());
+ let start_expr = input.start.map(|e| self.reconstruct_expression(e, &()));
+ let end_expr = input.end.map(|(incl, e)| (incl, self.reconstruct_expression(e, &())));
+
+ let ty = self.state.type_table.get(&array_id);
+ let Some(Type::Array(arr_ty)) = &ty else {
+ panic!("Type checking guaranteed that this is an array.");
+ };
+
+ let start_val = match &start_expr {
+ Some((expr, _)) => expr.as_u32(),
+ None => Some(0),
+ };
+ let end_val = match &end_expr {
+ Some((inclusive, (expr, _))) => {
+ expr.as_u32().and_then(|v| if *inclusive { v.checked_add(1) } else { Some(v) })
+ }
+ None => arr_ty.length.as_u32(),
+ };
+
+ if let (Some(start), Some(end)) = (start_val, end_val)
+ && end >= start
+ {
+ // If the array value is known, extract the sub-array directly.
+ if let Some(array_value) = array_opt {
+ let slice_values: Vec<_> = (start..end)
+ .map(|i| array_value.array_index(i as usize).expect("Type checking verified bounds.").clone())
+ .collect();
+ let value = Value::make_array(slice_values.into_iter());
+ let expr = self.value_to_expression(&value, span, id).expect(VALUE_ERROR);
+ return (expr, Some(value));
+ }
+
+ // Otherwise expand arr[start..end] into [arr[start], ..., arr[end-1]].
+ let elem_type = arr_ty.element_type().clone();
+ let mut elements = Vec::with_capacity((end - start) as usize);
+ for i in start..end {
+ let index = Expression::Literal(Literal::integer(
+ IntegerType::U32,
+ i.to_string(),
+ span,
+ self.state.node_builder.next_id(),
+ ));
+ let access = ArrayAccess { array: array.clone(), index, span, id: self.state.node_builder.next_id() };
+ self.state.type_table.insert(access.id, elem_type.clone());
+ elements.push(Expression::ArrayAccess(Box::new(access)));
+ }
+
+ let array_expr = ArrayExpression { elements, span, id };
+ return (Expression::Array(array_expr), None);
+ }
+
+ self.slice_bounds_not_evaluated = Some(span);
+ let reconstructed = SliceExpression {
+ array,
+ start: start_expr.map(|(e, _)| e),
+ end: end_expr.map(|(incl, (e, _))| (incl, e)),
+ span,
+ id,
+ };
+ (reconstructed.into(), None)
+ }
+
fn reconstruct_tuple_access(
&mut self,
input: TupleAccess,
@@ -309,21 +381,78 @@ impl AstReconstructor for ConstPropagationVisitor<'_> {
let (left, lhs_opt_value) = self.reconstruct_expression(input.left, &());
let (right, rhs_opt_value) = self.reconstruct_expression(input.right, &());
- if let (Some(lhs_value), Some(rhs_value)) = (lhs_opt_value, rhs_opt_value) {
+ if let (Some(lhs_value), Some(rhs_value)) = (&lhs_opt_value, &rhs_opt_value) {
// We were able to evaluate both operands, so we can evaluate this expression.
match const_eval::evaluate_binary(
span,
input.op,
- &lhs_value,
- &rhs_value,
+ lhs_value,
+ rhs_value,
&self.state.type_table.get(&input_id),
) {
Ok(new_value) => {
let new_expr = self.value_to_expression(&new_value, span, input_id).expect(VALUE_ERROR);
return (new_expr, Some(new_value));
}
- Err(err) => self
- .emit_err(StaticAnalyzerError::compile_time_binary_op(lhs_value, rhs_value, input.op, err, span)),
+ Err(err) => self.emit_err(StaticAnalyzerError::compile_time_binary_op(
+ lhs_value.clone(),
+ rhs_value.clone(),
+ input.op,
+ err,
+ span,
+ )),
+ }
+ }
+
+ // Handle array concatenation when values aren't known but types are arrays.
+ if matches!(input.op, BinaryOperation::Add) {
+ let left_type = self.state.type_table.get(&left.id());
+ let right_type = self.state.type_table.get(&right.id());
+
+ if let (Some(Type::Array(left_arr)), Some(Type::Array(right_arr))) = (left_type, right_type) {
+ let left_len = match &left {
+ Expression::Array(arr) => Some(arr.elements.len() as u32),
+ _ => left_arr.length.as_u32(),
+ };
+ let right_len = match &right {
+ Expression::Array(arr) => Some(arr.elements.len() as u32),
+ _ => right_arr.length.as_u32(),
+ };
+
+ if let (Some(left_len), Some(right_len)) = (left_len, right_len) {
+ let mut elements = Vec::with_capacity((left_len + right_len) as usize);
+
+ let mut add_elements = |arr: &Expression, arr_type: &ArrayType, len: u32| {
+ if let Expression::Array(array_expr) = arr {
+ for elem in &array_expr.elements {
+ elements.push(elem.clone());
+ }
+ } else {
+ for i in 0..len {
+ let index = Expression::Literal(Literal::integer(
+ IntegerType::U32,
+ i.to_string(),
+ span,
+ self.state.node_builder.next_id(),
+ ));
+ let access = ArrayAccess {
+ array: arr.clone(),
+ index,
+ span,
+ id: self.state.node_builder.next_id(),
+ };
+ self.state.type_table.insert(access.id, arr_type.element_type().clone());
+ elements.push(Expression::ArrayAccess(Box::new(access)));
+ }
+ }
+ };
+
+ add_elements(&left, &left_arr, left_len);
+ add_elements(&right, &right_arr, right_len);
+
+ let array_expr = ArrayExpression { elements, span, id: input_id };
+ return (Expression::Array(array_expr), None);
+ }
}
}
diff --git a/crates/passes/src/const_propagation/mod.rs b/crates/passes/src/const_propagation/mod.rs
index 53802d02c34..ac929c5c59b 100644
--- a/crates/passes/src/const_propagation/mod.rs
+++ b/crates/passes/src/const_propagation/mod.rs
@@ -38,6 +38,8 @@ pub struct ConstPropagationOutput {
pub array_length_not_evaluated: Option,
/// A repeat expression count which was not able to be evaluated.
pub repeat_count_not_evaluated: Option,
+ /// A slice bound which was not able to be evaluated.
+ pub slice_bounds_not_evaluated: Option,
}
/// A pass to perform const propagation and folding.
@@ -74,6 +76,7 @@ impl Pass for ConstPropagation {
array_index_not_evaluated: None,
array_length_not_evaluated: None,
repeat_count_not_evaluated: None,
+ slice_bounds_not_evaluated: None,
};
ast.ast = visitor.reconstruct_program(ast.ast);
visitor.state.handler.last_err()?;
@@ -84,6 +87,7 @@ impl Pass for ConstPropagation {
array_index_not_evaluated: visitor.array_index_not_evaluated,
array_length_not_evaluated: visitor.array_length_not_evaluated,
repeat_count_not_evaluated: visitor.repeat_count_not_evaluated,
+ slice_bounds_not_evaluated: visitor.slice_bounds_not_evaluated,
})
}
}
@@ -99,6 +103,7 @@ impl<'a> ConstPropagationVisitor<'a> {
array_index_not_evaluated: None,
array_length_not_evaluated: None,
repeat_count_not_evaluated: None,
+ slice_bounds_not_evaluated: None,
}
}
}
diff --git a/crates/passes/src/const_propagation/visitor.rs b/crates/passes/src/const_propagation/visitor.rs
index e2496a7dad0..5ab83ddd4cb 100644
--- a/crates/passes/src/const_propagation/visitor.rs
+++ b/crates/passes/src/const_propagation/visitor.rs
@@ -36,6 +36,8 @@ pub struct ConstPropagationVisitor<'a> {
pub array_length_not_evaluated: Option,
/// A repeat expression count which was not able to be evaluated.
pub repeat_count_not_evaluated: Option,
+ /// A slice bound which was not able to be evaluated.
+ pub slice_bounds_not_evaluated: Option,
}
impl ConstPropagationVisitor<'_> {
diff --git a/crates/passes/src/monomorphization/ast.rs b/crates/passes/src/monomorphization/ast.rs
index 63714a79aa1..ca4ec2f1c47 100644
--- a/crates/passes/src/monomorphization/ast.rs
+++ b/crates/passes/src/monomorphization/ast.rs
@@ -100,6 +100,7 @@ impl AstReconstructor for MonomorphizationVisitor<'_> {
Expression::Literal(value) => self.reconstruct_literal(value, &()),
Expression::MemberAccess(access) => self.reconstruct_member_access(*access, &()),
Expression::Repeat(repeat) => self.reconstruct_repeat(*repeat, &()),
+ Expression::Slice(slice) => self.reconstruct_slice(*slice, &()),
Expression::Ternary(ternary) => self.reconstruct_ternary(*ternary, &()),
Expression::Tuple(tuple) => self.reconstruct_tuple(tuple, &()),
Expression::TupleAccess(access) => self.reconstruct_tuple_access(*access, &()),
diff --git a/crates/passes/src/option_lowering/ast.rs b/crates/passes/src/option_lowering/ast.rs
index 8e221e0dc0b..d85a65a5d6c 100644
--- a/crates/passes/src/option_lowering/ast.rs
+++ b/crates/passes/src/option_lowering/ast.rs
@@ -99,6 +99,7 @@ impl leo_ast::AstReconstructor for OptionLoweringVisitor<'_> {
Expression::Literal(e) => self.reconstruct_literal(e, additional),
Expression::MemberAccess(e) => self.reconstruct_member_access(*e, additional),
Expression::Repeat(e) => self.reconstruct_repeat(*e, additional),
+ Expression::Slice(e) => self.reconstruct_slice(*e, additional),
Expression::Ternary(e) => self.reconstruct_ternary(*e, additional),
Expression::Tuple(e) => self.reconstruct_tuple(e, additional),
Expression::TupleAccess(e) => self.reconstruct_tuple_access(*e, additional),
diff --git a/crates/passes/src/static_analysis/future_checker.rs b/crates/passes/src/static_analysis/future_checker.rs
index 85aad2dfb6b..35398d39978 100644
--- a/crates/passes/src/static_analysis/future_checker.rs
+++ b/crates/passes/src/static_analysis/future_checker.rs
@@ -102,6 +102,7 @@ impl AstVisitor for FutureChecker<'_> {
Expression::Literal(literal) => self.visit_literal(literal, &Position::Misc),
Expression::MemberAccess(access) => self.visit_member_access(access, &Position::Misc),
Expression::Repeat(repeat) => self.visit_repeat(repeat, &Position::Misc),
+ Expression::Slice(slice) => self.visit_slice(slice, &Position::Misc),
Expression::Ternary(ternary) => self.visit_ternary(ternary, &Position::Misc),
Expression::Tuple(tuple) => self.visit_tuple(tuple, additional),
Expression::TupleAccess(access) => self.visit_tuple_access(access, &Position::Misc),
diff --git a/crates/passes/src/static_single_assignment/expression.rs b/crates/passes/src/static_single_assignment/expression.rs
index 36d580b41f3..cd3757d02d3 100644
--- a/crates/passes/src/static_single_assignment/expression.rs
+++ b/crates/passes/src/static_single_assignment/expression.rs
@@ -32,6 +32,7 @@ use leo_ast::{
MemberAccess,
Path,
RepeatExpression,
+ SliceExpression,
Statement,
TernaryExpression,
TupleAccess,
@@ -232,6 +233,13 @@ impl ExpressionConsumer for SsaFormingVisitor<'_> {
(RepeatExpression { expr, ..input }.into(), statements)
}
+ fn consume_slice(&mut self, input: SliceExpression) -> Self::Output {
+ let (array, statements) = self.consume_expression_and_define(input.array);
+
+ // By now, the start and end should be literals. So we just ignore them. There is no need to SSA them.
+ (SliceExpression { array, ..input }.into(), statements)
+ }
+
/// Consumes a ternary expression, accumulating any statements that are generated.
fn consume_ternary(&mut self, input: TernaryExpression) -> Self::Output {
// Reconstruct the condition of the ternary expression.
diff --git a/crates/passes/src/type_checking/CLAUDE.md b/crates/passes/src/type_checking/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/crates/passes/src/type_checking/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/crates/passes/src/type_checking/ast.rs b/crates/passes/src/type_checking/ast.rs
index 5f5284b1e12..9ede711daae 100644
--- a/crates/passes/src/type_checking/ast.rs
+++ b/crates/passes/src/type_checking/ast.rs
@@ -386,6 +386,7 @@ impl AstVisitor for TypeCheckingVisitor<'_> {
Expression::Literal(literal) => self.visit_literal(literal, additional),
Expression::MemberAccess(access) => self.visit_member_access_general(access, false, additional),
Expression::Repeat(repeat) => self.visit_repeat(repeat, additional),
+ Expression::Slice(slice) => self.visit_slice(slice, additional),
Expression::Ternary(ternary) => self.visit_ternary(ternary, additional),
Expression::Tuple(tuple) => self.visit_tuple(tuple, additional),
Expression::TupleAccess(access) => self.visit_tuple_access_general(access, false, additional),
@@ -506,6 +507,98 @@ impl AstVisitor for TypeCheckingVisitor<'_> {
type_
}
+ fn visit_slice(&mut self, input: &SliceExpression, additional: &Self::AdditionalInput) -> Self::Output {
+ // Type check the array expression.
+ let array_type = self.visit_expression(&input.array, &None);
+
+ // Make sure the array type is actually an array.
+ let element_type = match &array_type {
+ Type::Array(array_ty) => array_ty.element_type().clone(),
+ Type::Err => return Type::Err,
+ _ => {
+ self.emit_err(TypeCheckerError::type_should_be2(array_type.clone(), "an array", input.array.span()));
+ return Type::Err;
+ }
+ };
+
+ // Type check the start expression if present.
+ if let Some(start) = &input.start {
+ let ty = self.visit_expression_infer_default_u32(start);
+ self.assert_unsigned_type(&ty, start.span());
+ }
+
+ // Type check the end expression if present.
+ if let Some((_, end)) = &input.end {
+ let ty = self.visit_expression_infer_default_u32(end);
+ self.assert_unsigned_type(&ty, end.span());
+ }
+
+ // Get the start index (default 0). If start is present but not resolvable, defer.
+ let start = match &input.start {
+ Some(e) => match e.as_u32() {
+ Some(v) => v,
+ None => return Type::Err,
+ },
+ None => 0,
+ };
+
+ // Get the original array length if known.
+ let array_len = if let Type::Array(array_ty) = &array_type { array_ty.length.as_u32() } else { None };
+
+ // Compute the slice length.
+ let slice_len = match (&input.end, array_len) {
+ (Some((inclusive, end_expr)), _) => {
+ if let Some(raw_end) = end_expr.as_u32() {
+ let Some(end) = (if *inclusive { raw_end.checked_add(1) } else { Some(raw_end) }) else {
+ self.emit_err(TypeCheckerError::slice_range_invalid(raw_end, raw_end, input.span()));
+ return Type::Err;
+ };
+ if end <= start {
+ self.emit_err(TypeCheckerError::slice_range_invalid(start, raw_end, input.span()));
+ return Type::Err;
+ }
+ Some(end - start)
+ } else {
+ None
+ }
+ }
+ (None, Some(arr_len)) => {
+ // Slice to end of array.
+ if start >= arr_len {
+ self.emit_err(TypeCheckerError::slice_out_of_bounds(start, arr_len, arr_len, input.span()));
+ return Type::Err;
+ }
+ Some(arr_len - start)
+ }
+ (None, None) => None,
+ };
+
+ // If slice length is known, check bounds.
+ if let (Some(slice_len), Some(arr_len)) = (slice_len, array_len)
+ && start + slice_len > arr_len
+ {
+ self.emit_err(TypeCheckerError::slice_out_of_bounds(start, start + slice_len, arr_len, input.span()));
+ }
+
+ // Build the result type.
+ let result_type = if let Some(len) = slice_len {
+ let len_expr = Expression::Literal(Literal::integer(
+ IntegerType::U32,
+ len.to_string(),
+ input.span(),
+ self.state.node_builder.next_id(),
+ ));
+ Type::Array(ArrayType::new(element_type, len_expr))
+ } else {
+ // Bounds not yet resolvable (e.g. const variables not yet propagated).
+ // Return Type::Err silently; the fixed-point loop will retry after const prop.
+ return Type::Err;
+ };
+
+ self.maybe_assert_type(&result_type, additional, input.span());
+ result_type
+ }
+
fn visit_intrinsic(&mut self, input: &IntrinsicExpression, expected: &Self::AdditionalInput) -> Self::Output {
// Check core struct name and function.
let Some(intrinsic) = self.get_intrinsic(input) else {
@@ -674,21 +767,47 @@ impl AstVisitor for TypeCheckingVisitor<'_> {
result_t
}
BinaryOperation::Add => {
- let operand_expected = self.unwrap_optional_type(destination);
+ let unwrapped_dest = self.unwrap_optional_type(destination);
+
+ // For array concatenation, don't pass destination type as expected (operands have different type than result).
+ let is_array_dest = matches!(unwrapped_dest, Some(Type::Array(_)));
+ let operand_expected = if is_array_dest { None } else { unwrapped_dest.clone() };
- // The expected type for both `left` and `right` is the unwrapped type
let mut t1 = self.visit_expression(&input.left, &operand_expected);
let mut t2 = self.visit_expression(&input.right, &operand_expected);
- // Infer `Numeric` types if possible
+ // Handle array concatenation.
+ if let (Type::Array(arr1), Type::Array(arr2)) = (&t1, &t2) {
+ if arr1.element_type() != arr2.element_type() {
+ self.emit_err(TypeCheckerError::array_concat_element_mismatch(
+ arr1.element_type(),
+ arr2.element_type(),
+ input.span(),
+ ));
+ return Type::Err;
+ }
+ // Result type: [ElementType; len1 + len2]
+ let result_length = Expression::Binary(Box::new(BinaryExpression {
+ left: (*arr1.length).clone(),
+ op: BinaryOperation::Add,
+ right: (*arr2.length).clone(),
+ id: self.state.node_builder.next_id(),
+ span: Default::default(),
+ }));
+ let result_t = Type::Array(ArrayType::new(arr1.element_type().clone(), result_length));
+ self.maybe_assert_type(&result_t, destination, input.span());
+ return result_t;
+ }
+
+ // Infer `Numeric` types if possible.
infer_numeric_types(self, &mut t1, &mut t2);
- // Now sanity check everything
+ // Now sanity check everything.
let assert_add_type = |type_: &Type, span: Span| {
if !matches!(type_, Type::Err | Type::Field | Type::Group | Type::Scalar | Type::Integer(_)) {
self.emit_err(TypeCheckerError::type_should_be2(
type_,
- "a field, group, scalar, or integer",
+ "a field, group, scalar, integer, or array",
span,
));
}
diff --git a/tests/expectations/compiler/array/array_concat.out b/tests/expectations/compiler/array/array_concat.out
new file mode 100644
index 00000000000..a329f6cf5ba
--- /dev/null
+++ b/tests/expectations/compiler/array/array_concat.out
@@ -0,0 +1,19 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 2u32].private;
+ input r1 as [u32; 2u32].private;
+ cast r0[0u32] r0[1u32] r1[0u32] r1[1u32] into r2 as [u32; 4u32];
+ output r2 as [u32; 4u32].private;
+
+function flatten:
+ input r0 as [[boolean; 2u32]; 2u32].private;
+ cast r0[0u32][0u32] r0[0u32][1u32] r0[1u32][0u32] r0[1u32][1u32] into r1 as [boolean; 4u32];
+ assert.eq r1 r1;
+ output r1 as [boolean; 4u32].private;
+
+function concat_different_sizes:
+ input r0 as [u8; 3u32].private;
+ input r1 as [u8; 2u32].private;
+ cast r0[0u32] r0[1u32] r0[2u32] r1[0u32] r1[1u32] into r2 as [u8; 5u32];
+ output r2 as [u8; 5u32].private;
diff --git a/tests/expectations/compiler/array/array_concat_chained.out b/tests/expectations/compiler/array/array_concat_chained.out
new file mode 100644
index 00000000000..cc70e18c6a0
--- /dev/null
+++ b/tests/expectations/compiler/array/array_concat_chained.out
@@ -0,0 +1,8 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 2u32].private;
+ input r1 as [u32; 2u32].private;
+ input r2 as [u32; 2u32].private;
+ cast r0[0u32] r0[1u32] r1[0u32] r1[1u32] r2[0u32] r2[1u32] into r3 as [u32; 6u32];
+ output r3 as [u32; 6u32].private;
diff --git a/tests/expectations/compiler/array/array_concat_literal.out b/tests/expectations/compiler/array/array_concat_literal.out
new file mode 100644
index 00000000000..37350dd15d6
--- /dev/null
+++ b/tests/expectations/compiler/array/array_concat_literal.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 2u32].private;
+ cast 1u32 2u32 r0[0u32] r0[1u32] into r1 as [u32; 4u32];
+ output r1 as [u32; 4u32].private;
diff --git a/tests/expectations/compiler/array/array_concat_type_mismatch_fail.out b/tests/expectations/compiler/array/array_concat_type_mismatch_fail.out
new file mode 100644
index 00000000000..ab55d3b5a01
--- /dev/null
+++ b/tests/expectations/compiler/array/array_concat_type_mismatch_fail.out
@@ -0,0 +1,5 @@
+Error [ETYC0372178]: Cannot concatenate arrays with different element types `u32` and `u8`.
+ --> compiler-test:5:16
+ |
+ 5 | return a + b;
+ | ^^^^^
diff --git a/tests/expectations/compiler/array/array_concat_with_slice.out b/tests/expectations/compiler/array/array_concat_with_slice.out
new file mode 100644
index 00000000000..4cf6e6ce428
--- /dev/null
+++ b/tests/expectations/compiler/array/array_concat_with_slice.out
@@ -0,0 +1,7 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 4u32].private;
+ input r1 as [u32; 4u32].private;
+ cast r0[0u32] r0[1u32] r1[2u32] r1[3u32] into r2 as [u32; 4u32];
+ output r2 as [u32; 4u32].private;
diff --git a/tests/expectations/compiler/operations/add_type_fail.out b/tests/expectations/compiler/operations/add_type_fail.out
index df7bf309170..557555ab438 100644
--- a/tests/expectations/compiler/operations/add_type_fail.out
+++ b/tests/expectations/compiler/operations/add_type_fail.out
@@ -1,9 +1,9 @@
-Error [ETYC0372117]: Expected a field, group, scalar, or integer but type `bool` was found.
+Error [ETYC0372117]: Expected a field, group, scalar, integer, or array but type `bool` was found.
--> compiler-test:4:17
|
4 | return (true + false) as u8;
| ^^^^
-Error [ETYC0372117]: Expected a field, group, scalar, or integer but type `bool` was found.
+Error [ETYC0372117]: Expected a field, group, scalar, integer, or array but type `bool` was found.
--> compiler-test:4:24
|
4 | return (true + false) as u8;
diff --git a/tests/expectations/compiler/slice/slice_basic.out b/tests/expectations/compiler/slice/slice_basic.out
new file mode 100644
index 00000000000..edb58815c87
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_basic.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 8u32].private;
+ cast r0[2u32] r0[3u32] r0[4u32] into r1 as [u32; 3u32];
+ output r1 as [u32; 3u32].private;
diff --git a/tests/expectations/compiler/slice/slice_bool_array.out b/tests/expectations/compiler/slice/slice_bool_array.out
new file mode 100644
index 00000000000..e59f3c5ae7b
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_bool_array.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [boolean; 4u32].private;
+ cast r0[1u32] r0[2u32] into r1 as [boolean; 2u32];
+ output r1 as [boolean; 2u32].private;
diff --git a/tests/expectations/compiler/slice/slice_empty_fail.out b/tests/expectations/compiler/slice/slice_empty_fail.out
new file mode 100644
index 00000000000..00ca9227e55
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_empty_fail.out
@@ -0,0 +1,5 @@
+Error [ETYC0372176]: Slice range `5..5` is invalid: end must be greater than start.
+ --> compiler-test:5:16
+ |
+ 5 | return a[5..5];
+ | ^^^^^^^
diff --git a/tests/expectations/compiler/slice/slice_from_start.out b/tests/expectations/compiler/slice/slice_from_start.out
new file mode 100644
index 00000000000..bd62782c149
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_from_start.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 8u32].private;
+ cast r0[0u32] r0[1u32] r0[2u32] r0[3u32] into r1 as [u32; 4u32];
+ output r1 as [u32; 4u32].private;
diff --git a/tests/expectations/compiler/slice/slice_full.out b/tests/expectations/compiler/slice/slice_full.out
new file mode 100644
index 00000000000..1f96da9d022
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_full.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 4u32].private;
+ cast r0[0u32] r0[1u32] r0[2u32] r0[3u32] into r1 as [u32; 4u32];
+ output r1 as [u32; 4u32].private;
diff --git a/tests/expectations/compiler/slice/slice_inclusive.out b/tests/expectations/compiler/slice/slice_inclusive.out
new file mode 100644
index 00000000000..46a02936a53
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_inclusive.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 8u32].private;
+ cast r0[2u32] r0[3u32] r0[4u32] r0[5u32] into r1 as [u32; 4u32];
+ output r1 as [u32; 4u32].private;
diff --git a/tests/expectations/compiler/slice/slice_inclusive_invalid_range_fail.out b/tests/expectations/compiler/slice/slice_inclusive_invalid_range_fail.out
new file mode 100644
index 00000000000..1ea5f2ffc00
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_inclusive_invalid_range_fail.out
@@ -0,0 +1,5 @@
+Error [ETYC0372176]: Slice range `5..2` is invalid: end must be greater than start.
+ --> compiler-test:5:16
+ |
+ 5 | return a[5..=2];
+ | ^^^^^^^^
diff --git a/tests/expectations/compiler/slice/slice_inclusive_no_start.out b/tests/expectations/compiler/slice/slice_inclusive_no_start.out
new file mode 100644
index 00000000000..bd62782c149
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_inclusive_no_start.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 8u32].private;
+ cast r0[0u32] r0[1u32] r0[2u32] r0[3u32] into r1 as [u32; 4u32];
+ output r1 as [u32; 4u32].private;
diff --git a/tests/expectations/compiler/slice/slice_inclusive_oob_fail.out b/tests/expectations/compiler/slice/slice_inclusive_oob_fail.out
new file mode 100644
index 00000000000..4c5fb0101c6
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_inclusive_oob_fail.out
@@ -0,0 +1,5 @@
+Error [ETYC0372177]: Slice range `0..9` is out of bounds for an array of length `8`.
+ --> compiler-test:5:16
+ |
+ 5 | return a[0..=8];
+ | ^^^^^^^^
diff --git a/tests/expectations/compiler/slice/slice_invalid_range_fail.out b/tests/expectations/compiler/slice/slice_invalid_range_fail.out
new file mode 100644
index 00000000000..b720230ceeb
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_invalid_range_fail.out
@@ -0,0 +1,5 @@
+Error [ETYC0372176]: Slice range `5..3` is invalid: end must be greater than start.
+ --> compiler-test:5:16
+ |
+ 5 | return a[5..3];
+ | ^^^^^^^
diff --git a/tests/expectations/compiler/slice/slice_length_one.out b/tests/expectations/compiler/slice/slice_length_one.out
new file mode 100644
index 00000000000..71b8e6ec6a4
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_length_one.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 8u32].private;
+ cast r0[3u32] into r1 as [u32; 1u32];
+ output r1 as [u32; 1u32].private;
diff --git a/tests/expectations/compiler/slice/slice_negative_bounds_fail.out b/tests/expectations/compiler/slice/slice_negative_bounds_fail.out
new file mode 100644
index 00000000000..7484c652f43
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_negative_bounds_fail.out
@@ -0,0 +1,10 @@
+Error [ETYC0372117]: Expected an unsigned integer but type `i32` was found.
+ --> compiler-test:4:18
+ |
+ 4 | return a[-1i32..3i32];
+ | ^^^^^
+Error [ETYC0372117]: Expected an unsigned integer but type `i32` was found.
+ --> compiler-test:4:25
+ |
+ 4 | return a[-1i32..3i32];
+ | ^^^^
diff --git a/tests/expectations/compiler/slice/slice_nested.out b/tests/expectations/compiler/slice/slice_nested.out
new file mode 100644
index 00000000000..dc7c85ca01f
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_nested.out
@@ -0,0 +1,7 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 8u32].private;
+ cast r0[1u32] r0[2u32] r0[3u32] r0[4u32] into r1 as [u32; 4u32];
+ cast r1[0u32] r1[1u32] into r2 as [u32; 2u32];
+ output r2 as [u32; 2u32].private;
diff --git a/tests/expectations/compiler/slice/slice_nested_array.out b/tests/expectations/compiler/slice/slice_nested_array.out
new file mode 100644
index 00000000000..013ce027b4e
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_nested_array.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [[u32; 4u32]; 4u32].private;
+ cast r0[1u32] r0[2u32] into r1 as [[u32; 4u32]; 2u32];
+ output r1 as [[u32; 4u32]; 2u32].private;
diff --git a/tests/expectations/compiler/slice/slice_non_const_fail.out b/tests/expectations/compiler/slice/slice_non_const_fail.out
new file mode 100644
index 00000000000..03b6cf33cef
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_non_const_fail.out
@@ -0,0 +1,5 @@
+Error [ECMP0376015]: The bounds of this slice expression could not be determined at compile time.
+ --> compiler-test:5:16
+ |
+ 5 | return a[start..end];
+ | ^^^^^^^^^^^^^
diff --git a/tests/expectations/compiler/slice/slice_out_of_bounds_fail.out b/tests/expectations/compiler/slice/slice_out_of_bounds_fail.out
new file mode 100644
index 00000000000..2a3ead63b40
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_out_of_bounds_fail.out
@@ -0,0 +1,5 @@
+Error [ETYC0372177]: Slice range `5..10` is out of bounds for an array of length `8`.
+ --> compiler-test:5:16
+ |
+ 5 | return a[5..10];
+ | ^^^^^^^^
diff --git a/tests/expectations/compiler/slice/slice_to_end.out b/tests/expectations/compiler/slice/slice_to_end.out
new file mode 100644
index 00000000000..49e2285a791
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_to_end.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 8u32].private;
+ cast r0[3u32] r0[4u32] r0[5u32] r0[6u32] r0[7u32] into r1 as [u32; 5u32];
+ output r1 as [u32; 5u32].private;
diff --git a/tests/expectations/compiler/slice/slice_with_variables.out b/tests/expectations/compiler/slice/slice_with_variables.out
new file mode 100644
index 00000000000..98021637bdd
--- /dev/null
+++ b/tests/expectations/compiler/slice/slice_with_variables.out
@@ -0,0 +1,6 @@
+program test.aleo;
+
+function foo:
+ input r0 as [u32; 8u32].private;
+ cast r0[1u32] r0[2u32] r0[3u32] into r1 as [u32; 3u32];
+ output r1 as [u32; 3u32].private;
diff --git a/tests/expectations/compiler/statements/CLAUDE.md b/tests/expectations/compiler/statements/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/tests/expectations/compiler/statements/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/expectations/compiler/type_inference/unsuffixed_literals_fail.out b/tests/expectations/compiler/type_inference/unsuffixed_literals_fail.out
index e86bc0459a2..b5b2cc5212b 100644
--- a/tests/expectations/compiler/type_inference/unsuffixed_literals_fail.out
+++ b/tests/expectations/compiler/type_inference/unsuffixed_literals_fail.out
@@ -75,7 +75,7 @@ Error [ETYC0372004]: Could not determine the type of `1`
| ^
|
= Consider using explicit type annotations.
-Error [ETYC0372117]: Expected a field, group, scalar, or integer but type `bool` was found.
+Error [ETYC0372117]: Expected a field, group, scalar, integer, or array but type `bool` was found.
--> compiler-test:13:22
|
13 | let c0 = 1 + true;
diff --git a/tests/expectations/parser-statement/statement/CLAUDE.md b/tests/expectations/parser-statement/statement/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/tests/expectations/parser-statement/statement/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/expectations/parser-statement/unreachable/CLAUDE.md b/tests/expectations/parser-statement/unreachable/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/tests/expectations/parser-statement/unreachable/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/tests/compiler/array/array_concat.leo b/tests/tests/compiler/array/array_concat.leo
new file mode 100644
index 00000000000..2e794e5fa60
--- /dev/null
+++ b/tests/tests/compiler/array/array_concat.leo
@@ -0,0 +1,20 @@
+
+program test.aleo {
+ // Basic array concatenation
+ fn foo(a: [u32; 2], b: [u32; 2]) -> [u32; 4] {
+ return a + b;
+ }
+
+ // Nested array concatenation (flatten)
+ fn flatten(a: [[bool; 2]; 2]) -> [bool; 4] {
+ let b: [bool; 4] = a[0] + a[1];
+ let c: [bool; 4] = [a[0][0], a[0][1], a[1][0], a[1][1]];
+ assert_eq(b, c);
+ return b;
+ }
+
+ // Different sized arrays
+ fn concat_different_sizes(a: [u8; 3], b: [u8; 2]) -> [u8; 5] {
+ return a + b;
+ }
+}
diff --git a/tests/tests/compiler/array/array_concat_chained.leo b/tests/tests/compiler/array/array_concat_chained.leo
new file mode 100644
index 00000000000..d929d5a9137
--- /dev/null
+++ b/tests/tests/compiler/array/array_concat_chained.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Chained array concatenation: a + b + c
+ fn foo(a: [u32; 2], b: [u32; 2], c: [u32; 2]) -> [u32; 6] {
+ return a + b + c;
+ }
+}
diff --git a/tests/tests/compiler/array/array_concat_literal.leo b/tests/tests/compiler/array/array_concat_literal.leo
new file mode 100644
index 00000000000..cece167fa79
--- /dev/null
+++ b/tests/tests/compiler/array/array_concat_literal.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Concatenate a literal array with a variable array
+ fn foo(a: [u32; 2]) -> [u32; 4] {
+ return [1u32, 2u32] + a;
+ }
+}
diff --git a/tests/tests/compiler/array/array_concat_type_mismatch_fail.leo b/tests/tests/compiler/array/array_concat_type_mismatch_fail.leo
new file mode 100644
index 00000000000..839d8e39de4
--- /dev/null
+++ b/tests/tests/compiler/array/array_concat_type_mismatch_fail.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // This should fail: element types don't match
+ fn foo(a: [u32; 2], b: [u8; 2]) -> [u32; 4] {
+ return a + b;
+ }
+}
diff --git a/tests/tests/compiler/array/array_concat_with_slice.leo b/tests/tests/compiler/array/array_concat_with_slice.leo
new file mode 100644
index 00000000000..f683f061523
--- /dev/null
+++ b/tests/tests/compiler/array/array_concat_with_slice.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Concatenate sliced arrays
+ fn foo(a: [u32; 4], b: [u32; 4]) -> [u32; 4] {
+ return a[0..2] + b[2..4];
+ }
+}
diff --git a/tests/tests/compiler/slice/CLAUDE.md b/tests/tests/compiler/slice/CLAUDE.md
new file mode 100644
index 00000000000..59ab83fcc31
--- /dev/null
+++ b/tests/tests/compiler/slice/CLAUDE.md
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/tests/compiler/slice/slice_basic.leo b/tests/tests/compiler/slice/slice_basic.leo
new file mode 100644
index 00000000000..d38853c7d3c
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_basic.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Basic slice with both bounds
+ fn foo(a: [u32; 8]) -> [u32; 3] {
+ return a[2..5];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_bool_array.leo b/tests/tests/compiler/slice/slice_bool_array.leo
new file mode 100644
index 00000000000..118257ee89f
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_bool_array.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Slice with non-u32 element type
+ fn foo(a: [bool; 4]) -> [bool; 2] {
+ return a[1..3];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_empty_fail.leo b/tests/tests/compiler/slice/slice_empty_fail.leo
new file mode 100644
index 00000000000..90bfc129d58
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_empty_fail.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Empty slice (start == end) - should fail
+ fn foo(a: [u32; 8]) -> [u32; 0] {
+ return a[5..5];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_from_start.leo b/tests/tests/compiler/slice/slice_from_start.leo
new file mode 100644
index 00000000000..b0ddbda90f7
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_from_start.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Slice from start to index
+ fn foo(a: [u32; 8]) -> [u32; 4] {
+ return a[..4];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_full.leo b/tests/tests/compiler/slice/slice_full.leo
new file mode 100644
index 00000000000..0acfd4cf19f
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_full.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Full slice (copy entire array)
+ fn foo(a: [u32; 4]) -> [u32; 4] {
+ return a[..];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_inclusive.leo b/tests/tests/compiler/slice/slice_inclusive.leo
new file mode 100644
index 00000000000..1613b1d8dbe
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_inclusive.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Slice with inclusive end bound
+ fn foo(a: [u32; 8]) -> [u32; 4] {
+ return a[2..=5];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_inclusive_invalid_range_fail.leo b/tests/tests/compiler/slice/slice_inclusive_invalid_range_fail.leo
new file mode 100644
index 00000000000..1d6a94aac76
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_inclusive_invalid_range_fail.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Inclusive range with start > end - should fail
+ fn foo(a: [u32; 8]) -> [u32; 2] {
+ return a[5..=2];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_inclusive_no_start.leo b/tests/tests/compiler/slice/slice_inclusive_no_start.leo
new file mode 100644
index 00000000000..ba6b193136d
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_inclusive_no_start.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Inclusive range with no start bound
+ fn foo(a: [u32; 8]) -> [u32; 4] {
+ return a[..=3];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_inclusive_oob_fail.leo b/tests/tests/compiler/slice/slice_inclusive_oob_fail.leo
new file mode 100644
index 00000000000..45b525e3266
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_inclusive_oob_fail.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Inclusive range out of bounds - a[0..=8] on [u32; 8] means index 8 which is OOB
+ fn foo(a: [u32; 8]) -> [u32; 9] {
+ return a[0..=8];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_invalid_range_fail.leo b/tests/tests/compiler/slice/slice_invalid_range_fail.leo
new file mode 100644
index 00000000000..d1bd0e69fec
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_invalid_range_fail.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Invalid range - start > end - should fail
+ fn foo(a: [u32; 8]) -> [u32; 2] {
+ return a[5..3];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_length_one.leo b/tests/tests/compiler/slice/slice_length_one.leo
new file mode 100644
index 00000000000..cc835935b2a
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_length_one.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Minimum non-empty slice (length 1)
+ fn foo(a: [u32; 8]) -> [u32; 1] {
+ return a[3..4];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_negative_bounds_fail.leo b/tests/tests/compiler/slice/slice_negative_bounds_fail.leo
new file mode 100644
index 00000000000..43c4563d880
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_negative_bounds_fail.leo
@@ -0,0 +1,6 @@
+program test.aleo {
+ // Slice bounds must be unsigned integers; signed integers are rejected.
+ fn foo(a: [u32; 8]) -> [u32; 3] {
+ return a[-1i32..3i32];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_nested.leo b/tests/tests/compiler/slice/slice_nested.leo
new file mode 100644
index 00000000000..98eab9d21b2
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_nested.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Nested slicing: slice the result of a slice
+ fn foo(a: [u32; 8]) -> [u32; 2] {
+ return a[1..5][0..2];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_nested_array.leo b/tests/tests/compiler/slice/slice_nested_array.leo
new file mode 100644
index 00000000000..bcc748da776
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_nested_array.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Slice of a nested array
+ fn foo(a: [[u32; 4]; 4]) -> [[u32; 4]; 2] {
+ return a[1..3];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_non_const_fail.leo b/tests/tests/compiler/slice/slice_non_const_fail.leo
new file mode 100644
index 00000000000..51a4e90b7de
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_non_const_fail.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Non-const variable slice bounds - should fail (bounds must be compile-time constants)
+ fn foo(a: [u32; 8], start: u32, end: u32) -> [u32; 3] {
+ return a[start..end];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_out_of_bounds_fail.leo b/tests/tests/compiler/slice/slice_out_of_bounds_fail.leo
new file mode 100644
index 00000000000..b8acacf014d
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_out_of_bounds_fail.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Slice out of bounds - should fail
+ fn foo(a: [u32; 8]) -> [u32; 5] {
+ return a[5..10];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_to_end.leo b/tests/tests/compiler/slice/slice_to_end.leo
new file mode 100644
index 00000000000..b2bdf4b8a48
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_to_end.leo
@@ -0,0 +1,7 @@
+
+program test.aleo {
+ // Slice from index to end
+ fn foo(a: [u32; 8]) -> [u32; 5] {
+ return a[3..];
+ }
+}
diff --git a/tests/tests/compiler/slice/slice_with_variables.leo b/tests/tests/compiler/slice/slice_with_variables.leo
new file mode 100644
index 00000000000..4c60ebf0727
--- /dev/null
+++ b/tests/tests/compiler/slice/slice_with_variables.leo
@@ -0,0 +1,10 @@
+
+program test.aleo {
+ const START: u32 = 1u32;
+ const END: u32 = 4u32;
+
+ // Slice with const variables
+ fn foo(a: [u32; 8]) -> [u32; 3] {
+ return a[START..END];
+ }
+}