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 Aug 13, 2023
1 parent 53b9b8b commit 161b85b
Show file tree
Hide file tree
Showing 10 changed files with 198 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 @@ -8,6 +8,7 @@ const (
FlagMatchIgnoreCase
FlagIgnoreUnexported
FlagZeroValueOnPtrInconsistency
FlagTryUnderlyingTypeExtends
)

type ConversionFlags map[ConversionFlag]bool
Expand Down
70 changes: 70 additions & 0 deletions builder/named_extend.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"
)

// NamedExtendSource handles FlagTryUnderlyingTypeExtends

Check failure on line 8 in builder/named_extend.go

View workflow job for this annotation

GitHub Actions / build

Comment should end in a period (godot)
type NamedExtendSource struct{}

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

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

// Build creates conversion source code for the given source and target type.
func (*NamedExtendSource) 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 bool, underlyingTarget bool) {

Check failure on line 54 in builder/named_extend.go

View workflow job for this annotation

GitHub Actions / build

File is not `gofumpt`-ed with `-extra` (gofumpt)
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 @@ -229,6 +229,9 @@ func parseConverterComment(comment string, config ConverterConfig) (ConverterCon
case "useZeroValueOnPointerInconsistency":
config.Flags.Set(builder.FlagZeroValueOnPtrInconsistency)
continue
case "tryUnderlyingNamedTypeExtends":
config.Flags.Set(builder.FlagTryUnderlyingTypeExtends)
continue
}
return config, fmt.Errorf("unknown %s comment: %s", prefix, line)
}
Expand Down Expand Up @@ -324,6 +327,9 @@ func parseMethodComment(comment string) (Method, error) {
}
m.Flags.Set(builder.FlagWrapErrors)
continue
case "tryUnderlyingNamedTypeExtends":
m.Flags.Set(builder.FlagTryUnderlyingTypeExtends)
continue
}
return m, fmt.Errorf("unknown %s comment: %s", prefix, line)
}
Expand Down
4 changes: 4 additions & 0 deletions docs/conversion/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,7 @@ func (c *ConverterImpl) Convert(source example.Input) example.Output {
```

<!-- tabs:end -->

## Try underlying named types for custom methods

TODO
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.NamedExtendSource{},
&builder.BasicTargetPointerRule{},
&builder.Pointer{},
&builder.SourcePointer{},
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 @@ -447,11 +457,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:tryUnderlyingNamedTypeExtends
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:tryUnderlyingNamedTypeExtends
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:tryUnderlyingNamedTypeExtends
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 161b85b

Please sign in to comment.