reflect/prototype: add registration hooks for options

Add pseudo-hidden functions to register the concrete Go type used for
the optional types. Also, augment protoc-gen-go to specially generate the
descriptor proto to register these types.

This change does not add validation logic yet to ensure that the correct option
types are passed to the API.

Change-Id: I5decc897e14b4bf570a61cf17b57a066a2a0f9d7
Reviewed-on: https://go-review.googlesource.com/c/153017
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/protoc-gen-go/internal_gengo/reflect.go b/cmd/protoc-gen-go/internal_gengo/reflect.go
index 6b6ea68..0b0e03c 100644
--- a/cmd/protoc-gen-go/internal_gengo/reflect.go
+++ b/cmd/protoc-gen-go/internal_gengo/reflect.go
@@ -131,6 +131,17 @@
 
 	// TODO: Add v2 registration and stop v1 registration in genInitFunction.
 
+	// The descriptor proto needs to register the option types with the
+	// prototype so that the package can properly handle those option types.
+	if isDescriptor(f.File) {
+		for _, m := range f.allMessages {
+			name := m.GoIdent.GoName
+			if strings.HasSuffix(name, "Options") {
+				g.P(prototypePackage.Ident("X"), ".Register", name, "((*", name, ")(nil))")
+			}
+		}
+	}
+
 	g.P("}")
 }
 
diff --git a/reflect/prototype/options.go b/reflect/prototype/options.go
new file mode 100644
index 0000000..7ad8c92
--- /dev/null
+++ b/reflect/prototype/options.go
@@ -0,0 +1,40 @@
+// 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.
+
+package prototype
+
+import pref "github.com/golang/protobuf/v2/reflect/protoreflect"
+
+// X provides functionality internal to the protobuf module.
+//
+// WARNING: The compatibility agreement covers nothing except for functionality
+// needed to keep existing generated messages operational. The Go authors
+// are not responsible for breakages that occur due to unauthorized usages.
+var X internal
+
+type internal struct{}
+
+// optionTypes contains typed nil-pointers to each of the options types.
+// These are populated at init time by the descriptor package.
+var optionTypes struct {
+	File           pref.ProtoMessage
+	Enum           pref.ProtoMessage
+	EnumValue      pref.ProtoMessage
+	Message        pref.ProtoMessage
+	Field          pref.ProtoMessage
+	Oneof          pref.ProtoMessage
+	ExtensionRange pref.ProtoMessage
+	Service        pref.ProtoMessage
+	Method         pref.ProtoMessage
+}
+
+func (internal) RegisterFileOptions(m pref.ProtoMessage)           { optionTypes.File = m }
+func (internal) RegisterEnumOptions(m pref.ProtoMessage)           { optionTypes.Enum = m }
+func (internal) RegisterEnumValueOptions(m pref.ProtoMessage)      { optionTypes.EnumValue = m }
+func (internal) RegisterMessageOptions(m pref.ProtoMessage)        { optionTypes.Message = m }
+func (internal) RegisterFieldOptions(m pref.ProtoMessage)          { optionTypes.Field = m }
+func (internal) RegisterOneofOptions(m pref.ProtoMessage)          { optionTypes.Oneof = m }
+func (internal) RegisterExtensionRangeOptions(m pref.ProtoMessage) { optionTypes.ExtensionRange = m }
+func (internal) RegisterServiceOptions(m pref.ProtoMessage)        { optionTypes.Service = m }
+func (internal) RegisterMethodOptions(m pref.ProtoMessage)         { optionTypes.Method = m }
diff --git a/reflect/prototype/placeholder_type.go b/reflect/prototype/placeholder_type.go
index 0838fc2..85fe30e 100644
--- a/reflect/prototype/placeholder_type.go
+++ b/reflect/prototype/placeholder_type.go
@@ -41,7 +41,7 @@
 	placeholderName
 }
 
-func (t placeholderFile) Options() pref.ProtoMessage                     { return nil }
+func (t placeholderFile) Options() pref.ProtoMessage                     { return optionTypes.File }
 func (t placeholderFile) Path() string                                   { return t.path }
 func (t placeholderFile) Package() pref.FullName                         { return t.FullName() }
 func (t placeholderFile) Imports() pref.FileImports                      { return &emptyFiles }
