diff --git a/runtime/cmd/cmd.go b/runtime/cmd/cmd.go index bcba6c1a94..5a76ea8bfc 100644 --- a/runtime/cmd/cmd.go +++ b/runtime/cmd/cmd.go @@ -188,9 +188,9 @@ func PrepareInterpreter(filename string) (*interpreter.Interpreter, *sema.Checke inter, err := interpreter.NewInterpreter( checker, interpreter.WithPredefinedValues(valueDeclarations.ToValues()), - interpreter.WithUUIDHandler(func() uint64 { + interpreter.WithUUIDHandler(func() (uint64, error) { defer func() { uuid++ }() - return uuid + return uuid, nil }), ) must(err) diff --git a/runtime/interpreter/errors.go b/runtime/interpreter/errors.go index aec8486424..80aeb84cf2 100644 --- a/runtime/interpreter/errors.go +++ b/runtime/interpreter/errors.go @@ -196,7 +196,7 @@ func (e DestroyedCompositeError) Error() string { } // ForceAssignmentToNonNilResourceError - +// type ForceAssignmentToNonNilResourceError struct { LocationRange } @@ -206,7 +206,7 @@ func (e ForceAssignmentToNonNilResourceError) Error() string { } // ForceNilError - +// type ForceNilError struct { LocationRange } @@ -216,7 +216,7 @@ func (e ForceNilError) Error() string { } // TypeMismatchError - +// type TypeMismatchError struct { ExpectedType sema.Type LocationRange @@ -230,7 +230,7 @@ func (e TypeMismatchError) Error() string { } // InvalidPathDomainError - +// type InvalidPathDomainError struct { ActualDomain common.PathDomain ExpectedDomains []common.PathDomain @@ -257,7 +257,7 @@ func (e InvalidPathDomainError) SecondaryError() string { } // OverwriteError - +// type OverwriteError struct { Address AddressValue Path PathValue @@ -273,7 +273,7 @@ func (e OverwriteError) Error() string { } // CyclicLinkError - +// type CyclicLinkError struct { Address AddressValue Paths []PathValue @@ -298,7 +298,7 @@ func (e CyclicLinkError) Error() string { } // ArrayIndexOutOfBoundsError - +// type ArrayIndexOutOfBoundsError struct { Index int MaxIndex int @@ -312,3 +312,23 @@ func (e ArrayIndexOutOfBoundsError) Error() string { e.MaxIndex, ) } + +// EventEmissionUnavailableError +// +type EventEmissionUnavailableError struct { + LocationRange +} + +func (e EventEmissionUnavailableError) Error() string { + return "cannot emit event: unavailable" +} + +// UUIDUnavailableError +// +type UUIDUnavailableError struct { + LocationRange +} + +func (e UUIDUnavailableError) Error() string { + return "cannot get UUID: unavailable" +} diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index 96da865d33..fe721d24ff 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -78,7 +78,7 @@ type OnEventEmittedFunc func( inter *Interpreter, event *CompositeValue, eventType *sema.CompositeType, -) +) error // OnStatementFunc is a function that is triggered when a statement is about to be executed. // @@ -160,7 +160,7 @@ type ImportLocationHandlerFunc func( ) Import // UUIDHandlerFunc is a function that handles the generation of UUIDs. -type UUIDHandlerFunc func() uint64 +type UUIDHandlerFunc func() (uint64, error) // CompositeTypeCode contains the the "prepared" / "callable" "code" // for the functions and the destructor of a composite @@ -2557,7 +2557,18 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( fields := map[string]Value{} if declaration.CompositeKind == common.CompositeKindResource { - uuid := interpreter.uuidHandler() + + if interpreter.uuidHandler == nil { + panic(UUIDUnavailableError{ + LocationRange: invocation.LocationRange, + }) + } + + uuid, err := interpreter.uuidHandler() + if err != nil { + panic(err) + } + fields[sema.ResourceUUIDFieldName] = UInt64Value(uuid) } @@ -3489,7 +3500,16 @@ func (interpreter *Interpreter) VisitEmitStatement(statement *ast.EmitStatement) eventType := interpreter.Checker.Elaboration.EmitStatementEventTypes[statement] - interpreter.onEventEmitted(interpreter, event, eventType) + if interpreter.onEventEmitted == nil { + panic(EventEmissionUnavailableError{ + LocationRange: interpreter.locationRange(statement), + }) + } + + err := interpreter.onEventEmitted(interpreter, event, eventType) + if err != nil { + panic(err) + } // NOTE: no result, so it does *not* act like a return-statement return Done{} diff --git a/runtime/repl.go b/runtime/repl.go index f0200f6bfb..c464dc68fd 100644 --- a/runtime/repl.go +++ b/runtime/repl.go @@ -69,9 +69,9 @@ func NewREPL(onError func(error), onResult func(interpreter.Value), checkerOptio inter, err := interpreter.NewInterpreter( checker, interpreter.WithPredefinedValues(values), - interpreter.WithUUIDHandler(func() uint64 { + interpreter.WithUUIDHandler(func() (uint64, error) { defer func() { uuid++ }() - return uuid + return uuid, nil }), ) if err != nil { diff --git a/runtime/runtime.go b/runtime/runtime.go index 596d7471e0..84aac01ff4 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -542,44 +542,44 @@ func (r *interpreterRuntime) parseAndCheckProgram( sema.WithPredeclaredValues(valueDeclarations), sema.WithPredeclaredTypes(typeDeclarations), sema.WithValidTopLevelDeclarationsHandler(validTopLevelDeclarations), - sema.WithLocationHandler(func(identifiers []Identifier, location Location) (res []ResolvedLocation) { - var err error - wrapPanic(func() { - res, err = runtimeInterface.ResolveLocation(identifiers, location) - }) - if err != nil { - panic(err) - } - return - }), - sema.WithImportHandler(func(checker *sema.Checker, location ast.Location) (sema.Import, *sema.CheckerError) { - switch location { - case stdlib.CryptoChecker.Location: - return sema.CheckerImport{ - Checker: stdlib.CryptoChecker, - }, nil - - default: - var program *ast.Program - var err error - checker, checkerErr := checker.EnsureLoaded(location, func() *ast.Program { - program, err = importResolver(location) - return program + sema.WithLocationHandler( + func(identifiers []Identifier, location Location) (res []ResolvedLocation, err error) { + wrapPanic(func() { + res, err = runtimeInterface.ResolveLocation(identifiers, location) }) - // TODO: improve - if err != nil { - return nil, &sema.CheckerError{ - Errors: []error{err}, + return + }, + ), + sema.WithImportHandler( + func(checker *sema.Checker, location ast.Location) (sema.Import, *sema.CheckerError) { + switch location { + case stdlib.CryptoChecker.Location: + return sema.CheckerImport{ + Checker: stdlib.CryptoChecker, + }, nil + + default: + var program *ast.Program + var err error + checker, checkerErr := checker.EnsureLoaded(location, func() *ast.Program { + program, err = importResolver(location) + return program + }) + // TODO: improve + if err != nil { + return nil, &sema.CheckerError{ + Errors: []error{err}, + } } + if checkerErr != nil { + return nil, checkerErr + } + return sema.CheckerImport{ + Checker: checker, + }, nil } - if checkerErr != nil { - return nil, checkerErr - } - return sema.CheckerImport{ - Checker: checker, - }, nil - } - }), + }, + ), sema.WithCheckHandler(func(location ast.Location, check func()) { reportMetric( func() { @@ -630,8 +630,8 @@ func (r *interpreterRuntime) newInterpreter( inter *interpreter.Interpreter, eventValue *interpreter.CompositeValue, eventType *sema.CompositeType, - ) { - r.emitEvent(inter, runtimeInterface, eventValue, eventType) + ) error { + return r.emitEvent(inter, runtimeInterface, eventValue, eventType) }, ), interpreter.WithStorageKeyHandler( @@ -642,14 +642,10 @@ func (r *interpreterRuntime) newInterpreter( interpreter.WithInjectedCompositeFieldsHandler( r.injectedCompositeFieldsHandler(runtimeInterface, runtimeStorage), ), - interpreter.WithUUIDHandler(func() (uuid uint64) { - var err error + interpreter.WithUUIDHandler(func() (uuid uint64, err error) { wrapPanic(func() { uuid, err = runtimeInterface.GenerateUUID() }) - if err != nil { - panic(err) - } return }), interpreter.WithContractValueHandler( @@ -923,7 +919,7 @@ func (r *interpreterRuntime) emitEvent( runtimeInterface Interface, event *interpreter.CompositeValue, eventType *sema.CompositeType, -) { +) error { fields := make([]exportableValue, len(eventType.ConstructorParameters)) for i, parameter := range eventType.ConstructorParameters { @@ -940,9 +936,7 @@ func (r *interpreterRuntime) emitEvent( wrapPanic(func() { err = runtimeInterface.EmitEvent(exportedEvent) }) - if err != nil { - panic(err) - } + return err } func (r *interpreterRuntime) emitAccountEvent( diff --git a/runtime/sema/check_import_declaration.go b/runtime/sema/check_import_declaration.go index 4e46c6666d..3bca4230d8 100644 --- a/runtime/sema/check_import_declaration.go +++ b/runtime/sema/check_import_declaration.go @@ -50,7 +50,11 @@ func (checker *Checker) declareImportDeclaration(declaration *ast.ImportDeclarat EndPos: declaration.LocationPos, } - resolvedLocations := checker.resolveLocation(declaration.Identifiers, declaration.Location) + resolvedLocations, err := checker.resolveLocation(declaration.Identifiers, declaration.Location) + if err != nil { + checker.report(err) + return nil + } checker.Elaboration.ImportDeclarationsResolvedLocations[declaration] = resolvedLocations @@ -61,7 +65,7 @@ func (checker *Checker) declareImportDeclaration(declaration *ast.ImportDeclarat return nil } -func (checker *Checker) resolveLocation(identifiers []ast.Identifier, location ast.Location) []ResolvedLocation { +func (checker *Checker) resolveLocation(identifiers []ast.Identifier, location ast.Location) ([]ResolvedLocation, error) { // If no location handler is available, // default to resolving to a single location that declares all identifiers @@ -72,12 +76,11 @@ func (checker *Checker) resolveLocation(identifiers []ast.Identifier, location a Location: location, Identifiers: identifiers, }, - } + }, nil } // A location handler is available, // use it to resolve the location / identifiers - return checker.locationHandler(identifiers, location) } diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 6de9a60990..e20a191e26 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -72,7 +72,7 @@ type ResolvedLocation struct { Identifiers []ast.Identifier } -type LocationHandlerFunc func(identifiers []ast.Identifier, location ast.Location) []ResolvedLocation +type LocationHandlerFunc func(identifiers []ast.Identifier, location ast.Location) ([]ResolvedLocation, error) type ImportHandlerFunc func(checker *Checker, location ast.Location) (Import, *CheckerError) diff --git a/runtime/tests/checker/import_test.go b/runtime/tests/checker/import_test.go index f0ca09ee8a..5e38c22ecb 100644 --- a/runtime/tests/checker/import_test.go +++ b/runtime/tests/checker/import_test.go @@ -131,7 +131,7 @@ func TestCheckRepeatedImportResolution(t *testing.T) { ParseAndCheckOptions{ Options: []sema.Option{ sema.WithLocationHandler( - func(identifiers []ast.Identifier, location ast.Location) (result []sema.ResolvedLocation) { + func(identifiers []ast.Identifier, location ast.Location) (result []sema.ResolvedLocation, err error) { for _, identifier := range identifiers { result = append(result, sema.ResolvedLocation{ Location: ast.AddressLocation{ @@ -253,7 +253,7 @@ func TestCheckImportResolutionSplit(t *testing.T) { ParseAndCheckOptions{ Options: []sema.Option{ sema.WithLocationHandler( - func(identifiers []ast.Identifier, location ast.Location) (result []sema.ResolvedLocation) { + func(identifiers []ast.Identifier, location ast.Location) (result []sema.ResolvedLocation, err error) { for _, identifier := range identifiers { result = append(result, sema.ResolvedLocation{ Location: ast.AddressLocation{ diff --git a/runtime/tests/interpreter/import_test.go b/runtime/tests/interpreter/import_test.go index 167f8de3b8..b385dd1e42 100644 --- a/runtime/tests/interpreter/import_test.go +++ b/runtime/tests/interpreter/import_test.go @@ -183,7 +183,7 @@ func TestInterpretImportMultipleProgramsFromLocation(t *testing.T) { checker.ParseAndCheckOptions{ Options: []sema.Option{ sema.WithLocationHandler( - func(identifiers []ast.Identifier, location ast.Location) (result []sema.ResolvedLocation) { + func(identifiers []ast.Identifier, location ast.Location) (result []sema.ResolvedLocation, err error) { require.Equal(t, ast.AddressLocation{ diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 78b1cba4d8..4086b4b2b9 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -77,9 +77,9 @@ func parseCheckAndInterpretWithOptions( interpreterOptions := append( []interpreter.Option{ - interpreter.WithUUIDHandler(func() uint64 { + interpreter.WithUUIDHandler(func() (uint64, error) { uuid++ - return uuid + return uuid, nil }), }, options.Options..., @@ -3714,8 +3714,8 @@ func TestInterpretInitializerWithInterfacePreCondition(t *testing.T) { } } - uuidHandler := interpreter.WithUUIDHandler(func() uint64 { - return 0 + uuidHandler := interpreter.WithUUIDHandler(func() (uint64, error) { + return 0, nil }) if compositeKind == common.CompositeKindContract { @@ -5701,8 +5701,9 @@ func TestInterpretEmitEvent(t *testing.T) { ) inter.SetOnEventEmittedHandler( - func(_ *interpreter.Interpreter, event *interpreter.CompositeValue, eventType *sema.CompositeType) { + func(_ *interpreter.Interpreter, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { actualEvents = append(actualEvents, event) + return nil }, ) @@ -5901,8 +5902,9 @@ func TestInterpretEmitEventParameterTypes(t *testing.T) { var actualEvents []*interpreter.CompositeValue inter.SetOnEventEmittedHandler( - func(_ *interpreter.Interpreter, event *interpreter.CompositeValue, eventType *sema.CompositeType) { + func(_ *interpreter.Interpreter, event *interpreter.CompositeValue, eventType *sema.CompositeType) error { actualEvents = append(actualEvents, event) + return nil }, ) diff --git a/runtime/tests/interpreter/uuid_test.go b/runtime/tests/interpreter/uuid_test.go index 5ddfde88ca..6341ece3a4 100644 --- a/runtime/tests/interpreter/uuid_test.go +++ b/runtime/tests/interpreter/uuid_test.go @@ -93,9 +93,9 @@ func TestInterpretResourceUUID(t *testing.T) { inter, err := interpreter.NewInterpreter( importingChecker, interpreter.WithUUIDHandler( - func() uint64 { + func() (uint64, error) { defer func() { uuid++ }() - return uuid + return uuid, nil }, ), interpreter.WithImportLocationHandler(