blob: 78298a0b8a07e1248e8f6e2008a81505111e0986 [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
// TODO(toddw): Add tests for this logic.
import (
"sort"
"strconv"
"v.io/v23/vdl"
"v.io/x/ref/lib/vdl/compile"
)
// goImport represents a single import in the generated Go file.
// Example A: import "v.io/v23/abc"
// Example B: import foo "v.io/v23/abc"
type goImport struct {
// Name of the import.
// Example A: ""
// Example B: "foo"
Name string
// Path of the import.
// Example A: "v.io/v23/abc"
// Example B: "v.io/v23/abc"
Path string
// Local identifier within the generated go file to reference the imported
// package.
// Example A: "abc"
// Example B: "foo"
Local string
}
// goImports holds all imports for a generated Go file, splitting into two
// groups "system" and "user". The splitting is just for slightly nicer output,
// and to ensure we prefer system over user imports when dealing with package
// name collisions.
type goImports struct {
System, User []goImport
}
func newImports(file *compile.File, env *compile.Env) *goImports {
deps, user := computeDeps(file, env)
system := systemImports(deps, file)
seen := make(map[string]bool)
return &goImports{
System: system.Sort(seen),
User: user.Sort(seen),
}
}
// importMap maps from package path to package name. It's used to collect
// package import information.
type importMap map[string]string
// AddPackage adds a regular dependency on pkg; some block of generated code
// will reference the pkg.
func (im importMap) AddPackage(pkg *compile.Package) {
im[pkg.GenPath] = pkg.Name
}
// AddForcedPackage adds a "forced" dependency on pkg. This means that we need
// to import pkg even if no other block of generated code references the pkg.
func (im importMap) AddForcedPackage(pkg *compile.Package) {
if im[pkg.GenPath] == "" {
im[pkg.GenPath] = "_"
}
}
func (im importMap) DeletePackage(pkg *compile.Package) {
delete(im, pkg.GenPath)
}
func (im importMap) Sort(seen map[string]bool) []goImport {
var sortedPaths []string
for path := range im {
sortedPaths = append(sortedPaths, path)
}
sort.Strings(sortedPaths)
var ret []goImport
for _, path := range sortedPaths {
ret = append(ret, uniqueImport(im[path], path, seen))
}
return ret
}
// Each import must end up with a unique local name. Here's some examples.
// uniqueImport("a", "v.io/a", {}) -> goImport{"", "v.io/a", "a"}
// uniqueImport("z", "v.io/a", {}) -> goImport{"", "v.io/a", "z"}
// uniqueImport("a", "v.io/a", {"a"}) -> goImport{"a_2", "v.io/a", "a_2"}
// uniqueImport("a", "v.io/a", {"a", "a_2"}) -> goImport{"a_3", "v.io/a", "a_3"}
// uniqueImport("_", "v.io/a", {}) -> goImport{"_", "v.io/a", ""}
// uniqueImport("_", "v.io/a", {"a"}) -> goImport{"_", "v.io/a", ""}
// uniqueImport("_", "v.io/a", {"a", "a_2"}) -> goImport{"_", "v.io/a", ""}
func uniqueImport(pkgName, pkgPath string, seen map[string]bool) goImport {
if pkgName == "_" {
return goImport{"_", pkgPath, ""}
}
name := ""
iter := 1
for {
local := pkgName
if iter > 1 {
local += "_" + strconv.Itoa(iter)
name = local
}
if !seen[local] {
// Found a unique local name - return the import.
seen[local] = true
return goImport{name, pkgPath, local}
}
iter++
}
}
// LookupLocal returns the local identifier within the generated go file that
// identifies the given pkgPath.
func (x *goImports) LookupLocal(pkgPath string) string {
if local := lookupLocal(pkgPath, x.System); local != "" {
return local
}
return lookupLocal(pkgPath, x.User)
}
func lookupLocal(pkgPath string, imports []goImport) string {
ix := sort.Search(
len(imports),
func(i int) bool { return imports[i].Path >= pkgPath },
)
if ix < len(imports) && imports[ix].Path == pkgPath {
return imports[ix].Local
}
return ""
}
type deps struct {
any bool
typeObject bool
enumTypeDef bool
streamingMethods bool
methodTags bool
}
func computeDeps(file *compile.File, env *compile.Env) (deps, importMap) {
deps, user := &deps{}, make(importMap)
// TypeDef.Type is always defined in our package; add deps on the base type.
for _, def := range file.TypeDefs {
addTypeDeps(def.BaseType, env, deps, user)
if def.Type.Kind() == vdl.Enum {
deps.enumTypeDef = true
}
}
// Consts contribute their value types.
for _, def := range file.ConstDefs {
addValueTypeDeps(def.Value, env, deps, user)
}
// Interfaces contribute their arg types and tag values, as well as embedded
// interfaces.
for _, iface := range file.Interfaces {
for _, embed := range iface.TransitiveEmbeds() {
user.AddPackage(embed.File.Package)
}
for _, method := range iface.Methods {
for _, arg := range method.InArgs {
addTypeDeps(arg.Type, env, deps, user)
}
for _, arg := range method.OutArgs {
addTypeDeps(arg.Type, env, deps, user)
}
if stream := method.InStream; stream != nil {
addTypeDeps(stream, env, deps, user)
deps.streamingMethods = true
}
if stream := method.OutStream; stream != nil {
addTypeDeps(stream, env, deps, user)
deps.streamingMethods = true
}
for _, tag := range method.Tags {
addValueTypeDeps(tag, env, deps, user)
deps.methodTags = true
}
}
}
// Errors contribute their param types.
for _, def := range file.ErrorDefs {
for _, param := range def.Params {
addTypeDeps(param.Type, env, deps, user)
}
}
// Native types contribute their imports, for the auto-generated native
// conversion function type assertion.
for _, native := range file.Package.Config.Go.WireToNativeTypes {
for _, imp := range native.Imports {
user[imp.Path] = imp.Name
}
}
// Now remove self and built-in package dependencies. Every package can use
// itself and the built-in package, so we don't need to record this.
user.DeletePackage(file.Package)
user.DeletePackage(compile.BuiltInPackage)
return *deps, user
}
// Add package deps iff t is a defined (named) type.
func addTypeDepIfDefined(t *vdl.Type, env *compile.Env, deps *deps, user importMap) bool {
if t == vdl.AnyType {
deps.any = true
}
if t == vdl.TypeObjectType {
deps.typeObject = true
}
if def := env.FindTypeDef(t); def != nil {
pkg := def.File.Package
if native, ok := pkg.Config.Go.WireToNativeTypes[def.Name]; ok {
// There is a native type configured for this defined type. Add the
// imports corresponding to the native type.
for _, imp := range native.Imports {
user[imp.Path] = imp.Name
}
// Also add a "forced" import on the regular VDL package, to ensure the
// wire type is registered, to establish the wire<->native mapping.
user.AddForcedPackage(pkg)
} else {
// There's no native type configured for this defined type. Add the
// imports corresponding to the VDL package.
user.AddPackage(pkg)
}
return true
}
return false
}
// Add immediate package deps for t and subtypes of t.
func addTypeDeps(t *vdl.Type, env *compile.Env, deps *deps, user importMap) {
if addTypeDepIfDefined(t, env, deps, user) {
// We don't track transitive dependencies, only immediate dependencies.
return
}
// Not all types have TypeDefs; e.g. unnamed lists have no corresponding
// TypeDef, so we need to traverse those recursively.
addSubTypeDeps(t, env, deps, user)
}
// Add immediate package deps for subtypes of t.
func addSubTypeDeps(t *vdl.Type, env *compile.Env, deps *deps, user importMap) {
switch t.Kind() {
case vdl.Array, vdl.List, vdl.Optional:
addTypeDeps(t.Elem(), env, deps, user)
case vdl.Set:
addTypeDeps(t.Key(), env, deps, user)
case vdl.Map:
addTypeDeps(t.Key(), env, deps, user)
addTypeDeps(t.Elem(), env, deps, user)
case vdl.Struct, vdl.Union:
for ix := 0; ix < t.NumField(); ix++ {
addTypeDeps(t.Field(ix).Type, env, deps, user)
}
}
}
// Add immediate package deps for v.Type(), and subvalues. We must traverse the
// value to know which types are actually used; e.g. an empty struct doesn't
// have a dependency on its field types.
//
// The purpose of this method is to identify the package and type dependencies
// for const or tag values.
func addValueTypeDeps(v *vdl.Value, env *compile.Env, deps *deps, user importMap) {
t := v.Type()
addTypeDepIfDefined(t, env, deps, user)
// Track transitive dependencies, by traversing subvalues recursively.
switch t.Kind() {
case vdl.Array, vdl.List:
for ix := 0; ix < v.Len(); ix++ {
addValueTypeDeps(v.Index(ix), env, deps, user)
}
case vdl.Set:
for _, key := range v.Keys() {
addValueTypeDeps(key, env, deps, user)
}
case vdl.Map:
for _, key := range v.Keys() {
addValueTypeDeps(key, env, deps, user)
addValueTypeDeps(v.MapIndex(key), env, deps, user)
}
case vdl.Struct:
if v.IsZero() {
// We print zero-valued structs as {}, so we stop the traversal here.
return
}
for ix := 0; ix < t.NumField(); ix++ {
addValueTypeDeps(v.StructField(ix), env, deps, user)
}
case vdl.Union:
_, field := v.UnionField()
addValueTypeDeps(field, env, deps, user)
case vdl.Any, vdl.Optional:
if elem := v.Elem(); elem != nil {
addValueTypeDeps(elem, env, deps, user)
}
case vdl.TypeObject:
// TypeObject has dependencies on everything its zero value depends on.
addValueTypeDeps(vdl.ZeroValue(v.TypeObject()), env, deps, user)
}
}
// systemImports returns the vdl system imports for the given file and deps.
// You might think we could simply capture the imports during code generation,
// and then dump out all imports afterwards. Unfortunately that strategy
// doesn't work, because of potential package name collisions in the imports.
//
// When generating code we need to know the local identifier used to reference a
// given imported package. But the local identifier changes if there are
// collisions with other imported packages. An example:
//
// package pkg
//
// import "a/foo"
// import foo_2 "b/foo"
//
// type X foo.T
// type Y foo_2.T
//
// Note that in order to generate code for X and Y, we need to know what local
// identifier to use. But we only know what local identifier to use after we've
// collected all imports and resolved collisions.
func systemImports(deps deps, file *compile.File) importMap {
system := make(importMap)
if deps.any || deps.typeObject || deps.methodTags || len(file.TypeDefs) > 0 {
// System import for vdl.Value, vdl.Type and vdl.Register.
system["v.io/v23/vdl"] = "vdl"
}
if deps.enumTypeDef {
system["fmt"] = "fmt"
}
if len(file.Interfaces) > 0 {
system["v.io/v23"] = "v23"
system["v.io/v23/context"] = "context"
system["v.io/v23/rpc"] = "rpc"
}
if deps.streamingMethods {
system["io"] = "io"
}
if len(file.ErrorDefs) > 0 {
system["v.io/v23/context"] = "context"
system["v.io/v23/i18n"] = "i18n"
// If the user has specified any errors, typically we need to import the
// "v.io/v23/verror" package. However we allow vdl code-generation
// in the "v.io/v23/verror" package itself, to specify common
// errors. Special-case this scenario to avoid self-cyclic package
// dependencies.
if file.Package.Path != "v.io/v23/verror" {
system["v.io/v23/verror"] = "verror"
}
}
// Now remove self package dependencies.
system.DeletePackage(file.Package)
return system
}