THRIFT-5511 Full support for the new net6 "nullability" semantics
Client: netstd
Patch: Jens Geyer

This closes #2516
diff --git a/test/netstd/Client/Client.csproj b/test/netstd/Client/Client.csproj
index e312990..9d4ab48 100644
--- a/test/netstd/Client/Client.csproj
+++ b/test/netstd/Client/Client.csproj
@@ -49,8 +49,8 @@
     <Exec Condition="'$(OS)' == 'Windows_NT'" Command="where thrift" ConsoleToMSBuild="true">
       <Output TaskParameter="ConsoleOutput" PropertyName="PathToThrift" />
     </Exec>
-    <Exec Condition="Exists('$(PathToThrift)')" Command="&quot;$(PathToThrift)&quot; -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
-    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
-    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('$(PathToThrift)')" Command="&quot;$(PathToThrift)&quot; -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
   </Target>
 </Project>
diff --git a/test/netstd/Client/Performance/TestDataFactory.cs b/test/netstd/Client/Performance/TestDataFactory.cs
index 833947c..8dec3f3 100644
--- a/test/netstd/Client/Performance/TestDataFactory.cs
+++ b/test/netstd/Client/Performance/TestDataFactory.cs
@@ -40,9 +40,9 @@
             };
         }
 
-        private static THashSet<Insanity> CreateSetField(int count)
+        private static HashSet<Insanity> CreateSetField(int count)
         {
-            var retval = new THashSet<Insanity>();
+            var retval = new HashSet<Insanity>();
             for (var i = 0; i < count; ++i)
                 retval.Add(CreateInsanity(count));
             return retval;
@@ -90,41 +90,41 @@
             return retval;
         }
 
-        private static List<Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>> CreateListField(int count)
+        private static List<Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>> CreateListField(int count)
         {
-            var retval = new List<Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>>();
+            var retval = new List<Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>>();
             for (var i = 0; i < count; ++i)
                 retval.Add(CreateListFieldData(count));
             return retval;
         }
 
-        private static Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>> CreateListFieldData(int count)
+        private static Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>> CreateListFieldData(int count)
         {
-            var retval = new Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>();
+            var retval = new Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>();
             for (var i = 0; i < count; ++i)
                 retval.Add( CreateIntHashSet(count), CreateListFieldDataDict(count));
             return retval;
         }
 
-        private static THashSet<int> CreateIntHashSet(int count)
+        private static HashSet<int> CreateIntHashSet(int count)
         {
-            var retval = new THashSet<int>();
+            var retval = new HashSet<int>();
             for (var i = 0; i < count; ++i)
                 retval.Add(i);
             return retval;
         }
 
-        private static Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>> CreateListFieldDataDict(int count)
+        private static Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>> CreateListFieldDataDict(int count)
         {
-            var retval = new Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>();
+            var retval = new Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>();
             for (var i = 0; i < count; ++i)
                 retval.Add(i, CreateListFieldDataDictValue(count));
             return retval;
         }
 
-        private static THashSet<List<Dictionary<Insanity, string>>> CreateListFieldDataDictValue(int count)
+        private static HashSet<List<Dictionary<Insanity, string>>> CreateListFieldDataDictValue(int count)
         {
-            var retval = new THashSet<List<Dictionary<Insanity, string>>>();
+            var retval = new HashSet<List<Dictionary<Insanity, string>>>();
             for (var i = 0; i < count; ++i)
                 retval.Add( CreateListFieldDataDictValueList(count));
             return retval;
diff --git a/test/netstd/Client/TestClient.cs b/test/netstd/Client/TestClient.cs
index 0a7fa00..0c80b9c 100644
--- a/test/netstd/Client/TestClient.cs
+++ b/test/netstd/Client/TestClient.cs
@@ -644,9 +644,14 @@
                 Struct_thing = o,
                 I32_thing = 5
             };
-            var i2 = await client.testNest(o2, MakeTimeoutToken());
+            Xtruct2 i2 = await client.testNest(o2, MakeTimeoutToken());
             i = i2.Struct_thing;
