From 6f0023e6e83861b895864fabcca19daf7b7e11b6 Mon Sep 17 00:00:00 2001 From: GuangyuFan <97507466+FGYFFFF@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:59:30 +0800 Subject: [PATCH] feat: support generate nested struct for slim template (#101) --- args.go | 35 ++++++++++++++++++- generator/golang/option.go | 12 +++---- generator/golang/scope.go | 6 ++++ generator/golang/scope_internal.go | 45 +++++++++++++++++++++++-- generator/golang/templates/slim/slim.go | 6 +++- 5 files changed, 94 insertions(+), 10 deletions(-) diff --git a/args.go b/args.go index 1f4a47cd..4e3e7dee 100644 --- a/args.go +++ b/args.go @@ -27,6 +27,7 @@ import ( "github.com/cloudwego/thriftgo/generator" "github.com/cloudwego/thriftgo/generator/backend" + "github.com/cloudwego/thriftgo/generator/golang" "github.com/cloudwego/thriftgo/plugin" ) @@ -99,7 +100,12 @@ func (a *Arguments) Targets() (specs []*generator.LangSpec, err error) { if err != nil { return nil, err } - + opts, err := a.checkOptions(desc.Options) + if err != nil { + return nil, err + } + // checkOptions may modify the content of the options + desc.Options = opts spec := &generator.LangSpec{ Language: desc.Name, Options: desc.Options, @@ -109,6 +115,33 @@ func (a *Arguments) Targets() (specs []*generator.LangSpec, err error) { return } +// checkOptions used to validate the command parameters. +func (a *Arguments) checkOptions(opts []plugin.Option) ([]plugin.Option, error) { + params := plugin.Pack(opts) + cu := golang.NewCodeUtils(backend.DummyLogFunc()) + cu.HandleOptions(params) + if cu.Features().EnableNestedStruct { + // In nested mode, if template is not 'slim', it is automatically converted to slim + if cu.Template() != "slim" { + found := false + for _, opt := range opts { + if opt.Name == "template" { + log.Printf("[WARN] EnableNestedStruct is only available under the \"slim\" template, so adapt the template to \"slim\"") + opt.Desc = "slim" + found = true + break + } + } + if !found { + log.Printf("[WARN] EnableNestedStruct is only available under the \"slim\" template, so adapt the template to \"slim\"") + opts = append(opts, plugin.Option{Name: "template", Desc: "slim"}) + } + + } + } + return opts, nil +} + // MakeLogFunc creates logging functions according to command line flags. func (a *Arguments) MakeLogFunc() backend.LogFunc { logs := backend.DummyLogFunc() diff --git a/generator/golang/option.go b/generator/golang/option.go index f7c6530e..f79775af 100644 --- a/generator/golang/option.go +++ b/generator/golang/option.go @@ -55,12 +55,11 @@ type Features struct { CodeRef bool `code_ref:"Genenerate code ref by given idl-ref.yaml"` KeepCodeRefName bool `keep_code_ref_name:"Genenerate code ref but still keep file name."` TrimIDL bool `trim_idl:"Simplify IDL to the most concise form before generating code."` - - JSONStringer bool `json_stringer:"Generate the JSON marshal method in String() method."` - - WithFieldMask bool `with_field_mask:"Support field-mask for generated code."` - FieldMaskHalfway bool `field_mask_halfway:"Support set field-mask on not-root struct."` - FieldMaskZeroRequired bool `field_mask_zero_required:"Write zero value instead of current value for required fields filtered by fieldmask."` + EnableNestedStruct bool `enable_nested_struct:"Generate nested field when 'thrift.nested=\"true\"' annotation is set to field, valid only in 'slim template'"` + JSONStringer bool `json_stringer:"Generate the JSON marshal method in String() method."` + WithFieldMask bool `with_field_mask:"Support field-mask for generated code."` + FieldMaskHalfway bool `field_mask_halfway:"Support set field-mask on not-root struct."` + FieldMaskZeroRequired bool `field_mask_zero_required:"Write zero value instead of current value for required fields filtered by fieldmask."` } var defaultFeatures = Features{ @@ -95,6 +94,7 @@ var defaultFeatures = Features{ WithFieldMask: false, FieldMaskHalfway: false, FieldMaskZeroRequired: false, + EnableNestedStruct: false, } type param struct { diff --git a/generator/golang/scope.go b/generator/golang/scope.go index f55039f6..e61dd3c9 100644 --- a/generator/golang/scope.go +++ b/generator/golang/scope.go @@ -496,6 +496,7 @@ type Field struct { setter Name isset Name deepEqual Name + isNested bool } // GoName returns the name in go code of the field. @@ -555,6 +556,11 @@ func (f *Field) DeepEqual() Name { return f.deepEqual } +// IsNested returns whether the field is a nested type. +func (f *Field) IsNested() bool { + return f.isNested +} + // StructLike is a wrapper for the parser.StructLike. type StructLike struct { *parser.StructLike diff --git a/generator/golang/scope_internal.go b/generator/golang/scope_internal.go index eb3a0ffa..2c0986eb 100644 --- a/generator/golang/scope_internal.go +++ b/generator/golang/scope_internal.go @@ -24,8 +24,12 @@ import ( "github.com/cloudwego/thriftgo/pkg/namespace" ) -// A prefix to denote synthesized identifiers. -const prefix = "$" +const ( + // A prefix to denote synthesized identifiers. + prefix = "$" + // nestedAnnotation is to denote the field is nested type. + nestedAnnotation = "thrift.nested" +) func _p(id string) string { return prefix + id @@ -321,6 +325,15 @@ func (s *Scope) buildStructLike(cu *CodeUtils, v *parser.StructLike, usedName .. // reserve method names for _, f := range v.Fields { fn := s.identify(cu, f.Name) + if cu.Features().EnableNestedStruct && isNestedField(f) { + // EnableNestedStruct, the type name needs to be used when retrieving the value for getter&setter + fn = s.identify(cu, f.Type.Name) + if strings.Contains(fn, ".") { + fns := strings.Split(fn, ".") + fn = s.identify(cu, fns[len(fns)-1]) + } + } + st.scope.Add("Get"+fn, _p("get:"+f.Name)) if cu.Features().GenerateSetter { st.scope.Add("Set"+fn, _p("set:"+f.Name)) @@ -339,6 +352,10 @@ func (s *Scope) buildStructLike(cu *CodeUtils, v *parser.StructLike, usedName .. // field names for _, f := range v.Fields { fn := s.identify(cu, f.Name) + isNested := false + if cu.Features().EnableNestedStruct && isNestedField(f) { + isNested = true + } fn = st.scope.Add(fn, f.Name) id := id2str(f.ID) st.fields = append(st.fields, &Field{ @@ -350,6 +367,7 @@ func (s *Scope) buildStructLike(cu *CodeUtils, v *parser.StructLike, usedName .. setter: Name(st.scope.Get(_p("set:" + f.Name))), isset: Name(st.scope.Get(_p("isset:" + f.Name))), deepEqual: Name(st.scope.Get(_p("deepequal:" + id))), + isNested: isNested, }) } @@ -401,6 +419,18 @@ func (s *Scope) resolveTypesAndValues(cu *CodeUtils) { for f := range ff { v := f.Field f.typeName = ensureType(resolver.ResolveFieldTypeName(v)) + // This is used to set the real field name for nested struct, ex. + // type T struct { + // *Nested + // } + if cu.Features().EnableNestedStruct && isNestedField(f.Field) { + name := f.typeName.Deref().String() + if strings.Contains(name, ".") { + names := strings.Split(name, ".") + name = names[len(names)-1] + } + f.name = Name(name) + } f.frugalTypeName = ensureType(frugalResolver.ResolveFrugalTypeName(v.Type)) f.defaultTypeName = ensureType(resolver.GetDefaultValueTypeName(v)) if f.IsSetDefault() { @@ -450,3 +480,14 @@ func (s *Scope) resolveTypesAndValues(cu *CodeUtils) { } } } + +func isNestedField(f *parser.Field) bool { + annos := f.Annotations.Get(nestedAnnotation) + if len(annos) == 0 { + return false + } + if strings.EqualFold(annos[0], "true") { + return true + } + return false +} diff --git a/generator/golang/templates/slim/slim.go b/generator/golang/templates/slim/slim.go index 74d61381..4c9e8c9d 100644 --- a/generator/golang/templates/slim/slim.go +++ b/generator/golang/templates/slim/slim.go @@ -38,7 +38,11 @@ type {{$TypeName}} struct { {{- if and Features.ReserveComments .ReservedComments}} {{.ReservedComments}} {{- end}} - {{(.GoName)}} {{.GoTypeName}} {{GenFieldTags . (InsertionPoint $.Category $.Name .Name "tag")}} + {{- if .IsNested}} + {{.GoTypeName}} {{GenFieldTags . (InsertionPoint $.Category $.Name .Name "tag")}} + {{else}} + {{(.GoName)}} {{.GoTypeName}} {{GenFieldTags . (InsertionPoint $.Category $.Name .Name "tag")}} + {{- end}} {{- end}} {{- if Features.KeepUnknownFields}} {{- UseStdLibrary "unknown"}}