diff --git a/builder/builder.go b/builder/builder.go index 64d03ee..d40e548 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -64,6 +64,7 @@ type MethodContext struct { Conf *config.Method FieldsTarget string OutputPackagePath string + UseConstructor bool Signature xtype.Signature TargetType *xtype.Type HasMethod func(*MethodContext, types.Type, types.Type) bool diff --git a/builder/default.go b/builder/default.go index e8d03f0..e13376e 100644 --- a/builder/default.go +++ b/builder/default.go @@ -8,7 +8,7 @@ import ( ) func buildTargetVar(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type, errPath ErrorPath) ([]jen.Code, *jen.Statement, *Error) { - if ctx.Conf.Constructor == nil || + if !ctx.UseConstructor || !types.Identical(ctx.Conf.Source.T, source.T) || !types.Identical(ctx.Conf.Target.T, target.T) { name := ctx.Name(target.ID()) @@ -16,6 +16,7 @@ func buildTargetVar(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, so ctx.SetErrorTargetVar(jen.Id(name)) return []jen.Code{variable}, jen.Id(name), nil } + ctx.UseConstructor = false callTarget := target toPointer := target.Pointer && !ctx.Conf.Constructor.Target.Pointer diff --git a/builder/pointer.go b/builder/pointer.go index 6da834c..c479c61 100644 --- a/builder/pointer.go +++ b/builder/pointer.go @@ -16,21 +16,34 @@ func (*Pointer) Matches(_ *MethodContext, source, target *xtype.Type) bool { // Build creates conversion source code for the given source and target type. func (p *Pointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type, errPath ErrorPath) ([]jen.Code, *xtype.JenID, *Error) { ctx.SetErrorTargetVar(jen.Nil()) + if ctx.UseConstructor { + buildStmt, valueVar, err := buildTargetVar(gen, ctx, sourceID, source, target, errPath) + if err != nil { + return nil, nil, err + } + + stmt, err := gen.Assign(ctx, AssignOf(jen.Parens(jen.Op("*").Add(valueVar))), sourceID.Deref(source), source.PointerInner, target.PointerInner, errPath) + if err != nil { + return nil, nil, err.Lift(&Path{ + SourceID: "*", + SourceType: source.PointerInner.String, + TargetID: "*", + TargetType: target.PointerInner.String, + }) + } + + buildStmt = append(buildStmt, jen.If(sourceID.Code.Clone().Op("!=").Nil()).Block(stmt...)) + + return buildStmt, xtype.VariableID(valueVar), nil + } + return BuildByAssign(p, gen, ctx, sourceID, source, target, errPath) } func (*Pointer) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, sourceID *xtype.JenID, source, target *xtype.Type, errPath ErrorPath) ([]jen.Code, *Error) { ctx.SetErrorTargetVar(jen.Nil()) - valueSourceID := jen.Op("*").Add(sourceID.Code.Clone()) - if !source.PointerInner.Basic { - valueSourceID = jen.Parens(valueSourceID) - } - - innerID := xtype.OtherID(valueSourceID) - innerID.ParentPointer = sourceID - nextBlock, id, err := gen.Build( - ctx, innerID, source.PointerInner, target.PointerInner, errPath) + nextBlock, id, err := gen.Build(ctx, sourceID.Deref(source), source.PointerInner, target.PointerInner, errPath) if err != nil { return nil, err.Lift(&Path{ SourceID: "*", @@ -62,19 +75,31 @@ func (*SourcePointer) Matches(ctx *MethodContext, source, target *xtype.Type) bo // Build creates conversion source code for the given source and target type. func (s *SourcePointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type, path ErrorPath) ([]jen.Code, *xtype.JenID, *Error) { - return BuildByAssign(s, gen, ctx, sourceID, source, target, path) -} -func (*SourcePointer) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, sourceID *xtype.JenID, source, target *xtype.Type, path ErrorPath) ([]jen.Code, *Error) { - valueSourceID := jen.Op("*").Add(sourceID.Code.Clone()) - if !source.PointerInner.Basic { - valueSourceID = jen.Parens(valueSourceID) + if ctx.UseConstructor { + buildStmt, valueVar, err := buildTargetVar(gen, ctx, sourceID, source, target, path) + if err != nil { + return nil, nil, err + } + + stmt, err := gen.Assign(ctx, AssignOf(valueVar), sourceID.Deref(source), source.PointerInner, target, path) + if err != nil { + return nil, nil, err.Lift(&Path{ + SourceID: "*", + SourceType: source.PointerInner.String, + }) + } + + buildStmt = append(buildStmt, jen.If(sourceID.Code.Clone().Op("!=").Nil()).Block(stmt...)) + + return buildStmt, xtype.VariableID(valueVar), nil } - innerID := xtype.OtherID(valueSourceID) - innerID.ParentPointer = sourceID + return BuildByAssign(s, gen, ctx, sourceID, source, target, path) +} - nextInner, nextID, err := gen.Build(ctx, innerID, source.PointerInner, target, path) +func (*SourcePointer) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, sourceID *xtype.JenID, source, target *xtype.Type, path ErrorPath) ([]jen.Code, *Error) { + nextInner, nextID, err := gen.Build(ctx, sourceID.Deref(source), source.PointerInner, target, path) if err != nil { return nil, err.Lift(&Path{ SourceID: "*", @@ -102,11 +127,29 @@ func (*TargetPointer) Matches(_ *MethodContext, source, target *xtype.Type) bool // Build creates conversion source code for the given source and target type. func (*TargetPointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type, path ErrorPath) ([]jen.Code, *xtype.JenID, *Error) { ctx.SetErrorTargetVar(jen.Nil()) + + if ctx.UseConstructor { + buildStmt, valueVar, err := buildTargetVar(gen, ctx, sourceID, source, target, path) + if err != nil { + return nil, nil, err + } + + stmt, err := gen.Assign(ctx, AssignOf(jen.Parens(jen.Op("*").Add(valueVar))), sourceID, source, target.PointerInner, path) + if err != nil { + return nil, nil, err.Lift(&Path{ + TargetID: "*", + TargetType: target.PointerInner.String, + }) + } + + buildStmt = append(buildStmt, stmt...) + + return buildStmt, xtype.VariableID(valueVar), nil + } + stmt, id, err := gen.Build(ctx, sourceID, source, target.PointerInner, path) if err != nil { return nil, nil, err.Lift(&Path{ - SourceID: "*", - SourceType: source.String, TargetID: "*", TargetType: target.PointerInner.String, }) @@ -114,6 +157,7 @@ func (*TargetPointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.J pstmt, nextID := id.Pointer(target.PointerInner, ctx.Name) stmt = append(stmt, pstmt...) + return stmt, nextID, nil } diff --git a/example/default/generated/generated.go b/example/default/generated/generated.go index 2f85d94..1b50ef4 100644 --- a/example/default/generated/generated.go +++ b/example/default/generated/generated.go @@ -10,13 +10,11 @@ type ConverterImpl struct{} func (c *ConverterImpl) Convert(source *default1.Input) *default1.Output { pExampleOutput := default1.NewOutput() if source != nil { - var exampleOutput default1.Output - exampleOutput.Age = (*source).Age + (*pExampleOutput).Age = (*source).Age if (*source).Name != nil { xstring := *(*source).Name - exampleOutput.Name = &xstring + (*pExampleOutput).Name = &xstring } - pExampleOutput = &exampleOutput } return pExampleOutput } diff --git a/generator/generator.go b/generator/generator.go index 63a54fe..8c216a7 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -142,6 +142,7 @@ func (g *generator) buildMethod(genMethod *generatedMethod, context map[string]* Signature: genMethod.Signature, HasMethod: g.hasMethod, OutputPackagePath: g.conf.OutputPackagePath, + UseConstructor: genMethod.Constructor != nil, } var targetAssign *jen.Statement diff --git a/scenario/default_on_pointer.yml b/scenario/default_on_pointer.yml index 37b3c7a..31fbff1 100644 --- a/scenario/default_on_pointer.yml +++ b/scenario/default_on_pointer.yml @@ -32,9 +32,7 @@ success: func (c *ConverterImpl) Convert(source *execution.Input) (*execution.Output, error) { pExecutionOutput := execution.NewOutputWithDefaults() if source != nil { - var executionOutput execution.Output - executionOutput.Name = (*source).Name - pExecutionOutput = &executionOutput + (*pExecutionOutput).Name = (*source).Name } return pExecutionOutput, nil } diff --git a/scenario/default_on_pointer_error.yml b/scenario/default_on_pointer_error.yml new file mode 100644 index 0000000..f39b480 --- /dev/null +++ b/scenario/default_on_pointer_error.yml @@ -0,0 +1,47 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + Convert(source *Input) (*Output, error) + } + type Input struct { + Name int + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() *Output { + return &Output{ + Name: "string", + } + } +error: |- + Error while creating converter method: + @workdir/input.go:6 + func (github.com/jmattheis/goverter/execution.Converter).Convert(source *github.com/jmattheis/goverter/execution.Input) (*github.com/jmattheis/goverter/execution.Output, error) + [source] *github.com/jmattheis/goverter/execution.Input + [target] *github.com/jmattheis/goverter/execution.Output + + | *github.com/jmattheis/goverter/execution.Input + | + | | github.com/jmattheis/goverter/execution.Input + | | + | | | int + | | | + source*.Name + target*.Name + | | | + | | | string + | | + | | github.com/jmattheis/goverter/execution.Output + | + | *github.com/jmattheis/goverter/execution.Output + + TypeMismatch: Cannot convert int to string + + You can define a custom conversion method with extend: + https://goverter.jmattheis.de/reference/extend diff --git a/scenario/default_on_pointer_with_error.yml b/scenario/default_on_pointer_with_error.yml index 4a627f6..52ee3e9 100644 --- a/scenario/default_on_pointer_with_error.yml +++ b/scenario/default_on_pointer_with_error.yml @@ -33,9 +33,7 @@ success: return nil, err } if source != nil { - var executionOutput execution.Output - executionOutput.ID = (*source).ID - pExecutionOutput = &executionOutput + (*pExecutionOutput).ID = (*source).ID } return pExecutionOutput, nil } diff --git a/scenario/default_on_pointer_with_non_pointer_method.yml b/scenario/default_on_pointer_with_non_pointer_method.yml index 3de14a9..db83c31 100644 --- a/scenario/default_on_pointer_with_non_pointer_method.yml +++ b/scenario/default_on_pointer_with_non_pointer_method.yml @@ -32,9 +32,7 @@ success: executionOutput := execution.NewOutput() pExecutionOutput := &executionOutput if source != nil { - var executionOutput2 execution.Output - executionOutput2.Name = (*source).Name - pExecutionOutput = &executionOutput2 + (*pExecutionOutput).Name = (*source).Name } return pExecutionOutput } diff --git a/scenario/default_on_pointer_with_non_pointer_method_with_error.yml b/scenario/default_on_pointer_with_non_pointer_method_with_error.yml index 3326b3e..76acc1d 100644 --- a/scenario/default_on_pointer_with_non_pointer_method_with_error.yml +++ b/scenario/default_on_pointer_with_non_pointer_method_with_error.yml @@ -35,9 +35,7 @@ success: } pExecutionOutput := &executionOutput if source != nil { - var executionOutput2 execution.Output - executionOutput2.Name = (*source).Name - pExecutionOutput = &executionOutput2 + (*pExecutionOutput).Name = (*source).Name } return pExecutionOutput, nil } diff --git a/scenario/default_on_source_pointer_struct_target_struct.yml b/scenario/default_on_source_pointer_struct_target_struct.yml new file mode 100644 index 0000000..bd25839 --- /dev/null +++ b/scenario/default_on_source_pointer_struct_target_struct.yml @@ -0,0 +1,36 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + Convert(source Input) (*Output, error) + } + type Input struct { + Name string + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() *Output { + return &Output{ + Name: "string", + } + } +success: + - generated/generated.go: | + // 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, error) { + pExecutionOutput := execution.NewOutputWithDefaults() + (*pExecutionOutput).Name = source.Name + return pExecutionOutput, nil + } diff --git a/scenario/default_on_source_pointer_struct_target_struct_error.yml b/scenario/default_on_source_pointer_struct_target_struct_error.yml new file mode 100644 index 0000000..85270a8 --- /dev/null +++ b/scenario/default_on_source_pointer_struct_target_struct_error.yml @@ -0,0 +1,45 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + Convert(source Input) (*Output, error) + } + type Input struct { + Name int + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() *Output { + return &Output{ + Name: "string", + } + } +error: |- + Error while creating converter method: + @workdir/input.go:6 + func (github.com/jmattheis/goverter/execution.Converter).Convert(source github.com/jmattheis/goverter/execution.Input) (*github.com/jmattheis/goverter/execution.Output, error) + [source] github.com/jmattheis/goverter/execution.Input + [target] *github.com/jmattheis/goverter/execution.Output + + | github.com/jmattheis/goverter/execution.Input + | + | | int + | | + source .Name + target*.Name + | | | + | | | string + | | + | | github.com/jmattheis/goverter/execution.Output + | + | *github.com/jmattheis/goverter/execution.Output + + TypeMismatch: Cannot convert int to string + + You can define a custom conversion method with extend: + https://goverter.jmattheis.de/reference/extend diff --git a/scenario/default_on_source_pointer_struct_target_struct_invalid_sig.yml b/scenario/default_on_source_pointer_struct_target_struct_invalid_sig.yml new file mode 100644 index 0000000..df1a5ba --- /dev/null +++ b/scenario/default_on_source_pointer_struct_target_struct_invalid_sig.yml @@ -0,0 +1,41 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + Convert(source Input) (*Output, error) + } + type Input struct { + Name string + } + type Output struct { + Name string + } + + func NewOutputWithDefaults(string) *Output { + return &Output{ + Name: "string", + } + } +error: |- + Error while creating converter method: + @workdir/input.go:6 + func (github.com/jmattheis/goverter/execution.Converter).Convert(source github.com/jmattheis/goverter/execution.Input) (*github.com/jmattheis/goverter/execution.Output, error) + [source] github.com/jmattheis/goverter/execution.Input + [target] *github.com/jmattheis/goverter/execution.Output + + | github.com/jmattheis/goverter/execution.Input + | + source + target + | + | *github.com/jmattheis/goverter/execution.Output + + Error using method: + func github.com/jmattheis/goverter/execution.NewOutputWithDefaults(string) *github.com/jmattheis/goverter/execution.Output + [source] string + [target] *github.com/jmattheis/goverter/execution.Output + + Method source type mismatches with conversion source: string != github.com/jmattheis/goverter/execution.Input diff --git a/scenario/default_on_source_struct_target_pointer.yml b/scenario/default_on_source_struct_target_pointer.yml new file mode 100644 index 0000000..77fbe00 --- /dev/null +++ b/scenario/default_on_source_struct_target_pointer.yml @@ -0,0 +1,39 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + // goverter:useZeroValueOnPointerInconsistency + Convert(source *Input) (Output, error) + } + type Input struct { + Name string + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() Output { + return Output{ + Name: "string", + } + } +success: + - generated/generated.go: | + // 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, error) { + executionOutput := execution.NewOutputWithDefaults() + if source != nil { + executionOutput.Name = (*source).Name + } + return executionOutput, nil + } diff --git a/scenario/default_on_source_struct_target_pointer_default_mismatch.yml b/scenario/default_on_source_struct_target_pointer_default_mismatch.yml new file mode 100644 index 0000000..83a3ed1 --- /dev/null +++ b/scenario/default_on_source_struct_target_pointer_default_mismatch.yml @@ -0,0 +1,28 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + // goverter:useZeroValueOnPointerInconsistency + Convert(source *Input) (Output, error) + } + type Input struct { + Name string + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() *Output { + return Output{ Name: "string" } + } +error: |- + could not load package github.com/jmattheis/goverter/execution + + -: # github.com/jmattheis/goverter/execution + ./input.go:17:12: cannot use Output{…} (value of type Output) as *Output value in return statement + + Goverter cannot generate converters when there are compile errors because it + requires the type information from the compiled sources. diff --git a/scenario/default_on_source_struct_target_pointer_inner_error.yml b/scenario/default_on_source_struct_target_pointer_inner_error.yml new file mode 100644 index 0000000..42bf592 --- /dev/null +++ b/scenario/default_on_source_struct_target_pointer_inner_error.yml @@ -0,0 +1,46 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + // goverter:useZeroValueOnPointerInconsistency + Convert(source *Input) (Output, error) + } + type Input struct { + Name int + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() Output { + return Output{ + Name: "string", + } + } +error: |- + Error while creating converter method: + @workdir/input.go:7 + func (github.com/jmattheis/goverter/execution.Converter).Convert(source *github.com/jmattheis/goverter/execution.Input) (github.com/jmattheis/goverter/execution.Output, error) + [source] *github.com/jmattheis/goverter/execution.Input + [target] github.com/jmattheis/goverter/execution.Output + + | *github.com/jmattheis/goverter/execution.Input + | + | | github.com/jmattheis/goverter/execution.Input + | | + | | | int + | | | + source*.Name + target .Name + | | + | | string + | + | github.com/jmattheis/goverter/execution.Output + + TypeMismatch: Cannot convert int to string + + You can define a custom conversion method with extend: + https://goverter.jmattheis.de/reference/extend diff --git a/scenario/default_pointer_inconsistency.yml b/scenario/default_pointer_inconsistency.yml index 7cc44a5..013ed3c 100644 --- a/scenario/default_pointer_inconsistency.yml +++ b/scenario/default_pointer_inconsistency.yml @@ -33,9 +33,7 @@ success: func (c *ConverterImpl) Convert(source *execution.Input) execution.Output { executionOutput := execution.NewOutput() if source != nil { - var executionOutput2 execution.Output - executionOutput2.ID = (*source).ID - executionOutput = executionOutput2 + executionOutput.ID = (*source).ID } return executionOutput } diff --git a/scenario/map_overlapping_config2.yml b/scenario/map_overlapping_config2.yml index 292133e..d71866f 100644 --- a/scenario/map_overlapping_config2.yml +++ b/scenario/map_overlapping_config2.yml @@ -25,9 +25,7 @@ error: |- | github.com/jmattheis/goverter/execution.Input | - | | github.com/jmattheis/goverter/execution.Input - | | - source* + source target* | | | | github.com/jmattheis/goverter/execution.Output diff --git a/scenario/setting_ignored_recursive2.yml b/scenario/setting_ignored_recursive2.yml index 90a4edf..ae82c67 100644 --- a/scenario/setting_ignored_recursive2.yml +++ b/scenario/setting_ignored_recursive2.yml @@ -23,13 +23,11 @@ error: |- | github.com/jmattheis/goverter/execution.Input | - | | github.com/jmattheis/goverter/execution.Input - | | - | | | []github.com/jmattheis/goverter/execution.Input - | | | - | | | | github.com/jmattheis/goverter/execution.Input - | | | | - source*.Inputs [] + | | []github.com/jmattheis/goverter/execution.Input + | | + | | | github.com/jmattheis/goverter/execution.Input + | | | + source .Inputs [] target*.Outputs[] | | | | | | | | github.com/jmattheis/goverter/execution.Output diff --git a/scenario/type_mismatch_pointer.yml b/scenario/type_mismatch_pointer.yml index 96afd5a..434126a 100644 --- a/scenario/type_mismatch_pointer.yml +++ b/scenario/type_mismatch_pointer.yml @@ -24,11 +24,9 @@ error: |- | github.com/jmattheis/goverter/execution.Input | - | | github.com/jmattheis/goverter/execution.Input - | | - | | | int - | | | - source*.Age + | | int + | | + source .Age target*.Age | | | | | | int64 diff --git a/xtype/type.go b/xtype/type.go index 163c190..365f2f8 100644 --- a/xtype/type.go +++ b/xtype/type.go @@ -196,6 +196,16 @@ func (j *JenID) Pointer(t *Type, namer func(string) string) ([]jen.Code, *JenID) return stmt, OtherID(jen.Op("&").Id(name)) } +func (j *JenID) Deref(source *Type) *JenID { + valueSourceID := jen.Op("*").Add(j.Code.Clone()) + if !source.PointerInner.Basic { + valueSourceID = jen.Parens(valueSourceID) + } + innerID := OtherID(valueSourceID) + innerID.ParentPointer = j + return innerID +} + // VariableID is used, when the ID can be referenced. F.ex it is not a function call. func VariableID(code *jen.Statement) *JenID { return &JenID{Code: code, Variable: true}