-            Console.WriteLine(" = {" + i2.Byte_thing + ", {\"" + i.String_thing + "\", " + i.Byte_thing + ", " + i.I32_thing + ", " + i.I64_thing + "}, " + i2.I32_thing + "}");
+            Console.WriteLine(" = {" + i2.Byte_thing + ", {\""
+                            + (i?.String_thing ?? "<null>") + "\", "
+                            + (i?.Byte_thing ?? 0) + ", "
+                            + (i?.I32_thing ?? 0) + ", "
+                            + (i?.I64_thing ?? 0) + "}, "
+                            + i2.I32_thing + "}");
 
             var mapout = new Dictionary<int, int>();
             for (var j = 0; j < 5; j++)
@@ -681,7 +686,7 @@
 
             //set
             // TODO: Validate received message
-            var setout = new THashSet<int>();
+            var setout = new HashSet<int>();
             for (var j = -2; j < 3; j++)
             {
                 setout.Add(j);
@@ -937,7 +942,7 @@
             }
             catch (Xception2 ex)
             {
-                if (ex.ErrorCode != 2002 || ex.Struct_thing.String_thing != "This is an Xception2")
+                if (ex.ErrorCode != 2002 || ex.Struct_thing?.String_thing != "This is an Xception2")
                 {
                     Console.WriteLine("*** FAILED ***");
                     returnCode |= ErrorExceptions;
diff --git a/test/netstd/Server/Server.csproj b/test/netstd/Server/Server.csproj
index 546d0e2..439e5c1 100644
--- a/test/netstd/Server/Server.csproj
+++ b/test/netstd/Server/Server.csproj
@@ -51,8 +51,8 @@
     <Exec Condition="'$(OS)' == 'Windows_NT'" Command="where thrift" ConsoleToMSBuild="true">
       <Output TaskParameter="ConsoleOutput" PropertyName="PathToThrift" />
     </Exec>
-    <Exec Condition="Exists('$(PathToThrift)')" Command="&quot;$(PathToThrift)&quot; -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
-    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
-    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('$(PathToThrift)')" Command="&quot;$(PathToThrift)&quot; -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
   </Target>
 </Project>
diff --git a/test/netstd/Server/TestServer.cs b/test/netstd/Server/TestServer.cs
index 515a299..86072b0 100644
--- a/test/netstd/Server/TestServer.cs
+++ b/test/netstd/Server/TestServer.cs
@@ -229,10 +229,10 @@
                 return Task.CompletedTask;
             }
 
-            public Task<string> testString(string thing, CancellationToken cancellationToken)
+            public Task<string> testString(string? thing, CancellationToken cancellationToken)
             {
-                logger.Invoke("testString({0})", thing);
-                return Task.FromResult(thing);
+                logger.Invoke("testString({0})", thing ?? "<null>");
+                return Task.FromResult(thing ?? string.Empty);
             }
 
             public Task<bool> testBool(bool thing, CancellationToken cancellationToken)
@@ -265,117 +265,129 @@
                 return Task.FromResult(thing);
             }
 
-            public Task<byte[]> testBinary(byte[] thing, CancellationToken cancellationToken)
+            public Task<byte[]> testBinary(byte[]? thing, CancellationToken cancellationToken)
             {
-                logger.Invoke("testBinary({0} bytes)", thing.Length);
-                return Task.FromResult(thing);
+                logger.Invoke("testBinary({0} bytes)", thing?.Length ?? 0);
+                return Task.FromResult(thing ?? Array.Empty<byte>());
             }
 
-            public Task<Xtruct> testStruct(Xtruct thing, CancellationToken cancellationToken)
+            public Task<Xtruct> testStruct(Xtruct? thing, CancellationToken cancellationToken)
             {
-                logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.String_thing, thing.Byte_thing, thing.I32_thing, thing.I64_thing);
-                return Task.FromResult(thing);
+                logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing?.String_thing ?? "<null>", thing?.Byte_thing ?? 0, thing?.I32_thing ?? 0, thing?.I64_thing ?? 0);
+                return Task.FromResult(thing ?? new Xtruct());   // null returns are not allowed in Thrift
             }
 
