proto, internal/impl: don't create fast path Size for legacy Marshalers

Implementations of the legacy Marshaler type have no way to efficiently
compute the size of the message. Rather than generating an inefficient
fast-path Size method which marshals the message and examines the
length of the result, don't generate a fast-path at all.

Drop the requirement that a fast-path MarshalAppend requires a
corresponding Size.

Avoids O(N^2) behavior when marshaling a legacy Marshaler that
recursively calls proto.Marshal.

Change-Id: I4793cf32275d08f29c8e1a1a44a193d9a5724058
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/213443
Reviewed-by: Joe Tsai <joetsai@google.com>
diff --git a/proto/encode.go b/proto/encode.go
index 702190f..0801ce4 100644
--- a/proto/encode.go
+++ b/proto/encode.go
@@ -100,13 +100,15 @@
 func (o MarshalOptions) marshalMessage(b []byte, m protoreflect.Message) ([]byte, error) {
 	if methods := protoMethods(m); methods != nil && methods.MarshalAppend != nil &&
 		!(o.Deterministic && methods.Flags&protoiface.SupportMarshalDeterministic == 0) {
-		sz := methods.Size(m, protoiface.MarshalOptions(o))
-		if cap(b) < len(b)+sz {
-			x := make([]byte, len(b), growcap(cap(b), len(b)+sz))
-			copy(x, b)
-			b = x
+		if methods.Size != nil {
+			sz := methods.Size(m, protoiface.MarshalOptions(o))
+			if cap(b) < len(b)+sz {
+				x := make([]byte, len(b), growcap(cap(b), len(b)+sz))
+				copy(x, b)
+				b = x
+			}
+			o.UseCachedSize = true
 		}
-		o.UseCachedSize = true
 		return methods.MarshalAppend(b, m, protoiface.MarshalOptions(o))
 	}
 	return o.marshalMessageSlow(b, m)