compiler/protogen: add Semantic.SET to setter annotations

Provide an API to add the GeneratedCodeInfo.Annotation.Semantic enum to
annotations.

Change-Id: I92ab30619a94a117679a0eb16d8cb5b3a1352586
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/489795
Reviewed-by: Lasse Folger <lassefolger@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/protoc-gen-go/annotation_test.go b/cmd/protoc-gen-go/annotation_test.go
index c0f3c07..4ec1a2b 100644
--- a/cmd/protoc-gen-go/annotation_test.go
+++ b/cmd/protoc-gen-go/annotation_test.go
@@ -9,9 +9,11 @@
 	"io/ioutil"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
 	"google.golang.org/protobuf/encoding/prototext"
 	"google.golang.org/protobuf/internal/genid"
 	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/testing/protocmp"
 
 	"google.golang.org/protobuf/types/descriptorpb"
 )
@@ -33,22 +35,48 @@
 	wantInfo := &descriptorpb.GeneratedCodeInfo{}
 	for _, want := range []struct {
 		prefix, text, suffix string
-		path                 []int32
+		annotation           *descriptorpb.GeneratedCodeInfo_Annotation
 	}{{
 		"type ", "AnnotationsTestEnum", " int32",
-		[]int32{int32(genid.FileDescriptorProto_EnumType_field_number), 0},
+		&descriptorpb.GeneratedCodeInfo_Annotation{
+			Path: []int32{int32(genid.FileDescriptorProto_EnumType_field_number), 0},
+		},
 	}, {
 		"\t", "AnnotationsTestEnum_ANNOTATIONS_TEST_ENUM_VALUE", " AnnotationsTestEnum = 0",
-		[]int32{int32(genid.FileDescriptorProto_EnumType_field_number), 0, int32(genid.EnumDescriptorProto_Value_field_number), 0},
+		&descriptorpb.GeneratedCodeInfo_Annotation{
+			Path: []int32{int32(genid.FileDescriptorProto_EnumType_field_number), 0, int32(genid.EnumDescriptorProto_Value_field_number), 0},
+		},
 	}, {
 		"type ", "AnnotationsTestMessage", " struct {",
-		[]int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0},
+		&descriptorpb.GeneratedCodeInfo_Annotation{
+			Path: []int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0},
+		},
 	}, {
 		"\t", "AnnotationsTestField", " ",
-		[]int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0, int32(genid.DescriptorProto_Field_field_number), 0},
+		&descriptorpb.GeneratedCodeInfo_Annotation{
+			Path: []int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0, int32(genid.DescriptorProto_Field_field_number), 0},
+		},
+	}, {
+		"\t", "XXX_weak_M", " ",
+		&descriptorpb.GeneratedCodeInfo_Annotation{
+			Path: []int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0, int32(genid.DescriptorProto_Field_field_number), 1},
+		},
 	}, {
 		"func (x *AnnotationsTestMessage) ", "GetAnnotationsTestField", "() string {",
-		[]int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0, int32(genid.DescriptorProto_Field_field_number), 0},
+		&descriptorpb.GeneratedCodeInfo_Annotation{
+			Path: []int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0, int32(genid.DescriptorProto_Field_field_number), 0},
+		},
+	}, {
+		"func (x *AnnotationsTestMessage) ", "GetM", "() proto.Message {",
+		&descriptorpb.GeneratedCodeInfo_Annotation{
+			Path: []int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0, int32(genid.DescriptorProto_Field_field_number), 1},
+		},
+	}, {
+		"func (x *AnnotationsTestMessage) ", "SetM", "(v proto.Message) {",
+		&descriptorpb.GeneratedCodeInfo_Annotation{
+			Path:     []int32{int32(genid.FileDescriptorProto_MessageType_field_number), 0, int32(genid.DescriptorProto_Field_field_number), 1},
+			Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
+		},
 	}} {
 		s := want.prefix + want.text + want.suffix
 		pos := bytes.Index(sourceFile, []byte(s))
@@ -58,14 +86,15 @@
 		}
 		begin := pos + len(want.prefix)
 		end := begin + len(want.text)
-		wantInfo.Annotation = append(wantInfo.Annotation, &descriptorpb.GeneratedCodeInfo_Annotation{
-			Path:       want.path,
+		a := &descriptorpb.GeneratedCodeInfo_Annotation{
 			Begin:      proto.Int32(int32(begin)),
 			End:        proto.Int32(int32(end)),
 			SourceFile: proto.String("cmd/protoc-gen-go/testdata/annotations/annotations.proto"),
-		})
+		}
+		proto.Merge(a, want.annotation)
+		wantInfo.Annotation = append(wantInfo.Annotation, a)
 	}