-            public Task<Xtruct2> testNest(Xtruct2 nest, CancellationToken cancellationToken)
+            public Task<Xtruct2> testNest(Xtruct2? nest, CancellationToken cancellationToken)
             {
-                var thing = nest.Struct_thing;
+                var thing = nest?.Struct_thing;
                 logger.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})",
-                    nest.Byte_thing,
-                    thing.String_thing,
-                    thing.Byte_thing,
-                    thing.I32_thing,
-                    thing.I64_thing,
-                    nest.I32_thing);
-                return Task.FromResult(nest);
+                    nest?.Byte_thing ?? 0,
+                    thing?.String_thing ?? "<null>",
+                    thing?.Byte_thing ?? 0,
+                    thing?.I32_thing ?? 0,
+                    thing?.I64_thing ?? 0,
+                    nest?.I32_thing ?? 0);
+                return Task.FromResult(nest ?? new Xtruct2());   // null returns are not allowed in Thrift
             }
 
-            public Task<Dictionary<int, int>> testMap(Dictionary<int, int> thing, CancellationToken cancellationToken)
+            public Task<Dictionary<int, int>> testMap(Dictionary<int, int>? thing, CancellationToken cancellationToken)
             {
                 sb.Clear();
                 sb.Append("testMap({{");
-                var first = true;
-                foreach (var key in thing.Keys)
+                if (thing != null)
                 {
-                    if (first)
+                    var first = true;
+                    foreach (var key in thing.Keys)
                     {
-                        first = false;
+                        if (first)
+                        {
+                            first = false;
+                        }
+                        else
+                        {
+                            sb.Append(", ");
+                        }
+                        sb.AppendFormat("{0} => {1}", key, thing[key]);
                     }
-                    else
-                    {
-                        sb.Append(", ");
-                    }
-                    sb.AppendFormat("{0} => {1}", key, thing[key]);
                 }
                 sb.Append("}})");
                 logger.Invoke(sb.ToString());
-                return Task.FromResult(thing);
+                return Task.FromResult(thing ?? new Dictionary<int, int>());   // null returns are not allowed in Thrift
             }
 
-            public Task<Dictionary<string, string>> testStringMap(Dictionary<string, string> thing, CancellationToken cancellationToken)
+            public Task<Dictionary<string, string>> testStringMap(Dictionary<string, string>? thing, CancellationToken cancellationToken)
             {
                 sb.Clear();
                 sb.Append("testStringMap({{");
-                var first = true;
-                foreach (var key in thing.Keys)
+                if (thing != null)
                 {
-                    if (first)
+                    var first = true;
+                    foreach (var key in thing.Keys)
                     {
-                        first = false;
+                        if (first)
+                        {
+                            first = false;
+                        }
+                        else
+                        {
+                            sb.Append(", ");
+                        }
+                        sb.AppendFormat("{0} => {1}", key, thing[key]);
                     }
-                    else
-                    {
-                        sb.Append(", ");
-                    }
-                    sb.AppendFormat("{0} => {1}", key, thing[key]);
                 }
                 sb.Append("}})");
                 logger.Invoke(sb.ToString());
-                return Task.FromResult(thing);
+                return Task.FromResult(thing ?? new Dictionary<string, string>());   // null returns are not allowed in Thrift
             }
 
