Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func fmtErrors(msg string, errs []error) error {
}

// TODO: test cases
func applyAutoConvertFunctions(cfgs []structConfig) []structConfig {
func applyAutoConvertFunctions(localPackage string, cfgs []structConfig) []structConfig {
// Index the structs by name so any struct can refer to conversion
// functions for any other struct.
byName := make(map[string]structConfig, len(cfgs))
Expand All @@ -294,8 +294,9 @@ func applyAutoConvertFunctions(cfgs []structConfig) []structConfig {
}

for structIdx, s := range cfgs {
imports := newImports()
imports := newImports(localPackage)
imports.Add("", s.Target.Package)
imports.NeedInFile(s.Target.Package)

for fieldIdx, f := range s.Fields {
if _, ignored := s.IgnoreFields[f.SourceName]; ignored {
Expand Down
49 changes: 37 additions & 12 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func generateFiles(cfg config, targets map[string]targetPkg) error {

for _, group := range byOutput {
var decls []ast.Decl
imports := newImports()
imports := newImports(cfg.SourcePkg.pkg.PkgPath)

for _, sourceStruct := range group {
t := targets[sourceStruct.Target.Package].Structs[sourceStruct.Target.Struct]
Expand Down Expand Up @@ -92,6 +92,7 @@ func generateConversion(cfg structConfig, t targetStruct, imports *imports) (gen
var g generated

imports.Add("", cfg.Target.Package)
imports.NeedInFile(cfg.Target.Package)

targetType := &ast.SelectorExpr{
X: &ast.Ident{Name: path.Base(imports.AliasFor(cfg.Target.Package))},
Expand Down Expand Up @@ -129,6 +130,13 @@ func generateConversion(cfg structConfig, t targetStruct, imports *imports) (gen
Sel: &ast.Ident{Name: name},
}

if _, pkg := importFromType(sourceField.SourceType, imports); pkg != "" {
imports.Add("", pkg) // source packages
}
if _, pkg := importFromType(field.Type(), imports); pkg != "" {
imports.Add("", pkg) // target packages
}

if sourceField.FuncTo != "" || sourceField.FuncFrom != "" {
to.Body.List = append(to.Body.List, newAssignStmtUserFunc(
targetExpr,
Expand Down Expand Up @@ -165,7 +173,7 @@ func generateConversion(cfg structConfig, t targetStruct, imports *imports) (gen
}

// the assignmentKind is <target> := <source> so target==LHS source==RHS
rawKind, ok := computeAssignment(field.Type(), sourceField.SourceType)
rawKind, ok := computeAssignment(field.Type(), sourceField.SourceType, imports)
if !ok {
assignErrFn(nil)
continue
Expand Down Expand Up @@ -400,24 +408,34 @@ func writeFile(output string, contents []byte) error {
}

type imports struct {
byPkgPath map[string]string // package => alias(or default)
byAlias map[string]string // alias(or default) => package
hasAlias map[string]struct{} // package is using a non-default name
byPkgPath map[string]string // package => alias(or default)
byAlias map[string]string // alias(or default) => package
hasAlias map[string]struct{} // package is using a non-default name
neededInFile map[string]struct{} // package import is required in file
localPackage string
}

func newImports() *imports {
func newImports(localPackage string) *imports {
return &imports{
byPkgPath: make(map[string]string),
byAlias: make(map[string]string),
hasAlias: make(map[string]struct{}),
byPkgPath: make(map[string]string),
byAlias: make(map[string]string),
hasAlias: make(map[string]struct{}),
neededInFile: make(map[string]struct{}),
localPackage: localPackage,
}
}

func (i *imports) NeedInFile(pkgPath string) {
if _, exists := i.byPkgPath[pkgPath]; !exists {
panic("Only call NeedInFile after Add")
}

i.neededInFile[pkgPath] = struct{}{}
}

// Add an import with an optional alias. If no alias is specified, the default
// alias will be path.Base(). The alias for a package should always be looked up
// from AliasFor.
//
// TODO: remove alias arg?
func (i *imports) Add(alias string, pkgPath string) {
if _, exists := i.byPkgPath[pkgPath]; exists {
return
Expand Down Expand Up @@ -446,6 +464,9 @@ func (i *imports) Add(alias string, pkgPath string) {
}

func (i *imports) AliasFor(pkgPath string) string {
if pkgPath == i.localPackage {
return ""
}
return i.byPkgPath[pkgPath]
}

Expand All @@ -454,7 +475,11 @@ func (i *imports) Decl() *ast.GenDecl {

paths := make([]string, 0, len(i.byPkgPath))
for pkgPath := range i.byPkgPath {
paths = append(paths, pkgPath)
if pkgPath != i.localPackage {
if _, ok := i.neededInFile[pkgPath]; ok {
paths = append(paths, pkgPath)
}
}
}
sort.Strings(paths)

Expand Down
12 changes: 9 additions & 3 deletions generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestGenerateConversion(t *testing.T) {
newField("ID", types.Typ[types.String]),
},
}
imports := newImports()
imports := newImports("TODO")
gen, err := generateConversion(c, target, imports)
assert.NilError(t, err)

Expand Down Expand Up @@ -115,14 +115,14 @@ func TestGenerateConversion_WithMissingSourceField(t *testing.T) {
newField("Name", types.Typ[types.String]),
},
}
imports := newImports()
imports := newImports("TODO")
_, err := generateConversion(c, target, imports)
expected := "struct Node is missing field Name. Add the missing field or exclude it"
assert.ErrorContains(t, err, expected)
}

func TestImports(t *testing.T) {
imp := newImports()
imp := newImports("TODO")

t.Run("add duplicate import", func(t *testing.T) {
imp.Add("", "example.com/foo")
Expand Down Expand Up @@ -150,6 +150,12 @@ func TestImports(t *testing.T) {
t.Skip("Decls value depends on previous subtests")
}
t.Run("Decls", func(t *testing.T) {
imp.NeedInFile("example.com/foo")
imp.NeedInFile("example.com/some/foo")
imp.NeedInFile("example.com/stars")

imp.Add("", "example.com/totally-ignored")

file := &ast.File{Name: &ast.Ident{Name: "src"}}
file.Decls = append(file.Decls, imp.Decl())
out, err := astToBytes(&token.FileSet{}, file)
Expand Down
3 changes: 3 additions & 0 deletions internal/e2e/core/cluster_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type ClusterNode struct {
Label Label
// Labels []Label
// WorkPointer []*Workload
InnerLabel inner.Label
InnerLabel2 inner.Label
InnerLabel3 inner.Label

O *Other
I inner.Inner
Expand Down
2 changes: 2 additions & 0 deletions internal/e2e/core/inner/inner.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ package inner
type Inner struct {
M string
}

type Label string
6 changes: 6 additions & 0 deletions internal/e2e/sourcepkg/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sourcepkg
import (
"github.com/hashicorp/mog/internal/e2e/core"
"github.com/hashicorp/mog/internal/e2e/core/inner"
"github.com/hashicorp/mog/internal/e2e/sourcepkg/outer"
)

// Node source structure for e2e testing mog.
Expand All @@ -20,6 +21,9 @@ type Node struct {
Meta map[string]interface{}
Work []Workload
// WorkPointer []*Workload
InnerLabel string
InnerLabel2 outer.Label
InnerLabel3 LocalLabel

O *core.Other
I inner.Inner
Expand Down Expand Up @@ -65,6 +69,8 @@ type Node struct {
type StringSlice []string
type WorkloadSlice []Workload

type LocalLabel string

// mog annotation:
//
// name=Core
Expand Down
3 changes: 3 additions & 0 deletions internal/e2e/sourcepkg/outer/outer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package outer

type Label string
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func runMog(opts options) error {
return fmt.Errorf("failed to load targets: %w", err)
}

cfg.Structs = applyAutoConvertFunctions(cfg.Structs)
cfg.Structs = applyAutoConvertFunctions(cfg.SourcePkg.pkg.PkgPath, cfg.Structs)

log.Printf("Generating code for %d structs", len(cfg.Structs))

Expand Down
17 changes: 13 additions & 4 deletions mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func convertibleButNotIdentical(typ, typeDecode types.Type) bool {
//
// If this is not possible, or not currently supported (nil, false) is
// returned.
func computeAssignment(leftType, rightType types.Type) (assignmentKind, bool) {
func computeAssignment(leftType, rightType types.Type, imports *imports) (assignmentKind, bool) {
// First check if the types are naturally directly assignable. Only allow
// type pairs that are symmetrically assignable for simplicity.
if types.AssignableTo(rightType, leftType) {
Expand All @@ -201,6 +201,15 @@ func computeAssignment(leftType, rightType types.Type) (assignmentKind, bool) {
if convertibleButNotIdentical(rightType, rightTypeDecode) ||
convertibleButNotIdentical(leftType, leftTypeDecode) {

if _, pkg := importFromType(leftType, imports); pkg != "" {
imports.Add("", pkg)
imports.NeedInFile(pkg)
}
if _, pkg := importFromType(rightType, imports); pkg != "" {
imports.Add("", pkg)
imports.NeedInFile(pkg)
}

return &singleAssignmentKind{
Left: leftType,
Right: rightType,
Expand Down Expand Up @@ -238,7 +247,7 @@ func computeAssignment(leftType, rightType types.Type) (assignmentKind, bool) {
}

// the elements have to be assignable
rawOp, ok := computeAssignment(left.Elem(), right.Elem())
rawOp, ok := computeAssignment(left.Elem(), right.Elem(), imports)
if !ok {
return nil, false
}
Expand All @@ -262,7 +271,7 @@ func computeAssignment(leftType, rightType types.Type) (assignmentKind, bool) {
return nil, false
}

rawKeyOp, ok := computeAssignment(left.Key(), right.Key())
rawKeyOp, ok := computeAssignment(left.Key(), right.Key(), imports)
if !ok {
return nil, false
}
Expand All @@ -277,7 +286,7 @@ func computeAssignment(leftType, rightType types.Type) (assignmentKind, bool) {
}

// the map values have to be assignable
rawOp, ok := computeAssignment(left.Elem(), right.Elem())
rawOp, ok := computeAssignment(left.Elem(), right.Elem(), imports)
if !ok {
return nil, false
}
Expand Down
12 changes: 11 additions & 1 deletion testdata/TestE2E-expected-node_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@ import (
"path"
)

func importFromType(t types.Type, imports *imports) (alias, pkg string) {
if os.Getenv("DEBUG_MOG") == "1" {
defer func() {
fmt.Printf("IMPORT-FROM-TYPE: [%T :: %+v] => [%q, %q]\n",
t, t, alias, pkg,
)
}()
}

switch x := t.(type) {
case *types.Basic:
return "", ""
case *types.Named:
target := x.Obj()
return target.Pkg().Name(), target.Pkg().Path()
case *types.Pointer:
return importFromType(x.Elem(), imports)
// case *types.Slice:
// case *types.Map:
// case *types.Interface:
default:
return "", ""
}
}

// typeToExpr converts a go/types representation of a type into a go/ast
// representation of a type.
//
Expand Down