-	if !proto.Equal(gotInfo, wantInfo) {
-		t.Errorf("unexpected annotations for annotations.proto; got:\n%v\nwant:\n%v", gotInfo, wantInfo)
+	if diff := cmp.Diff(wantInfo, gotInfo, protocmp.Transform()); diff != "" {
+		t.Fatalf("unexpected annotations for annotations.proto (-want +got):\n%s", diff)
 	}
 }
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index f8b76bf..8cae432 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -614,7 +614,10 @@
 
 		genNoInterfacePragma(g, m.isTracked)
 
-		g.Annotate(m.GoIdent.GoName+".Set"+field.GoName, field.Location)
+		g.AnnotateSymbol(m.GoIdent.GoName+".Set"+field.GoName, protogen.Annotation{
+			Location: field.Location,
+			Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
+		})
 		leadingComments := appendDeprecationSuffix("",
 			field.Desc.ParentFile(),
 			field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated())
diff --git a/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go b/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go
index 2a5fd24..91caa37 100644
--- a/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go
+++ b/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go
@@ -8,6 +8,7 @@
 package annotations
 
 import (
+	proto "google.golang.org/protobuf/proto"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	reflect "reflect"
@@ -70,9 +71,11 @@
 type AnnotationsTestMessage struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
+	weakFields    protoimpl.WeakFields
 	unknownFields protoimpl.UnknownFields
 
-	AnnotationsTestField *string `protobuf:"bytes,1,opt,name=AnnotationsTestField" json:"AnnotationsTestField,omitempty"`
+	AnnotationsTestField *string  `protobuf:"bytes,1,opt,name=AnnotationsTestField" json:"AnnotationsTestField,omitempty"`
+	XXX_weak_M           struct{} `protobuf:"bytes,2,opt,name=m,weak=fmt.M" json:"m,omitempty"`
 }
 
 func (x *AnnotationsTestMessage) Reset() {
@@ -114,6 +117,22 @@
 	return ""
 }
 
+func (x *AnnotationsTestMessage) GetM() proto.Message {
+	var w protoimpl.WeakFields
+	if x != nil {
+		w = x.weakFields
+	}
+	return protoimpl.X.GetWeak(w, 2, "fmt.M")
+}
+
+func (x *AnnotationsTestMessage) SetM(v proto.Message) {
+	var w *protoimpl.WeakFields
+	if x != nil {
+		w = &x.weakFields
+	}
+	protoimpl.X.SetWeak(w, 2, "fmt.M", v)
+}
+
 var File_cmd_protoc_gen_go_testdata_annotations_annotations_proto protoreflect.FileDescriptor
 
 var file_cmd_protoc_gen_go_testdata_annotations_annotations_proto_rawDesc = []byte{
@@ -122,20 +141,24 @@
 	0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74,
 	0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x67, 0x6f, 0x70, 0x72,
 	0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x61, 0x6e, 0x6e, 0x6f, 0x74,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4c, 0x0a, 0x16, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x2e, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61,
+	0x74, 0x61, 0x2f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2f, 0x66, 0x6d, 0x74, 0x2f, 0x6d,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x66, 0x0a, 0x16, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61,
 	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
 	0x12, 0x32, 0x0a, 0x14, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x54,
 	0x65, 0x73, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14,
 	0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x54, 0x65, 0x73, 0x74, 0x46,
-	0x69, 0x65, 0x6c, 0x64, 0x2a, 0x36, 0x0a, 0x13, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x73, 0x54, 0x65, 0x73, 0x74, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x1f, 0x0a, 0x1b, 0x41,
-	0x4e, 0x4e, 0x4f, 0x54, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f,
-	0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x00, 0x42, 0x43, 0x5a, 0x41,
-	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72,
-	0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73,
-	0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x73,
+	0x69, 0x65, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x01, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x06, 0x2e, 0x66, 0x6d, 0x74, 0x2e, 0x4d, 0x42, 0x02, 0x50, 0x01, 0x52, 0x01, 0x6d, 0x2a, 0x36,
+	0x0a, 0x13, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x54, 0x65, 0x73,
+	0x74, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x1f, 0x0a, 0x1b, 0x41, 0x4e, 0x4e, 0x4f, 0x54, 0x41, 0x54,
+	0x49, 0x4f, 0x4e, 0x53, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x56,
+	0x41, 0x4c, 0x55, 0x45, 0x10, 0x00, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+	0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d,
+	0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f,
+	0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x58, 0x00,
 }
 
 var (
@@ -177,6 +200,8 @@
 			case 1:
 				return &v.sizeCache
 			case 2:
+				return &v.weakFields
+			case 3:
 				return &v.unknownFields
 			default:
 				return nil
diff --git a/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go.meta b/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go.meta
index 91f84f3..d0d5ea6 100644
--- a/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go.meta
+++ b/cmd/protoc-gen-go/testdata/annotations/annotations.pb.go.meta
@@ -1 +1 @@
-annotation:{path:5 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:470 end:489} annotation:{path:5 path:0 path:2 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:506 end:553} annotation:{path:4 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:1912 end:1934} annotation:{path:4 path:0 path:2 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:2058 end:2078} annotation:{path:4 path:0 path:2 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:3225 end:3248}
\ No newline at end of file
+annotation:{path:5 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:512 end:531} annotation:{path:5 path:0 path:2 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:548 end:595} annotation:{path:4 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:1954 end:1976} annotation:{path:4 path:0 path:2 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:2136 end:2156} annotation:{path:4 path:0 path:2 path:1 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:2256 end:2266} annotation:{path:4 path:0 path:2 path:0 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:3397 end:3420} annotation:{path:4 path:0 path:2 path:1 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:3563 end:3567} annotation:{path:4 path:0 path:2 path:1 source_file:"cmd/protoc-gen-go/testdata/annotations/annotations.proto" begin:3730 end:3734 semantic:SET}
\ No newline at end of file
diff --git a/cmd/protoc-gen-go/testdata/annotations/annotations.proto b/cmd/protoc-gen-go/testdata/annotations/annotations.proto
index acd7b86..d022ab3 100644
--- a/cmd/protoc-gen-go/testdata/annotations/annotations.proto
+++ b/cmd/protoc-gen-go/testdata/annotations/annotations.proto
@@ -6,10 +6,14 @@
 
 package goproto.protoc.annotations;
 
+import weak "cmd/protoc-gen-go/testdata/imports/fmt/m.proto";
+
 option go_package = "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/annotations";
 
 message AnnotationsTestMessage {
   optional string AnnotationsTestField = 1;
+
+  optional fmt.M m = 2 [weak = true];
 }
 
 enum AnnotationsTestEnum {
diff --git a/compiler/protogen/protogen.go b/compiler/protogen/protogen.go
index 2d2171e..431e880 100644
--- a/compiler/protogen/protogen.go
+++ b/compiler/protogen/protogen.go
@@ -920,7 +920,7 @@
 	packageNames     map[GoImportPath]GoPackageName
 	usedPackageNames map[GoPackageName]bool
 	manualImports    map[GoImportPath]bool
-	annotations      map[string][]Location
+	annotations      map[string][]Annotation
 }
 
 // NewGeneratedFile creates a new generated file with the given filename
@@ -933,7 +933,7 @@
 		packageNames:     make(map[GoImportPath]GoPackageName),
 		usedPackageNames: make(map[GoPackageName]bool),
 		manualImports:    make(map[GoImportPath]bool),
-		annotations:      make(map[string][]Location),
+		annotations:      make(map[string][]Annotation),
 	}
 
 	// All predeclared identifiers in Go are already used.
