internal/impl: move legacy files into impl

The internal/legacy package was originally separated out from internal/impl
to avoid a cyclic dependency on descriptor proto. However, the dependency
that legacy has on descriptor has long been dropped such that we can
now merge the two packages together again.

All legacy related logic are in a file with a legacy prefix.

Change-Id: I2424fc0f50721696ad06fa7cebb9bdd0babea13c
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/178542
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/impl/export.go b/internal/impl/api_export.go
similarity index 79%
rename from internal/impl/export.go
rename to internal/impl/api_export.go
index 8c7c121..d031d17 100644
--- a/internal/impl/export.go
+++ b/internal/impl/api_export.go
@@ -11,6 +11,7 @@
 	"google.golang.org/protobuf/encoding/prototext"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/reflect/prototype"
+	piface "google.golang.org/protobuf/runtime/protoiface"
 )
 
 // Export is a zero-length named type that exists only to export a set of
@@ -26,7 +27,7 @@
 	if ev, ok := e.(pref.Enum); ok {
 		return ev
 	}
-	return legacyWrapper.EnumOf(e)
+	return legacyWrapEnum(reflect.ValueOf(e))
 }
 
 // EnumTypeOf returns the protoreflect.EnumType for e.
@@ -39,7 +40,7 @@
 			},
 		}
 	}
-	return legacyWrapper.EnumTypeOf(e)
+	return legacyLoadEnumType(reflect.TypeOf(e))
 }
 
 // EnumDescriptorOf returns the protoreflect.EnumDescriptor for e.
@@ -47,7 +48,7 @@
 	if ev, ok := e.(pref.Enum); ok {
 		return ev.Descriptor()
 	}
-	return legacyWrapper.EnumDescriptorOf(e)
+	return LegacyLoadEnumDesc(reflect.TypeOf(e))
 }
 
 // EnumStringOf returns the enum value as a string, either as the name if
@@ -69,7 +70,7 @@
 	if mv, ok := m.(pref.ProtoMessage); ok {
 		return mv.ProtoReflect()
 	}
-	return legacyWrapper.MessageOf(m)
+	return legacyWrapMessage(reflect.ValueOf(m)).ProtoReflect()
 }
 
 // MessageTypeOf returns the protoreflect.MessageType for m.
@@ -82,7 +83,7 @@
 			},
 		}
 	}
-	return legacyWrapper.MessageTypeOf(m)
+	return legacyLoadMessageInfo(reflect.TypeOf(m)).PBType
 }
 
 // MessageDescriptorOf returns the protoreflect.MessageDescriptor for m.
@@ -90,7 +91,7 @@
 	if mv, ok := m.(pref.ProtoMessage); ok {
 		return mv.ProtoReflect().Descriptor()
 	}
-	return legacyWrapper.MessageDescriptorOf(m)
+	return LegacyLoadMessageDesc(reflect.TypeOf(m))
 }
 
 // MessageStringOf returns the message value as a string,
@@ -99,3 +100,13 @@
 	b, _ := prototext.MarshalOptions{AllowPartial: true}.Marshal(m)
 	return string(b)
 }
