Skip to content

Commit 7b77e33

Browse files
committed
Write to array and struct members.
This is supported with a new pass, `WriteTransforming`, which converts assignments to struct or array members into assignments to variables, and any time the struct or array is accessed, reconstructs it from the variables. Tests for this functionality are in `array_write.leo`. Also fixed the test `assign_fail.leo`, which seems to have been in a useless state for a long time. Fixes #28389, #28430
1 parent a3eb1bc commit 7b77e33

File tree

28 files changed

+2523
-508
lines changed

28 files changed

+2523
-508
lines changed

compiler/ast/src/expressions/literal.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ impl Literal {
101101
pub fn display_decimal(&self) -> impl '_ + fmt::Display {
102102
DisplayDecimal(self)
103103
}
104+
105+
/// For an integer literal, parse it and cast it to a u32.
106+
///
107+
/// Panics if `self` is not an integer literal.
108+
pub fn as_u32(&self) -> Option<u32> {
109+
if let LiteralVariant::Integer(_, s) = &self.variant {
110+
u32::from_str_by_radix(&s.replace("_", "")).ok()
111+
} else {
112+
panic!("`as_u32` must only be called on integer literals");
113+
}
114+
}
104115
}
105116

106117
impl fmt::Display for DisplayDecimal<'_> {

compiler/compiler/src/compiler.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ impl<N: Network> Compiler<N> {
146146

147147
self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
148148

149+
self.do_pass::<WriteTransforming>(())?;
150+
151+
self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
152+
149153
self.do_pass::<Flattening>(())?;
150154

151155
self.do_pass::<FunctionInlining>(())?;

compiler/passes/src/const_propagation/statement.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ impl StatementReconstructor for ConstPropagationVisitor<'_> {
5454
}
5555

5656
fn reconstruct_assign(&mut self, assign: AssignStatement) -> (Statement, Self::AdditionalOutput) {
57-
(AssignStatement { value: self.reconstruct_expression(assign.value).0, ..assign }.into(), None)
57+
let value = self.reconstruct_expression(assign.value).0;
58+
let place = self.reconstruct_expression(assign.place).0;
59+
(AssignStatement { value, place, ..assign }.into(), None)
5860
}
5961

