internal: move usePresenceForField to internal/filedesc

The presence bitmap decision logic is based on the field descriptor,
and in a follow-up CL I want to make both, the runtime and the code
generator, use the same code, so the internal/filedesc package
seems like a good location.

Change-Id: I0216249e00295b3194aaeceb7144ee7cff8dd7ef
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/670515
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Lasse Folger <[email protected]>
diff --git a/internal/filedesc/presence.go b/internal/filedesc/presence.go
new file mode 100644
index 0000000..a12ec97
--- /dev/null
+++ b/internal/filedesc/presence.go
@@ -0,0 +1,33 @@
+// Copyright 2025 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 filedesc
+
+import "google.golang.org/protobuf/reflect/protoreflect"
+
+// UsePresenceForField reports whether the presence bitmap should be used for
+// the specified field.
+func UsePresenceForField(fd protoreflect.FieldDescriptor) (usePresence, canBeLazy bool) {
+	switch {
+	case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
+		// Oneof fields never use the presence bitmap.
+		//
+		// Synthetic oneofs are an exception: Those are used to implement proto3
+		// optional fields and hence should follow non-oneof field semantics.
+		return false, false
+
+	case fd.IsMap():
+		// Map-typed fields never use the presence bitmap.
+		return false, false
+
+	case fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind:
+		// Lazy fields always use the presence bitmap (only messages can be lazy).
+		isLazy := fd.(interface{ IsLazy() bool }).IsLazy()
+		return isLazy, isLazy
+
+	default:
+		// If the field has presence, use the presence bitmap.
+		return fd.HasPresence(), false
+	}
+}
diff --git a/internal/impl/codec_message_opaque.go b/internal/impl/codec_message_opaque.go
index 41c1f74..bdad12a 100644
--- a/internal/impl/codec_message_opaque.go
+++ b/internal/impl/codec_message_opaque.go
@@ -11,6 +11,7 @@
 
 	"google.golang.org/protobuf/encoding/protowire"
 	"google.golang.org/protobuf/internal/encoding/messageset"
+	"google.golang.org/protobuf/internal/filedesc"
 	"google.golang.org/protobuf/internal/order"
 	"google.golang.org/protobuf/reflect/protoreflect"
 	piface "google.golang.org/protobuf/runtime/protoiface"
@@ -80,7 +81,7 @@
 		// permit us to skip over definitely-unset fields at marshal time.
 
 		var hasPresence bool
-		hasPresence, cf.isLazy = usePresenceForField(si, fd)
+		hasPresence, cf.isLazy = filedesc.UsePresenceForField(fd)
 
 		if hasPresence {
 			cf.presenceIndex, mi.presenceSize = presenceIndex(mi.Desc, fd)
diff --git a/internal/impl/message_opaque.go b/internal/impl/message_opaque.go
index e6b3e41..63a731b 100644
--- a/internal/impl/message_opaque.go
+++ b/internal/impl/message_opaque.go
@@ -11,6 +11,7 @@
 	"strings"
 	"sync/atomic"
 
+	"google.golang.org/protobuf/internal/filedesc"
 	"google.golang.org/protobuf/reflect/protoreflect"
 )
 
@@ -53,7 +54,7 @@
 		fd := fds.Get(i)
 		fs := si.fieldsByNumber[fd.Number()]
 		var fi fieldInfo
-		usePresence, _ := usePresenceForField(si, fd)
+		usePresence, _ := filedesc.UsePresenceForField(fd)
 
 		switch {
 		case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
@@ -598,29 +599,3 @@
 func (mi *MessageInfo) present(p pointer, index uint32) bool {
 	return p.Apply(mi.presenceOffset).PresenceInfo().Present(index)
 }
-
-// usePresenceForField reports whether the specified field gets an entry in the
-// presence bitmap.
-func usePresenceForField(si opaqueStructInfo, fd protoreflect.FieldDescriptor) (usePresence, canBeLazy bool) {
-	switch {
-	case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
-		// Oneof fields never use the presence bitmap.
-		//
-		// Synthetic oneofs are an exception: Those are used to implement proto3
-		// optional fields and hence should follow non-oneof field semantics.
-		return false, false
-
-	case fd.IsMap():
-		// Map-typed fields never use the presence bitmap.
-		return false, false
-
-	case fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind:
-		// Lazy fields always use the presence bitmap (only messages can be lazy).
-		isLazy := fd.(interface{ IsLazy() bool }).IsLazy()
-		return isLazy, isLazy
-
-	default:
-		// If the field has presence, use the presence bitmap.
-		return fd.HasPresence(), false
-	}
-}