+
+// ExtensionDescFromType returns the legacy protoiface.ExtensionDescV1 for t.
+func (Export) ExtensionDescFromType(t pref.ExtensionType) *piface.ExtensionDescV1 {
+	return legacyExtensionDescFromType(t)
+}
+
+// ExtensionTypeFromDesc returns the v2 protoreflect.ExtensionType for d.
+func (Export) ExtensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType {
+	return legacyExtensionTypeFromDesc(d)
+}
diff --git a/internal/impl/encode_field.go b/internal/impl/encode_field.go
index 2e4884d..53a8eda 100644
--- a/internal/impl/encode_field.go
+++ b/internal/impl/encode_field.go
@@ -82,11 +82,11 @@
 	} else {
 		return pointerCoderFuncs{
 			size: func(p pointer, tagsize int, opts marshalOptions) int {
-				m := legacyWrapper.MessageOf(p.AsValueOf(ft).Elem().Interface()).Interface()
+				m := legacyWrapMessage(p.AsValueOf(ft).Elem())
 				return sizeMessage(m, tagsize, opts)
 			},
 			marshal: func(b []byte, p pointer, wiretag uint64, opts marshalOptions) ([]byte, error) {
-				m := legacyWrapper.MessageOf(p.AsValueOf(ft).Elem().Interface()).Interface()
+				m := legacyWrapMessage(p.AsValueOf(ft).Elem())
 				return appendMessage(b, m, wiretag, opts)
 			},
 		}
@@ -141,11 +141,11 @@
 	} else {
 		return pointerCoderFuncs{
 			size: func(p pointer, tagsize int, opts marshalOptions) int {
-				m := legacyWrapper.MessageOf(p.AsValueOf(ft).Elem().Interface()).Interface()
+				m := legacyWrapMessage(p.AsValueOf(ft).Elem())
 				return sizeGroup(m, tagsize, opts)
 			},
 			marshal: func(b []byte, p pointer, wiretag uint64, opts marshalOptions) ([]byte, error) {
-				m := legacyWrapper.MessageOf(p.AsValueOf(ft).Elem().Interface()).Interface()
+				m := legacyWrapMessage(p.AsValueOf(ft).Elem())
 				return appendGroup(b, m, wiretag, opts)
 			},
 		}
diff --git a/internal/legacy/enum.go b/internal/impl/legacy_enum.go
similarity index 71%
rename from internal/legacy/enum.go
rename to internal/impl/legacy_enum.go
index d38aa8c..7aa8b84 100644
--- a/internal/legacy/enum.go
+++ b/internal/impl/legacy_enum.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package legacy
+package impl
 
 import (
 	"fmt"
@@ -16,85 +16,85 @@
 	"google.golang.org/protobuf/reflect/prototype"
 )
 
-// wrapEnum wraps v as a protoreflect.Enum,
+// legacyWrapEnum wraps v as a protoreflect.Enum,
 // where v must be a int32 kind and not implement the v2 API already.
-func wrapEnum(v reflect.Value) pref.Enum {
-	et := loadEnumType(v.Type())
+func legacyWrapEnum(v reflect.Value) pref.Enum {
+	et := legacyLoadEnumType(v.Type())
 	return et.New(pref.EnumNumber(v.Int()))
 }
 
-var enumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType
+var legacyEnumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType
 
-// loadEnumType dynamically loads a protoreflect.EnumType for t,
+// legacyLoadEnumType dynamically loads a protoreflect.EnumType for t,
 // where t must be an int32 kind and not implement the v2 API already.
-func loadEnumType(t reflect.Type) pref.EnumType {
+func legacyLoadEnumType(t reflect.Type) pref.EnumType {
 	// Fast-path: check if a EnumType is cached for this concrete type.
-	if et, ok := enumTypeCache.Load(t); ok {
+	if et, ok := legacyEnumTypeCache.Load(t); ok {
 		return et.(pref.EnumType)
 	}
 
 	// Slow-path: derive enum descriptor and initialize EnumType.
 	var et pref.EnumType
 	var m sync.Map // map[protoreflect.EnumNumber]proto.Enum
-	ed := LoadEnumDesc(t)
+	ed := LegacyLoadEnumDesc(t)
 	et = &prototype.Enum{
 		EnumDescriptor: ed,
 		NewEnum: func(n pref.EnumNumber) pref.Enum {
 			if e, ok := m.Load(n); ok {
 				return e.(pref.Enum)
 			}
-			e := &enumWrapper{num: n, pbTyp: et, goTyp: t}
+			e := &legacyEnumWrapper{num: n, pbTyp: et, goTyp: t}
 			m.Store(n, e)
 			return e
 		},
 	}
-	if et, ok := enumTypeCache.LoadOrStore(t, et); ok {
+	if et, ok := legacyEnumTypeCache.LoadOrStore(t, et); ok {
 		return et.(pref.EnumType)
 	}
 	return et
 }
 
-type enumWrapper struct {
+type legacyEnumWrapper struct {
 	num   pref.EnumNumber
 	pbTyp pref.EnumType
 	goTyp reflect.Type
 }
 
 // TODO: Remove this.
-func (e *enumWrapper) Type() pref.EnumType {
+func (e *legacyEnumWrapper) Type() pref.EnumType {
 	return e.pbTyp
 }
-func (e *enumWrapper) Descriptor() pref.EnumDescriptor {
+func (e *legacyEnumWrapper) Descriptor() pref.EnumDescriptor {
 	return e.pbTyp.Descriptor()
 }
-func (e *enumWrapper) Number() pref.EnumNumber {
+func (e *legacyEnumWrapper) Number() pref.EnumNumber {
 	return e.num
 }
-func (e *enumWrapper) ProtoReflect() pref.Enum {
+func (e *legacyEnumWrapper) ProtoReflect() pref.Enum {
 	return e
 }
-func (e *enumWrapper) ProtoUnwrap() interface{} {
+func (e *legacyEnumWrapper) ProtoUnwrap() interface{} {
 	v := reflect.New(e.goTyp).Elem()
 	v.SetInt(int64(e.num))
 	return v.Interface()
 }
 
 var (
-	_ pref.Enum        = (*enumWrapper)(nil)
-	_ pvalue.Unwrapper = (*enumWrapper)(nil)
+	_ pref.Enum        = (*legacyEnumWrapper)(nil)
+	_ pvalue.Unwrapper = (*legacyEnumWrapper)(nil)
 )
 
-var enumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
+var legacyEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
 
-var enumNumberType = reflect.TypeOf(pref.EnumNumber(0))
+var legacyEnumNumberType = reflect.TypeOf(pref.EnumNumber(0))
 
-// LoadEnumDesc returns an EnumDescriptor derived from the Go type,
+// LegacyLoadEnumDesc returns an EnumDescriptor derived from the Go type,
 // which must be an int32 kind and not implement the v2 API already.
 //
 // This is exported for testing purposes.
-func LoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
+func LegacyLoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
 	// Fast-path: check if an EnumDescriptor is cached for this concrete type.
-	if ed, ok := enumDescCache.Load(t); ok {
+	if ed, ok := legacyEnumDescCache.Load(t); ok {
 		return ed.(pref.EnumDescriptor)
 	}
 
@@ -102,7 +102,7 @@
 	if t.Kind() != reflect.Int32 || t.PkgPath() == "" {
 		panic(fmt.Sprintf("got %v, want named int32 kind", t))
 	}
-	if t == enumNumberType {
+	if t == legacyEnumNumberType {
 		panic(fmt.Sprintf("cannot be %v", t))
 	}
 
@@ -114,7 +114,7 @@
 	}
 	if ed, ok := ev.(enumV1); ok {
 		b, idxs := ed.EnumDescriptor()
-		fd := loadFileDesc(b)
+		fd := legacyLoadFileDesc(b)
 
 		// Derive syntax.
 		switch fd.GetSyntax() {
@@ -125,7 +125,7 @@
 		}
 
 		// Derive the full name and correct enum descriptor.
-		var ed *enumDescriptorProto
+		var ed *legacyEnumDescriptorProto
 		e.FullName = pref.FullName(fd.GetPackage())
 		if len(idxs) == 1 {
 			ed = fd.EnumType[idxs[0]]
@@ -160,7 +160,7 @@
 		// most operations continue to work. For example, prototext and protojson
 		// will be unable to parse a message with an enum value by name.
 		e.Syntax = pref.Proto2
-		e.FullName = deriveFullName(t)
+		e.FullName = legacyDeriveFullName(t)
 		e.Values = []ptype.EnumValue{{Name: "INVALID", Number: math.MinInt32}}
 	}
 
@@ -168,7 +168,7 @@
 	if err != nil {
 		panic(err)
 	}
-	if ed, ok := enumDescCache.LoadOrStore(t, ed); ok {
+	if ed, ok := legacyEnumDescCache.LoadOrStore(t, ed); ok {
 		return ed.(pref.EnumDescriptor)
 	}
 	return ed
diff --git a/internal/legacy/extension.go b/internal/impl/legacy_extension.go
similarity index 77%
rename from internal/legacy/extension.go
rename to internal/impl/legacy_extension.go
index c8cb3e9..33ca82c 100644
--- a/internal/legacy/extension.go
+++ b/internal/impl/legacy_extension.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package legacy
+package impl
 
 import (
 	"fmt"
@@ -11,7 +11,6 @@
 
 	"google.golang.org/protobuf/internal/descfmt"
 	ptag "google.golang.org/protobuf/internal/encoding/tag"
-	pimpl "google.golang.org/protobuf/internal/impl"
 	ptype "google.golang.org/protobuf/internal/prototype"
 	pvalue "google.golang.org/protobuf/internal/value"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
@@ -20,9 +19,9 @@
 	piface "google.golang.org/protobuf/runtime/protoiface"
 )
 
-// extensionDescKey is a comparable version of protoiface.ExtensionDescV1
+// legacyExtensionDescKey is a comparable version of protoiface.ExtensionDescV1
 // suitable for use as a key in a map.
-type extensionDescKey struct {
+type legacyExtensionDescKey struct {
 	typeV2        pref.ExtensionType
 	extendedType  reflect.Type
 	extensionType reflect.Type
@@ -32,8 +31,8 @@
 	filename      string
 }
 
-func extensionDescKeyOf(d *piface.ExtensionDescV1) extensionDescKey {
-	return extensionDescKey{
+func legacyExtensionDescKeyOf(d *piface.ExtensionDescV1) legacyExtensionDescKey {
+	return legacyExtensionDescKey{
 		d.Type,
 		reflect.TypeOf(d.ExtendedType),
 		reflect.TypeOf(d.ExtensionType),
@@ -42,13 +41,13 @@
 }
 
 var (
-	extensionTypeCache sync.Map // map[extensionDescKey]protoreflect.ExtensionType
-	extensionDescCache sync.Map // map[protoreflect.ExtensionType]*protoiface.ExtensionDescV1
+	legacyExtensionTypeCache sync.Map // map[legacyExtensionDescKey]protoreflect.ExtensionType
+	legacyExtensionDescCache sync.Map // map[protoreflect.ExtensionType]*protoiface.ExtensionDescV1
 )
 
-// extensionDescFromType converts a v2 protoreflect.ExtensionType to a
+// legacyExtensionDescFromType converts a v2 protoreflect.ExtensionType to a
 // protoiface.ExtensionDescV1. The returned ExtensionDesc must not be mutated.
-func extensionDescFromType(xt pref.ExtensionType) *piface.ExtensionDescV1 {
+func legacyExtensionDescFromType(xt pref.ExtensionType) *piface.ExtensionDescV1 {
 	// Fast-path: check whether an extension desc is already nested within.
 	if xt, ok := xt.(interface {
 		ProtoLegacyExtensionDesc() *piface.ExtensionDescV1
@@ -60,7 +59,7 @@
 
 	// Fast-path: check the cache for whether this ExtensionType has already
 	// been converted to a legacy descriptor.
-	if d, ok := extensionDescCache.Load(xt); ok {
+	if d, ok := legacyExtensionDescCache.Load(xt); ok {
 		return d.(*piface.ExtensionDescV1)
 	}
 
@@ -113,7 +112,7 @@
 		}
 		if ed, ok := reflect.Zero(t).Interface().(enumV1); ok && protoPkg == "" {
 			b, _ := ed.EnumDescriptor()
-			protoPkg = loadFileDesc(b).GetPackage()
+			protoPkg = legacyLoadFileDesc(b).GetPackage()
 		}
 
 		if protoPkg != "" {
@@ -137,17 +136,17 @@
 		Tag:           ptag.Marshal(xt.Descriptor(), enumName),
 		Filename:      filename,
 	}
-	if d, ok := extensionDescCache.LoadOrStore(xt, d); ok {
+	if d, ok := legacyExtensionDescCache.LoadOrStore(xt, d); ok {
 		return d.(*piface.ExtensionDescV1)
 	}
 	return d
 }
 
-// extensionTypeFromDesc converts a protoiface.ExtensionDescV1 to a
+// legacyExtensionTypeFromDesc converts a protoiface.ExtensionDescV1 to a
 // v2 protoreflect.ExtensionType. The returned descriptor type takes ownership
 // of the input extension desc. The input must not be mutated so long as the
 // returned type is still in use.
-func extensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType {
+func legacyExtensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType {
 	// Fast-path: check whether an extension type is already nested within.
 	if d.Type != nil {
 		return d.Type
@@ -155,8 +154,8 @@
 
 	// Fast-path: check the cache for whether this ExtensionType has already
 	// been converted from a legacy descriptor.
-	dk := extensionDescKeyOf(d)
-	if t, ok := extensionTypeCache.Load(dk); ok {
+	dk := legacyExtensionDescKeyOf(d)
+	if t, ok := legacyExtensionTypeCache.Load(dk); ok {
 		return t.(pref.ExtensionType)
 	}
 
@@ -177,13 +176,13 @@
 		if e, ok := reflect.Zero(t).Interface().(pref.Enum); ok {
 			ed = e.Descriptor()
 		} else {
-			ed = LoadEnumDesc(t)
+			ed = LegacyLoadEnumDesc(t)
 		}
 	case pref.MessageKind, pref.GroupKind:
 		if m, ok := reflect.Zero(t).Interface().(pref.ProtoMessage); ok {
 			md = m.ProtoReflect().Descriptor()
 		} else {
-			md = LoadMessageDesc(t)
+			md = LegacyLoadMessageDesc(t)
 		}
 	}
 	xd, err := ptype.NewExtension(&ptype.StandaloneExtension{
@@ -195,26 +194,27 @@
 		Options:      f.Options,
 		EnumType:     ed,
 		MessageType:  md,
-		ExtendedType: pimpl.Export{}.MessageDescriptorOf(d.ExtendedType),
+		ExtendedType: Export{}.MessageDescriptorOf(d.ExtendedType),
 	})
 	if err != nil {
 		panic(err)
 	}
-	xt := ExtensionTypeOf(xd, t)
+	xt := LegacyExtensionTypeOf(xd, t)
 
 	// Cache the conversion for both directions.
-	extensionDescCache.LoadOrStore(xt, d)
-	if xt, ok := extensionTypeCache.LoadOrStore(dk, xt); ok {
+	legacyExtensionDescCache.LoadOrStore(xt, d)
+	if xt, ok := legacyExtensionTypeCache.LoadOrStore(dk, xt); ok {
 		return xt.(pref.ExtensionType)
 	}
 	return xt
 }
 
-// ExtensionTypeOf returns a protoreflect.ExtensionType where the type of the
-// field is t. The type t must be provided if the field is an enum or message.
+// LegacyExtensionTypeOf returns a protoreflect.ExtensionType where the
+// element type of the field is t. The type t must be provided if the field
+// is an enum or message.
 //
 // This is exported for testing purposes.
-func ExtensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType {
+func LegacyExtensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType {
 	var conv pvalue.Converter
 	var isLegacy bool
 	xt := &prototype.Extension{ExtensionDescriptor: xd}
@@ -234,7 +234,7 @@
 	}
 
 	// Wrap ExtensionType such that GoType presents the legacy Go type.
-	xt2 := &extensionType{ExtensionType: xt}
+	xt2 := &legacyExtensionType{ExtensionType: xt}
 	if xd.Cardinality() != pref.Repeated {
 		xt2.typ = t
 		xt2.new = func() pref.Value {
@@ -276,7 +276,7 @@
 	return xt2
 }
 
-type extensionType struct {
+type legacyExtensionType struct {
 	pref.ExtensionType
 	typ         reflect.Type
 	new         func() pref.Value
@@ -284,8 +284,8 @@
 	interfaceOf func(pref.Value) interface{}
 }
 
-func (x *extensionType) GoType() reflect.Type                 { return x.typ }
-func (x *extensionType) New() pref.Value                      { return x.new() }
-func (x *extensionType) ValueOf(v interface{}) pref.Value     { return x.valueOf(v) }
-func (x *extensionType) InterfaceOf(v pref.Value) interface{} { return x.interfaceOf(v) }
-func (x *extensionType) Format(s fmt.State, r rune)           { descfmt.FormatDesc(s, r, x.Descriptor()) }
+func (x *legacyExtensionType) GoType() reflect.Type                 { return x.typ }
+func (x *legacyExtensionType) New() pref.Value                      { return x.new() }
+func (x *legacyExtensionType) ValueOf(v interface{}) pref.Value     { return x.valueOf(v) }
+func (x *legacyExtensionType) InterfaceOf(v pref.Value) interface{} { return x.interfaceOf(v) }
+func (x *legacyExtensionType) Format(s fmt.State, r rune)           { descfmt.FormatDesc(s, r, x.Descriptor()) }
diff --git a/internal/legacy/extension_test.go b/internal/impl/legacy_extension_test.go
similarity index 64%
rename from internal/legacy/extension_test.go
rename to internal/impl/legacy_extension_test.go
index c9bc9d4..13a2c6d 100644
--- a/internal/legacy/extension_test.go
+++ b/internal/impl/legacy_extension_test.go
@@ -2,14 +2,13 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package legacy_test
+package impl_test
 
 import (
 	"reflect"
 	"testing"
 
 	pimpl "google.golang.org/protobuf/internal/impl"
-	plegacy "google.golang.org/protobuf/internal/legacy"
 	ptype "google.golang.org/protobuf/internal/prototype"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
 	piface "google.golang.org/protobuf/runtime/protoiface"
@@ -17,15 +16,15 @@
 	proto2_20180125 "google.golang.org/protobuf/internal/testprotos/legacy/proto2.v1.0.0-20180125-92554152"
 )
 
-type legacyTestMessage struct {
+type legacyExtendedMessage struct {
 	XXX_unrecognized       []byte
 	XXX_InternalExtensions map[int32]pimpl.ExtensionFieldV1
 }
 
-func (*legacyTestMessage) Reset()         {}
-func (*legacyTestMessage) String() string { return "" }
-func (*legacyTestMessage) ProtoMessage()  {}
-func (*legacyTestMessage) ExtensionRangeArray() []piface.ExtensionRangeV1 {
+func (*legacyExtendedMessage) Reset()         {}
+func (*legacyExtendedMessage) String() string { return "" }
+func (*legacyExtendedMessage) ProtoMessage()  {}
+func (*legacyExtendedMessage) ExtensionRangeArray() []piface.ExtensionRangeV1 {
 	return []piface.ExtensionRangeV1{{Start: 10000, End: 20000}}
 }
 
@@ -34,23 +33,23 @@
 	if err != nil {
 		panic(err)
 	}
-	return plegacy.ExtensionTypeOf(xd, reflect.TypeOf(v))
+	return pimpl.LegacyExtensionTypeOf(xd, reflect.TypeOf(v))
 }
 
 var (
-	parentDesc    = pimpl.Export{}.MessageDescriptorOf((*legacyTestMessage)(nil))
-	messageV1Desc = pimpl.Export{}.MessageDescriptorOf((*proto2_20180125.Message_ChildMessage)(nil))
+	extParentDesc    = pimpl.Export{}.MessageDescriptorOf((*legacyExtendedMessage)(nil))
+	extMessageV1Desc = pimpl.Export{}.MessageDescriptorOf((*proto2_20180125.Message_ChildMessage)(nil))
 
 	wantType = mustMakeExtensionType(&ptype.StandaloneExtension{
 		FullName:     "fizz.buzz.optional_message_v1",
 		Number:       10007,
 		Cardinality:  pref.Optional,
 		Kind:         pref.MessageKind,
-		MessageType:  messageV1Desc,
-		ExtendedType: parentDesc,
+		MessageType:  extMessageV1Desc,
+		ExtendedType: extParentDesc,
 	}, (*proto2_20180125.Message_ChildMessage)(nil))
 	wantDesc = &piface.ExtensionDescV1{
-		ExtendedType:  (*legacyTestMessage)(nil),
+		ExtendedType:  (*legacyExtendedMessage)(nil),
 		ExtensionType: (*proto2_20180125.Message_ChildMessage)(nil),
 		Field:         10007,
 		Name:          "fizz.buzz.optional_message_v1",
@@ -61,14 +60,14 @@
 func BenchmarkConvert(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
-		xd := plegacy.Export{}.ExtensionDescFromType(wantType)
-		gotType := plegacy.Export{}.ExtensionTypeFromDesc(xd)
+		xd := pimpl.Export{}.ExtensionDescFromType(wantType)
+		gotType := pimpl.Export{}.ExtensionTypeFromDesc(xd)
 		if gotType != wantType {
 			b.Fatalf("ExtensionType mismatch: got %p, want %p", gotType, wantType)
 		}
 
-		xt := plegacy.Export{}.ExtensionTypeFromDesc(wantDesc)
-		gotDesc := plegacy.Export{}.ExtensionDescFromType(xt)
+		xt := pimpl.Export{}.ExtensionTypeFromDesc(wantDesc)
+		gotDesc := pimpl.Export{}.ExtensionDescFromType(xt)
 		if gotDesc != wantDesc {
 			b.Fatalf("ExtensionDesc mismatch: got %p, want %p", gotDesc, wantDesc)
 		}
diff --git a/internal/legacy/file.go b/internal/impl/legacy_file.go
similarity index 74%
rename from internal/legacy/file.go
rename to internal/impl/legacy_file.go
index e98508b..ddf33f7 100644
--- a/internal/legacy/file.go
+++ b/internal/impl/legacy_file.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package legacy
+package impl
 
 import (
 	"bytes"
@@ -28,18 +28,18 @@
 	}
 )
 
-var fileDescCache sync.Map // map[*byte]*descriptorpb.FileDescriptorProto
+var legacyFileDescCache sync.Map // map[*byte]*descriptorpb.FileDescriptorProto
 
-// loadFileDesc unmarshals b as a compressed FileDescriptorProto message.
+// legacyLoadFileDesc unmarshals b as a compressed FileDescriptorProto message.
 //
 // This assumes that b is immutable and that b does not refer to part of a
 // concatenated series of GZIP files (which would require shenanigans that
 // rely on the concatenation properties of both protobufs and GZIP).
 // File descriptors generated by protoc-gen-go do not rely on that property.
-func loadFileDesc(b []byte) *fileDescriptorProto {
+func legacyLoadFileDesc(b []byte) *legacyFileDescriptorProto {
 	// Fast-path: check whether we already have a cached file descriptor.
-	if fd, ok := fileDescCache.Load(&b[0]); ok {
-		return fd.(*fileDescriptorProto)
+	if fd, ok := legacyFileDescCache.Load(&b[0]); ok {
+		return fd.(*legacyFileDescriptorProto)
 	}
 
 	// Slow-path: decompress and unmarshal the file descriptor proto.
@@ -51,9 +51,9 @@
 	if err != nil {
 		panic(err)
 	}
-	fd := parseFileDescProto(b)
-	if fd, ok := fileDescCache.LoadOrStore(&b[0], fd); ok {
-		return fd.(*fileDescriptorProto)
+	fd := legacyParseFileDescProto(b)
+	if fd, ok := legacyFileDescCache.LoadOrStore(&b[0], fd); ok {
+		return fd.(*legacyFileDescriptorProto)
 	}
 	return fd
 }
diff --git a/internal/legacy/file_test.go b/internal/impl/legacy_file_test.go
similarity index 61%
rename from internal/legacy/file_test.go
rename to internal/impl/legacy_file_test.go
index ac1a7b0..d4c5890 100644
--- a/internal/legacy/file_test.go
+++ b/internal/impl/legacy_file_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package legacy_test
+package impl_test
 
 import (
 	"bytes"
@@ -11,9 +11,9 @@
 	"reflect"
 	"testing"
 
-	cmp "github.com/google/go-cmp/cmp"
-	legacy "google.golang.org/protobuf/internal/legacy"
-	pragma "google.golang.org/protobuf/internal/pragma"
+	"github.com/google/go-cmp/cmp"
+	"google.golang.org/protobuf/internal/impl"
+	"google.golang.org/protobuf/internal/pragma"
 	"google.golang.org/protobuf/proto"
 	pdesc "google.golang.org/protobuf/reflect/protodesc"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
@@ -59,343 +59,343 @@
 
 	fileDescP2_20160225 := mustLoadFileDesc(new(proto2_20160225.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20160225.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20160225.SiblingEnum(0))),
 		want: fileDescP2_20160225.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20160225.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20160225.Message_ChildEnum(0))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.SiblingMessage))),
 		want: fileDescP2_20160225.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_ChildMessage))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message))),
 		want: fileDescP2_20160225.Messages().ByName("Message"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_NamedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_NamedGroup))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Messages().ByName("NamedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_OptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_OptionalGroup))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Messages().ByName("OptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_RequiredGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_RequiredGroup))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Messages().ByName("RequiredGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_RepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_RepeatedGroup))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Messages().ByName("RepeatedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_OneofGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_OneofGroup))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Messages().ByName("OneofGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_ExtensionOptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_ExtensionOptionalGroup))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Messages().ByName("ExtensionOptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_ExtensionRepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160225.Message_ExtensionRepeatedGroup))),
 		want: fileDescP2_20160225.Messages().ByName("Message").Messages().ByName("ExtensionRepeatedGroup"),
 	}}...)
 
 	fileDescP3_20160225 := mustLoadFileDesc(new(proto3_20160225.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20160225.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20160225.SiblingEnum(0))),
 		want: fileDescP3_20160225.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20160225.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20160225.Message_ChildEnum(0))),
 		want: fileDescP3_20160225.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20160225.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20160225.SiblingMessage))),
 		want: fileDescP3_20160225.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20160225.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20160225.Message_ChildMessage))),
 		want: fileDescP3_20160225.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20160225.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20160225.Message))),
 		want: fileDescP3_20160225.Messages().ByName("Message"),
 	}}...)
 
 	fileDescP2_20160519 := mustLoadFileDesc(new(proto2_20160519.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20160519.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20160519.SiblingEnum(0))),
 		want: fileDescP2_20160519.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20160519.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20160519.Message_ChildEnum(0))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.SiblingMessage))),
 		want: fileDescP2_20160519.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_ChildMessage))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message))),
 		want: fileDescP2_20160519.Messages().ByName("Message"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_NamedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_NamedGroup))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Messages().ByName("NamedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_OptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_OptionalGroup))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Messages().ByName("OptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_RequiredGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_RequiredGroup))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Messages().ByName("RequiredGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_RepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_RepeatedGroup))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Messages().ByName("RepeatedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_OneofGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_OneofGroup))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Messages().ByName("OneofGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_ExtensionOptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_ExtensionOptionalGroup))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Messages().ByName("ExtensionOptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_ExtensionRepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20160519.Message_ExtensionRepeatedGroup))),
 		want: fileDescP2_20160519.Messages().ByName("Message").Messages().ByName("ExtensionRepeatedGroup"),
 	}}...)
 
 	fileDescP3_20160519 := mustLoadFileDesc(new(proto3_20160519.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20160519.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20160519.SiblingEnum(0))),
 		want: fileDescP3_20160519.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20160519.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20160519.Message_ChildEnum(0))),
 		want: fileDescP3_20160519.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20160519.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20160519.SiblingMessage))),
 		want: fileDescP3_20160519.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20160519.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20160519.Message_ChildMessage))),
 		want: fileDescP3_20160519.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20160519.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20160519.Message))),
 		want: fileDescP3_20160519.Messages().ByName("Message"),
 	}}...)
 
 	fileDescP2_20180125 := mustLoadFileDesc(new(proto2_20180125.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20180125.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20180125.SiblingEnum(0))),
 		want: fileDescP2_20180125.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20180125.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20180125.Message_ChildEnum(0))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.SiblingMessage))),
 		want: fileDescP2_20180125.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_ChildMessage))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message))),
 		want: fileDescP2_20180125.Messages().ByName("Message"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_NamedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_NamedGroup))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Messages().ByName("NamedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_OptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_OptionalGroup))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Messages().ByName("OptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_RequiredGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_RequiredGroup))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Messages().ByName("RequiredGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_RepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_RepeatedGroup))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Messages().ByName("RepeatedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_OneofGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_OneofGroup))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Messages().ByName("OneofGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_ExtensionOptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_ExtensionOptionalGroup))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Messages().ByName("ExtensionOptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_ExtensionRepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180125.Message_ExtensionRepeatedGroup))),
 		want: fileDescP2_20180125.Messages().ByName("Message").Messages().ByName("ExtensionRepeatedGroup"),
 	}}...)
 
 	fileDescP3_20180125 := mustLoadFileDesc(new(proto3_20180125.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20180125.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20180125.SiblingEnum(0))),
 		want: fileDescP3_20180125.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20180125.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20180125.Message_ChildEnum(0))),
 		want: fileDescP3_20180125.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180125.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180125.SiblingMessage))),
 		want: fileDescP3_20180125.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180125.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180125.Message_ChildMessage))),
 		want: fileDescP3_20180125.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180125.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180125.Message))),
 		want: fileDescP3_20180125.Messages().ByName("Message"),
 	}}...)
 
 	fileDescP2_20180430 := mustLoadFileDesc(new(proto2_20180430.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20180430.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20180430.SiblingEnum(0))),
 		want: fileDescP2_20180430.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20180430.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20180430.Message_ChildEnum(0))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.SiblingMessage))),
 		want: fileDescP2_20180430.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_ChildMessage))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message))),
 		want: fileDescP2_20180430.Messages().ByName("Message"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_NamedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_NamedGroup))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Messages().ByName("NamedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_OptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_OptionalGroup))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Messages().ByName("OptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_RequiredGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_RequiredGroup))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Messages().ByName("RequiredGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_RepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_RepeatedGroup))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Messages().ByName("RepeatedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_OneofGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_OneofGroup))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Messages().ByName("OneofGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_ExtensionOptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_ExtensionOptionalGroup))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Messages().ByName("ExtensionOptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_ExtensionRepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180430.Message_ExtensionRepeatedGroup))),
 		want: fileDescP2_20180430.Messages().ByName("Message").Messages().ByName("ExtensionRepeatedGroup"),
 	}}...)
 
 	fileDescP3_20180430 := mustLoadFileDesc(new(proto3_20180430.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20180430.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20180430.SiblingEnum(0))),
 		want: fileDescP3_20180430.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20180430.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20180430.Message_ChildEnum(0))),
 		want: fileDescP3_20180430.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180430.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180430.SiblingMessage))),
 		want: fileDescP3_20180430.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180430.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180430.Message_ChildMessage))),
 		want: fileDescP3_20180430.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180430.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180430.Message))),
 		want: fileDescP3_20180430.Messages().ByName("Message"),
 	}}...)
 
 	fileDescP2_20180814 := mustLoadFileDesc(new(proto2_20180814.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20180814.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20180814.SiblingEnum(0))),
 		want: fileDescP2_20180814.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20180814.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20180814.Message_ChildEnum(0))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.SiblingMessage))),
 		want: fileDescP2_20180814.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_ChildMessage))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message))),
 		want: fileDescP2_20180814.Messages().ByName("Message"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_NamedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_NamedGroup))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Messages().ByName("NamedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_OptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_OptionalGroup))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Messages().ByName("OptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_RequiredGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_RequiredGroup))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Messages().ByName("RequiredGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_RepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_RepeatedGroup))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Messages().ByName("RepeatedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_OneofGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_OneofGroup))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Messages().ByName("OneofGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_ExtensionOptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_ExtensionOptionalGroup))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Messages().ByName("ExtensionOptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_ExtensionRepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20180814.Message_ExtensionRepeatedGroup))),
 		want: fileDescP2_20180814.Messages().ByName("Message").Messages().ByName("ExtensionRepeatedGroup"),
 	}}...)
 
 	fileDescP3_20180814 := mustLoadFileDesc(new(proto3_20180814.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20180814.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20180814.SiblingEnum(0))),
 		want: fileDescP3_20180814.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20180814.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20180814.Message_ChildEnum(0))),
 		want: fileDescP3_20180814.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180814.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180814.SiblingMessage))),
 		want: fileDescP3_20180814.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180814.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180814.Message_ChildMessage))),
 		want: fileDescP3_20180814.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20180814.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20180814.Message))),
 		want: fileDescP3_20180814.Messages().ByName("Message"),
 	}}...)
 
 	fileDescP2_20181126 := mustLoadFileDesc(new(proto2_20181126.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20181126.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20181126.SiblingEnum(0))),
 		want: fileDescP2_20181126.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto2_20181126.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto2_20181126.Message_ChildEnum(0))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.SiblingMessage))),
 		want: fileDescP2_20181126.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_ChildMessage))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message))),
 		want: fileDescP2_20181126.Messages().ByName("Message"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_NamedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_NamedGroup))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Messages().ByName("NamedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_OptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_OptionalGroup))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Messages().ByName("OptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_RequiredGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_RequiredGroup))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Messages().ByName("RequiredGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_RepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_RepeatedGroup))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Messages().ByName("RepeatedGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_OneofGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_OneofGroup))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Messages().ByName("OneofGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_ExtensionOptionalGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_ExtensionOptionalGroup))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Messages().ByName("ExtensionOptionalGroup"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_ExtensionRepeatedGroup))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto2_20181126.Message_ExtensionRepeatedGroup))),
 		want: fileDescP2_20181126.Messages().ByName("Message").Messages().ByName("ExtensionRepeatedGroup"),
 	}}...)
 
 	fileDescP3_20181126 := mustLoadFileDesc(new(proto3_20181126.Message).Descriptor())
 	tests = append(tests, []struct{ got, want pref.Descriptor }{{
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20181126.SiblingEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20181126.SiblingEnum(0))),
 		want: fileDescP3_20181126.Enums().ByName("SiblingEnum"),
 	}, {
-		got:  legacy.LoadEnumDesc(reflect.TypeOf(proto3_20181126.Message_ChildEnum(0))),
+		got:  impl.LegacyLoadEnumDesc(reflect.TypeOf(proto3_20181126.Message_ChildEnum(0))),
 		want: fileDescP3_20181126.Messages().ByName("Message").Enums().ByName("ChildEnum"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20181126.SiblingMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20181126.SiblingMessage))),
 		want: fileDescP3_20181126.Messages().ByName("SiblingMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20181126.Message_ChildMessage))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20181126.Message_ChildMessage))),
 		want: fileDescP3_20181126.Messages().ByName("Message").Messages().ByName("ChildMessage"),
 	}, {
-		got:  legacy.LoadMessageDesc(reflect.TypeOf(new(proto3_20181126.Message))),
+		got:  impl.LegacyLoadMessageDesc(reflect.TypeOf(new(proto3_20181126.Message))),
 		want: fileDescP3_20181126.Messages().ByName("Message"),
 	}}...)
 
