cmd/protoc-gen-go: add support for map fields

Generate the proper map[key]value type for map fields.
Include the protobuf_key and protobuf_val field tags.
Do not generate the map entry structs.

Fix an initialization order bug in protogen: While proto files cannot
contain circular dependencies, a single file can contain dependency
cycles. First create types for all the descriptors in a file, then add
in references (currently just field->message and field->enum) in a
second pass.

Change-Id: Ifedfa657d8dbb00413ba493adee1119b19c1b773
Reviewed-on: https://go-review.googlesource.com/135355
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index 3a062b4..815f080 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -246,6 +246,10 @@
 }
 
 func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, message *protogen.Message) {
+	if message.Desc.IsMapEntry() {
+		return
+	}
+
 	for _, e := range message.Enums {
 		genEnum(gen, g, f, e)
 	}
@@ -263,7 +267,19 @@
 		if pointer {
 			goType = "*" + goType
 		}
-		g.P(field.GoIdent, " ", goType, fmt.Sprintf(" `protobuf:%q json:%q`", fieldProtobufTag(field), fieldJSONTag(field)))
+		tags := []string{
+			fmt.Sprintf("protobuf:%q", fieldProtobufTag(field)),
+			fmt.Sprintf("json:%q", fieldJSONTag(field)),
+		}
+		if field.Desc.IsMap() {
+			key := field.MessageType.Fields[0]
+			val := field.MessageType.Fields[1]
+			tags = append(tags,
+				fmt.Sprintf("protobuf_key:%q", fieldProtobufTag(key)),
+				fmt.Sprintf("protobuf_val:%q", fieldProtobufTag(val)),
+			)
+		}
+		g.P(field.GoIdent, " ", goType, " `", strings.Join(tags, " "), "`")
 	}
 	g.P("XXX_NoUnkeyedLiteral struct{} `json:\"-\"`")
 	// TODO XXX_InternalExtensions
@@ -408,7 +424,6 @@
 //
 // If it returns pointer=true, the struct field is a pointer to the type.
 func fieldGoType(g *protogen.GeneratedFile, field *protogen.Field) (goType string, pointer bool) {
-	// TODO: map types
 	pointer = true
 	switch field.Desc.Kind() {
 	case protoreflect.BoolKind:
@@ -433,6 +448,11 @@
 		goType = "[]byte"
 		pointer = false
 	case protoreflect.MessageKind, protoreflect.GroupKind:
+		if field.Desc.IsMap() {
+			keyType, _ := fieldGoType(g, field.MessageType.Fields[0])
+			valType, _ := fieldGoType(g, field.MessageType.Fields[1])
+			return fmt.Sprintf("map[%v]%v", keyType, valType), false
+		}
 		goType = "*" + g.QualifiedGoIdent(field.MessageType.GoIdent)
 		pointer = false
 	}