Skip to content
46 changes: 46 additions & 0 deletions cmd/cue/cmd/testdata/script/cmd_exp_gengo_alias.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# reproduction for https://github.com/cue-lang/cue/issues/3974
exec cue exp gengotypes
cmp cue_types_main_gen.go expect-gengo

-- cue.mod/module.cue --
module: "mod.test"
language: version: "v0.13.0"

-- pkg/k8s.io/apimachinery/pkg/apis/meta/v1/types_go_gen.cue --
package v1

#ObjectMeta: {}

-- pkg/k8s.io/api/core/v1/types_go_gen.cue --
package v1

#PodSpec: {}

-- main.cue --
package main

import (
metav1 "mod.test/pkg/k8s.io/apimachinery/pkg/apis/meta/v1"
"mod.test/pkg/k8s.io/api/core/v1"
)

#Spec: {
meta: metav1.#ObjectMeta
spec: v1.#PodSpec
}

-- expect-gengo --
// Code generated by "cue exp gengotypes"; DO NOT EDIT.

package main

import (
"mod.test/pkg/k8s.io/api/core/v1"
metav1 "mod.test/pkg/k8s.io/apimachinery/pkg/apis/meta/v1"
)

type Spec struct {
Meta metav1.ObjectMeta `json:"meta"`

Spec v1.PodSpec `json:"spec"`
}
57 changes: 57 additions & 0 deletions cmd/cue/cmd/testdata/script/cmd_exp_gengo_alias_conflict.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# reproduction for https://github.com/cue-lang/cue/issues/3974
exec cue exp gengotypes
cmp cue_types_main_gen.go expect-gengo

-- cue.mod/module.cue --
module: "mod.test"
language: version: "v0.13.0"

-- pkg/foo/p1/p1.cue --
package p1

#Foo: int

-- pkg/bar/p2/p2.cue --
package p2

#Bar: string

-- f1.cue --
package main

import (
same "mod.test/pkg/foo/p1"
)

#A: {
foo: same.#Foo
}

-- f2.cue --
package main

import (
same "mod.test/pkg/bar/p2"
)

#B: {
bar: same.#Bar
}

-- expect-gengo --
// Code generated by "cue exp gengotypes"; DO NOT EDIT.

package main

import (
same1 "mod.test/pkg/bar/p2"
same "mod.test/pkg/foo/p1"
)

type A struct {
Foo same.Foo `json:"foo"`
}

type B struct {
Bar same1.Bar `json:"bar"`
}
57 changes: 57 additions & 0 deletions cmd/cue/cmd/testdata/script/cmd_exp_gengo_alias_duplicate.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# reproduction for https://github.com/cue-lang/cue/issues/3974
exec cue exp gengotypes
cmp cue_types_main_gen.go expect-gengo

-- cue.mod/module.cue --
module: "mod.test"
language: version: "v0.13.0"

-- pkg/same/same.cue --
package same

#Same: int

-- pkg/foo/foo.cue --
package foo

#Bar: string

-- f1.cue --
package main

import (
"mod.test/pkg/same"
)

#A: {
value: same.#Same
}

-- f2.cue --
package main

import (
same "mod.test/pkg/foo"
)

#B: {
bar: same.#Bar
}

-- expect-gengo --
// Code generated by "cue exp gengotypes"; DO NOT EDIT.

package main

import (
same1 "mod.test/pkg/foo"
"mod.test/pkg/same"
)

type A struct {
Value same.Same `json:"value"`
}

