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

This closes #2516
diff --git a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs
index 660b2b7..f423376 100644
--- a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs
+++ b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs
@@ -26,30 +26,30 @@
 {
     class MyServiceImpl : MyService.IAsync
     {
-        public Task<AsyncProcessor> AsyncProcessor_(AsyncProcessor input, CancellationToken cancellationToken = default)
+        public Task<AsyncProcessor> AsyncProcessor_(AsyncProcessor? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new AsyncProcessor() { Foo = input.Foo });
+            return Task.FromResult(new AsyncProcessor() { Foo = input?.Foo ?? 0 });
         }
 
-        public Task<BrokenResult> Broken(BrokenArgs input, CancellationToken cancellationToken = default)
+        public Task<BrokenResult> Broken(BrokenArgs? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new BrokenResult() { Foo = input.Foo });
+            return Task.FromResult(new BrokenResult() { Foo = input?.Foo ?? 0 });
         }
 
-        public Task<Client> Client_(Client input, CancellationToken cancellationToken = default)
+        public Task<Client> Client_(Client? input, CancellationToken cancellationToken = default)
         {
             _ = cancellationToken;
-            return Task.FromResult(new Client() { Foo = input.Foo });
+            return Task.FromResult(new Client() { Foo = input?.Foo ?? 0 });
         }
 
-        public Task<IAsync> IAsync_(IAsync input, CancellationToken cancellationToken = default)
+        public Task<IAsync> IAsync_(IAsync? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new IAsync() { Foo = input.Foo });
+            return Task.FromResult(new IAsync() { Foo = input?.Foo ?? 0 });
         }
 
-        public Task<InternalStructs> InternalStructs_(InternalStructs input, CancellationToken cancellationToken = default)
+        public Task<InternalStructs> InternalStructs_(InternalStructs? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new InternalStructs() { Foo = input.Foo });
+            return Task.FromResult(new InternalStructs() { Foo = input?.Foo ?? 0 });
         }
 
         public Task TestAsync(CancellationToken cancellationToken = default)
@@ -62,9 +62,9 @@
             return Task.CompletedTask;
         }
 
-        public Task<WorksRslt> Works(WorksArrrgs input, CancellationToken cancellationToken = default)
+        public Task<WorksRslt> Works(WorksArrrgs? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new WorksRslt() { Foo = input.Foo });
+            return Task.FromResult(new WorksRslt() { Foo = input?.Foo ?? 0 });
         }
     }
 }
diff --git a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
index 176e734..42a139c 100644
--- a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
+++ b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
@@ -68,14 +68,14 @@
     <Error Condition="$('$(ThriftBinaryVersion)'::StartsWith('$(ThriftVersionOutput)')) == true" Text="Thrift version returned: '$(ThriftBinaryVersion)' is not equal to the projects version '$(ThriftVersionOutput)'." />
     <Message Importance="high" Text="Generating tests with thrift binary: '$(PathToThrift)'" />
     <!-- Generate the thrift test files -->
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./CassandraTest.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./optional_required_default.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./name_conflicts.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./../../../../test/ThriftTest.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./../../../../contrib/fb303/if/fb303.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5253.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5320.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5382.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./CassandraTest.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./optional_required_default.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./name_conflicts.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./../../../../test/ThriftTest.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./../../../../contrib/fb303/if/fb303.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5253.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5320.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5382.thrift" />
   </Target>
 
 </Project>
diff --git a/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs b/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
index 778d24c..49108d1 100644
--- a/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
+++ b/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
@@ -83,8 +83,8 @@
         [TestMethod]
         public void TCollection_Set_Equals_Primitive_Test()
         {
-            var collection1 = new THashSet<int> {1,2,3};
-            var collection2 = new THashSet<int> {1,2,3};
+            var collection1 = new HashSet<int> {1,2,3};
+            var collection2 = new HashSet<int> {1,2,3};
             Assert.IsTrue(TCollections.Equals(collection1, collection2));
             Assert.IsTrue(collection1.SequenceEqual(collection2));
         }