@@ -1012,8 +1012,32 @@
 // The symbol may refer to a type, constant, variable, function, method, or
 // struct field.  The "T.sel" syntax is used to identify the method or field
 // 'sel' on type 'T'.
+//
+// Deprecated: Use the AnnotateSymbol method instead.
 func (g *GeneratedFile) Annotate(symbol string, loc Location) {
-	g.annotations[symbol] = append(g.annotations[symbol], loc)
+	g.AnnotateSymbol(symbol, Annotation{Location: loc})
+}
+
+// An Annotation provides semantic detail for a generated proto element.
+//
+// See the google.protobuf.GeneratedCodeInfo.Annotation documentation in
+// descriptor.proto for details.
+type Annotation struct {
+	// Location is the source .proto file for the element.
+	Location Location
+
+	// Semantic is the symbol's effect on the element in the original .proto file.
+	Semantic *descriptorpb.GeneratedCodeInfo_Annotation_Semantic
+}
+
+// AnnotateSymbol associates a symbol in a generated Go file with a location
+// in a source .proto file and a semantic type.
+//
+// The symbol may refer to a type, constant, variable, function, method, or
+// struct field.  The "T.sel" syntax is used to identify the method or field
+// 'sel' on type 'T'.
+func (g *GeneratedFile) AnnotateSymbol(symbol string, info Annotation) {
+	g.annotations[symbol] = append(g.annotations[symbol], info)
 }
 
 // Content returns the contents of the generated file.
@@ -1106,25 +1130,24 @@
 	return out.Bytes(), nil
 }
 
