Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/ast/src/const_eval/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<claude-mem-context>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file intentional?


</claude-mem-context>
15 changes: 15 additions & 0 deletions crates/ast/src/const_eval/evaluate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
};
Expand Down
19 changes: 19 additions & 0 deletions crates/ast/src/const_eval/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,25 @@ impl Value {
Some(array[i].clone().into())
}

pub fn array_slice(&self, start: usize, end_exclusive: Option<usize>) -> Option<Self> {
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()?;

Expand Down
3 changes: 3 additions & 0 deletions crates/ast/src/expressions/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<claude-mem-context>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same


</claude-mem-context>
19 changes: 18 additions & 1 deletion crates/ast/src/expressions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -109,6 +112,8 @@ pub enum Expression {
TupleAccess(Box<TupleAccess>),
/// An unary expression.
Unary(Box<UnaryExpression>),
/// An array slice expression e.g., `arr[2..5]`.
Slice(Box<SliceExpression>),
/// A unit expression e.g. `()`
Unit(UnitExpression),
}
Expand Down Expand Up @@ -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(),
}
Expand All @@ -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),
}
Expand All @@ -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(),
}
Expand All @@ -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),
}
Expand All @@ -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),
}
Expand All @@ -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
}
}
}

Expand Down Expand Up @@ -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),
Expand Down
60 changes: 60 additions & 0 deletions crates/ast/src/expressions/slice.rs
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

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<Expression>,
/// 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<SliceExpression> for Expression {
fn from(value: SliceExpression) -> Self {
Expression::Slice(Box::new(value))
}
}

crate::simple_node_impl!(SliceExpression);
3 changes: 3 additions & 0 deletions crates/ast/src/passes/consumer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions crates/ast/src/passes/reconstructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions crates/ast/src/passes/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
7 changes: 7 additions & 0 deletions crates/errors/src/errors/compiler/compiler_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
);
21 changes: 21 additions & 0 deletions crates/errors/src/errors/type_checker/type_checker_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
);
3 changes: 3 additions & 0 deletions crates/fmt/src/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<claude-mem-context>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same


</claude-mem-context>
2 changes: 1 addition & 1 deletion crates/fmt/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
11 changes: 11 additions & 0 deletions crates/fmt/tests/source/expr_slice.leo
Original file line number Diff line number Diff line change
@@ -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 ] ;

}
}
8 changes: 8 additions & 0 deletions crates/fmt/tests/target/expr_slice.leo
Original file line number Diff line number Diff line change
@@ -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];
}
}
3 changes: 3 additions & 0 deletions crates/parser-rowan/src/parser/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<claude-mem-context>

</claude-mem-context>
26 changes: 25 additions & 1 deletion crates/parser-rowan/src/parser/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
Loading