blob: bfd8f653a1628f698be1e8048f708b71d1c6ceba [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package golang
import (
"fmt"
"strconv"
"strings"
"v.io/v23/vdl"
"v.io/v23/vdlroot/vdltool"
"v.io/x/ref/lib/vdl/compile"
)
func localIdent(data *goData, file *compile.File, ident string) string {
if testingMode {
return ident
}
return data.Pkg(file.Package.GenPath) + ident
}
func nativeType(data *goData, native vdltool.GoType, wirePkg *compile.Package) string {
result := native.Type
for _, imp := range native.Imports {
// Translate the packages specified in the native type into local package
// identifiers. E.g. if the native type is "foo.Type" with import
// "path/to/foo", we need to replace "foo." in the native type with the
// local package identifier for "path/to/foo".
if strings.Contains(result, imp.Name+".") {
// Add the import dependency if there is a match.
pkg := data.Pkg(imp.Path)
result = strings.Replace(result, imp.Name+".", pkg, -1)
}
}
data.AddForcedPkg(wirePkg.GenPath)
return result
}
func toNative(data *goData, native vdltool.GoType, ttWire *vdl.Type) string {
if native.ToNative != "" {
result := native.ToNative
for _, imp := range native.Imports {
// Translate the packages specified in the native type into local package
// identifiers. E.g. if the native type is "foo.Type" with import
// "path/to/foo", we need to replace "foo." in the native type with the
// local package identifier for "path/to/foo".
if strings.Contains(result, imp.Name+".") {
// Add the import dependency if there is a match.
pkg := data.Pkg(imp.Path)
result = strings.Replace(result, imp.Name+".", pkg, -1)
}
}
return result
}
return typeGoWire(data, ttWire) + "ToNative"
}
func noCustomNative(native vdltool.GoType) bool {
return native.ToNative == "" && native.FromNative == ""
}
func typeHasNoCustomNative(data *goData, def *compile.TypeDef) bool {
if native, _, ok := findNativeType(data.Env, def.Type); ok {
return noCustomNative(native)
}
return true
}
func packageIdent(file *compile.File, ident string) string {
if testingMode {
return ident
}
return file.Package.Name + "." + ident
}
func qualifiedIdent(file *compile.File, ident string) string {
if testingMode {
return ident
}
return file.Package.QualifiedName(ident)
}
// typeGo translates vdl.Type into a Go type.
func typeGo(data *goData, t *vdl.Type) string {
if native, pkg, ok := findNativeType(data.Env, t); ok {
return nativeType(data, native, pkg)
}
return typeGoWire(data, t)
}
func typeGoWire(data *goData, t *vdl.Type) string {
if testingMode {
if t.Name() != "" {
return t.Name()
}
}
// Terminate recursion at defined types, which include both user-defined types
// (enum, struct, union) and built-in types.
if def := data.Env.FindTypeDef(t); def != nil {
switch {
case t == vdl.AnyType:
switch goAnyRepMode(data.Package) {
case goAnyRepRawBytes:
return "*" + data.Pkg("v.io/v23/vom") + "RawBytes"
case goAnyRepValue:
return "*" + data.Pkg("v.io/v23/vdl") + "Value"
default:
return "interface{}"
}
case t == vdl.TypeObjectType:
return "*" + data.Pkg("v.io/v23/vdl") + "Type"
case def.File == compile.BuiltInFile:
// Built-in primitives just use their name.
return def.Name
}
return localIdent(data, def.File, def.Name)
}
// Special-cases to allow error constants.
switch {
case t == vdl.ErrorType.Elem():
return data.Pkg("v.io/v23/vdl") + "WireError"
case t == vdl.ErrorType.Elem().Field(1).Type:
return data.Pkg("v.io/v23/vdl") + "WireRetryCode"
}
// Otherwise recurse through the type.
switch t.Kind() {
case vdl.Optional:
return "*" + typeGo(data, t.Elem())
case vdl.Array:
return "[" + strconv.Itoa(t.Len()) + "]" + typeGo(data, t.Elem())
case vdl.List:
return "[]" + typeGo(data, t.Elem())
case vdl.Set:
return "map[" + typeGo(data, t.Key()) + "]struct{}"
case vdl.Map:
return "map[" + typeGo(data, t.Key()) + "]" + typeGo(data, t.Elem())
default:
panic(fmt.Errorf("vdl: typeGo unhandled type %v %v", t.Kind(), t))
}
}
type goAnyRep int
const (
goAnyRepRawBytes goAnyRep = iota // Use vom.RawBytes to represent any.
goAnyRepValue // Use vdl.Value to represent any.
goAnyRepInterface // Use interface{} to represent any.
)
// goAnyRepMode returns the representation of the any type. By default we use
// vom.RawBytes, but for some hard-coded cases we use vdl.Value or interface{}.
//
// This is hard-coded because we don't want to allow the user to configure this
// behavior, since it's subtle and tricky. E.g. if the user picks the
// interface{} representation, their generated server stub would fail on vom
// decoding if the type isn't registered.
func goAnyRepMode(pkg *compile.Package) goAnyRep {
if strings.HasPrefix(pkg.Path, "v.io/v23/vom/testdata") {
// The vom/testdata/... packages use vdl.Value due to an import cycle: vom
// imports testdata/...
return goAnyRepValue
}
switch pkg.Path {
case "v.io/v23/vdl":
// The vdl package uses vdl.Value due to an import cycle: vom imports vdl.
return goAnyRepValue
case "signature":
// The signature package uses vdl.Value for two reasons:
// - an import cycle: vom imports vdlroot imports signature
// - any is used for method tags, and these are used via reflection,
// although interface{} is a reasonable alternative.
return goAnyRepValue
case "v.io/v23/vdl/vdltest", "v.io/v23/vom/vomtest":
// The vdltest and vomtest packages use interface{} for convenience in
// setting up test values.
return goAnyRepInterface
}
return goAnyRepRawBytes
}
// defineType returns the type definition for def.
func defineType(data *goData, def *compile.TypeDef) string {
s := fmt.Sprintf("%stype %s ", def.Doc, def.Name)
switch t := def.Type; t.Kind() {
case vdl.Enum:
s += fmt.Sprintf("int%s\nconst (", def.DocSuffix)
for ix := 0; ix < t.NumEnumLabel(); ix++ {
s += fmt.Sprintf("\n\t%s%s%s", def.LabelDoc[ix], def.Name, t.EnumLabel(ix))
if ix == 0 {
s += fmt.Sprintf(" %s = iota", def.Name)
}
s += def.LabelDocSuffix[ix]
}
s += fmt.Sprintf("\n)"+
"\n\n// %[1]sAll holds all labels for %[1]s."+
"\nvar %[1]sAll = [...]%[1]s{%[2]s}"+
"\n\n// %[1]sFromString creates a %[1]s from a string label."+
"\nfunc %[1]sFromString(label string) (x %[1]s, err error) {"+
"\n\terr = x.Set(label)"+
"\n\treturn"+
"\n}"+
"\n\n// Set assigns label to x."+
"\nfunc (x *%[1]s) Set(label string) error {"+
"\n\tswitch label {",
def.Name,
commaEnumLabels(def.Name, t))
for ix := 0; ix < t.NumEnumLabel(); ix++ {
s += fmt.Sprintf("\n\tcase %[2]q, %[3]q:"+
"\n\t\t*x = %[1]s%[2]s"+
"\n\t\treturn nil", def.Name, t.EnumLabel(ix), strings.ToLower(t.EnumLabel(ix)))
}
s += fmt.Sprintf("\n\t}"+
"\n\t*x = -1"+
"\n\treturn "+data.Pkg("fmt")+"Errorf(\"unknown label %%q in %[2]s\", label)"+
"\n}"+
"\n\n// String returns the string label of x."+
"\nfunc (x %[1]s) String() string {"+
"\n\tswitch x {", def.Name, packageIdent(def.File, def.Name))
for ix := 0; ix < t.NumEnumLabel(); ix++ {
s += fmt.Sprintf("\n\tcase %[1]s%[2]s:"+
"\n\t\treturn %[2]q", def.Name, t.EnumLabel(ix))
}
s += fmt.Sprintf("\n\t}"+
"\n\treturn \"\""+
"\n}"+
"\n\nfunc (%[1]s) __VDLReflect(struct{"+
"\n\tName string `vdl:%[3]q`"+
"\n\tEnum struct{ %[2]s string }"+
"\n}) {"+
"\n}",
def.Name, commaEnumLabels("", t), qualifiedIdent(def.File, def.Name))
case vdl.Struct:
s += "struct {"
for ix := 0; ix < t.NumField(); ix++ {
f := t.Field(ix)
s += "\n\t" + def.FieldDoc[ix] + f.Name + " "
s += typeGo(data, f.Type) + def.FieldDocSuffix[ix]
}
s += "\n}" + def.DocSuffix
s += fmt.Sprintf("\n"+
"\nfunc (%[1]s) __VDLReflect(struct{"+
"\n\tName string `vdl:%[2]q`"+
"\n}) {"+
"\n}",
def.Name, qualifiedIdent(def.File, def.Name))
case vdl.Union:
s = fmt.Sprintf("type ("+
"\n\t// %[1]s represents any single field of the %[1]s union type."+
"\n\t%[2]s%[1]s interface {"+
"\n\t\t// Index returns the field index."+
"\n\t\tIndex() int"+
"\n\t\t// Interface returns the field value as an interface."+
"\n\t\tInterface() interface{}"+
"\n\t\t// Name returns the field name."+
"\n\t\tName() string"+
"\n\t\t// __VDLReflect describes the %[1]s union type."+
"\n\t\t__VDLReflect(__%[1]sReflect)", def.Name, docBreak(def.Doc))
if !data.SkipGenZeroReadWrite(def) {
s += fmt.Sprintf("\n\t\tVDLIsZero() bool"+
"\n\t\tVDLWrite(%[1]sEncoder) error", data.Pkg("v.io/v23/vdl"))
}
s += fmt.Sprintf("\n\t}%[1]s", def.DocSuffix)
for ix := 0; ix < t.NumField(); ix++ {
f := t.Field(ix)
s += fmt.Sprintf("\n\t// %[1]s%[2]s represents field %[2]s of the %[1]s union type."+
"\n\t%[4]s%[1]s%[2]s struct{ Value %[3]s }%[5]s",
def.Name, f.Name, typeGo(data, f.Type),
docBreak(def.FieldDoc[ix]), def.FieldDocSuffix[ix])
}
s += fmt.Sprintf("\n\t// __%[1]sReflect describes the %[1]s union type."+
"\n\t__%[1]sReflect struct {"+
"\n\t\tName string `vdl:%[2]q`"+
"\n\t\tType %[1]s", def.Name, qualifiedIdent(def.File, def.Name))
s += "\n\t\tUnion struct {"
for ix := 0; ix < t.NumField(); ix++ {
s += fmt.Sprintf("\n\t\t\t%[2]s %[1]s%[2]s", def.Name, t.Field(ix).Name)
}
s += fmt.Sprintf("\n\t\t}\n\t}\n)")
for ix := 0; ix < t.NumField(); ix++ {
f := t.Field(ix)
s += fmt.Sprintf("\n\nfunc (x %[1]s%[2]s) Index() int { return %[3]d }"+
"\nfunc (x %[1]s%[2]s) Interface() interface{} { return x.Value }"+
"\nfunc (x %[1]s%[2]s) Name() string { return \"%[2]s\" }"+
"\nfunc (x %[1]s%[2]s) __VDLReflect(__%[1]sReflect) {}",
def.Name, f.Name, ix)
}
default:
s += typeGo(data, def.BaseType) + def.DocSuffix
s += fmt.Sprintf("\n"+
"\nfunc (%[1]s) __VDLReflect(struct{"+
"\n\tName string `vdl:%[2]q`"+
"\n}) {"+
"\n}",
def.Name, qualifiedIdent(def.File, def.Name))
}
return s
}
func commaEnumLabels(prefix string, t *vdl.Type) (s string) {
for ix := 0; ix < t.NumEnumLabel(); ix++ {
if ix > 0 {
s += ", "
}
s += prefix
s += t.EnumLabel(ix)
}
return
}
func embedGo(data *goData, embed *compile.Interface) string {
return localIdent(data, embed.File, embed.Name)
}