cmd/protoc-gen-go: generate enums

This produces exactly the same output (to the best of my ability to
determine) as github.com/golang/protobuf.

Change-Id: Ib60e7a836efb1eb0e5167b30458049ec239e7903
Reviewed-on: https://go-review.googlesource.com/134695
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index d330941..d91e7a1 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -18,8 +18,11 @@
 	"github.com/golang/protobuf/proto"
 	descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
 	"google.golang.org/proto/protogen"
+	"google.golang.org/proto/reflect/protoreflect"
 )
 
+const protoPackage = "github.com/golang/protobuf/proto"
+
 func main() {
 	protogen.Run(func(gen *protogen.Plugin) error {
 		for _, f := range gen.Files {
@@ -34,7 +37,9 @@
 
 type File struct {
 	*protogen.File
-	locationMap map[string][]*descpb.SourceCodeInfo_Location
+	locationMap   map[string][]*descpb.SourceCodeInfo_Location
+	descriptorVar string // var containing the gzipped FileDescriptorProto
+	init          []string
 }
 
 func genFile(gen *protogen.Plugin, file *protogen.File) {
@@ -47,6 +52,12 @@
 		f.locationMap[key] = append(f.locationMap[key], loc)
 	}
 
+	// Determine the name of the var holding the file descriptor:
+	//
+	//     fileDescriptor_<hash of filename>
+	filenameHash := sha256.Sum256([]byte(f.Desc.Path()))
+	f.descriptorVar = fmt.Sprintf("fileDescriptor_%s", hex.EncodeToString(filenameHash[:8]))
+
 	g := gen.NewGeneratedFile(f.GeneratedFilenamePrefix+".pb.go", f.GoImportPath)
 	g.P("// Code generated by protoc-gen-go. DO NOT EDIT.")
 	g.P("// source: ", f.Desc.Path())
@@ -57,20 +68,26 @@
 	g.P("package ", f.GoPackageName)
 	g.P()
 
+	for _, enum := range f.Enums {
+		genEnum(gen, g, f, enum)
+	}
 	for _, message := range f.Messages {
 		genMessage(gen, g, f, message)
 	}
 
+	if len(f.init) != 0 {
+		g.P("func init() {")
+		for _, s := range f.init {
+			g.P(s)
+		}
+		g.P("}")
+		g.P()
+	}
+
 	genFileDescriptor(gen, g, f)
 }
 
 func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File) {
-	// Determine the name of the var holding the file descriptor:
-	//
-	//     fileDescriptor_<hash of filename>
-	filenameHash := sha256.Sum256([]byte(f.Desc.Path()))
-	varName := fmt.Sprintf("fileDescriptor_%s", hex.EncodeToString(filenameHash[:8]))
-
 	// Trim the source_code_info from the descriptor.
 	// Marshal and gzip it.
 	descProto := proto.Clone(f.Proto).(*descpb.FileDescriptorProto)
@@ -86,9 +103,9 @@
 	w.Close()
 	b = buf.Bytes()
 
-	g.P("func init() { proto.RegisterFile(", strconv.Quote(f.Desc.Path()), ", ", varName, ") }")
+	g.P("func init() { proto.RegisterFile(", strconv.Quote(f.Desc.Path()), ", ", f.descriptorVar, ") }")
 	g.P()
-	g.P("var ", varName, " = []byte{")
+	g.P("var ", f.descriptorVar, " = []byte{")
 	g.P("// ", len(b), " bytes of a gzipped FileDescriptorProto")
 	for len(b) > 0 {
 		n := 16
@@ -108,7 +125,92 @@
 	g.P()
 }
 
+func genEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, enum *protogen.Enum) {
+	genComment(g, f, enum.Path)
+	// TODO: deprecation
+	g.P("type ", enum.GoIdent, " int32")
+	g.P("const (")
+	for _, value := range enum.Values {
+		genComment(g, f, value.Path)
+		// TODO: deprecation
+		g.P(value.GoIdent, " ", enum.GoIdent, " = ", value.Desc.Number())
+	}
+	g.P(")")
+	g.P()
+	nameMap := enum.GoIdent.GoName + "_name"
+	g.P("var ", nameMap, " = map[int32]string{")
+	generated := make(map[protoreflect.EnumNumber]bool)
+	for _, value := range enum.Values {
+		duplicate := ""
+		if _, present := generated[value.Desc.Number()]; present {
+			duplicate = "// Duplicate value: "
+		}
+		g.P(duplicate, value.Desc.Number(), ": ", strconv.Quote(string(value.Desc.Name())), ",")
+		generated[value.Desc.Number()] = true
+	}
+	g.P("}")
+	g.P()
+	valueMap := enum.GoIdent.GoName + "_value"
+	g.P("var ", valueMap, " = map[string]int32{")
+	for _, value := range enum.Values {
+		g.P(strconv.Quote(string(value.Desc.Name())), ": ", value.Desc.Number(), ",")
+	}
+	g.P("}")
+	g.P()
+	if enum.Desc.Syntax() != protoreflect.Proto3 {
+		g.P("func (x ", enum.GoIdent, ") Enum() *", enum.GoIdent, " {")
+		g.P("p := new(", enum.GoIdent, ")")
+		g.P("*p = x")
+		g.P("return p")
+		g.P("}")
+		g.P()
+	}
+	g.P("func (x ", enum.GoIdent, ") String() string {")
+	g.P("return ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "EnumName"}, "(", enum.GoIdent, "_name, int32(x))")
+	g.P("}")
+	g.P()
+
+	if enum.Desc.Syntax() != protoreflect.Proto3 {
+		g.P("func (x *", enum.GoIdent, ") UnmarshalJSON(data []byte) error {")
+		g.P("value, err := ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "UnmarshalJSONEnum"}, "(", enum.GoIdent, `_value, data, "`, enum.GoIdent, `")`)
+		g.P("if err != nil {")
+		g.P("return err")
+		g.P("}")
+		g.P("*x = ", enum.GoIdent, "(value)")
+		g.P("return nil")
+		g.P("}")
+		g.P()
+	}
+
+	var indexes []string
+	for i := 1; i < len(enum.Path); i += 2 {
+		indexes = append(indexes, strconv.Itoa(int(enum.Path[i])))
+	}
+	g.P("func (", enum.GoIdent, ") EnumDescriptor() ([]byte, []int) {")
+	g.P("return ", f.descriptorVar, ", []int{", strings.Join(indexes, ","), "}")
+	g.P("}")
+	g.P()
+
+	genWellKnownType(g, enum.GoIdent, enum.Desc)
+
+	// The name registered is, confusingly, <proto_package>.<go_ident>.
+	// This probably should have been the full name of the proto enum
+	// type instead, but changing it at this point would require thought.
+	regName := string(f.Desc.Package()) + "." + enum.GoIdent.GoName
+	f.init = append(f.init, fmt.Sprintf("%s(%q, %s, %s)",
+		g.QualifiedGoIdent(protogen.GoIdent{
+			GoImportPath: protoPackage,
+			GoName:       "RegisterEnum",
+		}),
+		regName, nameMap, valueMap,
+	))
+}
+
 func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, message *protogen.Message) {
+	for _, enum := range message.Enums {
+		genEnum(gen, g, f, enum)
+	}
+
 	genComment(g, f, message.Path)
 	g.P("type ", message.GoIdent, " struct {")
 	g.P("}")
@@ -142,3 +244,15 @@
 	}
 	return string(buf)
 }
+
+func genWellKnownType(g *protogen.GeneratedFile, ident protogen.GoIdent, desc protoreflect.Descriptor) {
+	if wellKnownTypes[desc.FullName()] {
+		g.P("func (", ident, `) XXX_WellKnownType() string { return "`, desc.Name(), `" }`)
+		g.P()
+	}
+}
+
+// Names of messages and enums for which we will generate XXX_WellKnownType methods.
+var wellKnownTypes = map[protoreflect.FullName]bool{
+	"google.protobuf.NullValue": true,
+}
diff --git a/cmd/protoc-gen-go/testdata/proto2/enum.pb.go b/cmd/protoc-gen-go/testdata/proto2/enum.pb.go
new file mode 100644
index 0000000..85b2fad
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/proto2/enum.pb.go
@@ -0,0 +1,278 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: proto2/enum.proto
+
+package proto2
+
+import proto "github.com/golang/protobuf/proto"
+
+// EnumType1 comment.
+type EnumType1 int32
+
+const (
+	// EnumType1_ONE comment.
+	EnumType1_ONE EnumType1 = 1
+	// EnumType1_TWO comment.
+	EnumType1_TWO EnumType1 = 2
+)
+
+var EnumType1_name = map[int32]string{
+	1: "ONE",
+	2: "TWO",
+}
+
+var EnumType1_value = map[string]int32{
+	"ONE": 1,
+	"TWO": 2,
+}
+
+func (x EnumType1) Enum() *EnumType1 {
+	p := new(EnumType1)
+	*p = x
+	return p
+}
+
+func (x EnumType1) String() string {
+	return proto.EnumName(EnumType1_name, int32(x))
+}
+
+func (x *EnumType1) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(EnumType1_value, data, "EnumType1")
+	if err != nil {
+		return err
+	}
+	*x = EnumType1(value)
+	return nil
+}
+
+func (EnumType1) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_de9f68860d540858, []int{0}
+}
+
+type EnumType2 int32
+
+const (
+	EnumType2_duplicate1 EnumType2 = 1
+	EnumType2_duplicate2 EnumType2 = 1
+)
+
+var EnumType2_name = map[int32]string{
+	1: "duplicate1",
+	// Duplicate value: 1: "duplicate2",
+}
+
+var EnumType2_value = map[string]int32{
+	"duplicate1": 1,
+	"duplicate2": 1,
+}
+
+func (x EnumType2) Enum() *EnumType2 {
+	p := new(EnumType2)
+	*p = x
+	return p
+}
+
+func (x EnumType2) String() string {
+	return proto.EnumName(EnumType2_name, int32(x))
+}
+
+func (x *EnumType2) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(EnumType2_value, data, "EnumType2")
+	if err != nil {
+		return err
+	}
+	*x = EnumType2(value)
+	return nil
+}
+
+func (EnumType2) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_de9f68860d540858, []int{1}
+}
+
+// NestedEnumType1A comment.
+type EnumContainerMessage1_NestedEnumType1A int32
+
+const (
+	// NestedEnumType1A_VALUE comment.
+	EnumContainerMessage1_NESTED_1A_VALUE EnumContainerMessage1_NestedEnumType1A = 0
+)
+
+var EnumContainerMessage1_NestedEnumType1A_name = map[int32]string{
+	0: "NESTED_1A_VALUE",
+}
+
+var EnumContainerMessage1_NestedEnumType1A_value = map[string]int32{
+	"NESTED_1A_VALUE": 0,
+}
+
+func (x EnumContainerMessage1_NestedEnumType1A) Enum() *EnumContainerMessage1_NestedEnumType1A {
+	p := new(EnumContainerMessage1_NestedEnumType1A)
+	*p = x
+	return p
+}
+
+func (x EnumContainerMessage1_NestedEnumType1A) String() string {
+	return proto.EnumName(EnumContainerMessage1_NestedEnumType1A_name, int32(x))
+}
+
+func (x *EnumContainerMessage1_NestedEnumType1A) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(EnumContainerMessage1_NestedEnumType1A_value, data, "EnumContainerMessage1_NestedEnumType1A")
+	if err != nil {
+		return err
+	}
+	*x = EnumContainerMessage1_NestedEnumType1A(value)
+	return nil
+}
+
+func (EnumContainerMessage1_NestedEnumType1A) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_de9f68860d540858, []int{0, 0}
+}
+
+type EnumContainerMessage1_NestedEnumType1B int32
+
+const (
+	EnumContainerMessage1_NESTED_1B_VALUE EnumContainerMessage1_NestedEnumType1B = 0
+)
+
+var EnumContainerMessage1_NestedEnumType1B_name = map[int32]string{
+	0: "NESTED_1B_VALUE",
+}
+
+var EnumContainerMessage1_NestedEnumType1B_value = map[string]int32{
+	"NESTED_1B_VALUE": 0,
+}
+
+func (x EnumContainerMessage1_NestedEnumType1B) Enum() *EnumContainerMessage1_NestedEnumType1B {
+	p := new(EnumContainerMessage1_NestedEnumType1B)
+	*p = x
+	return p
+}
+
+func (x EnumContainerMessage1_NestedEnumType1B) String() string {
+	return proto.EnumName(EnumContainerMessage1_NestedEnumType1B_name, int32(x))
+}
+
+func (x *EnumContainerMessage1_NestedEnumType1B) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(EnumContainerMessage1_NestedEnumType1B_value, data, "EnumContainerMessage1_NestedEnumType1B")
+	if err != nil {
+		return err
+	}
+	*x = EnumContainerMessage1_NestedEnumType1B(value)
+	return nil
+}
+
+func (EnumContainerMessage1_NestedEnumType1B) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_de9f68860d540858, []int{0, 1}
+}
+
+type EnumContainerMessage1 struct {
+}
+
+// NestedEnumType2A comment.
+type EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A int32
+
+const (
+	// NestedEnumType2A_VALUE comment.
+	EnumContainerMessage1_EnumContainerMessage2_NESTED_2A_VALUE EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A = 0
+)
+
+var EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A_name = map[int32]string{
+	0: "NESTED_2A_VALUE",
+}
+
+var EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A_value = map[string]int32{
+	"NESTED_2A_VALUE": 0,
+}
+
+func (x EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A) Enum() *EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A {
+	p := new(EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A)
+	*p = x
+	return p
+}
+
+func (x EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A) String() string {
+	return proto.EnumName(EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A_name, int32(x))
+}
+
+func (x *EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A_value, data, "EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A")
+	if err != nil {
+		return err
+	}
+	*x = EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A(value)
+	return nil
+}
+
+func (EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_de9f68860d540858, []int{0, 0, 0}
+}
+
+type EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B int32
+
+const (
+	EnumContainerMessage1_EnumContainerMessage2_NESTED_2B_VALUE EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B = 0
+)
+
+var EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B_name = map[int32]string{
+	0: "NESTED_2B_VALUE",
+}
+
+var EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B_value = map[string]int32{
+	"NESTED_2B_VALUE": 0,
+}
+
+func (x EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B) Enum() *EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B {
+	p := new(EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B)
+	*p = x
+	return p
+}
+
+func (x EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B) String() string {
+	return proto.EnumName(EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B_name, int32(x))
+}
+
+func (x *EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B) UnmarshalJSON(data []byte) error {
+	value, err := proto.UnmarshalJSONEnum(EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B_value, data, "EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B")
+	if err != nil {
+		return err
+	}
+	*x = EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B(value)
+	return nil
+}
+
+func (EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_de9f68860d540858, []int{0, 0, 1}
+}
+
+type EnumContainerMessage1_EnumContainerMessage2 struct {
+}
+
+func init() {
+	proto.RegisterEnum("goproto.protoc.proto2.EnumType1", EnumType1_name, EnumType1_value)
+	proto.RegisterEnum("goproto.protoc.proto2.EnumType2", EnumType2_name, EnumType2_value)
+	proto.RegisterEnum("goproto.protoc.proto2.EnumContainerMessage1_NestedEnumType1A", EnumContainerMessage1_NestedEnumType1A_name, EnumContainerMessage1_NestedEnumType1A_value)
+	proto.RegisterEnum("goproto.protoc.proto2.EnumContainerMessage1_NestedEnumType1B", EnumContainerMessage1_NestedEnumType1B_name, EnumContainerMessage1_NestedEnumType1B_value)
+	proto.RegisterEnum("goproto.protoc.proto2.EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A", EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A_name, EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2A_value)
+	proto.RegisterEnum("goproto.protoc.proto2.EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B", EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B_name, EnumContainerMessage1_EnumContainerMessage2_NestedEnumType2B_value)
+}
+
+func init() { proto.RegisterFile("proto2/enum.proto", fileDescriptor_de9f68860d540858) }
+
+var fileDescriptor_de9f68860d540858 = []byte{
+	// 242 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xb1, 0x4b, 0xc4, 0x30,
+	0x14, 0xc6, 0xcd, 0x39, 0x88, 0x19, 0x34, 0x56, 0x6e, 0x39, 0x70, 0xb9, 0x45, 0x38, 0xb8, 0x86,
+	0x64, 0x13, 0xa7, 0x56, 0xb3, 0x69, 0x6f, 0xb0, 0x2a, 0xb8, 0x1c, 0xa1, 0x7d, 0x3c, 0x0a, 0x6d,
+	0x5e, 0x69, 0xd3, 0xc1, 0xff, 0xd3, 0x3f, 0x48, 0xae, 0x81, 0xb3, 0x42, 0x75, 0xca, 0xf7, 0xe5,
+	0xfb, 0xf1, 0x1b, 0x1e, 0xbf, 0x6a, 0x3b, 0xf2, 0xa4, 0x25, 0xb8, 0xa1, 0x89, 0xc7, 0x1c, 0x2d,
+	0x91, 0xc6, 0x10, 0x6a, 0x11, 0x1e, 0xbd, 0xfe, 0x62, 0x7c, 0x69, 0xdc, 0xd0, 0x3c, 0x90, 0xf3,
+	0xb6, 0x72, 0xd0, 0x3d, 0x43, 0xdf, 0x5b, 0x04, 0xb5, 0xaa, 0xe6, 0x07, 0xbd, 0xbe, 0xe5, 0x22,
+	0x83, 0xde, 0x43, 0x79, 0x98, 0xf3, 0xcf, 0x16, 0x74, 0x12, 0x5d, 0xf3, 0xcb, 0xcc, 0xbc, 0xe4,
+	0xe6, 0x71, 0xaf, 0x93, 0xfd, 0x5b, 0xf2, 0xf4, 0x6a, 0xc4, 0xc9, 0x0c, 0x98, 0x4e, 0xc1, 0xf4,
+	0x6f, 0x50, 0x4d, 0x8d, 0xea, 0x1f, 0xa3, 0x9a, 0x1a, 0xd5, 0xd1, 0xb8, 0xb9, 0xe1, 0xe7, 0x47,
+	0x24, 0x3a, 0xe3, 0xa7, 0xbb, 0xcc, 0x08, 0x76, 0x08, 0xf9, 0xfb, 0x4e, 0x2c, 0x36, 0xf2, 0x67,
+	0xd6, 0xd1, 0x05, 0xe7, 0xe5, 0xd0, 0xd6, 0x55, 0x61, 0x3d, 0x28, 0xc1, 0x7e, 0x75, 0x2d, 0xd8,
+	0x6a, 0x21, 0x58, 0x7a, 0xff, 0x71, 0x87, 0x44, 0x58, 0x43, 0x8c, 0x54, 0x5b, 0x87, 0x31, 0x75,
+	0x28, 0xc7, 0x13, 0xca, 0xa2, 0x29, 0x43, 0x2a, 0xb6, 0x08, 0x6e, 0x8b, 0x24, 0x3d, 0xf4, 0xbe,
+	0xb4, 0xde, 0x86, 0x6f, 0xfd, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xf6, 0x53, 0xf7, 0xde, 0x8e, 0x01,
+	0x00, 0x00,
+}
diff --git a/cmd/protoc-gen-go/testdata/proto2/enum.proto b/cmd/protoc-gen-go/testdata/proto2/enum.proto
new file mode 100644
index 0000000..0faac3e
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/proto2/enum.proto
@@ -0,0 +1,47 @@
+// Copyright 2018 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.
+
+syntax = "proto2";
+
+package goproto.protoc.proto2;
+
+option go_package = "google.golang.org/proto/cmd/protoc-gen-go/testdata/proto2";
+
+// EnumType1 comment.
+enum EnumType1 {
+  // EnumType1_ONE comment.
+  ONE = 1;
+  // EnumType1_TWO comment.
+  TWO = 2;
+}
+
+enum EnumType2 {
+  option allow_alias = true;
+  duplicate1 = 1;
+  duplicate2 = 1;
+}
+
+message EnumContainerMessage1 {
+  // NestedEnumType1A comment.
+  enum NestedEnumType1A {
+    // NestedEnumType1A_VALUE comment.
+    NESTED_1A_VALUE = 0;
+  }
+
+  enum NestedEnumType1B {
+    NESTED_1B_VALUE = 0;
+  }
+
+  message EnumContainerMessage2 {
+    // NestedEnumType2A comment.
+    enum NestedEnumType2A {
+      // NestedEnumType2A_VALUE comment.
+      NESTED_2A_VALUE = 0;
+    }
+
+    enum NestedEnumType2B {
+      NESTED_2B_VALUE = 0;
+    }
+  }
+}
diff --git a/cmd/protoc-gen-go/testdata/proto3/enum.pb.go b/cmd/protoc-gen-go/testdata/proto3/enum.pb.go
new file mode 100644
index 0000000..569298b
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/proto3/enum.pb.go
@@ -0,0 +1,53 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: proto3/enum.proto
+
+package proto3
+
+import proto "github.com/golang/protobuf/proto"
+
+type Enum int32
+
+const (
+	Enum_ZERO Enum = 0
+	Enum_ONE  Enum = 1
+	Enum_TWO  Enum = 2
+)
+
+var Enum_name = map[int32]string{
+	0: "ZERO",
+	1: "ONE",
+	2: "TWO",
+}
+
+var Enum_value = map[string]int32{
+	"ZERO": 0,
+	"ONE":  1,
+	"TWO":  2,
+}
+
+func (x Enum) String() string {
+	return proto.EnumName(Enum_name, int32(x))
+}
+
+func (Enum) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_b4b9b1e8d161a9a6, []int{0}
+}
+
+func init() {
+	proto.RegisterEnum("goproto.protoc.proto3.Enum", Enum_name, Enum_value)
+}
+
+func init() { proto.RegisterFile("proto3/enum.proto", fileDescriptor_b4b9b1e8d161a9a6) }
+
+var fileDescriptor_b4b9b1e8d161a9a6 = []byte{
+	// 138 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0x28, 0xca, 0x2f,
+	0xc9, 0x37, 0xd6, 0x4f, 0xcd, 0x2b, 0xcd, 0xd5, 0x03, 0xb3, 0x85, 0x44, 0xd3, 0xf3, 0xc1, 0x0c,
+	0x08, 0x37, 0x19, 0x42, 0x19, 0x6b, 0x29, 0x71, 0xb1, 0xb8, 0xe6, 0x95, 0xe6, 0x0a, 0x71, 0x70,
+	0xb1, 0x44, 0xb9, 0x06, 0xf9, 0x0b, 0x30, 0x08, 0xb1, 0x73, 0x31, 0xfb, 0xfb, 0xb9, 0x0a, 0x30,
+	0x82, 0x18, 0x21, 0xe1, 0xfe, 0x02, 0x4c, 0x4e, 0xd6, 0x51, 0x96, 0xe9, 0xf9, 0xf9, 0xe9, 0x39,
+	0xa9, 0x7a, 0xe9, 0xf9, 0x39, 0x89, 0x79, 0xe9, 0x7a, 0xf9, 0x45, 0xe9, 0xfa, 0x60, 0xfd, 0xfa,
+	0xc9, 0xb9, 0x29, 0x10, 0x56, 0xb2, 0x6e, 0x7a, 0x6a, 0x9e, 0x6e, 0x7a, 0xbe, 0x7e, 0x49, 0x6a,
+	0x71, 0x49, 0x4a, 0x62, 0x49, 0x22, 0x44, 0xd8, 0x38, 0x89, 0x0d, 0x42, 0x03, 0x02, 0x00, 0x00,
+	0xff, 0xff, 0x02, 0x01, 0x6a, 0x95, 0x93, 0x00, 0x00, 0x00,
+}
diff --git a/cmd/protoc-gen-go/testdata/proto3/enum.proto b/cmd/protoc-gen-go/testdata/proto3/enum.proto
new file mode 100644
index 0000000..60e7b8e
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/proto3/enum.proto
@@ -0,0 +1,15 @@
+// Copyright 2018 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.
+
+syntax = "proto3";
+
+package goproto.protoc.proto3;
+
+option go_package = "google.golang.org/proto/cmd/protoc-gen-go/testdata/proto3";
+
+enum Enum {
+  ZERO = 0;
+  ONE = 1;
+  TWO = 2;
+}
diff --git a/protogen/protogen.go b/protogen/protogen.go
index 967f000..ada17ba 100644
--- a/protogen/protogen.go
+++ b/protogen/protogen.go
@@ -305,6 +305,7 @@
 	GoPackageName GoPackageName // name of this file's Go package
 	GoImportPath  GoImportPath  // import path of this file's Go package
 	Messages      []*Message    // top-level message declarations
