| // 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 |
| } |