Skip to content

Commit

Permalink
Add support for mutable arrays. (FuelLabs#3153)
Browse files Browse the repository at this point in the history
This adds support for reassigning array elements.

We now support this by extending reassignment `ProjectionKind` with a
new `ArrayIndex` variant which keeps track of the typed array index
reassignnment during semantic analysis.

Afterwards we implement this new projection in IR generation by lowering
to the `insert_element` IR instruction which updates the array element
value.

Closes FuelLabs#2457.

Co-authored-by: Mohammad Fawaz <[email protected]>
  • Loading branch information
tritao and mohammadfawaz authored Nov 29, 2022
1 parent e5dc94e commit 22db07e
Show file tree
Hide file tree
Showing 44 changed files with 377 additions and 12 deletions.
11 changes: 8 additions & 3 deletions docs/src/basics/built_in_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ let w: (u64) = (42,); // type error

## Arrays

An array is similar to a tuple, but an array's values must all be of the same type. Arrays can hold arbitrary types include non-primitive types.
An array is similar to a tuple, but an array's values must all be of the same type. Arrays can hold arbitrary types including non-primitive types.

An array is written as a comma-separated list inside square brackets:

Expand All @@ -106,8 +106,13 @@ Arrays are allocated on the stack since their size is known. An array's size is

Arrays can be iterated over, unlike tuples. An array's type is written as the type the array contains followed by the number of elements, semicolon-separated and within square brackets, e.g. `[u64; 5]`. To access an element in an array, use the _array indexing syntax_, i.e. square brackets.

Array elements can also be mutated if the underlying array is declared as mutable:

```sway
{{#include ../../../examples/arrays/src/main.sw}}
let mut x = [1, 2, 3, 4, 5];
x[0] = 0;
```

> **Note**: Arrays are currently immutable which means that changing elements of an array once initialized is not yet possible.
```sway
{{#include ../../../examples/arrays/src/main.sw}}
```
6 changes: 5 additions & 1 deletion examples/arrays/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ fn main() {
];

// Accessing an element of an array
let array_of_bools: [bool; 2] = [true, false];
let mut array_of_bools: [bool; 2] = [true, false];
assert(array_of_bools[0]);

// Mutating the element of an array
array_of_bools[1] = true;
assert(array_of_bools[1]);
}
52 changes: 50 additions & 2 deletions sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use crate::{
ir_generation::const_eval::{
compile_constant_expression, compile_constant_expression_to_constant,
},
language::{ty, *},
language::{
ty::{self, ProjectionKind},
*,
},
metadata::MetadataManager,
type_system::{LogId, TypeId, TypeInfo},
PartialEqWithTypeEngine, TypeEngine,
Expand Down Expand Up @@ -1580,7 +1583,7 @@ impl<'te> FnCompiler<'te> {
.expect("All local symbols must be in the lexical symbol map.");

// First look for a local ptr with the required name
let val = match self.function.get_local_ptr(context, name) {
let mut val = match self.function.get_local_ptr(context, name) {
Some(ptr) => {
let ptr_ty = *ptr.get_type(context);
self.current_block
Expand Down Expand Up @@ -1614,6 +1617,51 @@ impl<'te> FnCompiler<'te> {
.ins(context)
.store(val, reassign_val)
.add_metadatum(context, span_md_idx);
} else if ast_reassignment
.lhs_indices
.iter()
.any(|f| matches!(f, ProjectionKind::ArrayIndex { .. }))
{
let it = &mut ast_reassignment.lhs_indices.iter().peekable();
while let Some(ProjectionKind::ArrayIndex { index, .. }) = it.next() {
let index_val = self.compile_expression(context, md_mgr, *index.clone())?;
if index_val.is_diverging(context) {
return Ok(index_val);
}

let ty = match val.get_stripped_ptr_type(context).unwrap() {
Type::Array(aggregate) => aggregate,
_otherwise => {
let spans = ast_reassignment
.lhs_indices
.iter()
.fold(ast_reassignment.lhs_base_name.span(), |acc, lhs| {
Span::join(acc, lhs.span())
});
return Err(CompileError::Internal(
"Array index reassignment to non-array.",
spans,
));
}
};

// When handling nested array indexing, we should keep extracting the first
// elements up until the last, and insert into the last element.
let is_last_index = it.peek().is_none();
if is_last_index {
val = self
.current_block
.ins(context)
.insert_element(val, ty, reassign_val, index_val)
.add_metadatum(context, span_md_idx);
} else {
val = self
.current_block
.ins(context)
.extract_element(val, ty, index_val)
.add_metadatum(context, span_md_idx);
}
}
} else {
// An aggregate. Iterate over the field names from the left hand side and collect
// field indices. The struct type from the previous iteration is used to determine the
Expand Down
3 changes: 3 additions & 0 deletions sway-core/src/language/ty/expression/expression_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,9 @@ impl DisplayWithTypeEngine for TyExpressionVariant {
ProjectionKind::TupleField { index, .. } => {
write!(&mut place, "{}", index).unwrap();
}
ProjectionKind::ArrayIndex { index, .. } => {
write!(&mut place, "{:#?}", index).unwrap();
}
}
}
format!("reassignment to {}", place)
Expand Down
52 changes: 48 additions & 4 deletions sway-core/src/language/ty/expression/reassignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl PartialEqWithTypeEngine for TyReassignment {
fn eq(&self, rhs: &Self, type_engine: &TypeEngine) -> bool {
self.lhs_base_name == rhs.lhs_base_name
&& self.lhs_type == rhs.lhs_type
&& self.lhs_indices == rhs.lhs_indices
&& self.lhs_indices.eq(&rhs.lhs_indices, type_engine)
&& self.rhs.eq(&rhs.rhs, type_engine)
}
}
Expand All @@ -48,17 +48,60 @@ impl ReplaceDecls for TyReassignment {
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug)]
pub enum ProjectionKind {
StructField { name: Ident },
TupleField { index: usize, index_span: Span },
StructField {
name: Ident,
},
TupleField {
index: usize,
index_span: Span,
},
ArrayIndex {
index: Box<TyExpression>,
index_span: Span,
},
}

impl EqWithTypeEngine for ProjectionKind {}
impl PartialEqWithTypeEngine for ProjectionKind {
fn eq(&self, other: &Self, type_engine: &TypeEngine) -> bool {
match (self, other) {
(
ProjectionKind::StructField { name: l_name },
ProjectionKind::StructField { name: r_name },
) => l_name == r_name,
(
ProjectionKind::TupleField {
index: l_index,
index_span: l_index_span,
},
ProjectionKind::TupleField {
index: r_index,
index_span: r_index_span,
},
) => l_index == r_index && l_index_span == r_index_span,
(
ProjectionKind::ArrayIndex {
index: l_index,
index_span: l_index_span,
},
ProjectionKind::ArrayIndex {
index: r_index,
index_span: r_index_span,
},
) => l_index.eq(r_index, type_engine) && l_index_span == r_index_span,
_ => false,
}
}
}

impl Spanned for ProjectionKind {
fn span(&self) -> Span {
match self {
ProjectionKind::StructField { name } => name.span(),
ProjectionKind::TupleField { index_span, .. } => index_span.clone(),
ProjectionKind::ArrayIndex { index_span, .. } => index_span.clone(),
}
}
}
Expand All @@ -68,6 +111,7 @@ impl ProjectionKind {
match self {
ProjectionKind::StructField { name } => Cow::Borrowed(name.as_str()),
ProjectionKind::TupleField { index, .. } => Cow::Owned(index.to_string()),
ProjectionKind::ArrayIndex { index, .. } => Cow::Owned(format!("{:#?}", index)),
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1683,10 +1683,10 @@ impl ty::TyExpression {
let mut warnings = vec![];

let type_engine = ctx.type_engine;
let ctx = ctx
let mut ctx = ctx
.with_type_annotation(type_engine.insert_type(TypeInfo::Unknown))
.with_help_text("");
// ensure that the lhs is a variable expression or struct field access
// ensure that the lhs is a supported expression kind
match lhs {
ReassignmentTarget::VariableExpression(var) => {
let mut expr = var;
Expand Down Expand Up @@ -1732,6 +1732,20 @@ impl ty::TyExpression {
names_vec.push(ty::ProjectionKind::TupleField { index, index_span });
expr = prefix;
}
ExpressionKind::ArrayIndex(ArrayIndexExpression { prefix, index }) => {
let ctx = ctx.by_ref().with_help_text("");
let typed_index = check!(
ty::TyExpression::type_check(ctx, index.as_ref().clone()),
ty::TyExpression::error(span.clone(), type_engine),
warnings,
errors
);
names_vec.push(ty::ProjectionKind::ArrayIndex {
index: Box::new(typed_index),
index_span: index.span(),
});
expr = prefix;
}
_ => {
errors.push(CompileError::InvalidExpressionOnLhs { span });
return err(warnings, errors);
Expand Down
17 changes: 17 additions & 0 deletions sway-core/src/semantic_analysis/namespace/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,15 @@ impl Items {
full_name_for_error.push_str(&index.to_string());
full_span_for_error = Span::join(full_span_for_error, index_span.clone());
}
(
TypeInfo::Array(elem_ty, _count, _),
ty::ProjectionKind::ArrayIndex { index_span, .. },
) => {
parent_rover = symbol;
symbol = elem_ty;
symbol_span = index_span.clone();
full_span_for_error = index_span.clone();
}
(actually, ty::ProjectionKind::StructField { .. }) => {
errors.push(CompileError::FieldAccessOnNonStruct {
span: full_span_for_error,
Expand All @@ -353,6 +362,14 @@ impl Items {
});
return err(warnings, errors);
}
(actually, ty::ProjectionKind::ArrayIndex { .. }) => {
errors.push(CompileError::NotIndexable {
name: full_name_for_error,
span: full_span_for_error,
actually: type_engine.help_out(actually).to_string(),
});
return err(warnings, errors);
}
}
}
ok((symbol, parent_rover), warnings, errors)
Expand Down
7 changes: 7 additions & 0 deletions sway-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,12 @@ pub enum CompileError {
span: Span,
actually: String,
},
#[error("\"{name}\" is a {actually}, which is not an indexable expression.")]
NotIndexable {
name: String,
span: Span,
actually: String,
},
#[error("\"{name}\" is a {actually}, not an enum.")]
NotAnEnum {
name: String,
Expand Down Expand Up @@ -750,6 +756,7 @@ impl Spanned for CompileError {
ModuleNotFound { span, .. } => span.clone(),
NotATuple { span, .. } => span.clone(),
NotAStruct { span, .. } => span.clone(),
NotIndexable { span, .. } => span.clone(),
FieldAccessOnNonStruct { span, .. } => span.clone(),
FieldNotFound { field_name, .. } => field_name.span(),
SymbolNotFound { name, .. } => name.span(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = 'mutable_arrays'
source = 'member'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
name = "mutable_arrays"
entry = "main.sw"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
script;

fn main() -> bool {
let mut b = false;
b[0] = true;

let my_array: [u64; 1] = [1];
my_array[0] = 0;

takes_ref_mut_arr(my_array);

let mut my_array_2: [u64; 1] = [1];
my_array_2[0] = false;
my_array_2[0][1] = false;

false
}

fn takes_ref_mut_arr(ref mut arr: [u64; 1]) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
category = "fail"

# check: $()"b" is a bool, which is not an indexable expression.

# check: $()Assignment to immutable variable. Variable my_array is not declared as mutable.

# check: $()Cannot pass immutable argument to mutable parameter.

# check: $()Mismatched types.

# check: $()"my_array_2" is a u64, which is not an indexable expression.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = 'mutable_arrays'
source = 'member'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
name = "mutable_arrays"
entry = "main.sw"
implicit-std = false

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
script;

fn main() -> u64 {
let mut my_array: [u64; 1] = [1];
my_array[0] = 10;
my_array[0]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
category = "run"
expected_result = { action = "return", value = 10 }
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[[package]]
name = 'core'
source = 'path+from-root-B216E59DF5F0BD4C'

[[package]]
name = 'mutable_arrays_enum'
source = 'member'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
license = "Apache-2.0"
name = "mutable_arrays_enum"
entry = "main.sw"


[dependencies]
core = { path = "../../../../../../../sway-lib-core" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
script;

struct X {
value: u64
}

enum Foo {
Bar: X,
}

fn main() -> u64 {
let mut my_array: [Foo; 1] = [Foo::Bar(X{value: 10})];
my_array[0] = Foo::Bar(X{value: 20});
match my_array[0] {
Foo::Bar(x) => x.value,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
category = "run"
expected_result = { action = "return", value = 20 }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = 'mutable_arrays_multiple_nested'
source = 'member'
Loading

0 comments on commit 22db07e

Please sign in to comment.