diff --git a/internal/impl/legacy_hook.go b/internal/impl/legacy_hook.go
deleted file mode 100644
index 98eaf2f..0000000
--- a/internal/impl/legacy_hook.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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 impl
-
-import (
-	"reflect"
-
-	pvalue "google.golang.org/protobuf/internal/value"
-	pref "google.golang.org/protobuf/reflect/protoreflect"
-	piface "google.golang.org/protobuf/runtime/protoiface"
-)
-
-// TODO: Add a default LegacyWrapper that panics with a more helpful message?
-var legacyWrapper LegacyWrapper
-
-// RegisterLegacyWrapper registers a set of constructor functions that are
-// called when a legacy enum or message is encountered that does not natively
-// support the protobuf reflection APIs.
-func RegisterLegacyWrapper(w LegacyWrapper) {
-	legacyWrapper = w
-}
-
-// LegacyWrapper is a set of wrapper methods that wraps legacy v1 Go types
-// to implement the v2 reflection APIs.
-type LegacyWrapper interface {
-	NewConverter(reflect.Type, pref.Kind) pvalue.Converter
-
-	EnumOf(interface{}) pref.Enum
-	EnumTypeOf(interface{}) pref.EnumType
-	EnumDescriptorOf(interface{}) pref.EnumDescriptor
-
-	MessageOf(interface{}) pref.Message
-	MessageTypeOf(interface{}) pref.MessageType
-	MessageDescriptorOf(interface{}) pref.MessageDescriptor
-
-	// TODO: Remove these eventually.
-	// See the TODOs in internal/impl/legacy_extension.go.
-	ExtensionDescFromType(pref.ExtensionType) *piface.ExtensionDescV1
-	ExtensionTypeFromDesc(*piface.ExtensionDescV1) pref.ExtensionType
-}
diff --git a/internal/legacy/message.go b/internal/impl/legacy_message.go
similarity index 80%
rename from internal/legacy/message.go
rename to internal/impl/legacy_message.go
index b17d705..c54cfc1 100644
--- a/internal/legacy/message.go
+++ b/internal/impl/legacy_message.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package legacy
+package impl
 
 import (
 	"fmt"
@@ -12,32 +12,31 @@
 	"unicode"
 
 	ptag "google.golang.org/protobuf/internal/encoding/tag"
-	pimpl "google.golang.org/protobuf/internal/impl"
 	ptype "google.golang.org/protobuf/internal/prototype"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/reflect/prototype"
 )
 
