Pārlūkot izejas kodu

[ObjC] Runtime support for proto3 optional.

- Add a Descriptor flag to capture if the field should clear on being zeroed.
- Update the runtime to use the new clear on zero flag.
- Add a flag on message initialization to indicate the sources were generated
  with/without this support so the runtime can backfill the flag for older
  generated sources.
Thomas Van Lenten 5 gadi atpakaļ
vecāks
revīzija
8d224b4c48

+ 24 - 16
objectivec/GPBDescriptor.m

@@ -128,6 +128,8 @@ static NSArray *NewFieldsArrayForHasIndex(int hasIndex,
       (flags & GPBDescriptorInitializationFlag_FieldsWithDefault) != 0;
       (flags & GPBDescriptorInitializationFlag_FieldsWithDefault) != 0;
   BOOL usesClassRefs =
   BOOL usesClassRefs =
       (flags & GPBDescriptorInitializationFlag_UsesClassRefs) != 0;
       (flags & GPBDescriptorInitializationFlag_UsesClassRefs) != 0;
+  BOOL proto3OptionalKnown =
+      (flags & GPBDescriptorInitializationFlag_Proto3OptionalKnown) != 0;
 
 
   void *desc;
   void *desc;
   for (uint32_t i = 0; i < fieldCount; ++i) {
   for (uint32_t i = 0; i < fieldCount; ++i) {
@@ -146,6 +148,7 @@ static NSArray *NewFieldsArrayForHasIndex(int hasIndex,
         [[GPBFieldDescriptor alloc] initWithFieldDescription:desc
         [[GPBFieldDescriptor alloc] initWithFieldDescription:desc
                                              includesDefault:fieldsIncludeDefault
                                              includesDefault:fieldsIncludeDefault
                                                usesClassRefs:usesClassRefs
                                                usesClassRefs:usesClassRefs
+                                         proto3OptionalKnown:proto3OptionalKnown
                                                       syntax:syntax];
                                                       syntax:syntax];
     [fields addObject:fieldDescriptor];
     [fields addObject:fieldDescriptor];
     [fieldDescriptor release];
     [fieldDescriptor release];
@@ -488,6 +491,7 @@ uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
 - (instancetype)initWithFieldDescription:(void *)description
 - (instancetype)initWithFieldDescription:(void *)description
                          includesDefault:(BOOL)includesDefault
                          includesDefault:(BOOL)includesDefault
                            usesClassRefs:(BOOL)usesClassRefs
                            usesClassRefs:(BOOL)usesClassRefs
+                     proto3OptionalKnown:(BOOL)proto3OptionalKnown
                                   syntax:(GPBFileSyntax)syntax {
                                   syntax:(GPBFileSyntax)syntax {
   if ((self = [super init])) {
   if ((self = [super init])) {
     GPBMessageFieldDescription *coreDesc;
     GPBMessageFieldDescription *coreDesc;
@@ -504,20 +508,33 @@ uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
     BOOL isMessage = GPBDataTypeIsMessage(dataType);
     BOOL isMessage = GPBDataTypeIsMessage(dataType);
     BOOL isMapOrArray = GPBFieldIsMapOrArray(self);
     BOOL isMapOrArray = GPBFieldIsMapOrArray(self);
 
 
+    // If proto3 optionals weren't know, compute the flag for the rest of the runtime.
+    if (!proto3OptionalKnown) {
+      // If it was...
+      //  - proto3 syntax
+      //  - not repeated/map
+      //  - not in a oneof (negative has index)
+      //  - not a message (the flag doesn't make sense for messages)
+      BOOL clearOnZero = ((syntax == GPBFileSyntaxProto3) &&
+                          !isMapOrArray &&
+                          (coreDesc->hasIndex >= 0) &&
+                          !isMessage);
+      if (clearOnZero) {
+        coreDesc->flags |= GPBFieldClearHasIvarOnZero;
+      }
+    }
+
     if (isMapOrArray) {
     if (isMapOrArray) {
       // map<>/repeated fields get a *Count property (inplace of a has*) to
       // map<>/repeated fields get a *Count property (inplace of a has*) to
       // support checking if there are any entries without triggering
       // support checking if there are any entries without triggering
       // autocreation.
       // autocreation.
       hasOrCountSel_ = SelFromStrings(NULL, coreDesc->name, "_Count", NO);
       hasOrCountSel_ = SelFromStrings(NULL, coreDesc->name, "_Count", NO);
     } else {
     } else {
-      // If there is a positive hasIndex, then:
-      //   - All fields types for proto2 messages get has* selectors.
-      //   - Only message fields for proto3 messages get has* selectors.
-      // Note: the positive check is to handle oneOfs, we can't check
-      // containingOneof_ because it isn't set until after initialization.
+      // Know it is a single field; it gets has/setHas selectors if...
+      //  - not in a oneof (negative has index)
+      //  - not clearing on zero
       if ((coreDesc->hasIndex >= 0) &&
       if ((coreDesc->hasIndex >= 0) &&
-          (coreDesc->hasIndex != GPBNoHasBit) &&
-          ((syntax != GPBFileSyntaxProto3) || isMessage)) {
+          ((coreDesc->flags & GPBFieldClearHasIvarOnZero) == 0)) {
         hasOrCountSel_ = SelFromStrings("has", coreDesc->name, NULL, NO);
         hasOrCountSel_ = SelFromStrings("has", coreDesc->name, NULL, NO);
         setHasSel_ = SelFromStrings("setHas", coreDesc->name, NULL, YES);
         setHasSel_ = SelFromStrings("setHas", coreDesc->name, NULL, YES);
       }
       }
@@ -567,15 +584,6 @@ uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
   return self;
   return self;
 }
 }
 
 
-- (instancetype)initWithFieldDescription:(void *)description
-                         includesDefault:(BOOL)includesDefault
-                                  syntax:(GPBFileSyntax)syntax {
-  return [self initWithFieldDescription:description
-                        includesDefault:includesDefault
-                          usesClassRefs:NO
-                                 syntax:syntax];
-}
-
 - (void)dealloc {
 - (void)dealloc {
   if (description_->dataType == GPBDataTypeBytes &&
   if (description_->dataType == GPBDataTypeBytes &&
       !(description_->flags & GPBFieldRepeated)) {
       !(description_->flags & GPBFieldRepeated)) {

+ 12 - 7
objectivec/GPBDescriptor_PackagePrivate.h

@@ -45,6 +45,10 @@ typedef NS_OPTIONS(uint16_t, GPBFieldFlags) {
   GPBFieldOptional        = 1 << 3,
   GPBFieldOptional        = 1 << 3,
   GPBFieldHasDefaultValue = 1 << 4,
   GPBFieldHasDefaultValue = 1 << 4,
 
 
+  // Indicate that the field should "clear" when set to zero value. This is the
+  // proto3 non optional behavior for singular data (ints, data, string, enum)
+  // fields.
+  GPBFieldClearHasIvarOnZero = 1 << 5,
   // Indicates the field needs custom handling for the TextFormat name, if not
   // Indicates the field needs custom handling for the TextFormat name, if not
   // set, the name can be derived from the ObjC name.
   // set, the name can be derived from the ObjC name.
   GPBFieldTextFormatNameCustom = 1 << 6,
   GPBFieldTextFormatNameCustom = 1 << 6,
@@ -149,7 +153,13 @@ typedef NS_OPTIONS(uint32_t, GPBDescriptorInitializationFlags) {
   // This is used as a stopgap as we move from using class names to class
   // This is used as a stopgap as we move from using class names to class
   // references. The runtime needs to support both until we allow a
   // references. The runtime needs to support both until we allow a
   // breaking change in the runtime.
   // breaking change in the runtime.
-  GPBDescriptorInitializationFlag_UsesClassRefs        = 1 << 2,
+  GPBDescriptorInitializationFlag_UsesClassRefs     = 1 << 2,
+
+  // This flag is used to indicate that the generated sources already containg
+  // the `GPBFieldClearHasIvarOnZero` flag and it doesn't have to be computed
+  // at startup, this allows older generated code to still work with the
+  // current runtime library.
+  GPBDescriptorInitializationFlag_Proto3OptionalKnown = 1 << 3,
 };
 };
 
 
 @interface GPBDescriptor () {
 @interface GPBDescriptor () {
@@ -225,14 +235,9 @@ typedef NS_OPTIONS(uint32_t, GPBDescriptorInitializationFlags) {
 - (instancetype)initWithFieldDescription:(void *)description
 - (instancetype)initWithFieldDescription:(void *)description
                          includesDefault:(BOOL)includesDefault
                          includesDefault:(BOOL)includesDefault
                            usesClassRefs:(BOOL)usesClassRefs
                            usesClassRefs:(BOOL)usesClassRefs
+                     proto3OptionalKnown:(BOOL)proto3OptionalKnown
                                   syntax:(GPBFileSyntax)syntax;
                                   syntax:(GPBFileSyntax)syntax;
 
 
-// Deprecated. Equivalent to calling above with `usesClassRefs = NO`.
-- (instancetype)initWithFieldDescription:(void *)description
-                         includesDefault:(BOOL)includesDefault
-                                  syntax:(GPBFileSyntax)syntax;
-
-
 @end
 @end
 
 
 @interface GPBEnumDescriptor ()
 @interface GPBEnumDescriptor ()

+ 49 - 57
objectivec/GPBUtilities.m

@@ -400,6 +400,7 @@ void GPBMaybeClearOneof(GPBMessage *self, GPBOneofDescriptor *oneof,
 //%            NAME$S                     GPBFieldDescriptor *field,
 //%            NAME$S                     GPBFieldDescriptor *field,
 //%            NAME$S                     TYPE value,
 //%            NAME$S                     TYPE value,
 //%            NAME$S                     GPBFileSyntax syntax) {
 //%            NAME$S                     GPBFileSyntax syntax) {
+//%#pragma unused(syntax)
 //%  GPBOneofDescriptor *oneof = field->containingOneof_;
 //%  GPBOneofDescriptor *oneof = field->containingOneof_;
 //%  GPBMessageFieldDescription *fieldDesc = field->description_;
 //%  GPBMessageFieldDescription *fieldDesc = field->description_;
 //%  if (oneof) {
 //%  if (oneof) {
@@ -416,11 +417,10 @@ void GPBMaybeClearOneof(GPBMessage *self, GPBOneofDescriptor *oneof,
 //%  uint8_t *storage = (uint8_t *)self->messageStorage_;
 //%  uint8_t *storage = (uint8_t *)self->messageStorage_;
 //%  TYPE *typePtr = (TYPE *)&storage[fieldDesc->offset];
 //%  TYPE *typePtr = (TYPE *)&storage[fieldDesc->offset];
 //%  *typePtr = value;
 //%  *typePtr = value;
-//%  // proto2: any value counts as having been set; proto3, it
-//%  // has to be a non zero value or be in a oneof.
-//%  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
-//%                   || (value != (TYPE)0)
-//%                   || (field->containingOneof_ != NULL));
+//%  // If the value is zero, then we only count the field as "set" if the field
+//%  // shouldn't auto clear on zero.
+//%  BOOL hasValue = ((value != (TYPE)0)
+//%                   || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
 //%  GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
 //%  GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
 //%  GPBBecomeVisibleToAutocreator(self);
 //%  GPBBecomeVisibleToAutocreator(self);
 //%}
 //%}
@@ -548,6 +548,7 @@ void GPBSetObjectIvarWithFieldInternal(GPBMessage *self,
 void GPBSetRetainedObjectIvarWithFieldInternal(GPBMessage *self,
 void GPBSetRetainedObjectIvarWithFieldInternal(GPBMessage *self,
                                                GPBFieldDescriptor *field,
                                                GPBFieldDescriptor *field,
                                                id value, GPBFileSyntax syntax) {
                                                id value, GPBFileSyntax syntax) {
+#pragma unused(syntax)
   NSCAssert(self->messageStorage_ != NULL,
   NSCAssert(self->messageStorage_ != NULL,
             @"%@: All messages should have storage (from init)",
             @"%@: All messages should have storage (from init)",
             [self class]);
             [self class]);
@@ -596,24 +597,15 @@ void GPBSetRetainedObjectIvarWithFieldInternal(GPBMessage *self,
     }
     }
     // Clear "has" if they are being set to nil.
     // Clear "has" if they are being set to nil.
     BOOL setHasValue = (value != nil);
     BOOL setHasValue = (value != nil);
-    // Under proto3, Bytes & String fields get cleared by resetting them to
-    // their default (empty) values, so if they are set to something of length
-    // zero, they are being cleared.
-    if ((syntax == GPBFileSyntaxProto3) && !fieldIsMessage &&
+    // If the field should clear on a "zero" value, then check if the string/data
+    // was zero length, and clear instead.
+    if (((fieldDesc->flags & GPBFieldClearHasIvarOnZero) != 0) &&
         ([value length] == 0)) {
         ([value length] == 0)) {
-      // Except, if the field was in a oneof, then it still gets recorded as
-      // having been set so the state of the oneof can be serialized back out.
-      if (!oneof) {
-        setHasValue = NO;
-      }
-      if (setHasValue) {
-        NSCAssert(value != nil, @"Should never be setting has for nil");
-      } else {
-        // The value passed in was retained, it must be released since we
-        // aren't saving anything in the field.
-        [value release];
-        value = nil;
-      }
+      setHasValue = NO;
+      // The value passed in was retained, it must be released since we
+      // aren't saving anything in the field.
+      [value release];
+      value = nil;
     }
     }
     GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, setHasValue);
     GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, setHasValue);
   }
   }
@@ -802,6 +794,7 @@ void GPBSetBoolIvarWithFieldInternal(GPBMessage *self,
                                      GPBFieldDescriptor *field,
                                      GPBFieldDescriptor *field,
                                      BOOL value,
                                      BOOL value,
                                      GPBFileSyntax syntax) {
                                      GPBFileSyntax syntax) {
+#pragma unused(syntax)
   GPBMessageFieldDescription *fieldDesc = field->description_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBOneofDescriptor *oneof = field->containingOneof_;
   if (oneof) {
   if (oneof) {
@@ -814,11 +807,10 @@ void GPBSetBoolIvarWithFieldInternal(GPBMessage *self,
   // the offset is never negative)
   // the offset is never negative)
   GPBSetHasIvar(self, (int32_t)(fieldDesc->offset), fieldDesc->number, value);
   GPBSetHasIvar(self, (int32_t)(fieldDesc->offset), fieldDesc->number, value);
 
 
-  // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value or be in a oneof.
-  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
-                   || (value != (BOOL)0)
-                   || (field->containingOneof_ != NULL));
+  // If the value is zero, then we only count the field as "set" if the field
+  // shouldn't auto clear on zero.
+  BOOL hasValue = ((value != (BOOL)0)
+                   || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBBecomeVisibleToAutocreator(self);
   GPBBecomeVisibleToAutocreator(self);
 }
 }
@@ -873,6 +865,7 @@ void GPBSetInt32IvarWithFieldInternal(GPBMessage *self,
                                       GPBFieldDescriptor *field,
                                       GPBFieldDescriptor *field,
                                       int32_t value,
                                       int32_t value,
                                       GPBFileSyntax syntax) {
                                       GPBFileSyntax syntax) {
+#pragma unused(syntax)
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   if (oneof) {
   if (oneof) {
@@ -889,11 +882,10 @@ void GPBSetInt32IvarWithFieldInternal(GPBMessage *self,
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   int32_t *typePtr = (int32_t *)&storage[fieldDesc->offset];
   int32_t *typePtr = (int32_t *)&storage[fieldDesc->offset];
   *typePtr = value;
   *typePtr = value;
-  // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value or be in a oneof.
-  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
-                   || (value != (int32_t)0)
-                   || (field->containingOneof_ != NULL));
+  // If the value is zero, then we only count the field as "set" if the field
+  // shouldn't auto clear on zero.
+  BOOL hasValue = ((value != (int32_t)0)
+                   || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBBecomeVisibleToAutocreator(self);
   GPBBecomeVisibleToAutocreator(self);
 }
 }
@@ -949,6 +941,7 @@ void GPBSetUInt32IvarWithFieldInternal(GPBMessage *self,
                                        GPBFieldDescriptor *field,
                                        GPBFieldDescriptor *field,
                                        uint32_t value,
                                        uint32_t value,
                                        GPBFileSyntax syntax) {
                                        GPBFileSyntax syntax) {
+#pragma unused(syntax)
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   if (oneof) {
   if (oneof) {
@@ -965,11 +958,10 @@ void GPBSetUInt32IvarWithFieldInternal(GPBMessage *self,
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   uint32_t *typePtr = (uint32_t *)&storage[fieldDesc->offset];
   uint32_t *typePtr = (uint32_t *)&storage[fieldDesc->offset];
   *typePtr = value;
   *typePtr = value;
-  // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value or be in a oneof.
-  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
-                   || (value != (uint32_t)0)
-                   || (field->containingOneof_ != NULL));
+  // If the value is zero, then we only count the field as "set" if the field
+  // shouldn't auto clear on zero.
+  BOOL hasValue = ((value != (uint32_t)0)
+                   || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBBecomeVisibleToAutocreator(self);
   GPBBecomeVisibleToAutocreator(self);
 }
 }
@@ -1025,6 +1017,7 @@ void GPBSetInt64IvarWithFieldInternal(GPBMessage *self,
                                       GPBFieldDescriptor *field,
                                       GPBFieldDescriptor *field,
                                       int64_t value,
                                       int64_t value,
                                       GPBFileSyntax syntax) {
                                       GPBFileSyntax syntax) {
+#pragma unused(syntax)
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   if (oneof) {
   if (oneof) {
@@ -1041,11 +1034,10 @@ void GPBSetInt64IvarWithFieldInternal(GPBMessage *self,
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   int64_t *typePtr = (int64_t *)&storage[fieldDesc->offset];
   int64_t *typePtr = (int64_t *)&storage[fieldDesc->offset];
   *typePtr = value;
   *typePtr = value;
-  // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value or be in a oneof.
-  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
-                   || (value != (int64_t)0)
-                   || (field->containingOneof_ != NULL));
+  // If the value is zero, then we only count the field as "set" if the field
+  // shouldn't auto clear on zero.
+  BOOL hasValue = ((value != (int64_t)0)
+                   || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBBecomeVisibleToAutocreator(self);
   GPBBecomeVisibleToAutocreator(self);
 }
 }
@@ -1101,6 +1093,7 @@ void GPBSetUInt64IvarWithFieldInternal(GPBMessage *self,
                                        GPBFieldDescriptor *field,
                                        GPBFieldDescriptor *field,
                                        uint64_t value,
                                        uint64_t value,
                                        GPBFileSyntax syntax) {
                                        GPBFileSyntax syntax) {
+#pragma unused(syntax)
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   if (oneof) {
   if (oneof) {
@@ -1117,11 +1110,10 @@ void GPBSetUInt64IvarWithFieldInternal(GPBMessage *self,
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   uint64_t *typePtr = (uint64_t *)&storage[fieldDesc->offset];
   uint64_t *typePtr = (uint64_t *)&storage[fieldDesc->offset];
   *typePtr = value;
   *typePtr = value;
-  // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value or be in a oneof.
-  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
-                   || (value != (uint64_t)0)
-                   || (field->containingOneof_ != NULL));
+  // If the value is zero, then we only count the field as "set" if the field
+  // shouldn't auto clear on zero.
+  BOOL hasValue = ((value != (uint64_t)0)
+                   || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBBecomeVisibleToAutocreator(self);
   GPBBecomeVisibleToAutocreator(self);
 }
 }
@@ -1177,6 +1169,7 @@ void GPBSetFloatIvarWithFieldInternal(GPBMessage *self,
                                       GPBFieldDescriptor *field,
                                       GPBFieldDescriptor *field,
                                       float value,
                                       float value,
                                       GPBFileSyntax syntax) {
                                       GPBFileSyntax syntax) {
+#pragma unused(syntax)
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   if (oneof) {
   if (oneof) {
@@ -1193,11 +1186,10 @@ void GPBSetFloatIvarWithFieldInternal(GPBMessage *self,
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   float *typePtr = (float *)&storage[fieldDesc->offset];
   float *typePtr = (float *)&storage[fieldDesc->offset];
   *typePtr = value;
   *typePtr = value;
-  // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value or be in a oneof.
-  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
-                   || (value != (float)0)
-                   || (field->containingOneof_ != NULL));
+  // If the value is zero, then we only count the field as "set" if the field
+  // shouldn't auto clear on zero.
+  BOOL hasValue = ((value != (float)0)
+                   || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBBecomeVisibleToAutocreator(self);
   GPBBecomeVisibleToAutocreator(self);
 }
 }
@@ -1253,6 +1245,7 @@ void GPBSetDoubleIvarWithFieldInternal(GPBMessage *self,
                                        GPBFieldDescriptor *field,
                                        GPBFieldDescriptor *field,
                                        double value,
                                        double value,
                                        GPBFileSyntax syntax) {
                                        GPBFileSyntax syntax) {
+#pragma unused(syntax)
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBOneofDescriptor *oneof = field->containingOneof_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   GPBMessageFieldDescription *fieldDesc = field->description_;
   if (oneof) {
   if (oneof) {
@@ -1269,11 +1262,10 @@ void GPBSetDoubleIvarWithFieldInternal(GPBMessage *self,
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   uint8_t *storage = (uint8_t *)self->messageStorage_;
   double *typePtr = (double *)&storage[fieldDesc->offset];
   double *typePtr = (double *)&storage[fieldDesc->offset];
   *typePtr = value;
   *typePtr = value;
-  // proto2: any value counts as having been set; proto3, it
-  // has to be a non zero value or be in a oneof.
-  BOOL hasValue = ((syntax == GPBFileSyntaxProto2)
-                   || (value != (double)0)
-                   || (field->containingOneof_ != NULL));
+  // If the value is zero, then we only count the field as "set" if the field
+  // shouldn't auto clear on zero.
+  BOOL hasValue = ((value != (double)0)
+                   || ((fieldDesc->flags & GPBFieldClearHasIvarOnZero) == 0));
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBSetHasIvar(self, fieldDesc->hasIndex, fieldDesc->number, hasValue);
   GPBBecomeVisibleToAutocreator(self);
   GPBBecomeVisibleToAutocreator(self);
 }
 }