compiler/protogen: support -experimental_strip_nonfunctional_codegen

This flag is used by the (Google-internal) editions codegen test.
Having the functionality in the open source repository reduces
our maintenance burden (fewer/smaller patches).

Change-Id: Idb9c95e9b2cb8922584bcb7ca13a8ddef4347af0
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/606735
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Lasse Folger <lassefolger@google.com>
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index 5402dfd..1a3a342 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -76,11 +76,14 @@
 	g := gen.NewGeneratedFile(filename, file.GoImportPath)
 	f := newFileInfo(file)
 
-	genStandaloneComments(g, f, int32(genid.FileDescriptorProto_Syntax_field_number))
-	genGeneratedHeader(gen, g, f)
-	genStandaloneComments(g, f, int32(genid.FileDescriptorProto_Package_field_number))
+	var packageDoc protogen.Comments
+	if !gen.InternalStripForEditionsDiff() {
+		genStandaloneComments(g, f, int32(genid.FileDescriptorProto_Syntax_field_number))
+		genGeneratedHeader(gen, g, f)
+		genStandaloneComments(g, f, int32(genid.FileDescriptorProto_Package_field_number))
 
-	packageDoc := genPackageKnownComment(f)
+		packageDoc = genPackageKnownComment(f)
+	}
 	g.P(packageDoc, "package ", f.GoPackageName)
 	g.P()
 
@@ -106,7 +109,23 @@
 	}
 	genExtensions(g, f)
 
-	genReflectFileDescriptor(gen, g, f)
+	// The descriptor contains a lot of information about the syntax which is
+	// quite different between the proto2/3 version of a file and the equivalent
+	// editions version. For example, when a proto3 file is translated from
+	// proto3 to editions every field in that file that is marked optional in
+	// proto3 will have a features.field_presence option set.
+	// Another problem is that the descriptor contains implementation details
+	// that are not relevant for the semantic. For example, proto3 optional
+	// fields are implemented as oneof fields with one case. The descriptor
+	// contains different information about oneofs. If the file is translated
+	// to editions it no longer is treated as a oneof with one case and thus
+	// none of the oneof specific information is generated.
+	// To be able to compare the descriptor before and after translation of the
+	// associated proto file, we would need to trim many parts. This would lead
+	// to a brittle implementation in case the translation ever changes.
+	if !g.InternalStripForEditionsDiff() {
+		genReflectFileDescriptor(gen, g, f)
+	}
 
 	return g
 }
diff --git a/cmd/protoc-gen-go/main.go b/cmd/protoc-gen-go/main.go
index 431fbb9..f3b5acb 100644
--- a/cmd/protoc-gen-go/main.go
+++ b/cmd/protoc-gen-go/main.go
@@ -35,11 +35,13 @@
 	}
 
 	var (
-		flags   flag.FlagSet
-		plugins = flags.String("plugins", "", "deprecated option")
+		flags                                 flag.FlagSet
+		plugins                               = flags.String("plugins", "", "deprecated option")
+		experimentalStripNonFunctionalCodegen = flags.Bool("experimental_strip_nonfunctional_codegen", false, "experimental_strip_nonfunctional_codegen true means that the plugin will not emit certain parts of the generated code in order to make it possible to compare a proto2/proto3 file with its equivalent (according to proto spec) editions file. Primarily, this is the encoded descriptor.")
 	)
 	protogen.Options{
-		ParamFunc: flags.Set,
+		ParamFunc:                    flags.Set,
+		InternalStripForEditionsDiff: experimentalStripNonFunctionalCodegen,
 	}.Run(func(gen *protogen.Plugin) error {
 		if *plugins != "" {
 			return errors.New("protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC\n\n" +
diff --git a/compiler/protogen/protogen.go b/compiler/protogen/protogen.go
index 3c11644..966728e 100644
--- a/compiler/protogen/protogen.go
+++ b/compiler/protogen/protogen.go
@@ -152,6 +152,18 @@
 	// imported by a generated file. It returns the import path to use
 	// for this package.
 	ImportRewriteFunc func(GoImportPath) GoImportPath
+
+	// StripForEditionsDiff true means that the plugin will not emit certain
+	// parts of the generated code in order to make it possible to compare a
+	// proto2/proto3 file with its equivalent (according to proto spec)
+	// editions file. Primarily, this is the encoded descriptor.
+	//
+	// This must be a registered flag that is initialized by ParamFunc. It will
+	// be used by Options.New after it has parsed the flags.
+	//
+	// This struct field is for internal use by Go Protobuf only. Do not use it,
+	// we might remove it at any time.
+	InternalStripForEditionsDiff *bool
 }
 
 // New returns a new Plugin.
@@ -344,6 +356,15 @@
 	return gen, nil
 }
 
+// InternalStripForEditionsDiff returns whether or not to strip non-functional
+// codegen for editions diff testing.
+//
+// This function is for internal use by Go Protobuf only. Do not use it, we
+// might remove it at any time.
+func (gen *Plugin) InternalStripForEditionsDiff() bool {
+	return gen.opts.InternalStripForEditionsDiff != nil && *gen.opts.InternalStripForEditionsDiff
+}
+
 // Error records an error in code generation. The generator will report the
 // error back to protoc and will not produce output.
 func (gen *Plugin) Error(err error) {
@@ -538,7 +559,7 @@
 		Desc:     desc,
 		GoIdent:  newGoIdent(f, desc),
 		Location: loc,
-		Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)),
+		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
 	gen.enumsByName[desc.FullName()] = enum
 	for i, vds := 0, enum.Desc.Values(); i < vds.Len(); i++ {
@@ -575,7 +596,7 @@
 		GoIdent:  f.GoImportPath.Ident(name),
 		Parent:   enum,
 		Location: loc,
-		Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)),
+		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
 }
 
