Skip to content

Commit

Permalink
Try underlying types for named type custom methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jmattheis committed Oct 20, 2023
1 parent a1ae13d commit 5be9086
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 7 deletions.
3 changes: 3 additions & 0 deletions builder/builder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package builder

import (
"go/types"

"github.com/dave/jennifer/jen"
"github.com/jmattheis/goverter/namer"
"github.com/jmattheis/goverter/xtype"
Expand Down Expand Up @@ -40,6 +42,7 @@ type MethodContext struct {
TargetType *xtype.Type
AutoMap []string
Flags ConversionFlags
HasExtend func(types.Type, types.Type) bool
SeenNamed map[string]struct{}

TargetVar *jen.Statement
Expand Down
1 change: 1 addition & 0 deletions builder/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
FlagIgnoreUnexported
FlagZeroValueOnPtrInconsistency
FlagSkipCopySameType
FlagTryUnderlyingNamedType
)

type ConversionFlags map[ConversionFlag]bool
Expand Down
70 changes: 70 additions & 0 deletions builder/underlying.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package builder

import (
"github.com/dave/jennifer/jen"
"github.com/jmattheis/goverter/xtype"
)

// TryUnderlyingNamed handles FlagTryUnderlyingNamedType.
type TryUnderlyingNamed struct{}

// Matches returns true, if the builder can create handle the given types.
func (*TryUnderlyingNamed) Matches(ctx *MethodContext, source, target *xtype.Type) bool {
if !ctx.Flags.Has(FlagTryUnderlyingNamedType) {
return false
}

sourceUnderlying, targetUnderlying := findUnderlyingExtendMapping(ctx, source, target)
return sourceUnderlying || targetUnderlying
}

// Build creates conversion source code for the given source and target type.
func (*TryUnderlyingNamed) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type) ([]jen.Code, *xtype.JenID, *Error) {
sourceUnderlying, targetUnderlying := findUnderlyingExtendMapping(ctx, source, target)

innerSource := source
innerTarget := target

if sourceUnderlying {
innerSource = xtype.TypeOf(source.NamedType.Underlying())
sourceID = xtype.OtherID(innerSource.TypeAsJen().Call(sourceID.Code))
}

if targetUnderlying {
innerTarget = xtype.TypeOf(target.NamedType.Underlying())
}

stmt, id, err := gen.Build(ctx, sourceID, innerSource, innerTarget, NoWrap)
if err != nil {
return nil, nil, err.Lift(&Path{
SourceID: "*",
SourceType: innerSource.T.String(),
TargetID: "*",
TargetType: innerTarget.T.String(),
})
}

if targetUnderlying {
id = xtype.OtherID(target.TypeAsJen().Call(id.Code))
}

return stmt, id, err
}

