diff --git a/go.mod b/go.mod index a4ae46f0af..e94021232c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/kr/pretty v0.3.1 github.com/leanovate/gopter v0.2.9 github.com/logrusorgru/aurora/v4 v4.0.0 - github.com/onflow/atree v0.6.1-0.20240429171449-cb486ceb1f9c + github.com/onflow/atree v0.7.0-rc.2 github.com/rivo/uniseg v0.4.4 github.com/schollz/progressbar/v3 v3.13.1 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index 761161345a..31a4dd9659 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvr github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/onflow/atree v0.6.1-0.20240429171449-cb486ceb1f9c h1:Ol+qFATYiS7LfwQQKBjfLJ8z6VwzZehVrYH1JI2ssUU= -github.com/onflow/atree v0.6.1-0.20240429171449-cb486ceb1f9c/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= +github.com/onflow/atree v0.7.0-rc.2 h1:mZmVrl/zPlfI44EjV3FdR2QwIqT8nz1sCONUBFcML/U= +github.com/onflow/atree v0.7.0-rc.2/go.mod h1:xvP61FoOs95K7IYdIYRnNcYQGf4nbF/uuJ0tHf4DRuM= github.com/onflow/crypto v0.25.0 h1:BeWbLsh3ZD13Ej+Uky6kg1PL1ZIVBDVX+2MVBNwqddg= github.com/onflow/crypto v0.25.0/go.mod h1:C8FbaX0x8y+FxWjbkHy0Q4EASCDR9bSPWZqlpCLYyVI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= diff --git a/migrations/migration.go b/migrations/migration.go index 43cae56f2e..00e6896c1a 100644 --- a/migrations/migration.go +++ b/migrations/migration.go @@ -86,7 +86,7 @@ func (m *StorageMigration) WithErrorStacktrace(stacktraceEnabled bool) *StorageM } func (m *StorageMigration) Commit() error { - return m.storage.Commit(m.interpreter, false) + return m.storage.NondeterministicCommit(m.interpreter, false) } func (m *StorageMigration) Migrate(migrator StorageMapKeyMigrator) { diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 384641e701..63caab8ba5 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1188,7 +1188,10 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( var initializerFunction FunctionValue if declaration.Kind() == common.CompositeKindEvent { - // Initializer is a static function. + // Initializer could ideally be a bound function. + // However, since it is created and being called here itself, and + // because it is never passed around, it is OK to just create as static function + // without the bound-function wrapper. initializerFunction = NewStaticHostFunctionValue( declarationInterpreter, initializerType, diff --git a/runtime/interpreter/value_account_accountcapabilities.go b/runtime/interpreter/value_account_accountcapabilities.go index 537e0bf02e..df38ffba5c 100644 --- a/runtime/interpreter/value_account_accountcapabilities.go +++ b/runtime/interpreter/value_account_accountcapabilities.go @@ -27,9 +27,9 @@ import ( // Account.AccountCapabilities -var Account_AccountCapabilitiesTypeID = sema.Account_AccountCapabilitiesType.ID() -var Account_AccountCapabilitiesStaticType StaticType = PrimitiveStaticTypeAccount_AccountCapabilities // unmetered -var Account_AccountCapabilitiesFieldNames []string = nil +var account_AccountCapabilitiesTypeID = sema.Account_AccountCapabilitiesType.ID() +var account_AccountCapabilitiesStaticType StaticType = PrimitiveStaticTypeAccount_AccountCapabilities // unmetered +var account_AccountCapabilitiesFieldNames []string = nil func NewAccountAccountCapabilitiesValue( gauge common.MemoryGauge, @@ -53,9 +53,9 @@ func NewAccountAccountCapabilitiesValue( accountCapabilities := NewSimpleCompositeValue( gauge, - Account_AccountCapabilitiesTypeID, - Account_AccountCapabilitiesStaticType, - Account_AccountCapabilitiesFieldNames, + account_AccountCapabilitiesTypeID, + account_AccountCapabilitiesStaticType, + account_AccountCapabilitiesFieldNames, nil, nil, nil, diff --git a/runtime/repl.go b/runtime/repl.go index 11a2e8c12e..9d1c213aac 100644 --- a/runtime/repl.go +++ b/runtime/repl.go @@ -301,7 +301,7 @@ func (r *REPL) Accept(code []byte, eval bool) (inputIsComplete bool, err error) var expressionType sema.Type expressionStatement, isExpression := statement.(*ast.ExpressionStatement) if isExpression { - expressionType = r.checker.VisitExpression(expressionStatement.Expression, nil) + expressionType = r.checker.VisitExpression(expressionStatement.Expression, expressionStatement, nil) if !eval && expressionType != sema.InvalidType { r.onExpressionType(expressionType) } diff --git a/runtime/resource_duplicate_test.go b/runtime/resource_duplicate_test.go index 6d43f669f8..4fbd33be2e 100644 --- a/runtime/resource_duplicate_test.go +++ b/runtime/resource_duplicate_test.go @@ -29,7 +29,7 @@ import ( "github.com/onflow/cadence/encoding/json" . "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/interpreter" + "github.com/onflow/cadence/runtime/sema" . "github.com/onflow/cadence/runtime/tests/runtime_utils" . "github.com/onflow/cadence/runtime/tests/utils" ) @@ -204,6 +204,6 @@ func TestRuntimeResourceDuplicationWithContractTransfer(t *testing.T) { ) RequireError(t, err) - var nonTransferableValueError interpreter.NonTransferableValueError - require.ErrorAs(t, err, &nonTransferableValueError) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, err, &invalidMoveError) } diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 0f78771fbc..adbd5a4614 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -10634,123 +10634,6 @@ func TestRuntimeNonPublicAccessModifierInInterface(t *testing.T) { require.Len(t, conformanceErr.MemberMismatches, 2) } -func TestRuntimeMoveSelfVariable(t *testing.T) { - - t.Parallel() - - t.Run("contract", func(t *testing.T) { - t.Parallel() - - contract := []byte(` - access(all) contract Foo { - - access(all) fun moveSelf() { - var x = self! - } - } - `) - - runtime := NewTestInterpreterRuntimeWithConfig(Config{ - AtreeValidationEnabled: false, - }) - - address := common.MustBytesToAddress([]byte{0x1}) - - var contractCode []byte - - runtimeInterface := &TestRuntimeInterface{ - Storage: NewTestLedger(nil, nil), - OnGetSigningAccounts: func() ([]Address, error) { - return []Address{address}, nil - }, - OnGetAccountContractCode: func(location common.AddressLocation) ([]byte, error) { - return contractCode, nil - }, - OnResolveLocation: NewSingleIdentifierLocationResolver(t), - OnUpdateAccountContractCode: func(_ common.AddressLocation, code []byte) error { - contractCode = code - return nil - }, - OnEmitEvent: func(event cadence.Event) error { - return nil - }, - } - - nextTransactionLocation := NewTransactionLocationGenerator() - - // Deploy - - deploymentTx := DeploymentTransaction("Foo", contract) - err := runtime.ExecuteTransaction( - Script{ - Source: deploymentTx, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) - - // Execute script - - nextScriptLocation := NewScriptLocationGenerator() - - script := []byte(fmt.Sprintf(` - import Foo from %[1]s - - access(all) fun main(): Void { - Foo.moveSelf() - }`, - address.HexWithPrefix(), - )) - - _, err = runtime.ExecuteScript( - Script{ - Source: script, - }, - Context{ - Interface: runtimeInterface, - Location: nextScriptLocation(), - }, - ) - - RequireError(t, err) - require.ErrorAs(t, err, &interpreter.NonTransferableValueError{}) - }) - - t.Run("transaction", func(t *testing.T) { - t.Parallel() - - script := []byte(` - transaction { - prepare() { - var x = true ? self : self - } - execute {} - } - `) - - runtime := NewTestInterpreterRuntime() - runtimeInterface := &TestRuntimeInterface{} - - nextTransactionLocation := NewTransactionLocationGenerator() - - err := runtime.ExecuteTransaction( - Script{ - Source: script, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - - RequireError(t, err) - require.ErrorAs(t, err, &interpreter.NonTransferableValueError{}) - }) -} - func TestRuntimeContractWithInvalidCapability(t *testing.T) { t.Parallel() diff --git a/runtime/sema/check_array_expression.go b/runtime/sema/check_array_expression.go index 8121ae30f3..a10a2940ae 100644 --- a/runtime/sema/check_array_expression.go +++ b/runtime/sema/check_array_expression.go @@ -20,7 +20,7 @@ package sema import "github.com/onflow/cadence/runtime/ast" -func (checker *Checker) VisitArrayExpression(expression *ast.ArrayExpression) Type { +func (checker *Checker) VisitArrayExpression(arrayExpression *ast.ArrayExpression) Type { // visit all elements, ensure they are all the same type @@ -29,7 +29,7 @@ func (checker *Checker) VisitArrayExpression(expression *ast.ArrayExpression) Ty var elementType Type var resultType ArrayType - elementCount := len(expression.Values) + elementCount := len(arrayExpression.Values) switch typ := expectedType.(type) { @@ -43,7 +43,7 @@ func (checker *Checker) VisitArrayExpression(expression *ast.ArrayExpression) Ty &ConstantSizedArrayLiteralSizeError{ ExpectedSize: typ.Size, ActualSize: literalCount, - Range: expression.Range, + Range: arrayExpression.Range, }, ) } @@ -68,13 +68,12 @@ func (checker *Checker) VisitArrayExpression(expression *ast.ArrayExpression) Ty if elementCount > 0 { argumentTypes = make([]Type, elementCount) - for i, value := range expression.Values { - valueType := checker.VisitExpression(value, elementType) + for i, element := range arrayExpression.Values { + valueType := checker.VisitExpression(element, arrayExpression, elementType) argumentTypes[i] = valueType - checker.checkVariableMove(value) - checker.checkResourceMoveOperation(value, valueType) + checker.checkResourceMoveOperation(element, valueType) } } @@ -87,7 +86,7 @@ func (checker *Checker) VisitArrayExpression(expression *ast.ArrayExpression) Ty checker.report( &TypeAnnotationRequiredError{ Cause: "cannot infer type from array literal:", - Pos: expression.StartPos, + Pos: arrayExpression.StartPos, }, ) @@ -100,7 +99,7 @@ func (checker *Checker) VisitArrayExpression(expression *ast.ArrayExpression) Ty } checker.Elaboration.SetArrayExpressionTypes( - expression, + arrayExpression, ArrayExpressionTypes{ ArgumentTypes: argumentTypes, ArrayType: resultType, diff --git a/runtime/sema/check_assignment.go b/runtime/sema/check_assignment.go index e8a3dd03eb..56f03290b3 100644 --- a/runtime/sema/check_assignment.go +++ b/runtime/sema/check_assignment.go @@ -59,7 +59,7 @@ func (checker *Checker) checkAssignment( if checker.accessedSelfMember(target) == nil { checkValue = checker.VisitExpressionWithReferenceCheck } - valueType = checkValue(value, targetType) + valueType = checkValue(value, assignment, targetType) // NOTE: Visiting the `value` checks the compatibility between value and target types. // Check for the *target* type, so that assignment using non-resource typed value (e.g. `nil`) @@ -110,7 +110,6 @@ func (checker *Checker) checkAssignment( } checker.enforceViewAssignment(assignment, target) - checker.checkVariableMove(value) checker.recordResourceInvalidation( value, diff --git a/runtime/sema/check_attach_expression.go b/runtime/sema/check_attach_expression.go index e2b70676cf..d06681ed98 100644 --- a/runtime/sema/check_attach_expression.go +++ b/runtime/sema/check_attach_expression.go @@ -34,14 +34,13 @@ func (checker *Checker) VisitAttachExpression(expression *ast.AttachExpression) attachment := expression.Attachment baseExpression := expression.Base - baseType := checker.VisitExpression(baseExpression, checker.expectedType) + baseType := checker.VisitExpression(baseExpression, expression, checker.expectedType) attachmentType := checker.checkInvocationExpression(attachment) if attachmentType.IsInvalidType() || baseType.IsInvalidType() { return InvalidType } - checker.checkVariableMove(baseExpression) checker.checkResourceMoveOperation(baseExpression, attachmentType) // check that the attachment type is a valid attachment, diff --git a/runtime/sema/check_binary_expression.go b/runtime/sema/check_binary_expression.go index b4f77fbc2d..f290291f4a 100644 --- a/runtime/sema/check_binary_expression.go +++ b/runtime/sema/check_binary_expression.go @@ -67,7 +67,12 @@ func (checker *Checker) VisitBinaryExpression(expression *ast.BinaryExpression) // Visit the expression, with contextually expected type. Use the expected type // only for inferring wherever possible, but do not check for compatibility. // Compatibility is checked separately for each operand kind. - leftType = checker.VisitExpressionWithForceType(expression.Left, expectedType, false) + leftType = checker.VisitExpressionWithForceType( + expression.Left, + expression, + expectedType, + false, + ) leftIsInvalid := leftType.IsInvalidType() @@ -123,7 +128,12 @@ func (checker *Checker) VisitBinaryExpression(expression *ast.BinaryExpression) expectedType = leftType } - rightType = checker.VisitExpressionWithForceType(expression.Right, expectedType, false) + rightType = checker.VisitExpressionWithForceType( + expression.Right, + expression, + expectedType, + false, + ) rightIsInvalid := rightType.IsInvalidType() @@ -174,7 +184,12 @@ func (checker *Checker) VisitBinaryExpression(expression *ast.BinaryExpression) expectedType = optionalLeftType.Type } } - return checker.VisitExpressionWithForceType(expression.Right, expectedType, false) + return checker.VisitExpressionWithForceType( + expression.Right, + expression, + expectedType, + false, + ) }) rightIsInvalid := rightType.IsInvalidType() diff --git a/runtime/sema/check_casting_expression.go b/runtime/sema/check_casting_expression.go index 69d2b53a53..3e3b3ef948 100644 --- a/runtime/sema/check_casting_expression.go +++ b/runtime/sema/check_casting_expression.go @@ -44,7 +44,7 @@ func (checker *Checker) VisitCastingExpression(expression *ast.CastingExpression beforeErrors := len(checker.errors) - leftHandType, exprActualType := checker.visitExpression(leftHandExpression, expectedType) + leftHandType, exprActualType := checker.visitExpression(leftHandExpression, expression, expectedType) hasErrors := len(checker.errors) > beforeErrors diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index e70eb1ec2a..299849ad92 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2087,6 +2087,7 @@ func (checker *Checker) checkDefaultDestroyParamExpressionKind( func (checker *Checker) checkDefaultDestroyEventParam( param Parameter, + eventDeclaration ast.CompositeLikeDeclaration, astParam *ast.Parameter, containerType EntitlementSupportingType, containerDeclaration ast.Declaration, @@ -2113,7 +2114,8 @@ func (checker *Checker) checkDefaultDestroyEventParam( compositeContainer, compositeContainer.baseTypeDocString) } - param.DefaultArgument = checker.VisitExpression(paramDefaultArgument, paramType) + + param.DefaultArgument = checker.VisitExpression(paramDefaultArgument, eventDeclaration, paramType) // default events must have default arguments for all their parameters; this is enforced in the parser // we want to check that these arguments are all either literals or field accesses, and have primitive types @@ -2143,7 +2145,13 @@ func (checker *Checker) checkDefaultDestroyEvent( defer checker.leaveValueScope(eventDeclaration.EndPosition, true) for index, param := range eventType.ConstructorParameters { - checker.checkDefaultDestroyEventParam(param, constructorFunctionParameters[index], containerType, containerDeclaration) + checker.checkDefaultDestroyEventParam( + param, + eventDeclaration, + constructorFunctionParameters[index], + containerType, + containerDeclaration, + ) } } diff --git a/runtime/sema/check_conditional.go b/runtime/sema/check_conditional.go index d05229c51b..9a923b9555 100644 --- a/runtime/sema/check_conditional.go +++ b/runtime/sema/check_conditional.go @@ -30,7 +30,7 @@ func (checker *Checker) VisitIfStatement(statement *ast.IfStatement) (_ struct{} switch test := statement.Test.(type) { case ast.Expression: - checker.VisitExpression(test, BoolType) + checker.VisitExpression(test, statement, BoolType) checker.checkConditionalBranches( func() Type { @@ -90,14 +90,14 @@ func (checker *Checker) VisitConditionalExpression(expression *ast.ConditionalEx expectedType := checker.expectedType - checker.VisitExpression(expression.Test, BoolType) + checker.VisitExpression(expression.Test, expression, BoolType) thenType, elseType := checker.checkConditionalBranches( func() Type { - return checker.VisitExpression(expression.Then, expectedType) + return checker.VisitExpression(expression.Then, expression, expectedType) }, func() Type { - return checker.VisitExpression(expression.Else, expectedType) + return checker.VisitExpression(expression.Else, expression, expectedType) }, ) diff --git a/runtime/sema/check_conditions.go b/runtime/sema/check_conditions.go index b17c45e1c3..77cc47c6b1 100644 --- a/runtime/sema/check_conditions.go +++ b/runtime/sema/check_conditions.go @@ -51,11 +51,11 @@ func (checker *Checker) checkCondition(condition ast.Condition) { case *ast.TestCondition: // check test expression is boolean - checker.VisitExpression(condition.Test, BoolType) + checker.VisitExpression(condition.Test, condition, BoolType) // check message expression results in a string if condition.Message != nil { - checker.VisitExpression(condition.Message, StringType) + checker.VisitExpression(condition.Message, condition, StringType) } case *ast.EmitCondition: diff --git a/runtime/sema/check_create_expression.go b/runtime/sema/check_create_expression.go index 298ec57a30..c91432007c 100644 --- a/runtime/sema/check_create_expression.go +++ b/runtime/sema/check_create_expression.go @@ -34,7 +34,7 @@ func (checker *Checker) VisitCreateExpression(expression *ast.CreateExpression) invocation := expression.InvocationExpression - ty := checker.VisitExpression(invocation, nil) + ty := checker.VisitExpression(invocation, expression, nil) if ty.IsInvalidType() { return ty diff --git a/runtime/sema/check_destroy_expression.go b/runtime/sema/check_destroy_expression.go index 90cdaf9484..ac1e24d291 100644 --- a/runtime/sema/check_destroy_expression.go +++ b/runtime/sema/check_destroy_expression.go @@ -25,7 +25,7 @@ import ( func (checker *Checker) VisitDestroyExpression(expression *ast.DestroyExpression) (resultType Type) { resultType = VoidType - valueType := checker.VisitExpression(expression.Expression, nil) + valueType := checker.VisitExpression(expression.Expression, expression, nil) checker.ObserveImpureOperation(expression) checker.recordResourceInvalidation( diff --git a/runtime/sema/check_dictionary_expression.go b/runtime/sema/check_dictionary_expression.go index 08e4accb3a..bcbb92eb7f 100644 --- a/runtime/sema/check_dictionary_expression.go +++ b/runtime/sema/check_dictionary_expression.go @@ -50,12 +50,10 @@ func (checker *Checker) VisitDictionaryExpression(expression *ast.DictionaryExpr // NOTE: important to check move after each type check, // not combined after both type checks! - entryKeyType := checker.VisitExpression(entry.Key, keyType) - checker.checkVariableMove(entry.Key) + entryKeyType := checker.VisitExpression(entry.Key, expression, keyType) checker.checkResourceMoveOperation(entry.Key, entryKeyType) - entryValueType := checker.VisitExpression(entry.Value, valueType) - checker.checkVariableMove(entry.Value) + entryValueType := checker.VisitExpression(entry.Value, expression, valueType) checker.checkResourceMoveOperation(entry.Value, entryValueType) entryTypes[i] = DictionaryEntryType{ diff --git a/runtime/sema/check_expression.go b/runtime/sema/check_expression.go index 59eddccd45..007301fd5e 100644 --- a/runtime/sema/check_expression.go +++ b/runtime/sema/check_expression.go @@ -46,9 +46,50 @@ func (checker *Checker) VisitIdentifierExpression(expression *ast.IdentifierExpr checker.Elaboration.SetIdentifierInInvocationType(expression, valueType) } + checker.checkVariableMove(expression, variable) + return valueType } +func (checker *Checker) checkVariableMove(identifierExpression *ast.IdentifierExpression, variable *Variable) { + + reportMaybeInvalidMove := func(declarationKind common.DeclarationKind) { + // If the parent is member-access or index-access, then it's OK. + // e.g: `v.foo`, `v.bar()`, `v[T]` are OK. + switch parent := checker.parent.(type) { + case *ast.MemberExpression: + // TODO: No need for below check? i.e: should always be true + if parent.Expression == identifierExpression { + return + } + case *ast.IndexExpression: + // Only `v[foo]` is OK, `foo[v]` is not. + if parent.TargetExpression == identifierExpression { + return + } + } + + checker.report( + &InvalidMoveError{ + Name: variable.Identifier, + DeclarationKind: declarationKind, + Pos: identifierExpression.StartPosition(), + }, + ) + } + + switch ty := variable.Type.(type) { + case *TransactionType: + reportMaybeInvalidMove(common.DeclarationKindTransaction) + + case CompositeKindedType: + kind := ty.GetCompositeKind() + if kind == common.CompositeKindContract { + reportMaybeInvalidMove(common.DeclarationKindContract) + } + } +} + func (checker *Checker) checkReferenceValidity(variable *Variable, hasPosition ast.HasPosition) { typ := UnwrapOptionalType(variable.Type) if _, ok := typ.(*ReferenceType); !ok { @@ -161,7 +202,7 @@ func (checker *Checker) checkResourceVariableCapturingInFunction(variable *Varia func (checker *Checker) VisitExpressionStatement(statement *ast.ExpressionStatement) (_ struct{}) { expression := statement.Expression - ty := checker.VisitExpression(expression, nil) + ty := checker.VisitExpression(expression, statement, nil) if ty.IsResourceType() { checker.report( @@ -270,7 +311,7 @@ func (checker *Checker) visitIndexExpression( ) Type { targetExpression := indexExpression.TargetExpression - targetType := checker.VisitExpression(targetExpression, nil) + targetType := checker.VisitExpression(targetExpression, indexExpression, nil) // NOTE: check indexed type first for UX reasons @@ -309,6 +350,7 @@ func (checker *Checker) visitIndexExpression( } indexingType := checker.VisitExpression( indexExpression.IndexingExpression, + indexExpression, valueIndexedType.IndexingType(), ) diff --git a/runtime/sema/check_for.go b/runtime/sema/check_for.go index 31c9e1d92a..ad4466374e 100644 --- a/runtime/sema/check_for.go +++ b/runtime/sema/check_for.go @@ -41,7 +41,7 @@ func (checker *Checker) VisitForStatement(statement *ast.ForStatement) (_ struct } } - valueType := checker.VisitExpression(valueExpression, expectedType) + valueType := checker.VisitExpression(valueExpression, statement, expectedType) // Only get the element type if the array is not a resource array. // Otherwise, in addition to the `UnsupportedResourceForLoopError`, diff --git a/runtime/sema/check_force_expression.go b/runtime/sema/check_force_expression.go index bde55ea1e5..e7265a99f0 100644 --- a/runtime/sema/check_force_expression.go +++ b/runtime/sema/check_force_expression.go @@ -28,7 +28,7 @@ func (checker *Checker) VisitForceExpression(expression *ast.ForceExpression) Ty // i.e: if `x!` is `String`, then `x` is expected to be `String?`. expectedType := wrapWithOptionalIfNotNil(checker.expectedType) - valueType := checker.VisitExpression(expression.Expression, expectedType) + valueType := checker.VisitExpression(expression.Expression, expression, expectedType) if valueType.IsInvalidType() { return valueType diff --git a/runtime/sema/check_invocation_expression.go b/runtime/sema/check_invocation_expression.go index bf9018184c..c118850336 100644 --- a/runtime/sema/check_invocation_expression.go +++ b/runtime/sema/check_invocation_expression.go @@ -89,7 +89,7 @@ func (checker *Checker) checkInvocationExpression(invocationExpression *ast.Invo // check the invoked expression can be invoked invokedExpression := invocationExpression.InvokedExpression - expressionType := checker.VisitExpression(invokedExpression, nil) + expressionType := checker.VisitExpression(invokedExpression, invocationExpression, nil) // `inInvocation` should be reset before visiting arguments checker.inInvocation = false @@ -131,7 +131,7 @@ func (checker *Checker) checkInvocationExpression(invocationExpression *ast.Invo argumentTypes = make([]Type, 0, argumentCount) for _, argument := range invocationExpression.Arguments { - argumentType := checker.VisitExpression(argument.Expression, nil) + argumentType := checker.VisitExpression(argument.Expression, invocationExpression, nil) argumentTypes = append(argumentTypes, argumentType) } @@ -469,7 +469,7 @@ func (checker *Checker) checkInvocation( parameterTypes[argumentIndex] = checker.checkInvocationRequiredArgument( - invocationExpression.Arguments, + invocationExpression, argumentIndex, functionType, argumentTypes, @@ -482,7 +482,7 @@ func (checker *Checker) checkInvocation( for i := minCount; i < argumentCount; i++ { argument := invocationExpression.Arguments[i] // TODO: pass the expected type to support type inferring for parameters - argumentTypes[i] = checker.VisitExpression(argument.Expression, nil) + argumentTypes[i] = checker.VisitExpression(argument.Expression, invocationExpression, nil) } } @@ -571,7 +571,7 @@ func (checker *Checker) checkTypeParameterInference( } func (checker *Checker) checkInvocationRequiredArgument( - arguments ast.Arguments, + invocationExpression *ast.InvocationExpression, argumentIndex int, functionType *FunctionType, argumentTypes []Type, @@ -579,7 +579,7 @@ func (checker *Checker) checkInvocationRequiredArgument( ) ( parameterType Type, ) { - argument := arguments[argumentIndex] + argument := invocationExpression.Arguments[argumentIndex] parameter := functionType.Parameters[argumentIndex] parameterType = parameter.TypeAnnotation.Type @@ -637,7 +637,7 @@ func (checker *Checker) checkInvocationRequiredArgument( expectedType = nil } - argumentType = checker.VisitExpression(argument.Expression, expectedType) + argumentType = checker.VisitExpression(argument.Expression, invocationExpression, expectedType) // If we did not pass an expected type, // we must manually check that the argument type and the parameter type are compatible. @@ -659,7 +659,7 @@ func (checker *Checker) checkInvocationRequiredArgument( // We will then have to manually check that the argument type is compatible // with the parameter type (see below). - argumentType = checker.VisitExpression(argument.Expression, nil) + argumentType = checker.VisitExpression(argument.Expression, invocationExpression, nil) // Try to unify the parameter type with the argument type. // If unification fails, fall back to the parameter type for now. @@ -807,9 +807,6 @@ func (checker *Checker) checkInvocationArgumentParameterTypeCompatibility( } func (checker *Checker) checkInvocationArgumentMove(argument ast.Expression, argumentType Type) Type { - - checker.checkVariableMove(argument) checker.checkResourceMoveOperation(argument, argumentType) - return argumentType } diff --git a/runtime/sema/check_member_expression.go b/runtime/sema/check_member_expression.go index 3f04c0b29f..d2bc65e9da 100644 --- a/runtime/sema/check_member_expression.go +++ b/runtime/sema/check_member_expression.go @@ -168,7 +168,7 @@ func (checker *Checker) visitMember(expression *ast.MemberExpression, isAssignme // is an assignment, but the evaluation of the accessed exprssion itself (i.e. `a.b`) // is not, so we temporarily clear the `inAssignment` status here before restoring it later. accessedType = checker.withAssignment(false, func() Type { - return checker.VisitExpression(accessedExpression, nil) + return checker.VisitExpression(accessedExpression, expression, nil) }) }() @@ -345,16 +345,21 @@ func (checker *Checker) visitMember(expression *ast.MemberExpression, isAssignme // // This would result in a bound method for a resource, which is invalid. - if !checker.inInvocation && - member.DeclarationKind == common.DeclarationKindFunction && + if member.DeclarationKind == common.DeclarationKindFunction && !accessedType.IsInvalidType() && accessedType.IsResourceType() { - checker.report( - &ResourceMethodBindingError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, expression), - }, - ) + parent := checker.parent + parentInvocationExpr, parentIsInvocation := parent.(*ast.InvocationExpression) + + if !parentIsInvocation || + expression != parentInvocationExpr.InvokedExpression { + checker.report( + &ResourceMethodBindingError{ + Range: ast.NewRangeFromPositioned(checker.memoryGauge, expression), + }, + ) + } } // If the member, diff --git a/runtime/sema/check_reference_expression.go b/runtime/sema/check_reference_expression.go index ecbef01961..0c930da082 100644 --- a/runtime/sema/check_reference_expression.go +++ b/runtime/sema/check_reference_expression.go @@ -59,7 +59,7 @@ func (checker *Checker) VisitReferenceExpression(referenceExpression *ast.Refere beforeErrors := len(checker.errors) - referencedType, actualType := checker.visitExpression(referencedExpression, expectedLeftType) + referencedType, actualType := checker.visitExpression(referencedExpression, referenceExpression, expectedLeftType) // check that the type of the referenced value is not itself a reference var requireNoReferenceNesting func(actualType Type) diff --git a/runtime/sema/check_remove_statement.go b/runtime/sema/check_remove_statement.go index 9bc614a072..c6801325e3 100644 --- a/runtime/sema/check_remove_statement.go +++ b/runtime/sema/check_remove_statement.go @@ -32,7 +32,7 @@ func (checker *Checker) VisitRemoveStatement(statement *ast.RemoveStatement) (_ } nominalType := checker.convertNominalType(statement.Attachment) - base := checker.VisitExpression(statement.Value, nil) + base := checker.VisitExpression(statement.Value, statement, nil) checker.checkUnusedExpressionResourceLoss(base, statement.Value) if nominalType == InvalidType { diff --git a/runtime/sema/check_return_statement.go b/runtime/sema/check_return_statement.go index bc7bac2e6c..ad98d274fd 100644 --- a/runtime/sema/check_return_statement.go +++ b/runtime/sema/check_return_statement.go @@ -60,7 +60,7 @@ func (checker *Checker) VisitReturnStatement(statement *ast.ReturnStatement) (_ // If the return statement has a return value, // check that the value's type matches the enclosing function's return type - valueType := checker.VisitExpression(statement.Expression, returnType) + valueType := checker.VisitExpression(statement.Expression, statement, returnType) checker.Elaboration.SetReturnStatementTypes( statement, @@ -74,7 +74,6 @@ func (checker *Checker) VisitReturnStatement(statement *ast.ReturnStatement) (_ return } - checker.checkVariableMove(statement.Expression) checker.checkResourceMoveOperation(statement.Expression, valueType) return diff --git a/runtime/sema/check_swap.go b/runtime/sema/check_swap.go index ebe85a787c..6fc049e5ce 100644 --- a/runtime/sema/check_swap.go +++ b/runtime/sema/check_swap.go @@ -31,8 +31,8 @@ func (checker *Checker) VisitSwapStatement(swap *ast.SwapStatement) (_ struct{}) // Then re-visit the same expressions, this time treat them as the value-expr of the assignment. // The 'expected type' of the two expression would be the types obtained from the previous visit, swapped. - leftValueType := checker.VisitExpression(swap.Left, rightTargetType) - rightValueType := checker.VisitExpression(swap.Right, leftTargetType) + leftValueType := checker.VisitExpression(swap.Left, swap, rightTargetType) + rightValueType := checker.VisitExpression(swap.Right, swap, leftTargetType) checker.Elaboration.SetSwapStatementTypes( swap, diff --git a/runtime/sema/check_switch.go b/runtime/sema/check_switch.go index 5e2e878c4f..e2a9a87081 100644 --- a/runtime/sema/check_switch.go +++ b/runtime/sema/check_switch.go @@ -24,7 +24,7 @@ import ( func (checker *Checker) VisitSwitchStatement(statement *ast.SwitchStatement) (_ struct{}) { - testType := checker.VisitExpression(statement.Expression, nil) + testType := checker.VisitExpression(statement.Expression, statement, nil) testTypeIsValid := !testType.IsInvalidType() @@ -43,6 +43,7 @@ func (checker *Checker) VisitSwitchStatement(statement *ast.SwitchStatement) (_ checker.functionActivations.Current().WithSwitch(func() { checker.checkSwitchCasesStatements( + statement, statement.Cases, testType, testTypeIsValid, @@ -53,6 +54,7 @@ func (checker *Checker) VisitSwitchStatement(statement *ast.SwitchStatement) (_ } func (checker *Checker) checkSwitchCaseExpression( + statement *ast.SwitchStatement, caseExpression ast.Expression, testType Type, testTypeIsValid bool, @@ -63,7 +65,7 @@ func (checker *Checker) checkSwitchCaseExpression( caseExprExpectedType = testType } - caseType := checker.VisitExpression(caseExpression, caseExprExpectedType) + caseType := checker.VisitExpression(caseExpression, statement, caseExprExpectedType) if caseType.IsInvalidType() { return @@ -88,6 +90,7 @@ func (checker *Checker) checkSwitchCaseExpression( } func (checker *Checker) checkSwitchCasesStatements( + statement *ast.SwitchStatement, remainingCases []*ast.SwitchCase, testType Type, testTypeIsValid bool, @@ -128,6 +131,7 @@ func (checker *Checker) checkSwitchCasesStatements( } checker.checkSwitchCaseExpression( + statement, caseExpression, testType, testTypeIsValid, @@ -145,6 +149,7 @@ func (checker *Checker) checkSwitchCasesStatements( }, func() Type { checker.checkSwitchCasesStatements( + statement, remainingCases[1:], testType, testTypeIsValid, diff --git a/runtime/sema/check_unary_expression.go b/runtime/sema/check_unary_expression.go index 71b8abbddf..4f6137c1c1 100644 --- a/runtime/sema/check_unary_expression.go +++ b/runtime/sema/check_unary_expression.go @@ -30,7 +30,12 @@ func (checker *Checker) VisitUnaryExpression(expression *ast.UnaryExpression) Ty expectedType = checker.expectedType } - valueType := checker.VisitExpressionWithForceType(expression.Expression, expectedType, false) + valueType := checker.VisitExpressionWithForceType( + expression.Expression, + expression, + expectedType, + false, + ) reportInvalidUnaryOperator := func(expectedType Type) { checker.report( diff --git a/runtime/sema/check_variable_declaration.go b/runtime/sema/check_variable_declaration.go index ffff91a90e..cc9a642fa0 100644 --- a/runtime/sema/check_variable_declaration.go +++ b/runtime/sema/check_variable_declaration.go @@ -54,7 +54,7 @@ func (checker *Checker) visitVariableDeclarationValues(declaration *ast.Variable } } - valueType := checker.VisitExpression(declaration.Value, expectedValueType) + valueType := checker.VisitExpression(declaration.Value, declaration, expectedValueType) if isOptionalBinding { optionalType, isOptional := valueType.(*OptionalType) @@ -113,8 +113,6 @@ func (checker *Checker) visitVariableDeclarationValues(declaration *ast.Variable panic(errors.NewUnreachableError()) } - checker.checkVariableMove(declaration.Value) - // If only one value expression is provided, it is invalidated (if it has a resource type) checker.recordResourceInvalidation( @@ -203,7 +201,7 @@ func (checker *Checker) visitVariableDeclarationValues(declaration *ast.Variable if recordedResourceInvalidation != nil { checker.resources.RemoveTemporaryMoveInvalidation(recordedResourceInvalidation.resource, recordedResourceInvalidation.invalidation) } - checker.VisitExpression(declaration.Value, expectedValueType) + checker.VisitExpression(declaration.Value, declaration, expectedValueType) } } diff --git a/runtime/sema/check_while.go b/runtime/sema/check_while.go index 9a60294b04..519ce5821c 100644 --- a/runtime/sema/check_while.go +++ b/runtime/sema/check_while.go @@ -25,7 +25,7 @@ import ( func (checker *Checker) VisitWhileStatement(statement *ast.WhileStatement) (_ struct{}) { - checker.VisitExpression(statement.Test, BoolType) + checker.VisitExpression(statement.Test, statement, BoolType) // The body of the loop will maybe be evaluated. // That means that resource invalidations and diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index d9dcb64014..7846ed8c38 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -125,6 +125,7 @@ type Checker struct { inCreate bool isChecked bool inAssignment bool + parent ast.Element } var _ ast.DeclarationVisitor[struct{}] = &Checker{} @@ -2299,40 +2300,6 @@ func (checker *Checker) predeclaredMembers(containerType Type) []*Member { return predeclaredMembers } -func (checker *Checker) checkVariableMove(expression ast.Expression) { - - identifierExpression, ok := expression.(*ast.IdentifierExpression) - if !ok { - return - } - - variable := checker.valueActivations.Find(identifierExpression.Identifier.Identifier) - if variable == nil { - return - } - - reportInvalidMove := func(declarationKind common.DeclarationKind) { - checker.report( - &InvalidMoveError{ - Name: variable.Identifier, - DeclarationKind: declarationKind, - Pos: identifierExpression.StartPosition(), - }, - ) - } - - switch ty := variable.Type.(type) { - case *TransactionType: - reportInvalidMove(common.DeclarationKindTransaction) - - case CompositeKindedType: - kind := ty.GetCompositeKind() - if kind == common.CompositeKindContract { - reportInvalidMove(common.DeclarationKindContract) - } - } -} - func (checker *Checker) checkTypeAnnotation(typeAnnotation TypeAnnotation, pos ast.HasPosition) { switch typeAnnotation.TypeAnnotationState() { @@ -2529,25 +2496,25 @@ func (checker *Checker) convertInstantiationType(t *ast.InstantiationType) Type ) } -func (checker *Checker) VisitExpression(expr ast.Expression, expectedType Type) Type { +func (checker *Checker) VisitExpression(expr ast.Expression, parent ast.Element, expectedType Type) Type { // Always return 'visibleType' as the type of the expression, // to avoid bubbling up type-errors of inner expressions. - visibleType, _ := checker.visitExpression(expr, expectedType) + visibleType, _ := checker.visitExpression(expr, parent, expectedType) return visibleType } -func (checker *Checker) visitExpression(expr ast.Expression, expectedType Type) (visibleType Type, actualType Type) { - return checker.visitExpressionWithForceType(expr, expectedType, true) +func (checker *Checker) visitExpression(expr ast.Expression, parent ast.Element, expectedType Type) (visibleType Type, actualType Type) { + return checker.visitExpressionWithForceType(expr, parent, expectedType, true) } -func (checker *Checker) VisitExpressionWithForceType(expr ast.Expression, expectedType Type, forceType bool) Type { +func (checker *Checker) VisitExpressionWithForceType(expr ast.Expression, parent ast.Element, expectedType Type, forceType bool) Type { // Always return 'visibleType' as the type of the expression, // to avoid bubbling up type-errors of inner expressions. - visibleType, _ := checker.visitExpressionWithForceType(expr, expectedType, forceType) + visibleType, _ := checker.visitExpressionWithForceType(expr, parent, expectedType, forceType) return visibleType } -func (checker *Checker) VisitExpressionWithReferenceCheck(expr ast.Expression, targetType Type) Type { +func (checker *Checker) VisitExpressionWithReferenceCheck(expr ast.Expression, parent ast.Element, targetType Type) Type { // If the target type is or contains a reference type in particular, // we do NOT use it as the expected type, @@ -2584,7 +2551,7 @@ func (checker *Checker) VisitExpressionWithReferenceCheck(expr ast.Expression, t expectedType = nil } - visibleType := checker.VisitExpression(expr, expectedType) + visibleType := checker.VisitExpression(expr, parent, expectedType) // If we did not pass an expected type, // we must manually check that the argument type and the parameter type are compatible. @@ -2623,6 +2590,7 @@ func (checker *Checker) VisitExpressionWithReferenceCheck(expr ast.Expression, t // actualType - The actual type of the expression. func (checker *Checker) visitExpressionWithForceType( expr ast.Expression, + parent ast.Element, expectedType Type, forceType bool, ) (visibleType Type, actualType Type) { @@ -2630,11 +2598,15 @@ func (checker *Checker) visitExpressionWithForceType( // Cache the current contextually expected type, and set the `expectedType` // as the new contextually expected type. prevExpectedType := checker.expectedType + prevParent := checker.parent + checker.parent = parent checker.expectedType = expectedType defer func() { // Restore the prev contextually expected type checker.expectedType = prevExpectedType + // Restore the prev parent + checker.parent = prevParent }() actualType = ast.AcceptExpression[Type](expr, checker) diff --git a/runtime/sema/type.go b/runtime/sema/type.go index dbc0ff4558..8db1db61be 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -7380,7 +7380,8 @@ func IsPrimitiveOrContainerOfPrimitive(referencedType Type) bool { return IsPrimitiveOrContainerOfPrimitive(ty.Type) case *DictionaryType: - return IsPrimitiveOrContainerOfPrimitive(ty.ValueType) + return IsPrimitiveOrContainerOfPrimitive(ty.KeyType) && + IsPrimitiveOrContainerOfPrimitive(ty.ValueType) default: return ty.IsPrimitiveType() diff --git a/runtime/storage.go b/runtime/storage.go index 8e92f5edb1..f5d658489f 100644 --- a/runtime/storage.go +++ b/runtime/storage.go @@ -229,6 +229,17 @@ func (s *Storage) writeContractUpdate( // Commit serializes/saves all values in the readCache in storage (through the runtime interface). func (s *Storage) Commit(inter *interpreter.Interpreter, commitContractUpdates bool) error { + return s.commit(inter, commitContractUpdates, true) +} + +// NondeterministicCommit serializes and commits all values in the deltas storage +// in nondeterministic order. This function is used when commit ordering isn't +// required (e.g. migration programs). +func (s *Storage) NondeterministicCommit(inter *interpreter.Interpreter, commitContractUpdates bool) error { + return s.commit(inter, commitContractUpdates, false) +} + +func (s *Storage) commit(inter *interpreter.Interpreter, commitContractUpdates bool, deterministic bool) error { if commitContractUpdates { s.commitContractUpdates(inter) @@ -252,7 +263,11 @@ func (s *Storage) Commit(inter *interpreter.Interpreter, commitContractUpdates b common.UseMemory(s.memoryGauge, common.NewAtreeEncodedSlabMemoryUsage(deltas)) // TODO: report encoding metric for all encoded slabs - return s.PersistentSlabStorage.FastCommit(runtime.NumCPU()) + if deterministic { + return s.PersistentSlabStorage.FastCommit(runtime.NumCPU()) + } else { + return s.PersistentSlabStorage.NondeterministicFastCommit(runtime.NumCPU()) + } } func (s *Storage) commitNewStorageMaps() error { diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index 9294ed964a..c49b7e839f 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -1540,9 +1540,12 @@ func TestCheckAttachmentIllegalInit(t *testing.T) { let t = optContractRef?.Test() `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) - assert.IsType(t, &sema.InvalidAttachmentUsageError{}, errs[0]) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + + assert.IsType(t, &sema.InvalidAttachmentUsageError{}, errs[1]) }) } @@ -1655,9 +1658,12 @@ func TestCheckAttachmentAttachNonAttachment(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) - assert.IsType(t, &sema.NotCallableError{}, errs[0]) + assert.IsType(t, &sema.NotCallableError{}, errs[1]) }) } @@ -1773,9 +1779,12 @@ func TestCheckAttachmentAttachToNonComposite(t *testing.T) { `, ) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) - assert.IsType(t, &sema.NotCallableError{}, errs[0]) + assert.IsType(t, &sema.NotCallableError{}, errs[1]) }) t.Run("attachment", func(t *testing.T) { diff --git a/runtime/tests/checker/composite_test.go b/runtime/tests/checker/composite_test.go index a57ac1445c..af440e83e9 100644 --- a/runtime/tests/checker/composite_test.go +++ b/runtime/tests/checker/composite_test.go @@ -633,9 +633,14 @@ func TestCheckCompositeInitializerSelfUse(t *testing.T) { ) switch kind { - case common.CompositeKindStructure, common.CompositeKindContract, common.CompositeKindAttachment: + case common.CompositeKindStructure, common.CompositeKindAttachment: require.NoError(t, err) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 1) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + case common.CompositeKindResource: errs := RequireCheckerErrors(t, err, 1) @@ -674,9 +679,14 @@ func TestCheckCompositeFunctionSelfUse(t *testing.T) { ) switch kind { - case common.CompositeKindStructure, common.CompositeKindContract, common.CompositeKindAttachment: + case common.CompositeKindStructure, common.CompositeKindAttachment: require.NoError(t, err) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 1) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + case common.CompositeKindResource: errs := RequireCheckerErrors(t, err, 1) @@ -1981,19 +1991,36 @@ func TestCheckMutualTypeUseTopLevel(t *testing.T) { ) firstBody := "" + firstCallableFunc := "fun foo()" if !firstIsInterface { + usage := "b" + if secondKind == common.CompositeKindContract { + usage += ".foo()" + } + firstBody = fmt.Sprintf( - "{ %s b }", + "{ %s %s }", secondKind.DestructionKeyword(), + usage, ) + + firstCallableFunc += " {}" } secondBody := "" + secondCallableFunc := "fun foo()" if !secondIsInterface { + usage := "a" + if firstKind == common.CompositeKindContract { + usage += ".foo()" + } + secondBody = fmt.Sprintf( - "{ %s a }", + "{ %s %s }", firstKind.DestructionKeyword(), + usage, ) + secondCallableFunc += " {}" } t.Run(testName, func(t *testing.T) { @@ -2002,10 +2029,12 @@ func TestCheckMutualTypeUseTopLevel(t *testing.T) { ` %[1]s %[2]s A { fun use(_ b: %[3]s%[4]s) %[5]s + %[11]s } %[6]s %[7]s B { fun use(_ a: %[8]s%[9]s) %[10]s + %[12]s } `, firstKind.Keyword(), @@ -2018,6 +2047,8 @@ func TestCheckMutualTypeUseTopLevel(t *testing.T) { firstKind.Annotation(), firstTypeAnnotation, secondBody, + firstCallableFunc, + secondCallableFunc, ) _, err := ParseAndCheck(t, code) diff --git a/runtime/tests/checker/contract_test.go b/runtime/tests/checker/contract_test.go index 6f4a4ea847..c56026e0fc 100644 --- a/runtime/tests/checker/contract_test.go +++ b/runtime/tests/checker/contract_test.go @@ -744,7 +744,12 @@ func TestCheckBadContractNesting(t *testing.T) { func TestCheckContractEnumAccessRestricted(t *testing.T) { t.Parallel() - _, err := ParseAndCheckWithOptions(t, "contract foo{}let x = foo!", + _, err := ParseAndCheckWithOptions(t, ` + contract foo { + access(all) fun bar() {} + } + let x = foo.bar()! + `, ParseAndCheckOptions{ Config: &sema.Config{ AccessCheckMode: sema.AccessCheckModeStrict, diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index b5435cf914..076da567e8 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -250,9 +250,12 @@ func TestCheckEmitEvent(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) - assert.IsType(t, &sema.InvalidEventUsageError{}, errs[0]) + assert.IsType(t, &sema.InvalidEventUsageError{}, errs[1]) }) t.Run("emit non-event", func(t *testing.T) { diff --git a/runtime/tests/checker/move_test.go b/runtime/tests/checker/move_test.go new file mode 100644 index 0000000000..ddea3219e0 --- /dev/null +++ b/runtime/tests/checker/move_test.go @@ -0,0 +1,66 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package checker + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/runtime/sema" +) + +func TestCheckInvalidMoves(t *testing.T) { + + t.Parallel() + + t.Run("contract", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + access(all) contract Foo { + access(all) fun moveSelf() { + var x = self! + } + } + `) + + errors := RequireCheckerErrors(t, err, 1) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errors[0], &invalidMoveError) + }) + + t.Run("transaction", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + transaction { + prepare() { + var x = true ? self : self + } + execute {} + } + `) + + errors := RequireCheckerErrors(t, err, 2) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errors[0], &invalidMoveError) + require.ErrorAs(t, errors[1], &invalidMoveError) + }) +} diff --git a/runtime/tests/checker/nesting_test.go b/runtime/tests/checker/nesting_test.go index 47c125c638..c56279b7e8 100644 --- a/runtime/tests/checker/nesting_test.go +++ b/runtime/tests/checker/nesting_test.go @@ -197,9 +197,11 @@ func TestCheckCompositeDeclarationNestedTypeScopingInsideNestedOuter(t *testing. struct X { fun test() { - Test + Test.foo() } } + + fun foo() {} } `) diff --git a/runtime/tests/checker/operations_test.go b/runtime/tests/checker/operations_test.go index 38c02bcc7f..3781d18d4b 100644 --- a/runtime/tests/checker/operations_test.go +++ b/runtime/tests/checker/operations_test.go @@ -584,9 +584,10 @@ func TestCheckInvalidCompositeEquality(t *testing.T) { ), ) - if compositeKind == common.CompositeKindEnum { + switch compositeKind { + case common.CompositeKindEnum: require.NoError(t, err) - } else if compositeKind == common.CompositeKindAttachment { + case common.CompositeKindAttachment: errs := RequireCheckerErrors(t, err, 5) assert.IsType(t, &sema.InvalidAttachmentAnnotationError{}, errs[0]) @@ -594,7 +595,17 @@ func TestCheckInvalidCompositeEquality(t *testing.T) { assert.IsType(t, &sema.InvalidAttachmentAnnotationError{}, errs[2]) assert.IsType(t, &sema.InvalidAttachmentUsageError{}, errs[3]) assert.IsType(t, &sema.InvalidBinaryOperandsError{}, errs[4]) - } else { + + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 3) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + require.ErrorAs(t, errs[1], &invalidMoveError) + + assert.IsType(t, &sema.InvalidBinaryOperandsError{}, errs[2]) + + default: errs := RequireCheckerErrors(t, err, 1) assert.IsType(t, &sema.InvalidBinaryOperandsError{}, errs[0]) diff --git a/runtime/tests/checker/optional_test.go b/runtime/tests/checker/optional_test.go index 0a40571461..91d3bb9688 100644 --- a/runtime/tests/checker/optional_test.go +++ b/runtime/tests/checker/optional_test.go @@ -264,7 +264,14 @@ func TestCheckCompositeNilEquality(t *testing.T) { ), ) - require.NoError(t, err) + if compositeKind == common.CompositeKindContract { + errs := RequireCheckerErrors(t, err, 2) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + require.ErrorAs(t, errs[1], &invalidMoveError) + } else { + require.NoError(t, err) + } }) } @@ -338,9 +345,20 @@ func TestCheckInvalidCompositeNilEquality(t *testing.T) { ), ) - if compositeKind == common.CompositeKindEnum { + switch compositeKind { + case common.CompositeKindEnum: require.NoError(t, err) - } else { + + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 3) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + require.ErrorAs(t, errs[1], &invalidMoveError) + + assert.IsType(t, &sema.InvalidBinaryOperandsError{}, errs[2]) + + default: errs := RequireCheckerErrors(t, err, 1) assert.IsType(t, &sema.InvalidBinaryOperandsError{}, errs[0]) diff --git a/runtime/tests/checker/reference_test.go b/runtime/tests/checker/reference_test.go index ef35296c18..b0b42a09fe 100644 --- a/runtime/tests/checker/reference_test.go +++ b/runtime/tests/checker/reference_test.go @@ -3512,6 +3512,23 @@ func TestCheckDereference(t *testing.T) { } `, ) + + // Dictionaries with composite typed keys cannot be dereferenced. + runInvalidTestCase( + t, + "{Enum: Int}", + ` + access(all) enum E:Int { + access(all) case first + } + + access(all) fun main() { + var dict = {E.first: 0} + var ref = &dict as &{E: Int} + var deref = *ref + } + `, + ) }) runInvalidTestCase( diff --git a/runtime/tests/checker/resources_test.go b/runtime/tests/checker/resources_test.go index db21d40bc1..b548475ab7 100644 --- a/runtime/tests/checker/resources_test.go +++ b/runtime/tests/checker/resources_test.go @@ -93,13 +93,19 @@ func TestCheckFailableCastingWithResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidAttachmentUsageError{}, errs[1]) case common.CompositeKindStructure, - common.CompositeKindContract, common.CompositeKindEnum: errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[0]) + + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 2) assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[0]) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[1], &invalidMoveError) + case common.CompositeKindEvent: errs := RequireCheckerErrors(t, err, 2) @@ -171,7 +177,6 @@ func TestCheckFunctionDeclarationParameterWithResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidAttachmentAnnotationError{}, errs[0]) case common.CompositeKindStructure, - common.CompositeKindContract, common.CompositeKindEvent, common.CompositeKindEnum: @@ -179,6 +184,14 @@ func TestCheckFunctionDeclarationParameterWithResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[0]) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[0]) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[1], &invalidMoveError) + default: panic(errors.NewUnreachableError()) } @@ -244,12 +257,16 @@ func TestCheckFunctionDeclarationParameterWithoutResourceAnnotation(t *testing.T assert.IsType(t, &sema.InvalidAttachmentAnnotationError{}, errs[0]) case common.CompositeKindStructure, - common.CompositeKindContract, common.CompositeKindEvent, common.CompositeKindEnum: require.NoError(t, err) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 1) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + default: panic(errors.NewUnreachableError()) } @@ -779,7 +796,6 @@ func TestCheckFunctionExpressionParameterWithResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidAttachmentAnnotationError{}, errs[0]) case common.CompositeKindStructure, - common.CompositeKindContract, common.CompositeKindEvent, common.CompositeKindEnum: @@ -787,6 +803,14 @@ func TestCheckFunctionExpressionParameterWithResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[0]) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 2) + + assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[0]) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[1], &invalidMoveError) + default: panic(errors.NewUnreachableError()) } @@ -854,12 +878,17 @@ func TestCheckFunctionExpressionParameterWithoutResourceAnnotation(t *testing.T) assert.IsType(t, &sema.InvalidAttachmentAnnotationError{}, errs[0]) case common.CompositeKindStructure, - common.CompositeKindContract, common.CompositeKindEvent, common.CompositeKindEnum: require.NoError(t, err) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 1) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + default: panic(errors.NewUnreachableError()) } @@ -1097,7 +1126,6 @@ func TestCheckFunctionTypeParameterWithResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidAttachmentAnnotationError{}, errs[0]) case common.CompositeKindStructure, - common.CompositeKindContract, common.CompositeKindEvent, common.CompositeKindEnum: @@ -1106,6 +1134,15 @@ func TestCheckFunctionTypeParameterWithResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[0]) assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[1]) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 3) + + assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[0]) + assert.IsType(t, &sema.InvalidResourceAnnotationError{}, errs[1]) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[2], &invalidMoveError) + default: panic(errors.NewUnreachableError()) } @@ -1175,12 +1212,16 @@ func TestCheckFunctionTypeParameterWithoutResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidAttachmentAnnotationError{}, errs[1]) case common.CompositeKindStructure, - common.CompositeKindContract, common.CompositeKindEvent, common.CompositeKindEnum: require.NoError(t, err) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 1) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + default: panic(errors.NewUnreachableError()) } @@ -1408,11 +1449,14 @@ func TestCheckFailableCastingWithoutResourceAnnotation(t *testing.T) { assert.IsType(t, &sema.InvalidFailableResourceDowncastOutsideOptionalBindingError{}, errs[1]) assert.IsType(t, &sema.InvalidNonIdentifierFailableResourceDowncast{}, errs[2]) - case common.CompositeKindStructure, - common.CompositeKindContract: - + case common.CompositeKindStructure: require.NoError(t, err) + case common.CompositeKindContract: + errs := RequireCheckerErrors(t, err, 1) + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) + case common.CompositeKindEvent: errs := RequireCheckerErrors(t, err, 1) @@ -9418,6 +9462,135 @@ func TestCheckInvalidResourceDestructionInFunction(t *testing.T) { assert.IsType(t, &sema.ResourceCapturingError{}, errs[0]) } +func TestCheckInvalidNestedResourceCaptureOnLeft(t *testing.T) { + + t.Parallel() + + t.Run("on right", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + transaction { + var x: @AnyResource? + prepare() { + self.x <- nil + } + execute { + fun() { + let y <- self.x + destroy y + } + } + } + `) + require.NoError(t, err) + }) + + t.Run("resource field on right", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + var x: @AnyResource? + init() { + self.x <- nil + } + fun foo() { + fun() { + let y <- self.x <- nil + destroy y + } + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.ResourceCapturingError{}, errs[0]) + }) + + t.Run("on left", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + transaction { + var x: @AnyResource? + prepare() { + self.x <- nil + } + execute { + fun() { + self.x <-! nil + } + + destroy self.x + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.ResourceCapturingError{}, errs[0]) + }) + + t.Run("on left method scope", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + transaction { + var x: @AnyResource? + prepare() { + self.x <- nil + } + execute { + self.x <-! nil + + destroy self.x + } + } + `) + require.NoError(t, err) + }) + + t.Run("contract self variable on left", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + contract C { + var x: @AnyResource? + + init() { + self.x <- nil + } + + fun foo() { + fun() { + self.x <-! nil + } + } + } + `) + require.NoError(t, err) + }) + + t.Run("contract self variable on left method scope", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + contract C { + var x: @AnyResource? + + init() { + self.x <- nil + } + + fun foo() { + self.x <-! nil + } + } + `) + require.NoError(t, err) + }) +} + func TestCheckInvalidationInCondition(t *testing.T) { t.Parallel() @@ -9674,8 +9847,9 @@ func TestCheckBoundFunctionToResource(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + errs := RequireCheckerErrors(t, err, 2) + assert.IsType(t, &sema.ResourceMethodBindingError{}, errs[0]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) }) t.Run("function expression", func(t *testing.T) { @@ -9703,8 +9877,9 @@ func TestCheckBoundFunctionToResource(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) assert.IsType(t, &sema.ResourceCapturingError{}, errs[0]) + assert.IsType(t, &sema.ResourceMethodBindingError{}, errs[1]) }) t.Run("array expression", func(t *testing.T) { @@ -9729,9 +9904,37 @@ func TestCheckBoundFunctionToResource(t *testing.T) { } `) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 2) + assert.IsType(t, &sema.ResourceMethodBindingError{}, errs[0]) + assert.IsType(t, &sema.ResourceMethodBindingError{}, errs[1]) }) + t.Run("array expression map function", func(t *testing.T) { + t.Parallel() + + _, err := ParseAndCheck(t, ` + access(all) resource R { + access(all) fun sayHi() {} + } + + access(all) fun main() { + var r <- create R() + var f: fun(): Void = fun() {} + + // ArrayExpression trick to capture the bound function + [r.sayHi].map(fun(element: fun(): Void): Void { + f = element + }) + + f() // Bound resource method called here + + destroy r + } + `) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.ResourceMethodBindingError{}, errs[0]) + }) } func TestCheckBoundFunctionToResourceInAssignment(t *testing.T) { diff --git a/runtime/tests/checker/transactions_test.go b/runtime/tests/checker/transactions_test.go index f3c512be2d..c36b1a560d 100644 --- a/runtime/tests/checker/transactions_test.go +++ b/runtime/tests/checker/transactions_test.go @@ -477,10 +477,13 @@ func TestCheckInvalidTransactionSelfMoveReturnFromFunction(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) + errs := RequireCheckerErrors(t, err, 2) + + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, errs[0], &invalidMoveError) - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - typeMismatchErr := errs[0].(*sema.TypeMismatchError) + require.IsType(t, &sema.TypeMismatchError{}, errs[1]) + typeMismatchErr := errs[1].(*sema.TypeMismatchError) assert.Equal(t, sema.VoidType, typeMismatchErr.ExpectedType) assert.IsType(t, &sema.TransactionType{}, typeMismatchErr.ActualType) diff --git a/runtime/tests/interpreter/composite_value_test.go b/runtime/tests/interpreter/composite_value_test.go index 07c1106fae..21ed849bb9 100644 --- a/runtime/tests/interpreter/composite_value_test.go +++ b/runtime/tests/interpreter/composite_value_test.go @@ -194,7 +194,17 @@ func TestInterpretContractTransfer(t *testing.T) { `, value, ) - inter, _ := testAccount(t, address, true, nil, code, sema.Config{}) + inter, _ := testAccountWithErrorHandler( + t, + address, + true, + nil, + code, + sema.Config{}, + func(err error) { + var invalidMoveError *sema.InvalidMoveError + require.ErrorAs(t, err, &invalidMoveError) + }) _, err := inter.Invoke("test") RequireError(t, err) diff --git a/runtime/tests/interpreter/condition_test.go b/runtime/tests/interpreter/condition_test.go index dc9dd9ebc9..0e9329697d 100644 --- a/runtime/tests/interpreter/condition_test.go +++ b/runtime/tests/interpreter/condition_test.go @@ -835,7 +835,7 @@ func TestInterpretInitializerWithInterfacePreCondition(t *testing.T) { // use the contract singleton, so it is loaded testFunction = ` access(all) fun test() { - TestImpl + TestImpl.NoOpFunc() } ` } else { @@ -878,6 +878,8 @@ func TestInterpretInitializerWithInterfacePreCondition(t *testing.T) { emit Foo(x: x) } } + + access(all) fun NoOpFunc() {} } %[2]s diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 516f8c62d2..b0057a6328 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -3955,7 +3955,9 @@ func TestInterpretCompositeNilEquality(t *testing.T) { for _, compositeKind := range common.AllCompositeKinds { - if compositeKind == common.CompositeKindEvent || compositeKind == common.CompositeKindAttachment { + if compositeKind == common.CompositeKindEvent || + compositeKind == common.CompositeKindAttachment || + compositeKind == common.CompositeKindContract { continue } diff --git a/runtime/tests/interpreter/transactions_test.go b/runtime/tests/interpreter/transactions_test.go index 06ae652752..5ac7c6ef36 100644 --- a/runtime/tests/interpreter/transactions_test.go +++ b/runtime/tests/interpreter/transactions_test.go @@ -25,7 +25,9 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" + "github.com/onflow/cadence/runtime/tests/checker" . "github.com/onflow/cadence/runtime/tests/utils" "github.com/onflow/cadence/runtime/ast" @@ -319,3 +321,119 @@ func TestInterpretTransactions(t *testing.T) { ) }) } + +func TestRuntimeInvalidTransferInExecute(t *testing.T) { + + t.Parallel() + + inter, _ := parseCheckAndInterpretWithOptions(t, ` + access(all) resource Dummy {} + + transaction { + var vaults: @[AnyResource] + var account: auth(Storage) &Account + + prepare(account: auth(Storage) &Account) { + self.vaults <- [<-create Dummy(), <-create Dummy()] + self.account = account + } + + execute { + let x = fun(): @[AnyResource] { + var x <- self.vaults <- [<-create Dummy()] + return <-x + } + + var t <- self.vaults[0] <- self.vaults + destroy t + self.account.storage.save(<- x(), to: /storage/x42) + } + } + `, ParseCheckAndInterpretOptions{ + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + require.IsType(t, &sema.ResourceCapturingError{}, errs[0]) + }, + }) + + signer1 := stdlib.NewAccountReferenceValue(nil, nil, interpreter.AddressValue{1}, interpreter.UnauthorizedAccess, interpreter.EmptyLocationRange) + err := inter.InvokeTransaction(0, signer1) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceError{}) +} + +func TestRuntimeInvalidRecursiveTransferInExecute(t *testing.T) { + + t.Parallel() + + t.Run("Array", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + transaction { + var arr: @[AnyResource] + + prepare() { + self.arr <- [] + } + + execute { + self.arr.append(<-self.arr) + } + } + `) + + err := inter.InvokeTransaction(0) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + }) + + t.Run("Dictionary", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + transaction { + var dict: @{String: AnyResource} + + prepare() { + self.dict <- {} + } + + execute { + destroy self.dict.insert(key: "", <-self.dict) + } + } + `) + + err := inter.InvokeTransaction(0) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + }) + + t.Run("resource", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + resource R { + fun foo(_ r: @R) { + destroy r + } + } + + transaction { + var r: @R + + prepare() { + self.r <- create R() + } + + execute { + self.r.foo(<-self.r) + } + } + `) + + err := inter.InvokeTransaction(0) + require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + }) +}