Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental: index, index_assign, and nested storage maps #4331

Closed
wants to merge 7 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -1668,14 +1668,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 @@ -1782,8 +1782,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 @@ -1802,7 +1804,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 @@ -1824,6 +1826,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 @@ -1833,50 +1848,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
9 changes: 8 additions & 1 deletion sway-lib-std/src/experimental/storage.sw
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ use core::experimental::storage::StorageKey;
/// ```
#[storage(read, write)]
pub fn write<T>(slot: b256, offset: u64, value: T) {
if __size_of::<T>() == 0 {
return;
}
// Get the number of storage slots needed based on the size of `T`
let number_of_slots = (offset * 8 + __size_of::<T>() + 31) >> 5;

Expand Down Expand Up @@ -67,6 +70,10 @@ pub fn write<T>(slot: b256, offset: u64, value: T) {
/// ```
#[storage(read)]
pub fn read<T>(slot: b256, offset: u64) -> Option<T> {
if __size_of::<T>() == 0 {
return Option::None;
}

// NOTE: we are leaking this value on the heap.
// Get the number of storage slots needed based on the size of `T`
let number_of_slots = (offset * 8 + __size_of::<T>() + 31) >> 5;
Expand Down Expand Up @@ -102,7 +109,7 @@ pub fn read<T>(slot: b256, offset: u64) -> Option<T> {
/// assert(read::<u64>(ZERO_B256, 0).is_none());
/// ```
#[storage(write)]
pub fn clear<T>(slot: b256) -> bool {
fn clear<T>(slot: 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 Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = 'non_storage_handle_self'
source = 'member'
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
implicit-std = false
license = "Apache-2.0"
name = "non_storage_handle_self"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
script;

#[allow(dead_code)]
struct S {}

impl S {
// The only type ascription allowed for self is `core::experimental::storage::StorageHandle`
fn foo(self: u64, foo: u64) {}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
category = "fail"

# check: $()fn foo(self: u64, foo: u64) {}
# nextln $()invalid `self` parameter type: u64
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,47 @@
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<WalletUnlocked> {
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,
DeployConfiguration::default(),
)
.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