func findUnderlyingExtendMapping(ctx *MethodContext, source, target *xtype.Type) (underlyingSource, underlyingTarget bool) {
if source.Named {
if ctx.HasExtend(source.NamedType.Underlying(), target.NamedType) {
return true, false
}

if target.Named && ctx.HasExtend(source.NamedType.Underlying(), target.NamedType.Underlying()) {
return true, true
}
}

if target.Named && ctx.HasExtend(source.NamedType, target.NamedType.Underlying()) {
return false, true
}

return false, false
}
6 changes: 6 additions & 0 deletions comments/parse_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ func parseConverterComment(comment string, config ConverterConfig) (ConverterCon
case "useZeroValueOnPointerInconsistency":
config.Flags.Set(builder.FlagZeroValueOnPtrInconsistency)
continue
case "tryUnderlyingNamedType":
config.Flags.Set(builder.FlagTryUnderlyingNamedType)
continue
}
return config, fmt.Errorf("unknown %s comment: %s", prefix, line)
}
Expand Down Expand Up @@ -330,6 +333,9 @@ func parseMethodComment(comment string) (Method, error) {
}
m.Flags.Set(builder.FlagWrapErrors)
continue
case "tryUnderlyingNamedType":
m.Flags.Set(builder.FlagTryUnderlyingNamedType)
continue
}
return m, fmt.Errorf("unknown %s comment: %s", prefix, line)
}
Expand Down
66 changes: 66 additions & 0 deletions docs/conversion/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,69 @@ func (c *ConverterImpl) Convert(source example.Input) example.Output {
```

<!-- tabs:end -->

## Try underlying named types

For each type conversion, goverter will check if there is an existing method
that can be used. For named types only the type itself will be checked and not
the underlying type. For this type:

```go
type InputID int
```

`InputID` would be the named type and `int` the underlying type.

With `goverter:tryUnderlyingNamedType`, goverter will check all
named/underlying combinations.

- named -> underlying
- underlying -> named
- underlying -> underlying

<!-- tabs:start -->

#### **input.go**

```go
package example

// goverter:converter
// goverter:extend ConvertUnderlying
type Converter interface {
// goverter:tryUnderlyingNamedType
Convert(source Input) Output
}
func ConvertUnderlying(s int) string {
return ""
}
// these would be used too
// func ConvertUnderlying(s int) OutputID
// func ConvertUnderlying(s InputID) string

type InputID int
type OutputID string
type Input struct { ID InputID }
type Output struct { ID OutputID }
```

#### **generated/generated.go**

```go
package generated

import example "goverter/example"

type ConverterImpl struct{}

func (c *ConverterImpl) Convert(source example.Input) example.Output {
var output example.Output
output.ID = example.OutputID(example.ConvertUnderlying(int(source.ID)))
return output
}
```

<!-- tabs:end -->

Without the setting only the custom method with this signature could be used
for the conversion. `func ConvertUnderlying(s InputID) OutputID`
1 change: 1 addition & 0 deletions generator/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Config struct {

// BuildSteps that'll used for generation.
var BuildSteps = []builder.Builder{
&builder.TryUnderlyingNamed{},
&builder.SkipCopy{},
&builder.BasicTargetPointerRule{},
&builder.Pointer{},
Expand Down
18 changes: 11 additions & 7 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ func (g *generator) appendToFile() {
}
}

func (g *generator) HasExtend(source, target types.Type) bool {
signature := xtype.Signature{Source: source.String(), Target: target.String()}
_, ok := g.extend[signature]
if !ok {
_, ok = g.lookup[signature]
}
return ok
}

func (g *generator) findOverlappingExplicitStructMethod(method *methodDefinition) (*methodDefinition, bool) {
source := method.Source
target := method.Target
Expand Down Expand Up @@ -211,6 +220,7 @@ func (g *generator) buildMethod(method *methodDefinition, errWrapper builder.Err
Fields: method.Fields,
FieldsTarget: fieldsTarget,
SeenNamed: map[string]struct{}{},
HasExtend: g.HasExtend,
Flags: method.Flags,
AutoMap: method.AutoMap,
TargetType: method.Target,
Expand Down Expand Up @@ -450,11 +460,5 @@ func (g *generator) Build(
return g.Build(ctx, sourceID, source, target, errWrapper)
}

for _, rule := range BuildSteps {
if rule.Matches(ctx, source, target) {
return rule.Build(g, ctx, sourceID, source, target)
}
}

return nil, nil, builder.NewError(fmt.Sprintf("TypeMismatch: Cannot convert %s to %s", source.T, target.T))
return g.buildNoLookup(ctx, sourceID, source, target)
}
34 changes: 34 additions & 0 deletions scenario/extend_named_underlying_both.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
input:
input.go: |
package structs
// goverter:converter
// goverter:extend ConvertUnderlying
type Converter interface {
// goverter:tryUnderlyingNamedType
Convert(source Input) Output
}
func ConvertUnderlying(s int) string {
return ""
}
type InputID int
type OutputID string
type Input struct { ID InputID }
type Output struct { ID OutputID }
success: |
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
package generated
import execution "github.com/jmattheis/goverter/execution"
type ConverterImpl struct{}
func (c *ConverterImpl) Convert(source execution.Input) execution.Output {
var structsOutput execution.Output
structsOutput.ID = execution.OutputID(execution.ConvertUnderlying(int(source.ID)))
return structsOutput
}
34 changes: 34 additions & 0 deletions scenario/extend_named_underlying_global.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
input:
input.go: |
package structs
// goverter:converter
// goverter:extend ConvertUnderlying
// goverter:tryUnderlyingNamedType
type Converter interface {
Convert(source Input) Output
}
func ConvertUnderlying(s int) string {
return ""
}
type InputID int
type OutputID string
type Input struct { ID InputID }
type Output struct { ID OutputID }
success: |
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
package generated
import execution "github.com/jmattheis/goverter/execution"
type ConverterImpl struct{}
func (c *ConverterImpl) Convert(source execution.Input) execution.Output {
var structsOutput execution.Output
structsOutput.ID = execution.OutputID(execution.ConvertUnderlying(int(source.ID)))
return structsOutput
}
34 changes: 34 additions & 0 deletions scenario/extend_named_underlying_source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
input:
input.go: |
package structs
// goverter:converter
// goverter:extend ConvertUnderlying
type Converter interface {
// goverter:tryUnderlyingNamedType
Convert(source Input) Output
}
func ConvertUnderlying(s int) OutputID {
return ""
}
type InputID int
type OutputID string
type Input struct { ID InputID }
type Output struct { ID OutputID }
success: |
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
package generated
import execution "github.com/jmattheis/goverter/execution"
type ConverterImpl struct{}
func (c *ConverterImpl) Convert(source execution.Input) execution.Output {
var structsOutput execution.Output
structsOutput.ID = execution.ConvertUnderlying(int(source.ID))
return structsOutput
}
34 changes: 34 additions & 0 deletions scenario/extend_named_underlying_target.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
input:
input.go: |
package structs
// goverter:converter
// goverter:extend ConvertUnderlying
type Converter interface {
// goverter:tryUnderlyingNamedType
Convert(source Input) Output
}
func ConvertUnderlying(s InputID) string {
return ""
}
type InputID int
type OutputID string
type Input struct { ID InputID }
type Output struct { ID OutputID }
success: |
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
package generated
import execution "github.com/jmattheis/goverter/execution"
type ConverterImpl struct{}
func (c *ConverterImpl) Convert(source execution.Input) execution.Output {
var structsOutput execution.Output
structsOutput.ID = execution.OutputID(execution.ConvertUnderlying(source.ID))
return structsOutput
}

0 comments on commit 5be9086

Please sign in to comment.