internal/cmd/generate-protos: generate internal descfield package

Generate a list of descriptor fields from the descriptor.proto itself
as an internal package. Use these fields for the internal implementation.

Change-Id: Ib1ab0c5c6deb332ba6c8018ef55136b7e5974944
Reviewed-on: https://go-review.googlesource.com/c/164864
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/internal/cmd/generate-protos/main.go b/internal/cmd/generate-protos/main.go
index 8698346..cf7c688 100644
--- a/internal/cmd/generate-protos/main.go
+++ b/internal/cmd/generate-protos/main.go
@@ -12,6 +12,7 @@
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
 	"regexp"
 	"strings"
@@ -19,6 +20,7 @@
 	gengogrpc "github.com/golang/protobuf/v2/cmd/protoc-gen-go-grpc/internal_gengogrpc"
 	gengo "github.com/golang/protobuf/v2/cmd/protoc-gen-go/internal_gengo"
 	"github.com/golang/protobuf/v2/protogen"
+	"github.com/golang/protobuf/v2/reflect/protoreflect"
 )
 
 func init() {
@@ -31,6 +33,7 @@
 				if file.Generate {
 					gengo.GenerateFile(gen, file)
 					gengogrpc.GenerateFile(gen, file)
+					generateDescriptorFields(gen, file)
 				}
 			}
 			return nil
@@ -43,6 +46,15 @@
 	run       bool
 	protoRoot string
 	repoRoot  string
+
+	generatedPreamble = []string{
+		"// Copyright 2019 The Go Authors. All rights reserved.",
+		"// Use of this source code is governed by a BSD-style.",
+		"// license that can be found in the LICENSE file.",
+		"",
+		"// Code generated by generate-protos. DO NOT EDIT.",
+		"",
+	}
 )
 
 func main() {
@@ -152,6 +164,44 @@
 	check(err)
 }
 
+// generateDescriptorFields generates an internal package for descriptor.proto.
+func generateDescriptorFields(gen *protogen.Plugin, file *protogen.File) {
+	if file.Desc.Path() != "google/protobuf/descriptor.proto" {
+		return
+	}
+
+	const importPath = "github.com/golang/protobuf/v2/internal/descfield"
+	g := gen.NewGeneratedFile(importPath+"/field_gen.go", importPath)
+	for _, s := range generatedPreamble {
+		g.P(s)
+	}
+	g.P("// Package descfield contains constants for field numbers in descriptor.proto.")
+	g.P("package ", path.Base(importPath))
+	g.P("")
+
+	var processMessages func([]*protogen.Message)
+	processMessages = func(messages []*protogen.Message) {
+		for _, message := range messages {
+			g.P("// Field numbers for ", message.Desc.FullName(), ".")
+			g.P("const (")
+			for _, field := range message.Fields {
+				fd := field.Desc
+				typeName := fd.Kind().String()
+				switch fd.Kind() {
+				case protoreflect.EnumKind:
+					typeName = string(fd.EnumType().FullName())
+				case protoreflect.MessageKind, protoreflect.GroupKind:
+					typeName = string(fd.MessageType().FullName())
+				}
+				g.P(message.GoIdent.GoName, "_", field.GoName, "=", fd.Number(), "// ", fd.Cardinality(), " ", typeName)
+			}
+			g.P(")")
+			processMessages(message.Messages)
+		}
+	}
+	processMessages(file.Messages)
+}
+
 func syncOutput(dstDir, srcDir string) {
 	filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
 		if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") {