@@ -607,7 +628,7 @@
 		Desc:     desc,
 		GoIdent:  newGoIdent(f, desc),
 		Location: loc,
-		Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)),
+		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
 	gen.messagesByName[desc.FullName()] = message
 	for i, eds := 0, desc.Enums(); i < eds.Len(); i++ {
@@ -777,7 +798,7 @@
 		},
 		Parent:   message,
 		Location: loc,
-		Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)),
+		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
 	return field
 }
@@ -844,7 +865,7 @@
 			GoName:       parentPrefix + camelCased,
 		},
 		Location: loc,
-		Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)),
+		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
 }
 
@@ -869,7 +890,7 @@
 		Desc:     desc,
 		GoName:   strs.GoCamelCase(string(desc.Name())),
 		Location: loc,
-		Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)),
+		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
 	for i, mds := 0, desc.Methods(); i < mds.Len(); i++ {
 		service.Methods = append(service.Methods, newMethod(gen, f, service, mds.Get(i)))
@@ -899,7 +920,7 @@
 		GoName:   strs.GoCamelCase(string(desc.Name())),
 		Parent:   service,
 		Location: loc,
-		Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)),
+		Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
 	}
 	return method
 }
@@ -926,28 +947,30 @@
 
 // A GeneratedFile is a generated file.
 type GeneratedFile struct {
-	gen              *Plugin
-	skip             bool
-	filename         string
-	goImportPath     GoImportPath
-	buf              bytes.Buffer
-	packageNames     map[GoImportPath]GoPackageName
-	usedPackageNames map[GoPackageName]bool
-	manualImports    map[GoImportPath]bool
-	annotations      map[string][]Annotation
+	gen                  *Plugin
+	skip                 bool
+	filename             string
+	goImportPath         GoImportPath
+	buf                  bytes.Buffer
+	packageNames         map[GoImportPath]GoPackageName
+	usedPackageNames     map[GoPackageName]bool
+	manualImports        map[GoImportPath]bool
+	annotations          map[string][]Annotation
+	stripForEditionsDiff bool
 }
 
 // NewGeneratedFile creates a new generated file with the given filename
 // and import path.
 func (gen *Plugin) NewGeneratedFile(filename string, goImportPath GoImportPath) *GeneratedFile {
 	g := &GeneratedFile{
-		gen:              gen,
-		filename:         filename,
-		goImportPath:     goImportPath,
-		packageNames:     make(map[GoImportPath]GoPackageName),
-		usedPackageNames: make(map[GoPackageName]bool),
-		manualImports:    make(map[GoImportPath]bool),
-		annotations:      make(map[string][]Annotation),
+		gen:                  gen,
+		filename:             filename,
+		goImportPath:         goImportPath,
+		packageNames:         make(map[GoImportPath]GoPackageName),
+		usedPackageNames:     make(map[GoPackageName]bool),
+		manualImports:        make(map[GoImportPath]bool),
+		annotations:          make(map[string][]Annotation),
+		stripForEditionsDiff: gen.InternalStripForEditionsDiff(),
 	}
 
 	// All predeclared identifiers in Go are already used.
@@ -1020,6 +1043,17 @@
 	g.skip = false
 }
 
+// InternalStripForEditionsDiff returns true if the plugin should not emit certain
+// parts of the generated code in order to make it possible to compare a
+// proto2/proto3 file with its equivalent (according to proto spec) editions
+// file. Primarily, this is the encoded descriptor.
+//
+// This function is for internal use by Go Protobuf only. Do not use it, we
+// might remove it at any time.
+func (g *GeneratedFile) InternalStripForEditionsDiff() bool {
+	return g.stripForEditionsDiff
+}
+
 // Annotate associates a symbol in a generated Go file with a location in a
 // source .proto file.
 //
@@ -1298,7 +1332,10 @@
 	Trailing        Comments
 }
 
-func makeCommentSet(loc protoreflect.SourceLocation) CommentSet {
+func makeCommentSet(gen *Plugin, loc protoreflect.SourceLocation) CommentSet {
+	if gen.InternalStripForEditionsDiff() {
+		return CommentSet{}
+	}
 	var leadingDetached []Comments
 	for _, s := range loc.LeadingDetachedComments {
 		leadingDetached = append(leadingDetached, Comments(s))