Add support for proto3 optional fields introduced in protoc 3.12 (#591)
diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py
index a08e679..52efc8c 100755
--- a/generator/nanopb_generator.py
+++ b/generator/nanopb_generator.py
@@ -425,7 +425,11 @@
# In most other protobuf libraries proto3 submessages have
# "null" status. For nanopb, that is implemented as has_ field.
self.rules = 'OPTIONAL'
+ elif hasattr(desc, "proto3_optional") and desc.proto3_optional:
+ # Protobuf 3.12 introduced optional fields for proto3 syntax
+ self.rules = 'OPTIONAL'
else:
+ # Proto3 singular fields (without has_field)
self.rules = 'SINGULAR'
elif desc.label == FieldD.LABEL_REQUIRED:
self.rules = 'REQUIRED'
@@ -1061,18 +1065,22 @@
self.descriptorsize = field_options.descriptorsize
field = Field(self.name, f, field_options)
- if (hasattr(f, 'oneof_index') and
- f.HasField('oneof_index') and
- f.oneof_index not in no_unions):
- if f.oneof_index in self.oneofs:
+ if hasattr(f, 'oneof_index') and f.HasField('oneof_index'):
+ if hasattr(f, 'proto3_optional') and f.proto3_optional:
+ no_unions.append(f.oneof_index)
+
+ if f.oneof_index in no_unions:
+ self.fields.append(field)
+ elif f.oneof_index in self.oneofs:
self.oneofs[f.oneof_index].add_field(field)
if self.oneofs[f.oneof_index] not in self.fields:
self.fields.append(self.oneofs[f.oneof_index])
else:
self.fields.append(field)
- if field.math_include_required:
- self.math_include_required = True
+
+ if field.math_include_required:
+ self.math_include_required = True
if len(desc.extension_range) > 0:
field_options = get_nanopb_suboptions(desc, message_options, self.name + 'extensions')
@@ -2164,6 +2172,9 @@
f.name = results['sourcename']
f.content = results['sourcedata']
+ if hasattr(plugin_pb2.CodeGeneratorResponse, "FEATURE_PROTO3_OPTIONAL"):
+ response.supported_features = plugin_pb2.CodeGeneratorResponse.FEATURE_PROTO3_OPTIONAL
+
io.open(sys.stdout.fileno(), "wb").write(response.SerializeToString())
if __name__ == '__main__':
diff --git a/tests/SConstruct b/tests/SConstruct
index cd65145..1755012 100644
--- a/tests/SConstruct
+++ b/tests/SConstruct
@@ -92,10 +92,12 @@
status, output = context.TryAction('$PROTOC --version > $TARGET')
if status:
context.Result(str(output.strip()))
+ return str(output.strip())
else:
context.Display("error: %s\n" % output.strip())
context.did_show_result = 1
- context.Result("3.6.1") # Assumption
+ context.Result("unknown, assuming 3.6.1")
+ return "3.6.1"
conf = Configure(env, custom_tests =
{'CheckCCFLAGS': check_ccflags,
diff --git a/tests/proto3_optional/SConscript b/tests/proto3_optional/SConscript
new file mode 100644
index 0000000..513978e
--- /dev/null
+++ b/tests/proto3_optional/SConscript
@@ -0,0 +1,26 @@
+# Test proto3 "optional" field types.
+# This is supported in protoc 3.12 and newer.
+
+Import('env')
+import re
+
+version = None
+if 'PROTOC_VERSION' in env:
+ match = re.search('([0-9]+).([0-9]+).([0-9]+)', env['PROTOC_VERSION'])
+ version = (int(match[1]), int(match[2]), int(match[3]))
+
+print(env['PROTOC'])
+
+# Oneof is supported by protoc >= 3.12.0
+if env.GetOption('clean') or (version and (version[0] > 3 or (version[0] == 3 and version[1] >= 12))):
+
+ env2 = env.Clone()
+ env2.Append(PROTOCFLAGS = "--experimental_allow_proto3_optional")
+
+ env2.NanopbProto("optional.proto")
+ opt = env2.Program(["optional.c", "optional.pb.c",
+ "$COMMON/pb_decode.o", "$COMMON/pb_encode.o",
+ "$COMMON/pb_common.o"])
+ env2.RunTest(opt)
+
+
diff --git a/tests/proto3_optional/optional.c b/tests/proto3_optional/optional.c
new file mode 100644
index 0000000..a72035d
--- /dev/null
+++ b/tests/proto3_optional/optional.c
@@ -0,0 +1,47 @@
+#include <pb_encode.h>
+#include <pb_decode.h>
+#include <unittests.h>
+#include "optional.pb.h"
+
+int main()
+{
+ int status = 0;
+ uint8_t buf[256];
+ size_t msglen;
+
+ COMMENT("Test encoding message with optional field")
+ {
+ pb_ostream_t stream = pb_ostream_from_buffer(buf, sizeof(buf));
+ TestMessage msg = TestMessage_init_zero;
+
+ msg.has_opt_int = true;
+ msg.opt_int = 99;
+ msg.normal_int = 100;
+ msg.opt_int2 = 101;
+
+ TEST(pb_encode(&stream, TestMessage_fields, &msg));
+ msglen = stream.bytes_written;
+ }
+
+ COMMENT("Test decoding message with optional field")
+ {
+ pb_istream_t stream = pb_istream_from_buffer(buf, msglen);
+ TestMessage msg = TestMessage_init_zero;
+
+ /* These fields should be missing from the message
+ * so the values wouldn't be overwritten. */
+ msg.opt_int2 = 5;
+ msg.normal_int2 = 6;
+
+ TEST(pb_decode_noinit(&stream, TestMessage_fields, &msg));
+ TEST(msg.has_opt_int);
+ TEST(msg.opt_int == 99);
+ TEST(msg.normal_int == 100);
+ TEST(!msg.has_opt_int2);
+ TEST(msg.opt_int2 == 5);
+ TEST(msg.normal_int2 == 6);
+ }
+
+ return status;
+}
+
diff --git a/tests/proto3_optional/optional.proto b/tests/proto3_optional/optional.proto
new file mode 100644
index 0000000..de1eb37
--- /dev/null
+++ b/tests/proto3_optional/optional.proto
@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+message TestMessage {
+ optional int32 opt_int = 1;
+ int32 normal_int = 2;
+ optional int32 opt_int2 = 3;
+ int32 normal_int2 = 4;
+}
+