diff --git a/interpreter/interpreter.go b/interpreter/interpreter.go index 2835c13112..a163efb01f 100644 --- a/interpreter/interpreter.go +++ b/interpreter/interpreter.go @@ -4912,6 +4912,10 @@ func (interpreter *Interpreter) GetInterfaceType( typeID TypeID, ) (*sema.InterfaceType, error) { if location == nil { + var interfaceType = sema.NativeInterfaceTypes[qualifiedIdentifier] + if interfaceType != nil { + return interfaceType, nil + } return nil, InterfaceMissingLocationError{ QualifiedIdentifier: qualifiedIdentifier, } diff --git a/sema/bool_type.go b/sema/bool_type.go index 36f1db3e70..1c19660bd8 100644 --- a/sema/bool_type.go +++ b/sema/bool_type.go @@ -31,6 +31,9 @@ var BoolType = &SimpleType{ Comparable: true, Exportable: true, Importable: true, + conformances: []*InterfaceType{ + StructStringerType, + }, } var BoolTypeAnnotation = NewTypeAnnotation(BoolType) diff --git a/sema/character.cdc b/sema/character.cdc index 7b54afdf5d..3532e4713a 100644 --- a/sema/character.cdc +++ b/sema/character.cdc @@ -1,6 +1,6 @@ access(all) -struct Character: Storable, Primitive, Equatable, Comparable, Exportable, Importable { +struct Character: Storable, Primitive, Equatable, Comparable, Exportable, Importable, StructStringer { /// The byte array of the UTF-8 encoding. access(all) diff --git a/sema/character.gen.go b/sema/character.gen.go index b0b10694f2..308e2e0dc4 100644 --- a/sema/character.gen.go +++ b/sema/character.gen.go @@ -59,6 +59,7 @@ var CharacterType = &SimpleType{ Exportable: true, Importable: true, ContainFields: false, + conformances: []*InterfaceType{StructStringerType}, } func init() { diff --git a/sema/gen/main.go b/sema/gen/main.go index f524af2894..49fdaf24b6 100644 --- a/sema/gen/main.go +++ b/sema/gen/main.go @@ -164,6 +164,9 @@ type typeDecl struct { memberDeclarations []ast.Declaration nestedTypes []*typeDecl hasConstructor bool + + // used in simpleType generation + conformances []*sema.InterfaceType } type generator struct { @@ -429,9 +432,40 @@ func (g *generator) addConstructorDocStringDeclaration( ) } -func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ struct{}) { +func (g *generator) VisitCompositeOrInterfaceDeclaration(decl ast.ConformingDeclaration) (_ struct{}) { + var compositeKind common.CompositeKind + var typeName string + var typeDec *typeDecl + var members []ast.Declaration + var conformances []*ast.NominalType + var isInterfaceType bool + + switch actualDecl := decl.(type) { + case *ast.CompositeDeclaration: + compositeKind = actualDecl.Kind() + typeName = actualDecl.Identifier.Identifier + typeDec = &typeDecl{ + typeName: typeName, + fullTypeName: g.newFullTypeName(typeName), + compositeKind: compositeKind, + } + members = actualDecl.Members.Declarations() + conformances = actualDecl.Conformances + isInterfaceType = false + case *ast.InterfaceDeclaration: + compositeKind = actualDecl.Kind() + typeName = actualDecl.Identifier.Identifier + typeDec = &typeDecl{ + typeName: typeName, + fullTypeName: g.newFullTypeName(typeName), + compositeKind: compositeKind, + } + members = actualDecl.Members.Declarations() + isInterfaceType = true + default: + panic("Expected composite or interface declaration") + } - compositeKind := decl.CompositeKind switch compositeKind { case common.CompositeKindStructure, common.CompositeKindResource, @@ -441,25 +475,17 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ panic(fmt.Sprintf("%s declarations are not supported", compositeKind.Name())) } - typeName := decl.Identifier.Identifier - - typeDecl := &typeDecl{ - typeName: typeName, - fullTypeName: g.newFullTypeName(typeName), - compositeKind: compositeKind, - } - if len(g.typeStack) > 0 { parentType := g.typeStack[len(g.typeStack)-1] parentType.nestedTypes = append( parentType.nestedTypes, - typeDecl, + typeDec, ) } g.typeStack = append( g.typeStack, - typeDecl, + typeDec, ) defer func() { // Pop @@ -473,6 +499,8 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ // Check if the declaration is explicitly marked to be generated as a composite type. if _, ok := g.leadingPragma["compositeType"]; ok { generateSimpleType = false + } else if isInterfaceType { + generateSimpleType = false } else { // If not, decide what to generate depending on the type. @@ -492,13 +520,13 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ } } - for _, memberDeclaration := range decl.Members.Declarations() { + for _, memberDeclaration := range members { generateDeclaration(g, memberDeclaration) // Visiting unsupported declarations panics, // so only supported member declarations are added - typeDecl.memberDeclarations = append( - typeDecl.memberDeclarations, + typeDec.memberDeclarations = append( + typeDec.memberDeclarations, memberDeclaration, ) @@ -514,7 +542,7 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ } } - for _, conformance := range decl.Conformances { + for _, conformance := range conformances { switch conformance.Identifier.Identifier { case "Storable": if !generateSimpleType { @@ -523,7 +551,7 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ g.currentTypeID(), )) } - typeDecl.storable = true + typeDec.storable = true case "Primitive": if !generateSimpleType { @@ -532,7 +560,7 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ g.currentTypeID(), )) } - typeDecl.primitive = true + typeDec.primitive = true case "Equatable": if !generateSimpleType { @@ -541,7 +569,7 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ g.currentTypeID(), )) } - typeDecl.equatable = true + typeDec.equatable = true case "Comparable": if !generateSimpleType { @@ -550,7 +578,7 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ g.currentTypeID(), )) } - typeDecl.comparable = true + typeDec.comparable = true case "Exportable": if !generateSimpleType { @@ -559,10 +587,10 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ g.currentTypeID(), )) } - typeDecl.exportable = true + typeDec.exportable = true case "Importable": - typeDecl.importable = true + typeDec.importable = true case "ContainFields": if !generateSimpleType { @@ -571,18 +599,20 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ g.currentTypeID(), )) } - typeDecl.memberAccessible = true + typeDec.memberAccessible = true + case "StructStringer": + typeDec.conformances = append(typeDec.conformances, sema.StructStringerType) } } var typeVarDecl dst.Expr if generateSimpleType { - typeVarDecl = simpleTypeLiteral(typeDecl) + typeVarDecl = simpleTypeLiteral(typeDec) } else { - typeVarDecl = compositeTypeExpr(typeDecl) + typeVarDecl = compositeOrInterfaceTypeExpr(typeDec, isInterfaceType) } - fullTypeName := typeDecl.fullTypeName + fullTypeName := typeDec.fullTypeName tyVarName := typeVarName(fullTypeName) @@ -597,7 +627,7 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ ), ) - memberDeclarations := typeDecl.memberDeclarations + memberDeclarations := typeDec.memberDeclarations if len(memberDeclarations) > 0 { @@ -700,7 +730,7 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ }, } - if typeDecl.hasConstructor { + if typeDec.hasConstructor { stmts = append( stmts, &dst.AssignStmt{ @@ -736,8 +766,12 @@ func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ return } -func (*generator) VisitInterfaceDeclaration(_ *ast.InterfaceDeclaration) struct{} { - panic("interface declarations are not supported") +func (g *generator) VisitCompositeDeclaration(decl *ast.CompositeDeclaration) (_ struct{}) { + return g.VisitCompositeOrInterfaceDeclaration(decl) +} + +func (g *generator) VisitInterfaceDeclaration(decl *ast.InterfaceDeclaration) (_ struct{}) { + return g.VisitCompositeOrInterfaceDeclaration(decl) } func (*generator) VisitAttachmentDeclaration(_ *ast.AttachmentDeclaration) struct{} { @@ -1591,6 +1625,9 @@ func simpleTypeLiteral(ty *typeDecl) dst.Expr { // Comparable: false, // Exportable: false, // Importable: false, + // comformances: []*InterfaceType { + // StructStringerType, + // } //} isResource := ty.compositeKind == common.CompositeKindResource @@ -1609,6 +1646,33 @@ func simpleTypeLiteral(ty *typeDecl) dst.Expr { goKeyValue("ContainFields", goBoolLit(ty.memberAccessible)), } + if len(ty.conformances) > 0 { + var elts = []dst.Expr{} + for _, conformance := range ty.conformances { + var name = "" + switch conformance { + case sema.StructStringerType: + name = "StructStringerType" + default: + panic("Unsupported conformance typeID") + } + elts = append(elts, &dst.Ident{ + Name: name, + Path: semaPath, + }) + } + elements = append(elements, goKeyValue("conformances", &dst.CompositeLit{ + Type: &dst.ArrayType{ + Elt: &dst.StarExpr{ + X: &dst.Ident{ + Name: "InterfaceType", + }, + }, + }, + Elts: elts, + })) + } + return &dst.UnaryExpr{ Op: token.AND, X: &dst.CompositeLit{ @@ -1971,10 +2035,10 @@ func stringMemberResolverMapType() *dst.MapType { } } -func compositeTypeExpr(ty *typeDecl) dst.Expr { +func compositeOrInterfaceTypeExpr(ty *typeDecl, isInterfaceType bool) dst.Expr { // func() *CompositeType { - // var t = &CompositeType{ + // var t = &CompositeType { // Identifier: FooTypeName, // Kind: common.CompositeKindStructure, // ImportableBuiltin: false, @@ -1985,13 +2049,23 @@ func compositeTypeExpr(ty *typeDecl) dst.Expr { // return t // }() + // func() *InterfaceType { + // var t = &InterfaceType{ + // Identifier: FooTypeName, + // CompositeKind: common.CompositeKindStructure, + // } + // + // t.SetNestedType(FooBarTypeName, FooBarType) + // return t + // }() + const typeVarName = "t" statements := []dst.Stmt{ &dst.DeclStmt{ Decl: goVarDecl( typeVarName, - compositeTypeLiteral(ty), + compositeOrInterfaceTypeLiteral(ty, isInterfaceType), ), }, } @@ -2023,6 +2097,11 @@ func compositeTypeExpr(ty *typeDecl) dst.Expr { }, ) + name := "CompositeType" + if isInterfaceType { + name = "InterfaceType" + } + return &dst.CallExpr{ Fun: &dst.FuncLit{ Type: &dst.FuncType{ @@ -2032,7 +2111,7 @@ func compositeTypeExpr(ty *typeDecl) dst.Expr { { Type: &dst.StarExpr{ X: &dst.Ident{ - Name: "CompositeType", + Name: name, Path: semaPath, }, }, @@ -2047,21 +2126,30 @@ func compositeTypeExpr(ty *typeDecl) dst.Expr { } } -func compositeTypeLiteral(ty *typeDecl) dst.Expr { +func compositeOrInterfaceTypeLiteral(ty *typeDecl, isInterfaceType bool) dst.Expr { kind := compositeKindExpr(ty.compositeKind) elements := []dst.Expr{ goKeyValue("Identifier", typeNameVarIdent(ty.fullTypeName)), - goKeyValue("Kind", kind), - goKeyValue("ImportableBuiltin", goBoolLit(ty.importable)), - goKeyValue("HasComputedMembers", goBoolLit(true)), + } + + name := "InterfaceType" + if isInterfaceType { + elements = append(elements, + goKeyValue("CompositeKind", kind)) + } else { + name = "CompositeType" + elements = append(elements, + goKeyValue("Kind", kind), + goKeyValue("ImportableBuiltin", goBoolLit(ty.importable)), + goKeyValue("HasComputedMembers", goBoolLit(true))) } return &dst.UnaryExpr{ Op: token.AND, X: &dst.CompositeLit{ Type: &dst.Ident{ - Name: "CompositeType", + Name: name, Path: semaPath, }, Elts: elements, diff --git a/sema/gen/testdata/simple_interface/test.cdc b/sema/gen/testdata/simple_interface/test.cdc new file mode 100644 index 0000000000..388ef59051 --- /dev/null +++ b/sema/gen/testdata/simple_interface/test.cdc @@ -0,0 +1 @@ +access(all) struct interface Test {} diff --git a/sema/gen/testdata/simple_interface/test.golden.go b/sema/gen/testdata/simple_interface/test.golden.go new file mode 100644 index 0000000000..c976eafded --- /dev/null +++ b/sema/gen/testdata/simple_interface/test.golden.go @@ -0,0 +1,36 @@ +// Code generated from testdata/simple_interface/test.cdc. DO NOT EDIT. +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package simple_interface + +import ( + "github.com/onflow/cadence/common" + "github.com/onflow/cadence/sema" +) + +const TestTypeName = "Test" + +var TestType = func() *sema.InterfaceType { + var t = &sema.InterfaceType{ + Identifier: TestTypeName, + CompositeKind: common.CompositeKindStructure, + } + + return t +}() diff --git a/sema/path_type.go b/sema/path_type.go index 4334054f60..042be61502 100644 --- a/sema/path_type.go +++ b/sema/path_type.go @@ -31,6 +31,9 @@ var PathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, + conformances: []*InterfaceType{ + StructStringerType, + }, } var PathTypeAnnotation = NewTypeAnnotation(PathType) @@ -48,6 +51,9 @@ var StoragePathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, + conformances: []*InterfaceType{ + StructStringerType, + }, } var StoragePathTypeAnnotation = NewTypeAnnotation(StoragePathType) @@ -65,6 +71,9 @@ var CapabilityPathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, + conformances: []*InterfaceType{ + StructStringerType, + }, } var CapabilityPathTypeAnnotation = NewTypeAnnotation(CapabilityPathType) @@ -82,6 +91,9 @@ var PublicPathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, + conformances: []*InterfaceType{ + StructStringerType, + }, } var PublicPathTypeAnnotation = NewTypeAnnotation(PublicPathType) @@ -99,6 +111,9 @@ var PrivatePathType = &SimpleType{ Comparable: false, Exportable: true, Importable: true, + conformances: []*InterfaceType{ + StructStringerType, + }, } var PrivatePathTypeAnnotation = NewTypeAnnotation(PrivatePathType) diff --git a/sema/simple_type.go b/sema/simple_type.go index f05db593c5..db8f8da6bb 100644 --- a/sema/simple_type.go +++ b/sema/simple_type.go @@ -51,6 +51,12 @@ type SimpleType struct { Primitive bool IsResource bool ContainFields bool + + // allow simple types to define a set of interfaces it conforms to + // e.g. StructStringer + conformances []*InterfaceType + effectiveInterfaceConformanceSet *InterfaceSet + effectiveInterfaceConformanceSetOnce sync.Once } var _ Type = &SimpleType{} @@ -195,3 +201,18 @@ func (t *SimpleType) CompositeKind() common.CompositeKind { func (t *SimpleType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ func(err error)) { // NO-OP } + +func (t *SimpleType) EffectiveInterfaceConformanceSet() *InterfaceSet { + t.initializeEffectiveInterfaceConformanceSet() + return t.effectiveInterfaceConformanceSet +} + +func (t *SimpleType) initializeEffectiveInterfaceConformanceSet() { + t.effectiveInterfaceConformanceSetOnce.Do(func() { + t.effectiveInterfaceConformanceSet = NewInterfaceSet() + + for _, conformance := range t.conformances { + t.effectiveInterfaceConformanceSet.Add(conformance) + } + }) +} diff --git a/sema/string_type.go b/sema/string_type.go index 37a3707561..a3091cb248 100644 --- a/sema/string_type.go +++ b/sema/string_type.go @@ -143,6 +143,9 @@ var StringType = &SimpleType{ }, IndexingType: IntegerType, }, + conformances: []*InterfaceType{ + StructStringerType, + }, } var StringTypeAnnotation = NewTypeAnnotation(StringType) diff --git a/sema/struct_stringer.cdc b/sema/struct_stringer.cdc new file mode 100644 index 0000000000..224765fd90 --- /dev/null +++ b/sema/struct_stringer.cdc @@ -0,0 +1,7 @@ +/// StructStringer is an interface implemented by all the string convertible structs. +access(all) +struct interface StructStringer { + /// Returns the string representation of this object. + access(all) + view fun toString(): String +} diff --git a/sema/struct_stringer.gen.go b/sema/struct_stringer.gen.go new file mode 100644 index 0000000000..7f23bd22df --- /dev/null +++ b/sema/struct_stringer.gen.go @@ -0,0 +1,64 @@ +// Code generated from struct_stringer.cdc. DO NOT EDIT. +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sema + +import ( + "github.com/onflow/cadence/ast" + "github.com/onflow/cadence/common" +) + +const StructStringerTypeToStringFunctionName = "toString" + +var StructStringerTypeToStringFunctionType = &FunctionType{ + Purity: FunctionPurityView, + ReturnTypeAnnotation: NewTypeAnnotation( + StringType, + ), +} + +const StructStringerTypeToStringFunctionDocString = ` +Returns the string representation of this object. +` + +const StructStringerTypeName = "StructStringer" + +var StructStringerType = func() *InterfaceType { + var t = &InterfaceType{ + Identifier: StructStringerTypeName, + CompositeKind: common.CompositeKindStructure, + } + + return t +}() + +func init() { + var members = []*Member{ + NewUnmeteredFunctionMember( + StructStringerType, + PrimitiveAccess(ast.AccessAll), + StructStringerTypeToStringFunctionName, + StructStringerTypeToStringFunctionType, + StructStringerTypeToStringFunctionDocString, + ), + } + + StructStringerType.Members = MembersAsMap(members) + StructStringerType.Fields = MembersFieldNames(members) +} diff --git a/sema/struct_stringer.go b/sema/struct_stringer.go new file mode 100644 index 0000000000..a91c8796c7 --- /dev/null +++ b/sema/struct_stringer.go @@ -0,0 +1,21 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sema + +//go:generate go run ./gen struct_stringer.cdc struct_stringer.gen.go diff --git a/sema/type.go b/sema/type.go index c8d65c6d9b..9a13a2e762 100644 --- a/sema/type.go +++ b/sema/type.go @@ -312,6 +312,12 @@ func TypeActivationNestedType(typeActivation *VariableActivation, qualifiedIdent return ty } +// ConformingType is a type that can conform to interfaces +type ConformingType interface { + Type + EffectiveInterfaceConformanceSet() *InterfaceSet +} + // CompositeKindedType is a type which has a composite kind type CompositeKindedType interface { Type @@ -1197,7 +1203,9 @@ var _ IntegerRangedType = &NumericType{} var _ SaturatingArithmeticType = &NumericType{} func NewNumericType(typeName string) *NumericType { - return &NumericType{name: typeName} + return &NumericType{ + name: typeName, + } } func (t *NumericType) Tag() TypeTag { @@ -1375,6 +1383,17 @@ func (*NumericType) CheckInstantiated(_ ast.HasPosition, _ common.MemoryGauge, _ // NO-OP } +var numericTypeEffectiveInterfaceConformanceSet *InterfaceSet + +func init() { + numericTypeEffectiveInterfaceConformanceSet = NewInterfaceSet() + numericTypeEffectiveInterfaceConformanceSet.Add(StructStringerType) +} + +func (t *NumericType) EffectiveInterfaceConformanceSet() *InterfaceSet { + return numericTypeEffectiveInterfaceConformanceSet +} + // FixedPointNumericType represents all the types in the fixed-point range. type FixedPointNumericType struct { maxFractional *big.Int @@ -4210,6 +4229,7 @@ func init() { DeploymentResultType, HashableStructType, &InclusiveRangeType{}, + StructStringerType, }, ) @@ -7405,6 +7425,16 @@ func (t *AddressType) initializeMemberResolvers() { }) } +var addressTypeEffectiveInterfaceConformanceSet *InterfaceSet + +func init() { + addressTypeEffectiveInterfaceConformanceSet = NewInterfaceSet() + addressTypeEffectiveInterfaceConformanceSet.Add(StructStringerType) +} +func (t *AddressType) AddressInterfaceConformanceSet() *InterfaceSet { + return numericTypeEffectiveInterfaceConformanceSet +} + func IsPrimitiveOrContainerOfPrimitive(referencedType Type) bool { switch ty := referencedType.(type) { case *VariableSizedType: @@ -7821,7 +7851,7 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { IsSubsetOf(intersectionSubtype.EffectiveInterfaceConformanceSet()) } - case *CompositeType: + case ConformingType: // A type `T` // is a subtype of an intersection type `AnyResource{Us}` / `AnyStruct{Us}` / `Any{Us}`: // if `T` is a subtype of the intersection supertype, @@ -9511,10 +9541,49 @@ func (t *EntitlementMapType) CheckInstantiated(_ ast.HasPosition, _ common.Memor // NO-OP } +func extractNativeTypes( + types []Type, +) { + for len(types) > 0 { + lastIndex := len(types) - 1 + curType := types[lastIndex] + types[lastIndex] = nil + types = types[:lastIndex] + + switch actualType := curType.(type) { + case *CompositeType: + NativeCompositeTypes[actualType.QualifiedIdentifier()] = actualType + + nestedTypes := actualType.NestedTypes + if nestedTypes == nil { + continue + } + + nestedTypes.Foreach(func(_ string, nestedType Type) { + types = append(types, nestedType) + }) + case *InterfaceType: + NativeInterfaceTypes[actualType.QualifiedIdentifier()] = actualType + + nestedTypes := actualType.NestedTypes + if nestedTypes == nil { + continue + } + + nestedTypes.Foreach(func(_ string, nestedType Type) { + types = append(types, nestedType) + }) + default: + panic("Expected only composite or interface type") + } + + } +} + var NativeCompositeTypes = map[string]*CompositeType{} func init() { - compositeTypes := []*CompositeType{ + compositeTypes := []Type{ AccountKeyType, PublicKeyType, HashAlgorithmType, @@ -9523,26 +9592,15 @@ func init() { DeploymentResultType, } - for len(compositeTypes) > 0 { - lastIndex := len(compositeTypes) - 1 - compositeType := compositeTypes[lastIndex] - compositeTypes[lastIndex] = nil - compositeTypes = compositeTypes[:lastIndex] - - NativeCompositeTypes[compositeType.QualifiedIdentifier()] = compositeType + extractNativeTypes(compositeTypes) +} - nestedTypes := compositeType.NestedTypes - if nestedTypes == nil { - continue - } +var NativeInterfaceTypes = map[string]*InterfaceType{} - nestedTypes.Foreach(func(_ string, nestedType Type) { - nestedCompositeType, ok := nestedType.(*CompositeType) - if !ok { - return - } - - compositeTypes = append(compositeTypes, nestedCompositeType) - }) +func init() { + interfaceTypes := []Type{ + StructStringerType, } + + extractNativeTypes(interfaceTypes) } diff --git a/sema/type_test.go b/sema/type_test.go index 1ede9831c9..9d2cf503ac 100644 --- a/sema/type_test.go +++ b/sema/type_test.go @@ -2378,6 +2378,10 @@ func TestTypeInclusions(t *testing.T) { return } + if _, ok := typ.(*InterfaceType); ok { + return + } + if typ.IsResourceType() { return } diff --git a/tests/checker/stringer_test.go b/tests/checker/stringer_test.go new file mode 100644 index 0000000000..0939eb76fc --- /dev/null +++ b/tests/checker/stringer_test.go @@ -0,0 +1,75 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package checker + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/onflow/cadence/sema" +) + +func TestCheckStringer(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + let a: {StructStringer} = 1 + let b: {StructStringer} = false + let c: {StructStringer} = "hey" + access(all) + struct Foo: StructStringer { + view fun toString(): String { + return "foo" + } + } + let d: {StructStringer} = Foo() + let e: {StructStringer} = /public/foo + `) + + assert.NoError(t, err) +} + +func TestCheckInvalidStringer(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + resource R {} + + let a: {StructStringer} = <-create R() + let b: {StructStringer} = [<-create R()] + let c: {StructStringer} = {1: true} + struct Foo: StructStringer {} + struct Bar: StructStringer { + fun toString(): String { + return "bar" + } + } + `) + + errs := RequireCheckerErrors(t, err, 5) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[1]) + assert.IsType(t, &sema.TypeMismatchError{}, errs[2]) + assert.IsType(t, &sema.ConformanceError{}, errs[3]) + assert.IsType(t, &sema.ConformanceError{}, errs[4]) +} diff --git a/tests/interpreter/stringer_test.go b/tests/interpreter/stringer_test.go new file mode 100644 index 0000000000..d01acd5b43 --- /dev/null +++ b/tests/interpreter/stringer_test.go @@ -0,0 +1,125 @@ +/* + * Cadence - The resource-oriented smart contract programming language + * + * Copyright Flow Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interpreter_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/onflow/cadence/interpreter" + . "github.com/onflow/cadence/tests/utils" +) + +func TestStringerBasic(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) + struct Example: StructStringer { + view fun toString(): String { + return "example" + } + } + fun test(): String { + return Example().toString() + } + `) + + result, err := inter.Invoke("test") + require.NoError(t, err) + + RequireValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("example"), + result, + ) +} + +func TestStringerBuiltIn(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) + fun test(): String { + let v = 1 + return v.toString() + } + `) + + result, err := inter.Invoke("test") + require.NoError(t, err) + + RequireValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("1"), + result, + ) +} + +func TestStringerCast(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) + fun test(): String { + var s = 1 + var somevalue = s as {StructStringer} + return somevalue.toString() + } + `) + + result, err := inter.Invoke("test") + require.NoError(t, err) + + RequireValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("1"), + result, + ) +} + +func TestStringerAsValue(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + access(all) + fun test(): String { + var v = Type<{StructStringer}>() + return v.identifier + } + `) + + result, err := inter.Invoke("test") + require.NoError(t, err) + + RequireValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("{StructStringer}"), + result, + ) +}