From efb1b4aedd5781211d04ff585998872420bbaad1 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Tue, 11 Jul 2023 22:59:28 +0530 Subject: [PATCH] Introduce reversed in Array type --- runtime/interpreter/value.go | 45 +++++ runtime/sema/type.go | 40 ++++ .../tests/checker/arrays_dictionaries_test.go | 49 +++++ runtime/tests/interpreter/interpreter_test.go | 175 ++++++++++++++++++ 4 files changed, 309 insertions(+) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 2ef910c506..e028c31ecd 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -2411,6 +2411,20 @@ func (v *ArrayValue) GetMember(interpreter *Interpreter, locationRange LocationR ) }, ) + + case sema.ArrayTypeReversedFunctionName: + return NewHostFunctionValue( + interpreter, + sema.ArrayReversedFunctionType( + v.SemaType(interpreter).ElementType(false), + ), + func(invocation Invocation) Value { + return v.Reversed( + invocation.Interpreter, + invocation.LocationRange, + ) + }, + ) } return nil @@ -2870,6 +2884,37 @@ func (v *ArrayValue) Slice( ) } +func (v *ArrayValue) Reversed( + interpreter *Interpreter, + locationRange LocationRange, +) Value { + count := v.Count() + idx := count - 1 + + return NewArrayValueWithIterator( + interpreter, + NewVariableSizedStaticType(interpreter, v.Type.ElementType()), + common.ZeroAddress, + uint64(count), + func() Value { + if idx < 0 { + return nil + } + + value := v.Get(interpreter, locationRange, idx) + idx-- + + return value.Transfer( + interpreter, + locationRange, + atree.Address{}, + false, + nil, + ) + }, + ) +} + // NumberValue type NumberValue interface { ComparableValue diff --git a/runtime/sema/type.go b/runtime/sema/type.go index e2746a10f0..068c2cc2de 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -1788,6 +1788,12 @@ It does not modify the original array. If either of the parameters are out of the bounds of the array, or the indices are invalid (` + "`from > upTo`" + `), then the function will fail. ` +const ArrayTypeReversedFunctionName = "reversed" + +const arrayTypeReversedFunctionDocString = ` +Returns a new array with contents in the reversed order. +` + func getArrayMembers(arrayType ArrayType) map[string]MemberResolver { members := map[string]MemberResolver{ @@ -1881,6 +1887,31 @@ func getArrayMembers(arrayType ArrayType) map[string]MemberResolver { ) }, }, + ArrayTypeReversedFunctionName: { + Kind: common.DeclarationKindFunction, + Resolve: func(memoryGauge common.MemoryGauge, identifier string, targetRange ast.Range, report func(error)) *Member { + elementType := arrayType.ElementType(false) + + // It is impossible for a resource to be present in two arrays. + if elementType.IsResourceType() { + report( + &InvalidResourceArrayMemberError{ + Name: identifier, + DeclarationKind: common.DeclarationKindFunction, + Range: targetRange, + }, + ) + } + + return NewPublicFunctionMember( + memoryGauge, + arrayType, + identifier, + ArrayReversedFunctionType(elementType), + arrayTypeReversedFunctionDocString, + ) + }, + }, } // TODO: maybe still return members but report a helpful error? @@ -2193,6 +2224,15 @@ func ArraySliceFunctionType(elementType Type) *FunctionType { } } +func ArrayReversedFunctionType(elementType Type) *FunctionType { + return &FunctionType{ + Parameters: []Parameter{}, + ReturnTypeAnnotation: NewTypeAnnotation(&VariableSizedType{ + Type: elementType, + }), + } +} + // VariableSizedType is a variable sized array type type VariableSizedType struct { Type Type diff --git a/runtime/tests/checker/arrays_dictionaries_test.go b/runtime/tests/checker/arrays_dictionaries_test.go index 9bcf610093..442bdf896b 100644 --- a/runtime/tests/checker/arrays_dictionaries_test.go +++ b/runtime/tests/checker/arrays_dictionaries_test.go @@ -1078,6 +1078,55 @@ func TestCheckInvalidResourceFirstIndex(t *testing.T) { assert.IsType(t, &sema.ResourceLossError{}, errs[2]) } +func TestCheckArrayReversed(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun test() { + let x = [1, 2, 3] + let y = x.reversed() + } + `) + + require.NoError(t, err) +} + +func TestCheckArrayReversedInvalidArgs(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun test() { + let x = [1, 2, 3] + let y = x.reversed(100) + } + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.ArgumentCountError{}, errs[0]) +} + +func TestCheckResourceArrayReversedInvalid(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource X {} + + fun test(): @[X] { + let xs <- [<-create X()] + return <-xs.reversed() + } + `) + + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InvalidResourceArrayMemberError{}, errs[0]) + assert.IsType(t, &sema.ResourceLossError{}, errs[1]) +} + 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..e208726d32 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -10395,6 +10395,181 @@ func TestInterpretArrayFirstIndexDoesNotExist(t *testing.T) { ) } +func TestInterpretArrayReversed(t *testing.T) { + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + let xs = [1, 2, 3, 100, 200] + let ys = [100, 467, 297, 23] + let emptyVals: [Int] = [] + + fun reversedxs(): [Int] { + return xs.reversed() + } + fun originalxs(): [Int] { + return xs + } + + fun reversedys(): [Int] { + return ys.reversed() + } + fun originalys(): [Int] { + return ys + } + + fun reverseempty(): [Int] { + return emptyVals.reversed() + } + fun originalempty(): [Int] { + return emptyVals + } + + pub struct TestStruct { + pub var test: Int + + init(_ t: Int) { + self.test = t + } + } + + let sa = [TestStruct(1), TestStruct(2), TestStruct(3)] + + fun reversedsa(): [Int] { + let sa_rev = sa.reversed() + + let res: [Int] = []; + for s in sa_rev { + res.append(s.test) + } + + return res + } + fun originalsa(): [Int] { + let res: [Int] = []; + for s in sa { + res.append(s.test) + } + + return res + } + `) + + runValidCase := func(t *testing.T, reverseFuncName, originalFuncName string, reversedArray, originalArray *interpreter.ArrayValue) { + val, err := inter.Invoke(reverseFuncName) + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + reversedArray, + val, + ) + + origVal, err := inter.Invoke(originalFuncName) + require.NoError(t, err) + + // Original array remains unchanged + AssertValuesEqual( + t, + inter, + originalArray, + origVal, + ) + } + + runValidCase(t, "reverseempty", "originalempty", + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + ), interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + )) + + runValidCase(t, "reversedxs", "originalxs", + 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), + ), interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(1), + interpreter.NewUnmeteredIntValueFromInt64(2), + interpreter.NewUnmeteredIntValueFromInt64(3), + interpreter.NewUnmeteredIntValueFromInt64(100), + interpreter.NewUnmeteredIntValueFromInt64(200), + )) + + runValidCase(t, "reversedys", "originalys", + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(23), + interpreter.NewUnmeteredIntValueFromInt64(297), + interpreter.NewUnmeteredIntValueFromInt64(467), + interpreter.NewUnmeteredIntValueFromInt64(100), + ), interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(100), + interpreter.NewUnmeteredIntValueFromInt64(467), + interpreter.NewUnmeteredIntValueFromInt64(297), + interpreter.NewUnmeteredIntValueFromInt64(23), + )) + + runValidCase(t, "reversedsa", "originalsa", + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(3), + interpreter.NewUnmeteredIntValueFromInt64(2), + interpreter.NewUnmeteredIntValueFromInt64(1), + ), interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeInt, + }, + common.ZeroAddress, + interpreter.NewUnmeteredIntValueFromInt64(1), + interpreter.NewUnmeteredIntValueFromInt64(2), + interpreter.NewUnmeteredIntValueFromInt64(3), + )) +} + func TestInterpretOptionalReference(t *testing.T) { t.Parallel()