diff --git a/parse/getast.go b/parse/getast.go index d0ff380..431f6e7 100644 --- a/parse/getast.go +++ b/parse/getast.go @@ -355,6 +355,15 @@ func (fs *FileSet) getTypeSpecs(f *ast.File) { continue } + // `type X any` declares an empty interface just like + // `type X interface{}`; no methods can be generated + // for an interface type, so classify it with the + // interfaces rather than the generatable specs + if id, ok := s.Type.(*ast.Ident); ok && id.Name == "any" { + fs.Interfaces[s.Name.Name] = s.Type + continue + } + if s.Assign == 0 { fs.Specs[s.Name.Name] = s.Type } else { diff --git a/parse/getast_test.go b/parse/getast_test.go new file mode 100644 index 0000000..38ef11e --- /dev/null +++ b/parse/getast_test.go @@ -0,0 +1,59 @@ +package parse + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" + + "github.com/algorand/msgp/gen" +) + +// TestGetTypeSpecsAnyDeclaration covers classification of empty-interface +// type declarations. `type X interface{}` is routed to fs.Interfaces so no +// methods are generated for it; `type X any` must be routed the same way, +// since methods cannot be generated for an interface receiver (and the +// fallback used to emit non-compiling code for such declarations). +func TestGetTypeSpecsAnyDeclaration(t *testing.T) { + src := `package p + +type HandleAny any + +type HandleAnyAlias = any + +type HandleIface interface{} + +type Plain struct { + A int +} +` + f, err := parser.ParseFile(token.NewFileSet(), "p.go", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + fs := &FileSet{ + Specs: make(map[string]ast.Expr), + Aliases: make(map[string]ast.Expr), + Interfaces: make(map[string]ast.Expr), + Consts: make(map[string]ast.Expr), + Identities: make(map[string]gen.Elem), + } + fs.getTypeSpecs(f) + + for _, name := range []string{"HandleAny", "HandleAnyAlias", "HandleIface"} { + if _, ok := fs.Interfaces[name]; !ok { + t.Errorf("%s: expected in Interfaces", name) + } + if _, ok := fs.Specs[name]; ok { + t.Errorf("%s: must not be in Specs (would generate methods on an interface)", name) + } + if _, ok := fs.Aliases[name]; ok { + t.Errorf("%s: must not be in Aliases", name) + } + } + + if _, ok := fs.Specs["Plain"]; !ok { + t.Errorf("Plain: expected in Specs") + } +} diff --git a/tests/anydecl.go b/tests/anydecl.go new file mode 100644 index 0000000..265ec78 --- /dev/null +++ b/tests/anydecl.go @@ -0,0 +1,27 @@ +// This file exercises top-level empty-interface type declarations: the +// generator must skip `type X any` exactly like `type X interface{}` (methods +// cannot be generated for an interface receiver, and the IDENT/Intf fallbacks +// emit code that does not compile), while ordinary types declared alongside +// them still generate. +package tests + +//go:generate msgp + +// HandleAny is an opaque reference in the style of go-algorand's +// agreement.MessageHandle and network.Peer; no methods may be generated. +type HandleAny any + +// HandleAnyAlias is the alias form of the same declaration. +type HandleAnyAlias = any + +// HandleIface is the pre-Go-1.18 spelling of HandleAny. +type HandleIface interface{} + +// AnyDeclNeighbor must still get generated code despite the interface +// declarations surrounding it. +type AnyDeclNeighbor struct { + _struct struct{} `codec:",omitempty"` + + Name string `codec:"n"` + Count uint64 `codec:"c"` +} diff --git a/tests/anydecl_gen.go b/tests/anydecl_gen.go new file mode 100644 index 0000000..1f79685 --- /dev/null +++ b/tests/anydecl_gen.go @@ -0,0 +1,162 @@ +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +package tests + +import ( + "github.com/algorand/msgp/msgp" +) + +// The following msgp objects are implemented in this file: +// AnyDeclNeighbor +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> AnyDeclNeighborMaxSize() +// + +// MarshalMsg implements msgp.Marshaler +func (z *AnyDeclNeighbor) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(2) + var zb0001Mask uint8 /* 3 bits */ + if (*z).Count == 0 { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Name == "" { + zb0001Len-- + zb0001Mask |= 0x4 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "c" + o = append(o, 0xa1, 0x63) + o = msgp.AppendUint64(o, (*z).Count) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "n" + o = append(o, 0xa1, 0x6e) + o = msgp.AppendString(o, (*z).Name) + } + } + return +} + +func (_ *AnyDeclNeighbor) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*AnyDeclNeighbor) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *AnyDeclNeighbor) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + (*z).Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Name") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).Count, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Count") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = AnyDeclNeighbor{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "n": + (*z).Name, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Name") + return + } + case "c": + (*z).Count, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Count") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (z *AnyDeclNeighbor) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *AnyDeclNeighbor) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*AnyDeclNeighbor) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *AnyDeclNeighbor) Msgsize() (s int) { + s = 1 + 2 + msgp.StringPrefixSize + len((*z).Name) + 2 + msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z *AnyDeclNeighbor) MsgIsZero() bool { + return ((*z).Name == "") && ((*z).Count == 0) +} + +// AnyDeclNeighborMaxSize returns a maximum valid message size for this message type +func AnyDeclNeighborMaxSize() (s int) { + s = 1 + 2 + panic("Unable to determine max size: String type z.Name is unbounded") +} diff --git a/tests/anydecl_gen_test.go b/tests/anydecl_gen_test.go new file mode 100644 index 0000000..4051276 --- /dev/null +++ b/tests/anydecl_gen_test.go @@ -0,0 +1,74 @@ +//go:build !skip_msgp_testing + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +package tests + +import ( + "testing" + + "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestMarshalUnmarshalAnyDeclNeighbor(t *testing.T) { + partitiontest.PartitionTest(t) + v := AnyDeclNeighbor{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingAnyDeclNeighbor(t *testing.T) { + protocol.RunEncodingTest(t, &AnyDeclNeighbor{}) +} + +func BenchmarkMarshalMsgAnyDeclNeighbor(b *testing.B) { + v := AnyDeclNeighbor{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgAnyDeclNeighbor(b *testing.B) { + v := AnyDeclNeighbor{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalAnyDeclNeighbor(b *testing.B) { + v := AnyDeclNeighbor{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +}