cmd/protoc-gen-go: generate getters

Refactor fieldGoType to return the non-pointer type for scalar types
(i.e., "int", not "*int"), and a bool indicating whether the struct
field is a pointer to that type.

Change-Id: Ic80220e92f0b190e41ead847440d57af5c6cc919
Reviewed-on: https://go-review.googlesource.com/135336
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index b90b43c..3a062b4 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -259,7 +259,11 @@
 			continue
 		}
 		genComment(g, f, field.Path)
-		g.P(field.GoIdent, " ", fieldGoType(g, field), fmt.Sprintf(" `protobuf:%q json:%q`", fieldProtobufTag(field), fieldJSONTag(field)))
+		goType, pointer := fieldGoType(g, field)
+		if pointer {
+			goType = "*" + goType
+		}
+		g.P(field.GoIdent, " ", goType, fmt.Sprintf(" `protobuf:%q json:%q`", fieldProtobufTag(field), fieldJSONTag(field)))
 	}
 	g.P("XXX_NoUnkeyedLiteral struct{} `json:\"-\"`")
 	// TODO XXX_InternalExtensions
@@ -368,65 +372,78 @@
 				g.P("const ", defVarName, " ", goType, " = ", f)
 			}
 		default:
-			goType := fieldGoType(g, field)
-			goType = strings.TrimPrefix(goType, "*")
+			goType, _ := fieldGoType(g, field)
 			g.P("const ", defVarName, " ", goType, " = ", def.Interface())
 		}
 	}
 	g.P()
 
-	// TODO: getters
+	// Getters.
+	for _, field := range message.Fields {
+		goType, pointer := fieldGoType(g, field)
+		defaultValue := fieldDefaultValue(g, message, field)
+		g.P("func (m *", message.GoIdent, ") Get", field.GoIdent, "() ", goType, " {")
+		if field.Desc.Syntax() == protoreflect.Proto3 || defaultValue == "nil" {
+			g.P("if m != nil {")
+		} else {
+			g.P("if m != nil && m.", field.GoIdent, " != nil {")
+		}
+		star := ""
+		if pointer {
+			star = "*"
+		}
+		g.P("return ", star, " m.", field.GoIdent)
+		g.P("}")
+		g.P("return ", defaultValue)
+		g.P("}")
+		g.P()
+	}
 
 	for _, nested := range message.Messages {
 		genMessage(gen, g, f, nested)
 	}
 }
 
-func fieldGoType(g *protogen.GeneratedFile, field *protogen.Field) string {
+// fieldGoType returns the Go type used for a field.
+//
+// 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
-	var typ string
+	pointer = true
 	switch field.Desc.Kind() {
 	case protoreflect.BoolKind:
-		typ = "bool"
+		goType = "bool"
 	case protoreflect.EnumKind:
-		typ = g.QualifiedGoIdent(field.EnumType.GoIdent)
+		goType = g.QualifiedGoIdent(field.EnumType.GoIdent)
 	case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
-		typ = "int32"
+		goType = "int32"
 	case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
-		typ = "uint32"
+		goType = "uint32"
 	case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
-		typ = "int64"
+		goType = "int64"
 	case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
-		typ = "uint64"
+		goType = "uint64"
 	case protoreflect.FloatKind:
-		typ = "float32"
+		goType = "float32"
 	case protoreflect.DoubleKind:
-		typ = "float64"
+		goType = "float64"
 	case protoreflect.StringKind:
-		typ = "string"
+		goType = "string"
 	case protoreflect.BytesKind:
-		typ = "[]byte"
+		goType = "[]byte"
+		pointer = false
 	case protoreflect.MessageKind, protoreflect.GroupKind:
-		typ = "*" + g.QualifiedGoIdent(field.MessageType.GoIdent)
+		goType = "*" + g.QualifiedGoIdent(field.MessageType.GoIdent)
+		pointer = false
 	}
 	if field.Desc.Cardinality() == protoreflect.Repeated {
-		return "[]" + typ
+		goType = "[]" + goType
+		pointer = false
 	}
 	if field.Desc.Syntax() == protoreflect.Proto3 {
-		return typ
+		pointer = false
 	}
-	if field.Desc.OneofType() != nil {
-		return typ
-	}
-	nonPointerKinds := map[protoreflect.Kind]bool{
-		protoreflect.GroupKind:   true,
-		protoreflect.MessageKind: true,
-		protoreflect.BytesKind:   true,
-	}
-	if !nonPointerKinds[field.Desc.Kind()] {
-		return "*" + typ
-	}
-	return typ
+	return goType, pointer
 }
 
 func fieldProtobufTag(field *protogen.Field) string {
@@ -503,6 +520,31 @@
 	return strings.Join(tag, ",")
 }
 
+func fieldDefaultValue(g *protogen.GeneratedFile, message *protogen.Message, field *protogen.Field) string {
+	if field.Desc.Cardinality() == protoreflect.Repeated {
+		return "nil"
+	}
+	if field.Desc.HasDefault() {
+		defVarName := "Default_" + message.GoIdent.GoName + "_" + field.GoIdent.GoName
+		if field.Desc.Kind() == protoreflect.BytesKind {
+			return "append([]byte(nil), " + defVarName + "...)"
+		}
+		return defVarName
+	}
+	switch field.Desc.Kind() {
+	case protoreflect.BoolKind:
+		return "false"
+	case protoreflect.StringKind:
+		return `""`
+	case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.BytesKind:
+		return "nil"
+	case protoreflect.EnumKind:
+		return g.QualifiedGoIdent(field.EnumType.Values[0].GoIdent)
+	default:
+		return "0"
+	}
+}
+
 var wireTypes = map[protoreflect.Kind]string{
 	protoreflect.BoolKind:     "varint",
 	protoreflect.EnumKind:     "varint",