From 6f9aab8d53a1bcaf13ffa1dca22a0e5025d408d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Ron=C4=8Devi=C4=87?= Date: Wed, 31 Jan 2024 15:56:36 +0100 Subject: [PATCH] Dereferencing operator index `[]` (#5530) ## Description This PR implements index operator `[]` for [references](https://github.com/FuelLabs/sway-rfcs/blob/ironcev/amend-references/files/0010-references.sw). The overall effort related to references is tracked in #5063. `[]` is defined for references using this recursive definition: `[] := (*)[]`. This eliminates the need for the dereferencing operator `*` when working with references to arrays: ```Sway let array = [1, 2, 3]; let r = &&&array; assert((***r)[0] == r[0]); ``` ```Sway let r = &&&[ &&[1, 2, 3], &&[4, 5, 6] ]; assert(r[0][2] == 3); assert(r[1][0] == 4); ``` Additionally, the PR fixes two previously existing issues (see below screenshots): - the misleading error message on type not implementing the "index" method when indexing a non-indexable type. - the broken error span and expression text when indexing a non-indexable type in reassignments. Before: ![No method named index found for type](https://github.com/FuelLabs/sway/assets/4142833/4693510d-bcb4-45ca-8f41-7988a8d87b3e) ![Not an indexable expression - Before](https://github.com/FuelLabs/sway/assets/4142833/6b8f28f0-25db-4ba8-b6a5-3d5468c70cdc) After: ![Type is not indexable](https://github.com/FuelLabs/sway/assets/4142833/df160b3b-312e-40dc-b32f-71786ee382ea) ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [ ] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --- .../ast_node/expression/typed_expression.rs | 99 +++---- .../typed_expression/tuple_index_access.rs | 2 +- .../src/semantic_analysis/namespace/items.rs | 18 +- sway-core/src/type_system/info.rs | 18 +- sway-error/src/error.rs | 31 ++- .../should_fail/diverging_never/test.toml | 3 +- .../should_fail/mutable_arrays/test.toml | 8 +- .../should_fail/non_indexable_types/Forc.lock | 3 + .../should_fail/non_indexable_types/Forc.toml | 6 + .../non_indexable_types/src/main.sw | 36 +++ .../should_fail/non_indexable_types/test.toml | 84 ++++++ .../dereferencing_operator_index/Forc.lock | 13 + .../dereferencing_operator_index/Forc.toml | 8 + .../json_abi_oracle.json | 25 ++ .../dereferencing_operator_index/src/impls.sw | 258 ++++++++++++++++++ .../dereferencing_operator_index/src/main.sw | 196 +++++++++++++ .../dereferencing_operator_index/test.toml | 4 + .../dereferencing_operator_star/src/main.sw | 20 +- .../references_in_aggregates/src/main.sw | 24 ++ .../src/main.sw | 3 + .../src/main.sw | 15 +- 21 files changed, 778 insertions(+), 96 deletions(-) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/json_abi_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/src/impls.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/test.toml diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index 0eb9c2ca834..9c92c817db6 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -1799,65 +1799,70 @@ impl ty::TyExpression { let type_engine = ctx.engines.te(); let engines = ctx.engines(); - let prefix_te = { + let mut current_prefix_te = Box::new({ let ctx = ctx .by_ref() .with_help_text("") .with_type_annotation(type_engine.insert(engines, TypeInfo::Unknown, None)); - ty::TyExpression::type_check(handler, ctx, prefix.clone())? - }; - fn get_array_type(ty: TypeId, type_engine: &TypeEngine) -> Option { - match &*type_engine.get(ty) { - TypeInfo::Array(..) => Some((*type_engine.get(ty)).clone()), - TypeInfo::Alias { ty, .. } => get_array_type(ty.type_id, type_engine), - _ => None, - } + ty::TyExpression::type_check(handler, ctx, prefix)? + }); + + let mut current_type = type_engine.get_unaliased(current_prefix_te.return_type); + + let prefix_type_id = current_prefix_te.return_type; + let prefix_span = current_prefix_te.span.clone(); + + // Create the prefix part of the final array index expression. + // This might be an expression that directly evaluates to an array type, + // or an arbitrary number of dereferencing expressions where the last one + // dereference to an array type. + // + // We will either hit an array at the end or return an error, so the + // loop cannot be endless. + while !current_type.is_array() { + match &*current_type { + TypeInfo::Ref(referenced_type) => { + let referenced_type_id = referenced_type.type_id; + + current_prefix_te = Box::new(ty::TyExpression { + expression: ty::TyExpressionVariant::Deref(current_prefix_te), + return_type: referenced_type_id, + span: prefix_span.clone(), + }); + + current_type = type_engine.get_unaliased(referenced_type_id); + } + _ => { + return Err(handler.emit_err(CompileError::NotIndexable { + actually: engines.help_out(prefix_type_id).to_string(), + span: prefix_span.clone(), + })) + } + }; } - // If the return type is a static array then create a `ty::TyExpressionVariant::ArrayIndex`. - if let Some(TypeInfo::Array(elem_type, _)) = - get_array_type(prefix_te.return_type, type_engine) - { + let TypeInfo::Array(array_type_argument, _) = &*current_type else { + panic!("The current type must be an array."); + }; + + let index_te = { let type_info_u64 = TypeInfo::UnsignedInteger(IntegerBits::SixtyFour); let ctx = ctx .with_help_text("") .with_type_annotation(type_engine.insert(engines, type_info_u64, None)); - let index_te = ty::TyExpression::type_check(handler, ctx, index)?; - Ok(ty::TyExpression { - expression: ty::TyExpressionVariant::ArrayIndex { - prefix: Box::new(prefix_te), - index: Box::new(index_te), - }, - return_type: elem_type.type_id, - span, - }) - } else { - // Otherwise convert into a method call 'index(self, index)' via the std::ops::Index trait. - 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()), - ], - suffix: Ident::new_with_override("index".into(), span.clone()), - is_absolute: true, - }, - }, - type_arguments: TypeArgs::Regular(vec![]), - span: span.clone(), - }; - type_check_method_application( - handler, - ctx, - method_name, - vec![], - vec![prefix, index], - span, - ) - } + ty::TyExpression::type_check(handler, ctx, index)? + }; + + Ok(ty::TyExpression { + expression: ty::TyExpressionVariant::ArrayIndex { + prefix: current_prefix_te, + index: Box::new(index_te), + }, + return_type: array_type_argument.type_id, + span, + }) } fn type_check_intrinsic_function( diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/tuple_index_access.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/tuple_index_access.rs index 2a8ea3bb3d1..606773433c5 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/tuple_index_access.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/tuple_index_access.rs @@ -14,7 +14,7 @@ pub(crate) fn instantiate_tuple_index_access( let type_engine = engines.te(); let mut tuple_type_arg_to_access = None; let type_info = type_engine.get(parent.return_type); - let type_args = type_info.expect_tuple(handler, engines, parent.span.as_str(), &parent.span)?; + let type_args = type_info.expect_tuple(handler, engines, &parent.span)?; for (pos, type_arg) in type_args.iter().enumerate() { if pos == index { tuple_type_arg_to_access = Some(type_arg.clone()); diff --git a/sway-core/src/semantic_analysis/namespace/items.rs b/sway-core/src/semantic_analysis/namespace/items.rs index 98877f59461..fca38496363 100644 --- a/sway-core/src/semantic_analysis/namespace/items.rs +++ b/sway-core/src/semantic_analysis/namespace/items.rs @@ -353,7 +353,6 @@ impl Items { let mut symbol = symbol.return_type(handler, engines)?; let mut symbol_span = base_name.span(); let mut parent_rover = symbol; - let mut full_name_for_error = base_name.to_string(); let mut full_span_for_error = base_name.span(); for projection in projections { let resolved_type = match type_engine.to_typeinfo(symbol, &symbol_span) { @@ -412,7 +411,6 @@ impl Items { parent_rover = symbol; symbol = field_type_id; symbol_span = field_name.span().clone(); - full_name_for_error.push_str(field_name.as_str()); full_span_for_error = Span::join(full_span_for_error, field_name.span().clone()); } @@ -435,7 +433,6 @@ impl Items { parent_rover = symbol; symbol = *field_type; symbol_span = index_span.clone(); - full_name_for_error.push_str(&index.to_string()); full_span_for_error = Span::join(full_span_for_error, index_span.clone()); } ( @@ -445,7 +442,14 @@ impl Items { parent_rover = symbol; symbol = elem_ty.type_id; symbol_span = index_span.clone(); - full_span_for_error = index_span.clone(); + // `index_span` does not contain the enclosing square brackets. + // Which means, if this array index access is the last one before the + // erroneous expression, the `full_span_for_error` will be missing the + // closing `]`. We can live with this small glitch so far. To fix it, + // we would need to bring the full span of the index all the way from + // the parsing stage. An effort that doesn't pay off at the moment. + // TODO: Include the closing square bracket into the error span. + full_span_for_error = Span::join(full_span_for_error, index_span.clone()); } (actually, ty::ProjectionKind::StructField { .. }) => { return Err(handler.emit_err(CompileError::FieldAccessOnNonStruct { @@ -455,16 +459,14 @@ impl Items { } (actually, ty::ProjectionKind::TupleField { .. }) => { return Err(handler.emit_err(CompileError::NotATuple { - name: full_name_for_error, - span: full_span_for_error, actually: engines.help_out(actually).to_string(), + span: full_span_for_error, })); } (actually, ty::ProjectionKind::ArrayIndex { .. }) => { return Err(handler.emit_err(CompileError::NotIndexable { - name: full_name_for_error, - span: full_span_for_error, actually: engines.help_out(actually).to_string(), + span: full_span_for_error, })); } } diff --git a/sway-core/src/type_system/info.rs b/sway-core/src/type_system/info.rs index c479ccc5d11..6972c278dae 100644 --- a/sway-core/src/type_system/info.rs +++ b/sway-core/src/type_system/info.rs @@ -1092,6 +1092,10 @@ impl TypeInfo { matches!(self, TypeInfo::Ref(_)) } + pub fn is_array(&self) -> bool { + matches!(self, TypeInfo::Array(_, _)) + } + pub(crate) fn apply_type_arguments( self, handler: &Handler, @@ -1409,7 +1413,6 @@ impl TypeInfo { &self, handler: &Handler, engines: &Engines, - debug_string: impl Into, debug_span: &Span, ) -> Result, ErrorEmitted> { match self { @@ -1417,17 +1420,14 @@ impl TypeInfo { TypeInfo::Alias { ty: TypeArgument { type_id, .. }, .. - } => { - engines - .te() - .get(*type_id) - .expect_tuple(handler, engines, debug_string, debug_span) - } + } => engines + .te() + .get(*type_id) + .expect_tuple(handler, engines, debug_span), TypeInfo::ErrorRecovery(err) => Err(*err), a => Err(handler.emit_err(CompileError::NotATuple { - name: debug_string.into(), - span: debug_span.clone(), actually: engines.help_out(a).to_string(), + span: debug_span.clone(), })), } } diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index a25a90934e9..747025f52bb 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -287,18 +287,10 @@ pub enum CompileError { ModuleNotFound { span: Span, name: String }, #[error("This is a {actually}, not a struct. Fields can only be accessed on structs.")] FieldAccessOnNonStruct { actually: String, span: Span }, - #[error("\"{name}\" is a {actually}, not a tuple. Elements can only be access on tuples.")] - NotATuple { - name: String, - span: Span, - actually: String, - }, - #[error("\"{name}\" is a {actually}, which is not an indexable expression.")] - NotIndexable { - name: String, - span: Span, - actually: String, - }, + #[error("This is a {actually}, not a tuple. Elements can only be access on tuples.")] + NotATuple { actually: String, span: Span }, + #[error("This expression has type \"{actually}\", which is not an indexable type.")] + NotIndexable { actually: String, span: Span }, #[error("\"{name}\" is a {actually}, not an enum.")] NotAnEnum { name: String, @@ -1713,6 +1705,21 @@ impl ToDiagnostic for CompileError { }, help: vec![], }, + NotIndexable { actually, span } => Diagnostic { + reason: Some(Reason::new(code(1), "Type is not indexable".to_string())), + issue: Issue::error( + source_engine, + span.clone(), + format!("This expression has type \"{actually}\", which is not an indexable type.") + ), + hints: vec![], + help: vec![ + "Index operator `[]` can be used only on indexable types.".to_string(), + "In Sway, indexable types are:".to_string(), + format!("{}- arrays. E.g., `[u64;3]`.", Indent::Single), + format!("{}- references, direct or indirect, to arrays. E.g., `&[u64;3]` or `&&&[u64;3]`.", Indent::Single), + ], + }, _ => Diagnostic { // TODO: Temporary we use self here to achieve backward compatibility. // In general, self must not be used and will not be used once we diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/diverging_never/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/diverging_never/test.toml index 2d563102e63..d81bc350850 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/diverging_never/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/diverging_never/test.toml @@ -1,4 +1,5 @@ category = "fail" +# check: $()Type is not indexable # check: $()}[0]; -# nextln: $()No method named "index" found for type "Never". +# nextln: $()This expression has type "Never", which is not an indexable type. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/test.toml index e0a36d4032d..65f59b26cfc 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/mutable_arrays/test.toml @@ -1,6 +1,8 @@ category = "fail" -# check: $()"b" is a bool, which is not an indexable expression. +# check: $()Type is not indexable +# check: $()b[0] = true; +# nextln: $()This expression has type "bool", which is not an indexable type. # check: $()Assignment to immutable variable. Variable my_array is not declared as mutable. @@ -8,4 +10,6 @@ category = "fail" # check: $()Mismatched types. -# check: $()"my_array_2" is a u64, which is not an indexable expression. +# check: $()Type is not indexable +# check: $()my_array_2[0][1] = false; +# nextln: $()This expression has type "u64", which is not an indexable type. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/Forc.lock new file mode 100644 index 00000000000..21b23b96915 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = "non_indexable_types" +source = "member" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/Forc.toml new file mode 100644 index 00000000000..fbf09d622e5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "non_indexable_types" +entry = "main.sw" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/src/main.sw new file mode 100644 index 00000000000..c46759357eb --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/src/main.sw @@ -0,0 +1,36 @@ +script; + +struct S { + x: u8, + u8_field: u8, +} + +fn main() { + let mut not_array = 0; + let _ = not_array[0]; + not_array[0] = 1; + + let mut s = S { x: 0, u8_field: 0 }; + let _ = s[0]; + s[0] = 1; + + let _ = s.x[0]; + s.x[0] = 1; + + let mut array = [s, s]; + let _ = array[0].x[0]; + array[0].x[0] = 1; + + let _= array[0].u8_field[0]; + array[0].u8_field[0] = 1; + + let _ = array[0][0]; + array[0][0] = 1; + + let mut tuple = (1, 2); + let _ = tuple[0]; + tuple[0] = 1; + + let _ = tuple.1[0]; + tuple.1[0] = 1; +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/test.toml new file mode 100644 index 00000000000..c68732f35e5 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/non_indexable_types/test.toml @@ -0,0 +1,84 @@ +category = "fail" + +# We want to check for ^^^^ to have at least the check for the expected length of the error span. +# The reason is the bug in the error span for this error, that we had before. + +#check: $()Type is not indexable +#check: $()let _ = not_array[0]; +#nextln: $()^^^^^^^^^ +#sameln: $()This expression has type "numeric", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()not_array[0] = 1; +#nextln: $()^^^^^^^^^ +#sameln: $()This expression has type "numeric", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()let _ = s[0]; +#nextln: $()^ +#sameln: $()This expression has type "S", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()s[0] = 1; +#nextln: $()^ +#sameln: $()This expression has type "S", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()let _ = s.x[0]; +#nextln: $()^^^ +#sameln: $()This expression has type "u8", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()s.x[0] = 1; +#nextln: $()^^^ +#sameln: $()This expression has type "u8", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()let _ = array[0].x[0]; +#nextln: $()^^^^^^^^^^ +#sameln: $()This expression has type "u8", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()array[0].x[0] = 1; +#nextln: $()^^^^^^^^^^ +#sameln: $()This expression has type "u8", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()let _= array[0].u8_field[0]; +#nextln: $()^^^^^^^^^^^^^^^^^ +#sameln: $()This expression has type "u8", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()array[0].u8_field[0] = 1; +#nextln: $()^^^^^^^^^^^^^^^^^ +#sameln: $()This expression has type "u8", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()let _ = array[0][0]; +#nextln: $()^^^^^^^^ +#sameln: $()This expression has type "S", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()array[0][0] = 1; +#nextln: $()^^^^^^^ +#sameln: $()This expression has type "S", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()let _ = tuple[0]; +#nextln: $()^^^^^ +#sameln: $()This expression has type "(numeric, numeric)", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()tuple[0] = 1; +#nextln: $()^^^^^ +#sameln: $()This expression has type "(numeric, numeric)", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()let _ = tuple.1[0]; +#nextln: $()^^^^^^^ +#sameln: $()This expression has type "numeric", which is not an indexable type. + +#check: $()Type is not indexable +#check: $()tuple.1[0] = 1; +#nextln: $()^^^^^^^ +#sameln: $()This expression has type "numeric", which is not an indexable type. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/Forc.lock new file mode 100644 index 00000000000..4a3edc7f2ca --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-F936EAA5128B67EA" + +[[package]] +name = "dereferencing_operator_index" +source = "member" +dependencies = ["std"] + +[[package]] +name = "std" +source = "path+from-root-F936EAA5128B67EA" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/Forc.toml new file mode 100644 index 00000000000..ce440480dd9 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "dereferencing_operator_index" + +[dependencies] +std = { path = "../../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/json_abi_oracle.json new file mode 100644 index 00000000000..ad50b55d54c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/json_abi_oracle.json @@ -0,0 +1,25 @@ +{ + "configurables": [], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": null, + "type": "u64", + "typeId": 0, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/src/impls.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/src/impls.sw new file mode 100644 index 00000000000..dad85dd3649 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/src/impls.sw @@ -0,0 +1,258 @@ +library; + +use core::ops::Eq; +use std::bytes_conversions::u256::*; +use std::bytes_conversions::b256::*; + +pub trait TestInstance { + fn new() -> Self; + fn different() -> Self; +} + +impl TestInstance for bool { + fn new() -> Self { + true + } + fn different() -> Self { + false + } +} + +impl TestInstance for u8 { + fn new() -> Self { + 123 + } + fn different() -> Self { + 223 + } +} + +impl TestInstance for u16 { + fn new() -> Self { + 1234 + } + fn different() -> Self { + 4321 + } +} + +impl TestInstance for u32 { + fn new() -> Self { + 12345 + } + fn different() -> Self { + 54321 + } +} + +impl TestInstance for u64 { + fn new() -> Self { + 123456 + } + fn different() -> Self { + 654321 + } +} + +impl TestInstance for u256 { + fn new() -> Self { + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20u256 + } + fn different() -> Self { + 0x0203040405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1f1a20u256 + } +} + +impl TestInstance for str { + fn new() -> Self { + "1a2B3c" + } + fn different() -> Self { + "3A2b1C" + } +} + +impl Eq for str[6] { + fn eq(self, other: Self) -> bool { + let mut i = 0; + while i < 6 { + let ptr_self = __addr_of(self).add::(i); + let ptr_other = __addr_of(other).add::(i); + + if ptr_self.read::() != ptr_other.read::() { + return false; + } + + i = i + 1; + }; + + true + } +} + +impl TestInstance for str[6] { + fn new() -> Self { + __to_str_array("1a2B3c") + } + fn different() -> Self { + __to_str_array("3A2b1C") + } +} + +impl Eq for [u64;2] { + fn eq(self, other: Self) -> bool { + self[0] == other[0] && self[1] == other[1] + } +} + +impl TestInstance for [u64;2] { + fn new() -> Self { + [123456, 654321] + } + fn different() -> Self { + [654321, 123456] + } +} + +pub struct Struct { + x: u64, +} + +impl Eq for Struct { + fn eq(self, other: Self) -> bool { + self.x == other.x + } +} + +impl TestInstance for Struct { + fn new() -> Self { + Self { x: 98765 } + } + fn different() -> Self { + Self { x: 56789 } + } +} + +pub struct EmptyStruct { } + +impl Eq for EmptyStruct { + fn eq(self, other: Self) -> bool { + true + } +} + +impl TestInstance for EmptyStruct { + fn new() -> Self { + EmptyStruct { } + } + fn different() -> Self { + EmptyStruct { } + } +} + +pub enum Enum { + A: u64, +} + +impl Eq for Enum { + fn eq(self, other: Self) -> bool { + match (self, other) { + (Enum::A(l), Enum::A(r)) => l == r, + } + } +} + +impl TestInstance for Enum { + fn new() -> Self { + Self::A(123456) + } + fn different() -> Self { + Self::A(654321) + } +} + +impl Eq for (u8, u32) { + fn eq(self, other: Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +impl TestInstance for (u8, u32) { + fn new() -> Self { + (123, 12345) + } + fn different() -> Self { + (223, 54321) + } +} + +impl TestInstance for b256 { + fn new() -> Self { + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 + } + fn different() -> Self { + 0x0202020405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1f1a20 + } +} + +impl TestInstance for raw_ptr { + fn new() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + null_ptr.add::(42) + } + fn different() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + null_ptr.add::(42*2) + } +} + +impl TestInstance for raw_slice { + fn new() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + std::raw_slice::from_parts::(null_ptr, 42) + } + fn different() -> Self { + let null_ptr = asm() { zero: raw_ptr }; + + std::raw_slice::from_parts::(null_ptr, 42*2) + } +} + +impl Eq for raw_slice { + fn eq(self, other: Self) -> bool { + self.ptr() == other.ptr() && self.number_of_bytes() == other.number_of_bytes() + } +} + +impl TestInstance for () { + fn new() -> Self { + () + } + fn different() -> Self { + () + } +} + +impl Eq for () { + fn eq(self, other: Self) -> bool { + true + } +} + +impl TestInstance for [u64;0] { + fn new() -> Self { + [] + } + fn different() -> Self { + [] + } +} + +impl Eq for [u64;0] { + fn eq(self, other: Self) -> bool { + true + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/src/main.sw new file mode 100644 index 00000000000..76d06494263 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/src/main.sw @@ -0,0 +1,196 @@ +script; + +mod impls; +use impls::*; +use core::ops::Eq; + +#[inline(always)] +fn dereference_array() + where T: TestInstance + Eq +{ + let mut array = [T::new(), T::different()]; + let r_array = &array; + let r_r_array = &r_array; + let r_r_r_array = &r_r_array; + + assert(r_array[0] == array[0]); + assert(r_array[1] == array[1]); + + assert(r_r_array[0] == array[0]); + assert(r_r_array[1] == array[1]); + + assert(r_r_r_array[0] == array[0]); + assert(r_r_r_array[1] == array[1]); + + array[0] = T::different(); + array[1] = T::new(); + + assert(r_array[0] == array[0]); + assert(r_array[1] == array[1]); + + assert(r_r_array[0] == array[0]); + assert(r_r_array[1] == array[1]); + + assert(r_r_r_array[0] == array[0]); + assert(r_r_r_array[1] == array[1]); +} + +#[inline(never)] +fn dereference_array_not_inlined() + where T: TestInstance + Eq +{ + dereference_array::() +} + +#[inline(always)] +fn dereference_array_of_refs() + where T: TestInstance + Eq +{ + let mut array = [T::new(), T::different()]; + let mut array_of_refs = [&array, &array]; + + let r_array_of_refs = &array_of_refs; + let r_r_array_of_refs = &r_array_of_refs; + let r_r_r_array_of_refs = &r_r_array_of_refs; + + assert(r_array_of_refs[0][0] == array_of_refs[0][0]); + assert(r_array_of_refs[0][0] == array[0]); + assert(r_array_of_refs[1][1] == array_of_refs[1][1]); + assert(r_array_of_refs[1][1] == array[1]); + + assert(r_r_array_of_refs[0][0] == array_of_refs[0][0]); + assert(r_r_array_of_refs[0][0] == array[0]); + assert(r_r_array_of_refs[1][1] == array_of_refs[1][1]); + assert(r_r_array_of_refs[1][1] == array[1]); + + assert(r_r_r_array_of_refs[0][0] == array_of_refs[0][0]); + assert(r_r_r_array_of_refs[0][0] == array[0]); + assert(r_r_r_array_of_refs[1][1] == array_of_refs[1][1]); + assert(r_r_r_array_of_refs[1][1] == array[1]); + + array[0] = T::different(); + array[1] = T::new(); + + assert(r_array_of_refs[0][0] == array_of_refs[0][0]); + assert(r_array_of_refs[0][0] == array[0]); + assert(r_array_of_refs[1][1] == array_of_refs[1][1]); + assert(r_array_of_refs[1][1] == array[1]); + + assert(r_r_array_of_refs[0][0] == array_of_refs[0][0]); + assert(r_r_array_of_refs[0][0] == array[0]); + assert(r_r_array_of_refs[1][1] == array_of_refs[1][1]); + assert(r_r_array_of_refs[1][1] == array[1]); + + assert(r_r_r_array_of_refs[0][0] == array_of_refs[0][0]); + assert(r_r_r_array_of_refs[0][0] == array[0]); + assert(r_r_r_array_of_refs[1][1] == array_of_refs[1][1]); + assert(r_r_r_array_of_refs[1][1] == array[1]); + + let r = & & & & &[& & &array, & & &array, & & &array]; + + let mut j = 0; + let mut k = 0; + while j < 3 { + while k < 2 { + assert(r[j][k] == array[k]); + k += 1; + } + j += 1; + } +} + +#[inline(never)] +fn dereference_array_of_refs_not_inlined() + where T: TestInstance + Eq +{ + dereference_array_of_refs::() +} + +#[inline(never)] +fn test_all_inlined() { + dereference_array::<()>(); + dereference_array::(); + dereference_array::(); + dereference_array::(); + dereference_array::(); + dereference_array::(); + dereference_array::(); + dereference_array::<[u64;2]>(); + dereference_array::<[u64;0]>(); + dereference_array::(); + dereference_array::(); + dereference_array::(); + dereference_array::(); + dereference_array::(); + dereference_array::<(u8, u32)>(); + dereference_array::(); + dereference_array::(); + dereference_array::(); + + dereference_array_of_refs::<()>(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::<[u64;2]>(); + dereference_array_of_refs::<[u64;0]>(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::<(u8, u32)>(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); + dereference_array_of_refs::(); +} + +#[inline(never)] +fn test_not_inlined() { + dereference_array_not_inlined::<()>(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::<[u64;2]>(); + dereference_array_not_inlined::<[u64;0]>(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::<(u8, u32)>(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + dereference_array_not_inlined::(); + + dereference_array_of_refs_not_inlined::<()>(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::<[u64;2]>(); + dereference_array_of_refs_not_inlined::<[u64;0]>(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::<(u8, u32)>(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); + dereference_array_of_refs_not_inlined::(); +} + +fn main() -> u64 { + test_all_inlined(); + test_not_inlined(); + + 42 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/test.toml new file mode 100644 index 00000000000..6e4c2a8f967 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_index/test.toml @@ -0,0 +1,4 @@ +category = "run" +expected_result = { action = "return", value = 42 } +validate_abi = true +expected_warnings = 1000 # TODO-IG: Set the proper number diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_star/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_star/src/main.sw index 5c8c8eb593a..91771615317 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_star/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/dereferencing_operator_star/src/main.sw @@ -258,10 +258,7 @@ fn test_all_inlined() { dereference_array::(); dereference_array::(); dereference_array::(); - // TODO-IG: Uncomment once this issue is solved: https://github.com/FuelLabs/sway/issues/5377 - // thread 'main' panicked at sway-ir/src/optimize/sroa.rs:174:25: - // assertion failed: ty.is_aggregate(context) - //dereference_array::(); + dereference_array::(); dereference_array::<[u64;2]>(); dereference_array::<[u64;0]>(); dereference_array::(); @@ -280,8 +277,7 @@ fn test_all_inlined() { dereference_tuple::(); dereference_tuple::(); dereference_tuple::(); - // TODO-IG: Uncomment once this issue is solved: https://github.com/FuelLabs/sway/issues/5377 - //dereference_tuple::(); + dereference_tuple::(); dereference_tuple::<[u64;2]>(); dereference_tuple::<[u64;0]>(); dereference_tuple::(); @@ -300,8 +296,7 @@ fn test_all_inlined() { dereference_struct::(); dereference_struct::(); dereference_struct::(); - // TODO-IG: Uncomment once this issue is solved: https://github.com/FuelLabs/sway/issues/5377 - //dereference_struct::(); + dereference_struct::(); dereference_struct::<[u64;2]>(); dereference_struct::<[u64;0]>(); dereference_struct::(); @@ -361,8 +356,7 @@ fn test_not_inlined() { dereference_array_not_inlined::(); dereference_array_not_inlined::(); dereference_array_not_inlined::(); - // TODO-IG: Uncomment once this issue is solved: https://github.com/FuelLabs/sway/issues/5377 - //dereference_array_not_inlined::(); + dereference_array_not_inlined::(); dereference_array_not_inlined::<[u64;2]>(); dereference_array_not_inlined::<[u64;0]>(); dereference_array_not_inlined::(); @@ -381,8 +375,7 @@ fn test_not_inlined() { dereference_tuple_not_inlined::(); dereference_tuple_not_inlined::(); dereference_tuple_not_inlined::(); - // TODO-IG: Uncomment once this issue is solved: https://github.com/FuelLabs/sway/issues/5377 - //dereference_tuple_not_inlined::(); + dereference_tuple_not_inlined::(); dereference_tuple_not_inlined::<[u64;2]>(); dereference_tuple_not_inlined::<[u64;0]>(); dereference_tuple_not_inlined::(); @@ -401,8 +394,7 @@ fn test_not_inlined() { dereference_struct_not_inlined::(); dereference_struct_not_inlined::(); dereference_struct_not_inlined::(); - // TODO-IG: Uncomment once this issue is solved: https://github.com/FuelLabs/sway/issues/5377 - //dereference_struct_not_inlined::(); + dereference_struct_not_inlined::(); dereference_struct_not_inlined::<[u64;2]>(); dereference_struct_not_inlined::<[u64;0]>(); dereference_struct_not_inlined::(); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/references_in_aggregates/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/references_in_aggregates/src/main.sw index 667693134b6..0f5b38c1e78 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/references_in_aggregates/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/references_in_aggregates/src/main.sw @@ -59,6 +59,10 @@ fn in_structs() { assert((*&array)[0] == (*a.r_array)[0]); assert((*&array)[1] == (*a.r_array)[1]); assert((*&array)[2] == (*a.r_array)[2]); + + assert((&array)[0] == a.r_array[0]); + assert((&array)[1] == a.r_array[1]); + assert((&array)[2] == a.r_array[2]); let b_r_a_ptr = asm(r: b.r_a) { r: raw_ptr }; @@ -81,6 +85,10 @@ fn in_structs() { assert((*((*(*b.r_array)[0]).r_array))[0] == (*a.r_array)[0]); assert((*((*(*b.r_array)[0]).r_array))[1] == (*a.r_array)[1]); assert((*((*(*b.r_array)[0]).r_array))[2] == (*a.r_array)[2]); + + assert(((*(b.r_array)[0]).r_array)[0] == a.r_array[0]); + assert(((*(b.r_array)[0]).r_array)[1] == a.r_array[1]); + assert(((*(b.r_array)[0]).r_array)[2] == a.r_array[2]); } #[inline(never)] @@ -121,6 +129,10 @@ fn in_enums() { assert((*(*local_r_a).r_array)[0] == (*&array)[0]); assert((*(*local_r_a).r_array)[1] == (*&array)[1]); assert((*(*local_r_a).r_array)[2] == (*&array)[2]); + + assert((*local_r_a).r_array[0] == (&array)[0]); + assert((*local_r_a).r_array[1] == (&array)[1]); + assert((*local_r_a).r_array[2] == (&array)[2]); } _ => assert(false), } @@ -137,6 +149,10 @@ fn in_enums() { assert((*(*(*(*local_r_b).r_array)[0]).r_array)[0] == (*&array)[0]); assert((*(*(*(*local_r_b).r_array)[0]).r_array)[1] == (*&array)[1]); assert((*(*(*(*local_r_b).r_array)[0]).r_array)[2] == (*&array)[2]); + + assert((*(*local_r_b).r_array[0]).r_array[0] == (&array)[0]); + assert((*(*local_r_b).r_array[0]).r_array[1] == (&array)[1]); + assert((*(*local_r_b).r_array[0]).r_array[2] == (&array)[2]); } _ => assert(false), } @@ -172,6 +188,10 @@ fn in_arrays() { assert((*(*((*(*arr[1]).r_array)[2])).r_array)[0] == (*&array)[0]); assert((*(*((*(*arr[1]).r_array)[2])).r_array)[1] == (*&array)[1]); assert((*(*((*(*arr[1]).r_array)[2])).r_array)[2] == (*&array)[2]); + + assert((*((*arr[1]).r_array[2])).r_array[0] == (&array)[0]); + assert((*((*arr[1]).r_array[2])).r_array[1] == (&array)[1]); + assert((*((*arr[1]).r_array[2])).r_array[2] == (&array)[2]); } #[inline(never)] @@ -204,6 +224,10 @@ fn in_tuples() { assert((*(*((*(*tuple.1).r_array)[2])).r_array)[0] == (*&array)[0]); assert((*(*((*(*tuple.1).r_array)[2])).r_array)[1] == (*&array)[1]); assert((*(*((*(*tuple.1).r_array)[2])).r_array)[2] == (*&array)[2]); + + assert((*((*tuple.1).r_array[2])).r_array[0] == (&array)[0]); + assert((*((*tuple.1).r_array[2])).r_array[1] == (&array)[1]); + assert((*((*tuple.1).r_array[2])).r_array[2] == (&array)[2]); } #[inline(never)] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_function_parameters/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_function_parameters/src/main.sw index 95f38037aae..40f0e9ec480 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_function_parameters/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_function_parameters/src/main.sw @@ -64,6 +64,9 @@ fn array_parameter(p: [u64;2]) { assert(p_ptr.read::<[u64;2]>() == p); assert(*r_p_1 == *r_p_2); + + assert(r_p_1[0] == r_p_2[0]); + assert(r_p_1[1] == r_p_2[1]); } #[inline(never)] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_parts_of_aggregates/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_parts_of_aggregates/src/main.sw index 8a7cd641031..00bc48171b8 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_parts_of_aggregates/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/references/referencing_parts_of_aggregates/src/main.sw @@ -48,7 +48,7 @@ impl C { } } -// TODO-IG: Add tests for accessing via reference chains once dereferencing operators `[]` and `.` are implemented. +// TODO-IG: Add tests for accessing via reference chains once dereferencing operator `.` is implemented. #[inline(always)] fn struct_fields() { @@ -128,7 +128,6 @@ fn tuple_fields_not_inlined() { #[inline(always)] fn array_elements() { - // TODO-IG: Add tests for arrays of references once dereferencing operator `[]` is implemented. let x1 = 111u8; let x2 = 222u8; @@ -159,6 +158,18 @@ fn array_elements() { assert(*r_a3_a2_a1_x1 == x1); assert(*r_a3_a2_a1_x2 == x2); + + let a_r1 = [&x1, &x2]; + let a_r2 = [&a_r1, &a_r1]; + let a_r3 = [&a_r2, &a_r2]; + + let r_a_r3_a_r2_a_r1_x1: & &u8 = &a_r3[0][1][0]; + assert(**r_a_r3_a_r2_a_r1_x1 == x1); + + assert(*(&a_r3)[0][0][0] == x1); + assert(*(& &a_r3)[0][1][0] == x1); + assert(*(& & &a_r3)[0][0][1] == x2); + assert(*(& & & &a_r3)[0][1][1] == x2); } #[inline(never)]