diff --git a/interpreter/struct_stringer_test.go b/interpreter/struct_stringer_test.go index 3f1a7793f..63caf37ed 100644 --- a/interpreter/struct_stringer_test.go +++ b/interpreter/struct_stringer_test.go @@ -19,6 +19,7 @@ package interpreter_test import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -101,6 +102,84 @@ func TestStringerCast(t *testing.T) { ) } +func TestStringerFixedPointAndAddress(t *testing.T) { + + t.Parallel() + + runTest := func(t *testing.T, code, expected string) { + inter := parseCheckAndPrepare(t, code) + + result, err := inter.Invoke("test") + require.NoError(t, err) + + RequireValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue(expected), + result, + ) + } + + // Static cast to `{StructStringer}`, then call `toString`. + t.Run("static cast", func(t *testing.T) { + t.Parallel() + + for _, test := range []struct { + value string + expected string + }{ + {"1.0 as Fix64", "1.00000000"}, + {"1.0 as UFix64", "1.00000000"}, + {"1.0 as Fix128", "1.000000000000000000000000"}, + {"1.0 as UFix128", "1.000000000000000000000000"}, + {"0x1 as Address", "0x0000000000000001"}, + } { + value, expected := test.value, test.expected + t.Run(value, func(t *testing.T) { + t.Parallel() + + runTest(t, fmt.Sprintf(` + access(all) + fun test(): String { + let v = %s as {StructStringer} + return v.toString() + } + `, value), expected) + }) + } + }) + + // Runtime cast (`as?`) to `{StructStringer}`, then call `toString`. + t.Run("runtime cast", func(t *testing.T) { + t.Parallel() + + for _, test := range []struct { + value string + expected string + }{ + {"1.0 as Fix64", "1.00000000"}, + {"1.0 as UFix64", "1.00000000"}, + {"1.0 as Fix128", "1.000000000000000000000000"}, + {"1.0 as UFix128", "1.000000000000000000000000"}, + {"0x1 as Address", "0x0000000000000001"}, + } { + value, expected := test.value, test.expected + t.Run(value, func(t *testing.T) { + t.Parallel() + + runTest(t, fmt.Sprintf(` + access(all) + fun test(): String { + let v = %s + let s = (v as AnyStruct) as? {StructStringer} + return s!.toString() + } + `, value), expected) + }) + } + }) +} + func TestStringerAsValue(t *testing.T) { t.Parallel() diff --git a/sema/struct_stringer_test.go b/sema/struct_stringer_test.go index 119f25471..1a2d9cb25 100644 --- a/sema/struct_stringer_test.go +++ b/sema/struct_stringer_test.go @@ -35,7 +35,7 @@ func TestCheckStringer(t *testing.T) { let a: {StructStringer} = 1 let b: {StructStringer} = false let c: {StructStringer} = "hey" - access(all) + access(all) struct Foo: StructStringer { view fun toString(): String { return "foo" @@ -43,6 +43,11 @@ func TestCheckStringer(t *testing.T) { } let d: {StructStringer} = Foo() let e: {StructStringer} = /public/foo + let f: {StructStringer} = 1.0 as Fix64 + let g: {StructStringer} = 1.0 as UFix64 + let h: {StructStringer} = 1.0 as Fix128 + let i: {StructStringer} = 1.0 as UFix128 + let j: {StructStringer} = 0x1 as Address `) assert.NoError(t, err) diff --git a/sema/type.go b/sema/type.go index 3666e8122..ef71280ec 100644 --- a/sema/type.go +++ b/sema/type.go @@ -1746,6 +1746,7 @@ type FixedPointNumericType struct { } var _ Type = &FixedPointNumericType{} +var _ ConformingType = &FixedPointNumericType{} var _ IntegerRangedType = &FixedPointNumericType{} var _ FractionalRangedType = &FixedPointNumericType{} var _ SaturatingArithmeticType = &FixedPointNumericType{} @@ -1968,6 +1969,34 @@ func (*FixedPointNumericType) CheckInstantiated( // NO-OP } +var fixedPointNumericTypeEffectiveInterfaceConformanceSet *InterfaceSet +var fixedPointNumericTypeEffectiveInterfaceConformances []Conformance + +func init() { + fixedPointNumericTypeInterfaces := []*InterfaceType{ + StructStringerType, + } + + fixedPointNumericTypeEffectiveInterfaceConformanceSet = NewInterfaceSet() + for _, interfaceType := range fixedPointNumericTypeInterfaces { + fixedPointNumericTypeEffectiveInterfaceConformanceSet.Add(interfaceType) + } + + fixedPointNumericTypeEffectiveInterfaceConformances = distinctConformances( + fixedPointNumericTypeInterfaces, + nil, + map[*InterfaceType]struct{}{}, + ) +} + +func (t *FixedPointNumericType) EffectiveInterfaceConformanceSet() *InterfaceSet { + return fixedPointNumericTypeEffectiveInterfaceConformanceSet +} + +func (t *FixedPointNumericType) EffectiveInterfaceConformances() []Conformance { + return fixedPointNumericTypeEffectiveInterfaceConformances +} + // Numeric types // NumberType represents the super-type of all number types @@ -8045,6 +8074,7 @@ var TheAddressType = &AddressType{} var AddressTypeAnnotation = NewTypeAnnotation(TheAddressType) var _ Type = &AddressType{} +var _ ConformingType = &AddressType{} var _ IntegerRangedType = &AddressType{} func (*AddressType) IsType() {} @@ -8197,13 +8227,31 @@ func (t *AddressType) GetMembers() map[string]MemberResolver { } var addressTypeEffectiveInterfaceConformanceSet *InterfaceSet +var addressTypeEffectiveInterfaceConformances []Conformance func init() { + addressTypeInterfaces := []*InterfaceType{ + StructStringerType, + } + addressTypeEffectiveInterfaceConformanceSet = NewInterfaceSet() - addressTypeEffectiveInterfaceConformanceSet.Add(StructStringerType) + for _, interfaceType := range addressTypeInterfaces { + addressTypeEffectiveInterfaceConformanceSet.Add(interfaceType) + } + + addressTypeEffectiveInterfaceConformances = distinctConformances( + addressTypeInterfaces, + nil, + map[*InterfaceType]struct{}{}, + ) } -func (t *AddressType) AddressInterfaceConformanceSet() *InterfaceSet { - return numericTypeEffectiveInterfaceConformanceSet + +func (t *AddressType) EffectiveInterfaceConformanceSet() *InterfaceSet { + return addressTypeEffectiveInterfaceConformanceSet +} + +func (t *AddressType) EffectiveInterfaceConformances() []Conformance { + return addressTypeEffectiveInterfaceConformances } func IsPrimitiveOrContainerOfPrimitive(referencedType Type) bool {