-            public Task<THashSet<int>> testSet(THashSet<int> thing, CancellationToken cancellationToken)
+            public Task<HashSet<int>> testSet(HashSet<int>? thing, CancellationToken cancellationToken)
             {
                 sb.Clear();
                 sb.Append("testSet({{");
-                var first = true;
-                foreach (int elem in thing)
+                if (thing != null)
                 {
-                    if (first)
+                    var first = true;
+                    foreach (int elem in thing)
                     {
-                        first = false;
+                        if (first)
+                        {
+                            first = false;
+                        }
+                        else
+                        {
+                            sb.Append(", ");
+                        }
+                        sb.AppendFormat("{0}", elem);
                     }
-                    else
-                    {
-                        sb.Append(", ");
-                    }
-                    sb.AppendFormat("{0}", elem);
                 }
                 sb.Append("}})");
                 logger.Invoke(sb.ToString());
-                return Task.FromResult(thing);
+                return Task.FromResult(thing ?? new HashSet<int>());   // null returns are not allowed in Thrift
             }
 
-            public Task<List<int>> testList(List<int> thing, CancellationToken cancellationToken)
+            public Task<List<int>> testList(List<int>? thing, CancellationToken cancellationToken)
             {
                 sb.Clear();
                 sb.Append("testList({{");
-                var first = true;
-                foreach (var elem in thing)
+                if (thing != null)
                 {
-                    if (first)
+                    var first = true;
+                    foreach (var elem in thing)
                     {
-                        first = false;
+                        if (first)
+                        {
+                            first = false;
+                        }
+                        else
+                        {
+                            sb.Append(", ");
+                        }
+                        sb.AppendFormat("{0}", elem);
                     }
-                    else
-                    {
-                        sb.Append(", ");
-                    }
-                    sb.AppendFormat("{0}", elem);
                 }
                 sb.Append("}})");
                 logger.Invoke(sb.ToString());
-                return Task.FromResult(thing);
+                return Task.FromResult(thing ?? new List<int>());   // null returns are not allowed in Thrift
             }
 
             public Task<Numberz> testEnum(Numberz thing, CancellationToken cancellationToken)
@@ -409,7 +421,7 @@
                 return Task.FromResult(mapmap);
             }
 
-            public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanity(Insanity argument, CancellationToken cancellationToken)
+            public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanity(Insanity? argument, CancellationToken cancellationToken)
             {
                 logger.Invoke("testInsanity()");
 
@@ -428,8 +440,9 @@
                 var first_map = new Dictionary<Numberz, Insanity>();
                 var second_map = new Dictionary<Numberz, Insanity>(); ;
 
-                first_map[Numberz.TWO] = argument;
-                first_map[Numberz.THREE] = argument;
+                // null dict keys/values are not allowed in Thrift
+                first_map[Numberz.TWO] = argument ?? new Insanity();
+                first_map[Numberz.THREE] = argument ?? new Insanity();
 
                 second_map[Numberz.SIX] = new Insanity();
 
@@ -442,7 +455,7 @@
                 return Task.FromResult(insane);
             }
 
-            public Task<Xtruct> testMulti(sbyte arg0, int arg1, long arg2, Dictionary<short, string> arg3, Numberz arg4, long arg5,
+            public Task<Xtruct> testMulti(sbyte arg0, int arg1, long arg2, Dictionary<short, string>? arg3, Numberz arg4, long arg5,
                 CancellationToken cancellationToken)
             {
                 logger.Invoke("testMulti()");
@@ -455,9 +468,9 @@
                 return Task.FromResult(hello);
             }
 
-            public Task testException(string arg, CancellationToken cancellationToken)
+            public Task testException(string? arg, CancellationToken cancellationToken)
             {
-                logger.Invoke("testException({0})", arg);
+                logger.Invoke("testException({0})", arg ?? "<null>");
                 if (arg == "Xception")
                 {
                     var x = new Xception
@@ -474,9 +487,9 @@
                 return Task.CompletedTask;
             }
 
-            public Task<Xtruct> testMultiException(string arg0, string arg1, CancellationToken cancellationToken)
+            public Task<Xtruct> testMultiException(string? arg0, string? arg1, CancellationToken cancellationToken)
             {
-                logger.Invoke("testMultiException({0}, {1})", arg0, arg1);
+                logger.Invoke("testMultiException({0}, {1})", arg0 ?? "<null>", arg1 ?? "<null>");
                 if (arg0 == "Xception")
                 {
                     var x = new Xception