func (g *genConversion) convertibleOnlyWithinPackage(inType, outType *types.Type) bool { var t *types.Type var other *types.Type if inType.Name.Package == g.targetPackage { t, other = inType, outType } else { t, other = outType, inType } if t.Name.Package != g.targetPackage { return false } // If the type has opted out, skip it. tagvals := extractTag(t.CommentLines) if tagvals != nil { if tagvals[0] != "false" { glog.Fatalf("Type %v: unsupported %s value: %q", t, tagName, tagvals[0]) } glog.V(5).Infof("type %v requests no conversion generation, skipping", t) return false } // TODO: Consider generating functions for other kinds too. if t.Kind != types.Struct { return false } // Also, filter out private types. if namer.IsPrivateGoName(other.Name.Name) { return false } return true }
// assignGoTypeToProtoPackage looks for Go and Protobuf types that are referenced by a type in // a package. It will not recurse into protobuf types. func assignGoTypeToProtoPackage(p *protobufPackage, t *types.Type, local, global typeNameSet, optional map[types.Name]struct{}) { newT, isProto := isFundamentalProtoType(t) if isProto { t = newT } if otherP, ok := global[t.Name]; ok { if _, ok := local[t.Name]; !ok { p.Imports.AddType(&types.Type{ Kind: types.Protobuf, Name: otherP.ProtoTypeName(), }) } return } global[t.Name] = p if _, ok := local[t.Name]; ok { return } // don't recurse into existing proto types if isProto { p.Imports.AddType(t) return } local[t.Name] = p for _, m := range t.Members { if namer.IsPrivateGoName(m.Name) { continue } field := &protoField{} tag := reflect.StructTag(m.Tags).Get("protobuf") if tag == "-" { continue } if err := protobufTagToField(tag, field, m, t, p.ProtoTypeName()); err == nil && field.Type != nil { assignGoTypeToProtoPackage(p, field.Type, local, global, optional) continue } assignGoTypeToProtoPackage(p, m.Type, local, global, optional) } // TODO: should methods be walked? if t.Elem != nil { assignGoTypeToProtoPackage(p, t.Elem, local, global, optional) } if t.Key != nil { assignGoTypeToProtoPackage(p, t.Key, local, global, optional) } if t.Underlying != nil { if t.Kind == types.Alias && isOptionalAlias(t) { optional[t.Name] = struct{}{} } assignGoTypeToProtoPackage(p, t.Underlying, local, global, optional) } }
func copyableType(t *types.Type) bool { // If the type opts out of copy-generation, stop. ttag := extractTag(t.CommentLines) if ttag != nil && ttag.value == "false" { return false } // TODO: Consider generating functions for other kinds too. if t.Kind != types.Struct { return false } // Also, filter out private types. if namer.IsPrivateGoName(t.Name.Name) { return false } return true }
func Packages(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages { boilerplate, err := arguments.LoadGoBoilerplate() if err != nil { glog.Fatalf("Failed loading boilerplate: %v", err) } packages := generator.Packages{} header := append([]byte(fmt.Sprintf("// +build !%s\n\n", arguments.GeneratedBuildTag)), boilerplate...) header = append(header, []byte( ` // This file was autogenerated by defaulter-gen. Do not edit it manually! `)...) // Accumulate pre-existing default functions. // TODO: This is too ad-hoc. We need a better way. existingDefaulters := defaulterFuncMap{} buffer := &bytes.Buffer{} sw := generator.NewSnippetWriter(buffer, context, "$", "$") // We are generating defaults only for packages that are explicitly // passed as InputDir. for _, i := range context.Inputs { glog.V(5).Infof("considering pkg %q", i) pkg := context.Universe[i] if pkg == nil { // If the input had no Go files, for example. continue } // Add defaulting functions. getManualDefaultingFunctions(context, pkg, existingDefaulters) var peerPkgs []string if customArgs, ok := arguments.CustomArgs.(*CustomArgs); ok { for _, pkg := range customArgs.ExtraPeerDirs { if i := strings.Index(pkg, "/vendor/"); i != -1 { pkg = pkg[i+len("/vendor/"):] } peerPkgs = append(peerPkgs, pkg) } } // Make sure our peer-packages are added and fully parsed. for _, pp := range peerPkgs { context.AddDir(pp) getManualDefaultingFunctions(context, context.Universe[pp], existingDefaulters) } typesWith := extractTag(pkg.Comments) shouldCreateObjectDefaulterFn := func(t *types.Type) bool { if defaults, ok := existingDefaulters[t]; ok && defaults.object != nil { // A default generator is defined glog.V(5).Infof(" an object defaulter already exists as %s", defaults.base.Name) return false } // opt-out if checkTag(t.SecondClosestCommentLines, "false") { return false } // opt-in if checkTag(t.SecondClosestCommentLines, "true") { return true } // For every k8s:defaulter-gen tag at the package level, interpret the value as a // field name (like TypeMeta, ListMeta, ObjectMeta) and trigger defaulter generation // for any type with any of the matching field names. Provides a more useful package // level defaulting than global (because we only need defaulters on a subset of objects - // usually those with TypeMeta). if t.Kind == types.Struct && len(typesWith) > 0 { for _, field := range t.Members { for _, s := range typesWith { if field.Name == s { return true } } } } return false } newDefaulters := defaulterFuncMap{} for _, t := range pkg.Types { if !shouldCreateObjectDefaulterFn(t) { continue } if namer.IsPrivateGoName(t.Name.Name) { // We won't be able to convert to a private type. glog.V(5).Infof(" found a type %v, but it is a private name", t) continue } // create a synthetic type we can use during generation newDefaulters[t] = defaults{} } // only generate defaulters for objects that actually have defined defaulters // prevents empty defaulters from being registered for { promoted := 0 for t, d := range newDefaulters { if d.object != nil { continue } if buildCallTreeForType(t, true, existingDefaulters, newDefaulters) != nil { args := defaultingArgsFromType(t) sw.Do("$.inType|objectdefaultfn$", args) newDefaulters[t] = defaults{ object: &types.Type{ Name: types.Name{ Package: pkg.Path, Name: buffer.String(), }, Kind: types.Func, }, } buffer.Reset() promoted++ } } if promoted != 0 { continue } // prune any types that were not used for t, d := range newDefaulters { if d.object == nil { glog.V(6).Infof("did not generate defaulter for %s because no child defaulters were registered", t.Name) delete(newDefaulters, t) } } break } if len(newDefaulters) == 0 { glog.V(5).Infof("no defaulters in package %s", pkg.Name) } packages = append(packages, &generator.DefaultPackage{ PackageName: filepath.Base(pkg.Path), PackagePath: pkg.Path, HeaderText: header, GeneratorFunc: func(c *generator.Context) (generators []generator.Generator) { return []generator.Generator{ NewGenDefaulter(arguments.OutputFileBaseName, pkg.Path, existingDefaulters, newDefaulters, peerPkgs), } }, FilterFunc: func(c *generator.Context, t *types.Type) bool { return t.Name.Package == pkg.Path }, }) } return packages }
func membersToFields(locator ProtobufLocator, t *types.Type, localPackage types.Name, omitFieldTypes map[types.Name]struct{}) ([]protoField, error) { fields := []protoField{} for _, m := range t.Members { if namer.IsPrivateGoName(m.Name) { // skip private fields continue } if _, ok := omitFieldTypes[types.Name{Name: m.Type.Name.Name, Package: m.Type.Name.Package}]; ok { continue } tags := reflect.StructTag(m.Tags) field := protoField{ LocalPackage: localPackage, Tag: -1, Extras: make(map[string]string), } protobufTag := tags.Get("protobuf") if protobufTag == "-" { continue } if err := protobufTagToField(protobufTag, &field, m, t, localPackage); err != nil { return nil, err } // extract information from JSON field tag if tag := tags.Get("json"); len(tag) > 0 { parts := strings.Split(tag, ",") if len(field.Name) == 0 && len(parts[0]) != 0 { field.Name = parts[0] } if field.Tag == -1 && field.Name == "-" { continue } } if field.Type == nil { if err := memberTypeToProtobufField(locator, &field, m.Type); err != nil { return nil, fmt.Errorf("unable to embed type %q as field %q in %q: %v", m.Type, field.Name, t.Name, err) } } if len(field.Name) == 0 { field.Name = namer.IL(m.Name) } if field.Map && field.Repeated { // maps cannot be repeated field.Repeated = false field.Nullable = true } if !field.Nullable { field.Extras["(gogoproto.nullable)"] = "false" } if (field.Type.Name.Name == "bytes" && field.Type.Name.Package == "") || (field.Repeated && field.Type.Name.Package == "" && namer.IsPrivateGoName(field.Type.Name.Name)) { delete(field.Extras, "(gogoproto.nullable)") } if field.Name != m.Name { field.Extras["(gogoproto.customname)"] = strconv.Quote(m.Name) } field.CommentLines = m.CommentLines fields = append(fields, field) } // assign tags highest := 0 byTag := make(map[int]*protoField) // fields are in Go struct order, which we preserve for i := range fields { field := &fields[i] tag := field.Tag if tag != -1 { if existing, ok := byTag[tag]; ok { return nil, fmt.Errorf("field %q and %q both have tag %d", field.Name, existing.Name, tag) } byTag[tag] = field } if tag > highest { highest = tag } } // starting from the highest observed tag, assign new field tags for i := range fields { field := &fields[i] if field.Tag != -1 { continue } highest++ field.Tag = highest byTag[field.Tag] = field } return fields, nil }
func (b bodyGen) doStruct(sw *generator.SnippetWriter) error { if len(b.t.Name.Name) == 0 { return nil } if namer.IsPrivateGoName(b.t.Name.Name) { return nil } var alias *types.Type var fields []protoField options := []string{} allOptions := types.ExtractCommentTags("+", b.t.CommentLines) for k, v := range allOptions { switch { case strings.HasPrefix(k, "protobuf.options."): key := strings.TrimPrefix(k, "protobuf.options.") switch key { case "marshal": if v[0] == "false" { if !b.omitGogo { options = append(options, "(gogoproto.marshaler) = false", "(gogoproto.unmarshaler) = false", "(gogoproto.sizer) = false", ) } } default: if !b.omitGogo || !strings.HasPrefix(key, "(gogoproto.") { if key == "(gogoproto.goproto_stringer)" && v[0] == "false" { options = append(options, "(gogoproto.stringer) = false") } options = append(options, fmt.Sprintf("%s = %s", key, v[0])) } } // protobuf.as allows a type to have the same message contents as another Go type case k == "protobuf.as": fields = nil if alias = b.locator.GoTypeForName(types.Name{Name: v[0]}); alias == nil { return fmt.Errorf("type %v references alias %q which does not exist", b.t, v[0]) } // protobuf.embed instructs the generator to use the named type in this package // as an embedded message. case k == "protobuf.embed": fields = []protoField{ { Tag: 1, Name: v[0], Type: &types.Type{ Name: types.Name{ Name: v[0], Package: b.localPackage.Package, Path: b.localPackage.Path, }, }, }, } } } if alias == nil { alias = b.t } // If we don't explicitly embed anything, generate fields by traversing fields. if fields == nil { memberFields, err := membersToFields(b.locator, alias, b.localPackage, b.omitFieldTypes) if err != nil { return fmt.Errorf("type %v cannot be converted to protobuf: %v", b.t, err) } fields = memberFields } out := sw.Out() genComment(out, b.t.CommentLines, "") sw.Do(`message $.Name.Name$ { `, b.t) if len(options) > 0 { sort.Sort(sort.StringSlice(options)) for _, s := range options { fmt.Fprintf(out, " option %s;\n", s) } fmt.Fprintln(out) } for i, field := range fields { genComment(out, field.CommentLines, " ") fmt.Fprintf(out, " ") switch { case field.Map: case field.Repeated: fmt.Fprintf(out, "repeated ") case field.Required: fmt.Fprintf(out, "required ") default: fmt.Fprintf(out, "optional ") } sw.Do(`$.Type|local$ $.Name$ = $.Tag$`, field) if len(field.Extras) > 0 { extras := []string{} for k, v := range field.Extras { if b.omitGogo && strings.HasPrefix(k, "(gogoproto.") { continue } extras = append(extras, fmt.Sprintf("%s = %s", k, v)) } sort.Sort(sort.StringSlice(extras)) if len(extras) > 0 { fmt.Fprintf(out, " [") fmt.Fprint(out, strings.Join(extras, ", ")) fmt.Fprintf(out, "]") } } fmt.Fprintf(out, ";\n") if i != len(fields)-1 { fmt.Fprintf(out, "\n") } } fmt.Fprintf(out, "}\n\n") return nil }