type B struct {
Bar same1.Bar `json:"bar"`
}
73 changes: 69 additions & 4 deletions internal/encoding/gotypes/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ func Generate(ctx *cue.Context, insts ...*build.Instance) error {
g.pkgRoot = instVal
g.importCuePkgAsGoPkg = make(map[string]string)

// gather the import aliases for the instance.
// NOTE: this implementation means that if an import alias is employed in one file
// it will be used anywhere that package is referenced
importAliases, err := gatherImportAliases(inst)
if err != nil {
return err
}
g.importAliases = importAliases

iter, err := instVal.Fields(cue.Definitions(true))
if err != nil {
return err
Expand Down Expand Up @@ -100,7 +109,7 @@ func Generate(ctx *cue.Context, insts ...*build.Instance) error {
buf = fmt.Appendf(buf, format, args...)
}
printf("// Code generated by \"cue exp gengotypes\"; DO NOT EDIT.\n\n")
goPkgName := goPkgNameForInstance(inst, instVal)
goPkgName := goPkgNameForInstance(inst, instVal, nil)
if prev, ok := goPkgNamesDoneByDir[inst.Dir]; ok && prev != goPkgName {
return fmt.Errorf("cannot generate two Go packages in one directory; %s and %s", prev, goPkgName)
} else {
Expand All @@ -112,7 +121,11 @@ func Generate(ctx *cue.Context, insts ...*build.Instance) error {
if len(importedGo) > 0 {
printf("import (\n")
for _, path := range importedGo {
printf("\t%q\n", path)
if alias, ok := g.importAliases[path]; ok {
printf("\t%s %q\n", alias, path)
} else {
printf("\t%q\n", path)
}
}
printf(")\n")
}
Expand Down Expand Up @@ -217,6 +230,11 @@ type generator struct {

// def tracks the generation state for a single CUE definition.
def *generatedDef

// importAliases maps package names to a given alias. In the case that there are multiple aliases present for
// same package then we will use the first alias encountered. When the same alias is used for multiple packages
// across different files, then a number will be added to the end of the alias to avoid conflicts.
importAliases map[string]string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we organize fields on a per-package and per-definition basis. This should be on a per-package basis I assume.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow here: should I clarify the description or am I missing something in respect of the package handling implementation?

}

type qualifiedPath = string // [build.Instance.ImportPath] + " " + [cue.Path.String]
Expand Down Expand Up @@ -679,11 +697,16 @@ func goValueAttr(val cue.Value) cue.Attribute {

// goPkgNameForInstance determines what to name a Go package generated from a CUE instance.
// By default this is the CUE package name, but it can be overriden by a @go() package attribute.
func goPkgNameForInstance(inst *build.Instance, instVal cue.Value) string {
// When supplying importAliases, and if no package attribute is found, the returned package name
// reflects the alias name that the package is being imported as.
func goPkgNameForInstance(inst *build.Instance, instVal cue.Value, importAliases map[string]string) string {
attr := goValueAttr(instVal)
if s, _ := attr.String(0); s != "" {
return s
}
if alias, ok := importAliases[inst.ImportPath]; ok {
return alias
}
return inst.PkgName
}

Expand Down Expand Up @@ -751,7 +774,7 @@ func (g *generator) emitTypeReference(val cue.Value) (bool, typeFacts, error) {
facts.isNillable = true // pointers can be nil
}
if root != g.pkgRoot {
g.def.printf("%s.", goPkgNameForInstance(inst, root))
g.def.printf("%s.", goPkgNameForInstance(inst, root, g.importAliases))
}
g.def.printf("%s", name)
return true, facts, nil
Expand All @@ -772,3 +795,45 @@ func emitDocs(printf func(string, ...any), name string, groups []*ast.CommentGro
}
}
}

// gatherImportAliases collects the aliases from imports across the instance.
func gatherImportAliases(inst *build.Instance) (map[string]string, error) {
fileAliases := make(map[string]string)
tracked := make(map[string]int)

type pair struct{ path, alias string }
var explicit []pair

for _, f := range inst.Files {
for _, s := range f.Imports {
if s.Path == nil {
continue
}
pkgPath, err := strconv.Unquote(s.Path.Value)
if err != nil {
return nil, err
}
// Unaliased import: reserve its base name
if s.Name == nil {
base := filepath.Base(pkgPath)
tracked[base]++
continue
}
// Explicit alias: queue for resolution
alias := s.Name.Name
explicit = append(explicit, pair{path: pkgPath, alias: alias})
}
}

// Resolve explicit aliases with conflict suffixing.
for _, e := range explicit {
alias := e.alias
if count, ok := tracked[alias]; ok {
alias = fmt.Sprintf("%s%d", alias, count)
}
fileAliases[e.path] = alias
tracked[e.alias]++ // track the alias name (unsuffixed) for future conflicts
}

return fileAliases, nil
}