+	Enums         []*Enum       // top-level enum declarations
 	Generate      bool          // true if we should generate code for this file
 
 	// GeneratedFilenamePrefix is used to construct filenames for generated
@@ -351,6 +352,9 @@
 	for i, mdescs := 0, desc.Messages(); i < mdescs.Len(); i++ {
 		f.Messages = append(f.Messages, newMessage(gen, f, nil, mdescs.Get(i)))
 	}
+	for i, edescs := 0, desc.Enums(); i < edescs.Len(); i++ {
+		f.Enums = append(f.Enums, newEnum(gen, f, nil, edescs.Get(i)))
+	}
 	return f, nil
 }
 
@@ -380,6 +384,7 @@
 
 	GoIdent  GoIdent    // name of the generated Go type
 	Messages []*Message // nested message declarations
+	Enums    []*Enum    // nested enum declarations
 	Path     []int32    // location path of this message
 }
 
@@ -390,15 +395,73 @@
 	} else {
 		path = []int32{fileMessageField, int32(desc.Index())}
 	}
-	m := &Message{
+	message := &Message{
 		Desc:    desc,
 		GoIdent: newGoIdent(f, desc),
 		Path:    path,
 	}
 	for i, mdescs := 0, desc.Messages(); i < mdescs.Len(); i++ {
-		m.Messages = append(m.Messages, newMessage(gen, f, m, mdescs.Get(i)))
+		message.Messages = append(message.Messages, newMessage(gen, f, message, mdescs.Get(i)))
 	}
-	return m
+	for i, edescs := 0, desc.Enums(); i < edescs.Len(); i++ {
+		message.Enums = append(message.Enums, newEnum(gen, f, message, edescs.Get(i)))
+	}
+	return message
+}
+
+// An Enum describes an enum.
+type Enum struct {
+	Desc protoreflect.EnumDescriptor
+
+	GoIdent GoIdent      // name of the generated Go type
+	Values  []*EnumValue // enum values
+	Path    []int32      // location path of this enum
+}
+
+func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescriptor) *Enum {
+	var path []int32
+	if parent != nil {
+		path = pathAppend(parent.Path, messageEnumField, int32(desc.Index()))
+	} else {
+		path = []int32{fileEnumField, int32(desc.Index())}
+	}
+	enum := &Enum{
+		Desc:    desc,
+		GoIdent: newGoIdent(f, desc),
+		Path:    path,
+	}
+	for i, evdescs := 0, enum.Desc.Values(); i < evdescs.Len(); i++ {
+		enum.Values = append(enum.Values, newEnumValue(gen, f, parent, enum, evdescs.Get(i)))
+	}
+	return enum
+}
+
+// An EnumValue describes an enum value.
+type EnumValue struct {
+	Desc protoreflect.EnumValueDescriptor
+
+	GoIdent GoIdent // name of the generated Go type
+	Path    []int32 // location path of this enum value
+}
+
+func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc protoreflect.EnumValueDescriptor) *EnumValue {
+	// A top-level enum value's name is: EnumName_ValueName
+	// An enum value contained in a message is: MessageName_ValueName
+	//
+	// Enum value names are not camelcased.
+	parentIdent := enum.GoIdent
+	if message != nil {
+		parentIdent = message.GoIdent
+	}
+	name := parentIdent.GoName + "_" + string(desc.Name())
+	return &EnumValue{
+		Desc: desc,
+		GoIdent: GoIdent{
+			GoName:       name,
+			GoImportPath: f.GoImportPath,
+		},
+		Path: pathAppend(enum.Path, enumValueField, int32(desc.Index())),
+	}
 }
 
 // A GeneratedFile is a generated file.
