Skip to content

Commit

Permalink
codecgen: Honor name from struct tag during encoding, and support une…
Browse files Browse the repository at this point in the history
…xported types.

To support unexported types, we separate the temporary main package file from the package-level file.
This way, the package-level file can reference the unexported types, and just export a method
which can be called by the main package file.

In addition, we use the field name from the struct tag (structfieldinfo.encName) when encoding structs.

We also update the GenVersion as fundamental codecgen changes happened.
This way, users will be forced to update their generated code when using new version of codec.

Fixes #60
Fixes #61
  • Loading branch information
ugorji committed Apr 22, 2015
1 parent 141348d commit f1401fe
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 21 deletions.
73 changes: 54 additions & 19 deletions codec/codecgen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,40 @@ import (
"time"
)

const genFrunTmpl = `//+build ignore
const genFrunMainTmpl = `//+build ignore
package main
{{ if .Types }}import "{{ .ImportPath }}"{{ end }}
func main() {
{{ $.PackageName }}.CodecGenTempWrite{{ .RandString }}()
}
`

const genFrunPkgTmpl = `//+build codecgen
package {{ $.PackageName }}
import (
{{ if not .CodecPkgFiles }}{{ .CodecPkgName }} "{{ .CodecImportPath }}"{{ end }}
{{/*
{{ if .Types }}"{{ .ImportPath }}"{{ end }}
"io"
*/}}
"os"
"reflect"
"io"
"bytes"
"go/format"
)
{{/* This is not used anymore. Remove it.
func write(w io.Writer, s string) {
if _, err := io.WriteString(w, s); err != nil {
panic(err)
}
}
*/}}
func main() {
func CodecGenTempWrite{{ .RandString }}() {
fout, err := os.Create("{{ .OutFile }}")
if err != nil {
panic(err)
Expand All @@ -52,10 +65,10 @@ func main() {
var typs []reflect.Type
{{ range $index, $element := .Types }}
var t{{ $index }} {{ $.PackageName }}.{{ . }}
var t{{ $index }} {{ . }}
typs = append(typs, reflect.TypeOf(t{{ $index }}))
{{ end }}
{{ .CodecPkgName }}.Gen(&out, "{{ .BuildTag }}", "{{ .PackageName }}", {{ .UseUnsafe }}, typs...)
{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(&out, "{{ .BuildTag }}", "{{ .PackageName }}", {{ .UseUnsafe }}, typs...)
bout, err := format.Source(out.Bytes())
if err != nil {
fout.Write(out.Bytes())
Expand All @@ -67,10 +80,14 @@ func main() {
`

// Generate is given a list of *.go files to parse, and an output file (fout).
// It finds all types T in the files, and it creates a tmp file (frun).
// frun calls *genRunner.Selfer to write Selfer impls for each T.
//
// It finds all types T in the files, and it creates 2 tmp files (frun).
// - main package file passed to 'go run'
// - package level file which calls *genRunner.Selfer to write Selfer impls for each T.
// We use a package level file so that it can reference unexported types in the package being worked on.
// Tool then executes: "go run __frun__" which creates fout.
// fout contains Codec(En|De)codeSelf implementations for every type T.
//
func Generate(outfile, buildTag, codecPkgPath string, useUnsafe bool, goRunTag string,
regexName *regexp.Regexp, deleteTempFile bool, infiles ...string) (err error) {
// For each file, grab AST, find each type, and write a call to it.
Expand Down Expand Up @@ -98,6 +115,7 @@ func Generate(outfile, buildTag, codecPkgPath string, useUnsafe bool, goRunTag s
ImportPath string
OutFile string
PackageName string
RandString string
BuildTag string
Types []string
CodecPkgFiles bool
Expand All @@ -109,6 +127,7 @@ func Generate(outfile, buildTag, codecPkgPath string, useUnsafe bool, goRunTag s
CodecImportPath: codecPkgPath,
BuildTag: buildTag,
UseUnsafe: useUnsafe,
RandString: strconv.FormatInt(time.Now().UnixNano(), 10),
}
tv.ImportPath = pkg.ImportPath
if tv.ImportPath == tv.CodecImportPath {
Expand Down Expand Up @@ -136,7 +155,8 @@ func Generate(outfile, buildTag, codecPkgPath string, useUnsafe bool, goRunTag s
if gd, ok := d.(*ast.GenDecl); ok {
for _, dd := range gd.Specs {
if td, ok := dd.(*ast.TypeSpec); ok {
if len(td.Name.Name) == 0 || td.Name.Name[0] > 'Z' || td.Name.Name[0] < 'A' {
// if len(td.Name.Name) == 0 || td.Name.Name[0] > 'Z' || td.Name.Name[0] < 'A' {
if len(td.Name.Name) == 0 {
continue
}

Expand Down Expand Up @@ -164,43 +184,58 @@ func Generate(outfile, buildTag, codecPkgPath string, useUnsafe bool, goRunTag s
return
}

var frun *os.File
var frunMain, frunPkg *os.File
// we cannot use ioutil.TempFile, because we cannot guarantee the file suffix (.go).
// Also, we cannot create file in temp directory, because go run will not work (as it needs to see the types here).
// Consequently, create the temp file in the current directory, and remove when done.
// frun, err = ioutil.TempFile("", "codecgen-")
// frunName := filepath.Join(os.TempDir(), "codecgen-"+strconv.FormatInt(time.Now().UnixNano(), 10)+".go")
frunName := "codecgen-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".generated.go"
os.Remove(frunName)
if frun, err = os.Create(frunName); err != nil {
frunMainName := "codecgen-main-" + tv.RandString + ".generated.go"
frunPkgName := "codecgen-pkg-" + tv.RandString + ".generated.go"
os.Remove(frunMainName)
os.Remove(frunPkgName)
if frunMain, err = os.Create(frunMainName); err != nil {
return
}
if frunPkg, err = os.Create(frunPkgName); err != nil {
return
}
defer func() {
frun.Close()
frunMain.Close()
frunPkg.Close()
if deleteTempFile {
os.Remove(frun.Name())
os.Remove(frunMain.Name())
os.Remove(frunPkg.Name())
}
}()

t := template.New("")
if t, err = t.Parse(genFrunTmpl); err != nil {
if t, err = t.Parse(genFrunMainTmpl); err != nil {
return
}
if err = t.Execute(frunMain, &tv); err != nil {
return
}
frunMain.Close()
t = template.New("")
if t, err = t.Parse(genFrunPkgTmpl); err != nil {
return
}
if err = t.Execute(frun, &tv); err != nil {
if err = t.Execute(frunPkg, &tv); err != nil {
return
}
frun.Close()
frunPkg.Close()

// remove the outfile, so that running "go run ..." will not think that the types in the outfile already exist.
os.Remove(outfile)

// execute go run frun
cmd := exec.Command("go", "run", "-tags="+goRunTag, frun.Name())
cmd := exec.Command("go", "run", "-tags="+goRunTag, frunMain.Name())
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
if err = cmd.Run(); err != nil {
err = fmt.Errorf("Error running go run %s. Error: %v. stdout/err: %s", frun.Name(), err, buf.Bytes())
err = fmt.Errorf("Error running go run %s. Error: %v. stdout/err: %s", frunMain.Name(), err, buf.Bytes())
return
}
os.Stdout.Write(buf.Bytes())
Expand Down
5 changes: 3 additions & 2 deletions codec/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ import (
// It was a concious decision to have gen.go always explicitly call EncodeNil or TryDecodeAsNil.
// This way, there isn't a function call overhead just to see that we should not enter a block of code.

const GenVersion = 1 // increment this value each time codecgen changes fundamentally.
const GenVersion = 2 // increment this value each time codecgen changes fundamentally.

const (
genCodecPkg = "codec1978"
Expand Down Expand Up @@ -713,7 +713,8 @@ func (x *genRunner) encStruct(varname string, rtid uintptr, t reflect.Type) {
x.line("r.EncodeMapEntrySeparator()")
x.line("}")
}
x.line("r.EncodeString(codecSelferC_UTF8" + x.xs + ", string(\"" + t2.Name + "\"))")
// x.line("r.EncodeString(codecSelferC_UTF8" + x.xs + ", string(\"" + t2.Name + "\"))")
x.line("r.EncodeString(codecSelferC_UTF8" + x.xs + ", string(\"" + si.encName + "\"))")
x.line("if " + sepVarname + " {")
x.line("r.EncodeMapKVSeparator()")
x.line("}")
Expand Down
8 changes: 8 additions & 0 deletions codec/values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ type TestStruc struct {
Nteststruc *TestStruc
}

// small struct for testing that codecgen works for unexported types
type tLowerFirstLetter struct {
I int
u uint64
S string
b []byte
}

func newTestStruc(depth int, bench bool, useInterface, useStringKeyOnly bool) (ts *TestStruc) {
var i64a, i64b, i64c, i64d int64 = 64, 6464, 646464, 64646464

Expand Down

0 comments on commit f1401fe

Please sign in to comment.