diff --git a/README.md b/README.md index 6deee7eb..93cabc5b 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,11 @@ | `protoc-gen-cpp-tableau-loader` | C++17 | `*.pc.h` / `*.pc.cc` | | `protoc-gen-csharp-tableau-loader` | C# (Unity 2022.3 LTS / .NET 8) | `*.pc.cs` | +The Go plugin accepts the following options (via the `opt` field of `buf.gen.yaml`): + +- `pkg=` — package name of the generated loader code (default `tableau`). +- `const=true` — generate a const API that exposes [goconst](https://github.com/Kybxd/goconst) read-only `Foo_Const` views through `Messager.Data()` and the typed `Get*` / `Find*` accessors. Mutation through those APIs is a compile error. `Message()` remains the mutable escape hatch; the runtime `MutableCheck` still applies to it. Requires also running `protoc-gen-go-const` on the same protos (so `Foo_Const` types live in the protoconf package) and a dependency on `github.com/Kybxd/goconst`. + ## Quick start Use [`make.py`](./make.py) (Python 3.10+, stdlib only): diff --git a/cmd/protoc-gen-go-tableau-loader/helper/helper.go b/cmd/protoc-gen-go-tableau-loader/helper/helper.go index 762a0614..40345437 100644 --- a/cmd/protoc-gen-go-tableau-loader/helper/helper.go +++ b/cmd/protoc-gen-go-tableau-loader/helper/helper.go @@ -201,6 +201,13 @@ func FindMessageGoIdent(gen *protogen.Plugin, md protoreflect.MessageDescriptor) return msg.GoIdent } +// ConstViewType returns the goconst read-only view type name (e.g. +// "protoconf.Item_Const") for the given message descriptor. The returned value +// is already fully qualified, so it can be emitted directly via g.P. +func ConstViewType(g *protogen.GeneratedFile, gen *protogen.Plugin, md protoreflect.MessageDescriptor) string { + return g.QualifiedGoIdent(FindMessageGoIdent(gen, md)) + "_Const" +} + func FindEnum(gen *protogen.Plugin, ed protoreflect.EnumDescriptor) *protogen.Enum { if file, ok := gen.FilesByPath[ed.ParentFile().Path()]; ok { if enum := FindEnumByDescriptor(file.Enums, ed); enum != nil { diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/generator.go b/cmd/protoc-gen-go-tableau-loader/indexes/generator.go index b7431e0b..a4a2379d 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/generator.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/generator.go @@ -10,21 +10,23 @@ import ( ) type Generator struct { - gen *protogen.Plugin - g *protogen.GeneratedFile - descriptor *index.IndexDescriptor - message *protogen.Message + gen *protogen.Plugin + g *protogen.GeneratedFile + descriptor *index.IndexDescriptor + message *protogen.Message + constEnabled bool // level message keys helper.MapKeySlice } -func NewGenerator(gen *protogen.Plugin, g *protogen.GeneratedFile, descriptor *index.IndexDescriptor, message *protogen.Message) *Generator { +func NewGenerator(gen *protogen.Plugin, g *protogen.GeneratedFile, descriptor *index.IndexDescriptor, message *protogen.Message, constEnabled bool) *Generator { generator := &Generator{ - gen: gen, - g: g, - descriptor: descriptor, - message: message, + gen: gen, + g: g, + descriptor: descriptor, + message: message, + constEnabled: constEnabled, } generator.initLevelMessage() return generator @@ -64,6 +66,24 @@ func (x *Generator) mapValueType(index *index.LevelIndex) protogen.GoIdent { return helper.FindMessageGoIdent(x.gen, index.MD) } +// indexValueElem returns the element type used inside the index's slice/map +// containers for the indexed values. In non-const mode it is the mutable +// pointer type "*protoconf.Item"; in const mode it is the read-only value type +// "protoconf.Item_Const". +func (x *Generator) indexValueElem(index *index.LevelIndex) string { + if x.constEnabled { + return helper.ConstViewType(x.g, x.gen, index.MD) + } + return "*" + x.g.QualifiedGoIdent(x.mapValueType(index)) +} + +func (x *Generator) findFirstMissComment() string { + if x.constEnabled { + return "or a zero value if no value found." + } + return "or nil if no value found." +} + func (x *Generator) fieldGetter(fd protoreflect.FieldDescriptor) string { return fmt.Sprintf(".Get%s()", helper.ParseIndexFieldName(x.gen, fd)) } diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/index.go b/cmd/protoc-gen-go-tableau-loader/indexes/index.go index 2251791d..ed0d3b6a 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/index.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/index.go @@ -70,7 +70,7 @@ func (x *Generator) genIndexTypeDef() { } x.g.P("}") } - x.g.P("type ", x.indexMapType(index), " = map[", x.indexMapKeyType(index), "][]*", x.mapValueType(index)) + x.g.P("type ", x.indexMapType(index), " = map[", x.indexMapKeyType(index), "][]", x.indexValueElem(index)) x.g.P() } } @@ -187,14 +187,18 @@ func (x *Generator) generateOneMulticolumnIndex(lm *index.LevelMessage, index *i func (x *Generator) genIndexLoaderCommon(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { indexContainerName := x.indexContainerName(index, 0) - x.g.P("x.", indexContainerName, "[key] = append(x.", indexContainerName, "[key], ", parentDataName, ")") + appender := parentDataName + if x.constEnabled { + appender = parentDataName + ".AsConst()" + } + x.g.P("x.", indexContainerName, "[key] = append(x.", indexContainerName, "[key], ", appender, ")") for i := 1; i < lm.LeveledContainerDepth(); i++ { indexContainerName := x.indexContainerName(index, i) if i == 1 { x.g.P("if x.", indexContainerName, "[k1] == nil {") x.g.P("x.", indexContainerName, "[k1] = make(", x.indexMapType(index), ")") x.g.P("}") - x.g.P("x.", indexContainerName, "[k1][key] = append(x.", indexContainerName, "[k1][key], ", parentDataName, ")") + x.g.P("x.", indexContainerName, "[k1][key] = append(x.", indexContainerName, "[k1][key], ", appender, ")") } else { var fields []string for j := 1; j <= i; j++ { @@ -206,7 +210,7 @@ func (x *Generator) genIndexLoaderCommon(lm *index.LevelMessage, index *index.Le x.g.P("if x.", indexContainerName, "[", keyName, "] == nil {") x.g.P("x.", indexContainerName, "[", keyName, "] = make(", x.indexMapType(index), ")") x.g.P("}") - x.g.P("x.", indexContainerName, "[", keyName, "][key] = append(x.", indexContainerName, "[", keyName, "][key], ", parentDataName, ")") + x.g.P("x.", indexContainerName, "[", keyName, "][key] = append(x.", indexContainerName, "[", keyName, "][key], ", appender, ")") } } } @@ -217,7 +221,7 @@ func (x *Generator) genIndexSorter() { if len(index.SortedColFields) != 0 { x.g.P("// Index(sort): ", index.Index) indexContainerName := x.indexContainerName(index, 0) - x.g.P(indexContainerName, "Sorter := func(itemList []*", x.mapValueType(index), ") func(i, j int) bool {") + x.g.P(indexContainerName, "Sorter := func(itemList []", x.indexValueElem(index), ") func(i, j int) bool {") x.g.P("return func(i, j int) bool {") for i, field := range index.SortedColFields { fieldName, _ := x.parseKeyFieldNameAndSuffix(field) @@ -269,7 +273,7 @@ func (x *Generator) genIndexFinders() { params := keys.GenGetParams() args := keys.GenGetArguments() x.g.P("// Find", index.Name(), " finds a slice of all values of the given key(s).") - x.g.P("func (x *", messagerName, ") Find", index.Name(), "(", params, ") []*", x.mapValueType(index), " {") + x.g.P("func (x *", messagerName, ") Find", index.Name(), "(", params, ") []", x.indexValueElem(index), " {") if len(index.ColFields) == 1 { x.g.P("return x.", indexContainerName, "[", args, "]") } else { @@ -279,13 +283,17 @@ func (x *Generator) genIndexFinders() { x.g.P() x.g.P("// FindFirst", index.Name(), " finds the first value of the given key(s),") - x.g.P("// or nil if no value found.") - x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), "(", params, ") *", x.mapValueType(index), " {") + x.g.P("// ", x.findFirstMissComment()) + x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), "(", params, ") ", x.indexValueElem(index), " {") x.g.P("val := x.Find", index.Name(), "(", args, ")") x.g.P("if len(val) > 0 {") x.g.P("return val[0]") x.g.P("}") - x.g.P("return nil") + if x.constEnabled { + x.g.P("return ", x.indexValueElem(index), "{}") + } else { + x.g.P("return nil") + } x.g.P("}") x.g.P() @@ -310,7 +318,7 @@ func (x *Generator) genIndexFinders() { x.g.P("// Find", index.Name(), i, " finds a slice of all values of the given key(s) in the upper ", loadutil.Ordinal(i), "-level map") x.g.P("// specified by (", partArgs, ").") - x.g.P("func (x *", messagerName, ") Find", index.Name(), i, "(", partParams, ", ", params, ") []*", x.mapValueType(index), " {") + x.g.P("func (x *", messagerName, ") Find", index.Name(), i, "(", partParams, ", ", params, ") []", x.indexValueElem(index), " {") if len(index.ColFields) == 1 { x.g.P("return x.Find", index.Name(), "Map", i, "(", partArgs, ")[", args, "]") } else { @@ -320,13 +328,17 @@ func (x *Generator) genIndexFinders() { x.g.P() x.g.P("// FindFirst", index.Name(), i, " finds the first value of the given key(s) in the upper ", loadutil.Ordinal(i), "-level map") - x.g.P("// specified by (", partArgs, "), or nil if no value found.") - x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), i, "(", partParams, ", ", params, ") *", x.mapValueType(index), " {") + x.g.P("// specified by (", partArgs, "), ", x.findFirstMissComment()) + x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), i, "(", partParams, ", ", params, ") ", x.indexValueElem(index), " {") x.g.P("val := x.Find", index.Name(), i, "(", partArgs, ", ", args, ")") x.g.P("if len(val) > 0 {") x.g.P("return val[0]") x.g.P("}") - x.g.P("return nil") + if x.constEnabled { + x.g.P("return ", x.indexValueElem(index), "{}") + } else { + x.g.P("return nil") + } x.g.P("}") x.g.P() } diff --git a/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go b/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go index b022632a..b35fe220 100644 --- a/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go +++ b/cmd/protoc-gen-go-tableau-loader/indexes/ordered_index.go @@ -94,7 +94,7 @@ func (x *Generator) genOrderedIndexTypeDef() { x.g.P("}") x.g.P() } - x.g.P("type ", x.orderedIndexMapType(index), " = ", helper.TreeMapPackage.Ident("TreeMap"), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]") + x.g.P("type ", x.orderedIndexMapType(index), " = ", helper.TreeMapPackage.Ident("TreeMap"), "[", x.orderedIndexMapKeyType(index), ", []", x.indexValueElem(index), "]") x.g.P() } } @@ -127,7 +127,7 @@ func (x *Generator) genOrderedIndexLoader() { x.g.P("// OrderedIndex init.") for lm := x.descriptor.LevelMessage; lm != nil; lm = lm.NextLevel { for _, index := range lm.OrderedIndexes { - x.g.P("x.", x.orderedIndexContainerName(index, 0), " = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]()") + x.g.P("x.", x.orderedIndexContainerName(index, 0), " = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []", x.indexValueElem(index), "]()") for i := 1; i < lm.LeveledContainerDepth(); i++ { if i == 1 { x.g.P("x.", x.orderedIndexContainerName(index, i), " = make(map[", x.keys[0].Type, "]*", x.orderedIndexMapType(index), ")") @@ -211,18 +211,22 @@ func (x *Generator) generateOneMulticolumnOrderedIndex(lm *index.LevelMessage, i } func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *index.LevelIndex, parentDataName string) { + appender := parentDataName + if x.constEnabled { + appender = parentDataName + ".AsConst()" + } indexContainerName := x.orderedIndexContainerName(index, 0) x.g.P("value, _ := x.", indexContainerName, ".Get(key)") - x.g.P("x.", indexContainerName, ".Put(key, append(value, ", parentDataName, "))") + x.g.P("x.", indexContainerName, ".Put(key, append(value, ", appender, "))") for i := 1; i < lm.LeveledContainerDepth(); i++ { orderedIndexContainerName := x.orderedIndexContainerName(index, i) valueName := orderedIndexContainerName + "Value" if i == 1 { x.g.P("if x.", orderedIndexContainerName, "[k1] == nil {") - x.g.P("x.", orderedIndexContainerName, "[k1] = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]()") + x.g.P("x.", orderedIndexContainerName, "[k1] = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []", x.indexValueElem(index), "]()") x.g.P("}") x.g.P(valueName, ", _ := x.", orderedIndexContainerName, "[k1].Get(key)") - x.g.P("x.", orderedIndexContainerName, "[k1].Put(key, append(", valueName, ", ", parentDataName, "))") + x.g.P("x.", orderedIndexContainerName, "[k1].Put(key, append(", valueName, ", ", appender, "))") } else { var fields []string for j := 1; j <= i; j++ { @@ -232,10 +236,10 @@ func (x *Generator) genOrderedIndexLoaderCommon(lm *index.LevelMessage, index *i keyName := orderedIndexContainerName + "Keys" x.g.P(keyName, " := ", levelIndexKeyType, "{", strings.Join(fields, ", "), "}") x.g.P("if x.", orderedIndexContainerName, "[", keyName, "] == nil {") - x.g.P("x.", orderedIndexContainerName, "[", keyName, "] = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []*", x.mapValueType(index), "]()") + x.g.P("x.", orderedIndexContainerName, "[", keyName, "] = ", helper.TreeMapPackage.Ident(x.mapCtor(index)), "[", x.orderedIndexMapKeyType(index), ", []", x.indexValueElem(index), "]()") x.g.P("}") x.g.P(valueName, ", _ := x.", orderedIndexContainerName, "[", keyName, "].Get(key)") - x.g.P("x.", orderedIndexContainerName, "[", keyName, "].Put(key, append(", valueName, ", ", parentDataName, "))") + x.g.P("x.", orderedIndexContainerName, "[", keyName, "].Put(key, append(", valueName, ", ", appender, "))") } } } @@ -246,7 +250,7 @@ func (x *Generator) genOrderedIndexSorter() { if len(index.SortedColFields) != 0 { x.g.P("// OrderedIndex(sort): ", index.Index) indexContainerName := x.orderedIndexContainerName(index, 0) - x.g.P(indexContainerName, "Sorter := func(itemList []*", x.mapValueType(index), ") func(i, j int) bool {") + x.g.P(indexContainerName, "Sorter := func(itemList []", x.indexValueElem(index), ") func(i, j int) bool {") x.g.P("return func(i, j int) bool {") for i, field := range index.SortedColFields { fieldName, _ := x.parseKeyFieldNameAndSuffix(field) @@ -260,14 +264,14 @@ func (x *Generator) genOrderedIndexSorter() { } x.g.P("}") x.g.P("}") - x.g.P("x.", x.orderedIndexContainerName(index, 0), ".Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []*", x.mapValueType(index), ") bool {") + x.g.P("x.", x.orderedIndexContainerName(index, 0), ".Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []", x.indexValueElem(index), ") bool {") x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))") x.g.P("return true") x.g.P("})") // Iterate all leveled containers. for i := 1; i < lm.LeveledContainerDepth(); i++ { x.g.P("for _, itemMap := range x.", x.orderedIndexContainerName(index, i), " {") - x.g.P("itemMap.Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []*", x.mapValueType(index), ") bool {") + x.g.P("itemMap.Range(func(key ", x.orderedIndexMapKeyType(index), ", itemList []", x.indexValueElem(index), ") bool {") x.g.P(helper.SortPackage.Ident("Slice"), "(itemList, ", indexContainerName, "Sorter(itemList))") x.g.P("return true") x.g.P("})") @@ -300,7 +304,7 @@ func (x *Generator) genOrderedIndexFinders() { params := keys.GenGetParams() args := keys.GenGetArguments() x.g.P("// Find", index.Name(), " finds a slice of all values of the given key(s).") - x.g.P("func (x *", messagerName, ") Find", index.Name(), "(", params, ") []*", x.mapValueType(index), " {") + x.g.P("func (x *", messagerName, ") Find", index.Name(), "(", params, ") []", x.indexValueElem(index), " {") if len(index.ColFields) == 1 { x.g.P("val, _ := x.", indexContainerName, ".Get(", args, ")") } else { @@ -311,13 +315,17 @@ func (x *Generator) genOrderedIndexFinders() { x.g.P() x.g.P("// FindFirst", index.Name(), " finds the first value of the given key(s),") - x.g.P("// or nil if no value found.") - x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), "(", params, ") *", x.mapValueType(index), " {") + x.g.P("// ", x.findFirstMissComment()) + x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), "(", params, ") ", x.indexValueElem(index), " {") x.g.P("val := x.Find", index.Name(), "(", args, ")") x.g.P("if len(val) > 0 {") x.g.P("return val[0]") x.g.P("}") - x.g.P("return nil") + if x.constEnabled { + x.g.P("return ", x.indexValueElem(index), "{}") + } else { + x.g.P("return nil") + } x.g.P("}") x.g.P() @@ -342,7 +350,7 @@ func (x *Generator) genOrderedIndexFinders() { x.g.P("// Find", index.Name(), i, " finds a slice of all values of the given key(s) in the upper ", loadutil.Ordinal(i), "-level treemap") x.g.P("// specified by (", partArgs, ").") - x.g.P("func (x *", messagerName, ") Find", index.Name(), i, "(", partParams, ", ", params, ") []*", x.mapValueType(index), " {") + x.g.P("func (x *", messagerName, ") Find", index.Name(), i, "(", partParams, ", ", params, ") []", x.indexValueElem(index), " {") x.g.P("m := x.Find", index.Name(), "Map", i, "(", partArgs, ")") x.g.P("if m == nil {") x.g.P("return nil") @@ -357,13 +365,17 @@ func (x *Generator) genOrderedIndexFinders() { x.g.P() x.g.P("// FindFirst", index.Name(), i, " finds the first value of the given key(s) in the upper ", loadutil.Ordinal(i), "-level treemap") - x.g.P("// specified by (", partArgs, "), or nil if no value found.") - x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), i, "(", partParams, ", ", params, ") *", x.mapValueType(index), " {") + x.g.P("// specified by (", partArgs, "), ", x.findFirstMissComment()) + x.g.P("func (x *", messagerName, ") FindFirst", index.Name(), i, "(", partParams, ", ", params, ") ", x.indexValueElem(index), " {") x.g.P("val := x.Find", index.Name(), i, "(", partArgs, ", ", args, ")") x.g.P("if len(val) > 0 {") x.g.P("return val[0]") x.g.P("}") - x.g.P("return nil") + if x.constEnabled { + x.g.P("return ", x.indexValueElem(index), "{}") + } else { + x.g.P("return nil") + } x.g.P("}") x.g.P() } diff --git a/cmd/protoc-gen-go-tableau-loader/main.go b/cmd/protoc-gen-go-tableau-loader/main.go index 4d2dacba..efd9a46b 100644 --- a/cmd/protoc-gen-go-tableau-loader/main.go +++ b/cmd/protoc-gen-go-tableau-loader/main.go @@ -12,7 +12,10 @@ import ( const version = "0.12.0" -var pkg *string +var ( + pkg *string + constFlag *bool +) func main() { showVersion := flag.Bool("version", false, "print the version and exit") @@ -24,6 +27,7 @@ func main() { var flags flag.FlagSet pkg = flags.String("pkg", "tableau", "tableau package name") + constFlag = flags.Bool("const", false, "generate const API (read-only views via goconst) for syntax-level immutability on Data()/Get*/Find*") protogen.Options{ ParamFunc: flags.Set, diff --git a/cmd/protoc-gen-go-tableau-loader/messager.go b/cmd/protoc-gen-go-tableau-loader/messager.go index c30fa4f4..39f84822 100644 --- a/cmd/protoc-gen-go-tableau-loader/messager.go +++ b/cmd/protoc-gen-go-tableau-loader/messager.go @@ -56,8 +56,8 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protog messagerName := string(message.Desc.Name()) indexDescriptor := index.ParseIndexDescriptor(message.Desc) - orderedMapGenerator := orderedmap.NewGenerator(gen, g, message) - indexGenerator := indexes.NewGenerator(gen, g, indexDescriptor, message) + orderedMapGenerator := orderedmap.NewGenerator(gen, g, message, *constFlag) + indexGenerator := indexes.NewGenerator(gen, g, indexDescriptor, message, *constFlag) // type definitions orderedMapGenerator.GenOrderedMapTypeDef() @@ -86,14 +86,25 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protog g.P("}") g.P() - g.P("// Data returns the ", messagerName, "'s inner message data.") - g.P("func (x *", messagerName, ") Data() *", message.GoIdent, " {") - g.P("if x != nil {") - g.P("return x.data") - g.P("}") - g.P(`return nil`) - g.P("}") - g.P() + if *constFlag { + g.P("// Data returns the ", messagerName, "'s inner message data as a read-only view.") + g.P("func (x *", messagerName, ") Data() ", message.GoIdent, "_Const {") + g.P("if x != nil {") + g.P("return x.data.AsConst()") + g.P("}") + g.P("return ", message.GoIdent, "_Const{}") + g.P("}") + g.P() + } else { + g.P("// Data returns the ", messagerName, "'s inner message data.") + g.P("func (x *", messagerName, ") Data() *", message.GoIdent, " {") + g.P("if x != nil {") + g.P("return x.data") + g.P("}") + g.P("return nil") + g.P("}") + g.P() + } g.P("// Load loads ", messagerName, "'s content in the given dir, based on format and messager options.") g.P("func (x *", messagerName, ") Load(dir string, format ", helper.FormatPackage.Ident("Format"), " , opts *", helper.LoadPackage.Ident("MessagerOptions"), ") error {") @@ -116,13 +127,16 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protog g.P("// Store stores ", messagerName, "'s content to file in the specified directory and format.") g.P("// Available formats: JSON, Bin, and Text.") g.P("func (x *", messagerName, ") Store(dir string, format ", helper.FormatPackage.Ident("Format"), " , options ...", helper.StorePackage.Ident("Option"), ") error {") - g.P("return ", helper.StorePackage.Ident("Store"), "(x.Data(), dir, format, options...)") + g.P("return ", helper.StorePackage.Ident("Store"), "(x.data, dir, format, options...)") g.P("}") g.P() g.P("// Message returns the ", messagerName, "'s inner message data.") g.P("func (x *", messagerName, ") Message() ", helper.ProtoPackage.Ident("Message"), " {") - g.P(`return x.Data()`) + g.P("if x != nil {") + g.P("return x.data") + g.P("}") + g.P("return nil") g.P("}") g.P() @@ -168,9 +182,14 @@ func genMapGetters(gen *protogen.Plugin, g *protogen.GeneratedFile, message *pro getter := fmt.Sprintf("Get%v", depth) g.P("// ", getter, " finds value in the ", loadutil.Ordinal(depth), "-level map. It will return") g.P("// NotFound error if the key is not found.") - g.P("func (x *", messagerName, ") ", getter, "(", keys.GenGetParams(), ") (", helper.ParseMapValueType(gen, g, fd), ", error) {") - + returnType := helper.ParseMapValueType(gen, g, fd) returnEmptyValue := helper.GetTypeEmptyValue(fd.MapValue()) + if *constFlag && fd.MapValue().Kind() == protoreflect.MessageKind { + ctype := helper.ConstViewType(g, gen, fd.MapValue().Message()) + returnType = ctype + returnEmptyValue = ctype + "{}" + } + g.P("func (x *", messagerName, ") ", getter, "(", keys.GenGetParams(), ") (", returnType, ", error) {") var container string if depth == 1 { @@ -187,7 +206,11 @@ func genMapGetters(gen *protogen.Plugin, g *protogen.GeneratedFile, message *pro g.P("d := ", container, ".Get", field.GoName, "()") lastKeyName := keys[len(keys)-1].Name - g.P("if val, ok := d[", lastKeyName, "]; !ok {") + if *constFlag { + g.P("if val, ok := d.Get(", lastKeyName, "); !ok {") + } else { + g.P("if val, ok := d[", lastKeyName, "]; !ok {") + } g.P(`return `, returnEmptyValue, `, `, helper.FmtPackage.Ident("Errorf"), `("`, lastKeyName, `(%v) %w", `, lastKeyName, `, ErrNotFound)`) g.P("} else {") g.P(`return val, nil`) diff --git a/cmd/protoc-gen-go-tableau-loader/orderedmap/ordered_map.go b/cmd/protoc-gen-go-tableau-loader/orderedmap/ordered_map.go index 32c81333..838bb683 100644 --- a/cmd/protoc-gen-go-tableau-loader/orderedmap/ordered_map.go +++ b/cmd/protoc-gen-go-tableau-loader/orderedmap/ordered_map.go @@ -11,16 +11,18 @@ import ( ) type Generator struct { - gen *protogen.Plugin - g *protogen.GeneratedFile - message *protogen.Message + gen *protogen.Plugin + g *protogen.GeneratedFile + message *protogen.Message + constEnabled bool } -func NewGenerator(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protogen.Message) *Generator { +func NewGenerator(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protogen.Message, constEnabled bool) *Generator { return &Generator{ - gen: gen, - g: g, - message: message, + gen: gen, + g: g, + message: message, + constEnabled: constEnabled, } } @@ -45,6 +47,9 @@ func (x *Generator) mapValueFieldType(fd protoreflect.FieldDescriptor) string { if nextMapFD != nil { return "*" + x.mapValueType(fd) } + if x.constEnabled && fd.MapValue().Kind() == protoreflect.MessageKind { + return helper.ConstViewType(x.g, x.gen, fd.MapValue().Message()) + } return helper.ParseMapValueType(x.gen, x.g, fd) } @@ -77,9 +82,12 @@ func (x *Generator) genOrderedMapTypeDef(md protoreflect.MessageDescriptor, dept orderedMapValue := x.mapValueType(fd) nextMapFD := getNextLevelMapFD(fd.MapValue()) if nextMapFD != nil { - currValueType := helper.FindMessageGoIdent(x.gen, fd.MapValue().Message()) nextOrderedMap := x.mapType(nextMapFD) - x.g.P("type ", orderedMapValue, "= ", helper.PairPackage.Ident("Pair"), "[*", nextOrderedMap, ", *", currValueType, "];") + secondType := "*" + x.g.QualifiedGoIdent(helper.FindMessageGoIdent(x.gen, fd.MapValue().Message())) + if x.constEnabled && fd.MapValue().Kind() == protoreflect.MessageKind { + secondType = helper.ConstViewType(x.g, x.gen, fd.MapValue().Message()) + } + x.g.P("type ", orderedMapValue, "= ", helper.PairPackage.Ident("Pair"), "[*", nextOrderedMap, ", ", secondType, "];") } x.g.P("type ", orderedMap, "= ", helper.TreeMapPackage.Ident("TreeMap"), "[", keyType, ", ", x.mapValueFieldType(fd), "]") x.g.P() @@ -128,7 +136,7 @@ func (x *Generator) genOrderedMapLoader(md protoreflect.MessageDescriptor, depth keyType = "int" } orderedMapValue := x.mapValueType(fd) - mapName := fmt.Sprintf("x.Data().Get%s()", field.GoName) + mapName := fmt.Sprintf("x.data.Get%s()", field.GoName) nextMapFD := getNextLevelMapFD(fd.MapValue()) if depth == 1 { x.g.P("x.orderedMap = ", helper.TreeMapPackage.Ident("New"), "[", keyType, ", ", x.mapValueFieldType(fd), "]()") @@ -141,7 +149,13 @@ func (x *Generator) genOrderedMapLoader(md protoreflect.MessageDescriptor, depth } x.g.P("k", depth-1, "v := &", lastOrderedMapValue, "{") x.g.P("First: ", helper.TreeMapPackage.Ident("New"), "[", keyType, ", ", x.mapValueFieldType(fd), "](),") - x.g.P("Second: v", depth-1, ",") + if x.constEnabled { + // v{depth-1} is always the message at the previous level, + // and ordered maps only nest through message maps. + x.g.P("Second: v", depth-1, ".AsConst(),") + } else { + x.g.P("Second: v", depth-1, ",") + } x.g.P("}") x.g.P("map", depth-1, ".Put(", keyName, ", k", depth-1, "v)") } @@ -158,7 +172,11 @@ func (x *Generator) genOrderedMapLoader(md protoreflect.MessageDescriptor, depth if needConvertBoolNext { keyName = fmt.Sprintf("boolToInt(%s)", keyName) } - x.g.P("map", depth, ".Put(", keyName, ", v", depth, ")") + if x.constEnabled && fd.MapValue().Kind() == protoreflect.MessageKind { + x.g.P("map", depth, ".Put(", keyName, ", v", depth, ".AsConst())") + } else { + x.g.P("map", depth, ".Put(", keyName, ", v", depth, ")") + } } x.g.P("}") break diff --git a/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go b/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go index 4798904a..15484c4f 100644 --- a/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/hero_conf.pc.go @@ -70,12 +70,15 @@ func (x *HeroConf) Load(dir string, format format.Format, opts *load.MessagerOpt // Store stores HeroConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *HeroConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the HeroConf's inner message data. func (x *HeroConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -236,12 +239,15 @@ func (x *HeroBaseConf) Load(dir string, format format.Format, opts *load.Message // Store stores HeroBaseConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *HeroBaseConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the HeroBaseConf's inner message data. func (x *HeroBaseConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -261,7 +267,7 @@ func (x *HeroBaseConf) originalMessage() proto.Message { func (x *HeroBaseConf) processAfterLoad() error { // OrderedMap init. x.orderedMap = treemap.New[string, *HeroBaseConf_OrderedMap_base_HeroValue]() - for k1, v1 := range x.Data().GetHeroMap() { + for k1, v1 := range x.data.GetHeroMap() { map1 := x.orderedMap k1v := &HeroBaseConf_OrderedMap_base_HeroValue{ First: treemap.New[string, *base.Item](), diff --git a/test/go-tableau-loader/protoconf/loader/index_conf.pc.go b/test/go-tableau-loader/protoconf/loader/index_conf.pc.go index 5498f1e5..94027192 100644 --- a/test/go-tableau-loader/protoconf/loader/index_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/index_conf.pc.go @@ -75,12 +75,15 @@ func (x *FruitConf) Load(dir string, format format.Format, opts *load.MessagerOp // Store stores FruitConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *FruitConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the FruitConf's inner message data. func (x *FruitConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -346,12 +349,15 @@ func (x *Fruit6Conf) Load(dir string, format format.Format, opts *load.MessagerO // Store stores Fruit6Conf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *Fruit6Conf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the Fruit6Conf's inner message data. func (x *Fruit6Conf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -614,12 +620,15 @@ func (x *Fruit2Conf) Load(dir string, format format.Format, opts *load.MessagerO // Store stores Fruit2Conf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *Fruit2Conf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the Fruit2Conf's inner message data. func (x *Fruit2Conf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -951,12 +960,15 @@ func (x *Fruit3Conf) Load(dir string, format format.Format, opts *load.MessagerO // Store stores Fruit3Conf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *Fruit3Conf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the Fruit3Conf's inner message data. func (x *Fruit3Conf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -1196,12 +1208,15 @@ func (x *Fruit4Conf) Load(dir string, format format.Format, opts *load.MessagerO // Store stores Fruit4Conf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *Fruit4Conf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the Fruit4Conf's inner message data. func (x *Fruit4Conf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -1624,12 +1639,15 @@ func (x *Fruit5Conf) Load(dir string, format format.Format, opts *load.MessagerO // Store stores Fruit5Conf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *Fruit5Conf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the Fruit5Conf's inner message data. func (x *Fruit5Conf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. diff --git a/test/go-tableau-loader/protoconf/loader/item_conf.pc.go b/test/go-tableau-loader/protoconf/loader/item_conf.pc.go index b7405cab..1647d576 100644 --- a/test/go-tableau-loader/protoconf/loader/item_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/item_conf.pc.go @@ -139,12 +139,15 @@ func (x *ItemConf) Load(dir string, format format.Format, opts *load.MessagerOpt // Store stores ItemConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *ItemConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the ItemConf's inner message data. func (x *ItemConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -164,7 +167,7 @@ func (x *ItemConf) originalMessage() proto.Message { func (x *ItemConf) processAfterLoad() error { // OrderedMap init. x.orderedMap = treemap.New[uint32, *protoconf.ItemConf_Item]() - for k1, v1 := range x.Data().GetItemMap() { + for k1, v1 := range x.data.GetItemMap() { map1 := x.orderedMap map1.Put(k1, v1) } diff --git a/test/go-tableau-loader/protoconf/loader/patch_conf.pc.go b/test/go-tableau-loader/protoconf/loader/patch_conf.pc.go index 2e18b1c8..0f09dc40 100644 --- a/test/go-tableau-loader/protoconf/loader/patch_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/patch_conf.pc.go @@ -61,12 +61,15 @@ func (x *PatchReplaceConf) Load(dir string, format format.Format, opts *load.Mes // Store stores PatchReplaceConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *PatchReplaceConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the PatchReplaceConf's inner message data. func (x *PatchReplaceConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -127,12 +130,15 @@ func (x *PatchMergeConf) Load(dir string, format format.Format, opts *load.Messa // Store stores PatchMergeConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *PatchMergeConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the PatchMergeConf's inner message data. func (x *PatchMergeConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -204,12 +210,15 @@ func (x *RecursivePatchConf) Load(dir string, format format.Format, opts *load.M // Store stores RecursivePatchConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *RecursivePatchConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the RecursivePatchConf's inner message data. func (x *RecursivePatchConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. diff --git a/test/go-tableau-loader/protoconf/loader/test_conf.pc.go b/test/go-tableau-loader/protoconf/loader/test_conf.pc.go index b76c852e..337c3a20 100644 --- a/test/go-tableau-loader/protoconf/loader/test_conf.pc.go +++ b/test/go-tableau-loader/protoconf/loader/test_conf.pc.go @@ -110,12 +110,15 @@ func (x *ActivityConf) Load(dir string, format format.Format, opts *load.Message // Store stores ActivityConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *ActivityConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the ActivityConf's inner message data. func (x *ActivityConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -135,7 +138,7 @@ func (x *ActivityConf) originalMessage() proto.Message { func (x *ActivityConf) processAfterLoad() error { // OrderedMap init. x.orderedMap = treemap.New[uint64, *ActivityConf_OrderedMap_ActivityValue]() - for k1, v1 := range x.Data().GetActivityMap() { + for k1, v1 := range x.data.GetActivityMap() { map1 := x.orderedMap k1v := &ActivityConf_OrderedMap_ActivityValue{ First: treemap.New[uint32, *ActivityConf_OrderedMap_Activity_ChapterValue](), @@ -592,12 +595,15 @@ func (x *ChapterConf) Load(dir string, format format.Format, opts *load.Messager // Store stores ChapterConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *ChapterConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the ChapterConf's inner message data. func (x *ChapterConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -669,12 +675,15 @@ func (x *ThemeConf) Load(dir string, format format.Format, opts *load.MessagerOp // Store stores ThemeConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *ThemeConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the ThemeConf's inner message data. func (x *ThemeConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -795,12 +804,15 @@ func (x *TaskConf) Load(dir string, format format.Format, opts *load.MessagerOpt // Store stores TaskConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *TaskConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the TaskConf's inner message data. func (x *TaskConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager. @@ -1112,12 +1124,15 @@ func (x *StrcaseConf) Load(dir string, format format.Format, opts *load.Messager // Store stores StrcaseConf's content to file in the specified directory and format. // Available formats: JSON, Bin, and Text. func (x *StrcaseConf) Store(dir string, format format.Format, options ...store.Option) error { - return store.Store(x.Data(), dir, format, options...) + return store.Store(x.data, dir, format, options...) } // Message returns the StrcaseConf's inner message data. func (x *StrcaseConf) Message() proto.Message { - return x.Data() + if x != nil { + return x.data + } + return nil } // Messager returns the current messager.