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 b30970cd72..21650417d2 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 @@ -283,6 +290,10 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL } for _, nestedComposite := range members.Composites() { + 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) + } ast.AcceptDeclaration[struct{}](nestedComposite, checker) } @@ -428,10 +439,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 { @@ -819,6 +843,14 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( nestedCompositeDeclarationVariable := checker.valueActivations.Find(identifier.Identifier) + if ast.IsResourceDestructionDefaultEvent(identifier.Identifier) { + // Find the default event's type declaration + defaultEventType := + checker.typeActivations.Find(identifier.Identifier) + defaultEventComposite := defaultEventType.Type.(*CompositeType) + compositeType.DefaultDestroyEvent = defaultEventComposite + } + declarationMembers.Set( nestedCompositeDeclarationVariable.Identifier, &Member{ @@ -1963,6 +1995,120 @@ 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: + identifier := arg.Identifier.Identifier + // these are guaranteed to exist at time of destruction, so we allow them + if identifier == SelfIdentifier || identifier == BaseIdentifier { + break + } + // if it's an attachment, then it's also okay + if checker.typeActivations.Find(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) checkDefaultDestroyEventParam( + param Parameter, + astParam *ast.Parameter, + containerType ContainerType, + containerDeclaration ast.Declaration, +) { + paramType := param.TypeAnnotation.Type + 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()) + 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, astParam), + }) + } + + checker.checkDefaultDestroyParamExpressionKind(paramDefaultArgument) +} + +func (checker *Checker) checkDefaultDestroyEvent( + eventType *CompositeType, + eventDeclaration ast.CompositeLikeDeclaration, + containerType ContainerType, + containerDeclaration ast.Declaration, +) { + + // an event definition always has one "constructor" function in its declaration list + members := eventDeclaration.DeclarationMembers() + functions := members.Initializers() + constructorFunctionParameters := functions[0].FunctionDeclaration.ParameterList.Parameters + + checker.enterValueScope() + defer checker.leaveValueScope(eventDeclaration.EndPosition, true) + + for index, param := range eventType.ConstructorParameters { + checker.checkDefaultDestroyEventParam(param, constructorFunctionParameters[index], containerType, containerDeclaration) + } +} + func (checker *Checker) checkInitializers( initializers []*ast.SpecialFunctionDeclaration, fields []*ast.FieldDeclaration, diff --git a/runtime/sema/check_emit_statement.go b/runtime/sema/check_emit_statement.go index e93126b21c..4d15fc8d5a 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 ast.IsResourceDestructionDefaultEvent(compositeType.Identifier) { + 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/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index 0d967c3f31..a5e1d6b678 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 @@ -151,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 @@ -432,7 +442,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) + defaultEventComposite := nestedEvent.Type.(*CompositeType) + interfaceType.DefaultDestroyEvent = defaultEventComposite + } else { + checker.declareNestedEvent(nestedCompositeDeclaration, eventMembers, interfaceType) + } } } })() diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 4370c8df57..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 { @@ -4657,6 +4674,62 @@ func (e *InvalidAttachmentEntitlementError) EndPosition(common.MemoryGauge) ast. 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, + ) +} + +// 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, member access expressions on `self` or `base`, indexed access expressions on dictionaries, or attachment accesses" +} + +// 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()) +} + // InvalidTypeParameterizedNonNativeFunctionError type InvalidTypeParameterizedNonNativeFunctionError struct { diff --git a/runtime/sema/type.go b/runtime/sema/type.go index cf7be4bf8e..251769e704 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -2954,9 +2954,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 { @@ -4296,6 +4297,8 @@ type CompositeType struct { RequiredEntitlements *EntitlementOrderedSet AttachmentEntitlementAccess *EntitlementMapAccess + DefaultDestroyEvent *CompositeType + cachedIdentifiers *struct { TypeID TypeID QualifiedIdentifier string @@ -5152,6 +5155,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 62d6428648..4b3cda4c77 100644 --- a/runtime/tests/checker/events_test.go +++ b/runtime/tests/checker/events_test.go @@ -481,3 +481,706 @@ func TestCheckDeclareEventInInterface(t *testing.T) { }) } + +func TestCheckDefaultEventDeclaration(t *testing.T) { + + t.Parallel() + + t.Run("empty", func(t *testing.T) { + + t.Parallel() + + 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() + + checker, err := ParseAndCheck(t, ` + resource interface R { + event ResourceDestroyed() + } + `) + require.NoError(t, err) + + 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) { + + 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 interface 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]) + }) + + 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]) + }) + + 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) { + + 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("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("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() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: Address = 0x1) + } + `) + 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() + + _, 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 { + event ResourceDestroyed(name: String = self.field) + + let field : Int + + init() { + self.field = 3 + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + + 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() + + _, 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]) + }) + + 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 r2 <- create R2() + let ref = &r2 as &R2 + + resource R { + event ResourceDestroyed(name: UFix64 = ref.field) + } + + resource R2 { + let field : UFix64 + init() { + self.field = 0.0034 + } + } + `) + errs := RequireCheckerErrors(t, err, 1) + assert.IsType(t, &sema.DefaultDestroyInvalidArgumentError{}, errs[0]) + }) + + 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, ` + 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("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() + + _, 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("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() + + _, err := ParseAndCheck(t, ` + resource R { + event ResourceDestroyed(name: String? = self.get()[0]) + fun get(): {Int: String} { + return {} + } + } + `) + 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, ` + 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 { + let dict : {Int: String} + event ResourceDestroyed(name: String? = self.dict[index]) + + init() { + self.dict = {} + } + } + `) + 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) + }) + + 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) + }) +}