reflect/prototype: lower minimum support to Go1.9

The minimum supported version is currently Go1.10 since we use strings.Builder
for a fairly significant optimization when constructing all of the descriptor
full names.

The strings.Builder implementation is not particularly complicated,
so just fork it into our code. The golang/protobuf and the golang/go
projects share the same authors and copyright license.

Change-Id: Ibb9519dbe756327a07369f10f80c15761002b5e7
Reviewed-on: https://go-review.googlesource.com/136735
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/cmd/generate-types/main.go b/internal/cmd/generate-types/main.go
index 49d0e28..8d239d3 100644
--- a/internal/cmd/generate-types/main.go
+++ b/internal/cmd/generate-types/main.go
@@ -7,6 +7,7 @@
 package main
 
 import (
+	"bytes"
 	"flag"
 	"go/format"
 	"io/ioutil"
@@ -105,8 +106,8 @@
 
 	func (p *{{$nameListMeta}}) lazyInit(parent protoreflect.Descriptor, ts []{{.}}) *{{$nameList}} {
 		p.once.Do(func() {
-			nb := nameBuilderPool.Get().(*nameBuilder)
-			defer nameBuilderPool.Put(nb)
+			nb := getNameBuilder()
+			defer putNameBuilder(nb)
 			metas := make([]{{$nameMeta}}, len(ts))
 			for i := range ts {
 				t := &ts[i]
@@ -185,11 +186,11 @@
 `))
 
 func mustExecute(t *template.Template, data interface{}) string {
-	var sb strings.Builder
-	if err := t.Execute(&sb, data); err != nil {
+	var b bytes.Buffer
+	if err := t.Execute(&b, data); err != nil {
 		panic(err)
 	}
-	return sb.String()
+	return b.String()
 }
 
 func writeSource(file, src string) {
diff --git a/reflect/prototype/name_pure.go b/reflect/prototype/name_pure.go
new file mode 100644
index 0000000..293ad37
--- /dev/null
+++ b/reflect/prototype/name_pure.go
@@ -0,0 +1,19 @@
+// 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.
+
+// +build purego
+
+package prototype
+
+import pref "google.golang.org/proto/reflect/protoreflect"
+
+func getNameBuilder() *nameBuilder { return nil }
+func putNameBuilder(*nameBuilder)  {}
+
+type nameBuilder struct{}
+
+// Append is equivalent to protoreflect.FullName.Append.
+func (*nameBuilder) Append(prefix pref.FullName, name pref.Name) pref.FullName {
+	return prefix.Append(name)
+}
diff --git a/reflect/prototype/name_unsafe.go b/reflect/prototype/name_unsafe.go
new file mode 100644
index 0000000..88ad51c
--- /dev/null
+++ b/reflect/prototype/name_unsafe.go
@@ -0,0 +1,99 @@
+// 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.
+
+// +build !purego
+
+package prototype
+
+import (
+	"strings"
+	"sync"
+	"unsafe"
+
+	pref "google.golang.org/proto/reflect/protoreflect"
+)
+
+var nameBuilderPool = sync.Pool{
+	New: func() interface{} { return new(nameBuilder) },
+}
+
+func getNameBuilder() *nameBuilder {
+	return nameBuilderPool.Get().(*nameBuilder)
+}
+func putNameBuilder(b *nameBuilder) {
+	nameBuilderPool.Put(b)
+}
+
+type nameBuilder struct {
+	sb stringBuilder
+}
+
+// Append is equivalent to protoreflect.FullName.Append, but is optimized for
+// large batches of operations where each name has a shared lifetime.
+func (b *nameBuilder) Append(prefix pref.FullName, name pref.Name) pref.FullName {
+	const batchSize = 1 << 12
+	n := len(prefix) + len(".") + len(name)
+	if b.sb.Cap()-b.sb.Len() < n {
+		b.sb.Reset()
+		b.sb.Grow(batchSize)
+	}
+	if !strings.HasSuffix(b.sb.String(), string(prefix)) {
+		b.sb.WriteString(string(prefix))
+	}
+	b.sb.WriteByte('.')
+	b.sb.WriteString(string(name))
+	s := b.sb.String()
+	return pref.FullName(strings.TrimPrefix(s[len(s)-n:], "."))
+}
+
+// stringsBuilder is a simplified copy of the strings.Builder from Go1.12:
+//	* removed the shallow copy check
+//	* removed methods that we do not use (e.g. WriteRune)
+//
+// A forked version is used:
+//	* to enable Go1.9 support, but strings.Builder was added in Go1.10
+//	* for the Cap method, which was missing until Go1.12
+//
+// TODO: Remove this when Go1.12 is the minimally supported toolchain version.
+type stringBuilder struct {
+	buf []byte
+}
+
+func (b *stringBuilder) String() string {
+	return *(*string)(unsafe.Pointer(&b.buf))
+}
+func (b *stringBuilder) Len() int {
+	return len(b.buf)
+}
+func (b *stringBuilder) Cap() int {
+	return cap(b.buf)
+}
+func (b *stringBuilder) Reset() {
+	b.buf = nil
+}
+func (b *stringBuilder) grow(n int) {
+	buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
+	copy(buf, b.buf)
+	b.buf = buf
+}
+func (b *stringBuilder) Grow(n int) {
+	if n < 0 {
+		panic("stringBuilder.Grow: negative count")
+	}
+	if cap(b.buf)-len(b.buf) < n {
+		b.grow(n)
+	}
+}
+func (b *stringBuilder) Write(p []byte) (int, error) {
+	b.buf = append(b.buf, p...)
+	return len(p), nil
+}
+func (b *stringBuilder) WriteByte(c byte) error {
+	b.buf = append(b.buf, c)
+	return nil
+}
+func (b *stringBuilder) WriteString(s string) (int, error) {
+	b.buf = append(b.buf, s...)
+	return len(s), nil
+}
diff --git a/reflect/prototype/protofile_list_gen.go b/reflect/prototype/protofile_list_gen.go
index 8a5c375..bd4f691 100644
--- a/reflect/prototype/protofile_list_gen.go
+++ b/reflect/prototype/protofile_list_gen.go
@@ -24,8 +24,8 @@
 
 func (p *messagesMeta) lazyInit(parent protoreflect.Descriptor, ts []Message) *messages {
 	p.once.Do(func() {
-		nb := nameBuilderPool.Get().(*nameBuilder)
-		defer nameBuilderPool.Put(nb)
+		nb := getNameBuilder()
+		defer putNameBuilder(nb)
 		metas := make([]messageMeta, len(ts))
 		for i := range ts {
 			t := &ts[i]
@@ -74,8 +74,8 @@
 
 func (p *fieldsMeta) lazyInit(parent protoreflect.Descriptor, ts []Field) *fields {
 	p.once.Do(func() {
-		nb := nameBuilderPool.Get().(*nameBuilder)
-		defer nameBuilderPool.Put(nb)
+		nb := getNameBuilder()
+		defer putNameBuilder(nb)
 		metas := make([]fieldMeta, len(ts))
 		for i := range ts {
 			t := &ts[i]
@@ -157,8 +157,8 @@
 
 func (p *oneofsMeta) lazyInit(parent protoreflect.Descriptor, ts []Oneof) *oneofs {
 	p.once.Do(func() {
-		nb := nameBuilderPool.Get().(*nameBuilder)
-		defer nameBuilderPool.Put(nb)
+		nb := getNameBuilder()
+		defer putNameBuilder(nb)
 		metas := make([]oneofMeta, len(ts))
 		for i := range ts {
 			t := &ts[i]
@@ -203,8 +203,8 @@
 
 func (p *extensionsMeta) lazyInit(parent protoreflect.Descriptor, ts []Extension) *extensions {
 	p.once.Do(func() {
-		nb := nameBuilderPool.Get().(*nameBuilder)
-		defer nameBuilderPool.Put(nb)
+		nb := getNameBuilder()
+		defer putNameBuilder(nb)
 		metas := make([]extensionMeta, len(ts))
 		for i := range ts {
 			t := &ts[i]
@@ -249,8 +249,8 @@
 
 func (p *enumsMeta) lazyInit(parent protoreflect.Descriptor, ts []Enum) *enums {
 	p.once.Do(func() {
-		nb := nameBuilderPool.Get().(*nameBuilder)
-		defer nameBuilderPool.Put(nb)
+		nb := getNameBuilder()
+		defer putNameBuilder(nb)
 		metas := make([]enumMeta, len(ts))
 		for i := range ts {
 			t := &ts[i]
@@ -297,8 +297,8 @@
 
 func (p *enumValuesMeta) lazyInit(parent protoreflect.Descriptor, ts []EnumValue) *enumValues {
 	p.once.Do(func() {
-		nb := nameBuilderPool.Get().(*nameBuilder)
-		defer nameBuilderPool.Put(nb)
+		nb := getNameBuilder()
+		defer putNameBuilder(nb)
 		metas := make([]enumValueMeta, len(ts))
 		for i := range ts {
 			t := &ts[i]
@@ -361,8 +361,8 @@
 
 func (p *servicesMeta) lazyInit(parent protoreflect.Descriptor, ts []Service) *services {
 	p.once.Do(func() {
-		nb := nameBuilderPool.Get().(*nameBuilder)
-		defer nameBuilderPool.Put(nb)
+		nb := getNameBuilder()
+		defer putNameBuilder(nb)
 		metas := make([]serviceMeta, len(ts))
 		for i := range ts {
 			t := &ts[i]
@@ -407,8 +407,8 @@
 
 func (p *methodsMeta) lazyInit(parent protoreflect.Descriptor, ts []Method) *methods {
 	p.once.Do(func() {
-		nb := nameBuilderPool.Get().(*nameBuilder)
-		defer nameBuilderPool.Put(nb)
+		nb := getNameBuilder()
+		defer putNameBuilder(nb)
 		metas := make([]methodMeta, len(ts))
 		for i := range ts {
 			t := &ts[i]
diff --git a/reflect/prototype/protofile_type.go b/reflect/prototype/protofile_type.go
index 0cb251f..773fc58 100644
--- a/reflect/prototype/protofile_type.go
+++ b/reflect/prototype/protofile_type.go
@@ -578,33 +578,3 @@
 	}
 	return cur
 }
-
-var nameBuilderPool = sync.Pool{
-	New: func() interface{} { return new(nameBuilder) },
-}
-
-type nameBuilder struct {
-	sb strings.Builder
-
-	// TODO: See https://golang.org/issue/26269
-	rem int // conservative approximation for Cap-Len
-}
-
-// Append is equivalent to protoreflect.FullName.Append, but is optimized for
-// large batches of operations where each name has a shared lifetime.
-func (b *nameBuilder) Append(prefix pref.FullName, name pref.Name) pref.FullName {
-	const batchSize = 1 << 12
-	n := len(prefix) + len(".") + len(name)
-	if b.rem < n {
-		b.sb.Reset()
-		b.sb.Grow(batchSize)
-		b.rem = batchSize - n
-	}
-	if !strings.HasSuffix(b.sb.String(), string(prefix)) {
-		b.sb.WriteString(string(prefix))
-	}
-	b.sb.WriteByte('.')
-	b.sb.WriteString(string(name))
-	s := b.sb.String()
-	return pref.FullName(strings.TrimPrefix(s[len(s)-n:], "."))
-}
diff --git a/test.bash b/test.bash
index 23fcbdc..f3a4d5c 100755
--- a/test.bash
+++ b/test.bash
@@ -38,7 +38,7 @@
 
 # Download each Go toolchain version.
 GO_LATEST=go1.11
-GO_VERSIONS=(go1.10.3 $GO_LATEST)
+GO_VERSIONS=(go1.9.7 go1.10.3 $GO_LATEST)
 for GO_VERSION in ${GO_VERSIONS[@]}; do
 	if [ ! -d $GO_VERSION ]; then
 		print "download $GO_VERSION"