diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 9a95830648..9939450562 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -2192,6 +2192,22 @@ func (v *ArrayValue) FirstIndex(interpreter *Interpreter, locationRange Location return NilOptionalValue } +func (v *ArrayValue) Reverse(interpreter *Interpreter, locationRange LocationRange) Value { + count := v.Count() + + for leftIndex := 0; leftIndex < count/2; leftIndex++ { + rightIndex := count - leftIndex - 1 + + leftValue := v.Get(interpreter, locationRange, leftIndex) + rightValue := v.Get(interpreter, locationRange, rightIndex) + + v.Set(interpreter, locationRange, leftIndex, rightValue) + v.Set(interpreter, locationRange, rightIndex, leftValue) + } + + return Void +} + func (v *ArrayValue) Contains( interpreter *Interpreter, locationRange LocationRange, @@ -2371,6 +2387,18 @@ func (v *ArrayValue) GetMember(interpreter *Interpreter, locationRange LocationR }, ) + case sema.ArrayTypeReverseFunctionName: + return NewHostFunctionValue( + interpreter, + sema.ArrayReverseFunctionType, + func(invocation Invocation) Value { + return v.Reverse( + invocation.Interpreter, + invocation.LocationRange, + ) + }, + ) + case "contains": return NewHostFunctionValue( interpreter, diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 2e158dfb56..e8a05b7b13 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -1714,6 +1714,12 @@ Returns the index of the first element matching the given object in the array, n Available if the array element type is not resource-kinded and equatable. ` +const ArrayTypeReverseFunctionName = "reverse" + +const arrayTypeReverseFunctionDocString = ` +Reverses the elements of the array. +` + const arrayTypeContainsFunctionDocString = ` Returns true if the given object is in the array ` @@ -1865,6 +1871,18 @@ func getArrayMembers(arrayType ArrayType) map[string]MemberResolver { ) }, }, + ArrayTypeReverseFunctionName: { + Kind: common.DeclarationKindFunction, + Resolve: func(memoryGauge common.MemoryGauge, identifier string, targetRange ast.Range, report func(error)) *Member { + return NewPublicFunctionMember( + memoryGauge, + arrayType, + identifier, + ArrayReverseFunctionType, + arrayTypeReverseFunctionDocString, + ) + }, + }, } // TODO: maybe still return members but report a helpful error? @@ -2116,6 +2134,11 @@ func ArrayFirstIndexFunctionType(elementType Type) *FunctionType { ), } } + +var ArrayReverseFunctionType *FunctionType = &FunctionType{ + ReturnTypeAnnotation: NewTypeAnnotation(VoidType), +} + func ArrayContainsFunctionType(elementType Type) *FunctionType { return &FunctionType{ Parameters: []Parameter{ diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index 9bcf610093..9774b4dc8b 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1078,6 +1078,36 @@ func TestCheckInvalidResourceFirstIndex(t *testing.T) { assert.IsType(t, &sema.ResourceLossError{}, errs[2]) } +func TestCheckArrayReverse(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun test() { + let x = [1, 2, 3] + x.reverse() + } + `) + + require.NoError(t, err) +} + +func TestCheckInvalidArrayReverse(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun test() { + let x = [1, 2, 3] + x.reverse(100) + } + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) +} + func TestCheckArrayContains(t *testing.T) { t.Parallel() diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 5e56e230bd..51c72eb338 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -10395,6 +10395,67 @@ func TestInterpretArrayFirstIndexDoesNotExist(t *testing.T) { ) } +func TestInterpretArrayReverse(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + let xs = [1, 2, 3, 100, 200] + let ys = [100, 467, 297, 23] + + fun reversexs() { + return xs.reverse() + } + + fun reverseys() { + return ys.reverse() + } + `) + + _, err := inter.Invoke("reversexs") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(200), + interpreter.NewUnmeteredIntValueFromInt64(100), + interpreter.NewUnmeteredIntValueFromInt64(3), + interpreter.NewUnmeteredIntValueFromInt64(2), + interpreter.NewUnmeteredIntValueFromInt64(1), + ), + inter.Globals.Get("xs").GetValue(), + ) + + _, err = inter.Invoke("reverseys") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(23), + interpreter.NewUnmeteredIntValueFromInt64(297), + interpreter.NewUnmeteredIntValueFromInt64(467), + interpreter.NewUnmeteredIntValueFromInt64(100), + ), + inter.Globals.Get("ys").GetValue(), + ) +} + func TestInterpretOptionalReference(t *testing.T) { t.Parallel()