6062
fn reconstruct_block(&mut self, mut block: Block) -> (Block, Self::AdditionalOutput) {

compiler/passes/src/destructuring/statement.rs

Lines changed: 68 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -37,75 +37,88 @@ use leo_span::Symbol;
3737
use itertools::{Itertools as _, izip};
3838

3939
impl StatementReconstructor for DestructuringVisitor<'_> {
40+
/// Modify assignments to tuples to become assignments to the corresponding variables.
41+
///
42+
/// There are two cases we handle:
43+
/// 1. An assignment to a tuple x, like `x = rhs;`.
44+
/// This we need to transform into individual assignments
45+
/// `x_i = rhs_i;`
46+
/// of the variables corresponding to members of `x` and `rhs`.
47+
/// 2. An assignment to a tuple member, like `x.2[i].member = rhs;`.
48+
/// This we need to change into
49+
/// `x_2[i].member = rhs;`
50+
/// where `x_2` is the variable corresponding to `x.2`.
4051
fn reconstruct_assign(&mut self, mut assign: AssignStatement) -> (Statement, Self::AdditionalOutput) {
4152
let (value, mut statements) = self.reconstruct_expression(assign.value);
4253

43-
match assign.place {
44-
Expression::Identifier(identifier) => {
45-
if let Type::Tuple(tuple_type) =
46-
self.state.type_table.get(&value.id()).expect("Expressions should have types.")
47-
{
48-
// It's a variable of tuple type. Aleo VM doesn't know about tuples, so
49-
// we'll need to handle this.
50-
let new_symbol = self.state.assigner.unique_symbol(identifier, "##");
51-
let new_identifier = Identifier::new(new_symbol, self.state.node_builder.next_id());
52-
self.state.type_table.insert(new_identifier.id(), Type::Tuple(tuple_type.clone()));
54+
if let Expression::Identifier(identifier) = assign.place {
55+
if let Type::Tuple(..) = self.state.type_table.get(&value.id()).expect("Expressions should have types.") {
56+
// This is the first case, assigning to a variable of tuple type.
57+
let identifiers = self.tuples.get(&identifier.name).expect("Tuple should have been encountered.");
5358

54-
let identifiers = self.tuples.get(&identifier.name).expect("Tuple should have been encountered.");
55-
56-
let Expression::Identifier(rhs) = value else {
57-
panic!("SSA should have ensured this is an identifier.");
58-
};
59-
60-
let rhs_identifiers = self.tuples.get(&rhs.name).expect("Tuple should have been encountered.");
59+
let Expression::Identifier(rhs) = value else {
60+
panic!("SSA should have ensured this is an identifier.");
61+
};
6162

62-
// Again, make an assignment for each identifier.
63-
for (identifier, rhs_identifier) in identifiers.iter().zip_eq(rhs_identifiers) {
64-
let stmt = AssignStatement {
65-
place: (*identifier).into(),
66-
value: (*rhs_identifier).into(),
67-
id: self.state.node_builder.next_id(),
68-
span: Default::default(),
69-
}
70-
.into();
63+
let rhs_identifiers = self.tuples.get(&rhs.name).expect("Tuple should have been encountered.");
7164

72-
statements.push(stmt);
65+
// Again, make an assignment for each identifier.
66+
for (&identifier, &rhs_identifier) in identifiers.iter().zip_eq(rhs_identifiers) {
67+
let stmt = AssignStatement {
68+
place: identifier.into(),
69+
value: rhs_identifier.into(),
70+
id: self.state.node_builder.next_id(),
71+
span: Default::default(),
7372
}
73+
.into();
7474

75-
(Statement::dummy(), statements)
76-
} else {
77-
assign.value = value;
78-
(assign.into(), statements)
75+
statements.push(stmt);
7976
}
77+
78+
// We don't need the original assignment, just the ones we've created.
79+
return (Statement::dummy(), statements);
8080
}
81+
}
8182

82-
Expression::TupleAccess(access) => {
83-
// We're assigning to a tuple member. Again, Aleo VM doesn't know about tuples,
84-
// so we'll need to handle this.
85-
let Expression::Identifier(identifier) = &access.tuple else {
86-
panic!("SSA should have ensured this is an identifier.");
87-
};
83+
// We need to check for case 2, so we loop and see if we find a tuple access.
8884

89-
let tuple_ids = self.tuples.get(&identifier.name).expect("Tuple should have been encountered.");
85+
assign.value = value;
86+
let mut place = &mut assign.place;
9087

91-
// This is the correspondig variable name of the member we're assigning to.
92-
let identifier = tuple_ids[access.index.value()];
88+
loop {
89+
match place {
90+
Expression::TupleAccess(access) => {
91+
// We're assigning to a tuple member, case 2 mentioned above.
92+
let Expression::Identifier(identifier) = &access.tuple else {
93+
panic!("SSA should have ensured this is an identifier.");
94+
};
9395

94-
// So just assign to the variable.
95-
let assign = AssignStatement {
96-
place: Expression::Identifier(identifier),
97-
value,
98-
span: Default::default(),
99-
id: self.state.node_builder.next_id(),
96+
let tuple_ids = self.tuples.get(&identifier.name).expect("Tuple should have been encountered.");
97+
98+
// This is the corresponding variable name of the member we're assigning to.
99+
let identifier = tuple_ids[access.index.value()];
100+
101+
*place = identifier.into();
102+
103+
return (assign.into(), statements);
100104
}
101-
.into();
102105

103-
(assign, statements)
104-
}
106+
Expression::ArrayAccess(access) => {
107+
// We need to investigate the array, as maybe it's inside a tuple access, like `tupl.0[1u8]`.
108+
place = &mut access.array;
109+
}
110+
111+
Expression::MemberAccess(access) => {
112+
// We need to investigate the struct, as maybe it's inside a tuple access, like `tupl.0.mem`.
113+
place = &mut access.inner;
114+
}
105115

106-
_ => {
107-
assign.value = value;
108-
(assign.into(), statements)
116+
Expression::Identifier(..) => {
117+
// There was no tuple access, so this is neither case 1 nor 2; there's nothing to do.
118+
return (assign.into(), statements);
119+
}
120+
121+
_ => panic!("Type checking should have prevented this."),
109122
}
110123
}
111124
}
@@ -117,7 +130,9 @@ impl StatementReconstructor for DestructuringVisitor<'_> {
117130
for statement in block.statements {
118131
let (reconstructed_statement, additional_statements) = self.reconstruct_statement(statement);
119132
statements.extend(additional_statements);
120-
statements.push(reconstructed_statement);
133+
if !reconstructed_statement.is_empty() {
134+
statements.push(reconstructed_statement);
135+
}
121136
}
122137

123138
(Block { statements, ..block }, Default::default())

compiler/passes/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,6 @@ pub use symbol_table_creation::*;
5858

5959
mod type_checking;
6060
pub use type_checking::*;
61+
62+
mod write_transforming;
63+
pub use write_transforming::*;

0 commit comments

Comments
 (0)