reflect/protoreflect: add helper methods to FieldDescriptor

Added API:
	FieldDescriptor.IsExtension
	FieldDescriptor.IsList
	FieldDescriptor.MapKey
	FieldDescriptor.MapValue
	FieldDescriptor.ContainingOneof
	FieldDescriptor.ContainingMessage

Deprecated API (to be removed in subsequent CL):
	FieldDescriptor.Oneof
	FieldDescriptor.Extendee

These methods help cleanup several common usage patterns.

Change-Id: I9a3ffabc2edb2173c536509b22f330f98bba7cf3
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/176977
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index 9e11fb0..cedda8b 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -559,20 +559,21 @@
 		goType = "[]byte"
 		pointer = false
 	case protoreflect.MessageKind, protoreflect.GroupKind:
-		if field.Desc.IsMap() {
-			keyType, _ := fieldGoType(g, field.Message.Fields[0])
-			valType, _ := fieldGoType(g, field.Message.Fields[1])
-			return fmt.Sprintf("map[%v]%v", keyType, valType), false
-		}
 		goType = "*" + g.QualifiedGoIdent(field.Message.GoIdent)
 		pointer = false
 	}
-	if field.Desc.Cardinality() == protoreflect.Repeated {
+	switch {
+	case field.Desc.IsList():
 		goType = "[]" + goType
 		pointer = false
+	case field.Desc.IsMap():
+		keyType, _ := fieldGoType(g, field.Message.Fields[0])
+		valType, _ := fieldGoType(g, field.Message.Fields[1])
+		return fmt.Sprintf("map[%v]%v", keyType, valType), false
 	}
+
 	// Extension fields always have pointer type, even when defined in a proto3 file.
-	if field.Desc.Syntax() == protoreflect.Proto3 && field.Desc.Extendee() == nil {
+	if field.Desc.Syntax() == protoreflect.Proto3 && !field.Desc.IsExtension() {
 		pointer = false
 	}
 	return goType, pointer
@@ -587,7 +588,7 @@
 }
 
 func fieldDefaultValue(g *protogen.GeneratedFile, message *protogen.Message, field *protogen.Field) string {
-	if field.Desc.Cardinality() == protoreflect.Repeated {
+	if field.Desc.IsList() {
 		return "nil"
 	}
 	if field.Desc.HasDefault() {
@@ -653,7 +654,7 @@
 	g.P("var (")
 	for i, extension := range f.allExtensions {
 		ed := extension.Desc
-		targetName := string(ed.Extendee().FullName())
+		targetName := string(ed.ContainingMessage().FullName())
 		typeName := ed.Kind().String()
 		switch ed.Kind() {
 		case protoreflect.EnumKind:
diff --git a/encoding/bench_test.go b/encoding/bench_test.go
index 88f62b7..fdb33d1 100644
--- a/encoding/bench_test.go
+++ b/encoding/bench_test.go
@@ -49,13 +49,12 @@
 	for i := 0; i < fieldDescs.Len(); i++ {
 		fd := fieldDescs.Get(i)
 		num := fd.Number()
-		if cardinality := fd.Cardinality(); cardinality == pref.Repeated {
-			if !fd.IsMap() {
-				setList(knownFields.Get(num).List(), fd, level)
-			} else {
-				setMap(knownFields.Get(num).Map(), fd, level)
-			}
-		} else {
+		switch {
+		case fd.IsList():
+			setList(knownFields.Get(num).List(), fd, level)
+		case fd.IsMap():
+			setMap(knownFields.Get(num).Map(), fd, level)
+		default:
 			setScalarField(knownFields, fd, level)
 		}
 	}
diff --git a/encoding/jsonpb/decode.go b/encoding/jsonpb/decode.go
index 7e142c5..1b5a5af 100644
--- a/encoding/jsonpb/decode.go
+++ b/encoding/jsonpb/decode.go
@@ -244,14 +244,20 @@
 			continue
 		}
 
-		if cardinality := fd.Cardinality(); cardinality == pref.Repeated {
-			// Map or list fields have cardinality of repeated.
-			if err := o.unmarshalRepeated(knownFields, fd); !nerr.Merge(err) {
+		switch {
+		case fd.IsList():
+			list := knownFields.Get(fd.Number()).List()
+			if err := o.unmarshalList(list, fd); !nerr.Merge(err) {
 				return errors.New("%v|%q: %v", fd.FullName(), name, err)
 			}
-		} else {
+		case fd.IsMap():
+			mmap := knownFields.Get(fd.Number()).Map()
+			if err := o.unmarshalMap(mmap, fd); !nerr.Merge(err) {
+				return errors.New("%v|%q: %v", fd.FullName(), name, err)
+			}
+		default:
 			// If field is a oneof, check if it has already been set.
-			if od := fd.Oneof(); od != nil {
+			if od := fd.ContainingOneof(); od != nil {
 				idx := uint64(od.Index())
 				if seenOneofs.Has(idx) {
 					return errors.New("%v: oneof is already set", od.FullName())
@@ -548,24 +554,6 @@
 	return pref.Value{}, unexpectedJSONError{jval}
 }
 
-// unmarshalRepeated unmarshals into a repeated field.
-func (o UnmarshalOptions) unmarshalRepeated(knownFields pref.KnownFields, fd pref.FieldDescriptor) error {
-	var nerr errors.NonFatal
-	num := fd.Number()
-	val := knownFields.Get(num)
-	if !fd.IsMap() {
-		if err := o.unmarshalList(val.List(), fd); !nerr.Merge(err) {
-			return err
-		}
-	} else {
-		if err := o.unmarshalMap(val.Map(), fd); !nerr.Merge(err) {
-			return err
-		}
-	}
-	return nerr.E
-}
-
-// unmarshalList unmarshals into given protoreflect.List.
 func (o UnmarshalOptions) unmarshalList(list pref.List, fd pref.FieldDescriptor) error {
 	var nerr errors.NonFatal
 	jval, err := o.decoder.Read()
@@ -610,10 +598,8 @@
 	return nerr.E
 }
 
-// unmarshalMap unmarshals into given protoreflect.Map.
 func (o UnmarshalOptions) unmarshalMap(mmap pref.Map, fd pref.FieldDescriptor) error {
 	var nerr errors.NonFatal
-
 	jval, err := o.decoder.Read()
 	if !nerr.Merge(err) {
 		return err
@@ -622,17 +608,11 @@
 		return unexpectedJSONError{jval}
 	}
 
-	fields := fd.Message().Fields()
-	keyDesc := fields.ByNumber(1)
-	valDesc := fields.ByNumber(2)
-
 	// Determine ahead whether map entry is a scalar type or a message type in
 	// order to call the appropriate unmarshalMapValue func inside the for loop
 	// below.
-	unmarshalMapValue := func() (pref.Value, error) {
-		return o.unmarshalScalar(valDesc)
-	}
-	switch valDesc.Kind() {
+	var unmarshalMapValue func() (pref.Value, error)
+	switch fd.MapValue().Kind() {
 	case pref.MessageKind, pref.GroupKind:
 		unmarshalMapValue = func() (pref.Value, error) {
 			var nerr errors.NonFatal
@@ -642,6 +622,10 @@
 			}
 			return pref.ValueOf(m), nerr.E
 		}
+	default:
+		unmarshalMapValue = func() (pref.Value, error) {
+			return o.unmarshalScalar(fd.MapValue())
+		}
 	}
 
 Loop:
@@ -666,7 +650,7 @@
 		}
 
 		// Unmarshal field name.
-		pkey, err := unmarshalMapKey(name, keyDesc)
+		pkey, err := unmarshalMapKey(name, fd.MapKey())
 		if !nerr.Merge(err) {
 			return err
 		}
diff --git a/encoding/jsonpb/encode.go b/encoding/jsonpb/encode.go
index 90767ce..256cd74 100644
--- a/encoding/jsonpb/encode.go
+++ b/encoding/jsonpb/encode.go
@@ -118,25 +118,14 @@
 
 // marshalValue marshals the given protoreflect.Value.
 func (o MarshalOptions) marshalValue(val pref.Value, fd pref.FieldDescriptor) error {
-	var nerr errors.NonFatal
-	if fd.Cardinality() == pref.Repeated {
-		// Map or repeated fields.
-		if fd.IsMap() {
-			if err := o.marshalMap(val.Map(), fd); !nerr.Merge(err) {
-				return err
-			}
-		} else {
-			if err := o.marshalList(val.List(), fd); !nerr.Merge(err) {
-				return err
-			}
-		}
-	} else {
-		// Required or optional fields.
-		if err := o.marshalSingular(val, fd); !nerr.Merge(err) {
-			return err
-		}
+	switch {
+	case fd.IsList():
+		return o.marshalList(val.List(), fd)
+	case fd.IsMap():
+		return o.marshalMap(val.Map(), fd)
+	default:
+		return o.marshalSingular(val, fd)
 	}
-	return nerr.E
 }
 
 // marshalSingular marshals the given non-repeated field value. This includes
@@ -226,17 +215,13 @@
 	o.encoder.StartObject()
 	defer o.encoder.EndObject()
 
-	msgFields := fd.Message().Fields()
-	keyType := msgFields.ByNumber(1)
-	valType := msgFields.ByNumber(2)
-
 	// Get a sorted list based on keyType first.
 	entries := make([]mapEntry, 0, mmap.Len())
 	mmap.Range(func(key pref.MapKey, val pref.Value) bool {
 		entries = append(entries, mapEntry{key: key, value: val})
 		return true
 	})
-	sortMap(keyType.Kind(), entries)
+	sortMap(fd.MapKey().Kind(), entries)
 
 	// Write out sorted list.
 	var nerr errors.NonFatal
@@ -244,7 +229,7 @@
 		if err := o.encoder.WriteName(entry.key.String()); !nerr.Merge(err) {
 			return err
 		}
-		if err := o.marshalSingular(entry.value, valType); !nerr.Merge(err) {
+		if err := o.marshalSingular(entry.value, fd.MapValue()); !nerr.Merge(err) {
 			return err
 		}
 	}
@@ -333,6 +318,6 @@
 	if xd.FullName().Parent() != md.FullName() {
 		return false
 	}
-	xmd, ok := xd.Extendee().(interface{ IsMessageSet() bool })
+	xmd, ok := xd.ContainingMessage().(interface{ IsMessageSet() bool })
 	return ok && xmd.IsMessageSet()
 }
diff --git a/encoding/textpb/decode.go b/encoding/textpb/decode.go
index 38092ac..c586331 100644
--- a/encoding/textpb/decode.go
+++ b/encoding/textpb/decode.go
@@ -159,14 +159,36 @@
 			return errors.New("%v contains unknown field: %v", messageDesc.FullName(), tkey)
 		}
 
-		if cardinality := fd.Cardinality(); cardinality == pref.Repeated {
-			// Map or list fields have cardinality of repeated.
-			if err := o.unmarshalRepeated(tval, fd, knownFields); !nerr.Merge(err) {
+		switch {
+		case fd.IsList():
+			// If input is not a list, turn it into a list.
+			var items []text.Value
+			if tval.Type() != text.List {
+				items = []text.Value{tval}
+			} else {
+				items = tval.List()
+			}
+
+			list := knownFields.Get(fd.Number()).List()
+			if err := o.unmarshalList(items, fd, list); !nerr.Merge(err) {
 				return err
 			}
-		} else {
+		case fd.IsMap():
+			// If input is not a list, turn it into a list.
+			var items []text.Value
+			if tval.Type() != text.List {
+				items = []text.Value{tval}
+			} else {
+				items = tval.List()
+			}
+
+			mmap := knownFields.Get(fd.Number()).Map()
+			if err := o.unmarshalMap(items, fd, mmap); !nerr.Merge(err) {
+				return err
+			}
+		default:
 			// If field is a oneof, check if it has already been set.
-			if od := fd.Oneof(); od != nil {
+			if od := fd.ContainingOneof(); od != nil {
 				idx := uint64(od.Index())
 				if seenOneofs.Has(idx) {
 					return errors.New("oneof %v is already set", od.FullName())
@@ -232,33 +254,6 @@
 	return nerr.E
 }
 
-// unmarshalRepeated unmarshals given text.Value into a repeated field. Caller should only
-// call this for cardinality=repeated.
-func (o UnmarshalOptions) unmarshalRepeated(input text.Value, fd pref.FieldDescriptor, knownFields pref.KnownFields) error {
-	var items []text.Value
-	// If input is not a list, turn it into a list.
-	if input.Type() != text.List {
-		items = []text.Value{input}
-	} else {
-		items = input.List()
-	}
-
-	var nerr errors.NonFatal
-	num := fd.Number()
-	val := knownFields.Get(num)
-	if !fd.IsMap() {
-		if err := o.unmarshalList(items, fd, val.List()); !nerr.Merge(err) {
-			return err
-		}
-	} else {
-		if err := o.unmarshalMap(items, fd, val.Map()); !nerr.Merge(err) {
-			return err
-		}
-	}
-
-	return nerr.E
-}
-
 // unmarshalScalar converts the given text.Value to a scalar/enum protoreflect.Value specified in
 // the given FieldDescriptor. Caller should not pass in a FieldDescriptor for a message/group kind.
 func unmarshalScalar(input text.Value, fd pref.FieldDescriptor) (pref.Value, error) {
@@ -358,14 +353,11 @@
 // unmarshalMap unmarshals given []text.Value into given protoreflect.Map.
 func (o UnmarshalOptions) unmarshalMap(input []text.Value, fd pref.FieldDescriptor, mmap pref.Map) error {
 	var nerr errors.NonFatal
-	fields := fd.Message().Fields()
-	keyDesc := fields.ByNumber(1)
-	valDesc := fields.ByNumber(2)
 
 	// Determine ahead whether map entry is a scalar type or a message type in order to call the
 	// appropriate unmarshalMapValue func inside the for loop below.
 	unmarshalMapValue := unmarshalMapScalarValue
-	switch valDesc.Kind() {
+	switch fd.MapValue().Kind() {
 	case pref.MessageKind, pref.GroupKind:
 		unmarshalMapValue = o.unmarshalMapMessageValue
 	}
@@ -378,11 +370,11 @@
 		if !nerr.Merge(err) {
 			return err
 		}
-		pkey, err := unmarshalMapKey(tkey, keyDesc)
+		pkey, err := unmarshalMapKey(tkey, fd.MapKey())
 		if !nerr.Merge(err) {
 			return err
 		}
-		err = unmarshalMapValue(tval, pkey, valDesc, mmap)
+		err = unmarshalMapValue(tval, pkey, fd.MapValue(), mmap)
 		if !nerr.Merge(err) {
 			return err
 		}
diff --git a/encoding/textpb/encode.go b/encoding/textpb/encode.go
index b8b2e71..9bd8279 100644
--- a/encoding/textpb/encode.go
+++ b/encoding/textpb/encode.go
@@ -132,28 +132,26 @@
 func (o MarshalOptions) appendField(msgFields [][2]text.Value, name text.Value, pval pref.Value, fd pref.FieldDescriptor) ([][2]text.Value, error) {
 	var nerr errors.NonFatal
 
-	if fd.Cardinality() == pref.Repeated {
-		// Map or repeated fields.
-		var items []text.Value
-		var err error
-		if fd.IsMap() {
-			items, err = o.marshalMap(pval.Map(), fd)
-			if !nerr.Merge(err) {
-				return msgFields, err
-			}
-		} else {
-			items, err = o.marshalList(pval.List(), fd)
-			if !nerr.Merge(err) {
-				return msgFields, err
-			}
+	switch {
+	case fd.IsList():
+		items, err := o.marshalList(pval.List(), fd)
+		if !nerr.Merge(err) {
+			return msgFields, err
 		}
 
-		// Add each item as key: value field.
 		for _, item := range items {
 			msgFields = append(msgFields, [2]text.Value{name, item})
 		}
-	} else {
-		// Required or optional fields.
+	case fd.IsMap():
+		items, err := o.marshalMap(pval.Map(), fd)
+		if !nerr.Merge(err) {
+			return msgFields, err
+		}
+
+		for _, item := range items {
+			msgFields = append(msgFields, [2]text.Value{name, item})
+		}
+	default:
 		tval, err := o.marshalSingular(pval, fd)
 		if !nerr.Merge(err) {
 			return msgFields, err
@@ -231,19 +229,16 @@
 	var nerr errors.NonFatal
 	// values is a list of messages.
 	values := make([]text.Value, 0, mmap.Len())
-	msgFields := fd.Message().Fields()
-	keyType := msgFields.ByNumber(1)
-	valType := msgFields.ByNumber(2)
 
 	var err error
-	mapsort.Range(mmap, keyType.Kind(), func(key pref.MapKey, val pref.Value) bool {
+	mapsort.Range(mmap, fd.MapKey().Kind(), func(key pref.MapKey, val pref.Value) bool {
 		var keyTxtVal text.Value
-		keyTxtVal, err = o.marshalSingular(key.Value(), keyType)
+		keyTxtVal, err = o.marshalSingular(key.Value(), fd.MapKey())
 		if !nerr.Merge(err) {
 			return false
 		}
 		var valTxtVal text.Value
-		valTxtVal, err = o.marshalSingular(val, valType)
+		valTxtVal, err = o.marshalSingular(val, fd.MapValue())
 		if !nerr.Merge(err) {
 			return false
 		}
@@ -314,7 +309,7 @@
 	if xd.FullName().Parent() != md.FullName() {
 		return false
 	}
-	xmd, ok := xd.Extendee().(interface{ IsMessageSet() bool })
+	xmd, ok := xd.ContainingMessage().(interface{ IsMessageSet() bool })
 	return ok && xmd.IsMessageSet()
 }
 
diff --git a/internal/encoding/tag/tag.go b/internal/encoding/tag/tag.go
index 72499c1..d4e868f 100644
--- a/internal/encoding/tag/tag.go
+++ b/internal/encoding/tag/tag.go
@@ -175,13 +175,13 @@
 	// The previous implementation does not tag extension fields as proto3,
 	// even when the field is defined in a proto3 file. Match that behavior
 	// for consistency.
-	if fd.Syntax() == pref.Proto3 && fd.Extendee() == nil {
+	if fd.Syntax() == pref.Proto3 && !fd.IsExtension() {
 		tag = append(tag, "proto3")
 	}
 	if fd.Kind() == pref.EnumKind && enumName != "" {
 		tag = append(tag, "enum="+enumName)
 	}
-	if fd.Oneof() != nil {
+	if fd.ContainingOneof() != nil {
 		tag = append(tag, "oneof")
 	}
 	// This must appear last in the tag, since commas in strings aren't escaped.
diff --git a/internal/fileinit/desc.go b/internal/fileinit/desc.go
index 2729da6..8854cab 100644
--- a/internal/fileinit/desc.go
+++ b/internal/fileinit/desc.go
@@ -431,23 +431,43 @@
 func (fd *fieldDesc) Options() pref.ProtoMessage {
 	return unmarshalOptions(descopts.Field, fd.options)
 }
-func (fd *fieldDesc) Number() pref.FieldNumber                   { return fd.number }
-func (fd *fieldDesc) Cardinality() pref.Cardinality              { return fd.cardinality }
-func (fd *fieldDesc) Kind() pref.Kind                            { return fd.kind }
-func (fd *fieldDesc) HasJSONName() bool                          { return fd.hasJSONName }
-func (fd *fieldDesc) JSONName() string                           { return fd.jsonName }
-func (fd *fieldDesc) IsPacked() bool                             { return fd.isPacked }
-func (fd *fieldDesc) IsWeak() bool                               { return fd.isWeak }
-func (fd *fieldDesc) IsMap() bool                                { return fd.isMap }
+func (fd *fieldDesc) Number() pref.FieldNumber      { return fd.number }
+func (fd *fieldDesc) Cardinality() pref.Cardinality { return fd.cardinality }
+func (fd *fieldDesc) Kind() pref.Kind               { return fd.kind }
+func (fd *fieldDesc) HasJSONName() bool             { return fd.hasJSONName }
+func (fd *fieldDesc) JSONName() string              { return fd.jsonName }
+func (fd *fieldDesc) IsPacked() bool                { return fd.isPacked }
+func (fd *fieldDesc) IsExtension() bool             { return false }
+func (fd *fieldDesc) IsWeak() bool                  { return fd.isWeak }
+func (fd *fieldDesc) IsList() bool                  { return fd.cardinality == pref.Repeated && !fd.IsMap() }
+func (fd *fieldDesc) IsMap() bool                   { return fd.isMap }
+func (fd *fieldDesc) MapKey() pref.FieldDescriptor {
+	if !fd.isMap {
+		return nil
+	}
+	return fd.Message().Fields().ByNumber(1)
+}
+func (fd *fieldDesc) MapValue() pref.FieldDescriptor {
+	if !fd.isMap {
+		return nil
+	}
+	return fd.Message().Fields().ByNumber(2)
+}
 func (fd *fieldDesc) HasDefault() bool                           { return fd.defVal.has }
 func (fd *fieldDesc) Default() pref.Value                        { return fd.defVal.get() }
 func (fd *fieldDesc) DefaultEnumValue() pref.EnumValueDescriptor { return fd.defVal.enum }
-func (fd *fieldDesc) Oneof() pref.OneofDescriptor                { return fd.oneofType }
-func (fd *fieldDesc) Extendee() pref.MessageDescriptor           { return nil }
-func (fd *fieldDesc) Enum() pref.EnumDescriptor                  { return fd.enumType }
-func (fd *fieldDesc) Message() pref.MessageDescriptor            { return fd.messageType }
-func (fd *fieldDesc) Format(s fmt.State, r rune)                 { pfmt.FormatDesc(s, r, fd) }
-func (fd *fieldDesc) ProtoType(pref.FieldDescriptor)             {}
+func (fd *fieldDesc) ContainingOneof() pref.OneofDescriptor      { return fd.oneofType }
+func (fd *fieldDesc) ContainingMessage() pref.MessageDescriptor {
+	return fd.parent.(pref.MessageDescriptor)
+}
+func (fd *fieldDesc) Enum() pref.EnumDescriptor       { return fd.enumType }
+func (fd *fieldDesc) Message() pref.MessageDescriptor { return fd.messageType }
+func (fd *fieldDesc) Format(s fmt.State, r rune)      { pfmt.FormatDesc(s, r, fd) }
+func (fd *fieldDesc) ProtoType(pref.FieldDescriptor)  {}
+
+// TODO: Remove this.
+func (fd *fieldDesc) Oneof() pref.OneofDescriptor      { return fd.oneofType }
+func (fd *fieldDesc) Extendee() pref.MessageDescriptor { return nil }
 
 func (od *oneofDesc) Options() pref.ProtoMessage {
 	return unmarshalOptions(descopts.Oneof, od.options)
@@ -505,13 +525,17 @@
 func (xd *extensionDesc) HasJSONName() bool                          { return xd.lazyInit().hasJSONName }
 func (xd *extensionDesc) JSONName() string                           { return xd.lazyInit().jsonName }
 func (xd *extensionDesc) IsPacked() bool                             { return xd.lazyInit().isPacked }
+func (xd *extensionDesc) IsExtension() bool                          { return true }
 func (xd *extensionDesc) IsWeak() bool                               { return false }
+func (xd *extensionDesc) IsList() bool                               { return xd.Cardinality() == pref.Repeated }
 func (xd *extensionDesc) IsMap() bool                                { return false }
+func (xd *extensionDesc) MapKey() pref.FieldDescriptor               { return nil }
+func (xd *extensionDesc) MapValue() pref.FieldDescriptor             { return nil }
 func (xd *extensionDesc) HasDefault() bool                           { return xd.lazyInit().defVal.has }
 func (xd *extensionDesc) Default() pref.Value                        { return xd.lazyInit().defVal.get() }
 func (xd *extensionDesc) DefaultEnumValue() pref.EnumValueDescriptor { return xd.lazyInit().defVal.enum }
-func (xd *extensionDesc) Oneof() pref.OneofDescriptor                { return nil }
-func (xd *extensionDesc) Extendee() pref.MessageDescriptor           { return xd.extendedType }
+func (xd *extensionDesc) ContainingOneof() pref.OneofDescriptor      { return nil }
+func (xd *extensionDesc) ContainingMessage() pref.MessageDescriptor  { return xd.extendedType }
 func (xd *extensionDesc) Enum() pref.EnumDescriptor                  { return xd.lazyInit().enumType }
 func (xd *extensionDesc) Message() pref.MessageDescriptor            { return xd.lazyInit().messageType }
 func (xd *extensionDesc) Format(s fmt.State, r rune)                 { pfmt.FormatDesc(s, r, xd) }
@@ -531,6 +555,10 @@
 	return xd.legacyDesc
 }
 
+// TODO: Remove this.
+func (xd *extensionDesc) Oneof() pref.OneofDescriptor      { return nil }
+func (xd *extensionDesc) Extendee() pref.MessageDescriptor { return xd.extendedType }
+
 type (
 	serviceDesc struct {
 		baseDesc
diff --git a/internal/fileinit/fileinit_test.go b/internal/fileinit/fileinit_test.go
index ac1c5e2..dd58fd0 100644
--- a/internal/fileinit/fileinit_test.go
+++ b/internal/fileinit/fileinit_test.go
@@ -88,7 +88,7 @@
 		f(field)
 		switch field.Kind() {
 		case protoreflect.MessageKind, protoreflect.GroupKind:
-			if field.Cardinality() == protoreflect.Repeated {
+			if field.IsList() {
 				for i, list := 0, value.List(); i < list.Len(); i++ {
 					visitFields(list.Get(i).Message(), f)
 				}
diff --git a/internal/impl/legacy_extension.go b/internal/impl/legacy_extension.go
index a0ae3f0..7a8fea7 100644
--- a/internal/impl/legacy_extension.go
+++ b/internal/impl/legacy_extension.go
@@ -64,7 +64,7 @@
 	}
 	t := extensionTypeFromDesc(x.Desc)
 	d := t.Descriptor()
-	if d.Cardinality() == pref.Repeated {
+	if d.IsList() {
 		return t.ValueOf(x.Value).List().Len() > 0
 	}
 	return true
@@ -105,7 +105,7 @@
 	}
 	t := extensionTypeFromDesc(x.Desc)
 	d := t.Descriptor()
-	if d.Cardinality() == pref.Repeated {
+	if d.IsList() {
 		t.ValueOf(x.Value).List().Truncate(0)
 		return
 	}
@@ -153,7 +153,7 @@
 
 func (p legacyExtensionTypes) Register(t pref.ExtensionType) {
 	d := t.Descriptor()
-	if p.mi.PBType.Descriptor().FullName() != d.Extendee().FullName() {
+	if p.mi.PBType.Descriptor().FullName() != d.ContainingMessage().FullName() {
 		panic("extended type mismatch")
 	}
 	if !p.mi.PBType.Descriptor().ExtensionRanges().Has(d.Number()) {
@@ -164,7 +164,7 @@
 		panic("extension descriptor already registered")
 	}
 	x.Desc = extensionDescFromType(t)
-	if d.Cardinality() == pref.Repeated {
+	if d.IsList() {
 		// If the field is repeated, initialize the entry with an empty list
 		// so that future Get operations can return a mutable and concrete list.
 		x.Value = t.InterfaceOf(t.New())
@@ -178,7 +178,7 @@
 		return
 	}
 	x := p.x.Get(d.Number())
-	if d.Cardinality() == pref.Repeated {
+	if d.IsList() {
 		// Treat an empty repeated field as unpopulated.
 		v := reflect.ValueOf(x.Value)
 		if x.Value == nil || v.IsNil() || v.Elem().Len() == 0 {
diff --git a/internal/impl/legacy_test.go b/internal/impl/legacy_test.go
index 28ab672..d5d4e54 100644
--- a/internal/impl/legacy_test.go
+++ b/internal/impl/legacy_test.go
@@ -685,7 +685,7 @@
 								// Ignore New since it a constructor.
 							case "Options":
 								// Ignore descriptor options since protos are not cmperable.
-							case "Oneof", "Extendee", "Enum", "Message":
+							case "ContainingOneof", "ContainingMessage", "Enum", "Message":
 								// Avoid descending into a dependency to avoid a cycle.
 								// Just record the full name if available.
 								//
@@ -694,6 +694,8 @@
 								if !v.IsNil() {
 									out[name] = v.Interface().(pref.Descriptor).FullName()
 								}
+							case "Oneof", "Extendee":
+								// TODO: Remove this.
 							default:
 								out[name] = m.Call(nil)[0].Interface()
 							}
diff --git a/internal/impl/message.go b/internal/impl/message.go
index 5c2ae28..a98e7bb 100644
--- a/internal/impl/message.go
+++ b/internal/impl/message.go
@@ -118,11 +118,11 @@
 		fs := si.fieldsByNumber[fd.Number()]
 		var fi fieldInfo
 		switch {
-		case fd.Oneof() != nil:
-			fi = fieldInfoForOneof(fd, si.oneofsByName[fd.Oneof().Name()], si.oneofWrappersByNumber[fd.Number()])
+		case fd.ContainingOneof() != nil:
+			fi = fieldInfoForOneof(fd, si.oneofsByName[fd.ContainingOneof().Name()], si.oneofWrappersByNumber[fd.Number()])
 		case fd.IsMap():
 			fi = fieldInfoForMap(fd, fs)
-		case fd.Cardinality() == pref.Repeated:
+		case fd.IsList():
 			fi = fieldInfoForList(fd, fs)
 		case fd.Kind() == pref.MessageKind || fd.Kind() == pref.GroupKind:
 			fi = fieldInfoForMessage(fd, fs)
diff --git a/internal/impl/message_field.go b/internal/impl/message_field.go
index 8cd82fe..10405d0 100644
--- a/internal/impl/message_field.go
+++ b/internal/impl/message_field.go
@@ -90,8 +90,8 @@
 	if ft.Kind() != reflect.Map {
 		panic(fmt.Sprintf("invalid type: got %v, want map kind", ft))
 	}
-	keyConv := pvalue.NewLegacyConverter(ft.Key(), fd.Message().Fields().ByNumber(1).Kind(), legacyWrapper)
-	valConv := pvalue.NewLegacyConverter(ft.Elem(), fd.Message().Fields().ByNumber(2).Kind(), legacyWrapper)
+	keyConv := pvalue.NewLegacyConverter(ft.Key(), fd.MapKey().Kind(), legacyWrapper)
+	valConv := pvalue.NewLegacyConverter(ft.Elem(), fd.MapValue().Kind(), legacyWrapper)
 	fieldOffset := offsetOf(fs)
 	// TODO: Implement unsafe fast path?
 	return fieldInfo{
diff --git a/internal/legacy/extension.go b/internal/legacy/extension.go
index 9174555..b700a7f 100644
--- a/internal/legacy/extension.go
+++ b/internal/legacy/extension.go
@@ -65,7 +65,8 @@
 
 	// Determine the parent type if possible.
 	var parent piface.MessageV1
-	if mt, _ := preg.GlobalTypes.FindMessageByName(xt.Descriptor().Extendee().FullName()); mt != nil {
+	messageName := xt.Descriptor().ContainingMessage().FullName()
+	if mt, _ := preg.GlobalTypes.FindMessageByName(messageName); mt != nil {
 		// Create a new parent message and unwrap it if possible.
 		mv := mt.New().Interface()
 		t := reflect.TypeOf(mv)
diff --git a/internal/legacy/file_test.go b/internal/legacy/file_test.go
index 9a9a3fc..c8d9af5 100644
--- a/internal/legacy/file_test.go
+++ b/internal/legacy/file_test.go
@@ -433,7 +433,7 @@
 					case "HasJSONName":
 						// Ignore this since the semantics of the field has
 						// changed across protoc and protoc-gen-go releases.
-					case "Oneof", "Extendee", "Enum", "Message":
+					case "ContainingOneof", "ContainingMessage", "Enum", "Message":
 						// Avoid descending into a dependency to avoid a cycle.
 						// Just record the full name if available.
 						//
@@ -442,6 +442,8 @@
 						if !v.IsNil() {
 							out[name] = v.Interface().(pref.Descriptor).FullName()
 						}
+					case "Oneof", "Extendee":
+						// TODO: Remove this.
 					default:
 						out[name] = m.Call(nil)[0].Interface()
 					}
diff --git a/internal/prototype/go_type.go b/internal/prototype/go_type.go
index 6c88a7c..6110e95 100644
--- a/internal/prototype/go_type.go
+++ b/internal/prototype/go_type.go
@@ -124,7 +124,7 @@
 // The type M is the concrete message type returned by NewMessage,
 // which is often, but not required to be, a pointer to a named struct type.
 func GoExtension(xd protoreflect.ExtensionDescriptor, et protoreflect.EnumType, mt protoreflect.MessageType) protoreflect.ExtensionType {
-	if xd.Extendee() == nil {
+	if !xd.IsExtension() {
 		panic("field descriptor does not extend a message")
 	}
 	switch xd.Kind() {
diff --git a/internal/prototype/protofile_list.go b/internal/prototype/protofile_list.go
index 426e695..99f1cc0 100644
--- a/internal/prototype/protofile_list.go
+++ b/internal/prototype/protofile_list.go
@@ -114,7 +114,7 @@
 		md, _ := parent.Parent()
 		fs := md.(pref.MessageDescriptor).Fields()
 		for i := 0; i < fs.Len(); i++ {
-			if f := fs.Get(i); od == f.Oneof() {
+			if f := fs.Get(i); od == f.ContainingOneof() {
 				p.typs = append(p.typs, f)
 			}
 		}
diff --git a/internal/prototype/protofile_type.go b/internal/prototype/protofile_type.go
index 4e3416f..a9d7bc3 100644
--- a/internal/prototype/protofile_type.go
+++ b/internal/prototype/protofile_type.go
@@ -171,21 +171,43 @@
 func (t fieldDesc) IsPacked() bool {
 	return isPacked(t.f.IsPacked, t.f.syntax, t.f.Cardinality, t.f.Kind)
 }
-func (t fieldDesc) IsWeak() bool { return t.f.IsWeak }
+func (t fieldDesc) IsExtension() bool { return false }
+func (t fieldDesc) IsWeak() bool      { return t.f.IsWeak }
+func (t fieldDesc) IsList() bool {
+	return t.f.Cardinality == pref.Repeated && !t.IsMap()
+}
 func (t fieldDesc) IsMap() bool {
 	mt := t.Message()
 	return mt != nil && mt.IsMapEntry()
 }
+func (t fieldDesc) MapKey() pref.FieldDescriptor {
+	if !t.IsMap() {
+		return nil
+	}
+	return t.Message().Fields().ByNumber(1)
+}
+func (t fieldDesc) MapValue() pref.FieldDescriptor {
+	if !t.IsMap() {
+		return nil
+	}
+	return t.Message().Fields().ByNumber(2)
+}
 func (t fieldDesc) HasDefault() bool                           { return t.f.Default.IsValid() }
 func (t fieldDesc) Default() pref.Value                        { return t.f.dv.value(t, t.f.Default) }
 func (t fieldDesc) DefaultEnumValue() pref.EnumValueDescriptor { return t.f.dv.enum(t, t.f.Default) }
-func (t fieldDesc) Oneof() pref.OneofDescriptor                { return t.f.ot.lazyInit(t, t.f.OneofName) }
-func (t fieldDesc) Extendee() pref.MessageDescriptor           { return nil }
-func (t fieldDesc) Enum() pref.EnumDescriptor                  { return t.f.et.lazyInit(t, &t.f.EnumType) }
-func (t fieldDesc) Message() pref.MessageDescriptor            { return t.f.mt.lazyInit(t, &t.f.MessageType) }
-func (t fieldDesc) Format(s fmt.State, r rune)                 { pfmt.FormatDesc(s, r, t) }
-func (t fieldDesc) ProtoType(pref.FieldDescriptor)             {}
-func (t fieldDesc) ProtoInternal(pragma.DoNotImplement)        {}
+func (t fieldDesc) ContainingOneof() pref.OneofDescriptor      { return t.f.ot.lazyInit(t, t.f.OneofName) }
+func (t fieldDesc) ContainingMessage() pref.MessageDescriptor {
+	return t.f.parent.(pref.MessageDescriptor)
+}
+func (t fieldDesc) Enum() pref.EnumDescriptor           { return t.f.et.lazyInit(t, &t.f.EnumType) }
+func (t fieldDesc) Message() pref.MessageDescriptor     { return t.f.mt.lazyInit(t, &t.f.MessageType) }
+func (t fieldDesc) Format(s fmt.State, r rune)          { pfmt.FormatDesc(s, r, t) }
+func (t fieldDesc) ProtoType(pref.FieldDescriptor)      {}
+func (t fieldDesc) ProtoInternal(pragma.DoNotImplement) {}
+
+// TODO: Remove this.
+func (t fieldDesc) Oneof() pref.OneofDescriptor      { return t.f.ot.lazyInit(t, t.f.OneofName) }
+func (t fieldDesc) Extendee() pref.MessageDescriptor { return nil }
 
 func isPacked(packed OptionalBool, s pref.Syntax, c pref.Cardinality, k pref.Kind) bool {
 	if packed == False || (packed == DefaultBool && s == pref.Proto2) {
@@ -299,18 +321,28 @@
 	// Extensions always use proto2 defaults for packing.
 	return isPacked(t.x.IsPacked, pref.Proto2, t.x.Cardinality, t.x.Kind)
 }
+func (t extensionDesc) IsExtension() bool                          { return true }
 func (t extensionDesc) IsWeak() bool                               { return false }
+func (t extensionDesc) IsList() bool                               { return t.x.Cardinality == pref.Repeated }
 func (t extensionDesc) IsMap() bool                                { return false }
+func (t extensionDesc) MapKey() pref.FieldDescriptor               { return nil }
+func (t extensionDesc) MapValue() pref.FieldDescriptor             { return nil }
 func (t extensionDesc) HasDefault() bool                           { return t.x.Default.IsValid() }
 func (t extensionDesc) Default() pref.Value                        { return t.x.dv.value(t, t.x.Default) }
 func (t extensionDesc) DefaultEnumValue() pref.EnumValueDescriptor { return t.x.dv.enum(t, t.x.Default) }
-func (t extensionDesc) Oneof() pref.OneofDescriptor                { return nil }
-func (t extensionDesc) Extendee() pref.MessageDescriptor           { return t.x.xt.lazyInit(t, &t.x.ExtendedType) }
-func (t extensionDesc) Enum() pref.EnumDescriptor                  { return t.x.et.lazyInit(t, &t.x.EnumType) }
-func (t extensionDesc) Message() pref.MessageDescriptor            { return t.x.mt.lazyInit(t, &t.x.MessageType) }
-func (t extensionDesc) Format(s fmt.State, r rune)                 { pfmt.FormatDesc(s, r, t) }
-func (t extensionDesc) ProtoType(pref.FieldDescriptor)             {}
-func (t extensionDesc) ProtoInternal(pragma.DoNotImplement)        {}
+func (t extensionDesc) ContainingOneof() pref.OneofDescriptor      { return nil }
+func (t extensionDesc) ContainingMessage() pref.MessageDescriptor {
+	return t.x.xt.lazyInit(t, &t.x.ExtendedType)
+}
+func (t extensionDesc) Enum() pref.EnumDescriptor           { return t.x.et.lazyInit(t, &t.x.EnumType) }
+func (t extensionDesc) Message() pref.MessageDescriptor     { return t.x.mt.lazyInit(t, &t.x.MessageType) }
+func (t extensionDesc) Format(s fmt.State, r rune)          { pfmt.FormatDesc(s, r, t) }
+func (t extensionDesc) ProtoType(pref.FieldDescriptor)      {}
+func (t extensionDesc) ProtoInternal(pragma.DoNotImplement) {}
+
+// TODO: Remove this.
+func (t extensionDesc) Oneof() pref.OneofDescriptor      { return nil }
+func (t extensionDesc) Extendee() pref.MessageDescriptor { return t.x.xt.lazyInit(t, &t.x.ExtendedType) }
 
 type enumMeta struct {
 	inheritedMeta
diff --git a/internal/prototype/standalone_type.go b/internal/prototype/standalone_type.go
index 3bf4115..a9b54e9 100644
--- a/internal/prototype/standalone_type.go
+++ b/internal/prototype/standalone_type.go
@@ -85,17 +85,25 @@
 func (t standaloneExtension) IsPacked() bool {
 	return isPacked(t.x.IsPacked, pref.Proto2, t.x.Cardinality, t.x.Kind)
 }
-func (t standaloneExtension) IsWeak() bool        { return false }
-func (t standaloneExtension) IsMap() bool         { return false }
-func (t standaloneExtension) HasDefault() bool    { return t.x.Default.IsValid() }
-func (t standaloneExtension) Default() pref.Value { return t.x.dv.value(t, t.x.Default) }
+func (t standaloneExtension) IsExtension() bool              { return true }
+func (t standaloneExtension) IsWeak() bool                   { return false }
+func (t standaloneExtension) IsList() bool                   { return t.x.Cardinality == pref.Repeated }
+func (t standaloneExtension) IsMap() bool                    { return false }
+func (t standaloneExtension) MapKey() pref.FieldDescriptor   { return nil }
+func (t standaloneExtension) MapValue() pref.FieldDescriptor { return nil }
+func (t standaloneExtension) HasDefault() bool               { return t.x.Default.IsValid() }
+func (t standaloneExtension) Default() pref.Value            { return t.x.dv.value(t, t.x.Default) }
 func (t standaloneExtension) DefaultEnumValue() pref.EnumValueDescriptor {
 	return t.x.dv.enum(t, t.x.Default)
 }
-func (t standaloneExtension) Oneof() pref.OneofDescriptor         { return nil }
-func (t standaloneExtension) Extendee() pref.MessageDescriptor    { return t.x.ExtendedType }
-func (t standaloneExtension) Enum() pref.EnumDescriptor           { return t.x.EnumType }
-func (t standaloneExtension) Message() pref.MessageDescriptor     { return t.x.MessageType }
-func (t standaloneExtension) Format(s fmt.State, r rune)          { pfmt.FormatDesc(s, r, t) }
-func (t standaloneExtension) ProtoType(pref.FieldDescriptor)      {}
-func (t standaloneExtension) ProtoInternal(pragma.DoNotImplement) {}
+func (t standaloneExtension) ContainingOneof() pref.OneofDescriptor     { return nil }
+func (t standaloneExtension) ContainingMessage() pref.MessageDescriptor { return t.x.ExtendedType }
+func (t standaloneExtension) Enum() pref.EnumDescriptor                 { return t.x.EnumType }
+func (t standaloneExtension) Message() pref.MessageDescriptor           { return t.x.MessageType }
+func (t standaloneExtension) Format(s fmt.State, r rune)                { pfmt.FormatDesc(s, r, t) }
+func (t standaloneExtension) ProtoType(pref.FieldDescriptor)            {}
+func (t standaloneExtension) ProtoInternal(pragma.DoNotImplement)       {}
+
+// TODO: Remove this.
+func (t standaloneExtension) Oneof() pref.OneofDescriptor      { return nil }
+func (t standaloneExtension) Extendee() pref.MessageDescriptor { return t.x.ExtendedType }
diff --git a/internal/prototype/type_test.go b/internal/prototype/type_test.go
index a8c171b..c00a816 100644
--- a/internal/prototype/type_test.go
+++ b/internal/prototype/type_test.go
@@ -18,6 +18,7 @@
 	scalar "github.com/golang/protobuf/v2/internal/scalar"
 	pdesc "github.com/golang/protobuf/v2/reflect/protodesc"
 	pref "github.com/golang/protobuf/v2/reflect/protoreflect"
+	"github.com/google/go-cmp/cmp"
 
 	descriptorpb "github.com/golang/protobuf/v2/types/descriptor"
 )
@@ -391,42 +392,46 @@
 				"Fields": M{
 					"Len": 2,
 					"ByNumber:1": M{
-						"Parent":      M{"FullName": pref.FullName("test.A")},
-						"Index":       0,
-						"Name":        pref.Name("key"),
-						"FullName":    pref.FullName("test.A.key"),
-						"Number":      pref.FieldNumber(1),
-						"Cardinality": pref.Optional,
-						"Kind":        pref.StringKind,
-						"Options":     &descriptorpb.FieldOptions{Deprecated: scalar.Bool(true)},
-						"HasJSONName": false,
-						"JSONName":    "key",
-						"IsPacked":    false,
-						"IsMap":       false,
-						"IsWeak":      false,
-						"Default":     "",
-						"Oneof":       nil,
-						"Extendee":    nil,
-						"Message":     nil,
-						"Enum":        nil,
+						"Parent":            M{"FullName": pref.FullName("test.A")},
+						"Index":             0,
+						"Name":              pref.Name("key"),
+						"FullName":          pref.FullName("test.A.key"),
+						"Number":            pref.FieldNumber(1),
+						"Cardinality":       pref.Optional,
+						"Kind":              pref.StringKind,
+						"Options":           &descriptorpb.FieldOptions{Deprecated: scalar.Bool(true)},
+						"HasJSONName":       false,
+						"JSONName":          "key",
+						"IsPacked":          false,
+						"IsList":            false,
+						"IsMap":             false,
+						"IsExtension":       false,
+						"IsWeak":            false,
+						"Default":           "",
+						"ContainingOneof":   nil,
+						"ContainingMessage": M{"FullName": pref.FullName("test.A")},
+						"Message":           nil,
+						"Enum":              nil,
 					},
 					"ByNumber:2": M{
-						"Parent":      M{"FullName": pref.FullName("test.A")},
-						"Index":       1,
-						"Name":        pref.Name("value"),
-						"FullName":    pref.FullName("test.A.value"),
-						"Number":      pref.FieldNumber(2),
-						"Cardinality": pref.Optional,
-						"Kind":        pref.MessageKind,
-						"JSONName":    "value",
-						"IsPacked":    false,
-						"IsMap":       false,
-						"IsWeak":      false,
-						"Default":     nil,
-						"Oneof":       nil,
-						"Extendee":    nil,
-						"Message":     M{"FullName": pref.FullName("test.B"), "IsPlaceholder": false},
-						"Enum":        nil,
+						"Parent":            M{"FullName": pref.FullName("test.A")},
+						"Index":             1,
+						"Name":              pref.Name("value"),
+						"FullName":          pref.FullName("test.A.value"),
+						"Number":            pref.FieldNumber(2),
+						"Cardinality":       pref.Optional,
+						"Kind":              pref.MessageKind,
+						"JSONName":          "value",
+						"IsPacked":          false,
+						"IsList":            false,
+						"IsMap":             false,
+						"IsExtension":       false,
+						"IsWeak":            false,
+						"Default":           nil,
+						"ContainingOneof":   nil,
+						"ContainingMessage": M{"FullName": pref.FullName("test.A")},
+						"Message":           M{"FullName": pref.FullName("test.B"), "IsPlaceholder": false},
+						"Enum":              nil,
 					},
 					"ByNumber:3": nil,
 				},
@@ -444,31 +449,40 @@
 					"Len":                  6,
 					"ByJSONName:field_one": nil,
 					"ByJSONName:fieldOne": M{
-						"Name":     pref.Name("field_one"),
-						"Index":    0,
-						"JSONName": "fieldOne",
-						"Default":  "hello, \"world!\"\n",
-						"Oneof":    M{"Name": pref.Name("O1"), "IsPlaceholder": false},
+						"Name":              pref.Name("field_one"),
+						"Index":             0,
+						"JSONName":          "fieldOne",
+						"Default":           "hello, \"world!\"\n",
+						"ContainingOneof":   M{"Name": pref.Name("O1"), "IsPlaceholder": false},
+						"ContainingMessage": M{"FullName": pref.FullName("test.B")},
 					},
 					"ByJSONName:fieldTwo": nil,
 					"ByJSONName:Field2": M{
-						"Name":        pref.Name("field_two"),
-						"Index":       1,
-						"HasJSONName": true,
-						"JSONName":    "Field2",
-						"Default":     pref.EnumNumber(1),
-						"Oneof":       M{"Name": pref.Name("O2"), "IsPlaceholder": false},
+						"Name":            pref.Name("field_two"),
+						"Index":           1,
+						"HasJSONName":     true,
+						"JSONName":        "Field2",
+						"Default":         pref.EnumNumber(1),
+						"ContainingOneof": M{"Name": pref.Name("O2"), "IsPlaceholder": false},
 					},
 					"ByName:fieldThree": nil,
 					"ByName:field_three": M{
-						"IsMap":   false,
-						"Message": M{"FullName": pref.FullName("test.C"), "IsPlaceholder": false},
-						"Oneof":   M{"Name": pref.Name("O2"), "IsPlaceholder": false},
+						"IsExtension":       false,
+						"IsMap":             false,
+						"MapKey":            nil,
+						"MapValue":          nil,
+						"Message":           M{"FullName": pref.FullName("test.C"), "IsPlaceholder": false},
+						"ContainingOneof":   M{"Name": pref.Name("O2"), "IsPlaceholder": false},
+						"ContainingMessage": M{"FullName": pref.FullName("test.B")},
 					},
 					"ByNumber:12": nil,
 					"ByNumber:4": M{
 						"Cardinality": pref.Repeated,
+						"IsExtension": false,
+						"IsList":      false,
 						"IsMap":       true,
+						"MapKey":      M{"Kind": pref.StringKind},
+						"MapValue":    M{"Kind": pref.MessageKind, "Message": M{"FullName": pref.FullName("test.B")}},
 						"Default":     nil,
 						"Message":     M{"FullName": pref.FullName("test.A"), "IsPlaceholder": false},
 					},
@@ -476,12 +490,14 @@
 						"Cardinality": pref.Repeated,
 						"Kind":        pref.Int32Kind,
 						"IsPacked":    true,
+						"IsList":      true,
+						"IsMap":       false,
 						"Default":     int32(0),
 					},
 					"ByNumber:6": M{
-						"Cardinality": pref.Required,
-						"Default":     []byte(nil),
-						"Oneof":       nil,
+						"Cardinality":     pref.Required,
+						"Default":         []byte(nil),
+						"ContainingOneof": nil,
 					},
 				},
 				"Oneofs": M{
@@ -601,14 +617,20 @@
 		"Extensions": M{
 			"Len": 1,
 			"ByName:X": M{
-				"Name":        pref.Name("X"),
-				"Number":      pref.FieldNumber(1000),
-				"Cardinality": pref.Repeated,
-				"Kind":        pref.MessageKind,
-				"IsPacked":    false,
-				"Message":     M{"FullName": pref.FullName("test.C"), "IsPlaceholder": false},
-				"Extendee":    M{"FullName": pref.FullName("test.B"), "IsPlaceholder": false},
-				"Options":     &descriptorpb.FieldOptions{Packed: scalar.Bool(true)},
+				"Name":              pref.Name("X"),
+				"Number":            pref.FieldNumber(1000),
+				"Cardinality":       pref.Repeated,
+				"Kind":              pref.MessageKind,
+				"IsExtension":       true,
+				"IsPacked":          false,
+				"IsList":            true,
+				"IsMap":             false,
+				"MapKey":            nil,
+				"MapValue":          nil,
+				"ContainingOneof":   nil,
+				"ContainingMessage": M{"FullName": pref.FullName("test.B"), "IsPlaceholder": false},
+				"Message":           M{"FullName": pref.FullName("test.C"), "IsPlaceholder": false},
+				"Options":           &descriptorpb.FieldOptions{Packed: scalar.Bool(true)},
 			},
 		},
 		"Services": M{
@@ -768,7 +790,8 @@
 			HasJSONName: true
 			JSONName:    "Field4"
 			IsMap:       true
-			Message:     test.A
+			MapKey:      string
+			MapValue:    test.B
 		}, {
 			Name:        field_five
 			Number:      5
@@ -776,6 +799,7 @@
 			Kind:        int32
 			JSONName:    "fieldFive"
 			IsPacked:    true
+			IsList:      true
 		}, {
 			Name:        field_six
 			Number:      6
@@ -821,6 +845,8 @@
 			Number:      1000
 			Cardinality: repeated
 			Kind:        message
+			IsExtension: true
+			IsList:      true
 			Extendee:    test.B
 			Message:     test.C
 		}]
@@ -839,6 +865,8 @@
 		Number:      1000
 		Cardinality: repeated
 		Kind:        message
+		IsExtension: true
+		IsList:      true
 		Extendee:    test.B
 		Message:     test.C
 	}]
@@ -856,8 +884,8 @@
 	tests := []struct{ fmt, want string }{{"%v", compactMultiFormat(want)}, {"%+v", want}}
 	for _, tt := range tests {
 		got := fmt.Sprintf(tt.fmt, fd)
-		if got != tt.want {
-			t.Errorf("fmt.Sprintf(%q, fd):\ngot:  %s\nwant: %s", tt.fmt, got, tt.want)
+		if diff := cmp.Diff(got, tt.want); diff != "" {
+			t.Errorf("fmt.Sprintf(%q, fd) mismatch (-got +want):\n%s", tt.fmt, diff)
 		}
 	}
 }
diff --git a/internal/typefmt/desc_test.go b/internal/typefmt/desc_test.go
index 296131c..ab3f9b4 100644
--- a/internal/typefmt/desc_test.go
+++ b/internal/typefmt/desc_test.go
@@ -23,6 +23,12 @@
 
 		"DescriptorByName": true, // specific to FileDescriptor
 		"DefaultEnumValue": true, // specific to FieldDescriptor
+		"MapKey":           true, // specific to FieldDescriptor
+		"MapValue":         true, // specific to FieldDescriptor
+
+		// TODO: Remove this.
+		"Oneof":    true, // specific to FieldDescriptor
+		"Extendee": true, // specific to FieldDescriptor
 
 		// TODO: These should be removed or handled.
 		"DescriptorProto":       true,
diff --git a/internal/typefmt/stringer.go b/internal/typefmt/stringer.go
index eec6fd2..fdb1bdc 100644
--- a/internal/typefmt/stringer.go
+++ b/internal/typefmt/stringer.go
@@ -106,7 +106,7 @@
 var descriptorAccessors = map[reflect.Type][]string{
 	reflect.TypeOf((*pref.FileDescriptor)(nil)).Elem():      {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"},
 	reflect.TypeOf((*pref.MessageDescriptor)(nil)).Elem():   {"IsMapEntry", "Fields", "Oneofs", "ReservedNames", "ReservedRanges", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"},
-	reflect.TypeOf((*pref.FieldDescriptor)(nil)).Elem():     {"Number", "Cardinality", "Kind", "HasJSONName", "JSONName", "IsPacked", "IsMap", "IsWeak", "HasDefault", "Default", "Oneof", "Extendee", "Message", "Enum"},
+	reflect.TypeOf((*pref.FieldDescriptor)(nil)).Elem():     {"Number", "Cardinality", "Kind", "HasJSONName", "JSONName", "IsPacked", "IsExtension", "IsWeak", "IsList", "IsMap", "MapKey", "MapValue", "HasDefault", "Default", "ContainingOneof", "ContainingMessage", "Message", "Enum"},
 	reflect.TypeOf((*pref.OneofDescriptor)(nil)).Elem():     {"Fields"}, // not directly used; must keep in sync with formatDescOpt
 	reflect.TypeOf((*pref.EnumDescriptor)(nil)).Elem():      {"Values", "ReservedNames", "ReservedRanges"},
 	reflect.TypeOf((*pref.EnumValueDescriptor)(nil)).Elem(): {"Number"},
@@ -143,7 +143,42 @@
 		default:
 			rs.Append(rv, "Name")
 		}
-		if t, ok := t.(pref.OneofDescriptor); ok {
+		switch t := t.(type) {
+		case pref.FieldDescriptor:
+			for _, s := range descriptorAccessors[rt] {
+				switch s {
+				case "MapKey":
+					if k := t.MapKey(); k != nil {
+						rs.recs = append(rs.recs, [2]string{"MapKey", k.Kind().String()})
+					}
+				case "MapValue":
+					if v := t.MapValue(); v != nil {
+						switch v.Kind() {
+						case pref.EnumKind:
+							rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Enum().FullName())})
+						case pref.MessageKind, pref.GroupKind:
+							rs.recs = append(rs.recs, [2]string{"MapValue", string(v.Message().FullName())})
+						default:
+							rs.recs = append(rs.recs, [2]string{"MapValue", v.Kind().String()})
+						}
+					}
+				case "ContainingOneof":
+					if od := t.ContainingOneof(); od != nil {
+						rs.recs = append(rs.recs, [2]string{"Oneof", string(od.Name())})
+					}
+				case "ContainingMessage":
+					if t.IsExtension() {
+						rs.recs = append(rs.recs, [2]string{"Extendee", string(t.ContainingMessage().FullName())})
+					}
+				case "Message":
+					if !t.IsMap() {
+						rs.Append(rv, s)
+					}
+				default:
+					rs.Append(rv, s)
+				}
+			}
+		case pref.OneofDescriptor:
 			var ss []string
 			fs := t.Fields()
 			for i := 0; i < fs.Len(); i++ {
@@ -152,7 +187,7 @@
 			if len(ss) > 0 {
 				rs.recs = append(rs.recs, [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
 			}
-		} else {
+		default:
 			rs.Append(rv, descriptorAccessors[rt]...)
 		}
 		if rv.MethodByName("GoType").IsValid() {
diff --git a/proto/decode.go b/proto/decode.go
index 51b2068..37334be 100644
--- a/proto/decode.go
+++ b/proto/decode.go
@@ -105,12 +105,12 @@
 		switch {
 		case fieldDesc == nil:
 			err = errUnknown
-		case fieldDesc.Cardinality() != protoreflect.Repeated:
-			valLen, err = o.unmarshalScalarField(b[tagLen:], wtyp, num, knownFields, fieldDesc)
-		case !fieldDesc.IsMap():
+		case fieldDesc.IsList():
 			valLen, err = o.unmarshalList(b[tagLen:], wtyp, num, knownFields.Get(num).List(), fieldDesc)
-		default:
+		case fieldDesc.IsMap():
 			valLen, err = o.unmarshalMap(b[tagLen:], wtyp, num, knownFields.Get(num).Map(), fieldDesc)
+		default:
+			valLen, err = o.unmarshalScalarField(b[tagLen:], wtyp, num, knownFields, fieldDesc)
 		}
 		if err == errUnknown {
 			valLen = wire.ConsumeFieldValue(num, wtyp, b[tagLen:])
@@ -140,7 +140,7 @@
 		// TODO: C++ merges into oneofs, while v1 does not.
 		// Evaluate which behavior to pick.
 		var m protoreflect.Message
-		if knownFields.Has(num) && field.Oneof() == nil {
+		if knownFields.Has(num) && field.ContainingOneof() == nil {
 			m = knownFields.Get(num).Message()
 		} else {
 			m = knownFields.NewMessage(num)
@@ -166,8 +166,8 @@
 		return 0, wire.ParseError(n)
 	}
 	var (
-		keyField = field.Message().Fields().ByNumber(1)
-		valField = field.Message().Fields().ByNumber(2)
+		keyField = field.MapKey()
+		valField = field.MapValue()
 		key      protoreflect.Value
 		val      protoreflect.Value
 		haveKey  bool
diff --git a/proto/encode.go b/proto/encode.go
index 255d0a3..96d6799 100644
--- a/proto/encode.go
+++ b/proto/encode.go
@@ -176,25 +176,52 @@
 	}
 }
 
-func (o MarshalOptions) marshalField(b []byte, field protoreflect.FieldDescriptor, value protoreflect.Value) ([]byte, error) {
-	num := field.Number()
-	kind := field.Kind()
+func (o MarshalOptions) marshalField(b []byte, fd protoreflect.FieldDescriptor, value protoreflect.Value) ([]byte, error) {
+	num := fd.Number()
+	kind := fd.Kind()
 	switch {
-	case field.Cardinality() != protoreflect.Repeated:
-		b = wire.AppendTag(b, num, wireTypes[kind])
-		return o.marshalSingular(b, num, field, value)
-	case field.IsMap():
-		return o.marshalMap(b, num, kind, field.Message(), value.Map())
-	case field.IsPacked():
-		return o.marshalPacked(b, num, field, value.List())
+	case fd.IsList():
+		return o.marshalList(b, num, fd, value.List())
+	case fd.IsMap():
+		return o.marshalMap(b, num, fd, value.Map())
 	default:
-		return o.marshalList(b, num, field, value.List())
+		b = wire.AppendTag(b, num, wireTypes[kind])
+		return o.marshalSingular(b, num, fd, value)
 	}
 }
 
-func (o MarshalOptions) marshalMap(b []byte, num wire.Number, kind protoreflect.Kind, mdesc protoreflect.MessageDescriptor, mapv protoreflect.Map) ([]byte, error) {
-	keyf := mdesc.Fields().ByNumber(1)
-	valf := mdesc.Fields().ByNumber(2)
+func (o MarshalOptions) marshalList(b []byte, num wire.Number, fd protoreflect.FieldDescriptor, list protoreflect.List) ([]byte, error) {
+	if fd.IsPacked() {
+		b = wire.AppendTag(b, num, wire.BytesType)
+		b, pos := appendSpeculativeLength(b)
+		var nerr errors.NonFatal
+		for i, llen := 0, list.Len(); i < llen; i++ {
+			var err error
+			b, err = o.marshalSingular(b, num, fd, list.Get(i))
+			if !nerr.Merge(err) {
+				return b, err
+			}
+		}
+		b = finishSpeculativeLength(b, pos)
+		return b, nerr.E
+	}
+
+	kind := fd.Kind()
+	var nerr errors.NonFatal
+	for i, llen := 0, list.Len(); i < llen; i++ {
+		var err error
+		b = wire.AppendTag(b, num, wireTypes[kind])
+		b, err = o.marshalSingular(b, num, fd, list.Get(i))
+		if !nerr.Merge(err) {
+			return b, err
+		}
+	}
+	return b, nerr.E
+}
+
+func (o MarshalOptions) marshalMap(b []byte, num wire.Number, fd protoreflect.FieldDescriptor, mapv protoreflect.Map) ([]byte, error) {
+	keyf := fd.MapKey()
+	valf := fd.MapValue()
 	var nerr errors.NonFatal
 	var err error
 	o.rangeMap(mapv, keyf.Kind(), func(key protoreflect.MapKey, value protoreflect.Value) bool {
@@ -229,35 +256,6 @@
 	mapsort.Range(mapv, kind, f)
 }
 
-func (o MarshalOptions) marshalPacked(b []byte, num wire.Number, field protoreflect.FieldDescriptor, list protoreflect.List) ([]byte, error) {
-	b = wire.AppendTag(b, num, wire.BytesType)
-	b, pos := appendSpeculativeLength(b)
-	var nerr errors.NonFatal
-	for i, llen := 0, list.Len(); i < llen; i++ {
-		var err error
-		b, err = o.marshalSingular(b, num, field, list.Get(i))
-		if !nerr.Merge(err) {
-			return b, err
-		}
-	}
-	b = finishSpeculativeLength(b, pos)
-	return b, nerr.E
-}
-
-func (o MarshalOptions) marshalList(b []byte, num wire.Number, field protoreflect.FieldDescriptor, list protoreflect.List) ([]byte, error) {
-	kind := field.Kind()
-	var nerr errors.NonFatal
-	for i, llen := 0, list.Len(); i < llen; i++ {
-		var err error
-		b = wire.AppendTag(b, num, wireTypes[kind])
-		b, err = o.marshalSingular(b, num, field, list.Get(i))
-		if !nerr.Merge(err) {
-			return b, err
-		}
-	}
-	return b, nerr.E
-}
-
 // When encoding length-prefixed fields, we speculatively set aside some number of bytes
 // for the length, encode the data, and then encode the length (shifting the data if necessary
 // to make room).
diff --git a/proto/equal.go b/proto/equal.go
index 1b3c868..3931b1a 100644
--- a/proto/equal.go
+++ b/proto/equal.go
@@ -98,10 +98,10 @@
 // equalFields compares two fields.
 func equalFields(fd pref.FieldDescriptor, a, b pref.Value) bool {
 	switch {
+	case fd.IsList():
+		return equalList(fd, a.List(), b.List())
 	case fd.IsMap():
 		return equalMap(fd, a.Map(), b.Map())
-	case fd.Cardinality() == pref.Repeated:
-		return equalList(fd, a.List(), b.List())
 	default:
 		return equalValue(fd, a, b)
 	}
@@ -109,7 +109,6 @@
 
 // equalMap compares a map field.
 func equalMap(fd pref.FieldDescriptor, a, b pref.Map) bool {
-	fdv := fd.Message().Fields().ByNumber(2)
 	alen := a.Len()
 	if alen != b.Len() {
 		return false
@@ -117,7 +116,7 @@
 	equal := true
 	a.Range(func(k pref.MapKey, va pref.Value) bool {
 		vb := b.Get(k)
-		if !vb.IsValid() || !equalValue(fdv, va, vb) {
+		if !vb.IsValid() || !equalValue(fd.MapValue(), va, vb) {
 			equal = false
 			return false
 		}
diff --git a/proto/isinit.go b/proto/isinit.go
index 199f181..01f956d 100644
--- a/proto/isinit.go
+++ b/proto/isinit.go
@@ -53,20 +53,14 @@
 			return true
 		}
 		if field.IsMap() {
-			if md.Fields().ByNumber(2).Message() == nil {
+			if field.MapValue().Message() == nil {
 				return true
 			}
 		}
 		// Recurse into the field
 		stack := append(stack, field.Name())
 		switch {
-		case field.IsMap():
-			v.Map().Range(func(key pref.MapKey, v pref.Value) bool {
-				stack := append(stack, "[", key, "].")
-				err = isInitialized(v.Message(), stack)
-				return err == nil
-			})
-		case field.Cardinality() == pref.Repeated:
+		case field.IsList():
 			for i, list := 0, v.List(); i < list.Len(); i++ {
 				stack := append(stack, "[", i, "].")
 				err = isInitialized(list.Get(i).Message(), stack)
@@ -74,6 +68,12 @@
 					break
 				}
 			}
+		case field.IsMap():
+			v.Map().Range(func(key pref.MapKey, v pref.Value) bool {
+				stack := append(stack, "[", key, "].")
+				err = isInitialized(v.Message(), stack)
+				return err == nil
+			})
 		default:
 			stack := append(stack, ".")
 			err = isInitialized(v.Message(), stack)
diff --git a/proto/size.go b/proto/size.go
index b881653..e2a75b6 100644
--- a/proto/size.go
+++ b/proto/size.go
@@ -54,43 +54,38 @@
 	return size
 }
 
-func sizeField(field protoreflect.FieldDescriptor, value protoreflect.Value) (size int) {
-	num := field.Number()
-	kind := field.Kind()
+func sizeField(fd protoreflect.FieldDescriptor, value protoreflect.Value) (size int) {
+	num := fd.Number()
 	switch {
-	case field.Cardinality() != protoreflect.Repeated:
-		return wire.SizeTag(num) + sizeSingular(num, kind, value)
-	case field.IsMap():
-		return sizeMap(num, kind, field.Message(), value.Map())
-	case field.IsPacked():
-		return sizePacked(num, kind, value.List())
+	case fd.IsList():
+		return sizeList(num, fd, value.List())
+	case fd.IsMap():
+		return sizeMap(num, fd, value.Map())
 	default:
-		return sizeList(num, kind, value.List())
+		return wire.SizeTag(num) + sizeSingular(num, fd.Kind(), value)
 	}
 }
 
-func sizeMap(num wire.Number, kind protoreflect.Kind, mdesc protoreflect.MessageDescriptor, mapv protoreflect.Map) (size int) {
-	keyf := mdesc.Fields().ByNumber(1)
-	valf := mdesc.Fields().ByNumber(2)
-	mapv.Range(func(key protoreflect.MapKey, value protoreflect.Value) bool {
-		size += wire.SizeTag(num)
-		size += wire.SizeBytes(sizeField(keyf, key.Value()) + sizeField(valf, value))
-		return true
-	})
+func sizeList(num wire.Number, fd protoreflect.FieldDescriptor, list protoreflect.List) (size int) {
+	if fd.IsPacked() {
+		content := 0
+		for i, llen := 0, list.Len(); i < llen; i++ {
+			content += sizeSingular(num, fd.Kind(), list.Get(i))
+		}
+		return wire.SizeTag(num) + wire.SizeBytes(content)
+	}
+
+	for i, llen := 0, list.Len(); i < llen; i++ {
+		size += wire.SizeTag(num) + sizeSingular(num, fd.Kind(), list.Get(i))
+	}
 	return size
 }
 
-func sizePacked(num wire.Number, kind protoreflect.Kind, list protoreflect.List) (size int) {
-	content := 0
-	for i, llen := 0, list.Len(); i < llen; i++ {
-		content += sizeSingular(num, kind, list.Get(i))
-	}
-	return wire.SizeTag(num) + wire.SizeBytes(content)
-}
-
-func sizeList(num wire.Number, kind protoreflect.Kind, list protoreflect.List) (size int) {
-	for i, llen := 0, list.Len(); i < llen; i++ {
-		size += wire.SizeTag(num) + sizeSingular(num, kind, list.Get(i))
-	}
+func sizeMap(num wire.Number, fd protoreflect.FieldDescriptor, mapv protoreflect.Map) (size int) {
+	mapv.Range(func(key protoreflect.MapKey, value protoreflect.Value) bool {
+		size += wire.SizeTag(num)
+		size += wire.SizeBytes(sizeField(fd.MapKey(), key.Value()) + sizeField(fd.MapValue(), value))
+		return true
+	})
 	return size
 }
diff --git a/protogen/protogen.go b/protogen/protogen.go
index 41c2392..d84c6d0 100644
--- a/protogen/protogen.go
+++ b/protogen/protogen.go
@@ -637,9 +637,9 @@
 func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) *Field {
 	var loc Location
 	switch {
-	case desc.Extendee() != nil && message == nil:
+	case desc.IsExtension() && message == nil:
 		loc = f.location(fieldnum.FileDescriptorProto_Extension, int32(desc.Index()))
-	case desc.Extendee() != nil && message != nil:
+	case desc.IsExtension() && message != nil:
 		loc = message.Location.appendPath(fieldnum.DescriptorProto_Extension, int32(desc.Index()))
 	default:
 		loc = message.Location.appendPath(fieldnum.DescriptorProto_Field, int32(desc.Index()))
@@ -650,8 +650,8 @@
 		Parent:   message,
 		Location: loc,
 	}
-	if desc.Oneof() != nil {
-		field.Oneof = message.Oneofs[desc.Oneof().Index()]
+	if desc.ContainingOneof() != nil {
+		field.Oneof = message.Oneofs[desc.ContainingOneof().Index()]
 	}
 	return field
 }
@@ -677,8 +677,8 @@
 		}
 		field.Enum = enum
 	}
-	if desc.Extendee() != nil {
-		mname := desc.Extendee().FullName()
+	if desc.IsExtension() {
+		mname := desc.ContainingMessage().FullName()
 		message, ok := gen.messagesByName[mname]
 		if !ok {
 			return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), mname)
diff --git a/reflect/protodesc/toproto.go b/reflect/protodesc/toproto.go
index ad288a2..cba59db 100644
--- a/reflect/protodesc/toproto.go
+++ b/reflect/protodesc/toproto.go
@@ -96,12 +96,14 @@
 // google.protobuf.FieldDescriptorProto.
 func ToFieldDescriptorProto(field protoreflect.FieldDescriptor) *descriptorpb.FieldDescriptorProto {
 	p := &descriptorpb.FieldDescriptorProto{
-		Name:     scalar.String(string(field.Name())),
-		Number:   scalar.Int32(int32(field.Number())),
-		Label:    descriptorpb.FieldDescriptorProto_Label(field.Cardinality()).Enum(),
-		Type:     descriptorpb.FieldDescriptorProto_Type(field.Kind()).Enum(),
-		Extendee: fullNameOf(field.Extendee()),
-		Options:  field.Options().(*descriptorpb.FieldOptions),
+		Name:    scalar.String(string(field.Name())),
+		Number:  scalar.Int32(int32(field.Number())),
+		Label:   descriptorpb.FieldDescriptorProto_Label(field.Cardinality()).Enum(),
+		Type:    descriptorpb.FieldDescriptorProto_Type(field.Kind()).Enum(),
+		Options: field.Options().(*descriptorpb.FieldOptions),
+	}
+	if field.IsExtension() {
+		p.Extendee = fullNameOf(field.ContainingMessage())
 	}
 	switch field.Kind() {
 	case protoreflect.EnumKind:
@@ -125,7 +127,7 @@
 			p.DefaultValue = scalar.String(def)
 		}
 	}
-	if oneof := field.Oneof(); oneof != nil {
+	if oneof := field.ContainingOneof(); oneof != nil {
 		p.OneofIndex = scalar.Int32(int32(oneof.Index()))
 	}
 	return p
diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go
index 3971b44..231c18e 100644
--- a/reflect/protoreflect/type.go
+++ b/reflect/protoreflect/type.go
@@ -285,23 +285,41 @@
 	// It is usually the camel-cased form of the field name.
 	JSONName() string
 
-	// IsPacked reports whether repeated primitive numeric kinds should be
-	// serialized using a packed encoding.
-	// If true, then it implies Cardinality is Repeated.
-	IsPacked() bool
+	// IsExtension reports whether this is an extension field. If false,
+	// then Parent and ContainingMessage refer to the same message.
+	// Otherwise, ContainingMessage and Parent almost certainly differ.
+	IsExtension() bool
 
 	// IsWeak reports whether this is a weak field, which does not impose a
 	// direct dependency on the target type.
 	// If true, then MessageDescriptor returns a placeholder type.
 	IsWeak() bool
 
-	// IsMap reports whether this field represents a map.
-	// The value type for the associated field is a Map instead of a List.
-	//
-	// If true, it implies that Kind is MessageKind, Cardinality is Repeated,
-	// and MessageDescriptor.IsMapEntry is true.
+	// IsPacked reports whether repeated primitive numeric kinds should be
+	// serialized using a packed encoding.
+	// If true, then it implies Cardinality is Repeated.
+	IsPacked() bool
+
+	// IsList reports whether this field represents a list,
+	// where the value type for the associated field is a List.
+	// It is equivalent to checking whether Cardinality is Repeated and
+	// that IsMap reports false.
+	IsList() bool
+
+	// IsMap reports whether this field represents a map,
+	// where the value type for the associated field is a Map.
+	// It is equivalent to checking whether Cardinality is Repeated,
+	// that the Kind is MessageKind, and that Message.IsMapEntry reports true.
 	IsMap() bool
 
+	// MapKey returns the field descriptor for the key in the map entry.
+	// It returns nil if IsMap reports false.
+	MapKey() FieldDescriptor
+
+	// MapValue returns the field descriptor for the value in the map entry.
+	// It returns nil if IsMap reports false.
+	MapValue() FieldDescriptor
+
 	// HasDefault reports whether this field has a default value.
 	HasDefault() bool
 
@@ -312,19 +330,26 @@
 	// The Value type is determined by the Kind.
 	Default() Value
 
-	// DefaultEnumValue returns the EnumValueDescriptor for the default value
+	// DefaultEnumValue returns the enum value descriptor for the default value
 	// of an enum field, and is nil for any other kind of field.
 	DefaultEnumValue() EnumValueDescriptor
 
-	// Oneof is the containing oneof that this field belongs to,
-	// and is nil if this field is not part of a oneof.
+	// TODO: Remove this.
+	// Deprecated: Use ContainingOneof instead.
 	Oneof() OneofDescriptor
-
-	// Extendee returns a message descriptor for the extended message
-	// that this extension field belongs in.
-	// It returns nil if this field is not an extension.
+	// TODO: Remove this.
+	// Deprecated: Use ContainingMessage instead.
 	Extendee() MessageDescriptor
 
+	// ContainingOneof is the containing oneof that this field belongs to,
+	// and is nil if this field is not part of a oneof.
+	ContainingOneof() OneofDescriptor
+
+	// ContainingMessage is the containing message that this field belongs to.
+	// For extension fields, this may not necessarily be the parent message
+	// that the field is declared within.
+	ContainingMessage() MessageDescriptor
+
 	// Enum is the enum descriptor if Kind is EnumKind.
 	// It returns nil for any other Kind.
 	Enum() EnumDescriptor
diff --git a/reflect/protoregistry/registry.go b/reflect/protoregistry/registry.go
index 84e276a..9d021de 100644
--- a/reflect/protoregistry/registry.go
+++ b/reflect/protoregistry/registry.go
@@ -381,7 +381,7 @@
 			// Check for conflicts in extensionsByMessage.
 			if xt, _ := typ.(protoreflect.ExtensionType); xt != nil {
 				field := xt.Descriptor().Number()
-				message := xt.Descriptor().Extendee().FullName()
+				message := xt.Descriptor().ContainingMessage().FullName()
 				if r.extensionsByMessage[message][field] != nil {
 					if firstErr == nil {
 						firstErr = errors.New("extension %v is already registered on message %v", name, message)
diff --git a/testing/prototest/prototest.go b/testing/prototest/prototest.go
index f3a1a9f..058ef24 100644
--- a/testing/prototest/prototest.go
+++ b/testing/prototest/prototest.go
@@ -26,10 +26,10 @@
 	for i := 0; i < md.Fields().Len(); i++ {
 		fd := md.Fields().Get(i)
 		switch {
+		case fd.IsList():
+			testFieldList(t, m, fd)
 		case fd.IsMap():
 			testFieldMap(t, m, fd)
-		case fd.Cardinality() == pref.Repeated:
-			testFieldList(t, m, fd)
 		case fd.Kind() == pref.FloatKind || fd.Kind() == pref.DoubleKind:
 			testFieldFloat(t, m, fd)
 		}
@@ -105,7 +105,7 @@
 			if fd.Cardinality() == pref.Repeated {
 				wantHas = false
 			}
-			if fd.Oneof() != nil {
+			if fd.ContainingOneof() != nil {
 				wantHas = true
 			}
 		}
@@ -122,12 +122,12 @@
 		t.Errorf("after clearing %q:\nHas(%v) = %v, want %v", name, num, got, want)
 	}
 	switch {
-	case fd.IsMap():
-		if got := known.Get(num); got.Map().Len() != 0 {
+	case fd.IsList():
+		if got := known.Get(num); got.List().Len() != 0 {
 			t.Errorf("after clearing %q:\nGet(%v) = %v, want empty list", name, num, formatValue(got))
 		}
-	case fd.Cardinality() == pref.Repeated:
-		if got := known.Get(num); got.List().Len() != 0 {
+	case fd.IsMap():
+		if got := known.Get(num); got.Map().Len() != 0 {
 			t.Errorf("after clearing %q:\nGet(%v) = %v, want empty list", name, num, formatValue(got))
 		}
 	default:
@@ -421,6 +421,16 @@
 func newValue(m pref.Message, fd pref.FieldDescriptor, n seed, stack []pref.MessageDescriptor) pref.Value {
 	num := fd.Number()
 	switch {
+	case fd.IsList():
+		list := m.New().KnownFields().Get(num).List()
+		if n == 0 {
+			return pref.ValueOf(list)
+		}
+		list.Append(newListElement(fd, list, 0, stack))
+		list.Append(newListElement(fd, list, minVal, stack))
+		list.Append(newListElement(fd, list, maxVal, stack))
+		list.Append(newListElement(fd, list, n, stack))
+		return pref.ValueOf(list)
 	case fd.IsMap():
 		mapv := m.New().KnownFields().Get(num).Map()
 		if n == 0 {
@@ -431,16 +441,6 @@
 		mapv.Set(newMapKey(fd, maxVal), newMapValue(fd, mapv, maxVal, stack))
 		mapv.Set(newMapKey(fd, n), newMapValue(fd, mapv, 10*n, stack))
 		return pref.ValueOf(mapv)
-	case fd.Cardinality() == pref.Repeated:
-		list := m.New().KnownFields().Get(num).List()
-		if n == 0 {
-			return pref.ValueOf(list)
-		}
-		list.Append(newListElement(fd, list, 0, stack))
-		list.Append(newListElement(fd, list, minVal, stack))
-		list.Append(newListElement(fd, list, maxVal, stack))
-		list.Append(newListElement(fd, list, n, stack))
-		return pref.ValueOf(list)
 	case fd.Message() != nil:
 		return populateMessage(m.KnownFields().NewMessage(num), n, stack)
 	default:
@@ -456,12 +456,12 @@
 }
 
 func newMapKey(fd pref.FieldDescriptor, n seed) pref.MapKey {
-	kd := fd.Message().Fields().ByNumber(1)
+	kd := fd.MapKey()
 	return newScalarValue(kd, n).MapKey()
 }
 
 func newMapValue(fd pref.FieldDescriptor, mapv pref.Map, n seed, stack []pref.MessageDescriptor) pref.Value {
-	vd := fd.Message().Fields().ByNumber(2)
+	vd := fd.MapValue()
 	if vd.Message() == nil {
 		return newScalarValue(vd, n)
 	}