cmd/protoc-gen-go: generate def= protobuf field tag, default constants

Change-Id: Id5cc34f0c1a5eb72f19e648844a5480827bcbda3
Reviewed-on: https://go-review.googlesource.com/135256
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index 9213ea8..b90b43c 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -13,6 +13,7 @@
 	"encoding/hex"
 	"flag"
 	"fmt"
+	"math"
 	"strconv"
 	"strings"
 
@@ -321,6 +322,59 @@
 	})
 	g.P()
 
+	// Constants and vars holding the default values of fields.
+	for _, field := range message.Fields {
+		if !field.Desc.HasDefault() {
+			continue
+		}
+		defVarName := "Default_" + message.GoIdent.GoName + "_" + field.GoIdent.GoName
+		def := field.Desc.Default()
+		switch field.Desc.Kind() {
+		case protoreflect.StringKind:
+			g.P("const ", defVarName, " string = ", strconv.Quote(def.String()))
+		case protoreflect.BytesKind:
+			g.P("var ", defVarName, " []byte = []byte(", strconv.Quote(string(def.Bytes())), ")")
+		case protoreflect.EnumKind:
+			enum := field.EnumType
+			evalue := enum.Values[enum.Desc.Values().ByNumber(def.Enum()).Index()]
+			g.P("const ", defVarName, " ", field.EnumType.GoIdent, " = ", evalue.GoIdent)
+		case protoreflect.FloatKind, protoreflect.DoubleKind:
+			// Floating point numbers need extra handling for -Inf/Inf/NaN.
+			f := field.Desc.Default().Float()
+			goType := "float64"
+			if field.Desc.Kind() == protoreflect.FloatKind {
+				goType = "float32"
+			}
+			// funcCall returns a call to a function in the math package,
+			// possibly converting the result to float32.
+			funcCall := func(fn, param string) string {
+				s := g.QualifiedGoIdent(protogen.GoIdent{
+					GoImportPath: "math",
+					GoName:       fn,
+				}) + param
+				if goType != "float64" {
+					s = goType + "(" + s + ")"
+				}
+				return s
+			}
+			switch {
+			case math.IsInf(f, -1):
+				g.P("var ", defVarName, " ", goType, " = ", funcCall("Inf", "(-1)"))
+			case math.IsInf(f, 1):
+				g.P("var ", defVarName, " ", goType, " = ", funcCall("Inf", "(1)"))
+			case math.IsNaN(f):
+				g.P("var ", defVarName, " ", goType, " = ", funcCall("NaN", "()"))
+			default:
+				g.P("const ", defVarName, " ", goType, " = ", f)
+			}
+		default:
+			goType := fieldGoType(g, field)
+			goType = strings.TrimPrefix(goType, "*")
+			g.P("const ", defVarName, " ", goType, " = ", def.Interface())
+		}
+	}
+	g.P()
+
 	// TODO: getters
 
 	for _, nested := range message.Messages {
@@ -390,7 +444,6 @@
 	case protoreflect.Repeated:
 		tag = append(tag, "rep")
 	}
-	// TODO: default values
 	// TODO: packed
 	// name
 	name := string(field.Desc.Name())
@@ -417,6 +470,36 @@
 	if field.Desc.OneofType() != nil {
 		tag = append(tag, "oneof")
 	}
+	// default value
+	// This must appear last in the tag, since commas in strings aren't escaped.
+	if field.Desc.HasDefault() {
+		var def string
+		switch field.Desc.Kind() {
+		case protoreflect.BoolKind:
+			if field.Desc.Default().Bool() {
+				def = "1"
+			} else {
+				def = "0"
+			}
+		case protoreflect.BytesKind:
+			def = string(field.Desc.Default().Bytes())
+		case protoreflect.FloatKind, protoreflect.DoubleKind:
+			f := field.Desc.Default().Float()
+			switch {
+			case math.IsInf(f, -1):
+				def = "-inf"
+			case math.IsInf(f, 1):
+				def = "inf"
+			case math.IsNaN(f):
+				def = "nan"
+			default:
+				def = fmt.Sprint(f)
+			}
+		default:
+			def = fmt.Sprint(field.Desc.Default().Interface())
+		}
+		tag = append(tag, "def="+def)
+	}
 	return strings.Join(tag, ",")
 }