Skip to content

Commit

Permalink
Support looping storage references
Browse files Browse the repository at this point in the history
  • Loading branch information
SupunS committed Oct 17, 2023
1 parent 00dd3fd commit f13e9bd
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 14 deletions.
49 changes: 36 additions & 13 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -19844,6 +19844,7 @@ var _ TypeIndexableValue = &StorageReferenceValue{}
var _ MemberAccessibleValue = &StorageReferenceValue{}
var _ AuthorizedValue = &StorageReferenceValue{}
var _ ReferenceValue = &StorageReferenceValue{}
var _ IterableValue = &StorageReferenceValue{}

func NewUnmeteredStorageReferenceValue(
authorization Authorization,
Expand Down Expand Up @@ -20196,6 +20197,37 @@ func (*StorageReferenceValue) DeepRemove(_ *Interpreter) {

func (*StorageReferenceValue) isReference() {}

func (v *StorageReferenceValue) Iterator(interpreter *Interpreter, locationRange LocationRange) ValueIterator {
referencedValue := v.mustReferencedValue(interpreter, locationRange)
return referenceValueIterator(interpreter, referencedValue, v.BorrowedType, locationRange)
}

func referenceValueIterator(
interpreter *Interpreter,
referencedValue Value,
borrowedType sema.Type,
locationRange LocationRange,
) ValueIterator {
referencedIterable, ok := referencedValue.(IterableValue)
if !ok {
panic(errors.NewUnreachableError())
}

referencedValueIterator := referencedIterable.Iterator(interpreter, locationRange)

referencedType, ok := borrowedType.(sema.ValueIndexableType)
if !ok {
panic(errors.NewUnreachableError())
}

elementType := referencedType.ElementType(false)

return ReferenceValueIterator{
iterator: referencedValueIterator,
elementType: elementType,
}
}

// EphemeralReferenceValue

type EphemeralReferenceValue struct {
Expand Down Expand Up @@ -20544,21 +20576,11 @@ func (*EphemeralReferenceValue) isReference() {}

func (v *EphemeralReferenceValue) Iterator(interpreter *Interpreter, locationRange LocationRange) ValueIterator {
referencedValue := v.MustReferencedValue(interpreter, locationRange)
referenceValueIterator := referencedValue.(IterableValue).Iterator(interpreter, locationRange)

referencedType, ok := v.BorrowedType.(sema.ValueIndexableType)
if !ok {
panic(errors.NewUnreachableError())
}

elementType := referencedType.ElementType(false)

return ReferenceValueIterator{
iterator: referenceValueIterator,
elementType: elementType,
}
return referenceValueIterator(interpreter, referencedValue, v.BorrowedType, locationRange)
}

// ReferenceValueIterator

type ReferenceValueIterator struct {
iterator ValueIterator
elementType sema.Type
Expand All @@ -20573,6 +20595,7 @@ func (i ReferenceValueIterator) Next(interpreter *Interpreter) Value {
return nil
}

// For non-primitive values, return a reference.
if i.elementType.ContainFieldsOrElements() {
return NewEphemeralReferenceValue(interpreter, UnauthorizedAccess, element, i.elementType)
}
Expand Down
102 changes: 101 additions & 1 deletion runtime/tests/interpreter/for_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package interpreter_test

import (
"github.com/onflow/cadence/runtime/sema"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -295,7 +296,7 @@ func TestInterpretForStatementCapturing(t *testing.T) {
)
}

func TestInterpretReferencesInForLoop(t *testing.T) {
func TestInterpretEphemeralReferencesInForLoop(t *testing.T) {

t.Parallel()

Expand Down Expand Up @@ -386,3 +387,102 @@ func TestInterpretReferencesInForLoop(t *testing.T) {
require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{})
})
}

func TestInterpretStorageReferencesInForLoop(t *testing.T) {

t.Parallel()

t.Run("Primitive array", func(t *testing.T) {
t.Parallel()

address := interpreter.NewUnmeteredAddressValueFromBytes([]byte{42})

inter, _ := testAccount(t, address, true, nil, `
fun test() {
var array = ["Hello", "World", "Foo", "Bar"]
account.storage.save(array, to: /storage/array)
let arrayRef = account.storage.borrow<&[String]>(from: /storage/array)!
for element in arrayRef {
let e: String = element // Must be the concrete string
}
}`, sema.Config{})

_, err := inter.Invoke("test")
require.NoError(t, err)
})

t.Run("Struct array", func(t *testing.T) {
t.Parallel()

address := interpreter.NewUnmeteredAddressValueFromBytes([]byte{42})

inter, _ := testAccount(t, address, true, nil, `
struct Foo{}
fun test() {
var array = [Foo(), Foo()]
account.storage.save(array, to: /storage/array)
let arrayRef = account.storage.borrow<&[Foo]>(from: /storage/array)!
for element in arrayRef {
let e: &Foo = element // Must be a reference
}
}`, sema.Config{})

_, err := inter.Invoke("test")
require.NoError(t, err)
})

t.Run("Resource array", func(t *testing.T) {
t.Parallel()

address := interpreter.NewUnmeteredAddressValueFromBytes([]byte{42})

inter, _ := testAccount(t, address, true, nil, `
resource Foo{}
fun test() {
var array <- [ <- create Foo(), <- create Foo()]
account.storage.save(<- array, to: /storage/array)
let arrayRef = account.storage.borrow<&[Foo]>(from: /storage/array)!
for element in arrayRef {
let e: &Foo = element // Must be a reference
}
}`, sema.Config{})

_, err := inter.Invoke("test")
require.NoError(t, err)
})

t.Run("Moved resource array", func(t *testing.T) {
t.Parallel()

address := interpreter.NewUnmeteredAddressValueFromBytes([]byte{42})

inter, _ := testAccount(t, address, true, nil, `
resource Foo{}
fun test() {
var array <- [ <- create Foo(), <- create Foo()]
account.storage.save(<- array, to: /storage/array)
let arrayRef = account.storage.borrow<&[Foo]>(from: /storage/array)!
let movedArray <- account.storage.load<@[Foo]>(from: /storage/array)!
for element in arrayRef {
let e: &Foo = element // Must be a reference
}
destroy movedArray
}`, sema.Config{})

_, err := inter.Invoke("test")
require.ErrorAs(t, err, &interpreter.DereferenceError{})
})
}

0 comments on commit f13e9bd

Please sign in to comment.