@@ -57,7 +57,7 @@
 	placeholderName
 }
 
-func (t placeholderMessage) Options() pref.ProtoMessage            { return nil }
+func (t placeholderMessage) Options() pref.ProtoMessage            { return optionTypes.Message }
 func (t placeholderMessage) IsMapEntry() bool                      { return false }
 func (t placeholderMessage) Fields() pref.FieldDescriptors         { return &emptyFields }
 func (t placeholderMessage) Oneofs() pref.OneofDescriptors         { return &emptyOneofs }
@@ -73,7 +73,7 @@
 	placeholderName
 }
 
-func (t placeholderEnum) Options() pref.ProtoMessage        { return nil }
+func (t placeholderEnum) Options() pref.ProtoMessage        { return optionTypes.Enum }
 func (t placeholderEnum) Values() pref.EnumValueDescriptors { return &emptyEnumValues }
 func (t placeholderEnum) Format(s fmt.State, r rune)        { pfmt.FormatDesc(s, r, t) }
 func (t placeholderEnum) ProtoType(pref.EnumDescriptor)     {}
diff --git a/reflect/prototype/protofile_type.go b/reflect/prototype/protofile_type.go
index 053d8b8..55631b1 100644
--- a/reflect/prototype/protofile_type.go
+++ b/reflect/prototype/protofile_type.go
@@ -47,6 +47,14 @@
 }
 type fileDesc struct{ f *File }
 
