From 89f2367cdf873e1ff0277803066bd3a5e8dea407 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sat, 9 Sep 2023 20:55:06 +0200 Subject: [PATCH 1/4] test: fix type in scenario --- ...{invlaid_signature_source.yml => invalid_signature_source.yml} | 0 ...{invlaid_signature_target.yml => invalid_signature_target.yml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename scenario/{invlaid_signature_source.yml => invalid_signature_source.yml} (100%) rename scenario/{invlaid_signature_target.yml => invalid_signature_target.yml} (100%) diff --git a/scenario/invlaid_signature_source.yml b/scenario/invalid_signature_source.yml similarity index 100% rename from scenario/invlaid_signature_source.yml rename to scenario/invalid_signature_source.yml diff --git a/scenario/invlaid_signature_target.yml b/scenario/invalid_signature_target.yml similarity index 100% rename from scenario/invlaid_signature_target.yml rename to scenario/invalid_signature_target.yml From 568078ca18d6f0e0d026367524b2840b2ab7feec Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sat, 9 Sep 2023 21:26:36 +0200 Subject: [PATCH 2/4] feat: add `goverter:skipCopySameType` --- README.md | 9 +++--- builder/flags.go | 1 + builder/skipcopy.go | 19 ++++++++++++ comments/parse_docs.go | 6 ++++ docs/conversion/misc.md | 51 +++++++++++++++++++++++++++++++ generator/generate.go | 1 + generator/generator.go | 3 ++ scenario/skipcopy_identity.yml | 19 ++++++++++++ scenario/skipcopy_inner.yml | 56 ++++++++++++++++++++++++++++++++++ scenario/skipcopy_method.yml | 30 ++++++++++++++++++ 10 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 builder/skipcopy.go create mode 100644 scenario/skipcopy_identity.yml create mode 100644 scenario/skipcopy_inner.yml create mode 100644 scenario/skipcopy_method.yml diff --git a/README.md b/README.md index 983e0ef5..21a5941c 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,11 @@ use reflection. ## Features - **Fast execution**: No reflection is used at runtime -- Automatically [deep - copies](https://en.wikipedia.org/wiki/Object_copying#Deep_copy) builtin - types: slices, maps, named types, primitive types, pointers, structs with - same fields +- Automatically converts builtin types: slices, maps, named types, primitive + types, pointers, structs with same fields +- [Deep copies](https://en.wikipedia.org/wiki/Object_copying#Deep_copy) per + default and supports [shallow + copying](https://en.wikipedia.org/wiki/Object_copying#Shallow_copy) - **Customizable**: [You can implement custom converter methods](https://goverter.jmattheis.de/#/conversion/custom) - [Clear errors when generating the conversion methods](https://goverter.jmattheis.de/#/conversion/?id=error-early) if - the target struct has unmapped fields diff --git a/builder/flags.go b/builder/flags.go index 55d48561..4877913c 100644 --- a/builder/flags.go +++ b/builder/flags.go @@ -8,6 +8,7 @@ const ( FlagMatchIgnoreCase FlagIgnoreUnexported FlagZeroValueOnPtrInconsistency + FlagSkipCopySameType ) type ConversionFlags map[ConversionFlag]bool diff --git a/builder/skipcopy.go b/builder/skipcopy.go new file mode 100644 index 00000000..fdc32128 --- /dev/null +++ b/builder/skipcopy.go @@ -0,0 +1,19 @@ +package builder + +import ( + "github.com/dave/jennifer/jen" + "github.com/jmattheis/goverter/xtype" +) + +// SkipCopy handles FlagSkipCopySameType. +type SkipCopy struct{} + +// Matches returns true, if the builder can create handle the given types. +func (*SkipCopy) Matches(ctx *MethodContext, source, target *xtype.Type) bool { + return ctx.Flags.Has(FlagSkipCopySameType) && source.T.String() == target.T.String() +} + +// Build creates conversion source code for the given source and target type. +func (*SkipCopy) Build(_ Generator, _ *MethodContext, sourceID *xtype.JenID, _, _ *xtype.Type) ([]jen.Code, *xtype.JenID, *Error) { + return nil, sourceID, nil +} diff --git a/comments/parse_docs.go b/comments/parse_docs.go index ea38270b..5ada1720 100644 --- a/comments/parse_docs.go +++ b/comments/parse_docs.go @@ -226,6 +226,9 @@ func parseConverterComment(comment string, config ConverterConfig) (ConverterCon case "ignoreMissing": config.Flags.Set(builder.FlagIgnoreMissing) continue + case "skipCopySameType": + config.Flags.Set(builder.FlagSkipCopySameType) + continue case "useZeroValueOnPointerInconsistency": config.Flags.Set(builder.FlagZeroValueOnPtrInconsistency) continue @@ -318,6 +321,9 @@ func parseMethodComment(comment string) (Method, error) { case "useZeroValueOnPointerInconsistency": m.Flags.Set(builder.FlagZeroValueOnPtrInconsistency) continue + case "skipCopySameType": + m.Flags.Set(builder.FlagSkipCopySameType) + continue case "wrapErrors": if strings.TrimSpace(remaining) != "" { return m, fmt.Errorf("invalid %s:wrapErrors, parameters not supported", prefix) diff --git a/docs/conversion/misc.md b/docs/conversion/misc.md index 915d7ed2..9286a96a 100644 --- a/docs/conversion/misc.md +++ b/docs/conversion/misc.md @@ -151,3 +151,54 @@ func (c *ConverterImpl) Convert(source example.Input) example.Output { ``` + +## Skip copy same type + +Goverter deep copies instances when converting the source to the target type. +With `goverter:skipCopySameType` you instruct Goverter to skip copying +instances when the source and target type is the same. + +The setting can be enabled on both the converter interface and methods. + + + +#### **input.go** + +```go +package example + +// goverter:converter +// goverter:skipCopySameType +type Converter interface { + Convert(source Input) Output +} + +type Input struct { + Name *string + ItemCounts map[string]int +} + +type Output struct { + Name *string + ItemCounts map[string]int +} +``` + +#### **generated/generated.go** + +```go +package generated + +import example "goverter/example" + +type ConverterImpl struct{} + +func (c *ConverterImpl) Convert(source example.Input) example.Output { + var exampleOutput example.Output + exampleOutput.Name = source.Name + exampleOutput.ItemCounts = source.ItemCounts + return exampleOutput +} +``` + + diff --git a/generator/generate.go b/generator/generate.go index 47363d70..33d5c7f2 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.SkipCopy{}, &builder.BasicTargetPointerRule{}, &builder.Pointer{}, &builder.SourcePointer{}, diff --git a/generator/generator.go b/generator/generator.go index 41dbe123..68e562ca 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -423,6 +423,9 @@ func (g *generator) Build( case source.Pointer && source.PointerInner.Named && !source.PointerInner.Basic: createSubMethod = true } + if ctx.Flags.Has(builder.FlagSkipCopySameType) && source.T.String() == target.T.String() { + createSubMethod = false + } } ctx.MarkSeen(source) diff --git a/scenario/skipcopy_identity.yml b/scenario/skipcopy_identity.yml new file mode 100644 index 00000000..7f9421be --- /dev/null +++ b/scenario/skipcopy_identity.yml @@ -0,0 +1,19 @@ +input: + input.go: | + package skip + + // goverter:converter + // goverter:skipCopySameType + type Converter interface { + Convert(source map[string]int) map[string]int + } +success: | + // Code generated by github.com/jmattheis/goverter, DO NOT EDIT. + + package generated + + type ConverterImpl struct{} + + func (c *ConverterImpl) Convert(source map[string]int) map[string]int { + return source + } diff --git a/scenario/skipcopy_inner.yml b/scenario/skipcopy_inner.yml new file mode 100644 index 00000000..7224fefb --- /dev/null +++ b/scenario/skipcopy_inner.yml @@ -0,0 +1,56 @@ +input: + input.go: | + package skip + + import "time" + + // goverter:converter + // goverter:skipCopySameType + type Converter interface { + Convert(source Input) Output + } + + type Input struct { + ID *int + Map map[string]int + MapDifferentType map[string]int + CreatedAt time.Time + Unnamed struct{Name string} + } + type Output struct { + ID *int + Map map[string]int + MapDifferentType map[string]ID + CreatedAt *time.Time + Unnamed struct{Name string} + } + + type ID int +success: | + // Code generated by github.com/jmattheis/goverter, DO NOT EDIT. + + package generated + + import ( + execution "github.com/jmattheis/goverter/execution" + "time" + ) + + type ConverterImpl struct{} + + func (c *ConverterImpl) Convert(source execution.Input) execution.Output { + var skipOutput execution.Output + skipOutput.ID = source.ID + skipOutput.Map = source.Map + mapStringSkipID := make(map[string]execution.ID, len(source.MapDifferentType)) + for key, value := range source.MapDifferentType { + mapStringSkipID[key] = execution.ID(value) + } + skipOutput.MapDifferentType = mapStringSkipID + skipOutput.CreatedAt = c.timeTimeToPTimeTime(source.CreatedAt) + skipOutput.Unnamed = source.Unnamed + return skipOutput + } + func (c *ConverterImpl) timeTimeToPTimeTime(source time.Time) *time.Time { + return &source + } diff --git a/scenario/skipcopy_method.yml b/scenario/skipcopy_method.yml new file mode 100644 index 00000000..cfb2795c --- /dev/null +++ b/scenario/skipcopy_method.yml @@ -0,0 +1,30 @@ +input: + input.go: | + package skip + + // goverter:converter + type Converter interface { + // goverter:skipCopySameType + Convert(source Input) Output + } + + type Input struct { + ID *int + } + type Output struct { + ID *int + } +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 skipOutput execution.Output + skipOutput.ID = source.ID + return skipOutput + } From b0df45af059b302c37a730cc204b88ddde787bb1 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sat, 9 Sep 2023 21:26:51 +0200 Subject: [PATCH 3/4] fix: remove flaky test The test is flaky between different Go versions. --- scenario/generic_go16.yml | 40 --------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 scenario/generic_go16.yml diff --git a/scenario/generic_go16.yml b/scenario/generic_go16.yml deleted file mode 100644 index a6b7e773..00000000 --- a/scenario/generic_go16.yml +++ /dev/null @@ -1,40 +0,0 @@ -input: - go.mod: |- - module github.com/jmattheis/goverter/execution - go 1.16 - input.go: | - package execution - - // goverter:converter - type Converter interface { - Convert(source InputWrapper[Input]) OutputWrapper[Output] - } - - type InputWrapper[S any] struct { - Value S - } - type OutputWrapper[S any] struct { - Value S - } - - type Input struct { - Name string - Age int - } - type Output struct { - Name string - Age int - } -error: |- - could not load package github.com/jmattheis/goverter/execution - - -: # github.com/jmattheis/goverter/execution - ./input.go:5:32: type instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod) - ./input.go:5:54: type instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod) - ./input.go:8:19: type parameter requires go1.18 or later (-lang was set to go1.16; check go.mod) - ./input.go:8:21: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod) - ./input.go:11:20: type parameter requires go1.18 or later (-lang was set to go1.16; check go.mod) - ./input.go:11:22: predeclared any requires go1.18 or later (-lang was set to go1.16; check go.mod) - - Goverter cannot generate converters when there are compile errors because it - requires the type information from the compiled sources. From 316b8ea85bd03304692e60832ece0f57a48289db Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Sat, 9 Sep 2023 21:28:19 +0200 Subject: [PATCH 4/4] docs: bump version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21a5941c..dc4c553f 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ use reflection. 1. Run `goverter`: ```bash - $ go run github.com/jmattheis/goverter/cmd/goverter@v0.17.4 ./ + $ go run github.com/jmattheis/goverter/cmd/goverter@v0.18.0 ./ ``` See [Installation](https://goverter.jmattheis.de/#/install) for more information.