diff --git a/runtime/interpreter/value_type.go b/runtime/interpreter/value_type.go index 72452a8abc..f6c8a5995e 100644 --- a/runtime/interpreter/value_type.go +++ b/runtime/interpreter/value_type.go @@ -19,6 +19,8 @@ package interpreter import ( + "strings" + "github.com/onflow/atree" "github.com/onflow/cadence/runtime/common" @@ -172,6 +174,86 @@ func (v TypeValue) GetMember(interpreter *Interpreter, _ LocationRange, name str } return AsBoolValue(elaboration.IsRecovered) + + case sema.MetaTypeAddressFieldName: + staticType := v.Type + if staticType == nil { + return Nil + } + + var location common.Location + + switch staticType := staticType.(type) { + case *CompositeStaticType: + location = staticType.Location + + case *InterfaceStaticType: + location = staticType.Location + + default: + return Nil + } + + addressLocation, ok := location.(common.AddressLocation) + if !ok { + return Nil + } + + addressValue := NewAddressValue( + interpreter, + addressLocation.Address, + ) + return NewSomeValueNonCopying( + interpreter, + addressValue, + ) + + case sema.MetaTypeContractNameFieldName: + staticType := v.Type + if staticType == nil { + return Nil + } + + var location common.Location + var qualifiedIdentifier string + + switch staticType := staticType.(type) { + case *CompositeStaticType: + location = staticType.Location + qualifiedIdentifier = staticType.QualifiedIdentifier + + case *InterfaceStaticType: + location = staticType.Location + qualifiedIdentifier = staticType.QualifiedIdentifier + + default: + return Nil + } + + switch location.(type) { + case common.AddressLocation, + common.StringLocation: + + separatorIndex := strings.Index(qualifiedIdentifier, ".") + contractNameLength := len(qualifiedIdentifier) + if separatorIndex >= 0 { + contractNameLength = separatorIndex + } + + contractNameValue := NewStringValue( + interpreter, + common.NewStringMemoryUsage(contractNameLength), + func() string { + return qualifiedIdentifier[0:contractNameLength] + }, + ) + + return NewSomeValueNonCopying(interpreter, contractNameValue) + + default: + return Nil + } + } return nil diff --git a/runtime/sema/meta_type.go b/runtime/sema/meta_type.go index 6d2ff48673..440795dc39 100644 --- a/runtime/sema/meta_type.go +++ b/runtime/sema/meta_type.go @@ -69,6 +69,26 @@ const metaTypeIsRecoveredFieldDocString = ` The type was defined through a recovered program ` +const MetaTypeAddressFieldName = "address" + +var MetaTypeAddressFieldType = &OptionalType{ + Type: TheAddressType, +} + +const metaTypeAddressFieldDocString = ` +The address of the type, if it was declared in a contract deployed to an account +` + +const MetaTypeContractNameFieldName = "contractName" + +var MetaTypeContractNameFieldType = &OptionalType{ + Type: StringType, +} + +const metaTypeContractNameFieldDocString = ` +The contract name of the type, if it was declared in a contract +` + func init() { MetaType.Members = func(t *SimpleType) map[string]MemberResolver { return MembersAsResolvers([]*Member{ @@ -90,6 +110,18 @@ func init() { MetaTypeIsRecoveredFieldType, metaTypeIsRecoveredFieldDocString, ), + NewUnmeteredPublicConstantFieldMember( + t, + MetaTypeAddressFieldName, + MetaTypeAddressFieldType, + metaTypeAddressFieldDocString, + ), + NewUnmeteredPublicConstantFieldMember( + t, + MetaTypeContractNameFieldName, + MetaTypeContractNameFieldType, + metaTypeContractNameFieldDocString, + ), }) } } diff --git a/runtime/tests/checker/metatype_test.go b/runtime/tests/checker/metatype_test.go index d532f53f8c..b660a60e64 100644 --- a/runtime/tests/checker/metatype_test.go +++ b/runtime/tests/checker/metatype_test.go @@ -317,6 +317,27 @@ func TestCheckMetaTypeIsRecovered(t *testing.T) { let type: Type = Type() let isRecovered: Bool = type.isRecovered `) + require.NoError(t, err) +} + +func TestCheckMetaTypeAddress(t *testing.T) { + + t.Parallel() + _, err := ParseAndCheck(t, ` + let type: Type = Type() + let address: Address = type.address! + `) + require.NoError(t, err) +} + +func TestCheckMetaTypeContractName(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + let type: Type = Type() + let contractName: String = type.contractName! + `) require.NoError(t, err) } diff --git a/runtime/tests/interpreter/metatype_test.go b/runtime/tests/interpreter/metatype_test.go index 6140d38ae3..ac86360133 100644 --- a/runtime/tests/interpreter/metatype_test.go +++ b/runtime/tests/interpreter/metatype_test.go @@ -1067,3 +1067,347 @@ func TestInterpretMetaTypeIsRecovered(t *testing.T) { require.ErrorIs(t, err, importErr) }) } + +func TestInterpretMetaTypeAddress(t *testing.T) { + + t.Parallel() + + t.Run("built-in", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + let type = Type() + let address = type.address + `) + + AssertValuesEqual( + t, + inter, + interpreter.Nil, + inter.Globals.Get("address").GetValue(inter), + ) + }) + + t.Run("address location", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + fun test(): Address? { + let type = CompositeType("A.0000000000000001.X.Y")! + return type.address + } + `) + + addressLocation := common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x1}), + Name: "X", + } + + inter.SharedState.Config.ImportLocationHandler = + func(_ *interpreter.Interpreter, _ common.Location) interpreter.Import { + elaboration := sema.NewElaboration(nil) + elaboration.SetCompositeType( + addressLocation.TypeID(nil, "X.Y"), + &sema.CompositeType{ + Location: addressLocation, + Kind: common.CompositeKindStructure, + }, + ) + return interpreter.VirtualImport{ + Elaboration: elaboration, + } + } + + result, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredAddressValueFromBytes([]byte{0x1}), + ), + result, + ) + }) + + t.Run("string location", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + fun test(): Address? { + let type = CompositeType("S.test2.X.Y")! + return type.address + } + `) + + stringLocation := common.StringLocation("test2") + + inter.SharedState.Config.ImportLocationHandler = + func(_ *interpreter.Interpreter, _ common.Location) interpreter.Import { + elaboration := sema.NewElaboration(nil) + elaboration.SetCompositeType( + stringLocation.TypeID(nil, "X.Y"), + &sema.CompositeType{ + Location: stringLocation, + Kind: common.CompositeKindStructure, + }, + ) + return interpreter.VirtualImport{ + Elaboration: elaboration, + } + } + + result, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.Nil, + result, + ) + }) + + t.Run("unknown", func(t *testing.T) { + + t.Parallel() + + valueDeclarations := []stdlib.StandardLibraryValue{ + { + Name: "unknownType", + Type: sema.MetaType, + Value: interpreter.TypeValue{ + Type: nil, + }, + Kind: common.DeclarationKindConstant, + }, + } + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + for _, valueDeclaration := range valueDeclarations { + baseValueActivation.DeclareValue(valueDeclaration) + } + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + for _, valueDeclaration := range valueDeclarations { + interpreter.Declare(baseActivation, valueDeclaration) + } + + inter, err := parseCheckAndInterpretWithOptions(t, + ` + let address = unknownType.address + `, + ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + Config: &interpreter.Config{ + BaseActivationHandler: func(_ common.Location) *interpreter.VariableActivation { + return baseActivation + }, + }, + }, + ) + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.Nil, + inter.Globals.Get("address").GetValue(inter), + ) + }) +} + +func TestInterpretMetaTypeContractName(t *testing.T) { + + t.Parallel() + + t.Run("built-in", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + let type = Type() + let contractName = type.contractName + `) + + AssertValuesEqual( + t, + inter, + interpreter.Nil, + inter.Globals.Get("contractName").GetValue(inter), + ) + }) + + t.Run("address location", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + fun test(): String? { + let type = CompositeType("A.0000000000000001.X.Y")! + return type.contractName + } + `) + + addressLocation := common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x1}), + Name: "X", + } + + yType := &sema.CompositeType{ + Location: addressLocation, + Kind: common.CompositeKindStructure, + Identifier: "Y", + } + xType := &sema.CompositeType{ + Location: addressLocation, + Kind: common.CompositeKindContract, + Identifier: "X", + } + xType.SetNestedType("Y", yType) + yType.SetContainerType(xType) + + inter.SharedState.Config.ImportLocationHandler = + func(_ *interpreter.Interpreter, _ common.Location) interpreter.Import { + elaboration := sema.NewElaboration(nil) + elaboration.SetCompositeType( + addressLocation.TypeID(nil, "X"), + xType, + ) + elaboration.SetCompositeType( + addressLocation.TypeID(nil, "X.Y"), + yType, + ) + return interpreter.VirtualImport{ + Elaboration: elaboration, + } + } + + result, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredStringValue("X"), + ), + result, + ) + }) + + t.Run("string location", func(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + fun test(): String? { + let type = CompositeType("S.test2.X.Y")! + return type.contractName + } + `) + + stringLocation := common.StringLocation("test2") + + yType := &sema.CompositeType{ + Location: stringLocation, + Kind: common.CompositeKindStructure, + Identifier: "Y", + } + xType := &sema.CompositeType{ + Location: stringLocation, + Kind: common.CompositeKindContract, + Identifier: "X", + } + xType.SetNestedType("Y", yType) + yType.SetContainerType(xType) + + inter.SharedState.Config.ImportLocationHandler = + func(_ *interpreter.Interpreter, _ common.Location) interpreter.Import { + elaboration := sema.NewElaboration(nil) + elaboration.SetCompositeType( + stringLocation.TypeID(nil, "X"), + xType, + ) + elaboration.SetCompositeType( + stringLocation.TypeID(nil, "X.Y"), + yType, + ) + return interpreter.VirtualImport{ + Elaboration: elaboration, + } + } + + result, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredSomeValueNonCopying( + interpreter.NewUnmeteredStringValue("X"), + ), + result, + ) + }) + + t.Run("unknown", func(t *testing.T) { + + t.Parallel() + + valueDeclarations := []stdlib.StandardLibraryValue{ + { + Name: "unknownType", + Type: sema.MetaType, + Value: interpreter.TypeValue{ + Type: nil, + }, + Kind: common.DeclarationKindConstant, + }, + } + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + for _, valueDeclaration := range valueDeclarations { + baseValueActivation.DeclareValue(valueDeclaration) + } + + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + for _, valueDeclaration := range valueDeclarations { + interpreter.Declare(baseActivation, valueDeclaration) + } + + inter, err := parseCheckAndInterpretWithOptions(t, + ` + let contractName = unknownType.contractName + `, + ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivationHandler: func(_ common.Location) *sema.VariableActivation { + return baseValueActivation + }, + }, + Config: &interpreter.Config{ + BaseActivationHandler: func(_ common.Location) *interpreter.VariableActivation { + return baseActivation + }, + }, + }, + ) + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.Nil, + inter.Globals.Get("contractName").GetValue(inter), + ) + }) +}