@@ -432,11 +495,7 @@
 	for _, x := range v {
 		switch x := x.(type) {
 		case GoIdent:
-			if x.GoImportPath != g.goImportPath {
-				fmt.Fprint(&g.buf, g.goPackageName(x.GoImportPath))
-				fmt.Fprint(&g.buf, ".")
-			}
-			fmt.Fprint(&g.buf, x.GoName)
+			fmt.Fprint(&g.buf, g.QualifiedGoIdent(x))
 		default:
 			fmt.Fprint(&g.buf, x)
 		}
@@ -444,6 +503,27 @@
 	fmt.Fprintln(&g.buf)
 }
 
+// QualifiedGoIdent returns the string to use for a Go identifier.
+//
+// If the identifier is from a different Go package than the generated file,
+// the returned name will be qualified (package.name) and an import statement
+// for the identifier's package will be included in the file.
+func (g *GeneratedFile) QualifiedGoIdent(ident GoIdent) string {
+	if ident.GoImportPath == g.goImportPath {
+		return ident.GoName
+	}
+	if packageName, ok := g.packageNames[ident.GoImportPath]; ok {
+		return string(packageName) + "." + ident.GoName
+	}
+	packageName := cleanPackageName(baseName(string(ident.GoImportPath)))
+	for i, orig := 1, packageName; g.usedPackageNames[packageName]; i++ {
+		packageName = orig + GoPackageName(strconv.Itoa(i))
+	}
+	g.packageNames[ident.GoImportPath] = packageName
+	g.usedPackageNames[packageName] = true
+	return string(packageName) + "." + ident.GoName
+}
+
 func (g *GeneratedFile) goPackageName(importPath GoImportPath) GoPackageName {
 	if name, ok := g.packageNames[importPath]; ok {
 		return name
@@ -522,7 +602,7 @@
 	// field numbers in FileDescriptorProto
 	filePackageField = 2 // package
 	fileMessageField = 4 // message_type
-	fileenumField    = 5 // enum_type
+	fileEnumField    = 5 // enum_type
 	// field numbers in DescriptorProto
 	messageFieldField   = 2 // field
 	messageMessageField = 3 // nested_type