+// altOptions returns m as is if it is non-nil. Otherwise, it returns alt.
+func altOptions(m, alt pref.ProtoMessage) pref.ProtoMessage {
+	if m != nil {
+		return m
+	}
+	return alt
+}
+
 func newFile(f *File) fileDesc {
 	if f.fileMeta != nil {
 		panic("already initialized")
@@ -61,7 +69,7 @@
 func (t fileDesc) FullName() pref.FullName                          { return t.f.Package }
 func (t fileDesc) IsPlaceholder() bool                              { return false }
 func (t fileDesc) DescriptorProto() (pref.Message, bool)            { return nil, false }
-func (t fileDesc) Options() pref.ProtoMessage                       { return t.f.Options }
+func (t fileDesc) Options() pref.ProtoMessage                       { return altOptions(t.f.Options, optionTypes.File) }
 func (t fileDesc) Path() string                                     { return t.f.Path }
 func (t fileDesc) Package() pref.FullName                           { return t.f.Package }
 func (t fileDesc) Imports() pref.FileImports                        { return (*fileImports)(&t.f.Imports) }
@@ -169,7 +177,7 @@
 func (t messageDesc) FullName() pref.FullName               { return t.m.fullName }
 func (t messageDesc) IsPlaceholder() bool                   { return false }
 func (t messageDesc) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t messageDesc) Options() pref.ProtoMessage            { return t.m.Options }
+func (t messageDesc) Options() pref.ProtoMessage            { return altOptions(t.m.Options, optionTypes.Message) }
 func (t messageDesc) IsMapEntry() bool                      { return t.m.mo.lazyInit(t).isMapEntry }
 func (t messageDesc) Fields() pref.FieldDescriptors         { return t.m.fs.lazyInit(t, t.m.Fields) }
 func (t messageDesc) Oneofs() pref.OneofDescriptors         { return t.m.os.lazyInit(t, t.m.Oneofs) }
@@ -189,7 +197,7 @@
 
 func (p *messageOptions) lazyInit(m pref.MessageDescriptor) *messageOptions {
 	p.once.Do(func() {
-		if m.Options() != nil {
+		if m.Options() != optionTypes.Message {
 			const mapEntryFieldNumber = 7 // google.protobuf.MessageOptions.map_entry
 			fs := m.Options().ProtoReflect().KnownFields()
 			p.isMapEntry = fs.Get(mapEntryFieldNumber).Bool()
@@ -217,7 +225,7 @@
 func (t fieldDesc) FullName() pref.FullName                    { return t.f.fullName }
 func (t fieldDesc) IsPlaceholder() bool                        { return false }
 func (t fieldDesc) DescriptorProto() (pref.Message, bool)      { return nil, false }
-func (t fieldDesc) Options() pref.ProtoMessage                 { return t.f.Options }
+func (t fieldDesc) Options() pref.ProtoMessage                 { return altOptions(t.f.Options, optionTypes.Field) }
 func (t fieldDesc) Number() pref.FieldNumber                   { return t.f.Number }
 func (t fieldDesc) Cardinality() pref.Cardinality              { return t.f.Cardinality }
 func (t fieldDesc) Kind() pref.Kind                            { return t.f.Kind }
@@ -302,7 +310,7 @@
 			}
 		}
 
-		if f.Options() != nil {
+		if f.Options() != optionTypes.Field {
 			const packedFieldNumber = 2 // google.protobuf.FieldOptions.packed
 			const weakFieldNumber = 10  // google.protobuf.FieldOptions.weak
 			fs := f.Options().ProtoReflect().KnownFields()
@@ -317,7 +325,7 @@
 
 // isPacked reports whether the packed options is set.
 func isPacked(m pref.ProtoMessage) (isPacked bool) {
-	if m != nil {
+	if m != optionTypes.Field {
 		const packedFieldNumber = 2 // google.protobuf.FieldOptions.packed
 		fs := m.ProtoReflect().KnownFields()
 		isPacked = fs.Get(packedFieldNumber).Bool()
@@ -327,7 +335,7 @@
 
 // isWeak reports whether the weak options is set.
 func isWeak(m pref.ProtoMessage) (isWeak bool) {
-	if m != nil {
+	if m != optionTypes.Field {
 		const weakFieldNumber = 10 // google.protobuf.FieldOptions.weak
 		fs := m.ProtoReflect().KnownFields()
 		isWeak = fs.Get(weakFieldNumber).Bool()
@@ -366,7 +374,7 @@
 func (t oneofDesc) FullName() pref.FullName               { return t.o.fullName }
 func (t oneofDesc) IsPlaceholder() bool                   { return false }
 func (t oneofDesc) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t oneofDesc) Options() pref.ProtoMessage            { return t.o.Options }
+func (t oneofDesc) Options() pref.ProtoMessage            { return altOptions(t.o.Options, optionTypes.Oneof) }
 func (t oneofDesc) Fields() pref.FieldDescriptors         { return t.o.fs.lazyInit(t) }
 func (t oneofDesc) Format(s fmt.State, r rune)            { pfmt.FormatDesc(s, r, t) }
 func (t oneofDesc) ProtoType(pref.OneofDescriptor)        {}
@@ -389,7 +397,7 @@
 func (t extensionDesc) FullName() pref.FullName                    { return t.x.fullName }
 func (t extensionDesc) IsPlaceholder() bool                        { return false }
 func (t extensionDesc) DescriptorProto() (pref.Message, bool)      { return nil, false }
-func (t extensionDesc) Options() pref.ProtoMessage                 { return t.x.Options }
+func (t extensionDesc) Options() pref.ProtoMessage                 { return altOptions(t.x.Options, optionTypes.Field) }
 func (t extensionDesc) Number() pref.FieldNumber                   { return t.x.Number }
 func (t extensionDesc) Cardinality() pref.Cardinality              { return t.x.Cardinality }
 func (t extensionDesc) Kind() pref.Kind                            { return t.x.Kind }
@@ -426,7 +434,7 @@
 func (t enumDesc) FullName() pref.FullName               { return t.e.fullName }
 func (t enumDesc) IsPlaceholder() bool                   { return false }
 func (t enumDesc) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t enumDesc) Options() pref.ProtoMessage            { return t.e.Options }
+func (t enumDesc) Options() pref.ProtoMessage            { return altOptions(t.e.Options, optionTypes.Enum) }
 func (t enumDesc) Values() pref.EnumValueDescriptors     { return t.e.vs.lazyInit(t, t.e.Values) }
 func (t enumDesc) Format(s fmt.State, r rune)            { pfmt.FormatDesc(s, r, t) }
 func (t enumDesc) ProtoType(pref.EnumDescriptor)         {}
@@ -444,11 +452,13 @@
 func (t enumValueDesc) FullName() pref.FullName               { return t.v.fullName }
 func (t enumValueDesc) IsPlaceholder() bool                   { return false }
 func (t enumValueDesc) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t enumValueDesc) Options() pref.ProtoMessage            { return t.v.Options }
-func (t enumValueDesc) Number() pref.EnumNumber               { return t.v.Number }
-func (t enumValueDesc) Format(s fmt.State, r rune)            { pfmt.FormatDesc(s, r, t) }
-func (t enumValueDesc) ProtoType(pref.EnumValueDescriptor)    {}
-func (t enumValueDesc) ProtoInternal(pragma.DoNotImplement)   {}
+func (t enumValueDesc) Options() pref.ProtoMessage {
+	return altOptions(t.v.Options, optionTypes.EnumValue)
+}
+func (t enumValueDesc) Number() pref.EnumNumber             { return t.v.Number }
+func (t enumValueDesc) Format(s fmt.State, r rune)          { pfmt.FormatDesc(s, r, t) }
+func (t enumValueDesc) ProtoType(pref.EnumValueDescriptor)  {}
+func (t enumValueDesc) ProtoInternal(pragma.DoNotImplement) {}
 
 type serviceMeta struct {
 	inheritedMeta
@@ -464,7 +474,7 @@
 func (t serviceDesc) FullName() pref.FullName               { return t.s.fullName }
 func (t serviceDesc) IsPlaceholder() bool                   { return false }
 func (t serviceDesc) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t serviceDesc) Options() pref.ProtoMessage            { return t.s.Options }
+func (t serviceDesc) Options() pref.ProtoMessage            { return altOptions(t.s.Options, optionTypes.Service) }
 func (t serviceDesc) Methods() pref.MethodDescriptors       { return t.s.ms.lazyInit(t, t.s.Methods) }
 func (t serviceDesc) Format(s fmt.State, r rune)            { pfmt.FormatDesc(s, r, t) }
 func (t serviceDesc) ProtoType(pref.ServiceDescriptor)      {}
@@ -485,7 +495,7 @@
 func (t methodDesc) FullName() pref.FullName               { return t.m.fullName }
 func (t methodDesc) IsPlaceholder() bool                   { return false }
 func (t methodDesc) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t methodDesc) Options() pref.ProtoMessage            { return t.m.Options }
+func (t methodDesc) Options() pref.ProtoMessage            { return altOptions(t.m.Options, optionTypes.Method) }
 func (t methodDesc) InputType() pref.MessageDescriptor     { return t.m.mit.lazyInit(t, &t.m.InputType) }
 func (t methodDesc) OutputType() pref.MessageDescriptor    { return t.m.mot.lazyInit(t, &t.m.OutputType) }
 func (t methodDesc) IsStreamingClient() bool               { return t.m.IsStreamingClient }
diff --git a/reflect/prototype/standalone_type.go b/reflect/prototype/standalone_type.go
index 01f694f..12c2335 100644
--- a/reflect/prototype/standalone_type.go
+++ b/reflect/prototype/standalone_type.go
@@ -21,7 +21,9 @@
 func (t standaloneMessage) FullName() pref.FullName               { return t.m.FullName }
 func (t standaloneMessage) IsPlaceholder() bool                   { return false }
 func (t standaloneMessage) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t standaloneMessage) Options() pref.ProtoMessage            { return t.m.Options }
+func (t standaloneMessage) Options() pref.ProtoMessage {
+	return altOptions(t.m.Options, optionTypes.Message)
+}
 func (t standaloneMessage) IsMapEntry() bool                      { return t.m.options.lazyInit(t).isMapEntry }
 func (t standaloneMessage) Fields() pref.FieldDescriptors         { return t.m.fields.lazyInit(t, t.m.Fields) }
 func (t standaloneMessage) Oneofs() pref.OneofDescriptors         { return t.m.oneofs.lazyInit(t, t.m.Oneofs) }
@@ -43,7 +45,7 @@
 func (t standaloneEnum) FullName() pref.FullName               { return t.e.FullName }
 func (t standaloneEnum) IsPlaceholder() bool                   { return false }
 func (t standaloneEnum) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t standaloneEnum) Options() pref.ProtoMessage            { return t.e.Options }
+func (t standaloneEnum) Options() pref.ProtoMessage            { return altOptions(t.e.Options, optionTypes.Enum) }
 func (t standaloneEnum) Values() pref.EnumValueDescriptors     { return t.e.vals.lazyInit(t, t.e.Values) }
 func (t standaloneEnum) Format(s fmt.State, r rune)            { pfmt.FormatDesc(s, r, t) }
 func (t standaloneEnum) ProtoType(pref.EnumDescriptor)         {}
@@ -58,15 +60,17 @@
 func (t standaloneExtension) FullName() pref.FullName               { return t.x.FullName }
 func (t standaloneExtension) IsPlaceholder() bool                   { return false }
 func (t standaloneExtension) DescriptorProto() (pref.Message, bool) { return nil, false }
-func (t standaloneExtension) Options() pref.ProtoMessage            { return t.x.Options }
-func (t standaloneExtension) Number() pref.FieldNumber              { return t.x.Number }
-func (t standaloneExtension) Cardinality() pref.Cardinality         { return t.x.Cardinality }
-func (t standaloneExtension) Kind() pref.Kind                       { return t.x.Kind }
-func (t standaloneExtension) JSONName() string                      { return "" }
-func (t standaloneExtension) IsPacked() bool                        { return isPacked(t.Options()) }
-func (t standaloneExtension) IsWeak() bool                          { return false }
-func (t standaloneExtension) IsMap() bool                           { return false }
-func (t standaloneExtension) Default() pref.Value                   { return t.x.dv.value(t, t.x.Default) }
+func (t standaloneExtension) Options() pref.ProtoMessage {
+	return altOptions(t.x.Options, optionTypes.Field)
+}
+func (t standaloneExtension) Number() pref.FieldNumber      { return t.x.Number }
+func (t standaloneExtension) Cardinality() pref.Cardinality { return t.x.Cardinality }
+func (t standaloneExtension) Kind() pref.Kind               { return t.x.Kind }
+func (t standaloneExtension) JSONName() string              { return "" }
+func (t standaloneExtension) IsPacked() bool                { return isPacked(t.Options()) }
+func (t standaloneExtension) IsWeak() bool                  { return false }
+func (t standaloneExtension) IsMap() bool                   { return false }
+func (t standaloneExtension) Default() pref.Value           { return t.x.dv.value(t, t.x.Default) }
 func (t standaloneExtension) DefaultEnumValue() pref.EnumValueDescriptor {
 	return t.x.dv.enum(t, t.x.Default)
 }
diff --git a/types/descriptor/descriptor.pb.go b/types/descriptor/descriptor.pb.go
index 2732983..e91475d 100644
--- a/types/descriptor/descriptor.pb.go
+++ b/types/descriptor/descriptor.pb.go
@@ -3039,6 +3039,15 @@
 	if err != nil {
 		panic(err)
 	}
+	prototype.X.RegisterExtensionRangeOptions((*ExtensionRangeOptions)(nil))
+	prototype.X.RegisterFileOptions((*FileOptions)(nil))
+	prototype.X.RegisterMessageOptions((*MessageOptions)(nil))
+	prototype.X.RegisterFieldOptions((*FieldOptions)(nil))
+	prototype.X.RegisterOneofOptions((*OneofOptions)(nil))
+	prototype.X.RegisterEnumOptions((*EnumOptions)(nil))
+	prototype.X.RegisterEnumValueOptions((*EnumValueOptions)(nil))
+	prototype.X.RegisterServiceOptions((*ServiceOptions)(nil))
+	prototype.X.RegisterMethodOptions((*MethodOptions)(nil))
 }
 
 const _ = protoimpl.EnforceVersion(protoimpl.Version - 0)