Skip to content

Commit 9dfa0fb

Browse files
committed
Introduce index() and index_assign() to StorageMap and showcase nested maps
using both `get/insert` and the `[]` operator
1 parent 52bb053 commit 9dfa0fb

File tree

7 files changed

+417
-52
lines changed

7 files changed

+417
-52
lines changed

sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs

Lines changed: 97 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,14 +1674,14 @@ impl ty::TyExpression {
16741674
errors,
16751675
)
16761676
} else {
1677-
// Otherwise convert into a method call 'index(self, index)' via the std::ops::Index trait.
1677+
// Otherwise convert into a method call `index(self, index)`
16781678
let method_name = TypeBinding {
16791679
inner: MethodName::FromTrait {
16801680
call_path: CallPath {
1681-
prefixes: vec![
1682-
Ident::new_with_override("core".into(), span.clone()),
1683-
Ident::new_with_override("ops".into(), span.clone()),
1684-
],
1681+
// Eventually, this should be `core::ops::index` once we are able to implement
1682+
// the `Index` trait, but for now, we assume that `index` is part of `impl`
1683+
// self for the type of `prefix`.
1684+
prefixes: vec![],
16851685
suffix: Ident::new_with_override("index".into(), span.clone()),
16861686
is_absolute: true,
16871687
},
@@ -1788,8 +1788,10 @@ impl ty::TyExpression {
17881788
ReassignmentTarget::VariableExpression(var) => {
17891789
let mut expr = var;
17901790
let mut names_vec = Vec::new();
1791-
let (base_name, final_return_type) = loop {
1792-
match expr.kind {
1791+
let mut projection_index = 0;
1792+
let mut first_array_index_expr = None;
1793+
let base_name_and_final_return_type = loop {
1794+
match expr.kind.clone() {
17931795
ExpressionKind::Variable(name) => {
17941796
// check that the reassigned name exists
17951797
let unknown_decl = check!(
@@ -1808,7 +1810,7 @@ impl ty::TyExpression {
18081810
errors.push(CompileError::AssignmentToNonMutable { name, span });
18091811
return err(warnings, errors);
18101812
}
1811-
break (name, variable_decl.body.return_type);
1813+
break Some((name, variable_decl.body.return_type));
18121814
}
18131815
ExpressionKind::Subfield(SubfieldExpression {
18141816
prefix,
@@ -1830,6 +1832,19 @@ impl ty::TyExpression {
18301832
expr = prefix;
18311833
}
18321834
ExpressionKind::ArrayIndex(ArrayIndexExpression { prefix, index }) => {
1835+
// If this is the right most project (i.e. the first, since the we
1836+
// start counting from the right), and if this projection is an array
1837+
// index projection, then keep track of the `prefix` and the `index` in
1838+
// this case: we may need to call `index_assign()` later on if the
1839+
// compiler does not offer an intrinsic way of dealing with this
1840+
// reassignment
1841+
if projection_index == 0 {
1842+
first_array_index_expr = Some(ArrayIndexExpression {
1843+
prefix: prefix.clone(),
1844+
index: index.clone(),
1845+
});
1846+
}
1847+
18331848
let ctx = ctx.by_ref().with_help_text("");
18341849
let typed_index = check!(
18351850
ty::TyExpression::type_check(ctx, index.as_ref().clone()),
@@ -1839,50 +1854,88 @@ impl ty::TyExpression {
18391854
);
18401855
names_vec.push(ty::ProjectionKind::ArrayIndex {
18411856
index: Box::new(typed_index),
1842-
index_span: index.span(),
1857+
index_span: index.span().clone(),
18431858
});
18441859
expr = prefix;
18451860
}
1846-
_ => {
1847-
errors.push(CompileError::InvalidExpressionOnLhs { span });
1848-
return err(warnings, errors);
1849-
}
1861+
_ => break None,
18501862
}
1863+
projection_index += 1;
18511864
};
1852-
let names_vec = names_vec.into_iter().rev().collect::<Vec<_>>();
1853-
let (ty_of_field, _ty_of_parent) = check!(
1854-
ctx.namespace
1855-
.find_subfield_type(ctx.engines(), &base_name, &names_vec),
1856-
return err(warnings, errors),
1857-
warnings,
1858-
errors
1859-
);
1860-
// type check the reassignment
1861-
let ctx = ctx.with_type_annotation(ty_of_field).with_help_text("");
1862-
let rhs_span = rhs.span();
1863-
let rhs = check!(
1864-
ty::TyExpression::type_check(ctx, rhs),
1865-
ty::TyExpression::error(rhs_span, engines),
1866-
warnings,
1867-
errors
1868-
);
18691865

1870-
ok(
1871-
ty::TyExpression {
1872-
expression: ty::TyExpressionVariant::Reassignment(Box::new(
1873-
ty::TyReassignment {
1874-
lhs_base_name: base_name,
1875-
lhs_type: final_return_type,
1876-
lhs_indices: names_vec,
1877-
rhs,
1866+
match base_name_and_final_return_type {
1867+
Some((base_name, final_return_type)) => {
1868+
let names_vec = names_vec.into_iter().rev().collect::<Vec<_>>();
1869+
let (ty_of_field, _ty_of_parent) = check!(
1870+
ctx.namespace
1871+
.find_subfield_type(ctx.engines(), &base_name, &names_vec),
1872+
return err(warnings, errors),
1873+
warnings,
1874+
errors
1875+
);
1876+
// type check the reassignment
1877+
let ctx = ctx.with_type_annotation(ty_of_field).with_help_text("");
1878+
let rhs_span = rhs.span();
1879+
let rhs = check!(
1880+
ty::TyExpression::type_check(ctx, rhs),
1881+
ty::TyExpression::error(rhs_span, engines),
1882+
warnings,
1883+
errors
1884+
);
1885+
1886+
ok(
1887+
ty::TyExpression {
1888+
expression: ty::TyExpressionVariant::Reassignment(Box::new(
1889+
ty::TyReassignment {
1890+
lhs_base_name: base_name,
1891+
lhs_type: final_return_type,
1892+
lhs_indices: names_vec,
1893+
rhs,
1894+
},
1895+
)),
1896+
return_type: type_engine
1897+
.insert(decl_engine, TypeInfo::Tuple(Vec::new())),
1898+
span,
18781899
},
1879-
)),
1880-
return_type: type_engine.insert(decl_engine, TypeInfo::Tuple(Vec::new())),
1881-
span,
1900+
warnings,
1901+
errors,
1902+
)
1903+
}
1904+
None => match first_array_index_expr {
1905+
Some(ArrayIndexExpression { prefix, index }) => {
1906+
let method_name = TypeBinding {
1907+
inner: MethodName::FromTrait {
1908+
call_path: CallPath {
1909+
// Eventually, this should be `core::ops::index_assign`
1910+
// once we are able to implement the `IndexAssign` trait,
1911+
// but for now, we assume that `index_assign` is part of
1912+
// `impl` self for the type of `prefix`.
1913+
prefixes: vec![],
1914+
suffix: Ident::new_with_override(
1915+
"index_assign".into(),
1916+
span.clone(),
1917+
),
1918+
is_absolute: true,
1919+
},
1920+
},
1921+
type_arguments: TypeArgs::Regular(vec![]),
1922+
span: span.clone(),
1923+
};
1924+
1925+
type_check_method_application(
1926+
ctx,
1927+
method_name,
1928+
vec![],
1929+
vec![*prefix, *index, rhs],
1930+
span,
1931+
)
1932+
}
1933+
None => {
1934+
errors.push(CompileError::InvalidExpressionOnLhs { span });
1935+
err(warnings, errors)
1936+
}
18821937
},
1883-
warnings,
1884-
errors,
1885-
)
1938+
}
18861939
}
18871940
ReassignmentTarget::StorageField(storage_keyword_span, fields) => {
18881941
let ctx = ctx

sway-lib-std/src/experimental/storage.sw

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ use core::experimental::storage::StorageKey;
2424
/// ```
2525
#[storage(read, write)]
2626
pub fn write<T>(key: b256, offset: u64, value: T) {
27+
if __size_of::<T>() == 0 {
28+
return;
29+
}
30+
2731
// Get the number of storage slots needed based on the size of `T`
2832
let number_of_slots = (offset * 8 + __size_of::<T>() + 31) >> 5;
2933

@@ -68,6 +72,10 @@ pub fn write<T>(key: b256, offset: u64, value: T) {
6872
/// ```
6973
#[storage(read)]
7074
pub fn read<T>(key: b256, offset: u64) -> Option<T> {
75+
if __size_of::<T>() == 0 {
76+
return Option::None;
77+
}
78+
7179
// NOTE: we are leaking this value on the heap.
7280
// Get the number of storage slots needed based on the size of `T`
7381
let number_of_slots = (offset * 8 + __size_of::<T>() + 31) >> 5;
@@ -103,7 +111,7 @@ pub fn read<T>(key: b256, offset: u64) -> Option<T> {
103111
/// assert(read::<u64>(ZERO_B256, 0).is_none());
104112
/// ```
105113
#[storage(write)]
106-
pub fn clear<T>(key: b256) -> bool {
114+
fn clear<T>(key: b256) -> bool {
107115
// Get the number of storage slots needed based on the size of `T` as the ceiling of
108116
// `__size_of::<T>() / 32`
109117
let number_of_slots = (__size_of::<T>() + 31) >> 5;
@@ -116,13 +124,13 @@ impl<T> StorageKey<T> {
116124
/// Reads a value of type `T` starting at the location specified by `self`. If the value
117125
/// crosses the boundary of a storage slot, reading continues at the following slot.
118126
///
119-
/// Returns the value previously stored if a the storage slots read were
120-
/// valid and contain `value`. Panics otherwise.
127+
/// Returns the value previously stored if the storage slots read were valid and contain
128+
/// `value`. Reverts otherwise.
121129
///
122-
/// ### Arguments
123-
///
124-
/// None
130+
/// ### Reverts
125131
///
132+
/// Reverts if at least one of the storage slots needed to read a value of type `T` is not set.
133+
///
126134
/// ### Examples
127135
///
128136
/// ```sway
@@ -225,6 +233,38 @@ impl<K, V> StorageKey<StorageMap<K, V>> {
225233
write::<V>(key, 0, value);
226234
}
227235

236+
/// Retrieves the `StorageKey` that describes the raw location in storage of the value
237+
/// Inserts a key-value pair into the map using the `[]` operator
238+
///
239+
/// This is temporary until we are able to implement `trait IndexAssign`. The Sway compiler will
240+
/// de-sugar the index operator `[]` in an assignment expression to a call to `index_assign()`.
241+
///
242+
/// ### Arguments
243+
///
244+
/// * `key` - The key to which the value is paired.
245+
/// * `value` - The value to be stored.
246+
///
247+
/// ### Examples
248+
///
249+
/// ```sway
250+
/// storage {
251+
/// map: StorageMap<u64, bool> = StorageMap {}
252+
/// }
253+
///
254+
/// fn foo() {
255+
/// let key = 5_u64;
256+
/// let value = true;
257+
/// storage.map[key] = value; // de-sugars to `storage.map.index_assign(key, value);`
258+
/// let retrieved = storage.map.get(key).read();
259+
/// assert(value == retrieved);
260+
/// }
261+
/// ```
262+
#[storage(read, write)]
263+
pub fn index_assign(self, key: K, value: V) {
264+
let key = sha256((key, self.key));
265+
write::<V>(key, 0, value);
266+
}
267+
228268
/// Retrieves the `StorageKey` that describes the raw location in storage of the value
229269
/// stored at `key`, regardless of whether a value is actually stored at that location or not.
230270
///
@@ -243,8 +283,8 @@ impl<K, V> StorageKey<StorageMap<K, V>> {
243283
/// let key = 5_u64;
244284
/// let value = true;
245285
/// storage.map.insert(key, value);
246-
/// let retrieved_value = storage.map.get(key).read();
247-
/// assert(value == retrieved_value);
286+
/// let retrieved = storage.map.get(key).read();
287+
/// assert(value == retrieved);
248288
/// }
249289
/// ```
250290
#[storage(read)]
@@ -255,6 +295,37 @@ impl<K, V> StorageKey<StorageMap<K, V>> {
255295
}
256296
}
257297

298+
/// Retrieves the `StorageKey` that describes the raw location in storage of the value
299+
/// stored at `key`, regardless of whether a value is actually stored at that location or not.
300+
///
301+
/// This is temporary until we are able to implement `trait Index`. The Sway compiler will
302+
/// de-sugar the index operator `[]` in an expression to a call to `index()`.
303+
///
304+
/// ### Arguments
305+
///
306+
/// * `key` - The key to which the value is paired.
307+
///
308+
/// ### Examples
309+
///
310+
/// ```sway
311+
/// storage {
312+
/// map: StorageMap<u64, bool> = StorageMap {}
313+
/// }
314+
///
315+
/// fn foo() {
316+
/// let key = 5_u64;
317+
/// let value = true;
318+
/// storage.map.insert(key, value);
319+
/// let retrieved = storage.map[key].read(); // de-sugars to `storage.map.get(key).read()`
320+
/// assert(value == retrieved);
321+
/// }
322+
pub fn index(self, key: K) -> StorageKey<V> {
323+
StorageKey {
324+
key: sha256((key, self.key)),
325+
offset: 0,
326+
}
327+
}
328+
258329
/// Clears a value previously stored using a key
259330
///
260331
/// Return a Boolean indicating whether there was a value previously stored at `key`.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[[package]]
2+
name = 'core'
3+
source = 'path+from-root-2064A4F50B965AB3'
4+
5+
[[package]]
6+
name = 'experimental_storage_nested_maps'
7+
source = 'member'
8+
dependencies = ['std']
9+
10+
[[package]]
11+
name = 'std'
12+
source = 'path+from-root-2064A4F50B965AB3'
13+
dependencies = ['core']
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[project]
2+
authors = ["Fuel Labs <[email protected]>"]
3+
entry = "main.sw"
4+
license = "Apache-2.0"
5+
name = "experimental_storage_nested_maps"
6+
7+
[dependencies]
8+
std = { path = "../../../../../sway-lib-std" }
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use fuels::prelude::*;
2+
3+
abigen!(Contract(
4+
name = "TestExperimentalStorageNestedMapsContract",
5+
abi = "test_projects/experimental_storage_nested_maps/out/debug/experimental_storage_nested_maps-abi.json",
6+
));
7+
8+
async fn test_experimental_storage_nested_maps_instance(
9+
) -> TestExperimentalStorageNestedMapsContract<WalletUnlocked> {
10+
let wallet = launch_provider_and_get_wallet().await;
11+
let id = Contract::deploy(
12+
"test_projects/experimental_storage_nested_maps/out/debug/experimental_storage_nested_maps.bin",
13+
&wallet,
14+
DeployConfiguration::default(),
15+
)
16+
.await
17+
.unwrap();
18+
19+
TestExperimentalStorageNestedMapsContract::new(id.clone(), wallet)
20+
}
21+
22+
#[tokio::test]
23+
async fn nested_map_1_access() {
24+
let methods = test_experimental_storage_nested_maps_instance()
25+
.await
26+
.methods();
27+
28+
methods.nested_map_1_access().call().await.unwrap();
29+
}
30+
31+
#[tokio::test]
32+
async fn nested_map_2_access() {
33+
let methods = test_experimental_storage_nested_maps_instance()
34+
.await
35+
.methods();
36+
37+
methods.nested_map_2_access().call().await.unwrap();
38+
}
39+
40+
#[tokio::test]
41+
async fn nested_map_3_access() {
42+
let methods = test_experimental_storage_nested_maps_instance()
43+
.await
44+
.methods();
45+
46+
methods.nested_map_3_access().call().await.unwrap();
47+
}

0 commit comments

Comments
 (0)