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;
+}
+