From 161b85b8c4bb25f5b4213766cefd74e5d058a162 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sun, 13 Aug 2023 11:10:01 +0200 Subject: [PATCH] Try underlying types for named type custom methods --- builder/builder.go | 3 + builder/flags.go | 1 + builder/named_extend.go | 70 +++++++++++++++++++++ comments/parse_docs.go | 6 ++ docs/conversion/misc.md | 4 ++ generator/generate.go | 1 + generator/generator.go | 18 +++--- scenario/extend_named_underlying_both.yml | 34 ++++++++++ scenario/extend_named_underlying_source.yml | 34 ++++++++++ scenario/extend_named_underlying_target.yml | 34 ++++++++++ 10 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 builder/named_extend.go create mode 100644 scenario/extend_named_underlying_both.yml create mode 100644 scenario/extend_named_underlying_source.yml create mode 100644 scenario/extend_named_underlying_target.yml diff --git a/builder/builder.go b/builder/builder.go index 59c42fe2..76347884 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -1,6 +1,8 @@ package builder import ( + "go/types" + "github.com/dave/jennifer/jen" "github.com/jmattheis/goverter/namer" "github.com/jmattheis/goverter/xtype" @@ -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 diff --git a/builder/flags.go b/builder/flags.go index 55d48561..1347839c 100644 --- a/builder/flags.go +++ b/builder/flags.go @@ -8,6 +8,7 @@ const ( FlagMatchIgnoreCase FlagIgnoreUnexported FlagZeroValueOnPtrInconsistency + FlagTryUnderlyingTypeExtends ) type ConversionFlags map[ConversionFlag]bool diff --git a/builder/named_extend.go b/builder/named_extend.go new file mode 100644 index 00000000..d4b1fd34 --- /dev/null +++ b/builder/named_extend.go @@ -0,0 +1,70 @@ +package builder + +import ( + "github.com/dave/jennifer/jen" + "github.com/jmattheis/goverter/xtype" +) + +// NamedExtendSource handles FlagTryUnderlyingTypeExtends +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) { + 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 +} diff --git a/comments/parse_docs.go b/comments/parse_docs.go index ea38270b..0a797853 100644 --- a/comments/parse_docs.go +++ b/comments/parse_docs.go @@ -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) } @@ -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) } diff --git a/docs/conversion/misc.md b/docs/conversion/misc.md index 915d7ed2..2ddf4cd6 100644 --- a/docs/conversion/misc.md +++ b/docs/conversion/misc.md @@ -151,3 +151,7 @@ func (c *ConverterImpl) Convert(source example.Input) example.Output { ``` + +## Try underlying named types for custom methods + +TODO diff --git a/generator/generate.go b/generator/generate.go index 47363d70..26901c6a 100644 --- a/generator/generate.go +++ b/generator/generate.go @@ -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{}, diff --git a/generator/generator.go b/generator/generator.go index 41dbe123..6f536d7a 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -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 @@ -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, @@ -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) } diff --git a/scenario/extend_named_underlying_both.yml b/scenario/extend_named_underlying_both.yml new file mode 100644 index 00000000..7de4a961 --- /dev/null +++ b/scenario/extend_named_underlying_both.yml @@ -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 + } diff --git a/scenario/extend_named_underlying_source.yml b/scenario/extend_named_underlying_source.yml new file mode 100644 index 00000000..5007108d --- /dev/null +++ b/scenario/extend_named_underlying_source.yml @@ -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 + } diff --git a/scenario/extend_named_underlying_target.yml b/scenario/extend_named_underlying_target.yml new file mode 100644 index 00000000..9f2c7feb --- /dev/null +++ b/scenario/extend_named_underlying_target.yml @@ -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 + }