Skip to content

Commit

Permalink
Introduce index() and index_assign() to StorageMap and showcase…
Browse files Browse the repository at this point in the history
… nested maps

using both `get/insert` and the `[]` operator
  • Loading branch information
mohammadfawaz committed Mar 24, 2023
1 parent be85a10 commit 893905a
Show file tree
Hide file tree
Showing 7 changed files with 414 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1646,14 +1646,14 @@ impl ty::TyExpression {
errors,
)
} else {
// Otherwise convert into a method call 'index(self, index)' via the std::ops::Index trait.
// Otherwise convert into a method call `index(self, index)`
let method_name = TypeBinding {
inner: MethodName::FromTrait {
call_path: CallPath {
prefixes: vec![
Ident::new_with_override("core".into(), span.clone()),
Ident::new_with_override("ops".into(), span.clone()),
],
// Eventually, this should be `core::ops::index` once we are able to implement
// the `Index` trait, but for now, we assume that `index` is part of `impl`
// self for the type of `prefix`.
prefixes: vec![],
suffix: Ident::new_with_override("index".into(), span.clone()),
is_absolute: true,
},
Expand Down Expand Up @@ -1760,8 +1760,10 @@ impl ty::TyExpression {
ReassignmentTarget::VariableExpression(var) => {
let mut expr = var;
let mut names_vec = Vec::new();
let (base_name, final_return_type) = loop {
match expr.kind {
let mut projection_index = 0;
let mut first_array_index_expr = None;
let base_name_and_final_return_type = loop {
match expr.kind.clone() {
ExpressionKind::Variable(name) => {
// check that the reassigned name exists
let unknown_decl = check!(
Expand All @@ -1780,7 +1782,7 @@ impl ty::TyExpression {
errors.push(CompileError::AssignmentToNonMutable { name, span });
return err(warnings, errors);
}
break (name, variable_decl.body.return_type);
break Some((name, variable_decl.body.return_type));
}
ExpressionKind::Subfield(SubfieldExpression {
prefix,
Expand All @@ -1802,6 +1804,19 @@ impl ty::TyExpression {
expr = prefix;
}
ExpressionKind::ArrayIndex(ArrayIndexExpression { prefix, index }) => {
// If this is the right most project (i.e. the first, since the we
// start counting from the right), and if this projection is an array
// index projection, then keep track of the `prefix` and the `index` in
// this case: we may need to call `index_assign()` later on if the
// compiler does not offer an intrinsic way of dealing with this
// reassignment
if projection_index == 0 {
first_array_index_expr = Some(ArrayIndexExpression {
prefix: prefix.clone(),
index: index.clone(),
});
}

let ctx = ctx.by_ref().with_help_text("");
let typed_index = check!(
ty::TyExpression::type_check(ctx, index.as_ref().clone()),
Expand All @@ -1811,50 +1826,88 @@ impl ty::TyExpression {
);
names_vec.push(ty::ProjectionKind::ArrayIndex {
index: Box::new(typed_index),
index_span: index.span(),
index_span: index.span().clone(),
});
expr = prefix;
}
_ => {
errors.push(CompileError::InvalidExpressionOnLhs { span });
return err(warnings, errors);
}
_ => break None,
}
projection_index += 1;
};
let names_vec = names_vec.into_iter().rev().collect::<Vec<_>>();
let (ty_of_field, _ty_of_parent) = check!(
ctx.namespace
.find_subfield_type(ctx.engines(), &base_name, &names_vec),
return err(warnings, errors),
warnings,
errors
);
// type check the reassignment
let ctx = ctx.with_type_annotation(ty_of_field).with_help_text("");
let rhs_span = rhs.span();
let rhs = check!(
ty::TyExpression::type_check(ctx, rhs),
ty::TyExpression::error(rhs_span, engines),
warnings,
errors
);

ok(
ty::TyExpression {
expression: ty::TyExpressionVariant::Reassignment(Box::new(
ty::TyReassignment {
lhs_base_name: base_name,
lhs_type: final_return_type,
lhs_indices: names_vec,
rhs,
match base_name_and_final_return_type {
Some((base_name, final_return_type)) => {
let names_vec = names_vec.into_iter().rev().collect::<Vec<_>>();
let (ty_of_field, _ty_of_parent) = check!(
ctx.namespace
.find_subfield_type(ctx.engines(), &base_name, &names_vec),
return err(warnings, errors),
warnings,
errors
);
// type check the reassignment
let ctx = ctx.with_type_annotation(ty_of_field).with_help_text("");
let rhs_span = rhs.span();
let rhs = check!(
ty::TyExpression::type_check(ctx, rhs),
ty::TyExpression::error(rhs_span, engines),
warnings,
errors
);

ok(
ty::TyExpression {
expression: ty::TyExpressionVariant::Reassignment(Box::new(
ty::TyReassignment {
lhs_base_name: base_name,
lhs_type: final_return_type,
lhs_indices: names_vec,
rhs,
},
)),
return_type: type_engine
.insert(decl_engine, TypeInfo::Tuple(Vec::new())),
span,
},
)),
return_type: type_engine.insert(decl_engine, TypeInfo::Tuple(Vec::new())),
span,
warnings,
errors,
)
}
None => match first_array_index_expr {
Some(ArrayIndexExpression { prefix, index }) => {
let method_name = TypeBinding {
inner: MethodName::FromTrait {
call_path: CallPath {
// Eventually, this should be `core::ops::index_assign`
// once we are able to implement the `IndexAssign` trait,
// but for now, we assume that `index_assign` is part of
// `impl` self for the type of `prefix`.
prefixes: vec![],
suffix: Ident::new_with_override(
"index_assign".into(),
span.clone(),
),
is_absolute: true,
},
},
type_arguments: TypeArgs::Regular(vec![]),
span: span.clone(),
};

type_check_method_application(
ctx,
method_name,
vec![],
vec![*prefix, *index, rhs],
span,
)
}
None => {
errors.push(CompileError::InvalidExpressionOnLhs { span });
err(warnings, errors)
}
},
warnings,
errors,
)
}
}
ReassignmentTarget::StorageField(storage_keyword_span, fields) => {
let ctx = ctx
Expand Down
83 changes: 72 additions & 11 deletions sway-lib-std/src/experimental/storage.sw
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub fn read<T>(key: b256, offset: u64) -> Option<T> {
/// assert(read::<u64>(ZERO_B256, 0).is_none());
/// ```
#[storage(write)]
pub fn clear<T>(key: b256) -> bool {
fn clear<T>(key: b256) -> bool {
// Get the number of storage slots needed based on the size of `T` as the ceiling of
// `__size_of::<T>() / 32`
let number_of_slots = (__size_of::<T>() + 31) >> 5;
Expand All @@ -116,13 +116,13 @@ impl<T> StorageHandle<T> {
/// Reads a value of type `T` starting at the location specified by `self`. If the value
/// crosses the boundary of a storage slot, reading continues at the following slot.
///
/// Returns the value previously stored if a the storage slots read were
/// valid and contain `value`. Panics otherwise.
/// Returns the value previously stored if the storage slots read were valid and contain
/// `value`. Reverts otherwise.
///
/// ### Arguments
///
/// None
/// ### Reverts
///
/// Reverts if at least one of the storage slots needed to read a value of type `T` is not set.
///
/// ### Examples
///
/// ```sway
Expand Down Expand Up @@ -215,8 +215,8 @@ impl<K, V> StorageMap<K, V> {
/// let key = 5_u64;
/// let value = true;
/// storage.map.insert(key, value);
/// let retrieved_value = storage.map.get(key);
/// assert(value == retrieved_value);
/// let retrieved = storage.map.get(key).read();
/// assert(value == retrieved);
/// }
/// ```
#[storage(read, write)]
Expand All @@ -225,6 +225,37 @@ impl<K, V> StorageMap<K, V> {
write::<V>(key, 0, value);
}

/// Inserts a key-value pair into the map using the `[]` operator
///
/// This is temporary until we are able to implement `trait IndexAssign`. The Sway compiler will
/// de-sugar the index operator `[]` in an assignment expression to a call to `index_assign()`.
///
/// ### Arguments
///
/// * `key` - The key to which the value is paired.
/// * `value` - The value to be stored.
///
/// ### Examples
///
/// ```sway
/// storage {
/// map: StorageMap<u64, bool> = StorageMap {}
/// }
///
/// fn foo() {
/// let key = 5_u64;
/// let value = true;
/// storage.map[key] = value; // de-sugars to `storage.map.index_assign(key, value);`
/// let retrieved = storage.map.get(key).read();
/// assert(value == retrieved);
/// }
/// ```
#[storage(read, write)]
pub fn index_assign(self: StorageHandle<Self>, key: K, value: V) {
let key = sha256((key, self.key));
write::<V>(key, 0, value);
}

/// Retrieves the `StorageHandle` that describes the raw location in storage of the value
/// stored at `key`, regardless of whether a value is actually stored at that location or not.
///
Expand All @@ -243,18 +274,48 @@ impl<K, V> StorageMap<K, V> {
/// let key = 5_u64;
/// let value = true;
/// storage.map.insert(key, value);
/// let retrieved_value = storage.map.get(key).read();
/// assert(value == retrieved_value);
/// let retrieved = storage.map.get(key).read();
/// assert(value == retrieved);
/// }
/// ```
#[storage(read)]
pub fn get(self: StorageHandle<Self>, key: K) -> StorageHandle<V> {
StorageHandle {
key: sha256((key, self.key)),
offset: 0,
}
}

/// Retrieves the `StorageHandle` that describes the raw location in storage of the value
/// stored at `key`, regardless of whether a value is actually stored at that location or not.
///
/// This is temporary until we are able to implement `trait Index`. The Sway compiler will
/// de-sugar the index operator `[]` in an expression to a call to `index()`.
///
/// ### Arguments
///
/// * `key` - The key to which the value is paired.
///
/// ### Examples
///
/// ```sway
/// storage {
/// map: StorageMap<u64, bool> = StorageMap {}
/// }
///
/// fn foo() {
/// let key = 5_u64;
/// let value = true;
/// storage.map.insert(key, value);
/// let retrieved = storage.map[key].read(); // de-sugars to `storage.map.get(key).read()`
/// assert(value == retrieved);
/// }
pub fn index(self: StorageHandle<Self>, key: K) -> StorageHandle<V> {
StorageHandle {
key: sha256((key, self.key)),
offset: 0,
}
}

/// Clears a value previously stored using a key
///
/// Return a Boolean indicating whether there was a value previously stored at `key`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[package]]
name = 'core'
source = 'path+from-root-2064A4F50B965AB3'

[[package]]
name = 'experimental_storage_nested_maps'
source = 'member'
dependencies = ['std']

[[package]]
name = 'std'
source = 'path+from-root-2064A4F50B965AB3'
dependencies = ['core']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "experimental_storage_nested_maps"

[dependencies]
std = { path = "../../../../../sway-lib-std" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use fuels::prelude::*;

abigen!(Contract(
name = "TestExperimentalStorageNestedMapsContract",
abi = "test_projects/experimental_storage_nested_maps/out/debug/experimental_storage_nested_maps-abi.json",
));

async fn test_experimental_storage_nested_maps_instance(
) -> TestExperimentalStorageNestedMapsContract {
let wallet = launch_provider_and_get_wallet().await;
let id = Contract::deploy(
"test_projects/experimental_storage_nested_maps/out/debug/experimental_storage_nested_maps.bin",
&wallet,
TxParameters::default(),
StorageConfiguration::with_storage_path(Some(
"test_projects/experimental_storage_nested_maps/out/debug/experimental_storage_nested_maps-storage_slots.json"
.to_string(),
)),
)
.await
.unwrap();

TestExperimentalStorageNestedMapsContract::new(id.clone(), wallet)
}

#[tokio::test]
async fn nested_map_1_access() {
let methods = test_experimental_storage_nested_maps_instance()
.await
.methods();

methods.nested_map_1_access().call().await.unwrap();
}

#[tokio::test]
async fn nested_map_2_access() {
let methods = test_experimental_storage_nested_maps_instance()
.await
.methods();

methods.nested_map_2_access().call().await.unwrap();
}

#[tokio::test]
async fn nested_map_3_access() {
let methods = test_experimental_storage_nested_maps_instance()
.await
.methods();

methods.nested_map_3_access().call().await.unwrap();
}
Loading

0 comments on commit 893905a

Please sign in to comment.