diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index db901c20eb..64620efbe4 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -19844,6 +19844,7 @@ var _ TypeIndexableValue = &StorageReferenceValue{} var _ MemberAccessibleValue = &StorageReferenceValue{} var _ AuthorizedValue = &StorageReferenceValue{} var _ ReferenceValue = &StorageReferenceValue{} +var _ IterableValue = &StorageReferenceValue{} func NewUnmeteredStorageReferenceValue( authorization Authorization, @@ -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 { @@ -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 @@ -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) } diff --git a/runtime/tests/interpreter/for_test.go b/runtime/tests/interpreter/for_test.go index cfea4e4547..f1440f564a 100644 --- a/runtime/tests/interpreter/for_test.go +++ b/runtime/tests/interpreter/for_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/sema" . "github.com/onflow/cadence/runtime/tests/utils" "github.com/onflow/cadence/runtime/interpreter" @@ -295,7 +296,7 @@ func TestInterpretForStatementCapturing(t *testing.T) { ) } -func TestInterpretReferencesInForLoop(t *testing.T) { +func TestInterpretEphemeralReferencesInForLoop(t *testing.T) { t.Parallel() @@ -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{}) + }) +}