Implement automated marshalling for newtypes on primitives.

PiperOrigin-RevId: 296322954
This commit is contained in:
gVisor bot
2020-02-20 16:23:56 -08:00
committed by Copybara-Service
parent 4a73bae269
commit f1b72752e5
6 changed files with 286 additions and 91 deletions
+6 -2
View File
@@ -85,7 +85,7 @@ def go_imports(name, src, out):
cmd = ("$(location @org_golang_x_tools//cmd/goimports:goimports) $(SRCS) > $@"),
)
def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = False, **kwargs):
def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = False, marshal_debug = False, **kwargs):
"""Wraps the standard go_library and does stateification and marshalling.
The recommended way is to use this rule with mostly identical configuration as the native
@@ -108,6 +108,7 @@ def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = F
imports: imports required for stateify.
stateify: whether statify is enabled (default: true).
marshal: whether marshal is enabled (default: false).
marshal_debug: whether the gomarshal tools emits debugging output (default: false).
**kwargs: standard go_library arguments.
"""
all_srcs = srcs
@@ -146,7 +147,10 @@ def go_library(name, srcs, deps = [], imports = [], stateify = True, marshal = F
go_marshal(
name = name + suffix + "_abi_autogen",
srcs = src_subset,
debug = False,
debug = select({
"//tools/go_marshal:marshal_config_verbose": True,
"//conditions:default": marshal_debug,
}),
imports = imports,
package = name,
)
+5
View File
@@ -12,3 +12,8 @@ go_binary(
"//tools/go_marshal/gomarshal",
],
)
config_setting(
name = "marshal_config_verbose",
values = {"define": "gomarshal=verbose"},
)
+30 -13
View File
@@ -44,7 +44,8 @@ const (
// All recievers are single letters, so we don't allow import aliases to be a
// single letter.
var badIdents = []string{
"addr", "blk", "buf", "dst", "dsts", "err", "hdr", "len", "ptr", "src", "srcs", "task", "val",
"addr", "blk", "buf", "dst", "dsts", "err", "hdr", "idx", "inner", "len",
"ptr", "src", "srcs", "task", "val",
// All single-letter identifiers.
}
@@ -193,9 +194,9 @@ func (g *Generator) parse() ([]*ast.File, []*token.FileSet, error) {
return files, fsets, nil
}
// collectMarshallabeTypes walks the parsed AST and collects a list of type
// collectMarshallableTypes walks the parsed AST and collects a list of type
// declarations for which we need to generate the Marshallable interface.
func (g *Generator) collectMarshallabeTypes(a *ast.File, f *token.FileSet) []*ast.TypeSpec {
func (g *Generator) collectMarshallableTypes(a *ast.File, f *token.FileSet) []*ast.TypeSpec {
var types []*ast.TypeSpec
for _, decl := range a.Decls {
gdecl, ok := decl.(*ast.GenDecl)
@@ -222,14 +223,22 @@ func (g *Generator) collectMarshallabeTypes(a *ast.File, f *token.FileSet) []*as
continue
}
for _, spec := range gdecl.Specs {
// We already confirmed we're in a type declaration earlier.
// We already confirmed we're in a type declaration earlier, so this
// cast will succeed.
t := spec.(*ast.TypeSpec)
if _, ok := t.Type.(*ast.StructType); ok {
debugfAt(f.Position(t.Pos()), "Collected marshallable type %s.\n", t.Name.Name)
switch t.Type.(type) {
case *ast.StructType:
debugfAt(f.Position(t.Pos()), "Collected marshallable struct %s.\n", t.Name.Name)
types = append(types, t)
continue
case *ast.Ident: // Newtype on primitive.
debugfAt(f.Position(t.Pos()), "Collected marshallable newtype on primitive %s.\n", t.Name.Name)
types = append(types, t)
continue
}
debugf("Skipping declaration %v since it's not a struct declaration.\n", gdecl)
// A user specifically requested marshalling on this type, but we
// don't support it.
abortAt(f.Position(t.Pos()), fmt.Sprintf("Marshalling codegen was requested on type '%s', but go-marshal doesn't support this kind of declaration.\n", t.Name))
}
}
return types
@@ -269,12 +278,20 @@ func (g *Generator) collectImports(a *ast.File, f *token.FileSet) map[string]imp
}
func (g *Generator) generateOne(t *ast.TypeSpec, fset *token.FileSet) *interfaceGenerator {
// We're guaranteed to have only struct type specs by now. See
// Generator.collectMarshallabeTypes.
i := newInterfaceGenerator(t, fset)
i.validate()
i.emitMarshallable()
return i
switch ty := t.Type.(type) {
case *ast.StructType:
i.validateStruct()
i.emitMarshallableForStruct()
return i
case *ast.Ident:
i.validatePrimitiveNewtype(ty)
i.emitMarshallableForPrimitiveNewtype()
return i
default:
// This should've been filtered out by collectMarshallabeTypes.
panic(fmt.Sprintf("Unexpected type %+v", ty))
}
}
// generateOneTestSuite generates a test suite for the automatically generated
@@ -320,7 +337,7 @@ func (g *Generator) Run() error {
for i, a := range asts {
// Collect type declarations marked for code generation and generate
// Marshallable interfaces.
for _, t := range g.collectMarshallabeTypes(a, fsets[i]) {
for _, t := range g.collectMarshallableTypes(a, fsets[i]) {
impl := g.generateOne(t, fsets[i])
// Collect Marshallable types referenced by the generated code.
for ref, _ := range impl.ms {
@@ -55,9 +55,6 @@ func (g *interfaceGenerator) typeName() string {
// newinterfaceGenerator creates a new interface generator.
func newInterfaceGenerator(t *ast.TypeSpec, fset *token.FileSet) *interfaceGenerator {
if _, ok := t.Type.(*ast.StructType); !ok {
panic(fmt.Sprintf("Attempting to generate code for a not struct type %v", t))
}
g := &interfaceGenerator{
t: t,
r: receiverName(t),
@@ -103,9 +100,31 @@ func (g *interfaceGenerator) abortAt(p token.Pos, msg string) {
abortAt(g.f.Position(p), msg)
}
// validate ensures the type we're working with can be marshalled. These checks
// are done ahead of time and in one place so we can make assumptions later.
func (g *interfaceGenerator) validate() {
func (g *interfaceGenerator) validatePrimitiveNewtype(t *ast.Ident) {
switch t.Name {
case "int8", "uint8", "byte", "int16", "uint16", "int32", "uint32", "int64", "uint64":
// These are the only primitive types we're allow. Below, we provide
// suggestions for some disallowed types and reject them, then attempt
// to marshal any remaining types by invoking the marshal.Marshallable
// interface on them. If these types don't actually implement
// marshal.Marshallable, compilation of the generated code will fail
// with an appropriate error message.
return
case "int":
g.abortAt(t.Pos(), "Type 'int' has ambiguous width, use int32 or int64")
case "uint":
g.abortAt(t.Pos(), "Type 'uint' has ambiguous width, use uint32 or uint64")
case "string":
g.abortAt(t.Pos(), "Type 'string' is dynamically-sized and cannot be marshalled, use a fixed size byte array '[...]byte' instead")
default:
debugfAt(g.f.Position(t.Pos()), fmt.Sprintf("Found derived type '%s', will attempt dispatch via marshal.Marshallable.\n", t.Name))
}
}
// validateStruct ensures the type we're working with can be marshalled. These
// checks are done ahead of time and in one place so we can make assumptions
// later.
func (g *interfaceGenerator) validateStruct() {
g.forEachField(func(f *ast.Field) {
if len(f.Names) == 0 {
g.abortAt(f.Pos(), "Cannot marshal structs with embedded fields, give the field a name; use '_' for anonymous fields such as padding fields")
@@ -115,25 +134,7 @@ func (g *interfaceGenerator) validate() {
g.forEachField(func(f *ast.Field) {
fieldDispatcher{
primitive: func(_, t *ast.Ident) {
switch t.Name {
case "int8", "uint8", "byte", "int16", "uint16", "int32", "uint32", "int64", "uint64":
// These are the only primitive types we're allow. Below, we
// provide suggestions for some disallowed types and reject
// them, then attempt to marshal any remaining types by
// invoking the marshal.Marshallable interface on them. If
// these types don't actually implement
// marshal.Marshallable, compilation of the generated code
// will fail with an appropriate error message.
return
case "int":
g.abortAt(f.Pos(), "Type 'int' has ambiguous width, use int32 or int64")
case "uint":
g.abortAt(f.Pos(), "Type 'uint' has ambiguous width, use uint32 or uint64")
case "string":
g.abortAt(f.Pos(), "Type 'string' is dynamically-sized and cannot be marshalled, use a fixed size byte array '[...]byte' instead")
default:
debugfAt(g.f.Position(f.Pos()), fmt.Sprintf("Found derived type '%s', will attempt dispatch via marshal.Marshallable.\n", t.Name))
}
g.validatePrimitiveNewtype(t)
},
selector: func(_, _, _ *ast.Ident) {
// No validation to perform on selector fields. However this
@@ -190,7 +191,8 @@ func (g *interfaceGenerator) shiftDynamic(bufVar, name string) {
g.emit("%s = %s[%s.SizeBytes():]\n", bufVar, bufVar, name)
}
func (g *interfaceGenerator) marshalScalar(accessor, typ string, bufVar string) {
// marshalStructFieldScalar writes a single scalar field from a struct to a byte slice.
func (g *interfaceGenerator) marshalStructFieldScalar(accessor, typ, bufVar string) {
switch typ {
case "int8", "uint8", "byte":
g.emit("%s[0] = byte(%s)\n", bufVar, accessor)
@@ -213,43 +215,27 @@ func (g *interfaceGenerator) marshalScalar(accessor, typ string, bufVar string)
}
}
func (g *interfaceGenerator) unmarshalScalar(accessor, typ string, bufVar string) {
// unmarshalStructFieldScalar reads a single scalar field from a struct, from a
// byte slice.
func (g *interfaceGenerator) unmarshalStructFieldScalar(accessor, typ, bufVar string) {
switch typ {
case "int8":
g.emit("%s = int8(%s[0])\n", accessor, bufVar)
g.shift(bufVar, 1)
case "uint8":
g.emit("%s = uint8(%s[0])\n", accessor, bufVar)
g.shift(bufVar, 1)
case "byte":
g.emit("%s = %s[0]\n", accessor, bufVar)
g.shift(bufVar, 1)
case "int16":
case "int8", "uint8":
g.emit("%s = %s(%s[0])\n", accessor, typ, bufVar)
g.shift(bufVar, 1)
case "int16", "uint16":
g.recordUsedImport("usermem")
g.emit("%s = int16(usermem.ByteOrder.Uint16(%s[:2]))\n", accessor, bufVar)
g.emit("%s = %s(usermem.ByteOrder.Uint16(%s[:2]))\n", accessor, typ, bufVar)
g.shift(bufVar, 2)
case "uint16":
case "int32", "uint32":
g.recordUsedImport("usermem")
g.emit("%s = usermem.ByteOrder.Uint16(%s[:2])\n", accessor, bufVar)
g.shift(bufVar, 2)
case "int32":
g.recordUsedImport("usermem")
g.emit("%s = int32(usermem.ByteOrder.Uint32(%s[:4]))\n", accessor, bufVar)
g.emit("%s = %s(usermem.ByteOrder.Uint32(%s[:4]))\n", accessor, typ, bufVar)
g.shift(bufVar, 4)
case "uint32":
case "int64", "uint64":
g.recordUsedImport("usermem")
g.emit("%s = usermem.ByteOrder.Uint32(%s[:4])\n", accessor, bufVar)
g.shift(bufVar, 4)
case "int64":
g.recordUsedImport("usermem")
g.emit("%s = int64(usermem.ByteOrder.Uint64(%s[:8]))\n", accessor, bufVar)
g.shift(bufVar, 8)
case "uint64":
g.recordUsedImport("usermem")
g.emit("%s = usermem.ByteOrder.Uint64(%s[:8])\n", accessor, bufVar)
g.emit("%s = %s(usermem.ByteOrder.Uint64(%s[:8]))\n", accessor, typ, bufVar)
g.shift(bufVar, 8)
default:
g.emit("%s.UnmarshalBytes(%s[:%s.SizeBytes()])\n", accessor, bufVar, accessor)
@@ -258,6 +244,49 @@ func (g *interfaceGenerator) unmarshalScalar(accessor, typ string, bufVar string
}
}
// marshalPrimitiveScalar writes a single primitive variable to a byte slice.
func (g *interfaceGenerator) marshalPrimitiveScalar(accessor, typ, bufVar string) {
switch typ {
case "int8", "uint8", "byte":
g.emit("%s[0] = byte(*%s)\n", bufVar, accessor)
case "int16", "uint16":
g.recordUsedImport("usermem")
g.emit("usermem.ByteOrder.PutUint16(%s[:2], uint16(*%s))\n", bufVar, accessor)
case "int32", "uint32":
g.recordUsedImport("usermem")
g.emit("usermem.ByteOrder.PutUint32(%s[:4], uint32(*%s))\n", bufVar, accessor)
case "int64", "uint64":
g.recordUsedImport("usermem")
g.emit("usermem.ByteOrder.PutUint64(%s[:8], uint64(*%s))\n", bufVar, accessor)
default:
g.emit("inner := (*%s)(%s)\n", typ, accessor)
g.emit("inner.MarshalBytes(%s[:%s.SizeBytes()])\n", bufVar, accessor)
}
}
// unmarshalPrimitiveScalar read a single primitive variable from a byte slice.
func (g *interfaceGenerator) unmarshalPrimitiveScalar(accessor, typ, bufVar, typeCast string) {
switch typ {
case "byte":
g.emit("*%s = %s(%s[0])\n", accessor, typeCast, bufVar)
case "int8", "uint8":
g.emit("*%s = %s(%s(%s[0]))\n", accessor, typeCast, typ, bufVar)
case "int16", "uint16":
g.recordUsedImport("usermem")
g.emit("*%s = %s(%s(usermem.ByteOrder.Uint16(%s[:2])))\n", accessor, typeCast, typ, bufVar)
case "int32", "uint32":
g.recordUsedImport("usermem")
g.emit("*%s = %s(%s(usermem.ByteOrder.Uint32(%s[:4])))\n", accessor, typeCast, typ, bufVar)
case "int64", "uint64":
g.recordUsedImport("usermem")
g.emit("*%s = %s(%s(usermem.ByteOrder.Uint64(%s[:8])))\n", accessor, typeCast, typ, bufVar)
default:
g.emit("inner := (*%s)(%s)\n", typ, accessor)
g.emit("inner.UnmarshalBytes(%s[:%s.SizeBytes()])\n", bufVar, accessor)
}
}
// areFieldsPackedExpression returns a go expression checking whether g.t's fields are
// packed. Returns "", false if g.t has no fields that may be potentially
// packed, otherwise returns <clause>, true, where <clause> is an expression
@@ -274,7 +303,7 @@ func (g *interfaceGenerator) areFieldsPackedExpression() (string, bool) {
return strings.Join(cs, " && "), true
}
func (g *interfaceGenerator) emitMarshallable() {
func (g *interfaceGenerator) emitMarshallableForStruct() {
// Is g.t a packed struct without consideing field types?
thisPacked := true
g.forEachField(func(f *ast.Field) {
@@ -357,10 +386,10 @@ func (g *interfaceGenerator) emitMarshallable() {
}
return
}
g.marshalScalar(g.fieldAccessor(n), t.Name, "dst")
g.marshalStructFieldScalar(g.fieldAccessor(n), t.Name, "dst")
},
selector: func(n, tX, tSel *ast.Ident) {
g.marshalScalar(g.fieldAccessor(n), fmt.Sprintf("%s.%s", tX.Name, tSel.Name), "dst")
g.marshalStructFieldScalar(g.fieldAccessor(n), fmt.Sprintf("%s.%s", tX.Name, tSel.Name), "dst")
},
array: func(n, t *ast.Ident, size int) {
if n.Name == "_" {
@@ -377,9 +406,9 @@ func (g *interfaceGenerator) emitMarshallable() {
return
}
g.emit("for i := 0; i < %d; i++ {\n", size)
g.emit("for idx := 0; idx < %d; idx++ {\n", size)
g.inIndent(func() {
g.marshalScalar(fmt.Sprintf("%s[i]", g.fieldAccessor(n)), t.Name, "dst")
g.marshalStructFieldScalar(fmt.Sprintf("%s[idx]", g.fieldAccessor(n)), t.Name, "dst")
})
g.emit("}\n")
},
@@ -406,10 +435,10 @@ func (g *interfaceGenerator) emitMarshallable() {
}
return
}
g.unmarshalScalar(g.fieldAccessor(n), t.Name, "src")
g.unmarshalStructFieldScalar(g.fieldAccessor(n), t.Name, "src")
},
selector: func(n, tX, tSel *ast.Ident) {
g.unmarshalScalar(g.fieldAccessor(n), fmt.Sprintf("%s.%s", tX.Name, tSel.Name), "src")
g.unmarshalStructFieldScalar(g.fieldAccessor(n), fmt.Sprintf("%s.%s", tX.Name, tSel.Name), "src")
},
array: func(n, t *ast.Ident, size int) {
if n.Name == "_" {
@@ -426,9 +455,9 @@ func (g *interfaceGenerator) emitMarshallable() {
return
}
g.emit("for i := 0; i < %d; i++ {\n", size)
g.emit("for idx := 0; idx < %d; idx++ {\n", size)
g.inIndent(func() {
g.unmarshalScalar(fmt.Sprintf("%s[i]", g.fieldAccessor(n)), t.Name, "src")
g.unmarshalStructFieldScalar(fmt.Sprintf("%s[idx]", g.fieldAccessor(n)), t.Name, "src")
})
g.emit("}\n")
},
@@ -650,3 +679,144 @@ func (g *interfaceGenerator) emitMarshallable() {
})
g.emit("}\n\n")
}
// emitMarshallableForPrimitiveNewtype outputs code to implement the
// marshal.Marshallable interface for a newtype on a primitive. Primitive
// newtypes are always packed, so we can omit the various fallbacks required for
// non-packed structs.
func (g *interfaceGenerator) emitMarshallableForPrimitiveNewtype() {
g.recordUsedImport("io")
g.recordUsedImport("marshal")
g.recordUsedImport("reflect")
g.recordUsedImport("runtime")
g.recordUsedImport("safecopy")
g.recordUsedImport("unsafe")
g.recordUsedImport("usermem")
nt := g.t.Type.(*ast.Ident)
g.emit("// SizeBytes implements marshal.Marshallable.SizeBytes.\n")
g.emit("func (%s *%s) SizeBytes() int {\n", g.r, g.typeName())
g.inIndent(func() {
if size, dynamic := g.scalarSize(nt); !dynamic {
g.emit("return %d\n", size)
} else {
g.emit("return (*%s)(nil).SizeBytes()\n", nt.Name)
}
})
g.emit("}\n\n")
g.emit("// MarshalBytes implements marshal.Marshallable.MarshalBytes.\n")
g.emit("func (%s *%s) MarshalBytes(dst []byte) {\n", g.r, g.typeName())
g.inIndent(func() {
g.marshalPrimitiveScalar(g.r, nt.Name, "dst")
})
g.emit("}\n\n")
g.emit("// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.\n")
g.emit("func (%s *%s) UnmarshalBytes(src []byte) {\n", g.r, g.typeName())
g.inIndent(func() {
g.unmarshalPrimitiveScalar(g.r, nt.Name, "src", g.typeName())
})
g.emit("}\n\n")
g.emit("// Packed implements marshal.Marshallable.Packed.\n")
g.emit("func (%s *%s) Packed() bool {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("// Scalar newtypes are always packed.\n")
g.emit("return true\n")
})
g.emit("}\n\n")
g.emit("// MarshalUnsafe implements marshal.Marshallable.MarshalUnsafe.\n")
g.emit("func (%s *%s) MarshalUnsafe(dst []byte) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("safecopy.CopyIn(dst, unsafe.Pointer(%s))\n", g.r)
})
g.emit("}\n\n")
g.emit("// UnmarshalUnsafe implements marshal.Marshallable.UnmarshalUnsafe.\n")
g.emit("func (%s *%s) UnmarshalUnsafe(src []byte) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("safecopy.CopyOut(unsafe.Pointer(%s), src)\n", g.r)
})
g.emit("}\n\n")
g.emit("// CopyOut implements marshal.Marshallable.CopyOut.\n")
g.emit("func (%s *%s) CopyOut(task marshal.Task, addr usermem.Addr) error {\n", g.r, g.typeName())
g.inIndent(func() {
// Fast serialization.
g.emit("// Bypass escape analysis on %s. The no-op arithmetic operation on the\n", g.r)
g.emit("// pointer makes the compiler think val doesn't depend on %s.\n", g.r)
g.emit("// See src/runtime/stubs.go:noescape() in the golang toolchain.\n")
g.emit("ptr := unsafe.Pointer(%s)\n", g.r)
g.emit("val := uintptr(ptr)\n")
g.emit("val = val^0\n\n")
g.emit("// Construct a slice backed by %s's underlying memory.\n", g.r)
g.emit("var buf []byte\n")
g.emit("hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))\n")
g.emit("hdr.Data = val\n")
g.emit("hdr.Len = %s.SizeBytes()\n", g.r)
g.emit("hdr.Cap = %s.SizeBytes()\n\n", g.r)
g.emit("_, err := task.CopyOutBytes(addr, buf)\n")
g.emit("// Since we bypassed the compiler's escape analysis, indicate that %s\n", g.r)
g.emit("// must live until after the CopyOutBytes.\n")
g.emit("runtime.KeepAlive(%s)\n", g.r)
g.emit("return err\n")
})
g.emit("}\n\n")
g.emit("// CopyIn implements marshal.Marshallable.CopyIn.\n")
g.emit("func (%s *%s) CopyIn(task marshal.Task, addr usermem.Addr) error {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("// Bypass escape analysis on %s. The no-op arithmetic operation on the\n", g.r)
g.emit("// pointer makes the compiler think val doesn't depend on %s.\n", g.r)
g.emit("// See src/runtime/stubs.go:noescape() in the golang toolchain.\n")
g.emit("ptr := unsafe.Pointer(%s)\n", g.r)
g.emit("val := uintptr(ptr)\n")
g.emit("val = val^0\n\n")
g.emit("// Construct a slice backed by %s's underlying memory.\n", g.r)
g.emit("var buf []byte\n")
g.emit("hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))\n")
g.emit("hdr.Data = val\n")
g.emit("hdr.Len = %s.SizeBytes()\n", g.r)
g.emit("hdr.Cap = %s.SizeBytes()\n\n", g.r)
g.emit("_, err := task.CopyInBytes(addr, buf)\n")
g.emit("// Since we bypassed the compiler's escape analysis, indicate that %s\n", g.r)
g.emit("// must live until after the CopyInBytes.\n")
g.emit("runtime.KeepAlive(%s)\n", g.r)
g.emit("return err\n")
})
g.emit("}\n\n")
g.emit("// WriteTo implements io.WriterTo.WriteTo.\n")
g.emit("func (%s *%s) WriteTo(w io.Writer) (int64, error) {\n", g.r, g.typeName())
g.inIndent(func() {
g.emit("// Bypass escape analysis on %s. The no-op arithmetic operation on the\n", g.r)
g.emit("// pointer makes the compiler think val doesn't depend on %s.\n", g.r)
g.emit("// See src/runtime/stubs.go:noescape() in the golang toolchain.\n")
g.emit("ptr := unsafe.Pointer(%s)\n", g.r)
g.emit("val := uintptr(ptr)\n")
g.emit("val = val^0\n\n")
g.emit("// Construct a slice backed by %s's underlying memory.\n", g.r)
g.emit("var buf []byte\n")
g.emit("hdr := (*reflect.SliceHeader)(unsafe.Pointer(&buf))\n")
g.emit("hdr.Data = val\n")
g.emit("hdr.Len = %s.SizeBytes()\n", g.r)
g.emit("hdr.Cap = %s.SizeBytes()\n\n", g.r)
g.emit("len, err := w.Write(buf)\n")
g.emit("// Since we bypassed the compiler's escape analysis, indicate that %s\n", g.r)
g.emit("// must live until after the Write.\n")
g.emit("runtime.KeepAlive(%s)\n", g.r)
g.emit("return int64(len), err\n")
})
g.emit("}\n\n")
}
+2 -13
View File
@@ -49,9 +49,6 @@ type testGenerator struct {
}
func newTestGenerator(t *ast.TypeSpec) *testGenerator {
if _, ok := t.Type.(*ast.StructType); !ok {
panic(fmt.Sprintf("Attempting to generate code for a not struct type %v", t))
}
g := &testGenerator{
t: t,
r: receiverName(t),
@@ -69,14 +66,6 @@ func (g *testGenerator) typeName() string {
return g.t.Name.Name
}
func (g *testGenerator) forEachField(fn func(f *ast.Field)) {
// This is guaranteed to succeed because g.t is always a struct.
st := g.t.Type.(*ast.StructType)
for _, field := range st.Fields.List {
fn(field)
}
}
func (g *testGenerator) testFuncName(base string) string {
return fmt.Sprintf("%s%s", base, strings.Title(g.t.Name.Name))
}
@@ -89,7 +78,7 @@ func (g *testGenerator) inTestFunction(name string, body func()) {
func (g *testGenerator) emitTestNonZeroSize() {
g.inTestFunction("TestSizeNonZero", func() {
g.emit("x := &%s{}\n", g.typeName())
g.emit("var x %v\n", g.typeName())
g.emit("if x.SizeBytes() == 0 {\n")
g.inIndent(func() {
g.emit("t.Fatal(\"Marshallable.SizeBytes() should not return zero\")\n")
@@ -100,7 +89,7 @@ func (g *testGenerator) emitTestNonZeroSize() {
func (g *testGenerator) emitTestSuspectAlignment() {
g.inTestFunction("TestSuspectAlignment", func() {
g.emit("x := %s{}\n", g.typeName())
g.emit("var x %v\n", g.typeName())
g.emit("analysis.AlignmentCheck(t, reflect.TypeOf(x))\n")
})
}
+10
View File
@@ -103,3 +103,13 @@ type Stat struct {
CTime Timespec
_ [3]int64
}
// SignalSet is an example marshallable newtype on a primitive.
//
// +marshal
type SignalSet uint64
// SignalSetAlias is an example newtype on another marshallable type.
//
// +marshal
type SignalSetAlias SignalSet