diff --git a/runtime/environment.go b/runtime/environment.go index 91011df111..1edd4bcaef 100644 --- a/runtime/environment.go +++ b/runtime/environment.go @@ -897,8 +897,21 @@ func (e *interpreterEnvironment) newImportLocationHandler() interpreter.ImportLo func (e *interpreterEnvironment) newCompositeTypeHandler() interpreter.CompositeTypeHandlerFunc { return func(location common.Location, typeID common.TypeID) *sema.CompositeType { - if _, ok := location.(stdlib.FlowLocation); ok { + + switch location.(type) { + case stdlib.FlowLocation: return stdlib.FlowEventTypes[typeID] + + case nil: + qualifiedIdentifier := string(typeID) + ty := sema.TypeActivationNestedType(e.baseTypeActivation, qualifiedIdentifier) + if ty == nil { + return nil + } + + if compositeType, ok := ty.(*sema.CompositeType); ok { + return compositeType + } } return nil diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index acb6ee8bbf..de338e7b8d 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -4796,16 +4796,18 @@ func (interpreter *Interpreter) GetCompositeType( if compositeType != nil { return compositeType, nil } - } else { - config := interpreter.SharedState.Config - compositeTypeHandler := config.CompositeTypeHandler - if compositeTypeHandler != nil { - compositeType = compositeTypeHandler(location, typeID) - if compositeType != nil { - return compositeType, nil - } + } + + config := interpreter.SharedState.Config + compositeTypeHandler := config.CompositeTypeHandler + if compositeTypeHandler != nil { + compositeType = compositeTypeHandler(location, typeID) + if compositeType != nil { + return compositeType, nil } + } + if location != nil { compositeType = interpreter.getUserCompositeType(location, typeID) if compositeType != nil { return compositeType, nil diff --git a/runtime/predeclaredvalues_test.go b/runtime/predeclaredvalues_test.go index fee5147550..57a86788e4 100644 --- a/runtime/predeclaredvalues_test.go +++ b/runtime/predeclaredvalues_test.go @@ -29,7 +29,7 @@ import ( "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" - "github.com/onflow/cadence/runtime/tests/utils" + . "github.com/onflow/cadence/runtime/tests/utils" ) func TestRuntimePredeclaredValues(t *testing.T) { @@ -61,7 +61,7 @@ func TestRuntimePredeclaredValues(t *testing.T) { runtime := newTestInterpreterRuntime() - deploy := utils.DeploymentTransaction("C", contract) + deploy := DeploymentTransaction("C", contract) var accountCode []byte var events []cadence.Event @@ -132,57 +132,350 @@ func TestRuntimePredeclaredTypes(t *testing.T) { t.Parallel() - xType := sema.IntType + t.Run("type alias", func(t *testing.T) { + t.Parallel() + + xType := sema.IntType + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "x", + Type: xType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewUnmeteredIntValueFromInt64(2), + } + + typeDeclaration := stdlib.StandardLibraryType{ + Name: "X", + Type: xType, + Kind: common.DeclarationKindType, + } + + script := []byte(` + pub fun main(): X { + return x + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + scriptEnvironment.DeclareType(typeDeclaration) + + result, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + require.NoError(t, err) + + require.Equal(t, + cadence.Int{Value: big.NewInt(2)}, + result, + ) + }) + + t.Run("composite type, top-level, existing", func(t *testing.T) { + t.Parallel() + + xType := &sema.CompositeType{ + Identifier: "X", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "x", + Type: xType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewSimpleCompositeValue(nil, + xType.ID(), + interpreter.ConvertSemaCompositeTypeToStaticCompositeType(nil, xType), + nil, + nil, + nil, + nil, + nil, + ), + } + + typeDeclaration := stdlib.StandardLibraryType{ + Name: "X", + Type: xType, + Kind: common.DeclarationKindType, + } + + script := []byte(` + pub fun main(): X { + return x + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + scriptEnvironment.DeclareType(typeDeclaration) + + result, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + require.NoError(t, err) + + require.Equal(t, + cadence.ValueWithCachedTypeID( + cadence.Struct{ + StructType: cadence.NewStructType(nil, xType.QualifiedIdentifier(), []cadence.Field{}, nil), + Fields: []cadence.Value{}, + }, + ), + result, + ) + }) + + t.Run("composite type, top-level, non-existing", func(t *testing.T) { + t.Parallel() + + xType := &sema.CompositeType{ + Identifier: "X", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "x", + Type: xType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewSimpleCompositeValue(nil, + xType.ID(), + interpreter.ConvertSemaCompositeTypeToStaticCompositeType(nil, xType), + nil, + nil, + nil, + nil, + nil, + ), + } + + script := []byte(` + pub fun main(): AnyStruct { + return x + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + RequireError(t, err) + + var typeLoadingErr interpreter.TypeLoadingError + require.ErrorAs(t, err, &typeLoadingErr) + }) + + t.Run("composite type, nested, existing", func(t *testing.T) { + t.Parallel() + + yType := &sema.CompositeType{ + Identifier: "Y", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + xType := &sema.CompositeType{ + Identifier: "X", + Kind: common.CompositeKindContract, + Members: &sema.StringMemberOrderedMap{}, + } + + xType.SetNestedType(yType.Identifier, yType) + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "y", + Type: yType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewSimpleCompositeValue(nil, + yType.ID(), + interpreter.ConvertSemaCompositeTypeToStaticCompositeType(nil, yType), + nil, + nil, + nil, + nil, + nil, + ), + } + + typeDeclaration := stdlib.StandardLibraryType{ + Name: "X", + Type: xType, + Kind: common.DeclarationKindType, + } + + script := []byte(` + pub fun main(): X.Y { + return y + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + scriptEnvironment.DeclareType(typeDeclaration) + + result, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + require.NoError(t, err) + + require.Equal(t, + cadence.ValueWithCachedTypeID( + cadence.Struct{ + StructType: cadence.NewStructType(nil, yType.QualifiedIdentifier(), []cadence.Field{}, nil), + Fields: []cadence.Value{}, + }, + ), + result, + ) + }) + + t.Run("composite type, nested, non-existing", func(t *testing.T) { + t.Parallel() + + yType := &sema.CompositeType{ + Identifier: "Y", + Kind: common.CompositeKindStructure, + Members: &sema.StringMemberOrderedMap{}, + } + + xType := &sema.CompositeType{ + Identifier: "X", + Kind: common.CompositeKindContract, + Members: &sema.StringMemberOrderedMap{}, + } + + xType.SetNestedType(yType.Identifier, yType) + + valueDeclaration := stdlib.StandardLibraryValue{ + Name: "y", + Type: yType, + Kind: common.DeclarationKindConstant, + Value: interpreter.NewSimpleCompositeValue(nil, + yType.ID(), + interpreter.ConvertSemaCompositeTypeToStaticCompositeType(nil, yType), + nil, + nil, + nil, + nil, + nil, + ), + } + + script := []byte(` + pub fun main(): AnyStruct { + return y + } + `) + + runtime := newTestInterpreterRuntime() + + runtimeInterface := &testRuntimeInterface{ + storage: newTestLedger(nil, nil), + getSigningAccounts: func() ([]Address, error) { + return []Address{common.MustBytesToAddress([]byte{0x1})}, nil + }, + resolveLocation: singleIdentifierLocationResolver(t), + } + + // Run script + + scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) + scriptEnvironment.DeclareValue(valueDeclaration) + + _, err := runtime.ExecuteScript( + Script{ + Source: script, + }, + Context{ + Interface: runtimeInterface, + Location: common.ScriptLocation{}, + Environment: scriptEnvironment, + }, + ) + RequireError(t, err) + + var typeLoadingErr interpreter.TypeLoadingError + require.ErrorAs(t, err, &typeLoadingErr) + }) - valueDeclaration := stdlib.StandardLibraryValue{ - Name: "x", - Type: xType, - Kind: common.DeclarationKindConstant, - Value: interpreter.NewUnmeteredIntValueFromInt64(2), - } - - typeDeclaration := stdlib.StandardLibraryType{ - Name: "X", - Type: xType, - Kind: common.DeclarationKindType, - } - - script := []byte(` - pub fun main(): X { - return x - } - `) - - runtime := newTestInterpreterRuntime() - - runtimeInterface := &testRuntimeInterface{ - storage: newTestLedger(nil, nil), - getSigningAccounts: func() ([]Address, error) { - return []Address{common.MustBytesToAddress([]byte{0x1})}, nil - }, - resolveLocation: singleIdentifierLocationResolver(t), - } - - // Run script - - scriptEnvironment := NewScriptInterpreterEnvironment(Config{}) - scriptEnvironment.DeclareValue(valueDeclaration) - scriptEnvironment.DeclareType(typeDeclaration) - - result, err := runtime.ExecuteScript( - Script{ - Source: script, - }, - Context{ - Interface: runtimeInterface, - Location: common.ScriptLocation{}, - Environment: scriptEnvironment, - }, - ) - require.NoError(t, err) - - require.Equal(t, - cadence.Int{Value: big.NewInt(2)}, - result, - ) } diff --git a/runtime/sema/type.go b/runtime/sema/type.go index 9e2ab8b422..4eb524bc5e 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -31,6 +31,8 @@ import ( "github.com/onflow/cadence/runtime/errors" ) +const TypeIDSeparator = '.' + func qualifiedIdentifier(identifier string, containerType Type) string { if containerType == nil { return identifier @@ -56,7 +58,7 @@ func qualifiedIdentifier(identifier string, containerType Type) string { for i := len(identifiers) - 1; i >= 0; i-- { sb.WriteString(identifiers[i]) if i != 0 { - sb.WriteByte('.') + sb.WriteByte(TypeIDSeparator) } } @@ -229,6 +231,36 @@ func VisitThisAndNested(t Type, visit func(ty Type)) { }) } +func TypeActivationNestedType(typeActivation *VariableActivation, qualifiedIdentifier string) Type { + + typeIDComponents := strings.Split(qualifiedIdentifier, string(TypeIDSeparator)) + + rootTypeName := typeIDComponents[0] + variable := typeActivation.Find(rootTypeName) + if variable == nil { + return nil + } + ty := variable.Type + + // Traverse nested types until the leaf type + + for i := 1; i < len(typeIDComponents); i++ { + containerType, ok := ty.(ContainerType) + if !ok || !containerType.IsContainerType() { + return nil + } + + typeIDComponent := typeIDComponents[i] + + ty, ok = containerType.GetNestedTypes().Get(typeIDComponent) + if !ok { + return nil + } + } + + return ty +} + // CompositeKindedType is a type which has a composite kind type CompositeKindedType interface { Type