From 8743fae837305758afcf0cd34ca0f5bd00e71020 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 13 Sep 2023 14:13:20 -0400 Subject: [PATCH 01/48] remove parsing support for destructors --- runtime/ast/memberindices.go | 12 - runtime/ast/memberindices_test.go | 7 +- runtime/ast/members.go | 13 - runtime/common/declarationkind.go | 5 - runtime/common/declarationkind_string.go | 37 ++- runtime/interpreter/interpreter.go | 63 +--- runtime/parser/declaration.go | 5 +- runtime/parser/declaration_test.go | 303 ++------------------ runtime/sema/check_composite_declaration.go | 77 +---- runtime/sema/check_interface_declaration.go | 10 - 10 files changed, 56 insertions(+), 476 deletions(-) diff --git a/runtime/ast/memberindices.go b/runtime/ast/memberindices.go index 9279ad69df..93007a67c2 100644 --- a/runtime/ast/memberindices.go +++ b/runtime/ast/memberindices.go @@ -37,10 +37,6 @@ type memberIndices struct { _specialFunctions []*SpecialFunctionDeclaration // Use `Initializers()` instead _initializers []*SpecialFunctionDeclaration - // Semantically only one destructor is allowed, - // but the program might illegally declare multiple. - // Use `Destructors()` instead - _destructors []*SpecialFunctionDeclaration // Use `Functions()` _functions []*FunctionDeclaration // Use `FunctionsByIdentifier()` instead @@ -109,11 +105,6 @@ func (i *memberIndices) Initializers(declarations []Declaration) []*SpecialFunct return i._initializers } -func (i *memberIndices) Destructors(declarations []Declaration) []*SpecialFunctionDeclaration { - i.once.Do(i.initializer(declarations)) - return i._destructors -} - func (i *memberIndices) Fields(declarations []Declaration) []*FieldDeclaration { i.once.Do(i.initializer(declarations)) return i._fields @@ -175,7 +166,6 @@ func (i *memberIndices) init(declarations []Declaration) { i._functionsByIdentifier = make(map[string]*FunctionDeclaration) i._specialFunctions = make([]*SpecialFunctionDeclaration, 0) - i._destructors = make([]*SpecialFunctionDeclaration, 0) i._initializers = make([]*SpecialFunctionDeclaration, 0) i._composites = make([]*CompositeDeclaration, 0) @@ -211,8 +201,6 @@ func (i *memberIndices) init(declarations []Declaration) { switch declaration.Kind { case common.DeclarationKindInitializer: i._initializers = append(i._initializers, declaration) - case common.DeclarationKindDestructor: - i._destructors = append(i._destructors, declaration) } case *EntitlementDeclaration: diff --git a/runtime/ast/memberindices_test.go b/runtime/ast/memberindices_test.go index 8d18b389c5..978e27b495 100644 --- a/runtime/ast/memberindices_test.go +++ b/runtime/ast/memberindices_test.go @@ -54,10 +54,7 @@ func TestMemberIndices(t *testing.T) { specialFunctionA := &SpecialFunctionDeclaration{ Kind: common.DeclarationKindInitializer, } - specialFunctionB := &SpecialFunctionDeclaration{ - Kind: common.DeclarationKindDestructor, - } - specialFunctionC := &SpecialFunctionDeclaration{} + specialFunctionB := &SpecialFunctionDeclaration{} compositeA := &CompositeDeclaration{ Identifier: Identifier{Identifier: "A"}, @@ -98,7 +95,6 @@ func TestMemberIndices(t *testing.T) { interfaceB, compositeA, functionB, - specialFunctionC, compositeB, specialFunctionA, interfaceA, @@ -141,7 +137,6 @@ func TestMemberIndices(t *testing.T) { require.Equal(t, []*SpecialFunctionDeclaration{ specialFunctionB, - specialFunctionC, specialFunctionA, }, members.SpecialFunctions(), diff --git a/runtime/ast/members.go b/runtime/ast/members.go index f3a108e898..a438d67566 100644 --- a/runtime/ast/members.go +++ b/runtime/ast/members.go @@ -116,19 +116,6 @@ func (m *Members) Initializers() []*SpecialFunctionDeclaration { return m.indices.Initializers(m.declarations) } -func (m *Members) Destructors() []*SpecialFunctionDeclaration { - return m.indices.Destructors(m.declarations) -} - -// Destructor returns the first destructor, if any -func (m *Members) Destructor() *SpecialFunctionDeclaration { - destructors := m.Destructors() - if len(destructors) == 0 { - return nil - } - return destructors[0] -} - func (m *Members) FieldPosition(name string, compositeKind common.CompositeKind) Position { if compositeKind == common.CompositeKindEvent { parameters := m.Initializers()[0].FunctionDeclaration.ParameterList.ParametersByIdentifier() diff --git a/runtime/common/declarationkind.go b/runtime/common/declarationkind.go index ad037f6b43..70ca5f879b 100644 --- a/runtime/common/declarationkind.go +++ b/runtime/common/declarationkind.go @@ -44,7 +44,6 @@ const ( DeclarationKindEvent DeclarationKindField DeclarationKindInitializer - DeclarationKindDestructor DeclarationKindStructureInterface DeclarationKindResourceInterface DeclarationKindContractInterface @@ -117,8 +116,6 @@ func (k DeclarationKind) Name() string { return "field" case DeclarationKindInitializer: return "initializer" - case DeclarationKindDestructor: - return "destructor" case DeclarationKindAttachment: return "attachment" case DeclarationKindStructureInterface: @@ -176,8 +173,6 @@ func (k DeclarationKind) Keywords() string { return "event" case DeclarationKindInitializer: return "init" - case DeclarationKindDestructor: - return "destroy" case DeclarationKindAttachment: return "attachment" case DeclarationKindStructureInterface: diff --git a/runtime/common/declarationkind_string.go b/runtime/common/declarationkind_string.go index d59291eac0..4918be2db6 100644 --- a/runtime/common/declarationkind_string.go +++ b/runtime/common/declarationkind_string.go @@ -22,28 +22,27 @@ func _() { _ = x[DeclarationKindEvent-11] _ = x[DeclarationKindField-12] _ = x[DeclarationKindInitializer-13] - _ = x[DeclarationKindDestructor-14] - _ = x[DeclarationKindStructureInterface-15] - _ = x[DeclarationKindResourceInterface-16] - _ = x[DeclarationKindContractInterface-17] - _ = x[DeclarationKindEntitlement-18] - _ = x[DeclarationKindEntitlementMapping-19] - _ = x[DeclarationKindImport-20] - _ = x[DeclarationKindSelf-21] - _ = x[DeclarationKindBase-22] - _ = x[DeclarationKindTransaction-23] - _ = x[DeclarationKindPrepare-24] - _ = x[DeclarationKindExecute-25] - _ = x[DeclarationKindTypeParameter-26] - _ = x[DeclarationKindPragma-27] - _ = x[DeclarationKindEnum-28] - _ = x[DeclarationKindEnumCase-29] - _ = x[DeclarationKindAttachment-30] + _ = x[DeclarationKindStructureInterface-14] + _ = x[DeclarationKindResourceInterface-15] + _ = x[DeclarationKindContractInterface-16] + _ = x[DeclarationKindEntitlement-17] + _ = x[DeclarationKindEntitlementMapping-18] + _ = x[DeclarationKindImport-19] + _ = x[DeclarationKindSelf-20] + _ = x[DeclarationKindBase-21] + _ = x[DeclarationKindTransaction-22] + _ = x[DeclarationKindPrepare-23] + _ = x[DeclarationKindExecute-24] + _ = x[DeclarationKindTypeParameter-25] + _ = x[DeclarationKindPragma-26] + _ = x[DeclarationKindEnum-27] + _ = x[DeclarationKindEnumCase-28] + _ = x[DeclarationKindAttachment-29] } -const _DeclarationKind_name = "DeclarationKindUnknownDeclarationKindValueDeclarationKindFunctionDeclarationKindVariableDeclarationKindConstantDeclarationKindTypeDeclarationKindParameterDeclarationKindArgumentLabelDeclarationKindStructureDeclarationKindResourceDeclarationKindContractDeclarationKindEventDeclarationKindFieldDeclarationKindInitializerDeclarationKindDestructorDeclarationKindStructureInterfaceDeclarationKindResourceInterfaceDeclarationKindContractInterfaceDeclarationKindEntitlementDeclarationKindEntitlementMappingDeclarationKindImportDeclarationKindSelfDeclarationKindBaseDeclarationKindTransactionDeclarationKindPrepareDeclarationKindExecuteDeclarationKindTypeParameterDeclarationKindPragmaDeclarationKindEnumDeclarationKindEnumCaseDeclarationKindAttachment" +const _DeclarationKind_name = "DeclarationKindUnknownDeclarationKindValueDeclarationKindFunctionDeclarationKindVariableDeclarationKindConstantDeclarationKindTypeDeclarationKindParameterDeclarationKindArgumentLabelDeclarationKindStructureDeclarationKindResourceDeclarationKindContractDeclarationKindEventDeclarationKindFieldDeclarationKindInitializerDeclarationKindStructureInterfaceDeclarationKindResourceInterfaceDeclarationKindContractInterfaceDeclarationKindEntitlementDeclarationKindEntitlementMappingDeclarationKindImportDeclarationKindSelfDeclarationKindBaseDeclarationKindTransactionDeclarationKindPrepareDeclarationKindExecuteDeclarationKindTypeParameterDeclarationKindPragmaDeclarationKindEnumDeclarationKindEnumCaseDeclarationKindAttachment" -var _DeclarationKind_index = [...]uint16{0, 22, 42, 65, 88, 111, 130, 154, 182, 206, 229, 252, 272, 292, 318, 343, 376, 408, 440, 466, 499, 520, 539, 558, 584, 606, 628, 656, 677, 696, 719, 744} +var _DeclarationKind_index = [...]uint16{0, 22, 42, 65, 88, 111, 130, 154, 182, 206, 229, 252, 272, 292, 318, 351, 383, 415, 441, 474, 495, 514, 533, 559, 581, 603, 631, 652, 671, 694, 719} func (i DeclarationKind) String() string { if i >= DeclarationKind(len(_DeclarationKind_index)-1) { diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 55be367a38..a7a53cc1da 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -188,7 +188,6 @@ type FunctionWrapper = func(inner FunctionValue) FunctionValue // i.e. they wrap the functions / function wrappers that inherit them. type WrapperCode struct { InitializerFunctionWrapper FunctionWrapper - DestructorFunctionWrapper FunctionWrapper FunctionWrappers map[string]FunctionWrapper Functions map[string]FunctionValue } @@ -1149,15 +1148,6 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( initializerFunction = initializerFunctionWrapper(initializerFunction) } - // Wrap destructor - - destructorFunctionWrapper := - code.DestructorFunctionWrapper - - if destructorFunctionWrapper != nil { - destructorFunction = destructorFunctionWrapper(destructorFunction) - } - // Apply default functions, if conforming type does not provide the function // Iterating over the map in a non-deterministic way is OK, @@ -1541,41 +1531,15 @@ func (interpreter *Interpreter) compositeDestructorFunction( lexicalScope *VariableActivation, ) *InterpretedFunctionValue { - destructor := compositeDeclaration.DeclarationMembers().Destructor() - if destructor == nil { - return nil - } - - statements := destructor.FunctionDeclaration.FunctionBlock.Block.Statements - - var preConditions ast.Conditions - - conditions := destructor.FunctionDeclaration.FunctionBlock.PreConditions - if conditions != nil { - preConditions = *conditions - } - - var beforeStatements []ast.Statement - var rewrittenPostConditions ast.Conditions - - postConditions := destructor.FunctionDeclaration.FunctionBlock.PostConditions - if postConditions != nil { - postConditionsRewrite := - interpreter.Program.Elaboration.PostConditionsRewrite(postConditions) - - beforeStatements = postConditionsRewrite.BeforeStatements - rewrittenPostConditions = postConditionsRewrite.RewrittenPostConditions - } - return NewInterpretedFunctionValue( interpreter, nil, emptyImpureFunctionType, lexicalScope, - beforeStatements, - preConditions, - statements, - rewrittenPostConditions, + []ast.Statement{}, + ast.Conditions{}, + []ast.Statement{}, + ast.Conditions{}, ) } @@ -2234,13 +2198,11 @@ func (interpreter *Interpreter) declareInterface( interfaceType.InitializerParameters, lexicalScope, ) - destructorFunctionWrapper := interpreter.destructorFunctionWrapper(declaration.Members, lexicalScope) functionWrappers := interpreter.functionWrappers(declaration.Members, lexicalScope) defaultFunctions := interpreter.defaultFunctions(declaration.Members, lexicalScope) interpreter.SharedState.typeCodes.InterfaceCodes[typeID] = WrapperCode{ InitializerFunctionWrapper: initializerFunctionWrapper, - DestructorFunctionWrapper: destructorFunctionWrapper, FunctionWrappers: functionWrappers, Functions: defaultFunctions, } @@ -2278,23 +2240,6 @@ var voidFunctionType = &sema.FunctionType{ ReturnTypeAnnotation: sema.VoidTypeAnnotation, } -func (interpreter *Interpreter) destructorFunctionWrapper( - members *ast.Members, - lexicalScope *VariableActivation, -) FunctionWrapper { - - destructor := members.Destructor() - if destructor == nil { - return nil - } - - return interpreter.functionConditionsWrapper( - destructor.FunctionDeclaration, - voidFunctionType, - lexicalScope, - ) -} - func (interpreter *Interpreter) functionConditionsWrapper( declaration *ast.FunctionDeclaration, functionType *sema.FunctionType, diff --git a/runtime/parser/declaration.go b/runtime/parser/declaration.go index 2bec673b50..e36e442375 100644 --- a/runtime/parser/declaration.go +++ b/runtime/parser/declaration.go @@ -1884,10 +1884,7 @@ func parseSpecialFunctionDeclaration( declarationKind = common.DeclarationKindInitializer case KeywordDestroy: - if purity == ast.FunctionPurityView { - return nil, NewSyntaxError(*purityPos, "invalid view annotation on destructor") - } - declarationKind = common.DeclarationKindDestructor + p.report(NewSyntaxError(identifier.Pos, "custom destructor definitions are no longer permitted")) case KeywordPrepare: declarationKind = common.DeclarationKindPrepare diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index 7a36b96683..eac5a5f3a9 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -3479,25 +3479,6 @@ func TestParseCompositeDeclaration(t *testing.T) { ) }) - t.Run("resource with view destructor", func(t *testing.T) { - - t.Parallel() - - _, errs := testParseDeclarations(`resource S { - view destroy() {} - }`) - - utils.AssertEqualWithDiff(t, - []error{ - &SyntaxError{ - Message: "invalid view annotation on destructor", - Pos: ast.Position{Offset: 17, Line: 2, Column: 3}, - }, - }, - errs, - ) - }) - t.Run("resource with view field", func(t *testing.T) { t.Parallel() @@ -3801,7 +3782,6 @@ func TestParseAttachmentDeclaration(t *testing.T) { result, errs := testParseDeclarations(`access(all) attachment E for S { access(all) var foo: Int init() {} - destroy() {} access(all) fun getFoo(): Int {} }`) require.Empty(t, errs) @@ -3922,67 +3902,17 @@ func TestParseAttachmentDeclaration(t *testing.T) { }, Kind: 0xd, }, - &ast.SpecialFunctionDeclaration{ - FunctionDeclaration: &ast.FunctionDeclaration{ - ParameterList: &ast.ParameterList{ - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 84, - Line: 4, - Column: 10, - }, - EndPos: ast.Position{ - Offset: 85, - Line: 4, - Column: 11, - }, - }, - }, - FunctionBlock: &ast.FunctionBlock{ - Block: &ast.Block{ - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 87, - Line: 4, - Column: 13, - }, - EndPos: ast.Position{ - Offset: 88, - Line: 4, - Column: 14, - }, - }, - }, - }, - Identifier: ast.Identifier{ - Identifier: "destroy", - Pos: ast.Position{ - Offset: 77, - Line: 4, - Column: 3, - }, - }, - StartPos: ast.Position{ - Offset: 77, - Line: 4, - Column: 3, - }, - Access: ast.AccessNotSpecified, - Flags: 0x00, - }, - Kind: 0xe, - }, &ast.FunctionDeclaration{ ParameterList: &ast.ParameterList{ Range: ast.Range{ StartPos: ast.Position{ - Offset: 115, - Line: 5, + Offset: 99, + Line: 4, Column: 25, }, EndPos: ast.Position{ - Offset: 116, - Line: 5, + Offset: 100, + Line: 4, Column: 26, }, }, @@ -3992,15 +3922,15 @@ func TestParseAttachmentDeclaration(t *testing.T) { Identifier: ast.Identifier{ Identifier: "Int", Pos: ast.Position{ - Offset: 119, - Line: 5, + Offset: 103, + Line: 4, Column: 29, }, }, }, StartPos: ast.Position{ - Offset: 119, - Line: 5, + Offset: 103, + Line: 4, Column: 29, }, IsResource: false, @@ -4009,13 +3939,13 @@ func TestParseAttachmentDeclaration(t *testing.T) { Block: &ast.Block{ Range: ast.Range{ StartPos: ast.Position{ - Offset: 123, - Line: 5, + Offset: 107, + Line: 4, Column: 33, }, EndPos: ast.Position{ - Offset: 124, - Line: 5, + Offset: 108, + Line: 4, Column: 34, }, }, @@ -4024,14 +3954,14 @@ func TestParseAttachmentDeclaration(t *testing.T) { Identifier: ast.Identifier{ Identifier: "getFoo", Pos: ast.Position{ - Offset: 109, - Line: 5, + Offset: 93, + Line: 4, Column: 19, }, }, StartPos: ast.Position{ - Offset: 93, - Line: 5, + Offset: 77, + Line: 4, Column: 3, }, Access: ast.AccessAll, @@ -4045,8 +3975,8 @@ func TestParseAttachmentDeclaration(t *testing.T) { Column: 0, }, EndPos: ast.Position{ - Offset: 128, - Line: 6, + Offset: 112, + Line: 5, Column: 2, }, }, @@ -4063,7 +3993,6 @@ func TestParseAttachmentDeclaration(t *testing.T) { result, errs := testParseDeclarations(`access(all) attachment E for S { require entitlement X require entitlement Y - destroy() {} }`) require.Empty(t, errs) @@ -4111,59 +4040,7 @@ func TestParseAttachmentDeclaration(t *testing.T) { }, }, }, - Members: ast.NewUnmeteredMembers( - []ast.Declaration{ - &ast.SpecialFunctionDeclaration{ - FunctionDeclaration: &ast.FunctionDeclaration{ - ParameterList: &ast.ParameterList{ - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 93, - Line: 4, - Column: 10, - }, - EndPos: ast.Position{ - Offset: 94, - Line: 4, - Column: 11, - }, - }, - }, - FunctionBlock: &ast.FunctionBlock{ - Block: &ast.Block{ - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 96, - Line: 4, - Column: 13, - }, - EndPos: ast.Position{ - Offset: 97, - Line: 4, - Column: 14, - }, - }, - }, - }, - Identifier: ast.Identifier{ - Identifier: "destroy", - Pos: ast.Position{ - Offset: 86, - Line: 4, - Column: 3, - }, - }, - StartPos: ast.Position{ - Offset: 86, - Line: 4, - Column: 3, - }, - Access: ast.AccessNotSpecified, - }, - Kind: 0xe, - }, - }, - ), + Members: ast.NewUnmeteredMembers(nil), Range: ast.Range{ StartPos: ast.Position{ Offset: 0, @@ -4171,8 +4048,8 @@ func TestParseAttachmentDeclaration(t *testing.T) { Column: 0, }, EndPos: ast.Position{ - Offset: 101, - Line: 5, + Offset: 85, + Line: 4, Column: 2, }, }, @@ -4408,8 +4285,6 @@ func TestParseInterfaceDeclaration(t *testing.T) { access(all) fun getFoo(): Int access(all) fun getBar(): Int {} - - destroy() {} } `) @@ -4645,56 +4520,6 @@ func TestParseInterfaceDeclaration(t *testing.T) { }, Access: ast.AccessAll, }, - &ast.SpecialFunctionDeclaration{ - FunctionDeclaration: &ast.FunctionDeclaration{ - ParameterList: &ast.ParameterList{ - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 219, - Line: 11, - Column: 21, - }, - EndPos: ast.Position{ - Offset: 220, - Line: 11, - Column: 22, - }, - }, - }, - FunctionBlock: &ast.FunctionBlock{ - Block: &ast.Block{ - Range: ast.Range{ - StartPos: ast.Position{ - Offset: 222, - Line: 11, - Column: 24, - }, - EndPos: ast.Position{ - Offset: 223, - Line: 11, - Column: 25, - }, - }, - }, - }, - DocString: "", - Identifier: ast.Identifier{ - Identifier: "destroy", - Pos: ast.Position{ - Offset: 212, - Line: 11, - Column: 14, - }, - }, - StartPos: ast.Position{ - Offset: 212, - Line: 11, - Column: 14, - }, - Access: ast.AccessNotSpecified, - }, - Kind: 0xe, - }, }, ), Identifier: ast.Identifier{ @@ -4712,8 +4537,8 @@ func TestParseInterfaceDeclaration(t *testing.T) { Column: 10, }, EndPos: ast.Position{ - Offset: 235, - Line: 12, + Offset: 207, + Line: 10, Column: 10, }, }, @@ -7772,54 +7597,15 @@ func TestParseDestructor(t *testing.T) { destroy() {} } ` - result, errs := testParseProgram(code) - require.Empty(t, errs) - + _, errs := testParseDeclarations(code) utils.AssertEqualWithDiff(t, - []ast.Declaration{ - &ast.CompositeDeclaration{ - Access: ast.AccessNotSpecified, - CompositeKind: common.CompositeKindResource, - Identifier: ast.Identifier{ - Identifier: "Test", - Pos: ast.Position{Offset: 18, Line: 2, Column: 17}, - }, - Members: ast.NewUnmeteredMembers( - []ast.Declaration{ - &ast.SpecialFunctionDeclaration{ - Kind: common.DeclarationKindDestructor, - FunctionDeclaration: &ast.FunctionDeclaration{ - Access: ast.AccessNotSpecified, - Identifier: ast.Identifier{ - Identifier: "destroy", - Pos: ast.Position{Offset: 37, Line: 3, Column: 12}, - }, - ParameterList: &ast.ParameterList{ - Range: ast.Range{ - StartPos: ast.Position{Offset: 44, Line: 3, Column: 19}, - EndPos: ast.Position{Offset: 45, Line: 3, Column: 20}, - }, - }, - FunctionBlock: &ast.FunctionBlock{ - Block: &ast.Block{ - Range: ast.Range{ - StartPos: ast.Position{Offset: 47, Line: 3, Column: 22}, - EndPos: ast.Position{Offset: 48, Line: 3, Column: 23}, - }, - }, - }, - StartPos: ast.Position{Offset: 37, Line: 3, Column: 12}, - }, - }, - }, - ), - Range: ast.Range{ - StartPos: ast.Position{Offset: 9, Line: 2, Column: 8}, - EndPos: ast.Position{Offset: 58, Line: 4, Column: 8}, - }, + []error{ + &SyntaxError{ + Message: "custom destructor definitions are no longer permitted", + Pos: ast.Position{Offset: 37, Line: 3, Column: 12}, }, }, - result.Declarations(), + errs, ) } @@ -9086,9 +8872,6 @@ func TestParseMemberDocStrings(t *testing.T) { /// initNoBlock init() - - /// destroyWithBlock - destroy() {} } `) @@ -9141,37 +8924,11 @@ func TestParseMemberDocStrings(t *testing.T) { StartPos: ast.Position{Offset: 121, Line: 8, Column: 14}, }, }, - &ast.SpecialFunctionDeclaration{ - Kind: common.DeclarationKindDestructor, - FunctionDeclaration: &ast.FunctionDeclaration{ - Access: ast.AccessNotSpecified, - DocString: " destroyWithBlock", - Identifier: ast.Identifier{ - Identifier: "destroy", - Pos: ast.Position{Offset: 178, Line: 11, Column: 14}, - }, - ParameterList: &ast.ParameterList{ - Range: ast.Range{ - StartPos: ast.Position{Offset: 185, Line: 11, Column: 21}, - EndPos: ast.Position{Offset: 186, Line: 11, Column: 22}, - }, - }, - FunctionBlock: &ast.FunctionBlock{ - Block: &ast.Block{ - Range: ast.Range{ - StartPos: ast.Position{Offset: 188, Line: 11, Column: 24}, - EndPos: ast.Position{Offset: 189, Line: 11, Column: 25}, - }, - }, - }, - StartPos: ast.Position{Offset: 178, Line: 11, Column: 14}, - }, - }, }, ), Range: ast.Range{ StartPos: ast.Position{Offset: 11, Line: 2, Column: 10}, - EndPos: ast.Position{Offset: 201, Line: 12, Column: 10}, + EndPos: ast.Position{Offset: 138, Line: 9, Column: 10}, }, }, }, diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 6ee76438f3..7a0296bddf 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -267,20 +267,6 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL ) } - // NOTE: check destructors after initializer and functions - - checker.withSelfResourceInvalidationAllowed(func() { - checker.checkDestructors( - members.Destructors(), - members.FieldsByIdentifier(), - compositeType.Members, - compositeType, - declaration.DeclarationKind(), - declaration.DeclarationDocString(), - ContainerKindComposite, - ) - }) - // NOTE: visit entitlements, then interfaces, then composites // DON'T use `nestedDeclarations`, because of non-deterministic order @@ -2288,8 +2274,7 @@ func (checker *Checker) checkNestedIdentifier( // TODO: provide a more helpful error switch name { - case common.DeclarationKindInitializer.Keywords(), - common.DeclarationKindDestructor.Keywords(): + case common.DeclarationKindInitializer.Keywords(): checker.report( &InvalidNameError{ @@ -2330,7 +2315,7 @@ func (checker *Checker) VisitEnumCaseDeclaration(_ *ast.EnumCaseDeclaration) str func (checker *Checker) checkUnknownSpecialFunctions(functions []*ast.SpecialFunctionDeclaration) { for _, function := range functions { switch function.Kind { - case common.DeclarationKindInitializer, common.DeclarationKindDestructor: + case common.DeclarationKindInitializer: continue default: @@ -2359,64 +2344,6 @@ func (checker *Checker) checkSpecialFunctionDefaultImplementation(declaration as } } -func (checker *Checker) checkDestructors( - destructors []*ast.SpecialFunctionDeclaration, - fields map[string]*ast.FieldDeclaration, - members *StringMemberOrderedMap, - containerType CompositeKindedType, - containerDeclarationKind common.DeclarationKind, - containerDocString string, - containerKind ContainerKind, -) { - count := len(destructors) - - // only resource and resource interface declarations may - // declare a destructor - - if !containerType.IsResourceType() { - if count > 0 { - firstDestructor := destructors[0] - - checker.report( - &InvalidDestructorError{ - Range: ast.NewRangeFromPositioned( - checker.memoryGauge, - firstDestructor.FunctionDeclaration.Identifier, - ), - }, - ) - } - - return - } - - if count == 0 { - checker.checkNoDestructorNoResourceFields(members, fields, containerType, containerKind) - return - } - - firstDestructor := destructors[0] - checker.checkDestructor( - firstDestructor, - containerType, - containerDocString, - containerKind, - ) - - // destructor overloading is not supported - - if count > 1 { - secondDestructor := destructors[1] - - checker.report( - &UnsupportedOverloadingError{ - DeclarationKind: common.DeclarationKindDestructor, - Range: ast.NewRangeFromPositioned(checker.memoryGauge, secondDestructor), - }, - ) - } -} - // checkNoDestructorNoResourceFields checks that if there is no destructor there are // also no fields which have a resource type – otherwise those fields will be lost. // In interfaces this is allowed. diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index ea93bc7e6f..4f084ad608 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -134,16 +134,6 @@ func (checker *Checker) VisitInterfaceDeclaration(declaration *ast.InterfaceDecl fieldPositionGetter, ) - checker.checkDestructors( - declaration.Members.Destructors(), - declaration.Members.FieldsByIdentifier(), - interfaceType.Members, - interfaceType, - declaration.DeclarationKind(), - declaration.DeclarationDocString(), - kind, - ) - // NOTE: visit entitlements, then interfaces, then composites // DON'T use `nestedDeclarations`, because of non-deterministic order From b6476dfd6add976a7d2ac032131329508d1907ff Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 13 Sep 2023 16:10:22 -0400 Subject: [PATCH 02/48] remove typechecking support for destructors --- encoding/ccf/ccf_test.go | 4 - encoding/json/encoding_test.go | 4 - runtime/convertValues_test.go | 4 - runtime/ft_test.go | 4 - runtime/sema/check_composite_declaration.go | 77 ----- runtime/sema/errors.go | 70 ----- runtime/tests/checker/access_test.go | 8 - runtime/tests/checker/attachments_test.go | 66 +--- runtime/tests/checker/composite_test.go | 315 +------------------ runtime/tests/checker/conditions_test.go | 4 - runtime/tests/checker/initialization_test.go | 8 - runtime/tests/checker/nft_test.go | 15 - runtime/tests/checker/overloading_test.go | 38 --- runtime/tests/checker/reference_test.go | 12 - runtime/tests/checker/resources_test.go | 98 +----- runtime/tests/checker/storable_test.go | 12 - runtime/tests/checker/swap_test.go | 12 - 17 files changed, 10 insertions(+), 741 deletions(-) diff --git a/encoding/ccf/ccf_test.go b/encoding/ccf/ccf_test.go index 4c70554cbe..dab5c06f44 100644 --- a/encoding/ccf/ccf_test.go +++ b/encoding/ccf/ccf_test.go @@ -5218,10 +5218,6 @@ func TestEncodeResource(t *testing.T) { init(bar: @Bar) { self.bar <- bar } - - destroy() { - destroy self.bar - } } fun main(): @Foo { diff --git a/encoding/json/encoding_test.go b/encoding/json/encoding_test.go index 57b9177c39..46cca7db4f 100644 --- a/encoding/json/encoding_test.go +++ b/encoding/json/encoding_test.go @@ -1264,10 +1264,6 @@ func TestEncodeResource(t *testing.T) { init(bar: @Bar) { self.bar <- bar } - - destroy() { - destroy self.bar - } } fun main(): @Foo { diff --git a/runtime/convertValues_test.go b/runtime/convertValues_test.go index 8f6c898911..c97f2d1c82 100644 --- a/runtime/convertValues_test.go +++ b/runtime/convertValues_test.go @@ -1688,10 +1688,6 @@ func TestRuntimeExportNestedResourceValueFromScript(t *testing.T) { init(bar: @Bar) { self.bar <- bar } - - destroy() { - destroy self.bar - } } access(all) fun main(): @Foo { diff --git a/runtime/ft_test.go b/runtime/ft_test.go index a8ffbde2c2..3334085f0e 100644 --- a/runtime/ft_test.go +++ b/runtime/ft_test.go @@ -272,10 +272,6 @@ access(all) contract FlowToken: FungibleToken { vault.balance = 0.0 destroy vault } - - destroy() { - FlowToken.totalSupply = FlowToken.totalSupply - self.balance - } } // createEmptyVault diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 7a0296bddf..14f9581f66 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2344,83 +2344,6 @@ func (checker *Checker) checkSpecialFunctionDefaultImplementation(declaration as } } -// checkNoDestructorNoResourceFields checks that if there is no destructor there are -// also no fields which have a resource type – otherwise those fields will be lost. -// In interfaces this is allowed. -func (checker *Checker) checkNoDestructorNoResourceFields( - members *StringMemberOrderedMap, - fields map[string]*ast.FieldDeclaration, - containerType Type, - containerKind ContainerKind, -) { - if containerKind == ContainerKindInterface { - return - } - - for pair := members.Oldest(); pair != nil; pair = pair.Next() { - member := pair.Value - memberName := pair.Key - - // NOTE: check type, not resource annotation: - // the field could have a wrong annotation - if !member.TypeAnnotation.Type.IsResourceType() { - continue - } - - checker.report( - &MissingDestructorError{ - ContainerType: containerType, - FirstFieldName: memberName, - FirstFieldPos: fields[memberName].Identifier.Pos, - }, - ) - - // only report for first member - return - } -} - -func (checker *Checker) checkDestructor( - destructor *ast.SpecialFunctionDeclaration, - containerType CompositeKindedType, - containerDocString string, - containerKind ContainerKind, -) { - - if len(destructor.FunctionDeclaration.ParameterList.Parameters) != 0 { - checker.report( - &InvalidDestructorParametersError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, destructor.FunctionDeclaration.ParameterList), - }, - ) - } - - parameters := checker.parameters(destructor.FunctionDeclaration.ParameterList) - - checker.checkSpecialFunction( - destructor, - containerType, - containerDocString, - FunctionPurityImpure, - parameters, - containerKind, - nil, - ) - - checker.checkCompositeResourceInvalidated(containerType) -} - -// checkCompositeResourceInvalidated checks that if the container is a resource, -// that all resource fields are invalidated (moved or destroyed) -func (checker *Checker) checkCompositeResourceInvalidated(containerType Type) { - compositeType, isComposite := containerType.(*CompositeType) - if !isComposite || compositeType.Kind != common.CompositeKindResource { - return - } - - checker.checkResourceFieldsInvalidated(containerType, compositeType.Members) -} - // checkResourceFieldsInvalidated checks that all resource fields for a container // type are invalidated. func (checker *Checker) checkResourceFieldsInvalidated( diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index d15dce1be6..cdb52ed9d4 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -2539,76 +2539,6 @@ func (e *InvalidResourceAssignmentError) SecondaryError() string { return "consider force assigning (<-!) or swapping (<->)" } -// InvalidDestructorError - -type InvalidDestructorError struct { - ast.Range -} - -var _ SemanticError = &InvalidDestructorError{} -var _ errors.UserError = &InvalidDestructorError{} - -func (*InvalidDestructorError) isSemanticError() {} - -func (*InvalidDestructorError) IsUserError() {} - -func (e *InvalidDestructorError) Error() string { - return "cannot declare destructor for non-resource" -} - -// MissingDestructorError - -type MissingDestructorError struct { - ContainerType Type - FirstFieldName string - FirstFieldPos ast.Position -} - -var _ SemanticError = &MissingDestructorError{} -var _ errors.UserError = &MissingDestructorError{} - -func (*MissingDestructorError) isSemanticError() {} - -func (*MissingDestructorError) IsUserError() {} - -func (e *MissingDestructorError) Error() string { - return fmt.Sprintf( - "missing destructor for resource field `%s` in type `%s`", - e.FirstFieldName, - e.ContainerType.QualifiedString(), - ) -} - -func (e *MissingDestructorError) StartPosition() ast.Position { - return e.FirstFieldPos -} - -func (e *MissingDestructorError) EndPosition(memoryGauge common.MemoryGauge) ast.Position { - return e.FirstFieldPos.Shifted(memoryGauge, len(e.FirstFieldName)-1) -} - -// InvalidDestructorParametersError - -type InvalidDestructorParametersError struct { - ast.Range -} - -var _ SemanticError = &InvalidDestructorParametersError{} -var _ errors.UserError = &InvalidDestructorParametersError{} -var _ errors.SecondaryError = &InvalidDestructorParametersError{} - -func (*InvalidDestructorParametersError) isSemanticError() {} - -func (*InvalidDestructorParametersError) IsUserError() {} - -func (e *InvalidDestructorParametersError) Error() string { - return "invalid parameters for destructor" -} - -func (e *InvalidDestructorParametersError) SecondaryError() string { - return "consider removing these parameters" -} - // ResourceFieldNotInvalidatedError type ResourceFieldNotInvalidatedError struct { diff --git a/runtime/tests/checker/access_test.go b/runtime/tests/checker/access_test.go index 2851d52214..5c464dc5c7 100644 --- a/runtime/tests/checker/access_test.go +++ b/runtime/tests/checker/access_test.go @@ -1363,10 +1363,6 @@ func TestCheckAccessCompositeFieldVariableDeclarationWithSecondValue(t *testing. self.a <- create A() } - destroy() { - destroy self.a - } - access(all) fun test() { let oldA <- self.a <- create A() destroy oldA @@ -1471,10 +1467,6 @@ func TestCheckAccessInterfaceFieldVariableDeclarationWithSecondValue(t *testing. self.a <- create A() } - destroy() { - destroy self.a - } - access(all) fun test() { let oldA <- self.a <- create A() destroy oldA diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index a08cc339a5..9490a558d4 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -540,35 +540,12 @@ func TestCheckAttachmentWithMembers(t *testing.T) { init(x: @R) { self.x <- x } - destroy() { - destroy self.x - } }`, ) require.NoError(t, err) }) - t.Run("resource field no destroy", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R {} - attachment Test for R { - let x: @R - init(x: @R) { - self.x <- x - } - }`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.MissingDestructorError{}, errs[0]) - }) - t.Run("resource field in struct", func(t *testing.T) { t.Parallel() @@ -581,16 +558,12 @@ func TestCheckAttachmentWithMembers(t *testing.T) { init(x: @R) { self.x <- x } - destroy() { - destroy self.x - } }`, ) - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) assert.IsType(t, &sema.InvalidResourceFieldError{}, errs[0]) - assert.IsType(t, &sema.InvalidDestructorError{}, errs[1]) }) t.Run("field with same name as base type", func(t *testing.T) { @@ -639,29 +612,11 @@ func TestCheckAttachmentWithMembers(t *testing.T) { ` resource R {} attachment Test for R { - destroy() {} }`, ) require.NoError(t, err) }) - - t.Run("destroy in struct", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - struct S {} - attachment Test for S { - destroy() {} - }`, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.InvalidDestructorError{}, errs[0]) - }) } func TestCheckAttachmentConformance(t *testing.T) { @@ -1025,25 +980,6 @@ func TestCheckAttachmentBase(t *testing.T) { require.NoError(t, err) }) - t.Run("destroy", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - resource R { - fun foo() {} - } - attachment Test for R { - destroy() { - base.foo() - } - }`, - ) - - require.NoError(t, err) - }) - t.Run("interface base", func(t *testing.T) { t.Parallel() diff --git a/runtime/tests/checker/composite_test.go b/runtime/tests/checker/composite_test.go index 962b5da695..a5fb01bfe6 100644 --- a/runtime/tests/checker/composite_test.go +++ b/runtime/tests/checker/composite_test.go @@ -162,47 +162,6 @@ func TestCheckInitializerName(t *testing.T) { } } -func TestCheckDestructor(t *testing.T) { - - t.Parallel() - - for _, kind := range common.CompositeKindsWithFieldsAndFunctions { - - var baseType string - if kind == common.CompositeKindAttachment { - baseType = "for AnyResource" - } - - t.Run(kind.Keyword(), func(t *testing.T) { - - _, err := ParseAndCheck(t, - fmt.Sprintf( - ` - %s Test %s { - destroy() {} - } - `, - kind.Keyword(), - baseType, - ), - ) - - switch kind { - case common.CompositeKindStructure, common.CompositeKindContract: - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.InvalidDestructorError{}, errs[0]) - - case common.CompositeKindResource, common.CompositeKindAttachment: - require.NoError(t, err) - - default: - panic(errors.NewUnreachableError()) - } - }) - } -} - func TestCheckInvalidUnknownSpecialFunction(t *testing.T) { t.Parallel() @@ -289,16 +248,14 @@ func TestCheckInvalidCompositeFieldNames(t *testing.T) { ) if isInterface { - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) assert.IsType(t, &sema.InvalidNameError{}, errs[0]) - assert.IsType(t, &sema.InvalidNameError{}, errs[1]) } else { - errs := RequireCheckerErrors(t, err, 3) + errs := RequireCheckerErrors(t, err, 2) assert.IsType(t, &sema.InvalidNameError{}, errs[0]) - assert.IsType(t, &sema.InvalidNameError{}, errs[1]) - assert.IsType(t, &sema.MissingInitializerError{}, errs[2]) + assert.IsType(t, &sema.MissingInitializerError{}, errs[1]) } }) } @@ -597,7 +554,6 @@ func TestCheckInvalidCompositeSpecialFunction(t *testing.T) { ` %s Test %s { init() { X } - destroy() { Y } } `, kind.Keyword(), @@ -607,17 +563,15 @@ func TestCheckInvalidCompositeSpecialFunction(t *testing.T) { switch kind { case common.CompositeKindStructure, common.CompositeKindContract: - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) assert.IsType(t, &sema.NotDeclaredError{}, errs[0]) - assert.IsType(t, &sema.InvalidDestructorError{}, errs[1]) case common.CompositeKindResource, common.CompositeKindAttachment: - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) assert.IsType(t, &sema.NotDeclaredError{}, errs[0]) - assert.IsType(t, &sema.NotDeclaredError{}, errs[1]) default: panic(errors.NewUnreachableError()) @@ -672,7 +626,6 @@ func TestCheckCompositeInitializerSelfUse(t *testing.T) { ` %s Test %s { init() { self } - destroy() { self } } `, kind.Keyword(), @@ -682,17 +635,14 @@ func TestCheckCompositeInitializerSelfUse(t *testing.T) { switch kind { case common.CompositeKindStructure, common.CompositeKindContract, common.CompositeKindAttachment: - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.InvalidDestructorError{}, errs[0]) + require.NoError(t, err) case common.CompositeKindResource: - errs := RequireCheckerErrors(t, err, 2) + errs := RequireCheckerErrors(t, err, 1) // TODO: handle `self` properly assert.IsType(t, &sema.ResourceLossError{}, errs[0]) - assert.IsType(t, &sema.ResourceLossError{}, errs[1]) default: panic(errors.NewUnreachableError()) @@ -773,25 +723,7 @@ func TestCheckInvalidCompositeMissingInitializer(t *testing.T) { } } -func TestCheckInvalidResourceMissingDestructor(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - resource Test { - let test: @Test - init(test: @Test) { - self.test <- test - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.MissingDestructorError{}, errs[0]) -} - -func TestCheckResourceWithDestructor(t *testing.T) { +func TestCheckResourceWithResourceField(t *testing.T) { t.Parallel() @@ -802,10 +734,6 @@ func TestCheckResourceWithDestructor(t *testing.T) { init(test: @Test) { self.test <- test } - - destroy() { - destroy self.test - } } `) @@ -836,15 +764,6 @@ func TestCheckInvalidResourceFieldWithMissingResourceAnnotation(t *testing.T) { ` } - destructorBody := "" - if !isInterface { - destructorBody = ` - { - destroy self.test - } - ` - } - annotationType := "Test" if isInterface { annotationType = "{Test}" @@ -857,14 +776,11 @@ func TestCheckInvalidResourceFieldWithMissingResourceAnnotation(t *testing.T) { let test: %[2]s init(test: @%[2]s) %[3]s - - destroy() %[4]s } `, interfaceKeyword, annotationType, initializerBody, - destructorBody, ), ) @@ -1987,221 +1903,6 @@ func TestCheckCompositeReferenceBeforeDeclaration(t *testing.T) { } } -func TestCheckInvalidDestructorParameters(t *testing.T) { - - t.Parallel() - - interfacePossibilities := []bool{true, false} - - for _, isInterface := range interfacePossibilities { - - interfaceKeyword := "" - if isInterface { - interfaceKeyword = "interface" - } - - destructorBody := "" - if !isInterface { - destructorBody = "{}" - } - - t.Run(interfaceKeyword, func(t *testing.T) { - - _, err := ParseAndCheck(t, - fmt.Sprintf( - ` - resource %[1]s Test { - destroy(x: Int) %[2]s - } - `, - interfaceKeyword, - destructorBody, - ), - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.InvalidDestructorParametersError{}, errs[0]) - }) - } -} - -func TestCheckInvalidResourceWithDestructorMissingFieldInvalidation(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - resource Test { - let test: @Test - - init(test: @Test) { - self.test <- test - } - - destroy() {} - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ResourceFieldNotInvalidatedError{}, errs[0]) -} - -// This tests prevents a potential regression in `checkResourceFieldsInvalidated`: -// See https://github.com/dapperlabs/flow-go/issues/2533 -// -// The function contained a bug in which field invalidation was skipped for all remaining members -// once a non-resource member was encountered, instead of just skipping the non-resource member -// and continuing the check for the remaining members. - -func TestCheckInvalidResourceWithDestructorMissingFieldInvalidationFirstFieldNonResource(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - resource Test { - let a: Int - let b: @Test - - init(b: @Test) { - self.a = 1 - self.b <- b - } - - destroy() {} - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ResourceFieldNotInvalidatedError{}, errs[0]) -} - -func TestCheckInvalidResourceWithDestructorMissingDefinitiveFieldInvalidation(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - resource Test { - let test: @Test - - init(test: @Test) { - self.test <- test - } - - destroy() { - if false { - destroy self.test - } - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ResourceFieldNotInvalidatedError{}, errs[0]) -} - -func TestCheckResourceWithDestructorAndStructField(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - struct S {} - - resource Test { - let s: S - - init(s: S) { - self.s = s - } - - destroy() {} - } - `) - - require.NoError(t, err) -} - -func TestCheckInvalidResourceDestructorMoveInvalidation(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - resource Test { - let test: @Test - - init(test: @Test) { - self.test <- test - } - - destroy() { - absorb(<-self.test) - absorb(<-self.test) - } - } - - fun absorb(_ test: @Test) { - destroy test - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ResourceUseAfterInvalidationError{}, errs[0]) -} - -func TestCheckInvalidResourceDestructorRepeatedDestruction(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - resource Test { - let test: @Test - - init(test: @Test) { - self.test <- test - } - - destroy() { - destroy self.test - destroy self.test - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ResourceUseAfterInvalidationError{}, errs[0]) -} - -func TestCheckInvalidResourceDestructorCapturing(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - var duplicate: (fun(): @Test)? = nil - - resource Test { - let test: @Test - - init(test: @Test) { - self.test <- test - } - - destroy() { - duplicate = fun (): @Test { - return <-self.test - } - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ResourceCapturingError{}, errs[0]) -} - func TestCheckInvalidStructureFunctionWithMissingBody(t *testing.T) { t.Parallel() diff --git a/runtime/tests/checker/conditions_test.go b/runtime/tests/checker/conditions_test.go index 8960d89fc1..71bdddb321 100644 --- a/runtime/tests/checker/conditions_test.go +++ b/runtime/tests/checker/conditions_test.go @@ -905,10 +905,6 @@ func TestCheckFunctionWithPostTestConditionAndResourceResult(t *testing.T) { self.resources["duplicate"] <-! r return true } - - destroy() { - destroy self.resources - } } `) diff --git a/runtime/tests/checker/initialization_test.go b/runtime/tests/checker/initialization_test.go index 25411eeb97..d62ef838e1 100644 --- a/runtime/tests/checker/initialization_test.go +++ b/runtime/tests/checker/initialization_test.go @@ -205,10 +205,6 @@ func TestCheckInvalidRepeatedFieldInitialization(t *testing.T) { self.r <- create R() self.r <- create R() } - - destroy() { - destroy self.r - } } `) @@ -233,10 +229,6 @@ func TestCheckInvalidResourceMoveAfterInitialization(t *testing.T) { let r <- self.r destroy r } - - destroy() { - destroy self.r - } } `) diff --git a/runtime/tests/checker/nft_test.go b/runtime/tests/checker/nft_test.go index 6be756487c..8fa2b70edb 100644 --- a/runtime/tests/checker/nft_test.go +++ b/runtime/tests/checker/nft_test.go @@ -517,12 +517,6 @@ access(all) contract TopShot: NonFungibleToken { emit MomentMinted(momentID: self.id, playID: playID, setID: self.data.setID, serialNumber: self.data.serialNumber) } - - // If the Moment is destroyed, emit an event to indicate - // to outside ovbservers that it has been destroyed - destroy() { - emit MomentDestroyed(id: self.id) - } } // Admin is a special authorization resource that @@ -754,15 +748,6 @@ access(all) contract TopShot: NonFungibleToken { return nil } } - - // If a transaction destroys the Collection object, - // All the NFTs contained within are also destroyed! - // Much like when Damien Lillard destroys the hopes and - // dreams of the entire city of Houston. - // - destroy() { - destroy self.ownedNFTs - } } // ----------------------------------------------------------------------- diff --git a/runtime/tests/checker/overloading_test.go b/runtime/tests/checker/overloading_test.go index 75c4313a74..d7172edfc2 100644 --- a/runtime/tests/checker/overloading_test.go +++ b/runtime/tests/checker/overloading_test.go @@ -83,41 +83,3 @@ func TestCheckInvalidCompositeInitializerOverloading(t *testing.T) { } } } - -func TestCheckInvalidResourceDestructorOverloading(t *testing.T) { - - t.Parallel() - - interfacePossibilities := []bool{true, false} - - for _, isInterface := range interfacePossibilities { - - interfaceKeyword := "" - body := "" - if isInterface { - interfaceKeyword = "interface" - } else { - body = "{}" - } - - t.Run(interfaceKeyword, func(t *testing.T) { - - _, err := ParseAndCheck(t, - fmt.Sprintf( - ` - resource %[1]s X { - destroy() %[2]s - destroy(y: Int) %[2]s - } - `, - interfaceKeyword, - body, - ), - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.UnsupportedOverloadingError{}, errs[0]) - }) - } -} diff --git a/runtime/tests/checker/reference_test.go b/runtime/tests/checker/reference_test.go index 061a07002d..9e0b92c109 100644 --- a/runtime/tests/checker/reference_test.go +++ b/runtime/tests/checker/reference_test.go @@ -1854,9 +1854,6 @@ func TestCheckInvalidatedReferenceUse(t *testing.T) { init() { self.r2 <- create R2() } - destroy() { - destroy self.r2 - } } access(all) resource R2 { @@ -1864,9 +1861,6 @@ func TestCheckInvalidatedReferenceUse(t *testing.T) { init() { self.r3 <- create R3() } - destroy() { - destroy self.r3 - } } access(all) resource R3 { @@ -2602,9 +2596,6 @@ func TestCheckInvalidatedReferenceUse(t *testing.T) { init() { self.bar <-create Bar() } - destroy() { - destroy self.bar - } } resource Bar { @@ -2672,9 +2663,6 @@ func TestCheckInvalidatedReferenceUse(t *testing.T) { init() { self.bar <-create Bar() } - destroy() { - destroy self.bar - } } resource Bar { diff --git a/runtime/tests/checker/resources_test.go b/runtime/tests/checker/resources_test.go index f814147eee..10cdc2c1bd 100644 --- a/runtime/tests/checker/resources_test.go +++ b/runtime/tests/checker/resources_test.go @@ -618,15 +618,6 @@ func TestCheckFieldDeclarationWithResourceAnnotation(t *testing.T) { t.Parallel() - destructor := "" - if kind == common.CompositeKindResource { - destructor = ` - destroy() { - destroy self.t - } - ` - } - _, err := ParseAndCheck(t, fmt.Sprintf( ` @@ -637,13 +628,10 @@ func TestCheckFieldDeclarationWithResourceAnnotation(t *testing.T) { init(t: @T) { self.t %[2]s t } - - %[3]s } `, kind.Keyword(), kind.TransferOperator(), - destructor, ), ) @@ -690,15 +678,6 @@ func TestCheckFieldDeclarationWithoutResourceAnnotation(t *testing.T) { t.Parallel() - destructor := "" - if kind == common.CompositeKindResource { - destructor = ` - destroy() { - destroy self.t - } - ` - } - _, err := ParseAndCheck(t, fmt.Sprintf( ` @@ -709,13 +688,10 @@ func TestCheckFieldDeclarationWithoutResourceAnnotation(t *testing.T) { init(t: T) { self.t %[2]s t } - - %[3]s } `, kind.Keyword(), kind.TransferOperator(), - destructor, ), ) @@ -3142,18 +3118,6 @@ func testResourceNesting( ) } - destructor := "" - if !outerIsInterface && - outerCompositeKind == common.CompositeKindResource && - innerCompositeKind == common.CompositeKindResource { - - destructor = ` - destroy() { - destroy self.t - } - ` - } - innerBody := "{}" if innerCompositeKind == common.CompositeKindEvent { innerBody = "()" @@ -3169,12 +3133,11 @@ func testResourceNesting( program := fmt.Sprintf( ` - %[1]s %[2]s T%[10]s %[3]s + %[1]s %[2]s T%[9]s %[3]s %[4]s %[5]s U { let t: %[6]s%[7]s %[8]s - %[9]s } `, innerCompositeKind.Keyword(), @@ -3185,7 +3148,6 @@ func testResourceNesting( innerCompositeKind.Annotation(), innerTypeAnnotation, initializer, - destructor, innerConformances, ) @@ -3538,10 +3500,6 @@ func TestCheckInvalidResourceFieldMoveThroughVariableDeclaration(t *testing.T) { init(foo: @Foo) { self.foo <- foo } - - destroy() { - destroy self.foo - } } fun test(): @[Foo] { @@ -3577,10 +3535,6 @@ func TestCheckInvalidResourceFieldMoveThroughParameter(t *testing.T) { init(foo: @Foo) { self.foo <- foo } - - destroy() { - destroy self.foo - } } fun identity(_ foo: @Foo): @Foo { @@ -3621,10 +3575,6 @@ func TestCheckInvalidResourceFieldMoveSelf(t *testing.T) { fun test() { absorb(<-self.y) } - - destroy() { - destroy self.y - } } fun absorb(_ y: @Y) { @@ -3637,33 +3587,6 @@ func TestCheckInvalidResourceFieldMoveSelf(t *testing.T) { assert.IsType(t, &sema.InvalidNestedResourceMoveError{}, errs[0]) } -func TestCheckInvalidResourceFieldUseAfterDestroy(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - resource Y {} - - resource X { - - var y: @Y - - init() { - self.y <- create Y() - } - - destroy() { - destroy self.y - destroy self.y - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ResourceUseAfterInvalidationError{}, errs[0]) -} - func TestCheckResourceArrayAppend(t *testing.T) { t.Parallel() @@ -4042,10 +3965,6 @@ func TestCheckInvalidResourceConstantResourceFieldSwap(t *testing.T) { init(foo: @Foo) { self.foo <- foo } - - destroy() { - destroy self.foo - } } fun test() { @@ -4077,9 +3996,6 @@ func TestCheckResourceVariableResourceFieldSwap(t *testing.T) { self.foo <- foo } - destroy() { - destroy self.foo - } } fun test() { @@ -4108,10 +4024,6 @@ func TestCheckInvalidResourceFieldDestroy(t *testing.T) { init(foo: @Foo) { self.foo <- foo } - - destroy() { - destroy self.foo - } } fun test() { @@ -4205,10 +4117,6 @@ func TestCheckResourceFieldUseAndDestruction(t *testing.T) { let ri <- self.ris.remove(key: "first") absorb(<-ri) } - - destroy() { - destroy self.ris - } } fun absorb(_ ri: @{RI}?) { @@ -5762,10 +5670,6 @@ func TestCheckOptionalResourceBindingWithSecondValue(t *testing.T) { self.r <- create R() } - destroy () { - destroy self.r - } - fun duplicate(): @R? { if let r <- self.r <- nil { let r2 <- self.r <- nil diff --git a/runtime/tests/checker/storable_test.go b/runtime/tests/checker/storable_test.go index 1126507fee..25ee7dc4aa 100644 --- a/runtime/tests/checker/storable_test.go +++ b/runtime/tests/checker/storable_test.go @@ -270,7 +270,6 @@ func TestCheckStorable(t *testing.T) { var interfaceKeyword string var baseType string var initializer string - var destructor string if isInterface { interfaceKeyword = "interface" @@ -297,14 +296,6 @@ func TestCheckStorable(t *testing.T) { typeName, transferOperation.Operator(), ) - - if isResource { - destructor = ` - destroy() { - destroy self.value - } - ` - } } if compositeKind == common.CompositeKindAttachment { @@ -320,14 +311,11 @@ func TestCheckStorable(t *testing.T) { let value: %[1]s%[2]s %[3]s - - %[4]s } `, typeAnnotation, typeName, initializer, - destructor, ) } diff --git a/runtime/tests/checker/swap_test.go b/runtime/tests/checker/swap_test.go index ccb93a38ad..c5732bad2d 100644 --- a/runtime/tests/checker/swap_test.go +++ b/runtime/tests/checker/swap_test.go @@ -253,10 +253,6 @@ func TestCheckSwapResourceFields(t *testing.T) { init(x: @X) { self.x <- x } - - destroy() { - destroy self.x - } } fun test() { @@ -302,10 +298,6 @@ func TestCheckInvalidSwapConstantResourceFields(t *testing.T) { init(x: @X) { self.x <- x } - - destroy() { - destroy self.x - } } resource Z { @@ -314,10 +306,6 @@ func TestCheckInvalidSwapConstantResourceFields(t *testing.T) { init(x: @X) { self.x <- x } - - destroy() { - destroy self.x - } } fun test() { From ac178d1f1330c2aaf46ae012bfca89e654382808 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 15 Sep 2023 12:19:20 -0400 Subject: [PATCH 03/48] automatically delete nested resources on destroy --- runtime/interpreter/interpreter.go | 26 - runtime/interpreter/interpreter_expression.go | 2 - runtime/interpreter/value.go | 57 +- runtime/nft_test.go | 19 - runtime/parser/declaration_test.go | 8 +- runtime/resource_duplicate_test.go | 440 -------------- runtime/resourcedictionary_test.go | 43 +- runtime/runtime_test.go | 574 +----------------- runtime/storage_test.go | 12 - runtime/tests/interpreter/attachments_test.go | 120 +--- runtime/tests/interpreter/condition_test.go | 37 +- .../tests/interpreter/entitlements_test.go | 6 +- runtime/tests/interpreter/interpreter_test.go | 229 +------ runtime/tests/interpreter/member_test.go | 6 - runtime/tests/interpreter/reference_test.go | 15 - runtime/tests/interpreter/resources_test.go | 276 +-------- 16 files changed, 71 insertions(+), 1799 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index a7a53cc1da..a974a0d569 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -176,7 +176,6 @@ type CompositeTypeHandlerFunc func(location common.Location, typeID TypeID) *sem // these are the "leaf" nodes in the call chain, and are functions. type CompositeTypeCode struct { CompositeFunctions map[string]FunctionValue - DestructorFunction FunctionValue } type FunctionWrapper = func(inner FunctionValue) FunctionValue @@ -1129,12 +1128,6 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( } } - var destructorFunction FunctionValue - compositeDestructorFunction := interpreter.compositeDestructorFunction(declaration, lexicalScope) - if compositeDestructorFunction != nil { - destructorFunction = compositeDestructorFunction - } - functions := interpreter.compositeFunctions(declaration, lexicalScope) wrapFunctions := func(code WrapperCode) { @@ -1182,7 +1175,6 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( } interpreter.SharedState.typeCodes.CompositeCodes[compositeType.ID()] = CompositeTypeCode{ - DestructorFunction: destructorFunction, CompositeFunctions: functions, } @@ -1270,7 +1262,6 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( value.InjectedFields = injectedFields value.Functions = functions - value.Destructor = destructorFunction var self MemberAccessibleValue = value if declaration.Kind() == common.CompositeKindAttachment { @@ -1526,23 +1517,6 @@ func (interpreter *Interpreter) compositeInitializerFunction( ) } -func (interpreter *Interpreter) compositeDestructorFunction( - compositeDeclaration ast.CompositeLikeDeclaration, - lexicalScope *VariableActivation, -) *InterpretedFunctionValue { - - return NewInterpretedFunctionValue( - interpreter, - nil, - emptyImpureFunctionType, - lexicalScope, - []ast.Statement{}, - ast.Conditions{}, - []ast.Statement{}, - ast.Conditions{}, - ) -} - func (interpreter *Interpreter) defaultFunctions( members *ast.Members, lexicalScope *VariableActivation, diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index b75a1cba59..a79e359ab6 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1207,8 +1207,6 @@ func (interpreter *Interpreter) VisitCreateExpression(expression *ast.CreateExpr func (interpreter *Interpreter) VisitDestroyExpression(expression *ast.DestroyExpression) Value { value := interpreter.evalExpression(expression.Expression) - interpreter.invalidateResource(value) - locationRange := LocationRange{ Location: interpreter.Location, HasPosition: expression, diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index f0aa2a80aa..286625dccc 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -1792,6 +1792,8 @@ func (v *ArrayValue) Destroy(interpreter *Interpreter, locationRange LocationRan config := interpreter.SharedState.Config + interpreter.invalidateResource(v) + if config.InvalidatedResourceValidationEnabled { v.checkInvalidatedResourceUse(interpreter, locationRange) } @@ -16209,7 +16211,6 @@ func (UFix64Value) Scale() int { // CompositeValue type CompositeValue struct { - Destructor FunctionValue Location common.Location staticType StaticType Stringer func(gauge common.MemoryGauge, value *CompositeValue, seenReferences SeenReferences) string @@ -16414,6 +16415,8 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio interpreter.ReportComputation(common.ComputationKindDestroyCompositeValue, 1) + interpreter.invalidateResource(v) + config := interpreter.SharedState.Config if config.InvalidatedResourceValidationEnabled { @@ -16444,46 +16447,12 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio storageID, locationRange, func() { - // if this type has attachments, destroy all of them before invoking the destructor - v.forEachAttachment(interpreter, locationRange, func(attachment *CompositeValue) { - // an attachment's destructor may make reference to `base`, so we must set the base value - // for the attachment before invoking its destructor. For other functions, this happens - // automatically when the attachment is accessed with the access expression `v[A]`, which - // is a necessary pre-requisite for calling any members of the attachment. However, in - // the case of a destructor, this is called implicitly, and thus must have its `base` - // set manually - attachment.setBaseValue(interpreter, v, attachmentBaseAuthorization(interpreter, attachment)) - attachment.Destroy(interpreter, locationRange) - }) - interpreter = v.getInterpreter(interpreter) - // if composite was deserialized, dynamically link in the destructor - if v.Destructor == nil { - v.Destructor = interpreter.SharedState.typeCodes.CompositeCodes[v.TypeID()].DestructorFunction - } - - destructor := v.Destructor - - if destructor != nil { - var base *EphemeralReferenceValue - var self MemberAccessibleValue = v - if v.Kind == common.CompositeKindAttachment { - base, self = attachmentBaseAndSelfValues(interpreter, v) - } - invocation := NewInvocation( - interpreter, - &self, - base, - nil, - nil, - nil, - nil, - locationRange, - ) - - destructor.invoke(invocation) - } + // destroy every nested resource in this composite; note that this iteration includes attachments + v.ForEachField(interpreter, func(_ string, fieldValue Value) { + maybeDestroy(interpreter, locationRange, fieldValue) + }) }, ) @@ -17282,7 +17251,6 @@ func (v *CompositeValue) Transfer( res.ComputedFields = v.ComputedFields res.NestedVariables = v.NestedVariables res.Functions = v.Functions - res.Destructor = v.Destructor res.Stringer = v.Stringer res.isDestroyed = v.isDestroyed res.typeID = v.typeID @@ -17368,7 +17336,6 @@ func (v *CompositeValue) Clone(interpreter *Interpreter) Value { ComputedFields: v.ComputedFields, NestedVariables: v.NestedVariables, Functions: v.Functions, - Destructor: v.Destructor, Stringer: v.Stringer, isDestroyed: v.isDestroyed, typeID: v.typeID, @@ -18097,6 +18064,8 @@ func (v *DictionaryValue) Destroy(interpreter *Interpreter, locationRange Locati config := interpreter.SharedState.Config + interpreter.invalidateResource(v) + if config.InvalidatedResourceValidationEnabled { v.checkInvalidatedResourceUse(interpreter, locationRange) } @@ -19161,8 +19130,8 @@ func (NilValue) IsDestroyed() bool { return false } -func (v NilValue) Destroy(_ *Interpreter, _ LocationRange) { - // NO-OP +func (v NilValue) Destroy(interpreter *Interpreter, _ LocationRange) { + interpreter.invalidateResource(v) } func (NilValue) String() string { @@ -19346,6 +19315,8 @@ func (v *SomeValue) IsDestroyed() bool { func (v *SomeValue) Destroy(interpreter *Interpreter, locationRange LocationRange) { config := interpreter.SharedState.Config + interpreter.invalidateResource(v) + if config.InvalidatedResourceValidationEnabled { v.checkInvalidatedResourceUse(locationRange) } diff --git a/runtime/nft_test.go b/runtime/nft_test.go index f0fd51c3d2..ea70fedc67 100644 --- a/runtime/nft_test.go +++ b/runtime/nft_test.go @@ -507,10 +507,6 @@ access(all) contract TopShot: NonFungibleToken { emit MomentMinted(momentID: self.id, playID: playID, setID: self.data.setID, serialNumber: self.data.serialNumber) } - - destroy() { - emit MomentDestroyed(id: self.id) - } } // Admin is a special authorization resource that @@ -700,15 +696,6 @@ access(all) contract TopShot: NonFungibleToken { return nil } } - - // If a transaction destroys the Collection object, - // All the NFTs contained within are also destroyed - // Kind of like when Damien Lillard destroys the hopes and - // dreams of the entire city of Houston - // - destroy() { - destroy self.ownedNFTs - } } // ----------------------------------------------------------------------- @@ -1043,12 +1030,6 @@ access(all) contract TopShotShardedCollection { return self.collections[bucket]?.borrowMoment(id: id) ?? nil } - - // If a transaction destroys the Collection object, - // All the NFTs contained within are also destroyed - destroy() { - destroy self.collections - } } // Creates an empty ShardedCollection and returns it to the caller diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index eac5a5f3a9..f0712ff7a4 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -4065,12 +4065,11 @@ func TestParseAttachmentDeclaration(t *testing.T) { _, errs := testParseDeclarations(`access(all) attachment E for S { require entitlement - destroy() {} }`) utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ - Pos: ast.Position{Line: 3, Column: 10, Offset: 67}, - Message: "unexpected '('", + Pos: ast.Position{Line: 3, Column: 3, Offset: 60}, + Message: "unexpected token in type: '}'", }, }, errs) }) @@ -4081,7 +4080,6 @@ func TestParseAttachmentDeclaration(t *testing.T) { _, errs := testParseDeclarations(`access(all) attachment E for S { require X - destroy() {} }`) utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -4097,7 +4095,6 @@ func TestParseAttachmentDeclaration(t *testing.T) { _, errs := testParseDeclarations(`access(all) attachment E for S { require entitlement [X] - destroy() {} }`) utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ @@ -7771,7 +7768,6 @@ func TestParseInvalidCompositeFunctionNames(t *testing.T) { ` %[1]s %[2]s Test %[4]s { fun init() %[3]s - fun destroy() %[3]s } `, kind.Keyword(), diff --git a/runtime/resource_duplicate_test.go b/runtime/resource_duplicate_test.go index 0e36f4ac44..2d9d31dc27 100644 --- a/runtime/resource_duplicate_test.go +++ b/runtime/resource_duplicate_test.go @@ -23,455 +23,15 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/cadence/runtime/sema" - "github.com/onflow/cadence/runtime/tests/checker" . "github.com/onflow/cadence/runtime/tests/utils" ) -func TestRuntimeResourceDuplicationUsingDestructorIteration(t *testing.T) { - t.Parallel() - - t.Run("Reported error", func(t *testing.T) { - - t.Parallel() - - script := ` - // This Vault class is from Flow docs, used as our "victim" in this example - access(all) resource Vault { - // Balance of a user's Vault - // we use unsigned fixed point numbers for balances - // because they can represent decimals and do not allow negative values - access(all) var balance: UFix64 - - init(balance: UFix64) { - self.balance = balance - } - - access(all) fun withdraw(amount: UFix64): @Vault { - self.balance = self.balance - amount - return <-create Vault(balance: amount) - } - - access(all) fun deposit(from: @Vault) { - self.balance = self.balance + from.balance - destroy from - } - } - - // --- this code actually makes use of the vuln --- - access(all) resource DummyResource { - access(all) var dictRef: auth(Mutate) &{Bool: AnyResource}; - access(all) var arrRef: auth(Mutate) &[Vault]; - access(all) var victim: @Vault; - init(dictRef: auth(Mutate) &{Bool: AnyResource}, arrRef: auth(Mutate) &[Vault], victim: @Vault) { - self.dictRef = dictRef; - self.arrRef = arrRef; - self.victim <- victim; - } - - destroy() { - self.arrRef.append(<- self.victim) - self.dictRef[false] <-> self.dictRef[true]; // This screws up the destruction order - } - } - - access(all) fun duplicateResource(victim1: @Vault, victim2: @Vault): @[Vault]{ - let arr : @[Vault] <- []; - let dict: @{Bool: DummyResource} <- { } - let ref = &dict as auth(Mutate) &{Bool: AnyResource}; - let arrRef = &arr as auth(Mutate) &[Vault]; - - var v1: @DummyResource? <- create DummyResource(dictRef: ref, arrRef: arrRef, victim: <- victim1); - dict[false] <-> v1; - destroy v1; - - var v2: @DummyResource? <- create DummyResource(dictRef: ref, arrRef: arrRef, victim: <- victim2); - dict[true] <-> v2; - destroy v2; - - destroy dict // Trigger the destruction chain where dict[false] will be destructed twice - return <- arr; - } - - // --- end of vuln code --- - - access(all) fun main() { - - var v1 <- create Vault(balance: 1000.0); // This will be duplicated - var v2 <- create Vault(balance: 1.0); // This will be lost - var v3 <- create Vault(balance: 0.0); // We'll collect the spoils here - - // The call will return an array of [v1, v1] - var res <- duplicateResource(victim1: <- v1, victim2: <-v2) - - v3.deposit(from: <- res.removeLast()); - v3.deposit(from: <- res.removeLast()); - destroy res; - - log(v3.balance); - destroy v3; - } - ` - - runtime := newTestInterpreterRuntime() - - accountCodes := map[common.Location][]byte{} - - var events []cadence.Event - - signerAccount := common.MustBytesToAddress([]byte{0x1}) - - storage := newTestLedger(nil, nil) - - runtimeInterface := &testRuntimeInterface{ - getCode: func(location Location) (bytes []byte, err error) { - return accountCodes[location], nil - }, - storage: storage, - getSigningAccounts: func() ([]Address, error) { - return []Address{signerAccount}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { - return accountCodes[location], nil - }, - updateAccountContractCode: func(location common.AddressLocation, code []byte) error { - accountCodes[location] = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - log: func(s string) { - assert.Fail(t, "we should not reach this point") - }, - } - runtimeInterface.decodeArgument = func(b []byte, t cadence.Type) (value cadence.Value, err error) { - return json.Decode(nil, b) - } - - _, err := runtime.ExecuteScript( - Script{ - Source: []byte(script), - Arguments: [][]byte{}, - }, - Context{ - Interface: runtimeInterface, - Location: common.ScriptLocation{}, - }, - ) - - var checkerErr *sema.CheckerError - require.ErrorAs(t, err, &checkerErr) - - errs := checker.RequireCheckerErrors(t, checkerErr, 2) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) - }) - - t.Run("simplified", func(t *testing.T) { - - t.Parallel() - - script := ` - access(all) resource Vault { - access(all) var balance: UFix64 - access(all) var dictRef: auth(Mutate) &{Bool: Vault}; - - init(balance: UFix64, _ dictRef: auth(Mutate) &{Bool: Vault}) { - self.balance = balance - self.dictRef = dictRef; - } - - access(all) fun withdraw(amount: UFix64): @Vault { - self.balance = self.balance - amount - return <-create Vault(balance: amount, self.dictRef) - } - - access(all) fun deposit(from: @Vault) { - self.balance = self.balance + from.balance - destroy from - } - - destroy() { - self.dictRef[false] <-> self.dictRef[true]; // This screws up the destruction order - } - } - - access(all) fun main(): UFix64 { - - let dict: @{Bool: Vault} <- { } - let dictRef = &dict as auth(Mutate) &{Bool: Vault}; - - var v1 <- create Vault(balance: 1000.0, dictRef); // This will be duplicated - var v2 <- create Vault(balance: 1.0, dictRef); // This will be lost - - var v1Ref = &v1 as &Vault - - destroy dict.insert(key: false, <- v1) - destroy dict.insert(key: true, <- v2) - - destroy dict; - - // v1 is not destroyed! - return v1Ref.balance - } - ` - - runtime := newTestInterpreterRuntime() - - accountCodes := map[common.Location][]byte{} - - var events []cadence.Event - - signerAccount := common.MustBytesToAddress([]byte{0x1}) - - storage := newTestLedger(nil, nil) - - runtimeInterface := &testRuntimeInterface{ - getCode: func(location Location) (bytes []byte, err error) { - return accountCodes[location], nil - }, - storage: storage, - getSigningAccounts: func() ([]Address, error) { - return []Address{signerAccount}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { - return accountCodes[location], nil - }, - updateAccountContractCode: func(location common.AddressLocation, code []byte) error { - accountCodes[location] = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - } - runtimeInterface.decodeArgument = func(b []byte, t cadence.Type) (value cadence.Value, err error) { - return json.Decode(nil, b) - } - - _, err := runtime.ExecuteScript( - Script{ - Source: []byte(script), - Arguments: [][]byte{}, - }, - Context{ - Interface: runtimeInterface, - Location: common.ScriptLocation{}, - }, - ) - - var checkerErr *sema.CheckerError - require.ErrorAs(t, err, &checkerErr) - - errs := checker.RequireCheckerErrors(t, checkerErr, 3) - - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) - assert.IsType(t, &sema.InvalidatedResourceReferenceError{}, errs[2]) - }) - - t.Run("forEachKey", func(t *testing.T) { - - t.Parallel() - - script := ` - access(all) resource R{} - - access(all) fun main() { - var dict: @{Int: R} <- {} - - var r1: @R? <- create R() - var r2: @R? <- create R() - var r3: @R? <- create R() - - dict[0] <-> r1 - dict[1] <-> r2 - dict[2] <-> r3 - - destroy r1 - destroy r2 - destroy r3 - - let acc = getAuthAccount(0x1) - acc.storage.save(<-dict, to: /storage/foo) - - let ref = acc.storage.borrow(from: /storage/foo)! - - ref.forEachKey(fun(i: Int): Bool { - var r4: @R? <- create R() - ref[i+1] <-> r4 - destroy r4 - return true - }) - } - ` - - runtime := newTestInterpreterRuntime() - - accountCodes := map[common.Location][]byte{} - - var events []cadence.Event - - signerAccount := common.MustBytesToAddress([]byte{0x1}) - - storage := newTestLedger(nil, nil) - - runtimeInterface := &testRuntimeInterface{ - getCode: func(location Location) (bytes []byte, err error) { - return accountCodes[location], nil - }, - storage: storage, - getSigningAccounts: func() ([]Address, error) { - return []Address{signerAccount}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { - return accountCodes[location], nil - }, - updateAccountContractCode: func(location common.AddressLocation, code []byte) error { - accountCodes[location] = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - } - runtimeInterface.decodeArgument = func(b []byte, t cadence.Type) (value cadence.Value, err error) { - return json.Decode(nil, b) - } - - _, err := runtime.ExecuteScript( - Script{ - Source: []byte(script), - Arguments: [][]byte{}, - }, - Context{ - Interface: runtimeInterface, - Location: common.ScriptLocation{}, - }, - ) - - errs := checker.RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) - }) - - t.Run("array", func(t *testing.T) { - - t.Parallel() - - script := ` - access(all) resource Vault { - access(all) var balance: UFix64 - access(all) var arrRef: auth(Mutate) &[Vault] - - init(balance: UFix64, _ arrRef: auth(Mutate) &[Vault]) { - self.balance = balance - self.arrRef = arrRef; - } - - access(all) fun withdraw(amount: UFix64): @Vault { - self.balance = self.balance - amount - return <-create Vault(balance: amount, self.arrRef) - } - - access(all) fun deposit(from: @Vault) { - self.balance = self.balance + from.balance - destroy from - } - - destroy() { - self.arrRef.append(<-create Vault(balance: 0.0, self.arrRef)) - } - } - - access(all) fun main(): UFix64 { - - let arr: @[Vault] <- [] - let arrRef = &arr as auth(Mutate) &[Vault]; - - var v1 <- create Vault(balance: 1000.0, arrRef); // This will be duplicated - var v2 <- create Vault(balance: 1.0, arrRef); // This will be lost - - var v1Ref = &v1 as &Vault - - arr.append(<- v1) - arr.append(<- v2) - - destroy arr - - // v1 is not destroyed! - return v1Ref.balance - }` - - runtime := newTestInterpreterRuntime() - - accountCodes := map[common.Location][]byte{} - - var events []cadence.Event - - signerAccount := common.MustBytesToAddress([]byte{0x1}) - - storage := newTestLedger(nil, nil) - - runtimeInterface := &testRuntimeInterface{ - getCode: func(location Location) (bytes []byte, err error) { - return accountCodes[location], nil - }, - storage: storage, - getSigningAccounts: func() ([]Address, error) { - return []Address{signerAccount}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { - return accountCodes[location], nil - }, - updateAccountContractCode: func(location common.AddressLocation, code []byte) error { - accountCodes[location] = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - } - runtimeInterface.decodeArgument = func(b []byte, t cadence.Type) (value cadence.Value, err error) { - return json.Decode(nil, b) - } - - _, err := runtime.ExecuteScript( - Script{ - Source: []byte(script), - Arguments: [][]byte{}, - }, - Context{ - Interface: runtimeInterface, - Location: common.ScriptLocation{}, - }, - ) - RequireError(t, err) - - var checkerErr *sema.CheckerError - require.ErrorAs(t, err, &checkerErr) - - errs := checker.RequireCheckerErrors(t, checkerErr, 1) - - assert.IsType(t, &sema.InvalidatedResourceReferenceError{}, errs[0]) - }) -} - func TestRuntimeResourceDuplicationWithContractTransfer(t *testing.T) { t.Parallel() diff --git a/runtime/resourcedictionary_test.go b/runtime/resourcedictionary_test.go index 503d955e5e..3ea61725cc 100644 --- a/runtime/resourcedictionary_test.go +++ b/runtime/resourcedictionary_test.go @@ -45,11 +45,6 @@ const resourceDictionaryContract = ` access(all) fun increment() { self.value = self.value + 1 } - - destroy() { - log("destroying R") - log(self.value) - } } access(all) fun createR(_ value: Int): @R { @@ -77,10 +72,6 @@ const resourceDictionaryContract = ` access(all) fun forceInsert(_ id: String, _ r: @R) { self.rs[id] <-! r } - - destroy() { - destroy self.rs - } } access(all) fun createC(): @C { @@ -276,14 +267,14 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { assert.Equal(t, []string{ - "3", - `"destroying R"`, "3", "4", }, loggedMessages, ) + // DestructorTODO: add test for destruction event of R + // Remove the key removeTx := []byte(` @@ -315,14 +306,14 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { assert.Equal(t, []string{ - "4", - `"destroying R"`, "4", "nil", }, loggedMessages, ) + // DestructorTODO: add test for destruction event of R + // Read the deleted key loggedMessages = nil @@ -376,11 +367,11 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { assert.Equal(t, []string{ "1", - `"destroying R"`, - "1", }, loggedMessages, ) + + // DestructorTODO: add test for destruction event of R } func TestRuntimeResourceDictionaryValues_Nested(t *testing.T) { @@ -426,10 +417,6 @@ func TestRuntimeResourceDictionaryValues_Nested(t *testing.T) { access(all) fun forceInsert(_ id: String, _ r: @R) { self.rs[id] <-! r } - - destroy() { - destroy self.rs - } } access(all) fun createC2(): @C2 { @@ -447,10 +434,6 @@ func TestRuntimeResourceDictionaryValues_Nested(t *testing.T) { init() { self.c2s <- {} } - - destroy() { - destroy self.c2s - } } access(all) fun createC(): @C { @@ -625,10 +608,6 @@ func TestRuntimeResourceDictionaryValues_DictionaryTransfer(t *testing.T) { init() { self.rs <- {} } - - destroy() { - destroy self.rs - } } access(all) fun createC(): @C { @@ -991,15 +970,7 @@ func TestRuntimeResourceDictionaryValues_Destruction(t *testing.T) { ) require.NoError(t, err) - assert.Equal(t, - []string{ - `"destroying R"`, - "2", - `"destroying R"`, - "1", - }, - loggedMessages, - ) + // DestructorTODO: replace with test for destruction event of R twice } func TestRuntimeResourceDictionaryValues_Insertion(t *testing.T) { diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 02fd940cd0..29ea7f81bd 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -3830,13 +3830,7 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { contract := []byte(` access(all) contract Test { - access(all) resource R { - // test that the destructor is linked back into the nested resource - // after being loaded from storage - destroy() { - log("destroyed") - } - } + access(all) resource R {} init() { // store nested resource in account on deployment @@ -3860,7 +3854,6 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { deploy := DeploymentTransaction("Test", contract) var accountCode []byte - var loggedMessage string runtimeInterface := &testRuntimeInterface{ getCode: func(_ Location) (bytes []byte, err error) { @@ -3879,9 +3872,6 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { return nil }, emitEvent: func(event cadence.Event) error { return nil }, - log: func(message string) { - loggedMessage = message - }, } nextTransactionLocation := newTransactionLocationGenerator() @@ -3909,7 +3899,8 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { }) require.NoError(t, err) - assert.Equal(t, `"destroyed"`, loggedMessage) + // DestructorTODO: Assert default event is emitted here + } func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { @@ -3924,13 +3915,7 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { contract := []byte(` access(all) contract Test { - access(all) resource R { - // test that the destructor is linked back into the nested resource - // after being loaded from storage - destroy() { - log("destroyed") - } - } + access(all) resource R {} init() { // store nested resource in account on deployment @@ -3955,7 +3940,6 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { deploy := DeploymentTransaction("Test", contract) var accountCode []byte - var loggedMessage string runtimeInterface := &testRuntimeInterface{ getCode: func(_ Location) (bytes []byte, err error) { @@ -3974,9 +3958,6 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { return nil }, emitEvent: func(event cadence.Event) error { return nil }, - log: func(message string) { - loggedMessage = message - }, } nextTransactionLocation := newTransactionLocationGenerator() @@ -4005,7 +3986,7 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { ) require.NoError(t, err) - assert.Equal(t, `"destroyed"`, loggedMessage) + // DestructorTODO: Assert default event is emitted here } func TestRuntimeStorageLoadedDestructionAfterRemoval(t *testing.T) { @@ -4020,13 +4001,7 @@ func TestRuntimeStorageLoadedDestructionAfterRemoval(t *testing.T) { contract := []byte(` access(all) contract Test { - access(all) resource R { - // test that the destructor is linked back into the nested resource - // after being loaded from storage - destroy() { - log("destroyed") - } - } + access(all) resource R {} init() { // store nested resource in account on deployment @@ -8094,111 +8069,6 @@ func TestRuntimeUserPanicToError(t *testing.T) { require.Equal(t, retErr, err) } -func TestRuntimeDestructorReentrancyPrevention(t *testing.T) { - - t.Parallel() - - rt := newTestInterpreterRuntime() - - script := []byte(` - access(all) resource Vault { - // Balance of a user's Vault - // we use unsigned fixed point numbers for balances - // because they can represent decimals and do not allow negative values - access(all) var balance: UFix64 - - init(balance: UFix64) { - self.balance = balance - } - - access(all) fun withdraw(amount: UFix64): @Vault { - self.balance = self.balance - amount - return <-create Vault(balance: amount) - } - - access(all) fun deposit(from: @Vault) { - self.balance = self.balance + from.balance - destroy from - } - } - - // --- this code actually makes use of the vuln --- - access(all) resource InnerResource { - access(all) var victim: @Vault; - access(all) var here: Bool; - access(all) var parent: &OuterResource; - init(victim: @Vault, parent: &OuterResource) { - self.victim <- victim; - self.here = false; - self.parent = parent; - } - - destroy() { - if self.here == false { - self.here = true; - self.parent.reenter(); // will cause us to re-enter this destructor - } - self.parent.collect(from: <- self.victim); - } - } - - access(all) resource OuterResource { - access(all) var inner: @InnerResource?; - access(all) var collector: &Vault; - init(victim: @Vault, collector: &Vault) { - self.collector = collector; - self.inner <- create InnerResource(victim: <- victim, parent: &self as &OuterResource); - } - access(all) fun reenter() { - let inner <- self.inner <- nil; - destroy inner; - } - access(all) fun collect(from: @Vault) { - self.collector.deposit(from: <- from); - } - - destroy() { - destroy self.inner; - } - } - - access(all) fun doubleBalanceOfVault(vault: @Vault): @Vault { - var collector <- vault.withdraw(amount: 0.0); - var r <- create OuterResource(victim: <- vault, collector: &collector as &Vault); - destroy r; - return <- collector; - } - - // --- end of vuln code --- - - access(all) fun main(): UFix64 { - var v1 <- create Vault(balance: 1000.0); - var v2 <- doubleBalanceOfVault(vault: <- v1); - var v3 <- doubleBalanceOfVault(vault: <- v2); - let balance = v3.balance - destroy v3 - return balance - } - `) - - runtimeInterface := &testRuntimeInterface{ - storage: newTestLedger(nil, nil), - } - - _, err := rt.ExecuteScript( - Script{ - Source: script, - }, - Context{ - Interface: runtimeInterface, - Location: common.ScriptLocation{}, - }, - ) - RequireError(t, err) - - require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) -} - func TestRuntimeFlowEventTypes(t *testing.T) { t.Parallel() @@ -8311,11 +8181,6 @@ func TestRuntimeInvalidatedResourceUse(t *testing.T) { self.firstCopy <-> withdrawn return <- withdrawn } - - destroy() { - destroy self.vault - destroy self.firstCopy - } } access(all) fun doubleBalanceOfVault(_ victim: @VictimContract.Vault): @VictimContract.Vault { @@ -8432,433 +8297,6 @@ func TestRuntimeInvalidatedResourceUse(t *testing.T) { } -func TestRuntimeInvalidatedResourceUse2(t *testing.T) { - - t.Parallel() - - runtime := newTestInterpreterRuntime() - - signerAccount := common.MustBytesToAddress([]byte{0x1}) - - signers := []Address{signerAccount} - - accountCodes := map[Location][]byte{} - var events []cadence.Event - - runtimeInterface := &testRuntimeInterface{ - getCode: func(location Location) (bytes []byte, err error) { - return accountCodes[location], nil - }, - storage: newTestLedger(nil, nil), - getSigningAccounts: func() ([]Address, error) { - return signers, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { - return accountCodes[location], nil - }, - updateAccountContractCode: func(location common.AddressLocation, code []byte) (err error) { - accountCodes[location] = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - } - - nextTransactionLocation := newTransactionLocationGenerator() - - attacker := []byte(fmt.Sprintf(` - import VictimContract from %s - - access(all) contract AttackerContract { - - access(all) resource InnerResource { - access(all) var name: String - access(all) var parent: &OuterResource? - access(all) var vault: @VictimContract.Vault? - - init(_ name: String) { - self.name = name - self.parent = nil - self.vault <- nil - } - - access(all) fun setParent(_ parent: &OuterResource) { - self.parent = parent - } - - access(all) fun setVault(_ vault: @VictimContract.Vault) { - self.vault <-! vault - } - - destroy() { - self.parent!.shenanigans() - var vault: @VictimContract.Vault <- self.vault! - self.parent!.collect(<- vault) - } - } - - access(all) resource OuterResource { - access(all) var inner1: @InnerResource - access(all) var inner2: @InnerResource - access(all) var collector: &VictimContract.Vault - - init(_ victim: @VictimContract.Vault, _ collector: &VictimContract.Vault) { - self.collector = collector - var i1 <- create InnerResource("inner1") - var i2 <- create InnerResource("inner2") - self.inner1 <- i1 - self.inner2 <- i2 - self.inner1.setVault(<- victim) - self.inner1.setParent(&self as &OuterResource) - self.inner2.setParent(&self as &OuterResource) - } - - access(all) fun shenanigans() { - self.inner1 <-> self.inner2 - } - - access(all) fun collect(_ from: @VictimContract.Vault) { - self.collector.deposit(from: <- from) - } - - destroy() { - destroy self.inner1 - // inner1 and inner2 got swapped during the above line - destroy self.inner2 - } - } - - access(all) fun doubleBalanceOfVault(_ vault: @VictimContract.Vault): @VictimContract.Vault { - var collector <- vault.withdraw(amount: 0.0) - var outer <- create OuterResource(<- vault, &collector as &VictimContract.Vault) - destroy outer - return <- collector - } - - access(all) fun attack() { - var v1 <- VictimContract.faucet() - var v2 <- AttackerContract.doubleBalanceOfVault(<- v1) - destroy v2 - } - }`, - signerAccount.HexWithPrefix(), - )) - - victim := []byte(` - access(all) contract VictimContract { - access(all) resource Vault { - - // Balance of a user's Vault - // we use unsigned fixed point numbers for balances - // because they can represent decimals and do not allow negative values - access(all) var balance: UFix64 - - init(balance: UFix64) { - self.balance = balance - } - - access(all) fun withdraw(amount: UFix64): @Vault { - self.balance = self.balance - amount - return <-create Vault(balance: amount) - } - - access(all) fun deposit(from: @Vault) { - self.balance = self.balance + from.balance - destroy from - } - } - - access(all) fun faucet(): @VictimContract.Vault { - return <- create VictimContract.Vault(balance: 5.0) - } - } - `) - - // Deploy Victim - - deployVictim := DeploymentTransaction("VictimContract", victim) - err := runtime.ExecuteTransaction( - Script{ - Source: deployVictim, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) - - // Deploy Attacker - - deployAttacker := DeploymentTransaction("AttackerContract", attacker) - - err = runtime.ExecuteTransaction( - Script{ - Source: deployAttacker, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) - - // Attack - - attackTransaction := []byte(fmt.Sprintf(` - import VictimContract from %s - import AttackerContract from %s - - transaction { - execute { - AttackerContract.attack() - } - }`, - signerAccount.HexWithPrefix(), - signerAccount.HexWithPrefix(), - )) - - signers = nil - - err = runtime.ExecuteTransaction( - Script{ - Source: attackTransaction, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - - RequireError(t, err) - - require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) -} - -func TestRuntimeInvalidRecursiveTransferViaVariableDeclaration(t *testing.T) { - - t.Parallel() - - runtime := newTestInterpreterRuntime() - runtime.defaultConfig.AtreeValidationEnabled = false - - address := common.MustBytesToAddress([]byte{0x1}) - - contract := []byte(` - access(all) contract Test{ - - access(all) resource Holder{ - - access(all) var vaults: @[AnyResource] - - init(_ vaults: @[AnyResource]){ - self.vaults <- vaults - } - - access(all) fun x(): @[AnyResource] { - var x <- self.vaults <- [<-Test.dummy()] - return <-x - } - - destroy() { - var t <- self.vaults[0] <- self.vaults // here is the problem - destroy t - Test.account.storage.save(<- self.x(), to: /storage/x42) - } - } - - access(all) fun createHolder(_ vaults: @[AnyResource]): @Holder { - return <- create Holder(<-vaults) - } - - access(all) resource Dummy {} - - access(all) fun dummy(): @Dummy { - return <- create Dummy() - } - } - `) - - tx := []byte(` - import Test from 0x1 - - transaction { - - prepare(acct: &Account) { - var holder <- Test.createHolder(<-[<-Test.dummy(), <-Test.dummy()]) - destroy holder - } - } - `) - - deploy := DeploymentTransaction("Test", contract) - - var accountCode []byte - var events []cadence.Event - - runtimeInterface := &testRuntimeInterface{ - getCode: func(_ Location) (bytes []byte, err error) { - return accountCode, nil - }, - storage: newTestLedger(nil, nil), - getSigningAccounts: func() ([]Address, error) { - return []Address{address}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(_ common.AddressLocation) (code []byte, err error) { - return accountCode, nil - }, - updateAccountContractCode: func(_ common.AddressLocation, code []byte) error { - accountCode = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - } - - nextTransactionLocation := newTransactionLocationGenerator() - - // Deploy - - err := runtime.ExecuteTransaction( - Script{ - Source: deploy, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) - - // Test - - err = runtime.ExecuteTransaction( - Script{ - Source: tx, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - RequireError(t, err) - - require.ErrorAs(t, err, &interpreter.RecursiveTransferError{}) -} - -func TestRuntimeInvalidRecursiveTransferViaFunctionArgument(t *testing.T) { - - t.Parallel() - - runtime := newTestInterpreterRuntime() - runtime.defaultConfig.AtreeValidationEnabled = false - - address := common.MustBytesToAddress([]byte{0x1}) - - contract := []byte(` - access(all) contract Test{ - - access(all) resource Holder { - - access(all) var vaults: @[AnyResource] - - init(_ vaults: @[AnyResource]) { - self.vaults <- vaults - } - - destroy() { - self.vaults.append(<-self.vaults) - } - } - - access(all) fun createHolder(_ vaults: @[AnyResource]): @Holder { - return <- create Holder(<-vaults) - } - - access(all) resource Dummy {} - - access(all) fun dummy(): @Dummy { - return <- create Dummy() - } - } - `) - - tx := []byte(` - import Test from 0x1 - - transaction { - - prepare(acct: &Account) { - var holder <- Test.createHolder(<-[<-Test.dummy(), <-Test.dummy()]) - destroy holder - } - } - `) - - deploy := DeploymentTransaction("Test", contract) - - var accountCode []byte - var events []cadence.Event - - runtimeInterface := &testRuntimeInterface{ - getCode: func(_ Location) (bytes []byte, err error) { - return accountCode, nil - }, - storage: newTestLedger(nil, nil), - getSigningAccounts: func() ([]Address, error) { - return []Address{address}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - getAccountContractCode: func(_ common.AddressLocation) (code []byte, err error) { - return accountCode, nil - }, - updateAccountContractCode: func(_ common.AddressLocation, code []byte) error { - accountCode = code - return nil - }, - emitEvent: func(event cadence.Event) error { - events = append(events, event) - return nil - }, - } - - nextTransactionLocation := newTransactionLocationGenerator() - - // Deploy - - err := runtime.ExecuteTransaction( - Script{ - Source: deploy, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - require.NoError(t, err) - - // Test - - err = runtime.ExecuteTransaction( - Script{ - Source: tx, - }, - Context{ - Interface: runtimeInterface, - Location: nextTransactionLocation(), - }, - ) - RequireError(t, err) - - require.ErrorAs(t, err, &interpreter.RecursiveTransferError{}) -} - func TestRuntimeOptionalReferenceAttack(t *testing.T) { t.Parallel() diff --git a/runtime/storage_test.go b/runtime/storage_test.go index 4f06e8b63c..dcb2a52eb1 100644 --- a/runtime/storage_test.go +++ b/runtime/storage_test.go @@ -849,10 +849,6 @@ func TestRuntimeBatchMintAndTransfer(t *testing.T) { access(all) fun getIDs(): [UInt64] { return self.ownedNFTs.keys } - - destroy() { - destroy self.ownedNFTs - } } init() { @@ -2389,10 +2385,6 @@ func TestRuntimeReferenceOwnerAccess(t *testing.T) { init () { self.nestedResources <- [<- create TestNestedResource()] } - - destroy () { - destroy self.nestedResources - } } access(all) fun makeTestNestingResource(): @TestNestingResource { @@ -2902,10 +2894,6 @@ func TestRuntimeStorageEnumCase(t *testing.T) { let oldR <- self.rs[r.id] <-! r destroy oldR } - - destroy() { - destroy self.rs - } } access(all) fun createEmptyCollection(): @Collection { diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 27d1b89818..52fdddeba6 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -596,9 +596,6 @@ func TestInterpretNestedAttach(t *testing.T) { self.y <- y self.i = base.i } - destroy() { - destroy self.y - } } attachment B for Y { } fun test(): Int { @@ -638,9 +635,6 @@ func TestInterpretNestedAttachFunction(t *testing.T) { self.y <- y self.i = base.i } - destroy() { - destroy self.y - } } attachment B for Y { } fun foo(): @Y { @@ -1248,11 +1242,7 @@ func TestInterpretAttachmentDestructor(t *testing.T) { inter := parseCheckAndInterpret(t, ` var destructorRun = false resource R {} - attachment A for R { - destroy() { - destructorRun = true - } - } + attachment A for R {} fun test() { let r <- create R() let r2 <- attach A() to <-r @@ -1263,7 +1253,7 @@ func TestInterpretAttachmentDestructor(t *testing.T) { _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual(t, inter, interpreter.TrueValue, inter.Globals.Get("destructorRun").GetValue()) + // DestructorTODO: replace with test for destruction event of A }) t.Run("base destructor executed last", func(t *testing.T) { @@ -1271,27 +1261,10 @@ func TestInterpretAttachmentDestructor(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - var lastDestructorRun = "" - resource R { - destroy() { - lastDestructorRun = "R" - } - } - attachment A for R { - destroy() { - lastDestructorRun = "A" - } - } - attachment B for R { - destroy() { - lastDestructorRun = "B" - } - } - attachment C for R { - destroy() { - lastDestructorRun = "C" - } - } + resource R {} + attachment A for R { } + attachment B for R { } + attachment C for R { } fun test() { let r <- create R() let r2 <- attach A() to <- attach B() to <- attach C() to <-r @@ -1302,42 +1275,7 @@ func TestInterpretAttachmentDestructor(t *testing.T) { _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual(t, inter, interpreter.NewUnmeteredStringValue("R"), inter.Globals.Get("lastDestructorRun").GetValue()) - }) - - t.Run("base destructor cannot add mutate attachments mid-destroy", func(t *testing.T) { - - t.Parallel() - - inter := parseCheckAndInterpret(t, ` - resource R { - fun foo() { - remove B from self - } - destroy() {} - } - attachment A for R { - destroy() { - - } - } - attachment B for R { - destroy() {} - } - attachment C for R { - destroy() { - base.foo() - } - } - fun test() { - let r <- create R() - let r2 <- attach A() to <- attach B() to <- attach C() to <-r - destroy r2 - } - `) - - _, err := inter.Invoke("test") - require.ErrorAs(t, err, &interpreter.AttachmentIterationMutationError{}) + // DestructorTODO: replace with test for destruction event for R being the last emitted }) t.Run("remove runs destroy", func(t *testing.T) { @@ -1345,11 +1283,7 @@ func TestInterpretAttachmentDestructor(t *testing.T) { inter := parseCheckAndInterpret(t, ` var destructorRun = false resource R {} - attachment A for R { - destroy() { - destructorRun = true - } - } + attachment A for R {} fun test(): @R { let r <- create R() let r2 <- attach A() to <-r @@ -1361,27 +1295,19 @@ func TestInterpretAttachmentDestructor(t *testing.T) { _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual(t, inter, interpreter.TrueValue, inter.Globals.Get("destructorRun").GetValue()) + // DestructorTODO: replace with test for destruction event of both A }) t.Run("remove runs resource field destroy", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` - var destructorRun = false resource R {} - resource R2 { - destroy() { - destructorRun = true - } - } + resource R2 {} attachment A for R { let r2: @R2 init() { self.r2 <- create R2() } - destroy() { - destroy self.r2 - } } fun test(): @R { let r <- create R() @@ -1394,28 +1320,20 @@ func TestInterpretAttachmentDestructor(t *testing.T) { _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual(t, inter, interpreter.TrueValue, inter.Globals.Get("destructorRun").GetValue()) + // DestructorTODO: replace with test for destruction event of R2 }) t.Run("nested attachments destroyed", func(t *testing.T) { inter := parseCheckAndInterpret(t, ` - var destructorRun = false resource R {} resource R2 {} - attachment B for R2 { - destroy() { - destructorRun = true - } - } + attachment B for R2 { } attachment A for R { let r2: @R2 init() { self.r2 <- attach B() to <-create R2() } - destroy() { - destroy self.r2 - } } fun test(): @R { let r <- create R() @@ -1428,7 +1346,7 @@ func TestInterpretAttachmentDestructor(t *testing.T) { _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual(t, inter, interpreter.TrueValue, inter.Globals.Get("destructorRun").GetValue()) + // DestructorTODO: replace with test for destruction event of both B }) } @@ -1523,9 +1441,6 @@ func TestInterpretAttachmentResourceReferenceInvalidation(t *testing.T) { init(r: @R) { self.r <- r } - destroy() { - destroy self.r - } } attachment A for R { access(all) var id: UInt8 @@ -1621,9 +1536,6 @@ func TestInterpretAttachmentResourceReferenceInvalidation(t *testing.T) { init(r: @R) { self.r <- r } - destroy() { - destroy self.r - } } attachment A for R { fun foo(): Int { return 3 } @@ -2006,9 +1918,6 @@ func TestInterpretForEachAttachment(t *testing.T) { init(_ name: String) { self.r <- create Sub(name) } - destroy() { - destroy self.r - } } attachment B for R {} attachment C for R { @@ -2016,9 +1925,6 @@ func TestInterpretForEachAttachment(t *testing.T) { init(_ name: String) { self.r <- create Sub(name) } - destroy() { - destroy self.r - } } fun test(): String { var r <- attach C("World") to <- attach B() to <- attach A("Hello") to <- create R() diff --git a/runtime/tests/interpreter/condition_test.go b/runtime/tests/interpreter/condition_test.go index 41ed2f404d..2ab0163954 100644 --- a/runtime/tests/interpreter/condition_test.go +++ b/runtime/tests/interpreter/condition_test.go @@ -983,7 +983,7 @@ func TestInterpretInitializerWithInterfacePreCondition(t *testing.T) { } } -func TestInterpretResourceInterfaceInitializerAndDestructorPreConditions(t *testing.T) { +func TestInterpretResourceInterfaceInitializerPreConditions(t *testing.T) { t.Parallel() @@ -1004,13 +1004,6 @@ func TestInterpretResourceInterfaceInitializerAndDestructorPreConditions(t *test emit InitPre(x: x) } } - - destroy() { - pre { - self.x < 3: "invalid destroy" - emit DestroyPre(x: self.x) - } - } } resource R: RI { @@ -1062,30 +1055,6 @@ func TestInterpretResourceInterfaceInitializerAndDestructorPreConditions(t *test _, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(2)) require.NoError(t, err) - require.Len(t, getEvents(), 2) - }) - - t.Run("3", func(t *testing.T) { - t.Parallel() - - inter, getEvents := newInterpreter(t) - _, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(3)) - RequireError(t, err) - - require.IsType(t, - interpreter.Error{}, - err, - ) - interpreterErr := err.(interpreter.Error) - - require.IsType(t, - interpreter.ConditionError{}, - interpreterErr.Err, - ) - conditionError := interpreterErr.Err.(interpreter.ConditionError) - - assert.Equal(t, "invalid destroy", conditionError.Message) - require.Len(t, getEvents(), 1) }) } @@ -1403,10 +1372,6 @@ func TestInterpretFunctionWithPostConditionAndResourceResult(t *testing.T) { check(r) return true } - - destroy() { - destroy self.resources - } } fun test(): Bool { diff --git a/runtime/tests/interpreter/entitlements_test.go b/runtime/tests/interpreter/entitlements_test.go index 42e7c57f60..e3c2a6acfd 100644 --- a/runtime/tests/interpreter/entitlements_test.go +++ b/runtime/tests/interpreter/entitlements_test.go @@ -2589,7 +2589,7 @@ func TestInterpretEntitledAttachments(t *testing.T) { ) }) - t.Run("fully entitled in init and destroy", func(t *testing.T) { + t.Run("fully entitled in init", func(t *testing.T) { t.Parallel() @@ -2615,10 +2615,6 @@ func TestInterpretEntitledAttachments(t *testing.T) { let x = self as! auth(Y, Z, F, G) &A let y = base as! auth(X, E) &R } - destroy() { - let x = self as! auth(Y, Z, F, G) &A - let y = base as! auth(X, E) &R - } } fun test() { let r <- attach A() to <-create R() with (E, X) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 6f2bb2e5d3..b13aa92fc1 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -6495,10 +6495,6 @@ func TestInterpretResourceMoveInArrayAndDestroy(t *testing.T) { init(bar: Int) { self.bar = bar } - - destroy() { - destroys = destroys + 1 - } } fun test(): Int { @@ -6528,12 +6524,7 @@ func TestInterpretResourceMoveInArrayAndDestroy(t *testing.T) { value, ) - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(2), - inter.Globals.Get("destroys").GetValue(), - ) + // DestructorTODO: replace with test for destruction event } func TestInterpretResourceMoveInDictionaryAndDestroy(t *testing.T) { @@ -6549,10 +6540,6 @@ func TestInterpretResourceMoveInDictionaryAndDestroy(t *testing.T) { init(bar: Int) { self.bar = bar } - - destroy() { - destroys = destroys + 1 - } } fun test() { @@ -6573,12 +6560,7 @@ func TestInterpretResourceMoveInDictionaryAndDestroy(t *testing.T) { _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(2), - inter.Globals.Get("destroys").GetValue(), - ) + // DestructorTODO: replace with test for destruction event } func TestInterpretClosure(t *testing.T) { @@ -6815,11 +6797,7 @@ func TestInterpretResourceDestroyExpressionDestructor(t *testing.T) { inter := parseCheckAndInterpret(t, ` var ranDestructor = false - resource R { - destroy() { - ranDestructor = true - } - } + resource R { } fun test() { let r <- create R() @@ -6837,12 +6815,7 @@ func TestInterpretResourceDestroyExpressionDestructor(t *testing.T) { _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual( - t, - inter, - interpreter.TrueValue, - inter.Globals.Get("ranDestructor").GetValue(), - ) + // DestructorTODO: replace with test for destruction event } func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { @@ -6850,14 +6823,7 @@ func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - var ranDestructorA = false - var ranDestructorB = false - - resource B { - destroy() { - ranDestructorB = true - } - } + resource B {} resource A { let b: @B @@ -6865,11 +6831,6 @@ func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { init(b: @B) { self.b <- b } - - destroy() { - ranDestructorA = true - destroy self.b - } } fun test() { @@ -6879,36 +6840,10 @@ func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { } `) - AssertValuesEqual( - t, - inter, - interpreter.FalseValue, - inter.Globals.Get("ranDestructorA").GetValue(), - ) - - AssertValuesEqual( - t, - inter, - interpreter.FalseValue, - inter.Globals.Get("ranDestructorB").GetValue(), - ) - _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual( - t, - inter, - interpreter.TrueValue, - inter.Globals.Get("ranDestructorA").GetValue(), - ) - - AssertValuesEqual( - t, - inter, - interpreter.TrueValue, - inter.Globals.Get("ranDestructorB").GetValue(), - ) + // DestructorTODO: replace with test for destruction event of both A and B } func TestInterpretResourceDestroyArray(t *testing.T) { @@ -6916,13 +6851,7 @@ func TestInterpretResourceDestroyArray(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - var destructionCount = 0 - - resource R { - destroy() { - destructionCount = destructionCount + 1 - } - } + resource R {} fun test() { let rs <- [<-create R(), <-create R()] @@ -6930,22 +6859,10 @@ func TestInterpretResourceDestroyArray(t *testing.T) { } `) - RequireValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(0), - inter.Globals.Get("destructionCount").GetValue(), - ) - _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(2), - inter.Globals.Get("destructionCount").GetValue(), - ) + // DestructorTODO: replace with test for destruction event emitted twice } func TestInterpretResourceDestroyDictionary(t *testing.T) { @@ -6953,13 +6870,7 @@ func TestInterpretResourceDestroyDictionary(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - var destructionCount = 0 - - resource R { - destroy() { - destructionCount = destructionCount + 1 - } - } + resource R { } fun test() { let rs <- {"r1": <-create R(), "r2": <-create R()} @@ -6967,22 +6878,10 @@ func TestInterpretResourceDestroyDictionary(t *testing.T) { } `) - RequireValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(0), - inter.Globals.Get("destructionCount").GetValue(), - ) - _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(2), - inter.Globals.Get("destructionCount").GetValue(), - ) + // DestructorTODO: replace with test for destruction event emitted twice } func TestInterpretResourceDestroyOptionalSome(t *testing.T) { @@ -6990,13 +6889,7 @@ func TestInterpretResourceDestroyOptionalSome(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - var destructionCount = 0 - - resource R { - destroy() { - destructionCount = destructionCount + 1 - } - } + resource R { } fun test() { let maybeR: @R? <- create R() @@ -7004,22 +6897,10 @@ func TestInterpretResourceDestroyOptionalSome(t *testing.T) { } `) - RequireValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(0), - inter.Globals.Get("destructionCount").GetValue(), - ) - _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(1), - inter.Globals.Get("destructionCount").GetValue(), - ) + // DestructorTODO: replace with test for destruction event } func TestInterpretResourceDestroyOptionalNil(t *testing.T) { @@ -7027,13 +6908,7 @@ func TestInterpretResourceDestroyOptionalNil(t *testing.T) { t.Parallel() inter := parseCheckAndInterpret(t, ` - var destructionCount = 0 - - resource R { - destroy() { - destructionCount = destructionCount + 1 - } - } + resource R {} fun test() { let maybeR: @R? <- nil @@ -7041,57 +6916,10 @@ func TestInterpretResourceDestroyOptionalNil(t *testing.T) { } `) - RequireValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(0), - inter.Globals.Get("destructionCount").GetValue(), - ) - _, err := inter.Invoke("test") require.NoError(t, err) - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(0), - inter.Globals.Get("destructionCount").GetValue(), - ) -} - -// TestInterpretResourceDestroyExpressionResourceInterfaceCondition tests that -// the resource interface's destructor is called, even if the conforming resource -// does not have an destructor -func TestInterpretResourceDestroyExpressionResourceInterfaceCondition(t *testing.T) { - - t.Parallel() - - inter := parseCheckAndInterpret(t, ` - resource interface I { - destroy() { - pre { false } - } - } - - resource R: I {} - - fun test() { - let r <- create R() - destroy r - } - `) - - _, err := inter.Invoke("test") - require.IsType(t, - interpreter.Error{}, - err, - ) - interpreterErr := err.(interpreter.Error) - - require.IsType(t, - interpreter.ConditionError{}, - interpreterErr.Err, - ) + // DestructorTODO: replace with test for destruction event not emitted } // TestInterpretInterfaceInitializer tests that the interface's initializer @@ -9189,10 +9017,6 @@ func TestInterpretResourceAssignmentForceTransfer(t *testing.T) { init() { self.x <-! create X() } - - destroy() { - destroy self.x - } } fun test() { @@ -9538,11 +9362,6 @@ func TestInterpretNestedDestroy(t *testing.T) { init(_ id: Int){ self.id = id } - - destroy(){ - log("destroying B with id:") - log(self.id) - } } resource A { @@ -9557,12 +9376,6 @@ func TestInterpretNestedDestroy(t *testing.T) { fun add(_ b: @B){ self.bs.append(<-b) } - - destroy() { - log("destroying A with id:") - log(self.id) - destroy self.bs - } } fun test() { @@ -9596,19 +9409,7 @@ func TestInterpretNestedDestroy(t *testing.T) { value, ) - assert.Equal(t, - []string{ - `"destroying A with id:"`, - "1", - `"destroying B with id:"`, - "2", - `"destroying B with id:"`, - "3", - `"destroying B with id:"`, - "4", - }, - logs, - ) + // DestructorTODO: replace with test for destruction event for A and B } // TestInterpretInternalAssignment ensures that a modification of an "internal" value diff --git a/runtime/tests/interpreter/member_test.go b/runtime/tests/interpreter/member_test.go index 26cb33b692..2c39bda253 100644 --- a/runtime/tests/interpreter/member_test.go +++ b/runtime/tests/interpreter/member_test.go @@ -700,9 +700,6 @@ func TestInterpretMemberAccess(t *testing.T) { init() { self.bar <- create Bar() } - destroy() { - destroy self.bar - } } resource Bar { @@ -710,9 +707,6 @@ func TestInterpretMemberAccess(t *testing.T) { init() { self.baz <- create Baz() } - destroy() { - destroy self.baz - } } resource Baz { diff --git a/runtime/tests/interpreter/reference_test.go b/runtime/tests/interpreter/reference_test.go index 2c345548ef..757107bd68 100644 --- a/runtime/tests/interpreter/reference_test.go +++ b/runtime/tests/interpreter/reference_test.go @@ -1186,9 +1186,6 @@ func TestInterpretResourceReferenceInvalidationOnMove(t *testing.T) { self.id = 1 self.bar <-create Bar() } - destroy() { - destroy self.bar - } } resource Bar { @@ -1196,9 +1193,6 @@ func TestInterpretResourceReferenceInvalidationOnMove(t *testing.T) { init() { self.baz <-create Baz() } - destroy() { - destroy self.baz - } } resource Baz { @@ -1310,9 +1304,6 @@ func TestInterpretResourceReferenceInvalidationOnMove(t *testing.T) { init() { self.optionalBar <-create Bar() } - destroy() { - destroy self.optionalBar - } } resource Bar { @@ -1358,9 +1349,6 @@ func TestInterpretResourceReferenceInvalidationOnMove(t *testing.T) { init() { self.bar <-create Bar() } - destroy() { - destroy self.bar - } } resource Bar { @@ -1446,9 +1434,6 @@ func TestInterpretResourceReferenceInvalidationOnMove(t *testing.T) { init() { self.bar <-create Bar() } - destroy() { - destroy self.bar - } } resource Bar { diff --git a/runtime/tests/interpreter/resources_test.go b/runtime/tests/interpreter/resources_test.go index 7e69be2f75..042f1c4f80 100644 --- a/runtime/tests/interpreter/resources_test.go +++ b/runtime/tests/interpreter/resources_test.go @@ -52,10 +52,6 @@ func TestInterpretOptionalResourceBindingWithSecondValue(t *testing.T) { self.r <- create R() } - destroy () { - destroy self.r - } - fun duplicate(): @R? { if let r <- self.r <- nil { let r2 <- self.r <- nil @@ -111,10 +107,6 @@ func TestInterpretImplicitResourceRemovalFromContainer(t *testing.T) { init(r2: @R2) { self.r2 <- r2 } - - destroy() { - destroy self.r2 - } } fun test(): String? { @@ -162,10 +154,6 @@ func TestInterpretImplicitResourceRemovalFromContainer(t *testing.T) { init(r2: @R2) { self.r2 <- r2 } - - destroy() { - destroy self.r2 - } } fun test(): String? { @@ -216,10 +204,6 @@ func TestInterpretImplicitResourceRemovalFromContainer(t *testing.T) { init(r2s: @{Int: R2}) { self.r2s <- r2s } - - destroy() { - destroy self.r2s - } } fun test(): String? { @@ -267,10 +251,6 @@ func TestInterpretImplicitResourceRemovalFromContainer(t *testing.T) { init(r2s: @{Int: R2}) { self.r2s <- r2s } - - destroy() { - destroy self.r2s - } } fun test(): String? { @@ -2153,196 +2133,6 @@ func TestInterpretResourceDestroyedInPreCondition(t *testing.T) { require.True(t, didError) } -func TestInterpretInvalidReentrantResourceDestruction(t *testing.T) { - - t.Parallel() - - t.Run("composite", func(t *testing.T) { - - t.Parallel() - - inter := parseCheckAndInterpret(t, ` - - resource Inner { - let outer: &Outer - - init(outer: &Outer) { - self.outer = outer - } - - destroy() { - self.outer.reenter() - } - } - - resource Outer { - var inner: @Inner? - - init() { - self.inner <-! create Inner(outer: &self as &Outer) - } - - fun reenter() { - let inner <- self.inner <- nil - destroy inner - } - - destroy() { - destroy self.inner - } - } - - fun test() { - let outer <- create Outer() - - destroy outer - } - `) - - _, err := inter.Invoke("test") - RequireError(t, err) - - require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) - }) - - t.Run("array", func(t *testing.T) { - - t.Parallel() - - inter := parseCheckAndInterpret(t, ` - - resource Inner { - let outer: &Outer - - init(outer: &Outer) { - self.outer = outer - } - - destroy() { - self.outer.reenter() - } - } - - resource Outer { - var inner: @[Inner] - - init() { - self.inner <- [<-create Inner(outer: &self as &Outer)] - } - - fun reenter() { - let inner <- self.inner <- [] - destroy inner - } - - destroy() { - destroy self.inner - } - } - - fun test() { - let outer <- create Outer() - - destroy outer - } - `) - - _, err := inter.Invoke("test") - RequireError(t, err) - - require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) - }) - - t.Run("dictionary", func(t *testing.T) { - - t.Parallel() - - inter := parseCheckAndInterpret(t, ` - - resource Inner { - let outer: &Outer - - init(outer: &Outer) { - self.outer = outer - } - - destroy() { - self.outer.reenter() - } - } - - resource Outer { - var inner: @{Int: Inner} - - init() { - self.inner <- {0: <-create Inner(outer: &self as &Outer)} - } - - fun reenter() { - let inner <- self.inner <- {} - destroy inner - } - - destroy() { - destroy self.inner - } - } - - fun test() { - let outer <- create Outer() - - destroy outer - } - `) - - _, err := inter.Invoke("test") - RequireError(t, err) - - require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) - }) -} - -func TestInterpretResourceFunctionInvocationAfterDestruction(t *testing.T) { - - t.Parallel() - - inter := parseCheckAndInterpret(t, ` - access(all) resource Vault { - access(all) fun foo(_ ignored: Bool) {} - } - - access(all) resource Attacker { - access(all) var vault: @Vault - - init() { - self.vault <- create Vault() - } - - access(all) fun shenanigans(): Bool { - var temp <- create Vault() - self.vault <-> temp - destroy temp - return true - } - - destroy() { - destroy self.vault - } - } - - access(all) fun main() { - let a <- create Attacker() - a.vault.foo(a.shenanigans()) - destroy a - } - `) - - _, err := inter.Invoke("main") - RequireError(t, err) - - require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) -} - func TestInterpretResourceFunctionReferenceValidity(t *testing.T) { t.Parallel() @@ -2369,10 +2159,6 @@ func TestInterpretResourceFunctionReferenceValidity(t *testing.T) { access(all) fun shenanigans2(_ ref: &Vault): &Vault { return ref } - - destroy() { - destroy self.vault - } } access(all) fun main() { @@ -2422,10 +2208,6 @@ func TestInterpretResourceFunctionResourceFunctionValidity(t *testing.T) { } return true } - - destroy() { - destroy self.vault - } } access(all) fun main() { @@ -2441,59 +2223,25 @@ func TestInterpretResourceFunctionResourceFunctionValidity(t *testing.T) { require.NoError(t, err) } -func TestInterpretInnerResourceDestruction(t *testing.T) { +func TestInterpretImplicitDestruction(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - access(all) resource InnerResource { - access(all) var name: String - access(all) var parent: &OuterResource? + t.Run("basic", func(t *testing.T) { - init(_ name: String) { - self.name = name - self.parent = nil - } - - access(all) fun setParent(_ parent: &OuterResource) { - self.parent = parent - } - - destroy() { - self.parent!.shenanigans() - } - } - - access(all) resource OuterResource { - access(all) var inner1: @InnerResource - access(all) var inner2: @InnerResource - - init() { - self.inner1 <- create InnerResource("inner1") - self.inner2 <- create InnerResource("inner2") + t.Parallel() - self.inner1.setParent(&self as &OuterResource) - self.inner2.setParent(&self as &OuterResource) - } + inter := parseCheckAndInterpret(t, ` - access(all) fun shenanigans() { - self.inner1 <-> self.inner2 - } + resource R {} - destroy() { - destroy self.inner1 - destroy self.inner2 + fun test() { + let r <- create R() + destroy r } - } - - access(all) fun main() { - let a <- create OuterResource() - destroy a - }`, - ) - - _, err := inter.Invoke("main") - RequireError(t, err) + `) - require.ErrorAs(t, err, &interpreter.InvalidatedResourceReferenceError{}) + _, err := inter.Invoke("test") + require.NoError(t, err) + }) } From 070e278e0f14532d1eaea5475ffb965b89866dd4 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 15 Sep 2023 14:22:53 -0400 Subject: [PATCH 04/48] remove unused variables --- runtime/interpreter/interpreter.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index a974a0d569..e73ede3f97 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -43,14 +43,6 @@ import ( // -var emptyImpureFunctionType = sema.NewSimpleFunctionType( - sema.FunctionPurityImpure, - nil, - sema.VoidTypeAnnotation, -) - -// - type getterSetter struct { target Value // allowMissing may be true when the got value is nil. @@ -2210,10 +2202,6 @@ func (interpreter *Interpreter) initializerFunctionWrapper( ) } -var voidFunctionType = &sema.FunctionType{ - ReturnTypeAnnotation: sema.VoidTypeAnnotation, -} - func (interpreter *Interpreter) functionConditionsWrapper( declaration *ast.FunctionDeclaration, functionType *sema.FunctionType, From 85eacf5e53653e40f0f8f49c100a85d4d22242ad Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 20 Sep 2023 13:50:58 -0400 Subject: [PATCH 05/48] add support for default arguments to AST --- runtime/ast/expression_test.go | 1 + runtime/ast/function_declaration_test.go | 2 ++ runtime/ast/parameter.go | 23 +++++++++++++++-------- runtime/parser/function.go | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/runtime/ast/expression_test.go b/runtime/ast/expression_test.go index 0c821fd9f9..88486245db 100644 --- a/runtime/ast/expression_test.go +++ b/runtime/ast/expression_test.go @@ -4414,6 +4414,7 @@ func TestFunctionExpression_MarshalJSON(t *testing.T) { "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, "EndPos": {"Offset": 5, "Line": 5, "Column": 7} }, + "DefaultArgument": null, "StartPos": {"Offset": 10, "Line": 11, "Column": 12}, "EndPos": {"Offset": 5, "Line": 5, "Column": 7} } diff --git a/runtime/ast/function_declaration_test.go b/runtime/ast/function_declaration_test.go index 3c680539d0..0c19dcbd85 100644 --- a/runtime/ast/function_declaration_test.go +++ b/runtime/ast/function_declaration_test.go @@ -241,6 +241,7 @@ func TestFunctionDeclaration_MarshalJSON(t *testing.T) { "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, "EndPos": {"Offset": 5, "Line": 5, "Column": 7} }, + "DefaultArgument": null, "StartPos": {"Offset": 10, "Line": 11, "Column": 12}, "EndPos": {"Offset": 5, "Line": 5, "Column": 7} } @@ -714,6 +715,7 @@ func TestSpecialFunctionDeclaration_MarshalJSON(t *testing.T) { "StartPos": {"Offset": 7, "Line": 8, "Column": 9}, "EndPos": {"Offset": 5, "Line": 5, "Column": 7} }, + "DefaultArgument": null, "StartPos": {"Offset": 10, "Line": 11, "Column": 12}, "EndPos": {"Offset": 5, "Line": 5, "Column": 7} } diff --git a/runtime/ast/parameter.go b/runtime/ast/parameter.go index 18295d6a93..b9975ccd82 100644 --- a/runtime/ast/parameter.go +++ b/runtime/ast/parameter.go @@ -25,10 +25,11 @@ import ( ) type Parameter struct { - TypeAnnotation *TypeAnnotation - Label string - Identifier Identifier - StartPos Position `json:"-"` + TypeAnnotation *TypeAnnotation + DefaultArgument *Expression + Label string + Identifier Identifier + StartPos Position `json:"-"` } func NewParameter( @@ -36,14 +37,16 @@ func NewParameter( label string, identifier Identifier, typeAnnotation *TypeAnnotation, + defaultArgument *Expression, startPos Position, ) *Parameter { common.UseMemory(gauge, common.ParameterMemoryUsage) return &Parameter{ - Label: label, - Identifier: identifier, - TypeAnnotation: typeAnnotation, - StartPos: startPos, + Label: label, + Identifier: identifier, + TypeAnnotation: typeAnnotation, + DefaultArgument: defaultArgument, + StartPos: startPos, } } @@ -68,6 +71,10 @@ func (p *Parameter) EndPosition(memoryGauge common.MemoryGauge) Position { return p.TypeAnnotation.EndPosition(memoryGauge) } +func (p *Parameter) HasDefaultArgument() bool { + return p.DefaultArgument != nil +} + func (p *Parameter) MarshalJSON() ([]byte, error) { type Alias Parameter return json.Marshal(&struct { diff --git a/runtime/parser/function.go b/runtime/parser/function.go index 6ff04f3862..5c42c89228 100644 --- a/runtime/parser/function.go +++ b/runtime/parser/function.go @@ -172,6 +172,7 @@ func parseParameter(p *parser) (*ast.Parameter, error) { argumentLabel, identifier, typeAnnotation, + nil, startPos, ), nil } From ef45306aa75e427430e3c84ab8de2f54c78c20e2 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 20 Sep 2023 17:16:29 -0400 Subject: [PATCH 06/48] parsing default event --- runtime/ast/composite.go | 6 ++ runtime/ast/parameter.go | 7 +- runtime/parser/declaration.go | 3 +- runtime/parser/declaration_test.go | 109 ++++++++++++++++------------- runtime/parser/function.go | 32 +++++++-- runtime/parser/transaction.go | 2 +- runtime/tests/utils/utils.go | 11 ++- 7 files changed, 106 insertions(+), 64 deletions(-) diff --git a/runtime/ast/composite.go b/runtime/ast/composite.go index 2c369ad0e5..3402e34c2a 100644 --- a/runtime/ast/composite.go +++ b/runtime/ast/composite.go @@ -43,6 +43,8 @@ type CompositeLikeDeclaration interface { Kind() common.CompositeKind } +const ResourceDestructionDefaultEventName = "ResourceDestroyed" + type CompositeDeclaration struct { Members *Members DocString string @@ -280,6 +282,10 @@ func (d *CompositeDeclaration) ConformanceList() []*NominalType { return d.Conformances } +func (d *CompositeDeclaration) IsResourceDestructionDefaultEvent() bool { + return d.CompositeKind == common.CompositeKindEvent && d.Identifier.Identifier == ResourceDestructionDefaultEventName +} + // FieldDeclarationFlags type FieldDeclarationFlags uint8 diff --git a/runtime/ast/parameter.go b/runtime/ast/parameter.go index b9975ccd82..5af4e9991b 100644 --- a/runtime/ast/parameter.go +++ b/runtime/ast/parameter.go @@ -26,7 +26,7 @@ import ( type Parameter struct { TypeAnnotation *TypeAnnotation - DefaultArgument *Expression + DefaultArgument Expression Label string Identifier Identifier StartPos Position `json:"-"` @@ -37,7 +37,7 @@ func NewParameter( label string, identifier Identifier, typeAnnotation *TypeAnnotation, - defaultArgument *Expression, + defaultArgument Expression, startPos Position, ) *Parameter { common.UseMemory(gauge, common.ParameterMemoryUsage) @@ -68,6 +68,9 @@ func (p *Parameter) StartPosition() Position { } func (p *Parameter) EndPosition(memoryGauge common.MemoryGauge) Position { + if p.HasDefaultArgument() { + return p.DefaultArgument.EndPosition(memoryGauge) + } return p.TypeAnnotation.EndPosition(memoryGauge) } diff --git a/runtime/parser/declaration.go b/runtime/parser/declaration.go index e36e442375..274d06c7df 100644 --- a/runtime/parser/declaration.go +++ b/runtime/parser/declaration.go @@ -861,7 +861,8 @@ func parseEventDeclaration( // Skip the identifier p.next() - parameterList, err := parseParameterList(p) + // if this is a `ResourceDestroyed` event (i.e., a default event declaration), parse default arguments + parameterList, err := parseParameterList(p, identifier.Identifier == ast.ResourceDestructionDefaultEventName) if err != nil { return nil, err } diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index f0712ff7a4..6e02055ce1 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -388,7 +388,7 @@ func TestParseParameterList(t *testing.T) { return Parse( nil, []byte(input), - parseParameterList, + func(p *parser) (*ast.ParameterList, error) { return parseParameterList(p, false) }, Config{}, ) } @@ -2424,11 +2424,11 @@ func TestParseEvent(t *testing.T) { ) }) - t.Run("two parameters, private", func(t *testing.T) { + t.Run("default event", func(t *testing.T) { t.Parallel() - result, errs := testParseDeclarations(" access(self) event E2 ( a : Int , b : String )") + result, errs := testParseDeclarations(` access(all) event ResourceDestroyed ( a : String = "foo")`) require.Empty(t, errs) utils.AssertEqualWithDiff(t, @@ -2458,70 +2458,53 @@ func TestParseEvent(t *testing.T) { Column: 29, }, }, - Identifier: ast.Identifier{ - Identifier: "a", - Pos: ast.Position{ - Offset: 25, - Line: 1, - Column: 25, - }, - }, - StartPos: ast.Position{ - Offset: 25, - Line: 1, - Column: 25, - }, - }, - { - TypeAnnotation: &ast.TypeAnnotation{ - Type: &ast.NominalType{ - Identifier: ast.Identifier{ - Identifier: "String", - Pos: ast.Position{ - Offset: 39, - Line: 1, - Column: 39, - }, + DefaultArgument: &ast.StringExpression{ + Value: "foo", + Range: ast.Range{ + StartPos: ast.Position{ + Offset: 52, + Line: 1, + Column: 52, + }, + EndPos: ast.Position{ + Offset: 56, + Line: 1, + Column: 56, }, - }, - StartPos: ast.Position{ - Offset: 39, - Line: 1, - Column: 39, }, }, Identifier: ast.Identifier{ - Identifier: "b", + Identifier: "a", Pos: ast.Position{ - Offset: 35, + Offset: 39, Line: 1, - Column: 35, + Column: 39, }, }, StartPos: ast.Position{ - Offset: 35, + Offset: 39, Line: 1, - Column: 35, + Column: 39, }, }, }, Range: ast.Range{ StartPos: ast.Position{ - Offset: 23, + Offset: 37, Line: 1, - Column: 23, + Column: 37, }, EndPos: ast.Position{ - Offset: 46, + Offset: 57, Line: 1, - Column: 46, + Column: 57, }, }, }, StartPos: ast.Position{ - Offset: 23, + Offset: 37, Line: 1, - Column: 23, + Column: 37, }, Access: ast.AccessNotSpecified, }, @@ -2530,11 +2513,11 @@ func TestParseEvent(t *testing.T) { }, ), Identifier: ast.Identifier{ - Identifier: "E2", + Identifier: "ResourceDestroyed", Pos: ast.Position{ - Offset: 20, + Offset: 19, Line: 1, - Column: 20, + Column: 19, }, }, Range: ast.Range{ @@ -2544,12 +2527,12 @@ func TestParseEvent(t *testing.T) { Column: 1, }, EndPos: ast.Position{ - Offset: 46, + Offset: 57, Line: 1, - Column: 46, + Column: 57, }, }, - Access: ast.AccessSelf, + Access: ast.AccessAll, CompositeKind: common.CompositeKindEvent, }, }, @@ -2557,6 +2540,34 @@ func TestParseEvent(t *testing.T) { ) }) + t.Run("default event with no default arg", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseDeclarations(" access(all) event ResourceDestroyed ( a : Int )") + + utils.AssertEqualWithDiff(t, []error{ + &SyntaxError{ + Pos: ast.Position{Line: 1, Column: 47, Offset: 47}, + Message: "expected a default argument after type annotation, got ')'", + }, + }, errs) + }) + + t.Run("non-default event with default arg", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseDeclarations(" access(all) event Foo ( a : Int = 3)") + + utils.AssertEqualWithDiff(t, []error{ + &SyntaxError{ + Pos: ast.Position{Line: 1, Column: 33, Offset: 33}, + Message: "expected comma or end of parameter list, got '='", + }, + }, errs) + }) + t.Run("invalid event name", func(t *testing.T) { _, errs := testParseDeclarations(`event continue {}`) diff --git a/runtime/parser/function.go b/runtime/parser/function.go index 5c42c89228..feb3cb5e15 100644 --- a/runtime/parser/function.go +++ b/runtime/parser/function.go @@ -32,7 +32,7 @@ func parsePurityAnnotation(p *parser) ast.FunctionPurity { return ast.FunctionPurityUnspecified } -func parseParameterList(p *parser) (*ast.ParameterList, error) { +func parseParameterList(p *parser, expectDefaultArguments bool) (*ast.ParameterList, error) { var parameters []*ast.Parameter p.skipSpaceAndComments() @@ -63,7 +63,7 @@ func parseParameterList(p *parser) (*ast.ParameterList, error) { Pos: p.current.StartPos, }) } - parameter, err := parseParameter(p) + parameter, err := parseParameter(p, expectDefaultArguments) if err != nil { return nil, err } @@ -120,7 +120,7 @@ func parseParameterList(p *parser) (*ast.ParameterList, error) { ), nil } -func parseParameter(p *parser) (*ast.Parameter, error) { +func parseParameter(p *parser, expectDefaultArgument bool) (*ast.Parameter, error) { p.skipSpaceAndComments() startPos := p.current.StartPos @@ -167,12 +167,34 @@ func parseParameter(p *parser) (*ast.Parameter, error) { return nil, err } + p.skipSpaceAndComments() + + var defaultArgument ast.Expression + + if expectDefaultArgument { + if !p.current.Is(lexer.TokenEqual) { + return nil, p.syntaxError( + "expected a default argument after type annotation, got %s", + p.current.Type, + ) + } + + // Skip the = + p.nextSemanticToken() + + defaultArgument, err = parseExpression(p, lowestBindingPower) + if err != nil { + return nil, err + } + + } + return ast.NewParameter( p.memoryGauge, argumentLabel, identifier, typeAnnotation, - nil, + defaultArgument, startPos, ), nil } @@ -363,7 +385,7 @@ func parseFunctionParameterListAndRest( ) { // Parameter list - parameterList, err = parseParameterList(p) + parameterList, err = parseParameterList(p, false) if err != nil { return } diff --git a/runtime/parser/transaction.go b/runtime/parser/transaction.go index 549c62b347..1ccff2a7a6 100644 --- a/runtime/parser/transaction.go +++ b/runtime/parser/transaction.go @@ -52,7 +52,7 @@ func parseTransactionDeclaration(p *parser, docString string) (*ast.TransactionD var err error if p.current.Is(lexer.TokenParenOpen) { - parameterList, err = parseParameterList(p) + parameterList, err = parseParameterList(p, false) if err != nil { return nil, err } diff --git a/runtime/tests/utils/utils.go b/runtime/tests/utils/utils.go index ce62a55b81..89bd139e8a 100644 --- a/runtime/tests/utils/utils.go +++ b/runtime/tests/utils/utils.go @@ -50,13 +50,12 @@ const ImportedLocation = common.StringLocation("imported") // // If the objects are not equal, this function prints a human-readable diff. func AssertEqualWithDiff(t *testing.T, expected, actual any) { - if !assert.Equal(t, expected, actual) { - // the maximum levels of a struct to recurse into - // this prevents infinite recursion from circular references - deep.MaxDepth = 100 - - diff := deep.Equal(expected, actual) + // the maximum levels of a struct to recurse into + // this prevents infinite recursion from circular references + deep.MaxDepth = 100 + diff := deep.Equal(expected, actual) + if !assert.Nil(t, diff) { if len(diff) != 0 { s := strings.Builder{} From 34f43446849db4a9424bd6ad92b5dce640d4c44e Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 20 Sep 2023 17:20:23 -0400 Subject: [PATCH 07/48] re-add removed test --- runtime/parser/declaration_test.go | 133 +++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index 6e02055ce1..666b5ea5d1 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -2424,6 +2424,139 @@ func TestParseEvent(t *testing.T) { ) }) + t.Run("two parameters, private", func(t *testing.T) { + + t.Parallel() + + result, errs := testParseDeclarations(" access(self) event E2 ( a : Int , b : String )") + require.Empty(t, errs) + + utils.AssertEqualWithDiff(t, + []ast.Declaration{ + &ast.CompositeDeclaration{ + Members: ast.NewUnmeteredMembers( + []ast.Declaration{ + &ast.SpecialFunctionDeclaration{ + FunctionDeclaration: &ast.FunctionDeclaration{ + ParameterList: &ast.ParameterList{ + Parameters: []*ast.Parameter{ + { + TypeAnnotation: &ast.TypeAnnotation{ + Type: &ast.NominalType{ + Identifier: ast.Identifier{ + Identifier: "Int", + Pos: ast.Position{ + Offset: 29, + Line: 1, + Column: 29, + }, + }, + }, + StartPos: ast.Position{ + Offset: 29, + Line: 1, + Column: 29, + }, + }, + Identifier: ast.Identifier{ + Identifier: "a", + Pos: ast.Position{ + Offset: 25, + Line: 1, + Column: 25, + }, + }, + StartPos: ast.Position{ + Offset: 25, + Line: 1, + Column: 25, + }, + }, + { + TypeAnnotation: &ast.TypeAnnotation{ + Type: &ast.NominalType{ + Identifier: ast.Identifier{ + Identifier: "String", + Pos: ast.Position{ + Offset: 39, + Line: 1, + Column: 39, + }, + }, + }, + StartPos: ast.Position{ + Offset: 39, + Line: 1, + Column: 39, + }, + }, + Identifier: ast.Identifier{ + Identifier: "b", + Pos: ast.Position{ + Offset: 35, + Line: 1, + Column: 35, + }, + }, + StartPos: ast.Position{ + Offset: 35, + Line: 1, + Column: 35, + }, + }, + }, + Range: ast.Range{ + StartPos: ast.Position{ + Offset: 23, + Line: 1, + Column: 23, + }, + EndPos: ast.Position{ + Offset: 46, + Line: 1, + Column: 46, + }, + }, + }, + StartPos: ast.Position{ + Offset: 23, + Line: 1, + Column: 23, + }, + Access: ast.AccessNotSpecified, + }, + Kind: common.DeclarationKindInitializer, + }, + }, + ), + Identifier: ast.Identifier{ + Identifier: "E2", + Pos: ast.Position{ + Offset: 20, + Line: 1, + Column: 20, + }, + }, + Range: ast.Range{ + StartPos: ast.Position{ + Offset: 1, + Line: 1, + Column: 1, + }, + EndPos: ast.Position{ + Offset: 46, + Line: 1, + Column: 46, + }, + }, + Access: ast.AccessSelf, + CompositeKind: common.CompositeKindEvent, + }, + }, + result, + ) + }) + t.Run("default event", func(t *testing.T) { t.Parallel() From d09b578ea359a6e807129f9dde12975b3c6e703c Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 21 Sep 2023 12:59:52 -0400 Subject: [PATCH 08/48] partial checking --- runtime/sema/check_composite_declaration.go | 36 +++++- runtime/sema/errors.go | 21 ++++ runtime/sema/type.go | 2 + runtime/tests/checker/events_test.go | 129 ++++++++++++++++++++ 4 files changed, 184 insertions(+), 4 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 14f9581f66..c8cab9d8d5 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -428,10 +428,23 @@ func (checker *Checker) declareNestedDeclarations( firstNestedCompositeDeclaration := nestedCompositeDeclarations[0] - reportInvalidNesting( - firstNestedCompositeDeclaration.DeclarationKind(), - firstNestedCompositeDeclaration.Identifier, - ) + // we want to permit this nesting under 2 conditions: the container is a resource declaration, + // and this nested declaration is a default destroy event + + if firstNestedCompositeDeclaration.IsResourceDestructionDefaultEvent() { + if len(nestedCompositeDeclarations) > 1 { + firstNestedCompositeDeclaration = nestedCompositeDeclarations[1] + reportInvalidNesting( + firstNestedCompositeDeclaration.DeclarationKind(), + firstNestedCompositeDeclaration.Identifier, + ) + } + } else { + reportInvalidNesting( + firstNestedCompositeDeclaration.DeclarationKind(), + firstNestedCompositeDeclaration.Identifier, + ) + } } else if len(nestedInterfaceDeclarations) > 0 { @@ -479,6 +492,13 @@ func (checker *Checker) declareNestedDeclarations( nestedDeclarationKind common.DeclarationKind, identifier ast.Identifier, ) { + if nestedCompositeKind == common.CompositeKindEvent && identifier.Identifier == ast.ResourceDestructionDefaultEventName { + checker.report(&DefaultDestroyEventInNonResourceError{ + Kind: containerDeclarationKind.Name(), + Range: ast.NewRangeFromPositioned(checker.memoryGauge, identifier), + }) + } + if containerDeclarationKind.IsInterfaceDeclaration() && !nestedDeclarationKind.IsInterfaceDeclaration() { switch nestedCompositeKind { case common.CompositeKindEvent: @@ -811,6 +831,14 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( nestedCompositeDeclarationVariable := checker.valueActivations.Find(identifier.Identifier) + if identifier.Identifier == ast.ResourceDestructionDefaultEventName && compositeKind == common.CompositeKindResource { + // Find the default event's type declaration + defaultEventType := + checker.typeActivations.Find(identifier.Identifier) + compositeType.DefaultDestroyEvent = defaultEventType.Type.(*CompositeType) + return + } + declarationMembers.Set( nestedCompositeDeclarationVariable.Identifier, &Member{ diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index cdb52ed9d4..0409456e32 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4636,3 +4636,24 @@ func (e *InvalidAttachmentEntitlementError) StartPosition() ast.Position { func (e *InvalidAttachmentEntitlementError) EndPosition(common.MemoryGauge) ast.Position { return e.Pos } + +// DefaultDestroyEventInNonResourceError + +type DefaultDestroyEventInNonResourceError struct { + Kind string + ast.Range +} + +var _ SemanticError = &DefaultDestroyEventInNonResourceError{} +var _ errors.UserError = &DefaultDestroyEventInNonResourceError{} + +func (*DefaultDestroyEventInNonResourceError) isSemanticError() {} + +func (*DefaultDestroyEventInNonResourceError) IsUserError() {} + +func (e *DefaultDestroyEventInNonResourceError) Error() string { + return fmt.Sprintf( + "cannot declare default destruction event in %s", + e.Kind, + ) +} diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 4292b0e8dc..00e9f9533b 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4264,6 +4264,8 @@ type CompositeType struct { RequiredEntitlements *EntitlementOrderedSet AttachmentEntitlementAccess *EntitlementMapAccess + DefaultDestroyEvent *CompositeType + cachedIdentifiers *struct { TypeID TypeID QualifiedIdentifier string diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 62d6428648..4b244ce042 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -481,3 +481,132 @@ func TestCheckDeclareEventInInterface(t *testing.T) { }) } + +func TestCheckDefaultEventDeclaration(t *testing.T) { + + t.Parallel() + + t.Run("empty", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed() + } + `) + require.NoError(t, err) + + }) + + t.Run("allowed in resource interface", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource interface R { + event ResourceDestroyed() + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyEventInNonResourceError{}, errs[0]) + }) + + t.Run("fail in struct", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + struct R { + event ResourceDestroyed() + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyEventInNonResourceError{}, errs[0]) + }) + + t.Run("fail in struct interface", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + struct R { + event ResourceDestroyed() + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyEventInNonResourceError{}, errs[0]) + }) + + t.Run("fail in contract", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + contract R { + event ResourceDestroyed() + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyEventInNonResourceError{}, errs[0]) + }) + + t.Run("fail in contract interface", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + contract interface R { + event ResourceDestroyed() + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyEventInNonResourceError{}, errs[0]) + }) + + t.Run("allowed in resource attachment", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + attachment A for AnyResource { + event ResourceDestroyed() + } + `) + require.NoError(t, err) + }) + + t.Run("not allowed in struct attachment", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + attachment A for AnyStruct { + event ResourceDestroyed() + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyEventInNonResourceError{}, errs[0]) + }) + + t.Run("nested declarations after first disallowed", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed() + event OtherEvent() + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + }) +} From 68467372485b124bd3d5539ce9a3e9a3ef528ae4 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 21 Sep 2023 13:38:34 -0400 Subject: [PATCH 09/48] support for declarations --- runtime/sema/check_composite_declaration.go | 16 ++++++++-------- runtime/sema/check_interface_declaration.go | 17 ++++++++++++++++- runtime/sema/type.go | 3 +++ runtime/tests/checker/events_test.go | 19 ++++++++++++++----- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index c8cab9d8d5..dfc5e94bbc 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -267,6 +267,13 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL ) } + if !compositeType.IsResourceType() && compositeType.DefaultDestroyEvent != nil { + checker.report(&DefaultDestroyEventInNonResourceError{ + Kind: declaration.DeclarationKind().Name(), + Range: ast.NewRangeFromPositioned(checker.memoryGauge, declaration), + }) + } + // NOTE: visit entitlements, then interfaces, then composites // DON'T use `nestedDeclarations`, because of non-deterministic order @@ -492,13 +499,6 @@ func (checker *Checker) declareNestedDeclarations( nestedDeclarationKind common.DeclarationKind, identifier ast.Identifier, ) { - if nestedCompositeKind == common.CompositeKindEvent && identifier.Identifier == ast.ResourceDestructionDefaultEventName { - checker.report(&DefaultDestroyEventInNonResourceError{ - Kind: containerDeclarationKind.Name(), - Range: ast.NewRangeFromPositioned(checker.memoryGauge, identifier), - }) - } - if containerDeclarationKind.IsInterfaceDeclaration() && !nestedDeclarationKind.IsInterfaceDeclaration() { switch nestedCompositeKind { case common.CompositeKindEvent: @@ -831,7 +831,7 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( nestedCompositeDeclarationVariable := checker.valueActivations.Find(identifier.Identifier) - if identifier.Identifier == ast.ResourceDestructionDefaultEventName && compositeKind == common.CompositeKindResource { + if identifier.Identifier == ast.ResourceDestructionDefaultEventName { // Find the default event's type declaration defaultEventType := checker.typeActivations.Find(identifier.Identifier) diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index 4f084ad608..e8edf27e56 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -134,6 +134,13 @@ func (checker *Checker) VisitInterfaceDeclaration(declaration *ast.InterfaceDecl fieldPositionGetter, ) + if !interfaceType.IsResourceType() && interfaceType.DefaultDestroyEvent != nil { + checker.report(&DefaultDestroyEventInNonResourceError{ + Kind: declaration.DeclarationKind().Name(), + Range: ast.NewRangeFromPositioned(checker.memoryGauge, declaration), + }) + } + // NOTE: visit entitlements, then interfaces, then composites // DON'T use `nestedDeclarations`, because of non-deterministic order @@ -432,7 +439,15 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa for _, nestedCompositeDeclaration := range declaration.Members.Composites() { if nestedCompositeDeclaration.Kind() == common.CompositeKindEvent { - checker.declareNestedEvent(nestedCompositeDeclaration, eventMembers, interfaceType) + if nestedCompositeDeclaration.IsResourceDestructionDefaultEvent() { + // Find the value declaration + nestedEvent := + checker.typeActivations.Find(nestedCompositeDeclaration.Identifier.Identifier) + interfaceType.DefaultDestroyEvent = nestedEvent.Type.(*CompositeType) + // interfaceType.DefaultDestroyEvent = + } else { + checker.declareNestedEvent(nestedCompositeDeclaration, eventMembers, interfaceType) + } } } })() diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 00e9f9533b..8f54a2e8bd 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4620,6 +4620,7 @@ func (t *CompositeType) InterfaceType() *InterfaceType { InitializerPurity: t.ConstructorPurity, containerType: t.containerType, NestedTypes: t.NestedTypes, + DefaultDestroyEvent: t.DefaultDestroyEvent, } } @@ -5050,6 +5051,8 @@ type InterfaceType struct { effectiveInterfaceConformances []Conformance effectiveInterfaceConformanceSet *InterfaceSet supportedEntitlements *EntitlementOrderedSet + + DefaultDestroyEvent *CompositeType } var _ Type = &InterfaceType{} diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 4b244ce042..c6fe46065d 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -490,27 +490,36 @@ func TestCheckDefaultEventDeclaration(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, ` + checker, err := ParseAndCheck(t, ` resource R { event ResourceDestroyed() } `) require.NoError(t, err) + variable, exists := checker.Elaboration.GetGlobalType("R") + require.True(t, exists) + + require.IsType(t, variable.Type, &sema.CompositeType{}) + require.Equal(t, variable.Type.(*sema.CompositeType).DefaultDestroyEvent.Identifier, "ResourceDestroyed") }) t.Run("allowed in resource interface", func(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, ` + checker, err := ParseAndCheck(t, ` resource interface R { event ResourceDestroyed() } `) - errs := RequireCheckerErrors(t, err, 1) + require.NoError(t, err) - assert.IsType(t, &sema.DefaultDestroyEventInNonResourceError{}, errs[0]) + variable, exists := checker.Elaboration.GetGlobalType("R") + require.True(t, exists) + + require.IsType(t, variable.Type, &sema.InterfaceType{}) + require.Equal(t, variable.Type.(*sema.InterfaceType).DefaultDestroyEvent.Identifier, "ResourceDestroyed") }) t.Run("fail in struct", func(t *testing.T) { @@ -532,7 +541,7 @@ func TestCheckDefaultEventDeclaration(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - struct R { + struct interface R { event ResourceDestroyed() } `) From 60c1d1ee93fe84d335d2cd7585656ccc2e8d6a16 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 22 Sep 2023 09:47:27 -0400 Subject: [PATCH 10/48] more progress on checking --- runtime/sema/check_composite_declaration.go | 28 ++++- runtime/sema/check_interface_declaration.go | 2 + runtime/sema/checker.go | 11 +- runtime/sema/errors.go | 35 ++++++ runtime/sema/type.go | 7 +- runtime/tests/checker/events_test.go | 117 ++++++++++++++++++++ 6 files changed, 194 insertions(+), 6 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index dfc5e94bbc..33395d3085 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -835,7 +835,9 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( // Find the default event's type declaration defaultEventType := checker.typeActivations.Find(identifier.Identifier) - compositeType.DefaultDestroyEvent = defaultEventType.Type.(*CompositeType) + defaultEventComposite := defaultEventType.Type.(*CompositeType) + checker.checkDefaultDestroyEvent(defaultEventComposite, nestedCompositeDeclaration, compositeType) + compositeType.DefaultDestroyEvent = defaultEventComposite return } @@ -2007,6 +2009,30 @@ func (checker *Checker) enumMembersAndOrigins( return } +func (checker *Checker) checkDefaultDestroyEvent( + eventType *CompositeType, + eventDeclaration ast.CompositeLikeDeclaration, + containterType ContainerType, +) { + // 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 + + checkParamTypeValid := func(paramType Type) { + if !IsSubType(paramType, StringType) && + !IsSubType(paramType, NumberType) && + !IsSubType(paramType, BoolType) { + checker.report(&DefaultDestroyInvalidParameterError{ + ParamType: paramType, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, eventDeclaration), + }) + } + } + + for _, param := range eventType.ConstructorParameters { + checkParamTypeValid(param.TypeAnnotation.Type) + } +} + func (checker *Checker) checkInitializers( initializers []*ast.SpecialFunctionDeclaration, fields []*ast.FieldDeclaration, diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index e8edf27e56..d35ecadc07 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -443,6 +443,8 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa // Find the value declaration nestedEvent := checker.typeActivations.Find(nestedCompositeDeclaration.Identifier.Identifier) + defaultEventComposite := nestedEvent.Type.(*CompositeType) + checker.checkDefaultDestroyEvent(defaultEventComposite, nestedCompositeDeclaration, interfaceType) interfaceType.DefaultDestroyEvent = nestedEvent.Type.(*CompositeType) // interfaceType.DefaultDestroyEvent = } else { diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index d206d15469..ab8a2a50b3 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1288,12 +1288,19 @@ func (checker *Checker) parameters(parameterList *ast.ParameterList) []Parameter for i, parameter := range parameterList.Parameters { convertedParameterType := checker.ConvertType(parameter.TypeAnnotation.Type) + var convertedParameterDefaultArgument Type = nil + if parameter.DefaultArgument != nil { + // default arg expressions must be subtypes of the type annotation + convertedParameterDefaultArgument = checker.VisitExpression(parameter.DefaultArgument, convertedParameterType) + } + // NOTE: copying resource annotation from source type annotation as-is, // so a potential error is properly reported parameters[i] = Parameter{ - Label: parameter.Label, - Identifier: parameter.Identifier.Identifier, + Label: parameter.Label, + Identifier: parameter.Identifier.Identifier, + DefaultArgument: convertedParameterDefaultArgument, TypeAnnotation: TypeAnnotation{ IsResource: parameter.TypeAnnotation.IsResource, Type: convertedParameterType, diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 0409456e32..8b3df1823b 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4657,3 +4657,38 @@ func (e *DefaultDestroyEventInNonResourceError) Error() string { e.Kind, ) } + +// DefaultDestroyInvalidArgumentError + +type DefaultDestroyInvalidArgumentError struct { + ast.Range +} + +var _ SemanticError = &DefaultDestroyInvalidArgumentError{} +var _ errors.UserError = &DefaultDestroyInvalidArgumentError{} + +func (*DefaultDestroyInvalidArgumentError) isSemanticError() {} + +func (*DefaultDestroyInvalidArgumentError) IsUserError() {} + +func (e *DefaultDestroyInvalidArgumentError) Error() string { + return "default destroy event arguments must be literals or member access expressions on `self`" +} + +// DefaultDestroyInvalidArgumentError + +type DefaultDestroyInvalidParameterError struct { + ParamType Type + ast.Range +} + +var _ SemanticError = &DefaultDestroyInvalidParameterError{} +var _ errors.UserError = &DefaultDestroyInvalidParameterError{} + +func (*DefaultDestroyInvalidParameterError) isSemanticError() {} + +func (*DefaultDestroyInvalidParameterError) IsUserError() {} + +func (e *DefaultDestroyInvalidParameterError) Error() string { + return fmt.Sprintf("`%s` is not a valid parameter type for a default destroy event", e.ParamType.QualifiedString()) +} diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 8f54a2e8bd..408fc27a97 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -2922,9 +2922,10 @@ func formatParameter(spaces bool, label, identifier, typeAnnotation string) stri } type Parameter struct { - TypeAnnotation TypeAnnotation - Label string - Identifier string + TypeAnnotation TypeAnnotation + DefaultArgument Type + Label string + Identifier string } func (p Parameter) String() string { diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index c6fe46065d..f41c65f8fd 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -618,4 +618,121 @@ func TestCheckDefaultEventDeclaration(t *testing.T) { assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) }) + + t.Run("cannot declare two default events", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed() + event ResourceDestroyed() + } + `) + errs := RequireCheckerErrors(t, err, 4) + + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + assert.IsType(t, &sema.RedeclarationError{}, errs[1]) + assert.IsType(t, &sema.RedeclarationError{}, errs[2]) + assert.IsType(t, &sema.RedeclarationError{}, errs[3]) + }) +} + +func TestCheckDefaultEventParamChecking(t *testing.T) { + + t.Parallel() + + t.Run("basic", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: String = "foo") + } + `) + require.NoError(t, err) + }) + + t.Run("3 param", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: String = "foo", id: UInt16 = 4, condition: Bool = true) + } + `) + require.NoError(t, err) + }) + + t.Run("type error", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: Int = "foo") + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + }) + + t.Run("field", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + let field : String + event ResourceDestroyed(name: String = self.field) + + init() { + self.field = "" + } + } + `) + require.NoError(t, err) + }) + + t.Run("field type mismatch", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + let field : Int + event ResourceDestroyed(name: String = self.field) + + init() { + self.field = 3 + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + }) + + t.Run("array field", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + let field : [Int] + event ResourceDestroyed(name: [Int] = self.field) + + init() { + self.field = [3] + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyInvalidParameterError{}, errs[0]) + }) + } From 2b1ef907b4fa30b820f42f5f80f5218b6b0d9d16 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 22 Sep 2023 11:32:19 -0400 Subject: [PATCH 11/48] fix checking order so variables are properly in scope --- runtime/sema/check_composite_declaration.go | 36 ++++++++++++++++----- runtime/sema/check_interface_declaration.go | 6 ++-- runtime/sema/checker.go | 11 ++----- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 33395d3085..ffcf746160 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -290,6 +290,10 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL } for _, nestedComposite := range members.Composites() { + if compositeType.DefaultDestroyEvent != nil { + // we enforce elsewhere that each composite can have only one default destroy event + checker.checkDefaultDestroyEvent(compositeType.DefaultDestroyEvent, nestedComposite, compositeType, declaration) + } ast.AcceptDeclaration[struct{}](nestedComposite, checker) } @@ -836,7 +840,6 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( defaultEventType := checker.typeActivations.Find(identifier.Identifier) defaultEventComposite := defaultEventType.Type.(*CompositeType) - checker.checkDefaultDestroyEvent(defaultEventComposite, nestedCompositeDeclaration, compositeType) compositeType.DefaultDestroyEvent = defaultEventComposite return } @@ -2012,24 +2015,41 @@ func (checker *Checker) enumMembersAndOrigins( func (checker *Checker) checkDefaultDestroyEvent( eventType *CompositeType, eventDeclaration ast.CompositeLikeDeclaration, - containterType ContainerType, + containerType ContainerType, + containerDeclaration ast.Declaration, ) { - // 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 - checkParamTypeValid := func(paramType Type) { + // an event definition always has one "constructor" function in its declaration list + members := eventDeclaration.DeclarationMembers() + functions := members.SpecialFunctions() + constructorFunctionParameters := functions[0].FunctionDeclaration.ParameterList.Parameters + + for index, param := range eventType.ConstructorParameters { + paramType := param.TypeAnnotation.Type + paramDefaultArgument := constructorFunctionParameters[index] + + // make `self` and `base` available when checking default arguments so the fields of the composite are available + checker.declareSelfValue(containerType, containerDeclaration.DeclarationDocString()) + if compositeContainer, isComposite := containerType.(*CompositeType); isComposite && compositeContainer.Kind == common.CompositeKindAttachment { + checker.declareBaseValue( + compositeContainer.baseType, + compositeContainer, + compositeContainer.baseTypeDocString) + } + checker.VisitExpression(paramDefaultArgument.DefaultArgument, 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 if !IsSubType(paramType, StringType) && !IsSubType(paramType, NumberType) && + !IsSubType(paramType, TheAddressType) && !IsSubType(paramType, BoolType) { checker.report(&DefaultDestroyInvalidParameterError{ ParamType: paramType, Range: ast.NewRangeFromPositioned(checker.memoryGauge, eventDeclaration), }) } - } - for _, param := range eventType.ConstructorParameters { - checkParamTypeValid(param.TypeAnnotation.Type) } } diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index d35ecadc07..bf8a172c04 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -158,6 +158,9 @@ func (checker *Checker) VisitInterfaceDeclaration(declaration *ast.InterfaceDecl if nestedComposite.Kind() == common.CompositeKindEvent { checker.visitCompositeLikeDeclaration(nestedComposite) } + if interfaceType.DefaultDestroyEvent != nil { + checker.checkDefaultDestroyEvent(interfaceType.DefaultDestroyEvent, nestedComposite, interfaceType, declaration) + } } return @@ -444,8 +447,7 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa nestedEvent := checker.typeActivations.Find(nestedCompositeDeclaration.Identifier.Identifier) defaultEventComposite := nestedEvent.Type.(*CompositeType) - checker.checkDefaultDestroyEvent(defaultEventComposite, nestedCompositeDeclaration, interfaceType) - interfaceType.DefaultDestroyEvent = nestedEvent.Type.(*CompositeType) + interfaceType.DefaultDestroyEvent = defaultEventComposite // interfaceType.DefaultDestroyEvent = } else { checker.declareNestedEvent(nestedCompositeDeclaration, eventMembers, interfaceType) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index ab8a2a50b3..d206d15469 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -1288,19 +1288,12 @@ func (checker *Checker) parameters(parameterList *ast.ParameterList) []Parameter for i, parameter := range parameterList.Parameters { convertedParameterType := checker.ConvertType(parameter.TypeAnnotation.Type) - var convertedParameterDefaultArgument Type = nil - if parameter.DefaultArgument != nil { - // default arg expressions must be subtypes of the type annotation - convertedParameterDefaultArgument = checker.VisitExpression(parameter.DefaultArgument, convertedParameterType) - } - // NOTE: copying resource annotation from source type annotation as-is, // so a potential error is properly reported parameters[i] = Parameter{ - Label: parameter.Label, - Identifier: parameter.Identifier.Identifier, - DefaultArgument: convertedParameterDefaultArgument, + Label: parameter.Label, + Identifier: parameter.Identifier.Identifier, TypeAnnotation: TypeAnnotation{ IsResource: parameter.TypeAnnotation.IsResource, Type: convertedParameterType, From 26b5adeac0910933b6c7bbad83cd3098a2e9799b Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 22 Sep 2023 12:06:32 -0400 Subject: [PATCH 12/48] check only certain expressions are allowed --- runtime/sema/check_composite_declaration.go | 52 +++- runtime/tests/checker/events_test.go | 270 +++++++++++++++++++- 2 files changed, 313 insertions(+), 9 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index ffcf746160..c4f2d7e8a2 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2024,9 +2024,44 @@ func (checker *Checker) checkDefaultDestroyEvent( functions := members.SpecialFunctions() constructorFunctionParameters := functions[0].FunctionDeclaration.ParameterList.Parameters + var checkParamExpressionKind func(ast.Expression) + checkParamExpressionKind = func(arg ast.Expression) { + switch arg := arg.(type) { + case *ast.StringExpression, + *ast.BoolExpression, + *ast.NilExpression, + *ast.IntegerExpression, + *ast.FixedPointExpression, + *ast.IdentifierExpression, + *ast.PathExpression: + break + case *ast.MemberExpression: + checkParamExpressionKind(arg.Expression) + case *ast.IndexExpression: + checkParamExpressionKind(arg.TargetExpression) + checkParamExpressionKind(arg.IndexingExpression) + + // indexing expressions on arrays can fail, and must be disallowed, but + // indexing expressions on dicts, or composites (for attachments) will return `nil` and thus never fail + targetExprType := checker.Elaboration.IndexExpressionTypes(arg).IndexedType + switch targetExprType.(type) { + case *VariableSizedType, *ConstantSizedType: + checker.report(&DefaultDestroyInvalidArgumentError{ + ast.NewRangeFromPositioned(checker.memoryGauge, arg), + }) + } + + default: + checker.report(&DefaultDestroyInvalidArgumentError{ + ast.NewRangeFromPositioned(checker.memoryGauge, arg), + }) + } + } + for index, param := range eventType.ConstructorParameters { paramType := param.TypeAnnotation.Type - paramDefaultArgument := constructorFunctionParameters[index] + paramExpr := constructorFunctionParameters[index] + paramDefaultArgument := paramExpr.DefaultArgument // make `self` and `base` available when checking default arguments so the fields of the composite are available checker.declareSelfValue(containerType, containerDeclaration.DeclarationDocString()) @@ -2036,20 +2071,23 @@ func (checker *Checker) checkDefaultDestroyEvent( compositeContainer, compositeContainer.baseTypeDocString) } - checker.VisitExpression(paramDefaultArgument.DefaultArgument, paramType) + checker.VisitExpression(paramDefaultArgument, paramType) + unwrappedParamType := UnwrapOptionalType(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 - if !IsSubType(paramType, StringType) && - !IsSubType(paramType, NumberType) && - !IsSubType(paramType, TheAddressType) && - !IsSubType(paramType, BoolType) { + if !IsSubType(unwrappedParamType, StringType) && + !IsSubType(unwrappedParamType, NumberType) && + !IsSubType(unwrappedParamType, TheAddressType) && + !IsSubType(unwrappedParamType, BoolType) { checker.report(&DefaultDestroyInvalidParameterError{ ParamType: paramType, - Range: ast.NewRangeFromPositioned(checker.memoryGauge, eventDeclaration), + Range: ast.NewRangeFromPositioned(checker.memoryGauge, paramExpr), }) } + checkParamExpressionKind(paramDefaultArgument) + } } diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index f41c65f8fd..226501d514 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -660,7 +660,7 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { _, err := ParseAndCheck(t, ` resource R { - event ResourceDestroyed(name: String = "foo", id: UInt16 = 4, condition: Bool = true) + event ResourceDestroyed(name: String = "foo", id: UInt16? = 4, condition: Bool = true) } `) require.NoError(t, err) @@ -697,15 +697,69 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { require.NoError(t, err) }) + t.Run("address", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + let field : Address + event ResourceDestroyed(name: Address? = self.field) + + init() { + self.field = 0x1 + } + } + `) + require.NoError(t, err) + }) + + t.Run("nil", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: Address? = nil) + } + `) + require.NoError(t, err) + }) + + t.Run("address expr", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: Address = 0x1) + } + `) + require.NoError(t, err) + }) + + t.Run("float", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: UFix64 = 0.0034) + } + `) + require.NoError(t, err) + }) + t.Run("field type mismatch", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` resource R { - let field : Int event ResourceDestroyed(name: String = self.field) + let field : Int + init() { self.field = 3 } @@ -735,4 +789,216 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { assert.IsType(t, &sema.DefaultDestroyInvalidParameterError{}, errs[0]) }) + t.Run("function call", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: Int = self.fn()) + + fun fn(): Int { + return 3 + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + + t.Run("external field", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + let s: S = S() + + resource R { + event ResourceDestroyed(name: UFix64 = s.field) + } + + struct S { + let field : UFix64 + init() { + self.field = 0.0034 + } + } + `) + require.NoError(t, err) + }) + + t.Run("double nested field", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + let s: S + event ResourceDestroyed(name: UFix64 = self.s.field) + init() { + self.s = S() + } + } + + struct S { + let field : UFix64 + init() { + self.field = 0.0034 + } + } + `) + require.NoError(t, err) + }) + + t.Run("function call member", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun getS(): S { + return S() + } + + resource R { + event ResourceDestroyed(name: UFix64 = getS().field) + } + + struct S { + let field : UFix64 + init() { + self.field = 0.0034 + } + } + `) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + + t.Run("method call member", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + fun getS(): S { + return S() + } + event ResourceDestroyed(name: UFix64 = self.getS().field) + } + + struct S { + let field : UFix64 + init() { + self.field = 0.0034 + } + } + `) + + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + + t.Run("array index expression", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + var arr : [String] = [] + + resource R { + event ResourceDestroyed(name: String? = arr[0]) + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + + t.Run("dict index expression", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + var arr : {Int: String} = {} + + resource R { + event ResourceDestroyed(name: String? = arr[0]) + } + `) + require.NoError(t, err) + }) + + t.Run("function call dict index expression", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + fun get(): {Int: String} { + return {} + } + + resource R { + event ResourceDestroyed(name: String? = get()[0]) + } + `) + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + + t.Run("function call dict indexed expression", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + var dict : {Int: String} = {} + + resource R { + event ResourceDestroyed(name: String? = dict[0+1]) + } + `) + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + + t.Run("attachment index expression", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + attachment A for R { + let name: String + init() { + self.name = "foo" + } + } + + resource R { + event ResourceDestroyed(name: String? = self[A]?.name) + } + `) + require.NoError(t, err) + }) + + t.Run("attachment with base", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + attachment A for R { + event ResourceDestroyed(name: Int = base.field) + } + + resource R { + let field : Int + + init() { + self.field = 3 + } + } + `) + require.NoError(t, err) + }) + } From ff166ec8e0cd2f511e4eb5ac10df19d5759af3bf Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 22 Sep 2023 12:16:14 -0400 Subject: [PATCH 13/48] amend error message --- runtime/sema/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 8b3df1823b..366799ea5b 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4672,7 +4672,7 @@ func (*DefaultDestroyInvalidArgumentError) isSemanticError() {} func (*DefaultDestroyInvalidArgumentError) IsUserError() {} func (e *DefaultDestroyInvalidArgumentError) Error() string { - return "default destroy event arguments must be literals or member access expressions on `self`" + return "default destroy event arguments must be literals, member access expressions, indexed access expressions on dictionaries, or attachment accesses" } // DefaultDestroyInvalidArgumentError From a986a76bc20f6a48e7d1d9c350e222cd6e27f0a1 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 22 Sep 2023 14:09:17 -0400 Subject: [PATCH 14/48] permit paths --- runtime/sema/check_composite_declaration.go | 14 +++- runtime/sema/errors.go | 2 +- runtime/tests/checker/events_test.go | 77 ++++++++++++++++----- 3 files changed, 73 insertions(+), 20 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index c4f2d7e8a2..af1487d799 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2032,9 +2032,20 @@ func (checker *Checker) checkDefaultDestroyEvent( *ast.NilExpression, *ast.IntegerExpression, *ast.FixedPointExpression, - *ast.IdentifierExpression, *ast.PathExpression: break + case *ast.IdentifierExpression: + // these are guaranteed to exist at time of destruction, so we allow them + if arg.Identifier.Identifier == SelfIdentifier || arg.Identifier.Identifier == BaseIdentifier { + break + } + // if it's an attachment, then it's also okay + if checker.typeActivations.Find(arg.Identifier.Identifier) != nil { + break + } + checker.report(&DefaultDestroyInvalidArgumentError{ + ast.NewRangeFromPositioned(checker.memoryGauge, arg), + }) case *ast.MemberExpression: checkParamExpressionKind(arg.Expression) case *ast.IndexExpression: @@ -2079,6 +2090,7 @@ func (checker *Checker) checkDefaultDestroyEvent( if !IsSubType(unwrappedParamType, StringType) && !IsSubType(unwrappedParamType, NumberType) && !IsSubType(unwrappedParamType, TheAddressType) && + !IsSubType(unwrappedParamType, PathType) && !IsSubType(unwrappedParamType, BoolType) { checker.report(&DefaultDestroyInvalidParameterError{ ParamType: paramType, diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 366799ea5b..bc356b6523 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -4672,7 +4672,7 @@ func (*DefaultDestroyInvalidArgumentError) isSemanticError() {} func (*DefaultDestroyInvalidArgumentError) IsUserError() {} func (e *DefaultDestroyInvalidArgumentError) Error() string { - return "default destroy event arguments must be literals, member access expressions, indexed access expressions on dictionaries, or attachment accesses" + return "default destroy event arguments must be literals, member access expressions on `self` or `base`, indexed access expressions on dictionaries, or attachment accesses" } // DefaultDestroyInvalidArgumentError diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 226501d514..ae6584561f 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -738,6 +738,18 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { require.NoError(t, err) }) + t.Run("path expr", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: PublicPath = /public/foo) + } + `) + require.NoError(t, err) + }) + t.Run("float", func(t *testing.T) { t.Parallel() @@ -812,20 +824,22 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - let s: S = S() - + let r2 <- create R2() + let ref = &r2 as &R2 + resource R { - event ResourceDestroyed(name: UFix64 = s.field) + event ResourceDestroyed(name: UFix64 = ref.field) } - struct S { + resource R2 { let field : UFix64 init() { self.field = 0.0034 } } `) - require.NoError(t, err) + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) }) t.Run("double nested field", func(t *testing.T) { @@ -905,10 +919,13 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - var arr : [String] = [] - resource R { - event ResourceDestroyed(name: String? = arr[0]) + var arr : [String] + event ResourceDestroyed(name: String? = self.arr[0]) + + init() { + self.arr = [] + } } `) errs := RequireCheckerErrors(t, err, 1) @@ -921,10 +938,13 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - var arr : {Int: String} = {} - resource R { - event ResourceDestroyed(name: String? = arr[0]) + let dict : {Int: String} + event ResourceDestroyed(name: String? = self.dict[0]) + + init() { + self.dict = {} + } } `) require.NoError(t, err) @@ -935,12 +955,11 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - fun get(): {Int: String} { - return {} - } - resource R { - event ResourceDestroyed(name: String? = get()[0]) + event ResourceDestroyed(name: String? = self.get()[0]) + fun get(): {Int: String} { + return {} + } } `) errs := RequireCheckerErrors(t, err, 1) @@ -952,10 +971,32 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - var dict : {Int: String} = {} + resource R { + let dict : {Int: String} + event ResourceDestroyed(name: String? = self.dict[0+1]) + init() { + self.dict = {} + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + + t.Run("external var expression", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + var index: Int = 3 resource R { - event ResourceDestroyed(name: String? = dict[0+1]) + let dict : {Int: String} + event ResourceDestroyed(name: String? = self.dict[index]) + + init() { + self.dict = {} + } } `) errs := RequireCheckerErrors(t, err, 1) From 01f5e1c05cd6aaf7b0ef586b4eac1802c4abd4e8 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 22 Sep 2023 15:43:56 -0400 Subject: [PATCH 15/48] update error message on unexpected default arg --- runtime/parser/declaration.go | 3 ++- runtime/parser/declaration_test.go | 2 +- runtime/parser/function.go | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/runtime/parser/declaration.go b/runtime/parser/declaration.go index 274d06c7df..58f86e012b 100644 --- a/runtime/parser/declaration.go +++ b/runtime/parser/declaration.go @@ -862,7 +862,8 @@ func parseEventDeclaration( p.next() // if this is a `ResourceDestroyed` event (i.e., a default event declaration), parse default arguments - parameterList, err := parseParameterList(p, identifier.Identifier == ast.ResourceDestructionDefaultEventName) + parseDefaultArguments := identifier.Identifier == ast.ResourceDestructionDefaultEventName + parameterList, err := parseParameterList(p, parseDefaultArguments) if err != nil { return nil, err } diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index 666b5ea5d1..efa816d963 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -2696,7 +2696,7 @@ func TestParseEvent(t *testing.T) { utils.AssertEqualWithDiff(t, []error{ &SyntaxError{ Pos: ast.Position{Line: 1, Column: 33, Offset: 33}, - Message: "expected comma or end of parameter list, got '='", + Message: "cannot use a default argument for this function", }, }, errs) }) diff --git a/runtime/parser/function.go b/runtime/parser/function.go index feb3cb5e15..69b202b1b4 100644 --- a/runtime/parser/function.go +++ b/runtime/parser/function.go @@ -187,6 +187,8 @@ func parseParameter(p *parser, expectDefaultArgument bool) (*ast.Parameter, erro return nil, err } + } else if p.current.Is(lexer.TokenEqual) { + return nil, p.syntaxError("cannot use a default argument for this function") } return ast.NewParameter( From 3295bbbf3ab928918e1eee4ea45bf7ab28932cf6 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 26 Sep 2023 10:50:43 -0400 Subject: [PATCH 16/48] emit default event --- runtime/interpreter/interpreter.go | 8 +++ runtime/interpreter/interpreter_invocation.go | 24 +++++++- runtime/interpreter/interpreter_statement.go | 31 +++++++---- runtime/interpreter/value.go | 55 ++++++++++++++----- runtime/tests/interpreter/interpreter_test.go | 29 ++++++---- 5 files changed, 108 insertions(+), 39 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 74781e7171..3a22d5a066 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1014,6 +1014,8 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( nestedVariables := map[string]*Variable{} + var destroyEventConstructor FunctionValue + (func() { interpreter.activations.PushNewWithCurrent() defer interpreter.activations.Pop() @@ -1060,6 +1062,11 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( memberIdentifier := nestedCompositeDeclaration.Identifier.Identifier nestedVariables[memberIdentifier] = nestedVariable + + // statically we know there is at most one of these + if nestedCompositeDeclaration.IsResourceDestructionDefaultEvent() { + destroyEventConstructor = nestedVariable.GetValue().(FunctionValue) + } } for _, nestedAttachmentDeclaration := range members.Attachments() { @@ -1249,6 +1256,7 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( value.InjectedFields = injectedFields value.Functions = functions + value.defaultDestroyEventConstructor = destroyEventConstructor var self MemberAccessibleValue = value if declaration.Kind() == common.CompositeKindAttachment { diff --git a/runtime/interpreter/interpreter_invocation.go b/runtime/interpreter/interpreter_invocation.go index 8f4fb6dbed..e75df27b5f 100644 --- a/runtime/interpreter/interpreter_invocation.go +++ b/runtime/interpreter/interpreter_invocation.go @@ -185,12 +185,32 @@ func (interpreter *Interpreter) invokeInterpretedFunctionActivated( ) } -// bindParameterArguments binds the argument values to the given parameters +// bindParameterArguments binds the argument values to the given parameters. +// the handling of default arguments makes a number of assumptions to simplify the implementation; +// namely that a) all default arguments are lazily evaluated at the site of the invocation, +// b) that either all the parameters or none of the parameters of a function have default arguments, +// and c) functions cannot currently be explicitly invoked if they have default arguments +// if we plan to generalize this further, we will need to relax those assumptions func (interpreter *Interpreter) bindParameterArguments( parameterList *ast.ParameterList, arguments []Value, ) { - for parameterIndex, parameter := range parameterList.Parameters { + parameters := parameterList.Parameters + + if len(parameters) < 1 { + return + } + + // if the first parameter has a default arg, all of them do, and the arguments list is empty + if parameters[0].DefaultArgument != nil { + // lazily evaluate the default argument expression in this context + for _, parameter := range parameters { + defaultArg := interpreter.evalExpression(parameter.DefaultArgument) + arguments = append(arguments, defaultArg) + } + } + + for parameterIndex, parameter := range parameters { argument := arguments[parameterIndex] interpreter.declareVariable(parameter.Identifier.Identifier, argument) } diff --git a/runtime/interpreter/interpreter_statement.go b/runtime/interpreter/interpreter_statement.go index 59d249c796..bdd86fb6f3 100644 --- a/runtime/interpreter/interpreter_statement.go +++ b/runtime/interpreter/interpreter_statement.go @@ -24,6 +24,7 @@ import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/errors" + "github.com/onflow/cadence/runtime/sema" ) func (interpreter *Interpreter) evalStatement(statement ast.Statement) StatementResult { @@ -401,18 +402,7 @@ func (interpreter *Interpreter) visitForStatementBody( return nil, false } -func (interpreter *Interpreter) VisitEmitStatement(statement *ast.EmitStatement) StatementResult { - event, ok := interpreter.evalExpression(statement.InvocationExpression).(*CompositeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - eventType := interpreter.Program.Elaboration.EmitStatementEventType(statement) - - locationRange := LocationRange{ - Location: interpreter.Location, - HasPosition: statement, - } +func (interpreter *Interpreter) emitEvent(event *CompositeValue, eventType *sema.CompositeType, locationRange LocationRange) { config := interpreter.SharedState.Config @@ -427,6 +417,23 @@ func (interpreter *Interpreter) VisitEmitStatement(statement *ast.EmitStatement) if err != nil { panic(err) } +} + +func (interpreter *Interpreter) VisitEmitStatement(statement *ast.EmitStatement) StatementResult { + + event, ok := interpreter.evalExpression(statement.InvocationExpression).(*CompositeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + eventType := interpreter.Program.Elaboration.EmitStatementEventType(statement) + + locationRange := LocationRange{ + Location: interpreter.Location, + HasPosition: statement, + } + + interpreter.emitEvent(event, eventType, locationRange) return nil } diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 434b945b94..fa7e4adc36 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16222,6 +16222,8 @@ type CompositeValue struct { QualifiedIdentifier string Kind common.CompositeKind isDestroyed bool + + defaultDestroyEventConstructor FunctionValue } type ComputedField func(*Interpreter, LocationRange) Value @@ -16457,6 +16459,31 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio }() } + // before actually performing the destruction (i.e. so that any fields are still available), + // emit the default destruction event, if one exists + + if v.defaultDestroyEventConstructor != nil { + var base *EphemeralReferenceValue + var self MemberAccessibleValue = v + if v.Kind == common.CompositeKindAttachment { + base, self = attachmentBaseAndSelfValues(interpreter, v) + } + mockInvocation := NewInvocation( + interpreter, + &self, + base, + nil, + []Value{}, + []sema.Type{}, + nil, + locationRange, + ) + + event := v.defaultDestroyEventConstructor.invoke(mockInvocation).(*CompositeValue) + eventType := interpreter.MustSemaTypeOfValue(event).(*sema.CompositeType) + interpreter.emitEvent(event, eventType, locationRange) + } + storageID := v.StorageID() interpreter.withResourceDestruction( @@ -17273,6 +17300,7 @@ func (v *CompositeValue) Transfer( res.typeID = v.typeID res.staticType = v.staticType res.base = v.base + res.defaultDestroyEventConstructor = v.defaultDestroyEventConstructor } onResourceOwnerChange := config.OnResourceOwnerChange @@ -17345,19 +17373,20 @@ func (v *CompositeValue) Clone(interpreter *Interpreter) Value { } return &CompositeValue{ - dictionary: dictionary, - Location: v.Location, - QualifiedIdentifier: v.QualifiedIdentifier, - Kind: v.Kind, - InjectedFields: v.InjectedFields, - ComputedFields: v.ComputedFields, - NestedVariables: v.NestedVariables, - Functions: v.Functions, - Stringer: v.Stringer, - isDestroyed: v.isDestroyed, - typeID: v.typeID, - staticType: v.staticType, - base: v.base, + dictionary: dictionary, + Location: v.Location, + QualifiedIdentifier: v.QualifiedIdentifier, + Kind: v.Kind, + InjectedFields: v.InjectedFields, + ComputedFields: v.ComputedFields, + NestedVariables: v.NestedVariables, + Functions: v.Functions, + Stringer: v.Stringer, + isDestroyed: v.isDestroyed, + typeID: v.typeID, + staticType: v.staticType, + base: v.base, + defaultDestroyEventConstructor: v.defaultDestroyEventConstructor, } } diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index b13aa92fc1..a76293f627 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -6794,28 +6794,33 @@ func TestInterpretResourceDestroyExpressionDestructor(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - var ranDestructor = false + var events []*interpreter.CompositeValue - resource R { } + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } fun test() { let r <- create R() destroy r } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) - AssertValuesEqual( - t, - inter, - interpreter.FalseValue, - inter.Globals.Get("ranDestructor").GetValue(), - ) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event + require.Len(t, events, 1) + require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") } func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { From 59210c1b6083a1afe96cf2e34859848717e08b43 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 26 Sep 2023 12:30:06 -0400 Subject: [PATCH 17/48] emit events on destroy --- runtime/interpreter/interpreter.go | 28 +- runtime/interpreter/interpreter_invocation.go | 18 -- runtime/interpreter/value.go | 63 +++-- runtime/tests/interpreter/attachments_test.go | 174 ++++++++++--- runtime/tests/interpreter/interpreter_test.go | 245 ++++++++++++------ 5 files changed, 361 insertions(+), 167 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 3a22d5a066..1431602436 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1101,6 +1101,29 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( locationRange := invocation.LocationRange self := *invocation.Self + // the handling of default arguments makes a number of assumptions to simplify the implementation; + // namely that a) all default arguments are lazily evaluated at the site of the invocation, + // b) that either all the parameters or none of the parameters of a function have default arguments, + // and c) functions cannot currently be explicitly invoked if they have default arguments + // if we plan to generalize this further, we will need to relax those assumptions + if len(compositeType.ConstructorParameters) < 1 { + return nil + } + + // event intefaces do not exist + compositeDecl := declaration.(*ast.CompositeDeclaration) + if compositeDecl.IsResourceDestructionDefaultEvent() { + parameters := compositeDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters + // if the first parameter has a default arg, all of them do, and the arguments list is empty + if parameters[0].DefaultArgument != nil { + // lazily evaluate the default argument expression in this context + for _, parameter := range parameters { + defaultArg := interpreter.evalExpression(parameter.DefaultArgument) + invocation.Arguments = append(invocation.Arguments, defaultArg) + } + } + } + for i, argument := range invocation.Arguments { parameter := compositeType.ConstructorParameters[i] self.SetMember( @@ -1122,6 +1145,10 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( functions := interpreter.compositeFunctions(declaration, lexicalScope) + if destroyEventConstructor != nil { + functions[resourceDefaultDestroyEventName] = destroyEventConstructor + } + wrapFunctions := func(code WrapperCode) { // Wrap initializer @@ -1256,7 +1283,6 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( value.InjectedFields = injectedFields value.Functions = functions - value.defaultDestroyEventConstructor = destroyEventConstructor var self MemberAccessibleValue = value if declaration.Kind() == common.CompositeKindAttachment { diff --git a/runtime/interpreter/interpreter_invocation.go b/runtime/interpreter/interpreter_invocation.go index e75df27b5f..2019df8a52 100644 --- a/runtime/interpreter/interpreter_invocation.go +++ b/runtime/interpreter/interpreter_invocation.go @@ -186,30 +186,12 @@ func (interpreter *Interpreter) invokeInterpretedFunctionActivated( } // bindParameterArguments binds the argument values to the given parameters. -// the handling of default arguments makes a number of assumptions to simplify the implementation; -// namely that a) all default arguments are lazily evaluated at the site of the invocation, -// b) that either all the parameters or none of the parameters of a function have default arguments, -// and c) functions cannot currently be explicitly invoked if they have default arguments -// if we plan to generalize this further, we will need to relax those assumptions func (interpreter *Interpreter) bindParameterArguments( parameterList *ast.ParameterList, arguments []Value, ) { parameters := parameterList.Parameters - if len(parameters) < 1 { - return - } - - // if the first parameter has a default arg, all of them do, and the arguments list is empty - if parameters[0].DefaultArgument != nil { - // lazily evaluate the default argument expression in this context - for _, parameter := range parameters { - defaultArg := interpreter.evalExpression(parameter.DefaultArgument) - arguments = append(arguments, defaultArg) - } - } - for parameterIndex, parameter := range parameters { argument := arguments[parameterIndex] interpreter.declareVariable(parameter.Identifier.Identifier, argument) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index fa7e4adc36..5fe722669c 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16222,8 +16222,6 @@ type CompositeValue struct { QualifiedIdentifier string Kind common.CompositeKind isDestroyed bool - - defaultDestroyEventConstructor FunctionValue } type ComputedField func(*Interpreter, LocationRange) Value @@ -16233,7 +16231,8 @@ type CompositeField struct { Name string } -const attachmentNamePrefix = "$" +const unrepresentableNamePrefix = "$" +const resourceDefaultDestroyEventName = unrepresentableNamePrefix + "ResourceDestroyed" var _ TypeIndexableValue = &CompositeValue{} @@ -16429,6 +16428,10 @@ func (v *CompositeValue) IsDestroyed() bool { return v.isDestroyed } +func (v *CompositeValue) defaultDestroyEventConstructor() FunctionValue { + return v.Functions[resourceDefaultDestroyEventName] +} + func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange LocationRange) { interpreter.ReportComputation(common.ComputationKindDestroyCompositeValue, 1) @@ -16460,18 +16463,26 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio } // before actually performing the destruction (i.e. so that any fields are still available), - // emit the default destruction event, if one exists + // compute the default arguments of the default destruction event (if it exists). However, + // wait until after the destruction completes to actually emit the event, so that the correct order + // is preserved and nested resource destroy events happen first - if v.defaultDestroyEventConstructor != nil { - var base *EphemeralReferenceValue + // the default destroy event constructor is encoded as a function on the resource (with an unrepresentable name) + // so that we can leverage existing atree encoding and decoding. However, we need to make sure functions are initialized + // if the composite was recently loaded from storage + v.InitializeFunctions(interpreter) + if constructor := v.defaultDestroyEventConstructor(); constructor != nil { var self MemberAccessibleValue = v if v.Kind == common.CompositeKindAttachment { + var base *EphemeralReferenceValue base, self = attachmentBaseAndSelfValues(interpreter, v) + interpreter.declareVariable(sema.BaseIdentifier, base) } + interpreter.declareVariable(sema.SelfIdentifier, self) mockInvocation := NewInvocation( interpreter, - &self, - base, + nil, + nil, nil, []Value{}, []sema.Type{}, @@ -16479,9 +16490,9 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio locationRange, ) - event := v.defaultDestroyEventConstructor.invoke(mockInvocation).(*CompositeValue) + event := constructor.invoke(mockInvocation).(*CompositeValue) eventType := interpreter.MustSemaTypeOfValue(event).(*sema.CompositeType) - interpreter.emitEvent(event, eventType, locationRange) + defer interpreter.emitEvent(event, eventType, locationRange) } storageID := v.StorageID() @@ -17300,7 +17311,6 @@ func (v *CompositeValue) Transfer( res.typeID = v.typeID res.staticType = v.staticType res.base = v.base - res.defaultDestroyEventConstructor = v.defaultDestroyEventConstructor } onResourceOwnerChange := config.OnResourceOwnerChange @@ -17373,20 +17383,19 @@ func (v *CompositeValue) Clone(interpreter *Interpreter) Value { } return &CompositeValue{ - dictionary: dictionary, - Location: v.Location, - QualifiedIdentifier: v.QualifiedIdentifier, - Kind: v.Kind, - InjectedFields: v.InjectedFields, - ComputedFields: v.ComputedFields, - NestedVariables: v.NestedVariables, - Functions: v.Functions, - Stringer: v.Stringer, - isDestroyed: v.isDestroyed, - typeID: v.typeID, - staticType: v.staticType, - base: v.base, - defaultDestroyEventConstructor: v.defaultDestroyEventConstructor, + dictionary: dictionary, + Location: v.Location, + QualifiedIdentifier: v.QualifiedIdentifier, + Kind: v.Kind, + InjectedFields: v.InjectedFields, + ComputedFields: v.ComputedFields, + NestedVariables: v.NestedVariables, + Functions: v.Functions, + Stringer: v.Stringer, + isDestroyed: v.isDestroyed, + typeID: v.typeID, + staticType: v.staticType, + base: v.base, } } @@ -17565,7 +17574,7 @@ func (v *CompositeValue) setBaseValue(interpreter *Interpreter, base *CompositeV } func attachmentMemberName(ty sema.Type) string { - return attachmentNamePrefix + string(ty.ID()) + return unrepresentableNamePrefix + string(ty.ID()) } func (v *CompositeValue) getAttachmentValue(interpreter *Interpreter, locationRange LocationRange, ty sema.Type) *CompositeValue { @@ -17705,7 +17714,7 @@ func (v *CompositeValue) forEachAttachment(interpreter *Interpreter, _ LocationR if key == nil { break } - if strings.HasPrefix(string(key.(StringAtreeValue)), attachmentNamePrefix) { + if strings.HasPrefix(string(key.(StringAtreeValue)), unrepresentableNamePrefix) { attachment, ok := MustConvertStoredValue(interpreter, value).(*CompositeValue) if !ok { panic(errors.NewExternalError(err)) diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 52fdddeba6..a6894e5a75 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1239,71 +1239,139 @@ func TestInterpretAttachmentDestructor(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - var destructorRun = false - resource R {} - attachment A for R {} + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } + attachment A for R { + event ResourceDestroyed() + } fun test() { let r <- create R() let r2 <- attach A() to <-r destroy r2 } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event of A + require.Len(t, events, 2) + require.Equal(t, events[0].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, events[1].QualifiedIdentifier, "R.ResourceDestroyed") }) t.Run("base destructor executed last", func(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - resource R {} - attachment A for R { } - attachment B for R { } - attachment C for R { } + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } + attachment A for R { + event ResourceDestroyed() + } + attachment B for R { + event ResourceDestroyed() + } + attachment C for R { + event ResourceDestroyed() + } fun test() { let r <- create R() let r2 <- attach A() to <- attach B() to <- attach C() to <-r destroy r2 } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event for R being the last emitted + require.Len(t, events, 4) + // the only part of this order that is important is that `R` is last + require.Equal(t, events[0].QualifiedIdentifier, "B.ResourceDestroyed") + require.Equal(t, events[1].QualifiedIdentifier, "C.ResourceDestroyed") + require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, events[3].QualifiedIdentifier, "R.ResourceDestroyed") }) t.Run("remove runs destroy", func(t *testing.T) { - inter := parseCheckAndInterpret(t, ` - var destructorRun = false - resource R {} - attachment A for R {} + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } + attachment A for R { + event ResourceDestroyed() + } fun test(): @R { let r <- create R() let r2 <- attach A() to <-r remove A from r2 return <-r2 } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event of both A + require.Len(t, events, 1) + require.Equal(t, events[0].QualifiedIdentifier, "A.ResourceDestroyed") }) t.Run("remove runs resource field destroy", func(t *testing.T) { - inter := parseCheckAndInterpret(t, ` - resource R {} - resource R2 {} + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } + resource R2 { + event ResourceDestroyed() + } attachment A for R { + event ResourceDestroyed() let r2: @R2 init() { self.r2 <- create R2() @@ -1315,21 +1383,43 @@ func TestInterpretAttachmentDestructor(t *testing.T) { remove A from r2 return <-r2 } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event of R2 + require.Len(t, events, 2) + require.Equal(t, events[0].QualifiedIdentifier, "R2.ResourceDestroyed") + require.Equal(t, events[1].QualifiedIdentifier, "A.ResourceDestroyed") }) t.Run("nested attachments destroyed", func(t *testing.T) { - inter := parseCheckAndInterpret(t, ` - resource R {} - resource R2 {} - attachment B for R2 { } + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } + resource R2 { + event ResourceDestroyed() + } + attachment B for R2 { + event ResourceDestroyed() + } attachment A for R { + event ResourceDestroyed() let r2: @R2 init() { self.r2 <- attach B() to <-create R2() @@ -1341,12 +1431,26 @@ func TestInterpretAttachmentDestructor(t *testing.T) { remove A from r2 return <-r2 } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event of both B + require.Len(t, events, 3) + require.Equal(t, events[0].QualifiedIdentifier, "B.ResourceDestroyed") + require.Equal(t, events[1].QualifiedIdentifier, "R2.ResourceDestroyed") + require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") }) } diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index a76293f627..77bf6f23b0 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -6486,10 +6486,11 @@ func TestInterpretResourceMoveInArrayAndDestroy(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - var destroys = 0 + var events []*interpreter.CompositeValue + inter, err := parseCheckAndInterpretWithOptions(t, ` resource Foo { + event ResourceDestroyed(bar: Int = self.bar) var bar: Int init(bar: Int) { @@ -6505,14 +6506,15 @@ func TestInterpretResourceMoveInArrayAndDestroy(t *testing.T) { destroy foos return bar } - `) - - AssertValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(0), - inter.Globals.Get("destroys").GetValue(), - ) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + require.NoError(t, err) value, err := inter.Invoke("test") require.NoError(t, err) @@ -6524,17 +6526,22 @@ func TestInterpretResourceMoveInArrayAndDestroy(t *testing.T) { value, ) - // DestructorTODO: replace with test for destruction event + require.Len(t, events, 2) + require.Equal(t, events[0].QualifiedIdentifier, "Foo.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[1].QualifiedIdentifier, "Foo.ResourceDestroyed") + require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 2)) } func TestInterpretResourceMoveInDictionaryAndDestroy(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - var destroys = 0 + var events []*interpreter.CompositeValue + inter, err := parseCheckAndInterpretWithOptions(t, ` resource Foo { + event ResourceDestroyed(bar: Int = self.bar) var bar: Int init(bar: Int) { @@ -6548,19 +6555,24 @@ func TestInterpretResourceMoveInDictionaryAndDestroy(t *testing.T) { let foos <- {"foo1": <-foo1, "foo2": <-foo2} destroy foos } - `) - - RequireValuesEqual( - t, - inter, - interpreter.NewUnmeteredIntValueFromInt64(0), - inter.Globals.Get("destroys").GetValue(), - ) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event + require.Len(t, events, 2) + require.Equal(t, events[0].QualifiedIdentifier, "Foo.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[1].QualifiedIdentifier, "Foo.ResourceDestroyed") + require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 2)) } func TestInterpretClosure(t *testing.T) { @@ -6827,10 +6839,21 @@ func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - resource B {} + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource B { + var foo: Int + event ResourceDestroyed(foo: Int = self.foo) + + init() { + self.foo = 5 + } + } resource A { + event ResourceDestroyed(foo: Int = self.b.foo) + let b: @B init(b: @B) { @@ -6843,88 +6866,153 @@ func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { let a <- create A(b: <-b) destroy a } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event of both A and B + require.Len(t, events, 2) + require.Equal(t, events[0].QualifiedIdentifier, "B.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "foo"), interpreter.NewIntValueFromInt64(nil, 5)) + require.Equal(t, events[1].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "foo"), interpreter.NewIntValueFromInt64(nil, 5)) } func TestInterpretResourceDestroyArray(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - resource R {} + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } fun test() { let rs <- [<-create R(), <-create R()] destroy rs } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event emitted twice + require.Len(t, events, 2) + require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, events[1].QualifiedIdentifier, "R.ResourceDestroyed") } func TestInterpretResourceDestroyDictionary(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - resource R { } + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } fun test() { let rs <- {"r1": <-create R(), "r2": <-create R()} destroy rs } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event emitted twice + require.Len(t, events, 2) + require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, events[1].QualifiedIdentifier, "R.ResourceDestroyed") } func TestInterpretResourceDestroyOptionalSome(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - resource R { } + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } fun test() { let maybeR: @R? <- create R() destroy maybeR } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event + require.Len(t, events, 1) + require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") } func TestInterpretResourceDestroyOptionalNil(t *testing.T) { t.Parallel() - inter := parseCheckAndInterpret(t, ` - resource R {} + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + event ResourceDestroyed() + } fun test() { let maybeR: @R? <- nil destroy maybeR } - `) + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + require.NoError(t, err) - _, err := inter.Invoke("test") + _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event not emitted + require.Len(t, events, 0) } // TestInterpretInterfaceInitializer tests that the interface's initializer @@ -9331,39 +9419,15 @@ func TestInterpretNestedDestroy(t *testing.T) { t.Parallel() - var logs []string - - logFunction := stdlib.NewStandardLibraryFunction( - "log", - &sema.FunctionType{ - Parameters: []sema.Parameter{ - { - Label: sema.ArgumentLabelNotRequired, - Identifier: "value", - TypeAnnotation: sema.AnyStructTypeAnnotation, - }, - }, - ReturnTypeAnnotation: sema.VoidTypeAnnotation, - }, - ``, - func(invocation interpreter.Invocation) interpreter.Value { - message := invocation.Arguments[0].String() - logs = append(logs, message) - return interpreter.Void - }, - ) - - baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) - baseValueActivation.DeclareValue(logFunction) - - baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) - interpreter.Declare(baseActivation, logFunction) + var events []*interpreter.CompositeValue inter, err := parseCheckAndInterpretWithOptions(t, ` resource B { let id: Int + event ResourceDestroyed(id: Int = self.id) + init(_ id: Int){ self.id = id } @@ -9373,6 +9437,8 @@ func TestInterpretNestedDestroy(t *testing.T) { let id: Int let bs: @[B] + event ResourceDestroyed(id: Int = self.id, bCount: Int = self.bs.length) + init(_ id: Int){ self.id = id self.bs <- [] @@ -9391,30 +9457,37 @@ func TestInterpretNestedDestroy(t *testing.T) { destroy a } - `, - ParseCheckAndInterpretOptions{ + `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - BaseActivation: baseActivation, - }, - CheckerConfig: &sema.Config{ - BaseValueActivation: baseValueActivation, + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, }, - HandleCheckerError: nil, - }, - ) + }) + require.NoError(t, err) value, err := inter.Invoke("test") require.NoError(t, err) + require.Len(t, events, 4) + require.Equal(t, events[0].QualifiedIdentifier, "B.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 2)) + require.Equal(t, events[1].QualifiedIdentifier, "B.ResourceDestroyed") + require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 3)) + require.Equal(t, events[2].QualifiedIdentifier, "B.ResourceDestroyed") + require.Equal(t, events[2].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 4)) + require.Equal(t, events[3].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, events[3].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[3].GetField(inter, interpreter.EmptyLocationRange, "bCount"), interpreter.NewIntValueFromInt64(nil, 3)) + AssertValuesEqual( t, inter, interpreter.Void, value, ) - - // DestructorTODO: replace with test for destruction event for A and B } // TestInterpretInternalAssignment ensures that a modification of an "internal" value From b8954d4f50447a4c7096ea3cc98eb73ec73f674e Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 26 Sep 2023 13:05:41 -0400 Subject: [PATCH 18/48] fix attachment base values --- runtime/interpreter/value.go | 3 + runtime/tests/checker/events_test.go | 70 +++++++++++++++++++ runtime/tests/interpreter/attachments_test.go | 57 +++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 5fe722669c..2126240eba 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16505,6 +16505,9 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio // destroy every nested resource in this composite; note that this iteration includes attachments v.ForEachField(interpreter, func(_ string, fieldValue Value) bool { + if compositeFieldValue, ok := fieldValue.(*CompositeValue); ok && compositeFieldValue.Kind == common.CompositeKindAttachment { + compositeFieldValue.setBaseValue(interpreter, v, attachmentBaseAuthorization(interpreter, compositeFieldValue)) + } maybeDestroy(interpreter, locationRange, fieldValue) return true }) diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index ae6584561f..080c69d50d 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -1042,4 +1042,74 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { require.NoError(t, err) }) + t.Run("attachment with entitled base", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement E + + attachment A for R { + event ResourceDestroyed(name: Int = base.field) + } + + resource R { + access(E) let field : Int + + init() { + self.field = 3 + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.InvalidAccessError{}, errs[0]) + }) + + t.Run("attachment with entitled base allowed", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement E + + attachment A for R { + require entitlement E + event ResourceDestroyed(name: Int = base.field) + } + + resource R { + access(E) let field : Int + + init() { + self.field = 3 + } + } + `) + require.NoError(t, err) + }) + + t.Run("attachment with entitled self", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + entitlement E + + entitlement mapping M { + E -> E + } + + access(M) attachment A for R { + access(E) let field : Int + event ResourceDestroyed(name: Int = self.field) + init() { + self.field = 3 + } + } + + resource R {} + `) + require.NoError(t, err) + }) + } diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index a6894e5a75..16aeee2fcb 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1452,6 +1452,63 @@ func TestInterpretAttachmentDestructor(t *testing.T) { require.Equal(t, events[1].QualifiedIdentifier, "R2.ResourceDestroyed") require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") }) + + t.Run("attachment default args properly scoped", func(t *testing.T) { + + t.Parallel() + + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource R { + var foo: String + event ResourceDestroyed() + init() { + self.foo = "baz" + } + fun setFoo(arg: String) { + self.foo = arg + } + } + attachment A for R { + var bar: Int + event ResourceDestroyed(foo: String = base.foo, bar: Int = self.bar) + init() { + self.bar = 1 + } + fun setBar(arg: Int) { + self.bar = arg + } + } + fun test() { + let r <- create R() + let r2 <- attach A() to <-r + r2.setFoo(arg: "foo") + r2[A]!.setBar(arg: 2) + destroy r2 + } + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + CheckerConfig: &sema.Config{ + AttachmentsEnabled: true, + }, + }) + require.NoError(t, err) + + _, err = inter.Invoke("test") + require.NoError(t, err) + + require.Len(t, events, 2) + require.Equal(t, events[0].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "foo"), interpreter.NewUnmeteredStringValue("foo")) + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 2)) + require.Equal(t, events[1].QualifiedIdentifier, "R.ResourceDestroyed") + }) } func TestInterpretAttachmentResourceReferenceInvalidation(t *testing.T) { From e40c4907cebd888601441fed9506b0b29baf4a7f Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 26 Sep 2023 16:51:47 -0400 Subject: [PATCH 19/48] fix scoping across interpreters --- runtime/interpreter/interpreter.go | 65 ++++++++--- runtime/interpreter/value.go | 14 +-- runtime/resourcedictionary_test.go | 26 ++++- runtime/runtime_test.go | 169 +++++++++++++++++++++++++++-- 4 files changed, 238 insertions(+), 36 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 1431602436..f33a7f2518 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -965,6 +965,47 @@ func (interpreter *Interpreter) declareAttachmentValue( return interpreter.declareCompositeValue(declaration, lexicalScope) } +// evaluates all the implicit default arguments to the default destroy event +// +// the handling of default arguments makes a number of assumptions to simplify the implementation; +// namely that a) all default arguments are lazily evaluated at the site of the invocation, +// b) that either all the parameters or none of the parameters of a function have default arguments, +// and c) functions cannot currently be explicitly invoked if they have default arguments +// +// if we plan to generalize this further, we will need to relax those assumptions +func (interpreter *Interpreter) evaluateDefaultDestroyEvent( + containerComposite *CompositeValue, + compositeDecl *ast.CompositeDeclaration, + compositeType *sema.CompositeType, + locationRange LocationRange, +) (arguments []Value) { + parameters := compositeDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters + + interpreter.activations.PushNewWithParent(interpreter.activations.CurrentOrNew()) + defer interpreter.activations.Pop() + + var self MemberAccessibleValue = containerComposite + if containerComposite.Kind == common.CompositeKindAttachment { + var base *EphemeralReferenceValue + base, self = attachmentBaseAndSelfValues(interpreter, containerComposite) + interpreter.declareVariable(sema.BaseIdentifier, base) + } + interpreter.declareVariable(sema.SelfIdentifier, self) + + for _, parameter := range parameters { + // lazily evaluate the default argument expressions + // note that we must evaluate them in the interpreter context that existed when the event + // was defined (i.e. the contract defining the resource) rather than the interpreter context + // that exists when the resource is destroyed. We accomplish this by using the original interpreter of the + // composite declaration, rather than the interpreter of the destroy expression + + defaultArg := interpreter.evalExpression(parameter.DefaultArgument) + arguments = append(arguments, defaultArg) + } + + return +} + // declareCompositeValue creates and declares the value for // the composite declaration. // @@ -1101,27 +1142,21 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( locationRange := invocation.LocationRange self := *invocation.Self - // the handling of default arguments makes a number of assumptions to simplify the implementation; - // namely that a) all default arguments are lazily evaluated at the site of the invocation, - // b) that either all the parameters or none of the parameters of a function have default arguments, - // and c) functions cannot currently be explicitly invoked if they have default arguments - // if we plan to generalize this further, we will need to relax those assumptions if len(compositeType.ConstructorParameters) < 1 { return nil } - // event intefaces do not exist + // event interfaces do not exist compositeDecl := declaration.(*ast.CompositeDeclaration) if compositeDecl.IsResourceDestructionDefaultEvent() { - parameters := compositeDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters - // if the first parameter has a default arg, all of them do, and the arguments list is empty - if parameters[0].DefaultArgument != nil { - // lazily evaluate the default argument expression in this context - for _, parameter := range parameters { - defaultArg := interpreter.evalExpression(parameter.DefaultArgument) - invocation.Arguments = append(invocation.Arguments, defaultArg) - } - } + // we implicitly pass the containing composite value as an argument to this invocation + containerComposite := invocation.Arguments[0].(*CompositeValue) + invocation.Arguments = interpreter.evaluateDefaultDestroyEvent( + containerComposite, + compositeDecl, + compositeType, + locationRange, + ) } for i, argument := range invocation.Arguments { diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 2126240eba..34ed073de6 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16472,19 +16472,15 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio // if the composite was recently loaded from storage v.InitializeFunctions(interpreter) if constructor := v.defaultDestroyEventConstructor(); constructor != nil { - var self MemberAccessibleValue = v - if v.Kind == common.CompositeKindAttachment { - var base *EphemeralReferenceValue - base, self = attachmentBaseAndSelfValues(interpreter, v) - interpreter.declareVariable(sema.BaseIdentifier, base) - } - interpreter.declareVariable(sema.SelfIdentifier, self) + + // pass the container value to the creation of the default event as an implicit argument, so that + // its fields are accessible in the body of the event constructor mockInvocation := NewInvocation( interpreter, nil, nil, nil, - []Value{}, + []Value{v}, []sema.Type{}, nil, locationRange, @@ -16492,6 +16488,8 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio event := constructor.invoke(mockInvocation).(*CompositeValue) eventType := interpreter.MustSemaTypeOfValue(event).(*sema.CompositeType) + + // emit the event once destruction is complete defer interpreter.emitEvent(event, eventType, locationRange) } diff --git a/runtime/resourcedictionary_test.go b/runtime/resourcedictionary_test.go index ab2380c0fc..75a61fb41b 100644 --- a/runtime/resourcedictionary_test.go +++ b/runtime/resourcedictionary_test.go @@ -38,6 +38,8 @@ const resourceDictionaryContract = ` access(all) resource R { + access(all) event ResourceDestroyed(value: Int = self.value) + access(all) var value: Int init(_ value: Int) { @@ -255,6 +257,7 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { `) loggedMessages = nil + events = nil err = runtime.ExecuteTransaction( Script{ @@ -274,8 +277,8 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { }, loggedMessages, ) - - // DestructorTODO: add test for destruction event of R + require.Len(t, events, 1) + require.Equal(t, events[0].String(), "A.000000000000cade.Test.R.ResourceDestroyed(value: 3)") // Remove the key @@ -294,6 +297,7 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { `) loggedMessages = nil + events = nil err = runtime.ExecuteTransaction( Script{ @@ -313,8 +317,8 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { }, loggedMessages, ) - - // DestructorTODO: add test for destruction event of R + require.Len(t, events, 1) + require.Equal(t, events[0].String(), "A.000000000000cade.Test.R.ResourceDestroyed(value: 4)") // Read the deleted key @@ -354,6 +358,7 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { `) loggedMessages = nil + events = nil err = runtime.ExecuteTransaction( Script{ @@ -372,8 +377,9 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { }, loggedMessages, ) + require.Len(t, events, 1) + require.Equal(t, events[0].String(), "A.000000000000cade.Test.R.ResourceDestroyed(value: 1)") - // DestructorTODO: add test for destruction event of R } func TestRuntimeResourceDictionaryValues_Nested(t *testing.T) { @@ -972,7 +978,15 @@ func TestRuntimeResourceDictionaryValues_Destruction(t *testing.T) { ) require.NoError(t, err) - // DestructorTODO: replace with test for destruction event of R twice + require.Len(t, events, 3) + require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[1].EventType.ID(), "A.0000000000000001.Test.R.ResourceDestroyed") + require.Equal(t, events[2].EventType.ID(), "A.0000000000000001.Test.R.ResourceDestroyed") + // one of the two needs to be 1, the other needs to be 2 + require.True(t, + (events[1].Fields[0].String() == "1" && events[2].Fields[0].String() == "2") || + (events[2].Fields[0].String() == "1" && events[1].Fields[0].String() == "2"), + ) } func TestRuntimeResourceDictionaryValues_Insertion(t *testing.T) { diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index cac4b463d7..af64dd54c8 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -3129,11 +3129,22 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { contract := []byte(` access(all) contract Test { - access(all) resource R {} + access(all) resource R { + access(self) var foo: Int + access(all) event ResourceDestroyed(foo: Int = self.foo) + init() { + self.foo = 0 + } + access(all) fun setFoo(_ arg: Int) { + self.foo = arg + } + } init() { // store nested resource in account on deployment - self.account.storage.save(<-create R(), to: /storage/r) + let r <-create R() + r.setFoo(3) + self.account.storage.save(<-r, to: /storage/r) } } `) @@ -3145,6 +3156,7 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { prepare(acct: auth(Storage) &Account) { let r <- acct.storage.load<@Test.R>(from: /storage/r) + r?.setFoo(6) destroy r } } @@ -3153,6 +3165,7 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { deploy := DeploymentTransaction("Test", contract) var accountCode []byte + var events []cadence.Event runtimeInterface := &TestRuntimeInterface{ OnGetCode: func(_ Location) (bytes []byte, err error) { @@ -3170,7 +3183,10 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { accountCode = code return nil }, - OnEmitEvent: func(event cadence.Event) error { return nil }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, } nextTransactionLocation := NewTransactionLocationGenerator() @@ -3198,8 +3214,139 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { }) require.NoError(t, err) - // DestructorTODO: Assert default event is emitted here + require.Len(t, events, 2) + require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[1].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") +} + +func TestRuntimeStorageLoadedDestructionConcreteTypeWithAttachment(t *testing.T) { + + t.Parallel() + runtime := NewTestInterpreterRuntimeWithAttachments() + + addressValue := Address{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + } + + attachmentContract := []byte(` + import Test from 0x01 + + access(all) contract TestAttach { + access(all) attachment A for Test.R { + access(all) event ResourceDestroyed(foo: Int = base.foo) + } + } + `) + + contract := []byte(` + access(all) contract Test { + + access(all) resource R { + access(all) var foo: Int + access(all) event ResourceDestroyed(foo: Int = self.foo) + init() { + self.foo = 0 + } + access(all) fun setFoo(_ arg: Int) { + self.foo = arg + } + } + + init() { + // store nested resource in account on deployment + let r <-create R() + r.setFoo(3) + self.account.storage.save(<-r, to: /storage/r) + } + } + `) + + tx := []byte(` + import Test from 0x01 + import TestAttach from 0x01 + + transaction { + + prepare(acct: auth(Storage) &Account) { + let r <- acct.storage.load<@Test.R>(from: /storage/r)! + let withAttachment <- attach TestAttach.A() to <-r + withAttachment.setFoo(6) + destroy withAttachment + } + } + `) + + deploy := DeploymentTransaction("Test", contract) + deployAttachment := DeploymentTransaction("TestAttach", attachmentContract) + + accountCodes := map[Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + OnGetCode: func(location Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{addressValue}, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnCreateAccount: func(payer Address) (address Address, err error) { + return addressValue, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: deploy, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + err = runtime.ExecuteTransaction( + Script{ + Source: deployAttachment, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + err = runtime.ExecuteTransaction( + Script{ + Source: tx, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }) + require.NoError(t, err) + + require.Len(t, events, 4) + require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[1].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[2].String(), "A.0000000000000001.TestAttach.A.ResourceDestroyed(foo: 6)") + require.Equal(t, events[3].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") } func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { @@ -3214,7 +3361,9 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { contract := []byte(` access(all) contract Test { - access(all) resource R {} + access(all) resource R { + access(all) event ResourceDestroyed() + } init() { // store nested resource in account on deployment @@ -3239,6 +3388,7 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { deploy := DeploymentTransaction("Test", contract) var accountCode []byte + var events []cadence.Event runtimeInterface := &TestRuntimeInterface{ OnGetCode: func(_ Location) (bytes []byte, err error) { @@ -3256,7 +3406,10 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { accountCode = code return nil }, - OnEmitEvent: func(event cadence.Event) error { return nil }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, } nextTransactionLocation := NewTransactionLocationGenerator() @@ -3285,7 +3438,9 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { ) require.NoError(t, err) - // DestructorTODO: Assert default event is emitted here + require.Len(t, events, 2) + require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[1].String(), "A.0000000000000001.Test.R.ResourceDestroyed()") } func TestRuntimeStorageLoadedDestructionAfterRemoval(t *testing.T) { From 24e61641efcfedb7442d57399fb631dc6caa21f0 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 26 Sep 2023 16:55:22 -0400 Subject: [PATCH 20/48] lint --- runtime/interpreter/interpreter_invocation.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/runtime/interpreter/interpreter_invocation.go b/runtime/interpreter/interpreter_invocation.go index 2019df8a52..8f4fb6dbed 100644 --- a/runtime/interpreter/interpreter_invocation.go +++ b/runtime/interpreter/interpreter_invocation.go @@ -185,14 +185,12 @@ func (interpreter *Interpreter) invokeInterpretedFunctionActivated( ) } -// bindParameterArguments binds the argument values to the given parameters. +// bindParameterArguments binds the argument values to the given parameters func (interpreter *Interpreter) bindParameterArguments( parameterList *ast.ParameterList, arguments []Value, ) { - parameters := parameterList.Parameters - - for parameterIndex, parameter := range parameters { + for parameterIndex, parameter := range parameterList.Parameters { argument := arguments[parameterIndex] interpreter.declareVariable(parameter.Identifier.Identifier, argument) } From 8d2169f23887d3ef67340491bb91d46e64633538 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 26 Sep 2023 16:56:06 -0400 Subject: [PATCH 21/48] fix lint --- runtime/sema/check_composite_declaration.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index a3346d45e4..b37fbb659f 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2053,7 +2053,7 @@ func (checker *Checker) checkDefaultDestroyEvent( break } checker.report(&DefaultDestroyInvalidArgumentError{ - ast.NewRangeFromPositioned(checker.memoryGauge, arg), + Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), }) case *ast.MemberExpression: checkParamExpressionKind(arg.Expression) @@ -2067,13 +2067,13 @@ func (checker *Checker) checkDefaultDestroyEvent( switch targetExprType.(type) { case *VariableSizedType, *ConstantSizedType: checker.report(&DefaultDestroyInvalidArgumentError{ - ast.NewRangeFromPositioned(checker.memoryGauge, arg), + Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), }) } default: checker.report(&DefaultDestroyInvalidArgumentError{ - ast.NewRangeFromPositioned(checker.memoryGauge, arg), + Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), }) } } From 0d9bf8ccb653a2fc6b0e5dda592b9e2ff0376d73 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 27 Sep 2023 12:23:01 -0400 Subject: [PATCH 22/48] emit interface events as well --- runtime/interpreter/interpreter.go | 39 +++- runtime/interpreter/value.go | 23 ++- runtime/runtime_test.go | 134 ++++++++++++ runtime/tests/interpreter/resources_test.go | 214 ++++++++++++++++++++ 4 files changed, 394 insertions(+), 16 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index f33a7f2518..18b524cfd5 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -178,9 +178,10 @@ type FunctionWrapper = func(inner FunctionValue) FunctionValue // These are "branch" nodes in the call chain, and are function wrappers, // i.e. they wrap the functions / function wrappers that inherit them. type WrapperCode struct { - InitializerFunctionWrapper FunctionWrapper - FunctionWrappers map[string]FunctionWrapper - Functions map[string]FunctionValue + InitializerFunctionWrapper FunctionWrapper + FunctionWrappers map[string]FunctionWrapper + Functions map[string]FunctionValue + DefaultDestroyEventConstructor FunctionValue } // TypeCodes is the value which stores the "prepared" / "callable" "code" @@ -1181,10 +1182,10 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( functions := interpreter.compositeFunctions(declaration, lexicalScope) if destroyEventConstructor != nil { - functions[resourceDefaultDestroyEventName] = destroyEventConstructor + functions[resourceDefaultDestroyEventName(compositeType)] = destroyEventConstructor } - wrapFunctions := func(code WrapperCode) { + wrapFunctions := func(ty *sema.InterfaceType, code WrapperCode) { // Wrap initializer @@ -1220,12 +1221,16 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( for name, functionWrapper := range code.FunctionWrappers { //nolint:maprange functions[name] = functionWrapper(functions[name]) } + + if code.DefaultDestroyEventConstructor != nil { + functions[resourceDefaultDestroyEventName(ty)] = code.DefaultDestroyEventConstructor + } } conformances := compositeType.EffectiveInterfaceConformances() for i := len(conformances) - 1; i >= 0; i-- { conformance := conformances[i].InterfaceType - wrapFunctions(interpreter.SharedState.typeCodes.InterfaceCodes[conformance.ID()]) + wrapFunctions(conformance, interpreter.SharedState.typeCodes.InterfaceCodes[conformance.ID()]) } interpreter.SharedState.typeCodes.CompositeCodes[compositeType.ID()] = CompositeTypeCode{ @@ -2228,13 +2233,29 @@ func (interpreter *Interpreter) declareInterface( interfaceType.InitializerParameters, lexicalScope, ) + + var defaultDestroyEventConstructor FunctionValue + for _, nestedCompositeDeclaration := range declaration.Members.Composites() { + // statically we know there is at most one of these + if nestedCompositeDeclaration.IsResourceDestructionDefaultEvent() { + var nestedVariable *Variable + lexicalScope, nestedVariable = interpreter.declareCompositeValue( + nestedCompositeDeclaration, + lexicalScope, + ) + defaultDestroyEventConstructor = nestedVariable.GetValue().(FunctionValue) + break + } + } + functionWrappers := interpreter.functionWrappers(declaration.Members, lexicalScope) defaultFunctions := interpreter.defaultFunctions(declaration.Members, lexicalScope) interpreter.SharedState.typeCodes.InterfaceCodes[typeID] = WrapperCode{ - InitializerFunctionWrapper: initializerFunctionWrapper, - FunctionWrappers: functionWrappers, - Functions: defaultFunctions, + InitializerFunctionWrapper: initializerFunctionWrapper, + FunctionWrappers: functionWrappers, + Functions: defaultFunctions, + DefaultDestroyEventConstructor: defaultDestroyEventConstructor, } } diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 34ed073de6..f053cfa4ab 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16232,7 +16232,7 @@ type CompositeField struct { } const unrepresentableNamePrefix = "$" -const resourceDefaultDestroyEventName = unrepresentableNamePrefix + "ResourceDestroyed" +const resourceDefaultDestroyEventPrefix = "ResourceDestroyed" + unrepresentableNamePrefix var _ TypeIndexableValue = &CompositeValue{} @@ -16428,8 +16428,17 @@ func (v *CompositeValue) IsDestroyed() bool { return v.isDestroyed } -func (v *CompositeValue) defaultDestroyEventConstructor() FunctionValue { - return v.Functions[resourceDefaultDestroyEventName] +func resourceDefaultDestroyEventName(t sema.ContainerType) string { + return resourceDefaultDestroyEventPrefix + string(t.ID()) +} + +func (v *CompositeValue) defaultDestroyEventConstructors() (constructors []FunctionValue) { + for name, f := range v.Functions { + if strings.HasPrefix(name, resourceDefaultDestroyEventPrefix) { + constructors = append(constructors, f) + } + } + return } func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange LocationRange) { @@ -16463,15 +16472,15 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio } // before actually performing the destruction (i.e. so that any fields are still available), - // compute the default arguments of the default destruction event (if it exists). However, - // wait until after the destruction completes to actually emit the event, so that the correct order + // compute the default arguments of the default destruction events (if any exist). However, + // wait until after the destruction completes to actually emit the events, so that the correct order // is preserved and nested resource destroy events happen first - // the default destroy event constructor is encoded as a function on the resource (with an unrepresentable name) + // default destroy event constructors are encoded as functions on the resource (with an unrepresentable name) // so that we can leverage existing atree encoding and decoding. However, we need to make sure functions are initialized // if the composite was recently loaded from storage v.InitializeFunctions(interpreter) - if constructor := v.defaultDestroyEventConstructor(); constructor != nil { + for _, constructor := range v.defaultDestroyEventConstructors() { // pass the container value to the creation of the default event as an implicit argument, so that // its fields are accessible in the body of the event constructor diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index af64dd54c8..e015f6544b 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -3349,6 +3349,140 @@ func TestRuntimeStorageLoadedDestructionConcreteTypeWithAttachment(t *testing.T) require.Equal(t, events[3].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") } +func TestRuntimeStorageLoadedDestructionConcreteTypeWithAttachmentUnloadedContract(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntimeWithAttachments() + + addressValue := Address{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + } + + attachmentContract := []byte(` + access(all) contract TestAttach { + access(all) resource interface I { + access(all) var foo: Int + access(all) event ResourceDestroyed(foo: Int = self.foo) + } + + access(all) attachment A for I { + access(all) event ResourceDestroyed(foo: Int = base.foo) + } + } + `) + + contract := []byte(` + import TestAttach from 0x01 + + access(all) contract Test { + + access(all) resource R: TestAttach.I { + access(all) var foo: Int + access(all) event ResourceDestroyed(foo: Int = self.foo) + init() { + self.foo = 0 + } + access(all) fun setFoo(_ arg: Int) { + self.foo = arg + } + } + + init() { + // store nested resource in account on deployment + let r <- attach TestAttach.A() to <-create R() + r.setFoo(3) + self.account.storage.save(<-r, to: /storage/r) + } + } + `) + + tx := []byte(` + import Test from 0x01 + + transaction { + + prepare(acct: auth(Storage) &Account) { + let r <- acct.storage.load<@Test.R>(from: /storage/r)! + r.setFoo(6) + destroy r + } + } + `) + + deploy := DeploymentTransaction("Test", contract) + deployAttachment := DeploymentTransaction("TestAttach", attachmentContract) + + accountCodes := map[Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + OnGetCode: func(location Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{addressValue}, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnCreateAccount: func(payer Address) (address Address, err error) { + return addressValue, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: deployAttachment, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + err = runtime.ExecuteTransaction( + Script{ + Source: deploy, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + err = runtime.ExecuteTransaction( + Script{ + Source: tx, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }) + require.NoError(t, err) + + require.Len(t, events, 5) + require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[1].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[2].String(), "A.0000000000000001.TestAttach.A.ResourceDestroyed(foo: 6)") + require.Equal(t, events[3].String(), "A.0000000000000001.TestAttach.I.ResourceDestroyed(foo: 6)") + require.Equal(t, events[4].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") +} + func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { t.Parallel() diff --git a/runtime/tests/interpreter/resources_test.go b/runtime/tests/interpreter/resources_test.go index 042f1c4f80..4c8f749a2a 100644 --- a/runtime/tests/interpreter/resources_test.go +++ b/runtime/tests/interpreter/resources_test.go @@ -2245,3 +2245,217 @@ func TestInterpretImplicitDestruction(t *testing.T) { require.NoError(t, err) }) } + +func TestInterpretResourceInterfaceDefaultDestroyEvent(t *testing.T) { + + t.Parallel() + + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource interface I { + access(all) let id: Int + event ResourceDestroyed(id: Int = self.id) + } + + resource A: I { + access(all) let id: Int + + init(id: Int) { + self.id = id + } + + event ResourceDestroyed(id: Int = self.id) + } + + resource B: I { + access(all) let id: Int + + init(id: Int) { + self.id = id + } + + event ResourceDestroyed(id: Int = self.id) + } + + fun test() { + let a <- create A(id: 1) + let b <- create B(id: 2) + let is: @[AnyResource] <- [<-a, <-b] + destroy is + } + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + + require.NoError(t, err) + _, err = inter.Invoke("test") + require.NoError(t, err) + + require.Len(t, events, 4) + require.Equal(t, events[0].QualifiedIdentifier, "I.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[1].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[2].QualifiedIdentifier, "I.ResourceDestroyed") + require.Equal(t, events[2].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 2)) + require.Equal(t, events[3].QualifiedIdentifier, "B.ResourceDestroyed") + require.Equal(t, events[3].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 2)) +} + +func TestInterpretResourceInterfaceDefaultDestroyEventMultipleInheritance(t *testing.T) { + + t.Parallel() + + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource interface I { + access(all) let id: Int + event ResourceDestroyed(id: Int = self.id) + } + + resource interface J { + access(all) let id: Int + event ResourceDestroyed(id: Int = self.id) + } + + resource A: I, J { + access(all) let id: Int + + init(id: Int) { + self.id = id + } + + event ResourceDestroyed(id: Int = self.id) + } + + fun test() { + let a <- create A(id: 1) + destroy a + } + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + + require.NoError(t, err) + _, err = inter.Invoke("test") + require.NoError(t, err) + + require.Len(t, events, 3) + require.Equal(t, events[0].QualifiedIdentifier, "I.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[1].QualifiedIdentifier, "J.ResourceDestroyed") + require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, events[2].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) +} + +func TestInterpretResourceInterfaceDefaultDestroyEventIndirectInheritance(t *testing.T) { + + t.Parallel() + + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource interface I { + access(all) let id: Int + event ResourceDestroyed(id: Int = self.id) + } + + resource interface J: I { + access(all) let id: Int + event ResourceDestroyed(id: Int = self.id) + } + + resource A: J { + access(all) let id: Int + + init(id: Int) { + self.id = id + } + + event ResourceDestroyed(id: Int = self.id) + } + + fun test() { + let a <- create A(id: 1) + destroy a + } + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + + require.NoError(t, err) + _, err = inter.Invoke("test") + require.NoError(t, err) + + require.Len(t, events, 3) + require.Equal(t, events[0].QualifiedIdentifier, "J.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[1].QualifiedIdentifier, "I.ResourceDestroyed") + require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, events[2].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) +} + +func TestInterpretResourceInterfaceDefaultDestroyEventNoCompositeEvent(t *testing.T) { + + t.Parallel() + + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + resource interface I { + access(all) let id: Int + event ResourceDestroyed(id: Int = self.id) + } + + resource interface J: I { + access(all) let id: Int + } + + resource A: J { + access(all) let id: Int + + init(id: Int) { + self.id = id + } + } + + fun test() { + let a <- create A(id: 1) + destroy a + } + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + }) + + require.NoError(t, err) + _, err = inter.Invoke("test") + require.NoError(t, err) + + require.Len(t, events, 1) + require.Equal(t, events[0].QualifiedIdentifier, "I.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) +} From 339767bb9a498a79bdbb89f309580c8af4c57a51 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 27 Sep 2023 13:05:30 -0400 Subject: [PATCH 23/48] Update runtime/ast/composite.go Co-authored-by: Supun Setunga --- runtime/ast/composite.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/ast/composite.go b/runtime/ast/composite.go index 3402e34c2a..05c35dcef4 100644 --- a/runtime/ast/composite.go +++ b/runtime/ast/composite.go @@ -283,7 +283,8 @@ func (d *CompositeDeclaration) ConformanceList() []*NominalType { } func (d *CompositeDeclaration) IsResourceDestructionDefaultEvent() bool { - return d.CompositeKind == common.CompositeKindEvent && d.Identifier.Identifier == ResourceDestructionDefaultEventName + return d.CompositeKind == common.CompositeKindEvent && + d.Identifier.Identifier == ResourceDestructionDefaultEventName } // FieldDeclarationFlags From 1e83b482b5a33a28df6009b77d1dec313ecc9450 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 27 Sep 2023 16:12:16 -0400 Subject: [PATCH 24/48] add test for same name interfaces --- runtime/runtime_test.go | 153 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index e015f6544b..9bf6840024 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -3483,6 +3483,159 @@ func TestRuntimeStorageLoadedDestructionConcreteTypeWithAttachmentUnloadedContra require.Equal(t, events[4].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") } +func TestRuntimeStorageLoadedDestructionConcreteTypeSameNamedInterface(t *testing.T) { + + t.Parallel() + + runtime := NewTestInterpreterRuntimeWithAttachments() + + addressValue := Address{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, + } + + interfaceContract1 := []byte(` + access(all) contract TestInterface1 { + access(all) resource interface I { + access(all) var foo: Int + access(all) event ResourceDestroyed(foo: Int = self.foo) + } + } + `) + + interfaceContract2 := []byte(` + access(all) contract TestInterface2 { + access(all) resource interface I { + access(all) var foo: Int + access(all) event ResourceDestroyed(foo: Int = self.foo) + } + } + `) + + contract := []byte(` + import TestInterface1 from 0x01 + import TestInterface2 from 0x01 + + access(all) contract Test { + + access(all) resource R: TestInterface1.I, TestInterface2.I { + access(all) var foo: Int + access(all) event ResourceDestroyed(foo: Int = self.foo) + init() { + self.foo = 0 + } + access(all) fun setFoo(_ arg: Int) { + self.foo = arg + } + } + + init() { + // store nested resource in account on deployment + let r <-create R() + r.setFoo(3) + self.account.storage.save(<-r, to: /storage/r) + } + } + `) + + tx := []byte(` + import Test from 0x01 + + transaction { + + prepare(acct: auth(Storage) &Account) { + let r <- acct.storage.load<@Test.R>(from: /storage/r)! + r.setFoo(6) + destroy r + } + } + `) + + deploy := DeploymentTransaction("Test", contract) + deployInterface1 := DeploymentTransaction("TestInterface1", interfaceContract1) + deployInterface2 := DeploymentTransaction("TestInterface2", interfaceContract2) + + accountCodes := map[Location][]byte{} + var events []cadence.Event + + runtimeInterface := &TestRuntimeInterface{ + OnGetCode: func(location Location) (bytes []byte, err error) { + return accountCodes[location], nil + }, + Storage: NewTestLedger(nil, nil), + OnGetSigningAccounts: func() ([]Address, error) { + return []Address{addressValue}, nil + }, + OnResolveLocation: NewSingleIdentifierLocationResolver(t), + OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) { + return accountCodes[location], nil + }, + OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error { + accountCodes[location] = code + return nil + }, + OnCreateAccount: func(payer Address) (address Address, err error) { + return addressValue, nil + }, + OnEmitEvent: func(event cadence.Event) error { + events = append(events, event) + return nil + }, + } + + nextTransactionLocation := NewTransactionLocationGenerator() + + err := runtime.ExecuteTransaction( + Script{ + Source: deployInterface1, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + err = runtime.ExecuteTransaction( + Script{ + Source: deployInterface2, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + err = runtime.ExecuteTransaction( + Script{ + Source: deploy, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }, + ) + require.NoError(t, err) + + err = runtime.ExecuteTransaction( + Script{ + Source: tx, + }, + Context{ + Interface: runtimeInterface, + Location: nextTransactionLocation(), + }) + require.NoError(t, err) + + require.Len(t, events, 6) + require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[1].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[2].EventType.ID(), "flow.AccountContractAdded") + require.Equal(t, events[3].String(), "A.0000000000000001.TestInterface1.I.ResourceDestroyed(foo: 6)") + require.Equal(t, events[4].String(), "A.0000000000000001.TestInterface2.I.ResourceDestroyed(foo: 6)") + require.Equal(t, events[5].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") +} + func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { t.Parallel() From 9f235895bc3ad5bde67169e4f22fa7080d4eaaaa Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 27 Sep 2023 16:15:51 -0400 Subject: [PATCH 25/48] fix test --- runtime/parser/declaration_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index efa816d963..ad4f936203 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -2577,18 +2577,18 @@ func TestParseEvent(t *testing.T) { TypeAnnotation: &ast.TypeAnnotation{ Type: &ast.NominalType{ Identifier: ast.Identifier{ - Identifier: "Int", + Identifier: "String", Pos: ast.Position{ - Offset: 29, + Offset: 43, Line: 1, - Column: 29, + Column: 43, }, }, }, StartPos: ast.Position{ - Offset: 29, + Offset: 43, Line: 1, - Column: 29, + Column: 43, }, }, DefaultArgument: &ast.StringExpression{ From 15543eb9ab6665c8bcb9dd08d3d88bcb29c00897 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 28 Sep 2023 10:41:45 -0400 Subject: [PATCH 26/48] respond to review --- runtime/ast/parameter.go | 33 ++++++++++++++++++++++++++++++ runtime/ast/parameterlist.go | 19 +---------------- runtime/parser/declaration_test.go | 33 ++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/runtime/ast/parameter.go b/runtime/ast/parameter.go index 5af4e9991b..151e973e13 100644 --- a/runtime/ast/parameter.go +++ b/runtime/ast/parameter.go @@ -22,6 +22,7 @@ import ( "encoding/json" "github.com/onflow/cadence/runtime/common" + "github.com/turbolent/prettier" ) type Parameter struct { @@ -88,3 +89,35 @@ func (p *Parameter) MarshalJSON() ([]byte, error) { Alias: (*Alias)(p), }) } + +const parameterDefaultArgumentSeparator = "=" + +func (p *Parameter) Doc() prettier.Doc { + var parameterDoc prettier.Concat + + if p.Label != "" { + parameterDoc = append( + parameterDoc, + prettier.Text(p.Label), + prettier.Space, + ) + } + + parameterDoc = append( + parameterDoc, + prettier.Text(p.Identifier.Identifier), + typeSeparatorSpaceDoc, + p.TypeAnnotation.Doc(), + ) + + if p.DefaultArgument != nil { + parameterDoc = append(parameterDoc, + prettier.Space, + prettier.Text(parameterDefaultArgumentSeparator), + prettier.Space, + p.DefaultArgument.Doc(), + ) + } + + return parameterDoc +} diff --git a/runtime/ast/parameterlist.go b/runtime/ast/parameterlist.go index 39bb31102f..02df007230 100644 --- a/runtime/ast/parameterlist.go +++ b/runtime/ast/parameterlist.go @@ -92,24 +92,7 @@ func (l *ParameterList) Doc() prettier.Doc { parameterDocs := make([]prettier.Doc, 0, len(l.Parameters)) for _, parameter := range l.Parameters { - var parameterDoc prettier.Concat - - if parameter.Label != "" { - parameterDoc = append( - parameterDoc, - prettier.Text(parameter.Label), - prettier.Space, - ) - } - - parameterDoc = append( - parameterDoc, - prettier.Text(parameter.Identifier.Identifier), - typeSeparatorSpaceDoc, - parameter.TypeAnnotation.Doc(), - ) - - parameterDocs = append(parameterDocs, parameterDoc) + parameterDocs = append(parameterDocs, parameter.Doc()) } return prettier.WrapParentheses( diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index ad4f936203..bdb3cd3794 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -7210,6 +7210,39 @@ func TestParseInvalidImportWithPurity(t *testing.T) { ) } +func TestParseInvalidDefaultArgument(t *testing.T) { + + t.Parallel() + + t.Run("function declaration ", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseDeclarations(" access(all) fun foo ( a : Int = 3) { } ") + + utils.AssertEqualWithDiff(t, []error{ + &SyntaxError{ + Pos: ast.Position{Line: 1, Column: 31, Offset: 31}, + Message: "cannot use a default argument for this function", + }, + }, errs) + }) + + t.Run("function expression ", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseDeclarations(" let foo = fun ( a : Int = 3) { } ") + + utils.AssertEqualWithDiff(t, []error{ + &SyntaxError{ + Pos: ast.Position{Line: 1, Column: 25, Offset: 25}, + Message: "cannot use a default argument for this function", + }, + }, errs) + }) +} + func TestParseInvalidEventWithPurity(t *testing.T) { t.Parallel() From 936f2ba8910b4a3221c67f88cac5fdd88cb4206c Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 28 Sep 2023 11:07:02 -0400 Subject: [PATCH 27/48] correct scoping --- runtime/sema/check_composite_declaration.go | 5 ++++- runtime/tests/checker/events_test.go | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index b37fbb659f..b3b0216395 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -2065,7 +2065,7 @@ func (checker *Checker) checkDefaultDestroyEvent( // indexing expressions on dicts, or composites (for attachments) will return `nil` and thus never fail targetExprType := checker.Elaboration.IndexExpressionTypes(arg).IndexedType switch targetExprType.(type) { - case *VariableSizedType, *ConstantSizedType: + case ArrayType: checker.report(&DefaultDestroyInvalidArgumentError{ Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), }) @@ -2078,6 +2078,9 @@ func (checker *Checker) checkDefaultDestroyEvent( } } + checker.enterValueScope() + defer checker.leaveValueScope(eventDeclaration.EndPosition, true) + for index, param := range eventType.ConstructorParameters { paramType := param.TypeAnnotation.Type paramExpr := constructorFunctionParameters[index] diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index ae6584561f..4ec165504e 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -1041,5 +1041,4 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { `) require.NoError(t, err) }) - } From c034b5aaeb452d7a5dc744f28171652ceedc2369 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 26 Oct 2023 11:21:35 -0400 Subject: [PATCH 28/48] respond to review --- runtime/interpreter/interpreter_expression.go | 2 ++ runtime/interpreter/value.go | 10 +------ runtime/parser/declaration.go | 2 +- runtime/parser/declaration_test.go | 5 ++-- runtime/parser/errors.go | 29 +++++++++++++++++++ 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/runtime/interpreter/interpreter_expression.go b/runtime/interpreter/interpreter_expression.go index 83010a28ef..171695b532 100644 --- a/runtime/interpreter/interpreter_expression.go +++ b/runtime/interpreter/interpreter_expression.go @@ -1234,6 +1234,8 @@ func (interpreter *Interpreter) VisitCreateExpression(expression *ast.CreateExpr func (interpreter *Interpreter) VisitDestroyExpression(expression *ast.DestroyExpression) Value { value := interpreter.evalExpression(expression.Expression) + interpreter.invalidateResource(value) + locationRange := LocationRange{ Location: interpreter.Location, HasPosition: expression, diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index f0ef94430d..0a6c58ffd4 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -1880,8 +1880,6 @@ func (v *ArrayValue) Destroy(interpreter *Interpreter, locationRange LocationRan config := interpreter.SharedState.Config - interpreter.invalidateResource(v) - if config.InvalidatedResourceValidationEnabled { v.checkInvalidatedResourceUse(interpreter, locationRange) } @@ -16548,8 +16546,6 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio interpreter.ReportComputation(common.ComputationKindDestroyCompositeValue, 1) - interpreter.invalidateResource(v) - config := interpreter.SharedState.Config if config.InvalidatedResourceValidationEnabled { @@ -18229,8 +18225,6 @@ func (v *DictionaryValue) Destroy(interpreter *Interpreter, locationRange Locati config := interpreter.SharedState.Config - interpreter.invalidateResource(v) - if config.InvalidatedResourceValidationEnabled { v.checkInvalidatedResourceUse(interpreter, locationRange) } @@ -19314,7 +19308,7 @@ func (NilValue) IsDestroyed() bool { } func (v NilValue) Destroy(interpreter *Interpreter, _ LocationRange) { - interpreter.invalidateResource(v) + } func (NilValue) String() string { @@ -19498,8 +19492,6 @@ func (v *SomeValue) IsDestroyed() bool { func (v *SomeValue) Destroy(interpreter *Interpreter, locationRange LocationRange) { config := interpreter.SharedState.Config - interpreter.invalidateResource(v) - if config.InvalidatedResourceValidationEnabled { v.checkInvalidatedResourceUse(locationRange) } diff --git a/runtime/parser/declaration.go b/runtime/parser/declaration.go index e36e442375..07cd19e31e 100644 --- a/runtime/parser/declaration.go +++ b/runtime/parser/declaration.go @@ -1884,7 +1884,7 @@ func parseSpecialFunctionDeclaration( declarationKind = common.DeclarationKindInitializer case KeywordDestroy: - p.report(NewSyntaxError(identifier.Pos, "custom destructor definitions are no longer permitted")) + p.report(&CustomDestructorError{Pos: identifier.Pos}) case KeywordPrepare: declarationKind = common.DeclarationKindPrepare diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index f0712ff7a4..d694dde576 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -7597,9 +7597,8 @@ func TestParseDestructor(t *testing.T) { _, errs := testParseDeclarations(code) utils.AssertEqualWithDiff(t, []error{ - &SyntaxError{ - Message: "custom destructor definitions are no longer permitted", - Pos: ast.Position{Offset: 37, Line: 3, Column: 12}, + &CustomDestructorError{ + Pos: ast.Position{Offset: 37, Line: 3, Column: 12}, }, }, errs, diff --git a/runtime/parser/errors.go b/runtime/parser/errors.go index a6e5944005..977b6369c8 100644 --- a/runtime/parser/errors.go +++ b/runtime/parser/errors.go @@ -289,3 +289,32 @@ func (e *MissingCommaInParameterListError) EndPosition(_ common.MemoryGauge) ast func (e *MissingCommaInParameterListError) Error() string { return "missing comma after parameter" } + +// CustomDestructorError + +type CustomDestructorError struct { + Pos ast.Position +} + +var _ ParseError = &CustomDestructorError{} +var _ errors.UserError = &CustomDestructorError{} + +func (*CustomDestructorError) isParseError() {} + +func (*CustomDestructorError) IsUserError() {} + +func (e *CustomDestructorError) StartPosition() ast.Position { + return e.Pos +} + +func (e *CustomDestructorError) EndPosition(_ common.MemoryGauge) ast.Position { + return e.Pos +} + +func (e *CustomDestructorError) Error() string { + return "custom destructor definitions are no longer permitted" +} + +func (e *CustomDestructorError) SecondaryError() string { + return "remove the destructor definition" +} From 8fb0ad2a4785560b4013e1f2f6fafa90772912f5 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 26 Oct 2023 11:31:42 -0400 Subject: [PATCH 29/48] respond to review --- runtime/tests/checker/events_test.go | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 4ec165504e..fba6aaecba 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -782,6 +782,23 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) }) + t.Run("self", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: @R = self) + } + `) + errs := RequireCheckerErrors(t, err, 4) + + assert.IsType(t, &sema.DefaultDestroyInvalidParameterError{}, errs[0]) + assert.IsType(t, &sema.ResourceLossError{}, errs[1]) + assert.IsType(t, &sema.InvalidEventParameterTypeError{}, errs[2]) + assert.IsType(t, &sema.InvalidResourceFieldError{}, errs[3]) + }) + t.Run("array field", func(t *testing.T) { t.Parallel() @@ -1041,4 +1058,30 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { `) require.NoError(t, err) }) + + t.Run("field name conflict", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + attachment A for R { + event ResourceDestroyed(name: Int = self.self, x: String = base.base) + let self: Int + let base: String + init() { + self.base = "foo" + self.self = 3 + } + } + + resource R { + let base: String + init() { + self.base = "foo" + } + event ResourceDestroyed(name: String? = self[A]?.base, x: Int? = self[A]?.self) + } + `) + require.NoError(t, err) + }) } From 48fb4b21d031a58b58f5c12b7e812afba1b3b5b0 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 27 Oct 2023 10:24:18 -0400 Subject: [PATCH 30/48] prevent manually emitting default destroy events --- runtime/sema/check_composite_declaration.go | 1 - runtime/sema/check_emit_statement.go | 9 ++++++ runtime/sema/errors.go | 17 ++++++++++ runtime/tests/checker/events_test.go | 35 +++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 8cff51b91b..bcdbe52b5d 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -849,7 +849,6 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( checker.typeActivations.Find(identifier.Identifier) defaultEventComposite := defaultEventType.Type.(*CompositeType) compositeType.DefaultDestroyEvent = defaultEventComposite - return } declarationMembers.Set( diff --git a/runtime/sema/check_emit_statement.go b/runtime/sema/check_emit_statement.go index e93126b21c..572c0f6df5 100644 --- a/runtime/sema/check_emit_statement.go +++ b/runtime/sema/check_emit_statement.go @@ -45,6 +45,15 @@ func (checker *Checker) VisitEmitStatement(statement *ast.EmitStatement) (_ stru return } + if compositeType.Identifier == ast.ResourceDestructionDefaultEventName { + checker.report( + &EmitDefaultDestroyEventError{ + Range: ast.NewRangeFromPositioned(checker.memoryGauge, statement.InvocationExpression), + }, + ) + return + } + checker.Elaboration.SetEmitStatementEventType(statement, compositeType) // Check that the emitted event is declared in the same location diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index b07bdd86b1..78dff438f1 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -2490,6 +2490,23 @@ func (e *EmitNonEventError) Error() string { ) } +// EmitDefaultDestroyEventError + +type EmitDefaultDestroyEventError struct { + ast.Range +} + +var _ SemanticError = &EmitDefaultDestroyEventError{} +var _ errors.UserError = &EmitDefaultDestroyEventError{} + +func (*EmitDefaultDestroyEventError) isSemanticError() {} + +func (*EmitDefaultDestroyEventError) IsUserError() {} + +func (e *EmitDefaultDestroyEventError) Error() string { + return "default destruction events may not be explicitly emitted" +} + // EmitImportedEventError type EmitImportedEventError struct { diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index fba6aaecba..20b401b0b2 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -636,6 +636,41 @@ func TestCheckDefaultEventDeclaration(t *testing.T) { assert.IsType(t, &sema.RedeclarationError{}, errs[2]) assert.IsType(t, &sema.RedeclarationError{}, errs[3]) }) + + t.Run("explicit emit disallowed", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed() + fun foo() { + emit ResourceDestroyed() + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.EmitDefaultDestroyEventError{}, errs[0]) + }) + + t.Run("explicit emit disallowed outside", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed() + } + + fun foo() { + emit R.ResourceDestroyed() + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.EmitDefaultDestroyEventError{}, errs[0]) + }) } func TestCheckDefaultEventParamChecking(t *testing.T) { From 8f044b2fdabc57a0a9142d05d261ec3ac9aab5fd Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 27 Oct 2023 10:40:39 -0400 Subject: [PATCH 31/48] respond to review --- runtime/sema/check_composite_declaration.go | 104 +++++++++++--------- runtime/sema/check_interface_declaration.go | 1 - runtime/tests/checker/events_test.go | 36 +++++++ 3 files changed, 94 insertions(+), 47 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index bcdbe52b5d..77ca0b7bf2 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -1995,6 +1995,63 @@ func (checker *Checker) enumMembersAndOrigins( return } +func (checker *Checker) checkDefaultDestroyParamExpressionKind( + arg ast.Expression, +) { + switch arg := arg.(type) { + case *ast.StringExpression, + *ast.BoolExpression, + *ast.NilExpression, + *ast.IntegerExpression, + *ast.FixedPointExpression, + *ast.PathExpression: + break + case *ast.IdentifierExpression: + // these are guaranteed to exist at time of destruction, so we allow them + if arg.Identifier.Identifier == SelfIdentifier || arg.Identifier.Identifier == BaseIdentifier { + break + } + // if it's an attachment, then it's also okay + if checker.typeActivations.Find(arg.Identifier.Identifier) != nil { + break + } + checker.report(&DefaultDestroyInvalidArgumentError{ + Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), + }) + case *ast.MemberExpression: + checker.checkDefaultDestroyParamExpressionKind(arg.Expression) + case *ast.IndexExpression: + checker.checkDefaultDestroyParamExpressionKind(arg.TargetExpression) + checker.checkDefaultDestroyParamExpressionKind(arg.IndexingExpression) + + // indexing expressions on arrays can fail, and must be disallowed, but + // indexing expressions on dicts, or composites (for attachments) will return `nil` and thus never fail + targetExprType := checker.Elaboration.IndexExpressionTypes(arg).IndexedType + // `nil` indicates that the index is a type-index (i.e. for an attachment access) + if targetExprType == nil { + return + } + + switch targetExprType := targetExprType.(type) { + case *DictionaryType: + return + case *ReferenceType: + if _, isDictionaryType := targetExprType.Type.(*DictionaryType); isDictionaryType { + return + } + } + + checker.report(&DefaultDestroyInvalidArgumentError{ + Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), + }) + + default: + checker.report(&DefaultDestroyInvalidArgumentError{ + Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), + }) + } +} + func (checker *Checker) checkDefaultDestroyEvent( eventType *CompositeType, eventDeclaration ast.CompositeLikeDeclaration, @@ -2007,51 +2064,6 @@ func (checker *Checker) checkDefaultDestroyEvent( functions := members.SpecialFunctions() constructorFunctionParameters := functions[0].FunctionDeclaration.ParameterList.Parameters - var checkParamExpressionKind func(ast.Expression) - checkParamExpressionKind = func(arg ast.Expression) { - switch arg := arg.(type) { - case *ast.StringExpression, - *ast.BoolExpression, - *ast.NilExpression, - *ast.IntegerExpression, - *ast.FixedPointExpression, - *ast.PathExpression: - break - case *ast.IdentifierExpression: - // these are guaranteed to exist at time of destruction, so we allow them - if arg.Identifier.Identifier == SelfIdentifier || arg.Identifier.Identifier == BaseIdentifier { - break - } - // if it's an attachment, then it's also okay - if checker.typeActivations.Find(arg.Identifier.Identifier) != nil { - break - } - checker.report(&DefaultDestroyInvalidArgumentError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), - }) - case *ast.MemberExpression: - checkParamExpressionKind(arg.Expression) - case *ast.IndexExpression: - checkParamExpressionKind(arg.TargetExpression) - checkParamExpressionKind(arg.IndexingExpression) - - // indexing expressions on arrays can fail, and must be disallowed, but - // indexing expressions on dicts, or composites (for attachments) will return `nil` and thus never fail - targetExprType := checker.Elaboration.IndexExpressionTypes(arg).IndexedType - switch targetExprType.(type) { - case ArrayType: - checker.report(&DefaultDestroyInvalidArgumentError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), - }) - } - - default: - checker.report(&DefaultDestroyInvalidArgumentError{ - Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), - }) - } - } - checker.enterValueScope() defer checker.leaveValueScope(eventDeclaration.EndPosition, true) @@ -2084,7 +2096,7 @@ func (checker *Checker) checkDefaultDestroyEvent( }) } - checkParamExpressionKind(paramDefaultArgument) + checker.checkDefaultDestroyParamExpressionKind(paramDefaultArgument) } } diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index 508c9a1c00..a5e1d6b678 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -448,7 +448,6 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa checker.typeActivations.Find(nestedCompositeDeclaration.Identifier.Identifier) defaultEventComposite := nestedEvent.Type.(*CompositeType) interfaceType.DefaultDestroyEvent = defaultEventComposite - // interfaceType.DefaultDestroyEvent = } else { checker.declareNestedEvent(nestedCompositeDeclaration, eventMembers, interfaceType) } diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 20b401b0b2..bc5e5d2b60 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -985,6 +985,25 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) }) + t.Run("array reference index expression", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + var arr : &[String] + event ResourceDestroyed(name: String? = self.arr[0]) + + init() { + self.arr = &[] + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + t.Run("dict index expression", func(t *testing.T) { t.Parallel() @@ -1002,6 +1021,23 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { require.NoError(t, err) }) + t.Run("dict reference index expression", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + let dict : &{Int: String} + event ResourceDestroyed(name: String? = self.dict[0]) + + init() { + self.dict = &{} + } + } + `) + require.NoError(t, err) + }) + t.Run("function call dict index expression", func(t *testing.T) { t.Parallel() From 3899fdb1b1cf9d724b1b1e8d8a6fbe4fa76151c0 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Fri, 27 Oct 2023 10:44:11 -0400 Subject: [PATCH 32/48] add tests --- runtime/tests/checker/events_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index bc5e5d2b60..4b3cda4c77 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -761,6 +761,34 @@ func TestCheckDefaultEventParamChecking(t *testing.T) { require.NoError(t, err) }) + t.Run("type", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: Type = Type<@R>()) + } + `) + errs := RequireCheckerErrors(t, err, 2) + assert.IsType(t, &sema.DefaultDestroyInvalidParameterError{}, errs[0]) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[1]) + }) + + t.Run("raw type", func(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: Type = R) + } + `) + errs := RequireCheckerErrors(t, err, 2) + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.DefaultDestroyInvalidParameterError{}, errs[1]) + }) + t.Run("address expr", func(t *testing.T) { t.Parallel() From 18e4c0d5e93d7e494fb2aecd2005c3cfc76af272 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 12:27:34 -0400 Subject: [PATCH 33/48] respond to review --- runtime/ast/composite.go | 6 +- runtime/parser/declaration.go | 2 +- runtime/sema/check_composite_declaration.go | 77 ++++++++++++--------- runtime/sema/check_emit_statement.go | 2 +- 4 files changed, 51 insertions(+), 36 deletions(-) diff --git a/runtime/ast/composite.go b/runtime/ast/composite.go index 05c35dcef4..66681bf42d 100644 --- a/runtime/ast/composite.go +++ b/runtime/ast/composite.go @@ -45,6 +45,10 @@ type CompositeLikeDeclaration interface { const ResourceDestructionDefaultEventName = "ResourceDestroyed" +func IsResourceDestructionDefaultEvent(identifier string) bool { + return identifier == ResourceDestructionDefaultEventName +} + type CompositeDeclaration struct { Members *Members DocString string @@ -284,7 +288,7 @@ func (d *CompositeDeclaration) ConformanceList() []*NominalType { func (d *CompositeDeclaration) IsResourceDestructionDefaultEvent() bool { return d.CompositeKind == common.CompositeKindEvent && - d.Identifier.Identifier == ResourceDestructionDefaultEventName + IsResourceDestructionDefaultEvent(d.Identifier.Identifier) } // FieldDeclarationFlags diff --git a/runtime/parser/declaration.go b/runtime/parser/declaration.go index 9b3b013026..5a3353f397 100644 --- a/runtime/parser/declaration.go +++ b/runtime/parser/declaration.go @@ -876,7 +876,7 @@ func parseEventDeclaration( p.next() // if this is a `ResourceDestroyed` event (i.e., a default event declaration), parse default arguments - parseDefaultArguments := identifier.Identifier == ast.ResourceDestructionDefaultEventName + parseDefaultArguments := ast.IsResourceDestructionDefaultEvent(identifier.Identifier) parameterList, err := parseParameterList(p, parseDefaultArguments) if err != nil { return nil, err diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 77ca0b7bf2..d06cd70059 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -843,7 +843,7 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( nestedCompositeDeclarationVariable := checker.valueActivations.Find(identifier.Identifier) - if identifier.Identifier == ast.ResourceDestructionDefaultEventName { + if ast.IsResourceDestructionDefaultEvent(identifier.Identifier) { // Find the default event's type declaration defaultEventType := checker.typeActivations.Find(identifier.Identifier) @@ -2007,12 +2007,13 @@ func (checker *Checker) checkDefaultDestroyParamExpressionKind( *ast.PathExpression: break case *ast.IdentifierExpression: + identifier := arg.Identifier.Identifier // these are guaranteed to exist at time of destruction, so we allow them - if arg.Identifier.Identifier == SelfIdentifier || arg.Identifier.Identifier == BaseIdentifier { + if identifier == SelfIdentifier || identifier == BaseIdentifier { break } // if it's an attachment, then it's also okay - if checker.typeActivations.Find(arg.Identifier.Identifier) != nil { + if checker.typeActivations.Find(identifier) != nil { break } checker.report(&DefaultDestroyInvalidArgumentError{ @@ -2052,6 +2053,44 @@ func (checker *Checker) checkDefaultDestroyParamExpressionKind( } } +func (checker *Checker) checkDefaultDestroyEventParam( + param Parameter, + index int, + constructorFunctionParameters []*ast.Parameter, + containerType ContainerType, + containerDeclaration ast.Declaration, +) { + paramType := param.TypeAnnotation.Type + paramExpr := constructorFunctionParameters[index] + paramDefaultArgument := paramExpr.DefaultArgument + + // make `self` and `base` available when checking default arguments so the fields of the composite are available + checker.declareSelfValue(containerType, containerDeclaration.DeclarationDocString()) + if compositeContainer, isComposite := containerType.(*CompositeType); isComposite && compositeContainer.Kind == common.CompositeKindAttachment { + checker.declareBaseValue( + compositeContainer.baseType, + compositeContainer, + compositeContainer.baseTypeDocString) + } + param.DefaultArgument = checker.VisitExpression(paramDefaultArgument, paramType) + + unwrappedParamType := UnwrapOptionalType(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 + if !IsSubType(unwrappedParamType, StringType) && + !IsSubType(unwrappedParamType, NumberType) && + !IsSubType(unwrappedParamType, TheAddressType) && + !IsSubType(unwrappedParamType, PathType) && + !IsSubType(unwrappedParamType, BoolType) { + checker.report(&DefaultDestroyInvalidParameterError{ + ParamType: paramType, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, paramExpr), + }) + } + + checker.checkDefaultDestroyParamExpressionKind(paramDefaultArgument) +} + func (checker *Checker) checkDefaultDestroyEvent( eventType *CompositeType, eventDeclaration ast.CompositeLikeDeclaration, @@ -2061,42 +2100,14 @@ func (checker *Checker) checkDefaultDestroyEvent( // an event definition always has one "constructor" function in its declaration list members := eventDeclaration.DeclarationMembers() - functions := members.SpecialFunctions() + functions := members.Initializers() constructorFunctionParameters := functions[0].FunctionDeclaration.ParameterList.Parameters checker.enterValueScope() defer checker.leaveValueScope(eventDeclaration.EndPosition, true) for index, param := range eventType.ConstructorParameters { - paramType := param.TypeAnnotation.Type - paramExpr := constructorFunctionParameters[index] - paramDefaultArgument := paramExpr.DefaultArgument - - // make `self` and `base` available when checking default arguments so the fields of the composite are available - checker.declareSelfValue(containerType, containerDeclaration.DeclarationDocString()) - if compositeContainer, isComposite := containerType.(*CompositeType); isComposite && compositeContainer.Kind == common.CompositeKindAttachment { - checker.declareBaseValue( - compositeContainer.baseType, - compositeContainer, - compositeContainer.baseTypeDocString) - } - param.DefaultArgument = checker.VisitExpression(paramDefaultArgument, paramType) - - unwrappedParamType := UnwrapOptionalType(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 - if !IsSubType(unwrappedParamType, StringType) && - !IsSubType(unwrappedParamType, NumberType) && - !IsSubType(unwrappedParamType, TheAddressType) && - !IsSubType(unwrappedParamType, PathType) && - !IsSubType(unwrappedParamType, BoolType) { - checker.report(&DefaultDestroyInvalidParameterError{ - ParamType: paramType, - Range: ast.NewRangeFromPositioned(checker.memoryGauge, paramExpr), - }) - } - - checker.checkDefaultDestroyParamExpressionKind(paramDefaultArgument) + checker.checkDefaultDestroyEventParam(param, index, constructorFunctionParameters, containerType, containerDeclaration) } } diff --git a/runtime/sema/check_emit_statement.go b/runtime/sema/check_emit_statement.go index 572c0f6df5..4d15fc8d5a 100644 --- a/runtime/sema/check_emit_statement.go +++ b/runtime/sema/check_emit_statement.go @@ -45,7 +45,7 @@ func (checker *Checker) VisitEmitStatement(statement *ast.EmitStatement) (_ stru return } - if compositeType.Identifier == ast.ResourceDestructionDefaultEventName { + if ast.IsResourceDestructionDefaultEvent(compositeType.Identifier) { checker.report( &EmitDefaultDestroyEventError{ Range: ast.NewRangeFromPositioned(checker.memoryGauge, statement.InvocationExpression), From 1be5186c69683d503ad85a0ef2b665a20f660981 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 12:37:41 -0400 Subject: [PATCH 34/48] fix lint --- runtime/ast/parameter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/ast/parameter.go b/runtime/ast/parameter.go index 151e973e13..ee0d3f6cc8 100644 --- a/runtime/ast/parameter.go +++ b/runtime/ast/parameter.go @@ -21,8 +21,9 @@ package ast import ( "encoding/json" - "github.com/onflow/cadence/runtime/common" "github.com/turbolent/prettier" + + "github.com/onflow/cadence/runtime/common" ) type Parameter struct { From 7931d902c8cfdad13bca2c85786f6660faf052da Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Mon, 30 Oct 2023 14:07:36 -0400 Subject: [PATCH 35/48] respond to review --- runtime/sema/check_composite_declaration.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index d06cd70059..21650417d2 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -290,7 +290,7 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL } for _, nestedComposite := range members.Composites() { - if compositeType.DefaultDestroyEvent != nil { + if compositeType.DefaultDestroyEvent != nil && nestedComposite.IsResourceDestructionDefaultEvent() { // we enforce elsewhere that each composite can have only one default destroy event checker.checkDefaultDestroyEvent(compositeType.DefaultDestroyEvent, nestedComposite, compositeType, declaration) } @@ -2055,14 +2055,12 @@ func (checker *Checker) checkDefaultDestroyParamExpressionKind( func (checker *Checker) checkDefaultDestroyEventParam( param Parameter, - index int, - constructorFunctionParameters []*ast.Parameter, + astParam *ast.Parameter, containerType ContainerType, containerDeclaration ast.Declaration, ) { paramType := param.TypeAnnotation.Type - paramExpr := constructorFunctionParameters[index] - paramDefaultArgument := paramExpr.DefaultArgument + paramDefaultArgument := astParam.DefaultArgument // make `self` and `base` available when checking default arguments so the fields of the composite are available checker.declareSelfValue(containerType, containerDeclaration.DeclarationDocString()) @@ -2084,7 +2082,7 @@ func (checker *Checker) checkDefaultDestroyEventParam( !IsSubType(unwrappedParamType, BoolType) { checker.report(&DefaultDestroyInvalidParameterError{ ParamType: paramType, - Range: ast.NewRangeFromPositioned(checker.memoryGauge, paramExpr), + Range: ast.NewRangeFromPositioned(checker.memoryGauge, astParam), }) } @@ -2107,7 +2105,7 @@ func (checker *Checker) checkDefaultDestroyEvent( defer checker.leaveValueScope(eventDeclaration.EndPosition, true) for index, param := range eventType.ConstructorParameters { - checker.checkDefaultDestroyEventParam(param, index, constructorFunctionParameters, containerType, containerDeclaration) + checker.checkDefaultDestroyEventParam(param, constructorFunctionParameters[index], containerType, containerDeclaration) } } From 3ba46dfcfde2aede946e8addc140c65bc2d8c649 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 12:41:45 -0400 Subject: [PATCH 36/48] respond to review --- runtime/interpreter/interpreter.go | 18 ++++---- runtime/interpreter/value.go | 2 +- runtime/sema/check_composite_declaration.go | 1 + runtime/sema/check_interface_declaration.go | 3 ++ runtime/sema/elaboration.go | 18 ++++++++ runtime/tests/interpreter/attachments_test.go | 42 ++++++++++++++++--- 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index c9fe52dcab..bb88c31f8b 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -2256,17 +2256,13 @@ func (interpreter *Interpreter) declareInterface( ) var defaultDestroyEventConstructor FunctionValue - for _, nestedCompositeDeclaration := range declaration.Members.Composites() { - // statically we know there is at most one of these - if nestedCompositeDeclaration.IsResourceDestructionDefaultEvent() { - var nestedVariable *Variable - lexicalScope, nestedVariable = interpreter.declareCompositeValue( - nestedCompositeDeclaration, - lexicalScope, - ) - defaultDestroyEventConstructor = nestedVariable.GetValue().(FunctionValue) - break - } + if defautlDestroyEvent := interpreter.Program.Elaboration.DefaultDestroyDeclaration(declaration); defautlDestroyEvent != nil { + var nestedVariable *Variable + lexicalScope, nestedVariable = interpreter.declareCompositeValue( + defautlDestroyEvent, + lexicalScope, + ) + defaultDestroyEventConstructor = nestedVariable.GetValue().(FunctionValue) } functionWrappers := interpreter.functionWrappers(declaration.Members, lexicalScope) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index cf01c6eeed..3582b1112d 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16380,7 +16380,7 @@ type CompositeField struct { } const unrepresentableNamePrefix = "$" -const resourceDefaultDestroyEventPrefix = "ResourceDestroyed" + unrepresentableNamePrefix +const resourceDefaultDestroyEventPrefix = ast.ResourceDestructionDefaultEventName + unrepresentableNamePrefix var _ TypeIndexableValue = &CompositeValue{} diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 8cff51b91b..2b4ad8e0b3 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -845,6 +845,7 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( if identifier.Identifier == ast.ResourceDestructionDefaultEventName { // Find the default event's type declaration + checker.Elaboration.SetDefaultDestroyDeclaration(declaration, nestedCompositeDeclaration) defaultEventType := checker.typeActivations.Find(identifier.Identifier) defaultEventComposite := defaultEventType.Type.(*CompositeType) diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index 508c9a1c00..a5a27c4bd4 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -443,6 +443,9 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa for _, nestedCompositeDeclaration := range declaration.Members.Composites() { if nestedCompositeDeclaration.Kind() == common.CompositeKindEvent { if nestedCompositeDeclaration.IsResourceDestructionDefaultEvent() { + + checker.Elaboration.SetDefaultDestroyDeclaration(declaration, nestedCompositeDeclaration) + // Find the value declaration nestedEvent := checker.typeActivations.Find(nestedCompositeDeclaration.Identifier.Identifier) diff --git a/runtime/sema/elaboration.go b/runtime/sema/elaboration.go index 8211be53b2..749a9bbbf2 100644 --- a/runtime/sema/elaboration.go +++ b/runtime/sema/elaboration.go @@ -148,6 +148,7 @@ type Elaboration struct { nestedResourceMoveExpressions map[ast.Expression]struct{} compositeNestedDeclarations map[ast.CompositeLikeDeclaration]map[string]ast.Declaration interfaceNestedDeclarations map[*ast.InterfaceDeclaration]map[string]ast.Declaration + defaultDestroyDeclarations map[ast.Declaration]ast.CompositeLikeDeclaration postConditionsRewrites map[*ast.Conditions]PostConditionsRewrite emitStatementEventTypes map[*ast.EmitStatement]*CompositeType compositeTypes map[TypeID]*CompositeType @@ -735,6 +736,23 @@ func (e *Elaboration) SetInterfaceNestedDeclarations( e.interfaceNestedDeclarations[declaration] = nestedDeclaration } +func (e *Elaboration) DefaultDestroyDeclaration(declaration ast.Declaration) ast.CompositeLikeDeclaration { + if e.defaultDestroyDeclarations == nil { + return nil + } + return e.defaultDestroyDeclarations[declaration] +} + +func (e *Elaboration) SetDefaultDestroyDeclaration( + declaration ast.Declaration, + eventDeclaration ast.CompositeLikeDeclaration, +) { + if e.defaultDestroyDeclarations == nil { + e.defaultDestroyDeclarations = map[ast.Declaration]ast.CompositeLikeDeclaration{} + } + e.defaultDestroyDeclarations[declaration] = eventDeclaration +} + func (e *Elaboration) PostConditionsRewrite(conditions *ast.Conditions) (rewrite PostConditionsRewrite) { if e.postConditionsRewrites == nil { return diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index 36c3c1ac70..9428d38f83 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1256,7 +1256,12 @@ func TestInterpretAttachmentDestructor(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func( + _ *interpreter.Interpreter, + _ interpreter.LocationRange, + event *interpreter.CompositeValue, + _ *sema.CompositeType, + ) error { events = append(events, event) return nil }, @@ -1301,7 +1306,12 @@ func TestInterpretAttachmentDestructor(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func( + _ *interpreter.Interpreter, + _ interpreter.LocationRange, + event *interpreter.CompositeValue, + _ *sema.CompositeType, + ) error { events = append(events, event) return nil }, @@ -1342,7 +1352,12 @@ func TestInterpretAttachmentDestructor(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func( + _ *interpreter.Interpreter, + _ interpreter.LocationRange, + event *interpreter.CompositeValue, + _ *sema.CompositeType, + ) error { events = append(events, event) return nil }, @@ -1386,7 +1401,12 @@ func TestInterpretAttachmentDestructor(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func( + _ *interpreter.Interpreter, + _ interpreter.LocationRange, + event *interpreter.CompositeValue, + _ *sema.CompositeType, + ) error { events = append(events, event) return nil }, @@ -1434,7 +1454,12 @@ func TestInterpretAttachmentDestructor(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func( + _ *interpreter.Interpreter, + _ interpreter.LocationRange, + event *interpreter.CompositeValue, + _ *sema.CompositeType, + ) error { events = append(events, event) return nil }, @@ -1490,7 +1515,12 @@ func TestInterpretAttachmentDestructor(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func( + _ *interpreter.Interpreter, + _ interpreter.LocationRange, + event *interpreter.CompositeValue, + _ *sema.CompositeType, + ) error { events = append(events, event) return nil }, From 366366d6b067f58cdcc436f611f7183d0d42608e Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 31 Oct 2023 13:46:00 -0400 Subject: [PATCH 37/48] use deterministic ordered function map --- runtime/environment.go | 2 +- runtime/interpreter/interpreter.go | 59 +++++++++++-------- runtime/interpreter/value.go | 22 +++++-- runtime/predeclaredvalues_test.go | 27 +++++---- runtime/stdlib/builtin.go | 4 +- runtime/stdlib/publickey.go | 11 ++-- runtime/stdlib/test_contract.go | 28 ++++----- runtime/tests/interpreter/import_test.go | 10 ++-- runtime/tests/interpreter/interpreter_test.go | 3 +- 9 files changed, 95 insertions(+), 71 deletions(-) diff --git a/runtime/environment.go b/runtime/environment.go index 9c1e40bb7c..895d6f44e5 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -981,7 +981,7 @@ func (e *interpreterEnvironment) newCompositeValueFunctionsHandler() interpreter inter *interpreter.Interpreter, locationRange interpreter.LocationRange, compositeValue *interpreter.CompositeValue, - ) map[string]interpreter.FunctionValue { + ) *interpreter.FunctionOrderedMap { handler := e.compositeValueFunctionsHandlers[compositeValue.TypeID()] if handler == nil { diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index bb88c31f8b..ee24b07fdc 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -37,6 +37,7 @@ import ( "github.com/onflow/cadence/runtime/activations" "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/sema" ) @@ -165,7 +166,7 @@ type CompositeValueFunctionsHandlerFunc func( inter *Interpreter, locationRange LocationRange, compositeValue *CompositeValue, -) map[string]FunctionValue +) *FunctionOrderedMap // CompositeTypeCode contains the "prepared" / "callable" "code" // for the functions and the destructor of a composite @@ -174,7 +175,7 @@ type CompositeValueFunctionsHandlerFunc func( // As there is no support for inheritance of concrete types, // these are the "leaf" nodes in the call chain, and are functions. type CompositeTypeCode struct { - CompositeFunctions map[string]FunctionValue + CompositeFunctions *FunctionOrderedMap } type FunctionWrapper = func(inner FunctionValue) FunctionValue @@ -187,7 +188,7 @@ type FunctionWrapper = func(inner FunctionValue) FunctionValue type WrapperCode struct { InitializerFunctionWrapper FunctionWrapper FunctionWrappers map[string]FunctionWrapper - Functions map[string]FunctionValue + Functions *FunctionOrderedMap DefaultDestroyEventConstructor FunctionValue } @@ -1195,7 +1196,7 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( functions := interpreter.compositeFunctions(declaration, lexicalScope) if destroyEventConstructor != nil { - functions[resourceDefaultDestroyEventName(compositeType)] = destroyEventConstructor + functions.Set(resourceDefaultDestroyEventName(compositeType), destroyEventConstructor) } wrapFunctions := func(ty *sema.InterfaceType, code WrapperCode) { @@ -1215,14 +1216,16 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( // we only apply the function wrapper to each function, // the order does not matter. - for name, function := range code.Functions { //nolint:maprange - if functions[name] != nil { - continue - } - if functions == nil { - functions = map[string]FunctionValue{} - } - functions[name] = function + if code.Functions != nil { + code.Functions.Foreach(func(name string, function FunctionValue) { + if functions == nil { + functions = orderedmap.New[FunctionOrderedMap](code.Functions.Len()) + } + if functions.Contains(name) { + return + } + functions.Set(name, function) + }) } // Wrap functions @@ -1232,11 +1235,12 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( // the order does not matter. for name, functionWrapper := range code.FunctionWrappers { //nolint:maprange - functions[name] = functionWrapper(functions[name]) + fn, _ := functions.Get(name) + functions.Set(name, functionWrapper(fn)) } if code.DefaultDestroyEventConstructor != nil { - functions[resourceDefaultDestroyEventName(ty)] = code.DefaultDestroyEventConstructor + functions.Set(resourceDefaultDestroyEventName(ty), code.DefaultDestroyEventConstructor) } } @@ -1594,7 +1598,7 @@ func (interpreter *Interpreter) compositeInitializerFunction( func (interpreter *Interpreter) defaultFunctions( members *ast.Members, lexicalScope *VariableActivation, -) map[string]FunctionValue { +) *FunctionOrderedMap { functionDeclarations := members.Functions() functionCount := len(functionDeclarations) @@ -1603,7 +1607,7 @@ func (interpreter *Interpreter) defaultFunctions( return nil } - functions := make(map[string]FunctionValue, functionCount) + functions := orderedmap.New[FunctionOrderedMap](functionCount) for _, functionDeclaration := range functionDeclarations { name := functionDeclaration.Identifier.Identifier @@ -1611,9 +1615,12 @@ func (interpreter *Interpreter) defaultFunctions( continue } - functions[name] = interpreter.compositeFunction( - functionDeclaration, - lexicalScope, + functions.Set( + name, + interpreter.compositeFunction( + functionDeclaration, + lexicalScope, + ), ) } @@ -1623,17 +1630,19 @@ func (interpreter *Interpreter) defaultFunctions( func (interpreter *Interpreter) compositeFunctions( compositeDeclaration ast.CompositeLikeDeclaration, lexicalScope *VariableActivation, -) map[string]FunctionValue { +) *FunctionOrderedMap { - functions := map[string]FunctionValue{} + functions := orderedmap.New[FunctionOrderedMap](len(compositeDeclaration.DeclarationMembers().Functions())) for _, functionDeclaration := range compositeDeclaration.DeclarationMembers().Functions() { name := functionDeclaration.Identifier.Identifier - functions[name] = + functions.Set( + name, interpreter.compositeFunction( functionDeclaration, lexicalScope, - ) + ), + ) } return functions @@ -4615,9 +4624,9 @@ func (interpreter *Interpreter) GetCompositeValueInjectedFields(v *CompositeValu func (interpreter *Interpreter) GetCompositeValueFunctions( v *CompositeValue, locationRange LocationRange, -) map[string]FunctionValue { +) *FunctionOrderedMap { - var functions map[string]FunctionValue + var functions *FunctionOrderedMap typeID := v.TypeID() diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 3582b1112d..1ef31575a8 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -37,6 +37,7 @@ import ( "github.com/onflow/cadence/runtime/ast" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/format" "github.com/onflow/cadence/runtime/sema" @@ -16350,6 +16351,8 @@ func (UFix64Value) Scale() int { // CompositeValue +type FunctionOrderedMap = orderedmap.OrderedMap[string, FunctionValue] + type CompositeValue struct { Location common.Location staticType StaticType @@ -16357,7 +16360,7 @@ type CompositeValue struct { injectedFields map[string]Value computedFields map[string]ComputedField NestedVariables map[string]*Variable - Functions map[string]FunctionValue + Functions *FunctionOrderedMap dictionary *atree.OrderedMap typeID TypeID @@ -16591,11 +16594,14 @@ func resourceDefaultDestroyEventName(t sema.ContainerType) string { } func (v *CompositeValue) defaultDestroyEventConstructors() (constructors []FunctionValue) { - for name, f := range v.Functions { + if v.Functions == nil { + return + } + v.Functions.Foreach(func(name string, f FunctionValue) { if strings.HasPrefix(name, resourceDefaultDestroyEventPrefix) { constructors = append(constructors, f) } - } + }) return } @@ -16831,9 +16837,13 @@ func (v *CompositeValue) GetFunction(interpreter *Interpreter, locationRange Loc if v.Functions == nil { v.Functions = interpreter.GetCompositeValueFunctions(v, locationRange) } + // if no functions were produced, the `Get` below will be nil + if v.Functions == nil { + return nil + } - function, ok := v.Functions[name] - if !ok { + function, present := v.Functions.Get(name) + if !present { return nil } @@ -17718,7 +17728,7 @@ func NewEnumCaseValue( locationRange LocationRange, enumType *sema.CompositeType, rawValue NumberValue, - functions map[string]FunctionValue, + functions *FunctionOrderedMap, ) *CompositeValue { fields := []CompositeField{ diff --git a/runtime/predeclaredvalues_test.go b/runtime/predeclaredvalues_test.go index b2f4e6cbbb..62b7633440 100644 --- a/runtime/predeclaredvalues_test.go +++ b/runtime/predeclaredvalues_test.go @@ -29,6 +29,7 @@ import ( "github.com/onflow/cadence" . "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" @@ -697,21 +698,21 @@ func TestRuntimePredeclaredTypeWithInjectedFunctions(t *testing.T) { inter *interpreter.Interpreter, locationRange interpreter.LocationRange, compositeValue *interpreter.CompositeValue, - ) map[string]interpreter.FunctionValue { + ) *interpreter.FunctionOrderedMap { require.NotNil(t, compositeValue) - return map[string]interpreter.FunctionValue{ - fooFunctionName: interpreter.NewHostFunctionValue( - inter, - fooFunctionType, - func(invocation interpreter.Invocation) interpreter.Value { - arg := invocation.Arguments[0] - require.IsType(t, interpreter.UInt8Value(0), arg) - - return interpreter.NewUnmeteredStringValue(strconv.Itoa(int(arg.(interpreter.UInt8Value) + 1))) - }, - ), - } + functions := orderedmap.New[interpreter.FunctionOrderedMap](1) + functions.Set(fooFunctionName, interpreter.NewHostFunctionValue( + inter, + fooFunctionType, + func(invocation interpreter.Invocation) interpreter.Value { + arg := invocation.Arguments[0] + require.IsType(t, interpreter.UInt8Value(0), arg) + + return interpreter.NewUnmeteredStringValue(strconv.Itoa(int(arg.(interpreter.UInt8Value) + 1))) + }, + )) + return functions }, ) diff --git a/runtime/stdlib/builtin.go b/runtime/stdlib/builtin.go index e1f085899f..65d71d2274 100644 --- a/runtime/stdlib/builtin.go +++ b/runtime/stdlib/builtin.go @@ -67,7 +67,7 @@ type CompositeValueFunctionsHandler func( inter *interpreter.Interpreter, locationRange interpreter.LocationRange, compositeValue *interpreter.CompositeValue, -) map[string]interpreter.FunctionValue +) *interpreter.FunctionOrderedMap type CompositeValueFunctionsHandlers map[common.TypeID]CompositeValueFunctionsHandler @@ -79,7 +79,7 @@ func DefaultStandardLibraryCompositeValueFunctionHandlers( inter *interpreter.Interpreter, _ interpreter.LocationRange, _ *interpreter.CompositeValue, - ) map[string]interpreter.FunctionValue { + ) *interpreter.FunctionOrderedMap { return PublicKeyFunctions(inter, handler) }, } diff --git a/runtime/stdlib/publickey.go b/runtime/stdlib/publickey.go index 854939269f..069fa27989 100644 --- a/runtime/stdlib/publickey.go +++ b/runtime/stdlib/publickey.go @@ -20,6 +20,7 @@ package stdlib import ( "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" @@ -350,9 +351,9 @@ type PublicKeyFunctionsHandler interface { func PublicKeyFunctions( gauge common.MemoryGauge, handler PublicKeyFunctionsHandler, -) map[string]interpreter.FunctionValue { - return map[string]interpreter.FunctionValue{ - sema.PublicKeyTypeVerifyFunctionName: newPublicKeyVerifySignatureFunction(gauge, handler), - sema.PublicKeyTypeVerifyPoPFunctionName: newPublicKeyVerifyPoPFunction(gauge, handler), - } +) *interpreter.FunctionOrderedMap { + functions := orderedmap.New[interpreter.FunctionOrderedMap](2) + functions.Set(sema.PublicKeyTypeVerifyFunctionName, newPublicKeyVerifySignatureFunction(gauge, handler)) + functions.Set(sema.PublicKeyTypeVerifyPoPFunctionName, newPublicKeyVerifyPoPFunction(gauge, handler)) + return functions } diff --git a/runtime/stdlib/test_contract.go b/runtime/stdlib/test_contract.go index fb0737b675..862f234ac1 100644 --- a/runtime/stdlib/test_contract.go +++ b/runtime/stdlib/test_contract.go @@ -1230,22 +1230,22 @@ func (t *TestContractType) NewTestContract( compositeValue := value.(*interpreter.CompositeValue) // Inject natively implemented function values - compositeValue.Functions[testTypeAssertFunctionName] = testTypeAssertFunction - compositeValue.Functions[testTypeAssertEqualFunctionName] = testTypeAssertEqualFunction - compositeValue.Functions[testTypeFailFunctionName] = testTypeFailFunction - compositeValue.Functions[testTypeExpectFunctionName] = t.expectFunction - compositeValue.Functions[testTypeReadFileFunctionName] = - newTestTypeReadFileFunction(testFramework) + compositeValue.Functions.Set(testTypeAssertFunctionName, testTypeAssertFunction) + compositeValue.Functions.Set(testTypeAssertEqualFunctionName, testTypeAssertEqualFunction) + compositeValue.Functions.Set(testTypeFailFunctionName, testTypeFailFunction) + compositeValue.Functions.Set(testTypeExpectFunctionName, t.expectFunction) + compositeValue.Functions.Set(testTypeReadFileFunctionName, + newTestTypeReadFileFunction(testFramework)) // Inject natively implemented matchers - compositeValue.Functions[testTypeNewMatcherFunctionName] = t.newMatcherFunction - compositeValue.Functions[testTypeEqualFunctionName] = t.equalFunction - compositeValue.Functions[testTypeBeEmptyFunctionName] = t.beEmptyFunction - compositeValue.Functions[testTypeHaveElementCountFunctionName] = t.haveElementCountFunction - compositeValue.Functions[testTypeContainFunctionName] = t.containFunction - compositeValue.Functions[testTypeBeGreaterThanFunctionName] = t.beGreaterThanFunction - compositeValue.Functions[testTypeBeLessThanFunctionName] = t.beLessThanFunction - compositeValue.Functions[testExpectFailureFunctionName] = t.expectFailureFunction + compositeValue.Functions.Set(testTypeNewMatcherFunctionName, t.newMatcherFunction) + compositeValue.Functions.Set(testTypeEqualFunctionName, t.equalFunction) + compositeValue.Functions.Set(testTypeBeEmptyFunctionName, t.beEmptyFunction) + compositeValue.Functions.Set(testTypeHaveElementCountFunctionName, t.haveElementCountFunction) + compositeValue.Functions.Set(testTypeContainFunctionName, t.containFunction) + compositeValue.Functions.Set(testTypeBeGreaterThanFunctionName, t.beGreaterThanFunction) + compositeValue.Functions.Set(testTypeBeLessThanFunctionName, t.beLessThanFunction) + compositeValue.Functions.Set(testExpectFailureFunctionName, t.expectFailureFunction) return compositeValue, nil } diff --git a/runtime/tests/interpreter/import_test.go b/runtime/tests/interpreter/import_test.go index ff699b8cbc..4eaec78726 100644 --- a/runtime/tests/interpreter/import_test.go +++ b/runtime/tests/interpreter/import_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/onflow/cadence/runtime/common/orderedmap" . "github.com/onflow/cadence/runtime/tests/utils" "github.com/onflow/cadence/runtime/ast" @@ -91,9 +92,10 @@ func TestInterpretVirtualImport(t *testing.T) { nil, common.ZeroAddress, ) - - value.Functions = map[string]interpreter.FunctionValue{ - "bar": interpreter.NewHostFunctionValue( + value.Functions = orderedmap.New[interpreter.FunctionOrderedMap](1) + value.Functions.Set( + "bar", + interpreter.NewHostFunctionValue( inter, &sema.FunctionType{ ReturnTypeAnnotation: sema.UIntTypeAnnotation, @@ -102,7 +104,7 @@ func TestInterpretVirtualImport(t *testing.T) { return interpreter.NewUnmeteredUInt64Value(42) }, ), - } + ) elaboration := sema.NewElaboration(nil) elaboration.SetCompositeType( diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 8c38cd68c2..1af9fb781d 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -28,6 +28,7 @@ import ( "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/activations" + "github.com/onflow/cadence/runtime/common/orderedmap" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -7383,7 +7384,7 @@ func TestInterpretEmitEventParameterTypes(t *testing.T) { nil, common.ZeroAddress, ) - sValue.Functions = map[string]interpreter.FunctionValue{} + sValue.Functions = orderedmap.New[interpreter.FunctionOrderedMap](0) validTypes := map[string]testValue{ "String": { From 229a189980f5701d78ba8efb4019e0d4da3ac33f Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 14:41:24 -0400 Subject: [PATCH 38/48] Update runtime/interpreter/interpreter.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bastian Müller --- runtime/interpreter/interpreter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index ee24b07fdc..eff3b28ea9 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -980,7 +980,7 @@ func (interpreter *Interpreter) declareAttachmentValue( return interpreter.declareCompositeValue(declaration, lexicalScope) } -// evaluates all the implicit default arguments to the default destroy event +// evaluateDefaultDestroyEvent evaluates all the implicit default arguments to the default destroy event. // // the handling of default arguments makes a number of assumptions to simplify the implementation; // namely that a) all default arguments are lazily evaluated at the site of the invocation, From e0b9d0378d365165ec2848458c9890ee01313557 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 15:09:23 -0400 Subject: [PATCH 39/48] respond to review --- runtime/interpreter/interpreter.go | 31 +++++++++++++++--------------- runtime/interpreter/value.go | 8 ++++++-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index eff3b28ea9..d229a9b0ce 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -989,31 +989,30 @@ func (interpreter *Interpreter) declareAttachmentValue( // // if we plan to generalize this further, we will need to relax those assumptions func (interpreter *Interpreter) evaluateDefaultDestroyEvent( - containerComposite *CompositeValue, - compositeDecl *ast.CompositeDeclaration, - compositeType *sema.CompositeType, - locationRange LocationRange, + containingResourceComposite *CompositeValue, + eventDecl *ast.CompositeDeclaration, ) (arguments []Value) { - parameters := compositeDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters + parameters := eventDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters - interpreter.activations.PushNewWithParent(interpreter.activations.CurrentOrNew()) + defer func() { + // Only unwind the call stack if there was no error + if r := recover(); r != nil { + panic(r) + } + interpreter.SharedState.callStack.Pop() + }() defer interpreter.activations.Pop() - var self MemberAccessibleValue = containerComposite - if containerComposite.Kind == common.CompositeKindAttachment { + var self MemberAccessibleValue = containingResourceComposite + if containingResourceComposite.Kind == common.CompositeKindAttachment { var base *EphemeralReferenceValue - base, self = attachmentBaseAndSelfValues(interpreter, containerComposite) + base, self = attachmentBaseAndSelfValues(interpreter, containingResourceComposite) interpreter.declareVariable(sema.BaseIdentifier, base) } interpreter.declareVariable(sema.SelfIdentifier, self) for _, parameter := range parameters { // lazily evaluate the default argument expressions - // note that we must evaluate them in the interpreter context that existed when the event - // was defined (i.e. the contract defining the resource) rather than the interpreter context - // that exists when the resource is destroyed. We accomplish this by using the original interpreter of the - // composite declaration, rather than the interpreter of the destroy expression - defaultArg := interpreter.evalExpression(parameter.DefaultArgument) arguments = append(arguments, defaultArg) } @@ -1166,11 +1165,11 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( if compositeDecl.IsResourceDestructionDefaultEvent() { // we implicitly pass the containing composite value as an argument to this invocation containerComposite := invocation.Arguments[0].(*CompositeValue) + interpreter.activations.PushNewWithParent(inter.activations.CurrentOrNew()) + interpreter.SharedState.callStack.Push(invocation) invocation.Arguments = interpreter.evaluateDefaultDestroyEvent( containerComposite, compositeDecl, - compositeType, - locationRange, ) } diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 1ef31575a8..cc03e7e0e2 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -16593,6 +16593,10 @@ func resourceDefaultDestroyEventName(t sema.ContainerType) string { return resourceDefaultDestroyEventPrefix + string(t.ID()) } +// get all the default destroy event constructors associated with this composite value. +// note that there can be more than one in the case where a resource inherits from an interface +// that also defines a default destroy event. When that composite is destroyed, all of these +// events will need to be emitted. func (v *CompositeValue) defaultDestroyEventConstructors() (constructors []FunctionValue) { if v.Functions == nil { return @@ -16648,7 +16652,7 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio // pass the container value to the creation of the default event as an implicit argument, so that // its fields are accessible in the body of the event constructor - mockInvocation := NewInvocation( + eventConstructorInvocation := NewInvocation( interpreter, nil, nil, @@ -16659,7 +16663,7 @@ func (v *CompositeValue) Destroy(interpreter *Interpreter, locationRange Locatio locationRange, ) - event := constructor.invoke(mockInvocation).(*CompositeValue) + event := constructor.invoke(eventConstructorInvocation).(*CompositeValue) eventType := interpreter.MustSemaTypeOfValue(event).(*sema.CompositeType) // emit the event once destruction is complete From 3cd0f5d1b638f539394cb9ef37d967967077f487 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 16:35:24 -0400 Subject: [PATCH 40/48] add comment --- runtime/interpreter/interpreter.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index d229a9b0ce..d8aff83c0b 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -1012,7 +1012,12 @@ func (interpreter *Interpreter) evaluateDefaultDestroyEvent( interpreter.declareVariable(sema.SelfIdentifier, self) for _, parameter := range parameters { - // lazily evaluate the default argument expressions + // "lazily" evaluate the default argument expressions. + // This "lazy" with respect to the event's declaration: + // if we declare a default event `ResourceDestroyed(foo: Int = self.x)`, + // `self.x` is evaluated in the context that exists when the event is destroyed, + // not the context when it is declared. This function is only called after the destroy + // triggers the event emission, so with respect to this function it's "eager". defaultArg := interpreter.evalExpression(parameter.DefaultArgument) arguments = append(arguments, defaultArg) } From e5af208fb0d206bada01d64718161f064baaf73d Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 17:14:24 -0400 Subject: [PATCH 41/48] rename variables --- runtime/interpreter/interpreter.go | 69 ++++++++++++++++-------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index d8aff83c0b..4ee5689bd0 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -988,28 +988,35 @@ func (interpreter *Interpreter) declareAttachmentValue( // and c) functions cannot currently be explicitly invoked if they have default arguments // // if we plan to generalize this further, we will need to relax those assumptions -func (interpreter *Interpreter) evaluateDefaultDestroyEvent( +func (declarationInterpreter *Interpreter) evaluateDefaultDestroyEvent( containingResourceComposite *CompositeValue, eventDecl *ast.CompositeDeclaration, + invocation Invocation, + invocationActivation *VariableActivation, ) (arguments []Value) { parameters := eventDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters + declarationInterpreter.activations.PushNewWithParent(invocationActivation) + declarationInterpreter.SharedState.callStack.Push(invocation) + + // interpreter.activations.PushNewWithParent(inter.activations.CurrentOrNew()) + // interpreter.SharedState.callStack.Push(invocation) defer func() { // Only unwind the call stack if there was no error if r := recover(); r != nil { panic(r) } - interpreter.SharedState.callStack.Pop() + declarationInterpreter.SharedState.callStack.Pop() }() - defer interpreter.activations.Pop() + defer declarationInterpreter.activations.Pop() var self MemberAccessibleValue = containingResourceComposite if containingResourceComposite.Kind == common.CompositeKindAttachment { var base *EphemeralReferenceValue - base, self = attachmentBaseAndSelfValues(interpreter, containingResourceComposite) - interpreter.declareVariable(sema.BaseIdentifier, base) + base, self = attachmentBaseAndSelfValues(declarationInterpreter, containingResourceComposite) + declarationInterpreter.declareVariable(sema.BaseIdentifier, base) } - interpreter.declareVariable(sema.SelfIdentifier, self) + declarationInterpreter.declareVariable(sema.SelfIdentifier, self) for _, parameter := range parameters { // "lazily" evaluate the default argument expressions. @@ -1018,7 +1025,7 @@ func (interpreter *Interpreter) evaluateDefaultDestroyEvent( // `self.x` is evaluated in the context that exists when the event is destroyed, // not the context when it is declared. This function is only called after the destroy // triggers the event emission, so with respect to this function it's "eager". - defaultArg := interpreter.evalExpression(parameter.DefaultArgument) + defaultArg := declarationInterpreter.evalExpression(parameter.DefaultArgument) arguments = append(arguments, defaultArg) } @@ -1055,7 +1062,7 @@ func (interpreter *Interpreter) declareCompositeValue( } } -func (interpreter *Interpreter) declareNonEnumCompositeValue( +func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( declaration ast.CompositeLikeDeclaration, lexicalScope *VariableActivation, ) ( @@ -1064,7 +1071,7 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( ) { identifier := declaration.DeclarationIdentifier().Identifier // NOTE: find *or* declare, as the function might have not been pre-declared (e.g. in the REPL) - variable = interpreter.findOrDeclareVariable(identifier) + variable = declarationInterpreter.findOrDeclareVariable(identifier) // Make the value available in the initializer lexicalScope.Set(identifier, variable) @@ -1077,15 +1084,15 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( var destroyEventConstructor FunctionValue (func() { - interpreter.activations.PushNewWithCurrent() - defer interpreter.activations.Pop() + declarationInterpreter.activations.PushNewWithCurrent() + defer declarationInterpreter.activations.Pop() // Pre-declare empty variables for all interfaces, composites, and function declarations predeclare := func(identifier ast.Identifier) { name := identifier.Identifier lexicalScope.Set( name, - interpreter.declareVariable(name, nil), + declarationInterpreter.declareVariable(name, nil), ) } @@ -1104,7 +1111,7 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( } for _, nestedInterfaceDeclaration := range members.Interfaces() { - interpreter.declareInterface(nestedInterfaceDeclaration, lexicalScope) + declarationInterpreter.declareInterface(nestedInterfaceDeclaration, lexicalScope) } for _, nestedCompositeDeclaration := range members.Composites() { @@ -1115,7 +1122,7 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( var nestedVariable *Variable lexicalScope, nestedVariable = - interpreter.declareCompositeValue( + declarationInterpreter.declareCompositeValue( nestedCompositeDeclaration, lexicalScope, ) @@ -1137,7 +1144,7 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( var nestedVariable *Variable lexicalScope, nestedVariable = - interpreter.declareAttachmentValue( + declarationInterpreter.declareAttachmentValue( nestedAttachmentDeclaration, lexicalScope, ) @@ -1147,17 +1154,17 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( } })() - compositeType := interpreter.Program.Elaboration.CompositeDeclarationType(declaration) + compositeType := declarationInterpreter.Program.Elaboration.CompositeDeclarationType(declaration) initializerType := compositeType.InitializerFunctionType() var initializerFunction FunctionValue if declaration.Kind() == common.CompositeKindEvent { initializerFunction = NewHostFunctionValue( - interpreter, + declarationInterpreter, initializerType, func(invocation Invocation) Value { - inter := invocation.Interpreter + invocationInterpreter := invocation.Interpreter locationRange := invocation.LocationRange self := *invocation.Self @@ -1170,18 +1177,18 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( if compositeDecl.IsResourceDestructionDefaultEvent() { // we implicitly pass the containing composite value as an argument to this invocation containerComposite := invocation.Arguments[0].(*CompositeValue) - interpreter.activations.PushNewWithParent(inter.activations.CurrentOrNew()) - interpreter.SharedState.callStack.Push(invocation) - invocation.Arguments = interpreter.evaluateDefaultDestroyEvent( + invocation.Arguments = declarationInterpreter.evaluateDefaultDestroyEvent( containerComposite, compositeDecl, + invocation, + invocationInterpreter.activations.CurrentOrNew(), ) } for i, argument := range invocation.Arguments { parameter := compositeType.ConstructorParameters[i] self.SetMember( - inter, + invocationInterpreter, locationRange, parameter.Identifier, argument, @@ -1191,13 +1198,13 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( }, ) } else { - compositeInitializerFunction := interpreter.compositeInitializerFunction(declaration, lexicalScope) + compositeInitializerFunction := declarationInterpreter.compositeInitializerFunction(declaration, lexicalScope) if compositeInitializerFunction != nil { initializerFunction = compositeInitializerFunction } } - functions := interpreter.compositeFunctions(declaration, lexicalScope) + functions := declarationInterpreter.compositeFunctions(declaration, lexicalScope) if destroyEventConstructor != nil { functions.Set(resourceDefaultDestroyEventName(compositeType), destroyEventConstructor) @@ -1251,24 +1258,24 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( conformances := compositeType.EffectiveInterfaceConformances() for i := len(conformances) - 1; i >= 0; i-- { conformance := conformances[i].InterfaceType - wrapFunctions(conformance, interpreter.SharedState.typeCodes.InterfaceCodes[conformance.ID()]) + wrapFunctions(conformance, declarationInterpreter.SharedState.typeCodes.InterfaceCodes[conformance.ID()]) } - interpreter.SharedState.typeCodes.CompositeCodes[compositeType.ID()] = CompositeTypeCode{ + declarationInterpreter.SharedState.typeCodes.CompositeCodes[compositeType.ID()] = CompositeTypeCode{ CompositeFunctions: functions, } - location := interpreter.Location + location := declarationInterpreter.Location qualifiedIdentifier := compositeType.QualifiedIdentifier() - config := interpreter.SharedState.Config + config := declarationInterpreter.SharedState.Config constructorType := compositeType.ConstructorFunctionType() constructorGenerator := func(address common.Address) *HostFunctionValue { return NewHostFunctionValue( - interpreter, + declarationInterpreter, constructorType, func(invocation Invocation) Value { @@ -1395,10 +1402,10 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( if declaration.Kind() == common.CompositeKindContract { variable.getter = func() Value { - positioned := ast.NewRangeFromPositioned(interpreter, declaration.DeclarationIdentifier()) + positioned := ast.NewRangeFromPositioned(declarationInterpreter, declaration.DeclarationIdentifier()) contractValue := config.ContractValueHandler( - interpreter, + declarationInterpreter, compositeType, constructorGenerator, positioned, From d8abdd4d778f2b6dfa4ddd1562590035e99891a3 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Wed, 1 Nov 2023 17:38:09 -0400 Subject: [PATCH 42/48] proper lexical scoping and testing --- runtime/interpreter/interpreter.go | 23 ++++------- runtime/tests/interpreter/resources_test.go | 42 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 4ee5689bd0..3e3f76dbf9 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -991,23 +991,11 @@ func (interpreter *Interpreter) declareAttachmentValue( func (declarationInterpreter *Interpreter) evaluateDefaultDestroyEvent( containingResourceComposite *CompositeValue, eventDecl *ast.CompositeDeclaration, - invocation Invocation, - invocationActivation *VariableActivation, + declarationActivation *VariableActivation, ) (arguments []Value) { parameters := eventDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters - declarationInterpreter.activations.PushNewWithParent(invocationActivation) - declarationInterpreter.SharedState.callStack.Push(invocation) - - // interpreter.activations.PushNewWithParent(inter.activations.CurrentOrNew()) - // interpreter.SharedState.callStack.Push(invocation) - defer func() { - // Only unwind the call stack if there was no error - if r := recover(); r != nil { - panic(r) - } - declarationInterpreter.SharedState.callStack.Pop() - }() + declarationInterpreter.activations.Push(declarationActivation) defer declarationInterpreter.activations.Pop() var self MemberAccessibleValue = containingResourceComposite @@ -1158,6 +1146,8 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( initializerType := compositeType.InitializerFunctionType() + declarationActivation := declarationInterpreter.activations.CurrentOrNew() + var initializerFunction FunctionValue if declaration.Kind() == common.CompositeKindEvent { initializerFunction = NewHostFunctionValue( @@ -1180,8 +1170,9 @@ func (declarationInterpreter *Interpreter) declareNonEnumCompositeValue( invocation.Arguments = declarationInterpreter.evaluateDefaultDestroyEvent( containerComposite, compositeDecl, - invocation, - invocationInterpreter.activations.CurrentOrNew(), + // to properly lexically scope the evaluation of default arguments, we capture the + // activations existing at the time when the event was defined and use them here + declarationActivation, ) } diff --git a/runtime/tests/interpreter/resources_test.go b/runtime/tests/interpreter/resources_test.go index 4c8f749a2a..bdd37b0d76 100644 --- a/runtime/tests/interpreter/resources_test.go +++ b/runtime/tests/interpreter/resources_test.go @@ -2459,3 +2459,45 @@ func TestInterpretResourceInterfaceDefaultDestroyEventNoCompositeEvent(t *testin require.Equal(t, events[0].QualifiedIdentifier, "I.ResourceDestroyed") require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) } + +func TestInterpretDefaultDestroyEventArgumentScoping(t *testing.T) { + + t.Parallel() + + var events []*interpreter.CompositeValue + + inter, err := parseCheckAndInterpretWithOptions(t, ` + let x = 1 + + resource R { + event ResourceDestroyed(x: Int = x) + } + + fun test() { + let x = 2 + let r <- create R() + // should emit R.ResourceDestroyed(x: 1), not R.ResourceDestroyed(x: 2) + destroy r + } + `, ParseCheckAndInterpretOptions{ + Config: &interpreter.Config{ + OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + events = append(events, event) + return nil + }, + }, + HandleCheckerError: func(err error) { + errs := checker.RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + // ... + }, + }) + + require.NoError(t, err) + _, err = inter.Invoke("test") + require.NoError(t, err) + + require.Len(t, events, 1) + require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "x"), interpreter.NewIntValueFromInt64(nil, 1)) +} From 920ea9428fa9c3e55305e5faf7ff1f48220033b6 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Thu, 2 Nov 2023 11:37:34 -0400 Subject: [PATCH 43/48] use new activation for default event param evaluation --- runtime/interpreter/interpreter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 3e3f76dbf9..4813f54ee1 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -995,7 +995,7 @@ func (declarationInterpreter *Interpreter) evaluateDefaultDestroyEvent( ) (arguments []Value) { parameters := eventDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters - declarationInterpreter.activations.Push(declarationActivation) + declarationInterpreter.activations.PushNewWithParent(declarationActivation) defer declarationInterpreter.activations.Pop() var self MemberAccessibleValue = containingResourceComposite From 89fbe55d33ab2d6dead499d76c4a622113da6f81 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 7 Nov 2023 13:58:01 -0500 Subject: [PATCH 44/48] style --- runtime/interpreter/interpreter.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 4813f54ee1..2b2c471bac 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -988,11 +988,13 @@ func (interpreter *Interpreter) declareAttachmentValue( // and c) functions cannot currently be explicitly invoked if they have default arguments // // if we plan to generalize this further, we will need to relax those assumptions -func (declarationInterpreter *Interpreter) evaluateDefaultDestroyEvent( +func (interpreter *Interpreter) evaluateDefaultDestroyEvent( containingResourceComposite *CompositeValue, eventDecl *ast.CompositeDeclaration, declarationActivation *VariableActivation, ) (arguments []Value) { + + declarationInterpreter := interpreter parameters := eventDecl.DeclarationMembers().Initializers()[0].FunctionDeclaration.ParameterList.Parameters declarationInterpreter.activations.PushNewWithParent(declarationActivation) From 4045c172bef9dffa176d0300de08d59bbefcaaca Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 7 Nov 2023 14:40:15 -0500 Subject: [PATCH 45/48] Update runtime/parser/declaration_test.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bastian Müller --- runtime/parser/declaration_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/parser/declaration_test.go b/runtime/parser/declaration_test.go index 2aac352169..7f9795c4af 100644 --- a/runtime/parser/declaration_test.go +++ b/runtime/parser/declaration_test.go @@ -388,7 +388,9 @@ func TestParseParameterList(t *testing.T) { return Parse( nil, []byte(input), - func(p *parser) (*ast.ParameterList, error) { return parseParameterList(p, false) }, + func(p *parser) (*ast.ParameterList, error) { + return parseParameterList(p, false) + }, Config{}, ) } From 13786b0de28078b481b188930cb3389a63acf263 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 7 Nov 2023 15:03:36 -0500 Subject: [PATCH 46/48] review comments --- runtime/interpreter/value.go | 4 +-- runtime/resourcedictionary_test.go | 3 +- runtime/sema/check_composite_declaration.go | 14 +++++++- runtime/sema/check_interface_declaration.go | 5 ++- runtime/tests/interpreter/interpreter_test.go | 36 ++++++++++--------- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index 3d95749008..44fc306c42 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -19315,9 +19315,7 @@ func (NilValue) IsDestroyed() bool { return false } -func (v NilValue) Destroy(interpreter *Interpreter, _ LocationRange) { - -} +func (v NilValue) Destroy(_ *Interpreter, _ LocationRange) {} func (NilValue) String() string { return format.Nil diff --git a/runtime/resourcedictionary_test.go b/runtime/resourcedictionary_test.go index 75a61fb41b..5cd07c4c3e 100644 --- a/runtime/resourcedictionary_test.go +++ b/runtime/resourcedictionary_test.go @@ -984,8 +984,7 @@ func TestRuntimeResourceDictionaryValues_Destruction(t *testing.T) { require.Equal(t, events[2].EventType.ID(), "A.0000000000000001.Test.R.ResourceDestroyed") // one of the two needs to be 1, the other needs to be 2 require.True(t, - (events[1].Fields[0].String() == "1" && events[2].Fields[0].String() == "2") || - (events[2].Fields[0].String() == "1" && events[1].Fields[0].String() == "2"), + (events[2].Fields[0].String() == "1" && events[1].Fields[0].String() == "2"), ) } diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 5c96e7fa8e..8267d69b88 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -848,7 +848,10 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( checker.Elaboration.SetDefaultDestroyDeclaration(declaration, nestedCompositeDeclaration) defaultEventType := checker.typeActivations.Find(identifier.Identifier) - defaultEventComposite := defaultEventType.Type.(*CompositeType) + defaultEventComposite, ok := defaultEventType.Type.(*CompositeType) + if !ok { + panic(errors.NewUnreachableError()) + } compositeType.DefaultDestroyEvent = defaultEventComposite } @@ -2006,8 +2009,11 @@ func (checker *Checker) checkDefaultDestroyParamExpressionKind( *ast.IntegerExpression, *ast.FixedPointExpression, *ast.PathExpression: + break + case *ast.IdentifierExpression: + identifier := arg.Identifier.Identifier // these are guaranteed to exist at time of destruction, so we allow them if identifier == SelfIdentifier || identifier == BaseIdentifier { @@ -2020,9 +2026,13 @@ func (checker *Checker) checkDefaultDestroyParamExpressionKind( checker.report(&DefaultDestroyInvalidArgumentError{ Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), }) + case *ast.MemberExpression: + checker.checkDefaultDestroyParamExpressionKind(arg.Expression) + case *ast.IndexExpression: + checker.checkDefaultDestroyParamExpressionKind(arg.TargetExpression) checker.checkDefaultDestroyParamExpressionKind(arg.IndexingExpression) @@ -2048,9 +2058,11 @@ func (checker *Checker) checkDefaultDestroyParamExpressionKind( }) default: + checker.report(&DefaultDestroyInvalidArgumentError{ Range: ast.NewRangeFromPositioned(checker.memoryGauge, arg), }) + } } diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index d6d5bb89ba..f41e718e78 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -449,7 +449,10 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa // Find the value declaration nestedEvent := checker.typeActivations.Find(nestedCompositeDeclaration.Identifier.Identifier) - defaultEventComposite := nestedEvent.Type.(*CompositeType) + defaultEventComposite, ok := nestedEvent.Type.(*CompositeType) + if !ok { + panic(errors.NewUnreachableError()) + } interfaceType.DefaultDestroyEvent = defaultEventComposite } else { checker.declareNestedEvent(nestedCompositeDeclaration, eventMembers, interfaceType) diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 52ef9ad669..43acc35179 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -6594,7 +6594,7 @@ func TestInterpretResourceMoveInArrayAndDestroy(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -6643,7 +6643,7 @@ func TestInterpretResourceMoveInDictionaryAndDestroy(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -6905,7 +6905,7 @@ func TestInterpretResourceDestroyExpressionDestructor(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -6954,7 +6954,7 @@ func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -6989,7 +6989,7 @@ func TestInterpretResourceDestroyArray(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -7022,7 +7022,7 @@ func TestInterpretResourceDestroyDictionary(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -7055,7 +7055,7 @@ func TestInterpretResourceDestroyOptionalSome(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -7087,7 +7087,7 @@ func TestInterpretResourceDestroyOptionalNil(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -9571,7 +9571,7 @@ func TestInterpretNestedDestroy(t *testing.T) { } `, ParseCheckAndInterpretOptions{ Config: &interpreter.Config{ - OnEventEmitted: func(inter *interpreter.Interpreter, locationRange interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { + OnEventEmitted: func(_ *interpreter.Interpreter, _ interpreter.LocationRange, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { events = append(events, event) return nil }, @@ -11724,17 +11724,12 @@ func TestInterpretSwapDictionaryKeysWithSideEffects(t *testing.T) { t.Run("resources", func(t *testing.T) { t.Parallel() - inter, _, err := parseCheckAndInterpretWithLogs(t, ` + inter, getEvents, err := parseCheckAndInterpretWithEvents(t, ` resource Resource { + event ResourceDestroyed(value: Int = self.value) var value: Int init(_ value: Int) { - log( - "Creating resource with UUID " - .concat(self.uuid.toString()) - .concat(" and value ") - .concat(value.toString()) - ) self.value = value } } @@ -11781,6 +11776,13 @@ func TestInterpretSwapDictionaryKeysWithSideEffects(t *testing.T) { _, err = inter.Invoke("test") require.NoError(t, err) - // DestructorTODO: replace with test for destruction event + events := getEvents() + require.Len(t, events, 3) + require.Equal(t, events[0].event.QualifiedIdentifier, "Resource.ResourceDestroyed") + require.Equal(t, events[0].event.GetField(inter, interpreter.EmptyLocationRange, "value"), interpreter.NewIntValueFromInt64(nil, 2)) + require.Equal(t, events[1].event.QualifiedIdentifier, "Resource.ResourceDestroyed") + require.Equal(t, events[1].event.GetField(inter, interpreter.EmptyLocationRange, "value"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, events[2].event.QualifiedIdentifier, "Resource.ResourceDestroyed") + require.Equal(t, events[2].event.GetField(inter, interpreter.EmptyLocationRange, "value"), interpreter.NewIntValueFromInt64(nil, 3)) }) } From 3dd41d5d59a682eaf6c0c3decee8018ad6fa9b28 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 7 Nov 2023 16:54:25 -0500 Subject: [PATCH 47/48] review comments --- runtime/resourcedictionary_test.go | 18 +++++++------- runtime/runtime_test.go | 38 +++++++++++++++--------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/runtime/resourcedictionary_test.go b/runtime/resourcedictionary_test.go index 5cd07c4c3e..ed463df2a5 100644 --- a/runtime/resourcedictionary_test.go +++ b/runtime/resourcedictionary_test.go @@ -278,7 +278,7 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { loggedMessages, ) require.Len(t, events, 1) - require.Equal(t, events[0].String(), "A.000000000000cade.Test.R.ResourceDestroyed(value: 3)") + require.Equal(t, "A.000000000000cade.Test.R.ResourceDestroyed(value: 3)", events[0].String()) // Remove the key @@ -318,7 +318,7 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { loggedMessages, ) require.Len(t, events, 1) - require.Equal(t, events[0].String(), "A.000000000000cade.Test.R.ResourceDestroyed(value: 4)") + require.Equal(t, "A.000000000000cade.Test.R.ResourceDestroyed(value: 4)", events[0].String()) // Read the deleted key @@ -378,7 +378,7 @@ func TestRuntimeResourceDictionaryValues(t *testing.T) { loggedMessages, ) require.Len(t, events, 1) - require.Equal(t, events[0].String(), "A.000000000000cade.Test.R.ResourceDestroyed(value: 1)") + require.Equal(t, "A.000000000000cade.Test.R.ResourceDestroyed(value: 1)", events[0].String()) } @@ -979,13 +979,11 @@ func TestRuntimeResourceDictionaryValues_Destruction(t *testing.T) { require.NoError(t, err) require.Len(t, events, 3) - require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[1].EventType.ID(), "A.0000000000000001.Test.R.ResourceDestroyed") - require.Equal(t, events[2].EventType.ID(), "A.0000000000000001.Test.R.ResourceDestroyed") - // one of the two needs to be 1, the other needs to be 2 - require.True(t, - (events[2].Fields[0].String() == "1" && events[1].Fields[0].String() == "2"), - ) + require.Equal(t, "flow.AccountContractAdded", events[0].EventType.ID()) + require.Equal(t, "A.0000000000000001.Test.R.ResourceDestroyed", events[1].EventType.ID()) + require.Equal(t, "A.0000000000000001.Test.R.ResourceDestroyed", events[2].EventType.ID()) + require.Equal(t, "1", events[2].Fields[0].String()) + require.Equal(t, "2", events[1].Fields[0].String()) } func TestRuntimeResourceDictionaryValues_Insertion(t *testing.T) { diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 9bf6840024..b99a3284ba 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -3215,8 +3215,8 @@ func TestRuntimeStorageLoadedDestructionConcreteType(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[1].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") + require.Equal(t, "flow.AccountContractAdded", events[0].EventType.ID()) + require.Equal(t, "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)", events[1].String()) } func TestRuntimeStorageLoadedDestructionConcreteTypeWithAttachment(t *testing.T) { @@ -3343,10 +3343,10 @@ func TestRuntimeStorageLoadedDestructionConcreteTypeWithAttachment(t *testing.T) require.NoError(t, err) require.Len(t, events, 4) - require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[1].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[2].String(), "A.0000000000000001.TestAttach.A.ResourceDestroyed(foo: 6)") - require.Equal(t, events[3].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") + require.Equal(t, "flow.AccountContractAdded", events[0].EventType.ID()) + require.Equal(t, "flow.AccountContractAdded", events[1].EventType.ID()) + require.Equal(t, "A.0000000000000001.TestAttach.A.ResourceDestroyed(foo: 6)", events[2].String()) + require.Equal(t, "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)", events[3].String()) } func TestRuntimeStorageLoadedDestructionConcreteTypeWithAttachmentUnloadedContract(t *testing.T) { @@ -3476,11 +3476,11 @@ func TestRuntimeStorageLoadedDestructionConcreteTypeWithAttachmentUnloadedContra require.NoError(t, err) require.Len(t, events, 5) - require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[1].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[2].String(), "A.0000000000000001.TestAttach.A.ResourceDestroyed(foo: 6)") - require.Equal(t, events[3].String(), "A.0000000000000001.TestAttach.I.ResourceDestroyed(foo: 6)") - require.Equal(t, events[4].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") + require.Equal(t, "flow.AccountContractAdded", events[0].EventType.ID()) + require.Equal(t, "flow.AccountContractAdded", events[1].EventType.ID()) + require.Equal(t, "A.0000000000000001.TestAttach.A.ResourceDestroyed(foo: 6)", events[2].String()) + require.Equal(t, "A.0000000000000001.TestAttach.I.ResourceDestroyed(foo: 6)", events[3].String()) + require.Equal(t, "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)", events[4].String()) } func TestRuntimeStorageLoadedDestructionConcreteTypeSameNamedInterface(t *testing.T) { @@ -3628,12 +3628,12 @@ func TestRuntimeStorageLoadedDestructionConcreteTypeSameNamedInterface(t *testin require.NoError(t, err) require.Len(t, events, 6) - require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[1].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[2].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[3].String(), "A.0000000000000001.TestInterface1.I.ResourceDestroyed(foo: 6)") - require.Equal(t, events[4].String(), "A.0000000000000001.TestInterface2.I.ResourceDestroyed(foo: 6)") - require.Equal(t, events[5].String(), "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)") + require.Equal(t, "flow.AccountContractAdded", events[0].EventType.ID()) + require.Equal(t, "flow.AccountContractAdded", events[1].EventType.ID()) + require.Equal(t, "flow.AccountContractAdded", events[2].EventType.ID()) + require.Equal(t, "A.0000000000000001.TestInterface1.I.ResourceDestroyed(foo: 6)", events[3].String()) + require.Equal(t, "A.0000000000000001.TestInterface2.I.ResourceDestroyed(foo: 6)", events[4].String()) + require.Equal(t, "A.0000000000000001.Test.R.ResourceDestroyed(foo: 6)", events[5].String()) } func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { @@ -3726,8 +3726,8 @@ func TestRuntimeStorageLoadedDestructionAnyResource(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].EventType.ID(), "flow.AccountContractAdded") - require.Equal(t, events[1].String(), "A.0000000000000001.Test.R.ResourceDestroyed()") + require.Equal(t, "flow.AccountContractAdded", events[0].EventType.ID()) + require.Equal(t, "A.0000000000000001.Test.R.ResourceDestroyed()", events[1].String()) } func TestRuntimeStorageLoadedDestructionAfterRemoval(t *testing.T) { From 481826f4d8cc6db7a6f2b22ed94a18c86bc69cf8 Mon Sep 17 00:00:00 2001 From: Daniel Sainati Date: Tue, 7 Nov 2023 17:24:09 -0500 Subject: [PATCH 48/48] more argument orders --- runtime/tests/checker/events_test.go | 4 +- runtime/tests/interpreter/attachments_test.go | 32 ++++----- runtime/tests/interpreter/interpreter_test.go | 66 +++++++++---------- runtime/tests/interpreter/resources_test.go | 48 +++++++------- 4 files changed, 75 insertions(+), 75 deletions(-) diff --git a/runtime/tests/checker/events_test.go b/runtime/tests/checker/events_test.go index 1a303927f2..86d1b097fc 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -501,7 +501,7 @@ func TestCheckDefaultEventDeclaration(t *testing.T) { require.True(t, exists) require.IsType(t, variable.Type, &sema.CompositeType{}) - require.Equal(t, variable.Type.(*sema.CompositeType).DefaultDestroyEvent.Identifier, "ResourceDestroyed") + require.Equal(t, "ResourceDestroyed", variable.Type.(*sema.CompositeType).DefaultDestroyEvent.Identifier) }) t.Run("allowed in resource interface", func(t *testing.T) { @@ -519,7 +519,7 @@ func TestCheckDefaultEventDeclaration(t *testing.T) { require.True(t, exists) require.IsType(t, variable.Type, &sema.InterfaceType{}) - require.Equal(t, variable.Type.(*sema.InterfaceType).DefaultDestroyEvent.Identifier, "ResourceDestroyed") + require.Equal(t, "ResourceDestroyed", variable.Type.(*sema.InterfaceType).DefaultDestroyEvent.Identifier) }) t.Run("fail in struct", func(t *testing.T) { diff --git a/runtime/tests/interpreter/attachments_test.go b/runtime/tests/interpreter/attachments_test.go index f269a4ffd6..678634da7e 100644 --- a/runtime/tests/interpreter/attachments_test.go +++ b/runtime/tests/interpreter/attachments_test.go @@ -1276,8 +1276,8 @@ func TestInterpretAttachmentDestructor(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].QualifiedIdentifier, "A.ResourceDestroyed") - require.Equal(t, events[1].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, "A.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, "R.ResourceDestroyed", events[1].QualifiedIdentifier) }) t.Run("base destructor executed last", func(t *testing.T) { @@ -1327,10 +1327,10 @@ func TestInterpretAttachmentDestructor(t *testing.T) { require.Len(t, events, 4) // the only part of this order that is important is that `R` is last - require.Equal(t, events[0].QualifiedIdentifier, "B.ResourceDestroyed") - require.Equal(t, events[1].QualifiedIdentifier, "C.ResourceDestroyed") - require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") - require.Equal(t, events[3].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, "B.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, "C.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, "A.ResourceDestroyed", events[2].QualifiedIdentifier) + require.Equal(t, "R.ResourceDestroyed", events[3].QualifiedIdentifier) }) t.Run("remove runs destroy", func(t *testing.T) { @@ -1372,7 +1372,7 @@ func TestInterpretAttachmentDestructor(t *testing.T) { require.NoError(t, err) require.Len(t, events, 1) - require.Equal(t, events[0].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, "A.ResourceDestroyed", events[0].QualifiedIdentifier) }) t.Run("remove runs resource field destroy", func(t *testing.T) { @@ -1421,8 +1421,8 @@ func TestInterpretAttachmentDestructor(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].QualifiedIdentifier, "R2.ResourceDestroyed") - require.Equal(t, events[1].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, "R2.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, "A.ResourceDestroyed", events[1].QualifiedIdentifier) }) t.Run("nested attachments destroyed", func(t *testing.T) { @@ -1474,9 +1474,9 @@ func TestInterpretAttachmentDestructor(t *testing.T) { require.NoError(t, err) require.Len(t, events, 3) - require.Equal(t, events[0].QualifiedIdentifier, "B.ResourceDestroyed") - require.Equal(t, events[1].QualifiedIdentifier, "R2.ResourceDestroyed") - require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") + require.Equal(t, "B.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, "R2.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, "A.ResourceDestroyed", events[2].QualifiedIdentifier) }) t.Run("attachment default args properly scoped", func(t *testing.T) { @@ -1535,10 +1535,10 @@ func TestInterpretAttachmentDestructor(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].QualifiedIdentifier, "A.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "foo"), interpreter.NewUnmeteredStringValue("foo")) - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 2)) - require.Equal(t, events[1].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, "A.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewUnmeteredStringValue("foo"), events[0].GetField(inter, interpreter.EmptyLocationRange, "foo")) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 2), events[0].GetField(inter, interpreter.EmptyLocationRange, "bar")) + require.Equal(t, "R.ResourceDestroyed", events[1].QualifiedIdentifier) }) } diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 43acc35179..31a1e05804 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -6613,10 +6613,10 @@ func TestInterpretResourceMoveInArrayAndDestroy(t *testing.T) { ) require.Len(t, events, 2) - require.Equal(t, events[0].QualifiedIdentifier, "Foo.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[1].QualifiedIdentifier, "Foo.ResourceDestroyed") - require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 2)) + require.Equal(t, "Foo.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[0].GetField(inter, interpreter.EmptyLocationRange, "bar")) + require.Equal(t, "Foo.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 2), events[1].GetField(inter, interpreter.EmptyLocationRange, "bar")) } func TestInterpretResourceMoveInDictionaryAndDestroy(t *testing.T) { @@ -6655,10 +6655,10 @@ func TestInterpretResourceMoveInDictionaryAndDestroy(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].QualifiedIdentifier, "Foo.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[1].QualifiedIdentifier, "Foo.ResourceDestroyed") - require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "bar"), interpreter.NewIntValueFromInt64(nil, 2)) + require.Equal(t, "Foo.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[0].GetField(inter, interpreter.EmptyLocationRange, "bar")) + require.Equal(t, "Foo.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 2), events[1].GetField(inter, interpreter.EmptyLocationRange, "bar")) } func TestInterpretClosure(t *testing.T) { @@ -6918,7 +6918,7 @@ func TestInterpretResourceDestroyExpressionDestructor(t *testing.T) { require.NoError(t, err) require.Len(t, events, 1) - require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, "R.ResourceDestroyed", events[0].QualifiedIdentifier) } func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { @@ -6966,10 +6966,10 @@ func TestInterpretResourceDestroyExpressionNestedResources(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].QualifiedIdentifier, "B.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "foo"), interpreter.NewIntValueFromInt64(nil, 5)) - require.Equal(t, events[1].QualifiedIdentifier, "A.ResourceDestroyed") - require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "foo"), interpreter.NewIntValueFromInt64(nil, 5)) + require.Equal(t, "B.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 5), events[0].GetField(inter, interpreter.EmptyLocationRange, "foo")) + require.Equal(t, "A.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 5), events[1].GetField(inter, interpreter.EmptyLocationRange, "foo")) } func TestInterpretResourceDestroyArray(t *testing.T) { @@ -7001,8 +7001,8 @@ func TestInterpretResourceDestroyArray(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") - require.Equal(t, events[1].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, "R.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, "R.ResourceDestroyed", events[1].QualifiedIdentifier) } func TestInterpretResourceDestroyDictionary(t *testing.T) { @@ -7034,8 +7034,8 @@ func TestInterpretResourceDestroyDictionary(t *testing.T) { require.NoError(t, err) require.Len(t, events, 2) - require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") - require.Equal(t, events[1].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, "R.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, "R.ResourceDestroyed", events[1].QualifiedIdentifier) } func TestInterpretResourceDestroyOptionalSome(t *testing.T) { @@ -7067,7 +7067,7 @@ func TestInterpretResourceDestroyOptionalSome(t *testing.T) { require.NoError(t, err) require.Len(t, events, 1) - require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") + require.Equal(t, "R.ResourceDestroyed", events[0].QualifiedIdentifier) } func TestInterpretResourceDestroyOptionalNil(t *testing.T) { @@ -9583,15 +9583,15 @@ func TestInterpretNestedDestroy(t *testing.T) { require.NoError(t, err) require.Len(t, events, 4) - require.Equal(t, events[0].QualifiedIdentifier, "B.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 2)) - require.Equal(t, events[1].QualifiedIdentifier, "B.ResourceDestroyed") - require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 3)) - require.Equal(t, events[2].QualifiedIdentifier, "B.ResourceDestroyed") - require.Equal(t, events[2].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 4)) - require.Equal(t, events[3].QualifiedIdentifier, "A.ResourceDestroyed") - require.Equal(t, events[3].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[3].GetField(inter, interpreter.EmptyLocationRange, "bCount"), interpreter.NewIntValueFromInt64(nil, 3)) + require.Equal(t, "B.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 2), events[0].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "B.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 3), events[1].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "B.ResourceDestroyed", events[2].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 4), events[2].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "A.ResourceDestroyed", events[3].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[3].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 3), events[3].GetField(inter, interpreter.EmptyLocationRange, "bCount")) AssertValuesEqual( t, @@ -11778,11 +11778,11 @@ func TestInterpretSwapDictionaryKeysWithSideEffects(t *testing.T) { events := getEvents() require.Len(t, events, 3) - require.Equal(t, events[0].event.QualifiedIdentifier, "Resource.ResourceDestroyed") - require.Equal(t, events[0].event.GetField(inter, interpreter.EmptyLocationRange, "value"), interpreter.NewIntValueFromInt64(nil, 2)) - require.Equal(t, events[1].event.QualifiedIdentifier, "Resource.ResourceDestroyed") - require.Equal(t, events[1].event.GetField(inter, interpreter.EmptyLocationRange, "value"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[2].event.QualifiedIdentifier, "Resource.ResourceDestroyed") - require.Equal(t, events[2].event.GetField(inter, interpreter.EmptyLocationRange, "value"), interpreter.NewIntValueFromInt64(nil, 3)) + require.Equal(t, "Resource.ResourceDestroyed", events[0].event.QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 2), events[0].event.GetField(inter, interpreter.EmptyLocationRange, "value")) + require.Equal(t, "Resource.ResourceDestroyed", events[1].event.QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[1].event.GetField(inter, interpreter.EmptyLocationRange, "value")) + require.Equal(t, "Resource.ResourceDestroyed", events[2].event.QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 3), events[2].event.GetField(inter, interpreter.EmptyLocationRange, "value")) }) } diff --git a/runtime/tests/interpreter/resources_test.go b/runtime/tests/interpreter/resources_test.go index bdd37b0d76..a4a42acab0 100644 --- a/runtime/tests/interpreter/resources_test.go +++ b/runtime/tests/interpreter/resources_test.go @@ -2298,14 +2298,14 @@ func TestInterpretResourceInterfaceDefaultDestroyEvent(t *testing.T) { require.NoError(t, err) require.Len(t, events, 4) - require.Equal(t, events[0].QualifiedIdentifier, "I.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[1].QualifiedIdentifier, "A.ResourceDestroyed") - require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[2].QualifiedIdentifier, "I.ResourceDestroyed") - require.Equal(t, events[2].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 2)) - require.Equal(t, events[3].QualifiedIdentifier, "B.ResourceDestroyed") - require.Equal(t, events[3].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 2)) + require.Equal(t, "I.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[0].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "A.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[1].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "I.ResourceDestroyed", events[2].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 2), events[2].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "B.ResourceDestroyed", events[3].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 2), events[3].GetField(inter, interpreter.EmptyLocationRange, "id")) } func TestInterpretResourceInterfaceDefaultDestroyEventMultipleInheritance(t *testing.T) { @@ -2353,12 +2353,12 @@ func TestInterpretResourceInterfaceDefaultDestroyEventMultipleInheritance(t *tes require.NoError(t, err) require.Len(t, events, 3) - require.Equal(t, events[0].QualifiedIdentifier, "I.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[1].QualifiedIdentifier, "J.ResourceDestroyed") - require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") - require.Equal(t, events[2].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, "I.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[0].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "J.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[1].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "A.ResourceDestroyed", events[2].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[2].GetField(inter, interpreter.EmptyLocationRange, "id")) } func TestInterpretResourceInterfaceDefaultDestroyEventIndirectInheritance(t *testing.T) { @@ -2406,12 +2406,12 @@ func TestInterpretResourceInterfaceDefaultDestroyEventIndirectInheritance(t *tes require.NoError(t, err) require.Len(t, events, 3) - require.Equal(t, events[0].QualifiedIdentifier, "J.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[1].QualifiedIdentifier, "I.ResourceDestroyed") - require.Equal(t, events[1].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) - require.Equal(t, events[2].QualifiedIdentifier, "A.ResourceDestroyed") - require.Equal(t, events[2].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, "J.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[0].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "I.ResourceDestroyed", events[1].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[1].GetField(inter, interpreter.EmptyLocationRange, "id")) + require.Equal(t, "A.ResourceDestroyed", events[2].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[2].GetField(inter, interpreter.EmptyLocationRange, "id")) } func TestInterpretResourceInterfaceDefaultDestroyEventNoCompositeEvent(t *testing.T) { @@ -2456,8 +2456,8 @@ func TestInterpretResourceInterfaceDefaultDestroyEventNoCompositeEvent(t *testin require.NoError(t, err) require.Len(t, events, 1) - require.Equal(t, events[0].QualifiedIdentifier, "I.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "id"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, "I.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[0].GetField(inter, interpreter.EmptyLocationRange, "id")) } func TestInterpretDefaultDestroyEventArgumentScoping(t *testing.T) { @@ -2498,6 +2498,6 @@ func TestInterpretDefaultDestroyEventArgumentScoping(t *testing.T) { require.NoError(t, err) require.Len(t, events, 1) - require.Equal(t, events[0].QualifiedIdentifier, "R.ResourceDestroyed") - require.Equal(t, events[0].GetField(inter, interpreter.EmptyLocationRange, "x"), interpreter.NewIntValueFromInt64(nil, 1)) + require.Equal(t, "R.ResourceDestroyed", events[0].QualifiedIdentifier) + require.Equal(t, interpreter.NewIntValueFromInt64(nil, 1), events[0].GetField(inter, interpreter.EmptyLocationRange, "x")) }