-// wrapMessage wraps v as a protoreflect.ProtoMessage,
+// legacyWrapMessage wraps v as a protoreflect.ProtoMessage,
 // where v must be a *struct kind and not implement the v2 API already.
-func wrapMessage(v reflect.Value) pref.ProtoMessage {
-	mt := loadMessageInfo(v.Type())
+func legacyWrapMessage(v reflect.Value) pref.ProtoMessage {
+	mt := legacyLoadMessageInfo(v.Type())
 	return mt.MessageOf(v.Interface()).Interface()
 }
 
-var messageTypeCache sync.Map // map[reflect.Type]*MessageInfo
+var legacyMessageTypeCache sync.Map // map[reflect.Type]*MessageInfo
 
-// loadMessageInfo dynamically loads a *MessageInfo for t,
+// legacyLoadMessageInfo dynamically loads a *MessageInfo for t,
 // where t must be a *struct kind and not implement the v2 API already.
-func loadMessageInfo(t reflect.Type) *pimpl.MessageInfo {
+func legacyLoadMessageInfo(t reflect.Type) *MessageInfo {
 	// Fast-path: check if a MessageInfo is cached for this concrete type.
-	if mt, ok := messageTypeCache.Load(t); ok {
-		return mt.(*pimpl.MessageInfo)
+	if mt, ok := legacyMessageTypeCache.Load(t); ok {
+		return mt.(*MessageInfo)
 	}
 
 	// Slow-path: derive message descriptor and initialize MessageInfo.
-	md := LoadMessageDesc(t)
-	mt := new(pimpl.MessageInfo)
+	md := LegacyLoadMessageDesc(t)
+	mt := new(MessageInfo)
 	mt.GoType = t
 	mt.PBType = &prototype.Message{
 		MessageDescriptor: md,
@@ -45,32 +44,34 @@
 			return mt.MessageOf(reflect.New(t.Elem()).Interface())
 		},
 	}
-	if mt, ok := messageTypeCache.LoadOrStore(t, mt); ok {
-		return mt.(*pimpl.MessageInfo)
+	if mt, ok := legacyMessageTypeCache.LoadOrStore(t, mt); ok {
+		return mt.(*MessageInfo)
 	}
 	return mt
 }
 
-var messageDescLock sync.Mutex
-var messageDescCache sync.Map // map[reflect.Type]protoreflect.MessageDescriptor
+var (
+	legacyMessageDescLock  sync.Mutex
+	legacyMessageDescCache sync.Map // map[reflect.Type]protoreflect.MessageDescriptor
+)
 
-// LoadMessageDesc returns an MessageDescriptor derived from the Go type,
+// LegacyLoadMessageDesc returns an MessageDescriptor derived from the Go type,
 // which must be a *struct kind and not implement the v2 API already.
 //
 // This is exported for testing purposes.
-func LoadMessageDesc(t reflect.Type) pref.MessageDescriptor {
-	return messageDescSet{}.Load(t)
+func LegacyLoadMessageDesc(t reflect.Type) pref.MessageDescriptor {
+	return legacyMessageDescSet{}.Load(t)
 }
 
-type messageDescSet struct {
+type legacyMessageDescSet struct {
 	visited map[reflect.Type]*ptype.StandaloneMessage
 	descs   []*ptype.StandaloneMessage
 	types   []reflect.Type
 }
 
-func (ms messageDescSet) Load(t reflect.Type) pref.MessageDescriptor {
+func (ms legacyMessageDescSet) Load(t reflect.Type) pref.MessageDescriptor {
 	// Fast-path: check if a MessageDescriptor is cached for this concrete type.
-	if mi, ok := messageDescCache.Load(t); ok {
+	if mi, ok := legacyMessageDescCache.Load(t); ok {
 		return mi.(pref.MessageDescriptor)
 	}
 
@@ -79,9 +80,9 @@
 	// Hold a global lock during message creation to ensure that each Go type
 	// maps to exactly one MessageDescriptor. After obtaining the lock, we must
 	// check again whether the message has already been handled.
-	messageDescLock.Lock()
-	defer messageDescLock.Unlock()
-	if mi, ok := messageDescCache.Load(t); ok {
+	legacyMessageDescLock.Lock()
+	defer legacyMessageDescLock.Unlock()
+	if mi, ok := legacyMessageDescCache.Load(t); ok {
 		return mi.(pref.MessageDescriptor)
 	}
 
@@ -101,13 +102,13 @@
 		// pseudo-messages (has a descriptor, but no generated Go type).
 		// Avoid caching these fake messages.
 		if t := ms.types[i]; t.Kind() != reflect.Map {
-			messageDescCache.Store(t, md)
+			legacyMessageDescCache.Store(t, md)
 		}
 	}
 	return mds[0]
 }
 
-func (ms *messageDescSet) processMessage(t reflect.Type) pref.MessageDescriptor {
+func (ms *legacyMessageDescSet) processMessage(t reflect.Type) pref.MessageDescriptor {
 	// Fast-path: Obtain a placeholder if the message is already processed.
 	if m, ok := ms.visited[t]; ok {
 		return ptype.PlaceholderMessage(m.FullName)
@@ -126,7 +127,7 @@
 	}
 	if md, ok := mv.(messageV1); ok {
 		b, idxs := md.Descriptor()
-		fd := loadFileDesc(b)
+		fd := legacyLoadFileDesc(b)
 
 		// Derive syntax.
 		switch fd.GetSyntax() {
@@ -148,7 +149,7 @@
 		// obtain the full name is through the registry. However, this is
 		// unreliable as some generated messages register with a fork of
 		// golang/protobuf, so the registry may not have this information.
-		m.FullName = deriveFullName(t.Elem())
+		m.FullName = legacyDeriveFullName(t.Elem())
 		m.Syntax = pref.Proto2
 
 		// Try to determine if the message is using proto3 by checking scalars.
@@ -223,7 +224,7 @@
 	return ptype.PlaceholderMessage(m.FullName)
 }
 
-func (ms *messageDescSet) parseField(tag, tagKey, tagVal string, goType reflect.Type, parent *ptype.StandaloneMessage) ptype.Field {
+func (ms *legacyMessageDescSet) parseField(tag, tagKey, tagVal string, goType reflect.Type, parent *ptype.StandaloneMessage) ptype.Field {
 	t := goType
 	isOptional := t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct
 	isRepeated := t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
@@ -237,7 +238,7 @@
 		if ev, ok := reflect.Zero(t).Interface().(pref.Enum); ok {
 			f.EnumType = ev.Descriptor()
 		} else {
-			f.EnumType = LoadEnumDesc(t)
+			f.EnumType = LegacyLoadEnumDesc(t)
 		}
 	}
 	if f.MessageType == nil && (f.Kind == pref.MessageKind || f.Kind == pref.GroupKind) {
@@ -246,7 +247,7 @@
 		} else if t.Kind() == reflect.Map {
 			m := &ptype.StandaloneMessage{
 				Syntax:     parent.Syntax,
-				FullName:   parent.FullName.Append(mapEntryName(f.Name)),
+				FullName:   parent.FullName.Append(legacyMapEntryName(f.Name)),
 				IsMapEntry: true,
 				Fields: []ptype.Field{
 					ms.parseField(tagKey, "", "", t.Key(), nil),
@@ -255,7 +256,7 @@
 			}
 			ms.visit(m, t)
 			f.MessageType = ptype.PlaceholderMessage(m.FullName)
-		} else if mv, ok := messageDescCache.Load(t); ok {
+		} else if mv, ok := legacyMessageDescCache.Load(t); ok {
 			f.MessageType = mv.(pref.MessageDescriptor)
 		} else {
 			f.MessageType = ms.processMessage(t)
@@ -264,7 +265,7 @@
 	return f
 }
 
-func (ms *messageDescSet) visit(m *ptype.StandaloneMessage, t reflect.Type) {
+func (ms *legacyMessageDescSet) visit(m *ptype.StandaloneMessage, t reflect.Type) {
 	if ms.visited == nil {
 		ms.visited = make(map[reflect.Type]*ptype.StandaloneMessage)
 	}
@@ -275,10 +276,10 @@
 	ms.types = append(ms.types, t)
 }
 
-// deriveFullName derives a fully qualified protobuf name for the given Go type
+// legacyDeriveFullName derives a fully qualified protobuf name for the given Go type
 // The provided name is not guaranteed to be stable nor universally unique.
 // It should be sufficiently unique within a program.
-func deriveFullName(t reflect.Type) pref.FullName {
+func legacyDeriveFullName(t reflect.Type) pref.FullName {
 	sanitize := func(r rune) rune {
 		switch {
 		case r == '/':
@@ -304,9 +305,9 @@
 	return pref.FullName(strings.Join(ss, "."))
 }
 
-// mapEntryName derives the message name for a map field of a given name.
+// legacyMapEntryName derives the message name for a map field of a given name.
 // This is identical to MapEntryName from parser.cc in the protoc source.
-func mapEntryName(s pref.Name) pref.Name {
+func legacyMapEntryName(s pref.Name) pref.Name {
 	var b []byte
 	nextUpper := true
 	for i := 0; i < len(s); i++ {
diff --git a/internal/impl/legacy_parse.go b/internal/impl/legacy_parse.go
new file mode 100644
index 0000000..90ba1b7
--- /dev/null
+++ b/internal/impl/legacy_parse.go
@@ -0,0 +1,169 @@
+// Copyright 2019 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 impl
+
+import (
+	"google.golang.org/protobuf/internal/encoding/wire"
+	"google.golang.org/protobuf/internal/fieldnum"
+)
+
+// To avoid a dependency from legacy to descriptor.proto, use a hand-written parser
+// for the bits of the descriptor we need.
+//
+// TODO: Consider unifying this with the parser in fileinit.
+
+type legacyFileDescriptorProto struct {
+	Syntax      string
+	Package     string
+	EnumType    []*legacyEnumDescriptorProto
+	MessageType []*legacyDescriptorProto
+}
+
+func (fd legacyFileDescriptorProto) GetSyntax() string  { return fd.Syntax }
+func (fd legacyFileDescriptorProto) GetPackage() string { return fd.Package }
+
+func legacyParseFileDescProto(b []byte) *legacyFileDescriptorProto {
+	fd := &legacyFileDescriptorProto{}
+	for len(b) > 0 {
+		num, typ, n := wire.ConsumeTag(b)
+		legacyParseCheck(n)
+		b = b[n:]
+		switch typ {
+		case wire.BytesType:
+			v, n := wire.ConsumeBytes(b)
+			b = b[n:]
+			switch num {
+			case fieldnum.FileDescriptorProto_Syntax:
+				fd.Syntax = string(v)
+			case fieldnum.FileDescriptorProto_Package:
+				fd.Package = string(v)
+			case fieldnum.FileDescriptorProto_EnumType:
+				fd.EnumType = append(fd.EnumType, legacyParseEnumDescProto(v))
+			case fieldnum.FileDescriptorProto_MessageType:
+				fd.MessageType = append(fd.MessageType, parseDescProto(v))
+			}
+		default:
+			n := wire.ConsumeFieldValue(num, typ, b)
+			legacyParseCheck(n)
+			b = b[n:]
+		}
+	}
+	return fd
+}
+
+type legacyDescriptorProto struct {
+	Name       string
+	NestedType []*legacyDescriptorProto
+	EnumType   []*legacyEnumDescriptorProto
+}
+
+func (md legacyDescriptorProto) GetName() string { return md.Name }
+
+func parseDescProto(b []byte) *legacyDescriptorProto {
+	md := &legacyDescriptorProto{}
+	for len(b) > 0 {
+		num, typ, n := wire.ConsumeTag(b)
+		legacyParseCheck(n)
+		b = b[n:]
+		switch typ {
+		case wire.BytesType:
+			v, n := wire.ConsumeBytes(b)
+			legacyParseCheck(n)
+			b = b[n:]
+			switch num {
+			case fieldnum.DescriptorProto_Name:
+				md.Name = string(v)
+			case fieldnum.DescriptorProto_NestedType:
+				md.NestedType = append(md.NestedType, parseDescProto(v))
+			case fieldnum.DescriptorProto_EnumType:
+				md.EnumType = append(md.EnumType, legacyParseEnumDescProto(v))
+			}
+		default:
+			n := wire.ConsumeFieldValue(num, typ, b)
+			legacyParseCheck(n)
+			b = b[n:]
+		}
+	}
+	return md
+}
+
+type legacyEnumDescriptorProto struct {
+	Name  string
+	Value []*legacyEnumValueDescriptorProto
+}
+
+func (ed legacyEnumDescriptorProto) GetName() string { return ed.Name }
+
+func legacyParseEnumDescProto(b []byte) *legacyEnumDescriptorProto {
+	ed := &legacyEnumDescriptorProto{}
+	for len(b) > 0 {
+		num, typ, n := wire.ConsumeTag(b)
+		legacyParseCheck(n)
+		b = b[n:]
+		switch typ {
+		case wire.BytesType:
+			v, n := wire.ConsumeBytes(b)
+			legacyParseCheck(n)
+			b = b[n:]
+			switch num {
+			case fieldnum.EnumDescriptorProto_Name:
+				ed.Name = string(v)
+			case fieldnum.EnumDescriptorProto_Value:
+				ed.Value = append(ed.Value, legacyParseEnumValueDescProto(v))
+			}
+		default:
+			n := wire.ConsumeFieldValue(num, typ, b)
+			legacyParseCheck(n)
+			b = b[n:]
+		}
+	}
+	return ed
+}
+
+type legacyEnumValueDescriptorProto struct {
+	Name   string
+	Number int32
+}
+
+func (ed legacyEnumValueDescriptorProto) GetName() string  { return ed.Name }
+func (ed legacyEnumValueDescriptorProto) GetNumber() int32 { return ed.Number }
+
+func legacyParseEnumValueDescProto(b []byte) *legacyEnumValueDescriptorProto {
+	vd := &legacyEnumValueDescriptorProto{}
+	for len(b) > 0 {
+		num, typ, n := wire.ConsumeTag(b)
+		legacyParseCheck(n)
+		b = b[n:]
+		switch typ {
+		case wire.VarintType:
+			v, n := wire.ConsumeVarint(b)
+			legacyParseCheck(n)
+			b = b[n:]
+			switch num {
+			case fieldnum.EnumValueDescriptorProto_Number:
+				vd.Number = int32(v)
+			}
+		case wire.BytesType:
+			v, n := wire.ConsumeBytes(b)
+			legacyParseCheck(n)
+			b = b[n:]
+			switch num {
+			case fieldnum.EnumDescriptorProto_Name:
+				vd.Name = string(v)
+			}
+		default:
+			n := wire.ConsumeFieldValue(num, typ, b)
+			legacyParseCheck(n)
+			b = b[n:]
+		}
+	}
+	return vd
+}
+
+func legacyParseCheck(n int) {
+	if n < 0 {
+		panic(wire.ParseError(n))
+	}
+}
diff --git a/internal/impl/legacy_test.go b/internal/impl/legacy_test.go
index a01dac1..d6ca941 100644
--- a/internal/impl/legacy_test.go
+++ b/internal/impl/legacy_test.go
@@ -8,13 +8,13 @@
 	"bytes"
 	"math"
 	"reflect"
+	"sync"
 	"testing"
 
 	cmp "github.com/google/go-cmp/cmp"
 	cmpopts "github.com/google/go-cmp/cmp/cmpopts"
 	pack "google.golang.org/protobuf/internal/encoding/pack"
 	pimpl "google.golang.org/protobuf/internal/impl"
-	plegacy "google.golang.org/protobuf/internal/legacy"
 	pragma "google.golang.org/protobuf/internal/pragma"
 	ptype "google.golang.org/protobuf/internal/prototype"
 	scalar "google.golang.org/protobuf/internal/scalar"
@@ -172,20 +172,12 @@
 	}
 }
 
-func mustMakeExtensionType(x *ptype.StandaloneExtension, v interface{}) pref.ExtensionType {
-	xd, err := ptype.NewExtension(x)
-	if err != nil {
-		panic(xd)
-	}
-	return plegacy.ExtensionTypeOf(xd, reflect.TypeOf(v))
-}
-
 var (
-	parentDesc    = pimpl.Export{}.MessageDescriptorOf((*legacyTestMessage)(nil))
-	enumV1Desc    = pimpl.Export{}.EnumDescriptorOf(proto2_20180125.Message_ChildEnum(0))
-	messageV1Desc = pimpl.Export{}.MessageDescriptorOf((*proto2_20180125.Message_ChildMessage)(nil))
-	enumV2Desc    = enumProto2Type.Descriptor()
-	messageV2Desc = enumMessagesType.PBType.Descriptor()
+	testParentDesc    = pimpl.Export{}.MessageDescriptorOf((*legacyTestMessage)(nil))
+	testEnumV1Desc    = pimpl.Export{}.EnumDescriptorOf(proto2_20180125.Message_ChildEnum(0))
+	testMessageV1Desc = pimpl.Export{}.MessageDescriptorOf((*proto2_20180125.Message_ChildMessage)(nil))
+	testEnumV2Desc    = enumProto2Type.Descriptor()
+	testMessageV2Desc = enumMessagesType.PBType.Descriptor()
 
 	extensionTypes = []pref.ExtensionType{
 		mustMakeExtensionType(&ptype.StandaloneExtension{
@@ -194,7 +186,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.BoolKind,
 			Default:      pref.ValueOf(true),
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_int32",
@@ -202,7 +194,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.Int32Kind,
 			Default:      pref.ValueOf(int32(-12345)),
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_uint32",
@@ -210,7 +202,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.Uint32Kind,
 			Default:      pref.ValueOf(uint32(3200)),
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_float",
@@ -218,7 +210,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.FloatKind,
 			Default:      pref.ValueOf(float32(3.14159)),
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_string",
@@ -226,7 +218,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.StringKind,
 			Default:      pref.ValueOf(string("hello, \"world!\"\n")),
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_bytes",
@@ -234,7 +226,7 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.BytesKind,
 			Default:      pref.ValueOf([]byte("dead\xde\xad\xbe\xefbeef")),
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_enum_v1",
@@ -242,16 +234,16 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.EnumKind,
 			Default:      pref.ValueOf(pref.EnumNumber(0)),
-			EnumType:     enumV1Desc,
-			ExtendedType: parentDesc,
+			EnumType:     testEnumV1Desc,
+			ExtendedType: testParentDesc,
 		}, proto2_20180125.Message_ChildEnum(0)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_message_v1",
 			Number:       10007,
 			Cardinality:  pref.Optional,
 			Kind:         pref.MessageKind,
-			MessageType:  messageV1Desc,
-			ExtendedType: parentDesc,
+			MessageType:  testMessageV1Desc,
+			ExtendedType: testParentDesc,
 		}, (*proto2_20180125.Message_ChildMessage)(nil)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_enum_v2",
@@ -259,90 +251,90 @@
 			Cardinality:  pref.Optional,
 			Kind:         pref.EnumKind,
 			Default:      pref.ValueOf(pref.EnumNumber(57005)),
-			EnumType:     enumV2Desc,
-			ExtendedType: parentDesc,
+			EnumType:     testEnumV2Desc,
+			ExtendedType: testParentDesc,
 		}, EnumProto2(0)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.optional_message_v2",
 			Number:       10009,
 			Cardinality:  pref.Optional,
 			Kind:         pref.MessageKind,
-			MessageType:  messageV2Desc,
-			ExtendedType: parentDesc,
+			MessageType:  testMessageV2Desc,
+			ExtendedType: testParentDesc,
 		}, (*EnumMessages)(nil)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_bool",
 			Number:       10010,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.BoolKind,
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_int32",
 			Number:       10011,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.Int32Kind,
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_uint32",
 			Number:       10012,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.Uint32Kind,
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_float",
 			Number:       10013,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.FloatKind,
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_string",
 			Number:       10014,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.StringKind,
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_bytes",
 			Number:       10015,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.BytesKind,
-			ExtendedType: parentDesc,
+			ExtendedType: testParentDesc,
 		}, nil),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_enum_v1",
 			Number:       10016,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.EnumKind,
-			EnumType:     enumV1Desc,
-			ExtendedType: parentDesc,
+			EnumType:     testEnumV1Desc,
+			ExtendedType: testParentDesc,
 		}, proto2_20180125.Message_ChildEnum(0)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_message_v1",
 			Number:       10017,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.MessageKind,
-			MessageType:  messageV1Desc,
-			ExtendedType: parentDesc,
+			MessageType:  testMessageV1Desc,
+			ExtendedType: testParentDesc,
 		}, (*proto2_20180125.Message_ChildMessage)(nil)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_enum_v2",
 			Number:       10018,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.EnumKind,
-			EnumType:     enumV2Desc,
-			ExtendedType: parentDesc,
+			EnumType:     testEnumV2Desc,
+			ExtendedType: testParentDesc,
 		}, EnumProto2(0)),
 		mustMakeExtensionType(&ptype.StandaloneExtension{
 			FullName:     "fizz.buzz.repeated_message_v2",
 			Number:       10019,
 			Cardinality:  pref.Repeated,
 			Kind:         pref.MessageKind,
-			MessageType:  messageV2Desc,
-			ExtendedType: parentDesc,
+			MessageType:  testMessageV2Desc,
+			ExtendedType: testParentDesc,
 		}, (*EnumMessages)(nil)),
 	}
 
@@ -646,8 +638,8 @@
 
 			wantType := extensionTypes[i]
 			wantDesc := extensionDescs[i]
-			gotType := plegacy.Export{}.ExtensionTypeFromDesc(wantDesc)
-			gotDesc := plegacy.Export{}.ExtensionDescFromType(wantType)
+			gotType := pimpl.Export{}.ExtensionTypeFromDesc(wantDesc)
+			gotDesc := pimpl.Export{}.ExtensionDescFromType(wantType)
 
 			// TODO: We need a test package to compare descriptors.
 			type list interface {
@@ -720,3 +712,88 @@
 		})
 	}
 }
+
+type (
+	MessageA struct {
+		A1 *MessageA `protobuf:"bytes,1,req,name=a1"`
+		A2 *MessageB `protobuf:"bytes,2,req,name=a2"`
+		A3 Enum      `protobuf:"varint,3,opt,name=a3,enum=legacy.Enum"`
+	}
+	MessageB struct {
+		B1 *MessageA `protobuf:"bytes,1,req,name=b1"`
+		B2 *MessageB `protobuf:"bytes,2,req,name=b2"`
+		B3 Enum      `protobuf:"varint,3,opt,name=b3,enum=legacy.Enum"`
+	}
+	Enum int32
+)
+
+// TestConcurrentInit tests that concurrent wrapping of multiple legacy types
+// results in the exact same descriptor being created.
+func TestConcurrentInit(t *testing.T) {
+	const numParallel = 5
+	var messageATypes [numParallel]pref.MessageType
+	var messageBTypes [numParallel]pref.MessageType
+	var enumDescs [numParallel]pref.EnumDescriptor
+
+	// Concurrently load message and enum types.
+	var wg sync.WaitGroup
+	for i := 0; i < numParallel; i++ {
+		i := i
+		wg.Add(3)
+		go func() {
+			defer wg.Done()
+			messageATypes[i] = pimpl.Export{}.MessageTypeOf((*MessageA)(nil))
+		}()
+		go func() {
+			defer wg.Done()
+			messageBTypes[i] = pimpl.Export{}.MessageTypeOf((*MessageB)(nil))
+		}()
+		go func() {
+			defer wg.Done()
+			enumDescs[i] = pimpl.Export{}.EnumDescriptorOf(Enum(0))
+		}()
+	}
+	wg.Wait()
+
+	var (
+		wantMTA = messageATypes[0]
+		wantMDA = messageATypes[0].Descriptor().Fields().ByNumber(1).Message()
+		wantMTB = messageBTypes[0]
+		wantMDB = messageBTypes[0].Descriptor().Fields().ByNumber(2).Message()
+		wantED  = messageATypes[0].Descriptor().Fields().ByNumber(3).Enum()
+	)
+
+	for _, gotMT := range messageATypes[1:] {
+		if gotMT != wantMTA {
+			t.Error("MessageType(MessageA) mismatch")
+		}
+		if gotMDA := gotMT.Descriptor().Fields().ByNumber(1).Message(); gotMDA != wantMDA {
+			t.Error("MessageDescriptor(MessageA) mismatch")
+		}
+		if gotMDB := gotMT.Descriptor().Fields().ByNumber(2).Message(); gotMDB != wantMDB {
+			t.Error("MessageDescriptor(MessageB) mismatch")
+		}
+		if gotED := gotMT.Descriptor().Fields().ByNumber(3).Enum(); gotED != wantED {
+			t.Error("EnumDescriptor(Enum) mismatch")
+		}
+	}
+	for _, gotMT := range messageBTypes[1:] {
+		if gotMT != wantMTB {
+			t.Error("MessageType(MessageB) mismatch")
+		}
+		if gotMDA := gotMT.Descriptor().Fields().ByNumber(1).Message(); gotMDA != wantMDA {
+			t.Error("MessageDescriptor(MessageA) mismatch")
+		}
+		if gotMDB := gotMT.Descriptor().Fields().ByNumber(2).Message(); gotMDB != wantMDB {
+			t.Error("MessageDescriptor(MessageB) mismatch")
+		}
+		if gotED := gotMT.Descriptor().Fields().ByNumber(3).Enum(); gotED != wantED {
+			t.Error("EnumDescriptor(Enum) mismatch")
+		}
+	}
+	for _, gotED := range enumDescs[1:] {
+		if gotED != wantED {
+			t.Error("EnumType(Enum) mismatch")
+		}
+	}
+}
diff --git a/internal/impl/message_field.go b/internal/impl/message_field.go
index a69d2c8..28fc7d1 100644
--- a/internal/impl/message_field.go
+++ b/internal/impl/message_field.go
@@ -12,6 +12,7 @@
 	"google.golang.org/protobuf/internal/encoding/wire"
 	pvalue "google.golang.org/protobuf/internal/value"
 	pref "google.golang.org/protobuf/reflect/protoreflect"
+	piface "google.golang.org/protobuf/runtime/protoiface"
 )
 
 type fieldInfo struct {
@@ -42,7 +43,7 @@
 	if !reflect.PtrTo(ot).Implements(ft) {
 		panic(fmt.Sprintf("invalid type: %v does not implement %v", ot, ft))
 	}
-	conv := newConverter(ot.Field(0).Type, fd.Kind())
+	conv, _ := newConverter(ot.Field(0).Type, fd.Kind())
 	fieldOffset := offsetOf(fs)
 	// TODO: Implement unsafe fast path?
 	return fieldInfo{
@@ -97,8 +98,8 @@
 	if ft.Kind() != reflect.Map {
 		panic(fmt.Sprintf("invalid type: got %v, want map kind", ft))
 	}
-	keyConv := newConverter(ft.Key(), fd.MapKey().Kind())
-	valConv := newConverter(ft.Elem(), fd.MapValue().Kind())
+	keyConv, _ := newConverter(ft.Key(), fd.MapKey().Kind())
+	valConv, _ := newConverter(ft.Elem(), fd.MapValue().Kind())
 	wiretag := wire.EncodeTag(fd.Number(), wireTypes[fd.Kind()])
 	fieldOffset := offsetOf(fs)
 	// TODO: Implement unsafe fast path?
@@ -139,7 +140,7 @@
 	if ft.Kind() != reflect.Slice {
 		panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
 	}
-	conv := newConverter(ft.Elem(), fd.Kind())
+	conv, _ := newConverter(ft.Elem(), fd.Kind())
 	var wiretag uint64
 	if !fd.IsPacked() {
 		wiretag = wire.EncodeTag(fd.Number(), wireTypes[fd.Kind()])
@@ -194,7 +195,7 @@
 			ft = ft.Elem()
 		}
 	}
-	conv := newConverter(ft, fd.Kind())
+	conv, _ := newConverter(ft, fd.Kind())
 	fieldOffset := offsetOf(fs)
 	wiretag := wire.EncodeTag(fd.Number(), wireTypes[fd.Kind()])
 	// TODO: Implement unsafe fast path?
@@ -264,7 +265,7 @@
 
 func fieldInfoForMessage(fd pref.FieldDescriptor, fs reflect.StructField) fieldInfo {
 	ft := fs.Type
-	conv := newConverter(ft, fd.Kind())
+	conv, _ := newConverter(ft, fd.Kind())
 	fieldOffset := offsetOf(fs)
 	// TODO: Implement unsafe fast path?
 	wiretag := wire.EncodeTag(fd.Number(), wireTypes[fd.Kind()])
@@ -338,9 +339,52 @@
 	}
 }
 
-func newConverter(t reflect.Type, k pref.Kind) pvalue.Converter {
-	if legacyWrapper != nil {
-		return legacyWrapper.NewConverter(t, k)
+var (
+	enumIfaceV2    = reflect.TypeOf((*pref.Enum)(nil)).Elem()
+	messageIfaceV1 = reflect.TypeOf((*piface.MessageV1)(nil)).Elem()
+	messageIfaceV2 = reflect.TypeOf((*pref.ProtoMessage)(nil)).Elem()
+)
+
+func newConverter(t reflect.Type, k pref.Kind) (conv pvalue.Converter, isLegacy bool) {
+	switch k {
+	case pref.EnumKind:
+		if t.Kind() == reflect.Int32 && !t.Implements(enumIfaceV2) {
+			return pvalue.Converter{
+				PBValueOf: func(v reflect.Value) pref.Value {
+					if v.Type() != t {
+						panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t))
+					}
+					return pref.ValueOf(pref.EnumNumber(v.Int()))
+				},
+				GoValueOf: func(v pref.Value) reflect.Value {
+					return reflect.ValueOf(v.Enum()).Convert(t)
+				},
+				NewEnum: func(n pref.EnumNumber) pref.Enum {
+					return legacyWrapEnum(reflect.ValueOf(n).Convert(t))
+				},
+			}, true
+		}
+	case pref.MessageKind, pref.GroupKind:
+		if t.Kind() == reflect.Ptr && t.Implements(messageIfaceV1) && !t.Implements(messageIfaceV2) {
+			return pvalue.Converter{
+				PBValueOf: func(v reflect.Value) pref.Value {
+					if v.Type() != t {
+						panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t))
+					}
+					return pref.ValueOf(Export{}.MessageOf(v.Interface()))
+				},
+				GoValueOf: func(v pref.Value) reflect.Value {
+					rv := reflect.ValueOf(v.Message().(pvalue.Unwrapper).ProtoUnwrap())
+					if rv.Type() != t {
+						panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), t))
+					}
+					return rv
+				},
+				NewMessage: func() pref.Message {
+					return legacyWrapMessage(reflect.New(t.Elem())).ProtoReflect()
+				},
+			}, true
+		}
 	}
-	return pvalue.NewConverter(t, k)
+	return pvalue.NewConverter(t, k), false
 }
diff --git a/internal/impl/message_field_extension.go b/internal/impl/message_field_extension.go
index ada2f87..3c403af 100644
--- a/internal/impl/message_field_extension.go
+++ b/internal/impl/message_field_extension.go
@@ -231,14 +231,14 @@
 			return desc
 		}
 	}