-// metaFile returns the contents of the file's metadata file, which is a
-// text formatted string of the google.protobuf.GeneratedCodeInfo.
-func (g *GeneratedFile) metaFile(content []byte) (string, error) {
+func (g *GeneratedFile) generatedCodeInfo(content []byte) (*descriptorpb.GeneratedCodeInfo, error) {
 	fset := token.NewFileSet()
 	astFile, err := parser.ParseFile(fset, "", content, 0)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	info := &descriptorpb.GeneratedCodeInfo{}
 
 	seenAnnotations := make(map[string]bool)
 	annotate := func(s string, ident *ast.Ident) {
 		seenAnnotations[s] = true
-		for _, loc := range g.annotations[s] {
+		for _, a := range g.annotations[s] {
 			info.Annotation = append(info.Annotation, &descriptorpb.GeneratedCodeInfo_Annotation{
-				SourceFile: proto.String(loc.SourceFile),
-				Path:       loc.Path,
+				SourceFile: proto.String(a.Location.SourceFile),
+				Path:       a.Location.Path,
 				Begin:      proto.Int32(int32(fset.Position(ident.Pos()).Offset)),
 				End:        proto.Int32(int32(fset.Position(ident.End()).Offset)),
+				Semantic:   a.Semantic,
 			})
 		}
 	}
@@ -1171,10 +1194,21 @@
 	}
 	for a := range g.annotations {
 		if !seenAnnotations[a] {
-			return "", fmt.Errorf("%v: no symbol matching annotation %q", g.filename, a)
+			return nil, fmt.Errorf("%v: no symbol matching annotation %q", g.filename, a)
 		}
 	}
 
+	return info, nil
+}
+
+// metaFile returns the contents of the file's metadata file, which is a
+// text formatted string of the google.protobuf.GeneratedCodeInfo.
+func (g *GeneratedFile) metaFile(content []byte) (string, error) {
+	info, err := g.generatedCodeInfo(content)
+	if err != nil {
+		return "", err
+	}
+
 	b, err := prototext.Marshal(info)
 	if err != nil {
 		return "", err
diff --git a/compiler/protogen/protogen_test.go b/compiler/protogen/protogen_test.go
index 4f5ceed..d7b5407 100644
--- a/compiler/protogen/protogen_test.go
+++ b/compiler/protogen/protogen_test.go
@@ -11,8 +11,10 @@
 
 	"github.com/google/go-cmp/cmp"
 
+	"google.golang.org/protobuf/internal/genid"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/testing/protocmp"
 
 	"google.golang.org/protobuf/types/descriptorpb"
 	"google.golang.org/protobuf/types/pluginpb"
@@ -344,3 +346,73 @@
 		t.Fatalf("content mismatch (-want +got):\n%s", diff)
 	}
 }
+
+func TestAnnotations(t *testing.T) {
+	gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{})
+	if err != nil {
+		t.Fatal(err)
+	}
+	loc := Location{SourceFile: "foo.go"}
+	g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
+
+	g.P("package foo")
+	g.P()
+
+	structName := "S"
+	fieldName := "Field"
+
+	messageLoc := loc.appendPath(genid.FileDescriptorProto_MessageType_field_number, 1)
+	fieldLoc := messageLoc.appendPath(genid.DescriptorProto_Field_field_number, 1)
+
+	g.Annotate(structName, messageLoc) // use deprecated version to test existing usages
+	g.P("type ", structName, " struct {")
+	g.AnnotateSymbol(structName+"."+fieldName, Annotation{Location: fieldLoc})
+	g.P(fieldName, " string")
+	g.P("}")
+	g.P()
+
+	g.AnnotateSymbol(fmt.Sprintf("%s.Set%s", structName, fieldName), Annotation{
+		Location: fieldLoc,
+		Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
+	})
+	g.P("func (m *", structName, ") Set", fieldName, "(x string) {")
+	g.P("m.", fieldName, " = x")
+	g.P("}")
+	g.P()
+
+	want := &descriptorpb.GeneratedCodeInfo{
+		Annotation: []*descriptorpb.GeneratedCodeInfo_Annotation{
+			{ // S
+				SourceFile: proto.String("foo.go"),
+				Path:       []int32{4, 1}, // message S
+				Begin:      proto.Int32(18),
+				End:        proto.Int32(19),
+			},
+			{ // S.F
+				SourceFile: proto.String("foo.go"),
+				Path:       []int32{4, 1, 2, 1},
+				Begin:      proto.Int32(30),
+				End:        proto.Int32(35),
+			},
+			{ // SetF
+				SourceFile: proto.String("foo.go"),
+				Path:       []int32{4, 1, 2, 1},
+				Begin:      proto.Int32(58),
+				End:        proto.Int32(66),
+				Semantic:   descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
+			},
+		},
+	}
+
+	content, err := g.Content()
+	if err != nil {
+		t.Fatalf("g.Content() = %v", err)
+	}
+	got, err := g.generatedCodeInfo(content)
+	if err != nil {
+		t.Fatalf("g.generatedCodeInfo(...) = %v", err)
+	}
+	if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
+		t.Fatalf("GeneratedCodeInfo mismatch (-want +got):\n%s", diff)
+	}
+}