Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type checking for default events #2812

Merged
merged 18 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 130 additions & 4 deletions runtime/sema/check_composite_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -283,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)
}
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
ast.AcceptDeclaration[struct{}](nestedComposite, checker)
}

Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -819,6 +843,15 @@ func (checker *Checker) declareCompositeLikeMembersAndValue(
nestedCompositeDeclarationVariable :=
checker.valueActivations.Find(identifier.Identifier)

if identifier.Identifier == ast.ResourceDestructionDefaultEventName {
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
// Find the default event's type declaration
defaultEventType :=
checker.typeActivations.Find(identifier.Identifier)
defaultEventComposite := defaultEventType.Type.(*CompositeType)
compositeType.DefaultDestroyEvent = defaultEventComposite
turbolent marked this conversation as resolved.
Show resolved Hide resolved
return
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
}

declarationMembers.Set(
nestedCompositeDeclarationVariable.Identifier,
&Member{
Expand Down Expand Up @@ -1963,6 +1996,99 @@ func (checker *Checker) enumMembersAndOrigins(
return
}

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.SpecialFunctions()
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
constructorFunctionParameters := functions[0].FunctionDeclaration.ParameterList.Parameters

var checkParamExpressionKind func(ast.Expression)
checkParamExpressionKind = func(arg ast.Expression) {
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
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 {
SupunS marked this conversation as resolved.
Show resolved Hide resolved
break
}
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
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:
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
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)

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())
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
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),
})
}

checkParamExpressionKind(paramDefaultArgument)
}
}

func (checker *Checker) checkInitializers(
initializers []*ast.SpecialFunctionDeclaration,
fields []*ast.FieldDeclaration,
Expand Down
21 changes: 20 additions & 1 deletion runtime/sema/check_interface_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -432,7 +442,16 @@ 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
// interfaceType.DefaultDestroyEvent =
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved
} else {
checker.declareNestedEvent(nestedCompositeDeclaration, eventMembers, interfaceType)
}
}
}
})()
Expand Down
56 changes: 56 additions & 0 deletions runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4657,6 +4657,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 {
Expand Down
11 changes: 8 additions & 3 deletions runtime/sema/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -4296,6 +4297,8 @@ type CompositeType struct {
RequiredEntitlements *EntitlementOrderedSet
AttachmentEntitlementAccess *EntitlementMapAccess

DefaultDestroyEvent *CompositeType

cachedIdentifiers *struct {
TypeID TypeID
QualifiedIdentifier string
Expand Down Expand Up @@ -5152,6 +5155,8 @@ type InterfaceType struct {
effectiveInterfaceConformances []Conformance
effectiveInterfaceConformanceSet *InterfaceSet
supportedEntitlements *EntitlementOrderedSet

DefaultDestroyEvent *CompositeType
}

var _ Type = &InterfaceType{}
Expand Down
Loading