@@ -92,8 +92,8 @@
         [TestMethod]
         public void TCollection_Set_Equals_Primitive_Different_Test()
         {
-            var collection1 = new THashSet<int> { 1, 2, 3 };
-            var collection2 = new THashSet<int> { 1, 2 };
+            var collection1 = new HashSet<int> { 1, 2, 3 };
+            var collection2 = new HashSet<int> { 1, 2 };
             Assert.IsFalse(TCollections.Equals(collection1, collection2));
             Assert.IsFalse(collection1.SequenceEqual(collection2));
 
@@ -105,8 +105,8 @@
         [TestMethod]
         public void TCollection_Set_Equals_Objects_Test()
         {
-            var collection1 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
-            var collection2 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
+            var collection1 = new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
+            var collection2 = new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
             Assert.IsTrue(TCollections.Equals(collection1, collection2));
             Assert.IsTrue(collection1.SequenceEqual(collection2));
         }
@@ -114,8 +114,8 @@
         [TestMethod]
         public void TCollection_Set_Set_Equals_Objects_Test()
         {
-            var collection1 = new THashSet<THashSet<ExampleClass>> { new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
-            var collection2 = new THashSet<THashSet<ExampleClass>> { new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
+            var collection1 = new HashSet<HashSet<ExampleClass>> { new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
+            var collection2 = new HashSet<HashSet<ExampleClass>> { new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
             Assert.IsTrue(TCollections.Equals(collection1, collection2));
             Assert.IsFalse(collection1.SequenceEqual(collection2));  // SequenceEqual() calls Equals() of the inner list instead of SequenceEqual()
         }
@@ -123,7 +123,7 @@
         [TestMethod]
         public void TCollection_Set_Equals_OneAndTheSameObject_Test()
         {
-            var collection1 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
+            var collection1 = new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
             var collection2 = collection1;      // references to one and the same collection
             Assert.IsTrue(TCollections.Equals(collection1, collection2));
             Assert.IsTrue(collection1.SequenceEqual(collection2));
diff --git a/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs b/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs
index 8de573e..73921ea 100644
--- a/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs
+++ b/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs
@@ -1,4 +1,4 @@
-// Licensed to the Apache Software Foundation(ASF) under one
+// Licensed to the Apache Software Foundation(ASF) under one
 // or more contributor license agreements.See the NOTICE file
 // distributed with this work for additional information
 // regarding copyright ownership.The ASF licenses this file
@@ -22,6 +22,8 @@
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Thrift.Collections;
 
+#pragma warning disable IDE0063  // simplify using 
+
 namespace Thrift.Tests.Collections
 {
     // ReSharper disable once InconsistentNaming
@@ -33,7 +35,7 @@
         {
             const int value = 1;
 
-            var hashSet = new THashSet<int> {value};
+            var hashSet = new HashSet<int> {value};
             
             Assert.IsTrue(hashSet.Contains(value));
 
diff --git a/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs b/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs
index 84fcab8..afffed5 100644
--- a/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs
+++ b/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs
@@ -24,8 +24,6 @@
 using OptReqDefTest;
 using Thrift.Collections;
 
-#nullable disable  // this is just test code, we leave it at that
-
 namespace Thrift.Tests.DataModel
 {
     // ReSharper disable once InconsistentNaming
@@ -51,7 +49,7 @@
             VerifyIdenticalContent(first, InitializeInstance(new RaceDetails()));
         }
 
-        private RaceDetails MakeNestedRaceDetails(int nesting)
+        private RaceDetails? MakeNestedRaceDetails(int nesting)
         {
             if (++nesting > 1)
                 return null;
@@ -61,7 +59,7 @@
             return instance;
         }
 
-        private jack MakeNestedUnion(int nesting)
+        private jack? MakeNestedUnion(int nesting)
         {
             if (++nesting > 1)
                 return null;
@@ -88,11 +86,11 @@
             instance.Req_one = default;
             instance.Req_two = default;
             instance.Req_three = default;
-            instance.Req_four = default;
-            instance.Req_five = default;
+            Assert.IsNotNull(instance.Req_four);
+            Assert.IsNotNull(instance.Req_five);
             instance.Req_six = default; 
             instance.Req_seven = default;;
-            instance.Req_eight = default; 
+            instance.Req_eight = default;
 
             // leave non-required fields unset again
             Assert.IsFalse(instance.__isset.def_one);
@@ -119,15 +117,15 @@
             Assert.AreEqual(instance.Req_two_with_value, 2.22);
             Assert.AreEqual(instance.Req_three_with_value, 3);
             Assert.AreEqual(instance.Req_four_with_value, "four");
-            Assert.AreEqual("five", Encoding.UTF8.GetString(instance.Req_five_with_value));
+            Assert.AreEqual("five", Encoding.UTF8.GetString(instance.Req_five_with_value!));
 
-            Assert.IsTrue(instance.Req_six_with_value.Count == 1);
+            Assert.IsTrue(instance.Req_six_with_value!.Count == 1);
             Assert.AreEqual(instance.Req_six_with_value[0], 6 );
 
-            Assert.IsTrue(instance.Req_seven_with_value.Count == 1);
+            Assert.IsTrue(instance.Req_seven_with_value!.Count == 1);
             Assert.IsTrue(instance.Req_seven_with_value.Contains(7));
 
-            Assert.IsTrue(instance.Req_eight_with_value.Count == 1);
+            Assert.IsTrue(instance.Req_eight_with_value!.Count == 1);
             Assert.IsTrue(instance.Req_eight_with_value[8] == 8);
 
             Assert.IsTrue(instance.__isset.def_one_with_value);
@@ -144,12 +142,16 @@
             if (nesting < 2)
             {
                 instance.Far_list = new List<Distance>() { Distance.foo, Distance.bar, Distance.baz };
-                instance.Far_set = new THashSet<Distance>() { Distance.foo, Distance.bar, Distance.baz };
+                instance.Far_set = new HashSet<Distance>() { Distance.foo, Distance.bar, Distance.baz };
                 instance.Far_map = new Dictionary<Distance, Distance>() { [Distance.foo] = Distance.foo, [Distance.bar] = Distance.bar, [Distance.baz] = Distance.baz };
 
-                instance.Far_set_list = new THashSet<List<Distance>>() { new List<Distance>() { Distance.foo } };
-                instance.Far_list_map_set = new List<Dictionary<sbyte, THashSet<Distance>>>() { new Dictionary<sbyte, THashSet<Distance>>() { [1] = new THashSet<Distance>() { Distance.baz } } };
-                instance.Far_map_dist_to_rds = new Dictionary<Distance, List<RaceDetails>>() { [Distance.bar] = new List<RaceDetails>() { MakeNestedRaceDetails(nesting) } };
+                instance.Far_set_list = new HashSet<List<Distance>>() { new List<Distance>() { Distance.foo } };
+                instance.Far_list_map_set = new List<Dictionary<sbyte, HashSet<Distance>>>() { new Dictionary<sbyte, HashSet<Distance>>() { [1] = new HashSet<Distance>() { Distance.baz } } };
+
+                instance.Far_map_dist_to_rds = new Dictionary<Distance, List<RaceDetails>>() { [Distance.bar] = new List<RaceDetails>()  };
+                var details = MakeNestedRaceDetails(nesting);
+                if (details != null)
+                    instance.Far_map_dist_to_rds[Distance.bar].Add(details);
 
                 instance.Req_nested = MakeNestedRaceDetails(nesting);
                 Assert.IsFalse(instance.__isset.opt_nested);
@@ -245,19 +247,19 @@
             instance.Triplesix = ModifyValue(instance.Triplesix);
         }
 
-        private jack ModifyValue(jack value, int level)
+        private jack? ModifyValue(jack? value, int level)
         {
             if (++level > 4)
                 return value;
 
             if (value == null)
                 value = MakeNestedUnion(0);
-            Debug.Assert(value.As_nested_struct != null);
+            Debug.Assert(value?.As_nested_struct != null);
             ModifyInstance(value.As_nested_struct, level);
             return value;
         }
 
-        private RaceDetails ModifyValue(RaceDetails value, int level)
+        private RaceDetails? ModifyValue(RaceDetails? value, int level)
         {
             if (++level > 4)
                 return value;
@@ -268,7 +270,7 @@
             return value;
         }
 
-        private Dictionary<Distance, List<RaceDetails>> ModifyValue(Dictionary<Distance, List<RaceDetails>> value, int level)
+        private Dictionary<Distance, List<RaceDetails>> ModifyValue(Dictionary<Distance, List<RaceDetails>>? value, int level)
         {
             if (value == null)
                 value = new Dictionary<Distance, List<RaceDetails>>();
@@ -283,29 +285,30 @@
             if (value.TryGetValue(Distance.bar, out var list) && (list.Count > 0))
             {
                 ModifyInstance(list[0], level);
-                list.Add(null);
+                //list.Add(null);  -- Thrift does not allow null values in containers
             }
 
-            value[Distance.baz] = null;
+            // Thrift does not allow null values in containers
+            //value[Distance.baz] = null;
 
             return value;
         }
 
-        private static List<Dictionary<sbyte, THashSet<Distance>>> ModifyValue(List<Dictionary<sbyte, THashSet<Distance>>> value)
+        private static List<Dictionary<sbyte, HashSet<Distance>>> ModifyValue(List<Dictionary<sbyte, HashSet<Distance>>>? value)
         {
             if (value == null)
-                value = new List<Dictionary<sbyte, THashSet<Distance>>>();
+                value = new List<Dictionary<sbyte, HashSet<Distance>>>();
 
             if (value.Count == 0)
-                value.Add(new Dictionary<sbyte, THashSet<Distance>>());
-            else
-                value.Add(null);
+                value.Add(new Dictionary<sbyte, HashSet<Distance>>());
+            //else
+            //value.Add(null); --Thrift does not allow null values in containers
 
             sbyte key = (sbyte)(value[0].Count + 10);
             if (value[0].Count == 0)
-                value[0].Add(key, new THashSet<Distance>());
-            else
-                value[0].Add(key, null);
+                value[0].Add(key, new HashSet<Distance>());
+            //else
+            //value[0].Add(key, null); --Thrift does not allow null values in containers
 
             foreach (var entry in value)
             {
@@ -327,15 +330,15 @@
             return value;
         }
 
-        private static THashSet<List<Distance>> ModifyValue(THashSet<List<Distance>> value)
+        private static HashSet<List<Distance>> ModifyValue(HashSet<List<Distance>>? value)
         {
             if (value == null)
-                value = new THashSet<List<Distance>>();
+                value = new HashSet<List<Distance>>();
 
             if (value.Count == 0)
                 value.Add(new List<Distance>());
-            else
-                value.Add(null);
+            //else
+            //value.Add(null); -- Thrift does not allow null values in containers
 
             foreach (var entry in value)
                 if( entry != null)
@@ -344,7 +347,7 @@
             return value;
         }
 
-        private static Dictionary<Distance, Distance> ModifyValue(Dictionary<Distance, Distance> value)
+        private static Dictionary<Distance, Distance> ModifyValue(Dictionary<Distance, Distance>? value)
         {
             if (value == null)
                 value = new Dictionary<Distance, Distance>();
@@ -354,10 +357,10 @@
             return value;
         }
 
-        private static THashSet<Distance> ModifyValue(THashSet<Distance> value)
+        private static HashSet<Distance> ModifyValue(HashSet<Distance>? value)
         {
             if (value == null)
-                value = new THashSet<Distance>();
+                value = new HashSet<Distance>();
 
             if (value.Contains(Distance.foo))
                 value.Remove(Distance.foo);
@@ -377,7 +380,7 @@
             return value;
         }
 
-        private static List<Distance> ModifyValue(List<Distance> value)
+        private static List<Distance> ModifyValue(List<Distance>? value)
         {
             if (value == null)
                 value = new List<Distance>();
@@ -392,7 +395,7 @@
             return !value;
         }
 
-        private static Dictionary<sbyte, short> ModifyValue(Dictionary<sbyte, short> value)
+        private static Dictionary<sbyte, short> ModifyValue(Dictionary<sbyte, short>? value)
         {
             if (value == null)
                 value = new Dictionary<sbyte, short>();
@@ -400,15 +403,15 @@
             return value;
         }
 
-        private static THashSet<long> ModifyValue(THashSet<long> value)
+        private static HashSet<long> ModifyValue(HashSet<long>? value)
         {
             if (value == null)
-                value = new THashSet<long>();
+                value = new HashSet<long>();
             value.Add(value.Count+100);
             return value;
         }
 
-        private static List<int> ModifyValue(List<int> value)
+        private static List<int> ModifyValue(List<int>? value)
         {
             if (value == null)
                 value = new List<int>();
@@ -416,16 +419,18 @@
             return value;
         }
 
-        private static byte[] ModifyValue(byte[] value)
+        private static byte[] ModifyValue(byte[]? value)
         {
             if (value == null)
                 value = new byte[1] { 0 };
             if (value.Length > 0)
                 value[0] = (value[0] < 0xFF) ? ++value[0] : (byte)0;
+            else
+                value = new byte[1] { 0 };
             return value;
         }
 
-        private static string ModifyValue(string value)
+        private static string ModifyValue(string? value)
         {
             return value + "1";
         }
diff --git a/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs b/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs
index 693b68e..ebc1717 100644
--- a/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs
+++ b/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs
@@ -25,13 +25,15 @@
 using OptReqDefTest;
 using Thrift.Collections;
 
+#pragma warning disable IDE0017  // init can be simplified - we don't want that here
+
 namespace Thrift.Tests.DataModel
 {
     // ReSharper disable once InconsistentNaming
     [TestClass]
     public class Thrift_5238
     {
-        private void CheckInstance(RaceDetails instance)
+        private static void CheckInstance(RaceDetails instance)
         {
             // object
             Assert.IsTrue(instance.__isset.def_nested);
@@ -42,14 +44,14 @@
             // string
             Assert.IsTrue(instance.__isset.def_four);
             Assert.IsTrue(instance.__isset.opt_four);
-            Assert.IsNull(instance.Req_four);
+            Assert.IsTrue(string.IsNullOrEmpty(instance.Req_four));
             Assert.IsNull(instance.Def_four);
             Assert.IsNull(instance.Opt_four);
 
             // byte[]
             Assert.IsTrue(instance.__isset.def_five);
             Assert.IsTrue(instance.__isset.opt_five);
-            Assert.IsNull(instance.Req_five);
+            Assert.IsTrue((instance.Req_five == null) || (instance.Req_five.Length == 0));
             Assert.IsNull(instance.Def_five);
             Assert.IsNull(instance.Opt_five);
 
@@ -66,6 +68,9 @@
         {
             var instance = new OptReqDefTest.RaceDetails();
 
+            // the following code INTENTIONALLY assigns null to non.nullable reftypes
+            #pragma warning disable CS8625
+
             // object
             instance.Def_nested = null;
             instance.Opt_nested = null;
@@ -85,6 +90,9 @@
             instance.Opt_six = null;
             instance.Def_six = null;
 
+            // back to normal
+            #pragma warning restore CS8625
+
             // test the setup
             CheckInstance(instance);