-	return legacyWrapper.ExtensionDescFromType(typ)
+	return Export{}.ExtensionDescFromType(typ)
 }
 
 func extensionTypeFromDesc(desc *piface.ExtensionDescV1) pref.ExtensionType {
 	if desc.Type != nil {
 		return desc.Type
 	}
-	return legacyWrapper.ExtensionTypeFromDesc(desc)
+	return Export{}.ExtensionTypeFromDesc(desc)
 }
 
 type ExtensionFieldV1 struct {
diff --git a/internal/legacy/export.go b/internal/legacy/export.go
deleted file mode 100644
index 701b578..0000000
--- a/internal/legacy/export.go
+++ /dev/null
@@ -1,110 +0,0 @@
-// 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 legacy
-
-import (
-	"fmt"
-	"reflect"
-
-	pimpl "google.golang.org/protobuf/internal/impl"
-	pvalue "google.golang.org/protobuf/internal/value"
-	pref "google.golang.org/protobuf/reflect/protoreflect"
-	piface "google.golang.org/protobuf/runtime/protoiface"
-)
-
-// Export is a zero-length named type that exists only to export a set of
-// functions that we do not want to appear in godoc.
-type Export struct{}
-
-func (Export) EnumOf(e interface{}) pref.Enum {
-	return wrapEnum(reflect.ValueOf(e))
-}
-
-func (Export) EnumTypeOf(e interface{}) pref.EnumType {
-	return loadEnumType(reflect.TypeOf(e))
-}
-
-func (Export) EnumDescriptorOf(e interface{}) pref.EnumDescriptor {
-	return LoadEnumDesc(reflect.TypeOf(e))
-}
-
-func (Export) MessageOf(m interface{}) pref.Message {
-	return wrapMessage(reflect.ValueOf(m)).ProtoReflect()
-}
-
-func (Export) MessageTypeOf(m interface{}) pref.MessageType {
-	return loadMessageInfo(reflect.TypeOf(m)).PBType
-}
-
-func (Export) MessageDescriptorOf(m interface{}) pref.MessageDescriptor {
-	return LoadMessageDesc(reflect.TypeOf(m))
-}
-
-func (Export) ExtensionDescFromType(t pref.ExtensionType) *piface.ExtensionDescV1 {
-	return extensionDescFromType(t)
-}
-
-func (Export) ExtensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType {
-	return extensionTypeFromDesc(d)
-}
-
-var (
-	enumIfaceV2    = reflect.TypeOf((*pref.Enum)(nil)).Elem()
-	messageIfaceV1 = reflect.TypeOf((*piface.MessageV1)(nil)).Elem()
-	messageIfaceV2 = reflect.TypeOf((*pref.ProtoMessage)(nil)).Elem()
-)
-
-func (Export) NewConverter(t reflect.Type, k pref.Kind) pvalue.Converter {
-	c, _ := newConverter(t, k)
-	return c
-}
-
-func newConverter(t reflect.Type, k pref.Kind) (pvalue.Converter, bool) {
-	switch k {
-	case pref.EnumKind:
-		if t.Kind() == reflect.Int32 && !t.Implements(enumIfaceV2) {
-			return pvalue.Converter{
-				PBValueOf: func(v reflect.Value) pref.Value {
-					if v.Type() != t {
-						panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t))
-					}
-					return pref.ValueOf(pref.EnumNumber(v.Int()))
-				},
-				GoValueOf: func(v pref.Value) reflect.Value {
-					return reflect.ValueOf(v.Enum()).Convert(t)
-				},
-				NewEnum: func(n pref.EnumNumber) pref.Enum {
-					return wrapEnum(reflect.ValueOf(n).Convert(t))
-				},
-			}, true
-		}
-	case pref.MessageKind, pref.GroupKind:
-		if t.Kind() == reflect.Ptr && t.Implements(messageIfaceV1) && !t.Implements(messageIfaceV2) {
-			return pvalue.Converter{
-				PBValueOf: func(v reflect.Value) pref.Value {
-					if v.Type() != t {
-						panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t))
-					}
-					return pref.ValueOf(Export{}.MessageOf(v.Interface()))
-				},
-				GoValueOf: func(v pref.Value) reflect.Value {
-					rv := reflect.ValueOf(v.Message().(pvalue.Unwrapper).ProtoUnwrap())
-					if rv.Type() != t {
-						panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), t))
-					}
-					return rv
-				},
-				NewMessage: func() pref.Message {
-					return wrapMessage(reflect.New(t.Elem())).ProtoReflect()
-				},
-			}, true
-		}
-	}
-	return pvalue.NewConverter(t, k), false
-}
-
-func init() {
-	pimpl.RegisterLegacyWrapper(Export{})
-}
diff --git a/internal/legacy/legacy_test.go b/internal/legacy/legacy_test.go
deleted file mode 100644
index be57f53..0000000
--- a/internal/legacy/legacy_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2019 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 legacy
-
-import (
-	"sync"
-	"testing"
-
-	"google.golang.org/protobuf/reflect/protoreflect"
-)
-
-type (
-	MessageA struct {
-		A1 *MessageA `protobuf:"bytes,1,req,name=a1"`
-		A2 *MessageB `protobuf:"bytes,2,req,name=a2"`
-		A3 Enum      `protobuf:"varint,3,opt,name=a3,enum=legacy.Enum"`
-	}
-	MessageB struct {
-		B1 *MessageA `protobuf:"bytes,1,req,name=b1"`
-		B2 *MessageB `protobuf:"bytes,2,req,name=b2"`
-		B3 Enum      `protobuf:"varint,3,opt,name=b3,enum=legacy.Enum"`
-	}
-	Enum int32
-)
-
-// TestConcurrentInit tests that concurrent wrapping of multiple legacy types
-// results in the exact same descriptor being created.
-func TestConcurrentInit(t *testing.T) {
-	const numParallel = 5
-	var messageATypes [numParallel]protoreflect.MessageType
-	var messageBTypes [numParallel]protoreflect.MessageType
-	var enumDescs [numParallel]protoreflect.EnumDescriptor
-
-	// Concurrently load message and enum types.
-	var wg sync.WaitGroup
-	for i := 0; i < numParallel; i++ {
-		i := i
-		wg.Add(3)
-		go func() {
-			defer wg.Done()
-			messageATypes[i] = Export{}.MessageTypeOf((*MessageA)(nil))
-		}()
-		go func() {
-			defer wg.Done()
-			messageBTypes[i] = Export{}.MessageTypeOf((*MessageB)(nil))
-		}()
-		go func() {
-			defer wg.Done()
-			enumDescs[i] = Export{}.EnumDescriptorOf(Enum(0))
-		}()
-	}
-	wg.Wait()
-
-	var (
-		wantMTA = messageATypes[0]
-		wantMDA = messageATypes[0].Descriptor().Fields().ByNumber(1).Message()
-		wantMTB = messageBTypes[0]
-		wantMDB = messageBTypes[0].Descriptor().Fields().ByNumber(2).Message()
-		wantED  = messageATypes[0].Descriptor().Fields().ByNumber(3).Enum()
-	)
-
-	for _, gotMT := range messageATypes[1:] {
-		if gotMT != wantMTA {
-			t.Error("MessageType(MessageA) mismatch")
-		}
-		if gotMDA := gotMT.Descriptor().Fields().ByNumber(1).Message(); gotMDA != wantMDA {
-			t.Error("MessageDescriptor(MessageA) mismatch")
-		}
-		if gotMDB := gotMT.Descriptor().Fields().ByNumber(2).Message(); gotMDB != wantMDB {
-			t.Error("MessageDescriptor(MessageB) mismatch")
-		}
-		if gotED := gotMT.Descriptor().Fields().ByNumber(3).Enum(); gotED != wantED {
-			t.Error("EnumDescriptor(Enum) mismatch")
-		}
-	}
-	for _, gotMT := range messageBTypes[1:] {
-		if gotMT != wantMTB {
-			t.Error("MessageType(MessageB) mismatch")
-		}
-		if gotMDA := gotMT.Descriptor().Fields().ByNumber(1).Message(); gotMDA != wantMDA {
-			t.Error("MessageDescriptor(MessageA) mismatch")
-		}
-		if gotMDB := gotMT.Descriptor().Fields().ByNumber(2).Message(); gotMDB != wantMDB {
-			t.Error("MessageDescriptor(MessageB) mismatch")
-		}
-		if gotED := gotMT.Descriptor().Fields().ByNumber(3).Enum(); gotED != wantED {
-			t.Error("EnumDescriptor(Enum) mismatch")
-		}
-	}
-	for _, gotED := range enumDescs[1:] {
-		if gotED != wantED {
-			t.Error("EnumType(Enum) mismatch")
-		}
-	}
-}
diff --git a/internal/legacy/parse.go b/internal/legacy/parse.go
deleted file mode 100644
index 6de9578..0000000
--- a/internal/legacy/parse.go
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2019 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 legacy
-
-import (
-	"google.golang.org/protobuf/internal/encoding/wire"
-	"google.golang.org/protobuf/internal/fieldnum"
-)
-
-// To avoid a dependency from legacy to descriptor.proto, use a hand-written parser
-// for the bits of the descriptor we need.
-//
-// TODO: Consider unifying this with the parser in fileinit.
-
-type fileDescriptorProto struct {
-	Syntax      string
-	Package     string
-	EnumType    []*enumDescriptorProto
-	MessageType []*descriptorProto
-}
-
-func (fd fileDescriptorProto) GetSyntax() string  { return fd.Syntax }
-func (fd fileDescriptorProto) GetPackage() string { return fd.Package }
-
-func parseFileDescProto(b []byte) *fileDescriptorProto {
-	fd := &fileDescriptorProto{}
-	for len(b) > 0 {
-		num, typ, n := wire.ConsumeTag(b)
-		parseCheck(n)
-		b = b[n:]
-		switch typ {
-		case wire.BytesType:
-			v, n := wire.ConsumeBytes(b)
-			b = b[n:]
-			switch num {
-			case fieldnum.FileDescriptorProto_Syntax:
-				fd.Syntax = string(v)
-			case fieldnum.FileDescriptorProto_Package:
-				fd.Package = string(v)
-			case fieldnum.FileDescriptorProto_EnumType:
-				fd.EnumType = append(fd.EnumType, parseEnumDescProto(v))
-			case fieldnum.FileDescriptorProto_MessageType:
-				fd.MessageType = append(fd.MessageType, parseDescProto(v))
-			}
-		default:
-			n := wire.ConsumeFieldValue(num, typ, b)
-			parseCheck(n)
-			b = b[n:]
-		}
-	}
-	return fd
-}
-
-type descriptorProto struct {
-	Name       string
-	NestedType []*descriptorProto
-	EnumType   []*enumDescriptorProto
-}
-
-func (md descriptorProto) GetName() string { return md.Name }
-
-func parseDescProto(b []byte) *descriptorProto {
-	md := &descriptorProto{}
-	for len(b) > 0 {
-		num, typ, n := wire.ConsumeTag(b)
-		parseCheck(n)
-		b = b[n:]
-		switch typ {
-		case wire.BytesType:
-			v, n := wire.ConsumeBytes(b)
-			parseCheck(n)
-			b = b[n:]
-			switch num {
-			case fieldnum.DescriptorProto_Name:
-				md.Name = string(v)
-			case fieldnum.DescriptorProto_NestedType:
-				md.NestedType = append(md.NestedType, parseDescProto(v))
-			case fieldnum.DescriptorProto_EnumType:
-				md.EnumType = append(md.EnumType, parseEnumDescProto(v))
-			}
-		default:
-			n := wire.ConsumeFieldValue(num, typ, b)
-			parseCheck(n)
-			b = b[n:]
-		}
-	}
-	return md
-}
-
-type enumDescriptorProto struct {
-	Name  string
-	Value []*enumValueDescriptorProto
-}
-
-func (ed enumDescriptorProto) GetName() string { return ed.Name }
-
-func parseEnumDescProto(b []byte) *enumDescriptorProto {
-	ed := &enumDescriptorProto{}
-	for len(b) > 0 {
-		num, typ, n := wire.ConsumeTag(b)
-		parseCheck(n)
-		b = b[n:]
-		switch typ {
-		case wire.BytesType:
-			v, n := wire.ConsumeBytes(b)
-			parseCheck(n)
-			b = b[n:]
-			switch num {
-			case fieldnum.EnumDescriptorProto_Name:
-				ed.Name = string(v)
-			case fieldnum.EnumDescriptorProto_Value:
-				ed.Value = append(ed.Value, parseEnumValueDescProto(v))
-			}
-		default:
-			n := wire.ConsumeFieldValue(num, typ, b)
-			parseCheck(n)
-			b = b[n:]
-		}
-	}
-	return ed
-}
-
-type enumValueDescriptorProto struct {
-	Name   string
-	Number int32
-}
-
-func (ed enumValueDescriptorProto) GetName() string  { return ed.Name }
-func (ed enumValueDescriptorProto) GetNumber() int32 { return ed.Number }
-
-func parseEnumValueDescProto(b []byte) *enumValueDescriptorProto {
-	vd := &enumValueDescriptorProto{}
-	for len(b) > 0 {
-		num, typ, n := wire.ConsumeTag(b)
-		parseCheck(n)
-		b = b[n:]
-		switch typ {
-		case wire.VarintType:
-			v, n := wire.ConsumeVarint(b)
-			parseCheck(n)
-			b = b[n:]
-			switch num {
-			case fieldnum.EnumValueDescriptorProto_Number:
-				vd.Number = int32(v)
-			}
-		case wire.BytesType:
-			v, n := wire.ConsumeBytes(b)
-			parseCheck(n)
-			b = b[n:]
-			switch num {
-			case fieldnum.EnumDescriptorProto_Name:
-				vd.Name = string(v)
-			}
-		default:
-			n := wire.ConsumeFieldValue(num, typ, b)
-			parseCheck(n)
-			b = b[n:]
-		}
-	}
-	return vd
-}
-
-func parseCheck(n int) {
-	if n < 0 {
-		panic(wire.ParseError(n))
-	}
-}
diff --git a/runtime/protolegacy/legacy.go b/runtime/protolegacy/legacy.go
index b9bad19..1465633 100644
--- a/runtime/protolegacy/legacy.go
+++ b/runtime/protolegacy/legacy.go
@@ -2,15 +2,11 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package protolegacy contains the default implementation for messages
-// generated by protoc-gen-go.
-//
-// WARNING: This package should only ever be imported by the v1 proto package.
-// The compatibility agreement covers nothing except for functionality needed
-// to provide v1 interoperability. Breakages that occur due to unauthorized
-// usages of this package are not the author's responsibility.
+// Deprecated: Do not use.
 package protolegacy
 
-import "google.golang.org/protobuf/internal/legacy"
+// TODO: Remove this.
 
-var X legacy.Export
+import "google.golang.org/protobuf/internal/impl"
+
+var X impl.Export