Fix config src-gen issues with binding nullable values (#89397)
authorLayomi Akinrinade <laakinri@microsoft.com>
Mon, 24 Jul 2023 23:18:26 +0000 (16:18 -0700)
committerGitHub <noreply@github.com>
Mon, 24 Jul 2023 23:18:26 +0000 (16:18 -0700)
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs

index 08015d0..bcd4195 100644 (file)
@@ -98,11 +98,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 foreach (TypeSpec type in types)
                 {
-                    TypeSpecKind kind = type.SpecKind;
+                    TypeSpec effectiveType = type.EffectiveType;
+                    TypeSpecKind kind = effectiveType.SpecKind;
 
                     EmitStartBlock($"if (type == typeof({type.MinimalDisplayString}))");
 
-                    if (type is ParsableFromStringSpec stringParsableType)
+                    if (effectiveType is ParsableFromStringSpec stringParsableType)
                     {
                         EmitCastToIConfigurationSection();
                         EmitBindLogicFromString(
@@ -113,9 +114,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                             checkForNullSectionValue: stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue,
                             useIncrementalStringValueIdentifier: false);
                     }
-                    else if (!EmitInitException(type))
+                    else if (!EmitInitException(effectiveType))
                     {
-                        EmitBindCoreCall(type, Identifier.obj, Identifier.configuration, InitializationKind.Declaration);
+                        EmitBindCoreCall(effectiveType, Identifier.obj, Identifier.configuration, InitializationKind.Declaration);
                         _writer.WriteLine($"return {Identifier.obj};");
                     }
 
index 098eeb4..c48dfa8 100644 (file)
@@ -217,7 +217,7 @@ if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launc
         }
 
         [Fact]
-        public void GetScalar()
+        public void Get_Scalar()
         {
             var dic = new Dictionary<string, string>
             {
@@ -239,7 +239,7 @@ if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launc
         }
 
         [Fact]
-        public void GetScalarNullable()
+        public void Get_ScalarNullable()
         {
             var dic = new Dictionary<string, string>
             {
@@ -261,6 +261,50 @@ if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launc
         }
 
         [Fact]
+        public void GetValue_Scalar()
+        {
+            var dic = new Dictionary<string, string>
+            {
+                {"Integer", "-2"},
+                {"Boolean", "TRUe"},
+                {"Nested:Integer", "11"}
+            };
+            var configurationBuilder = new ConfigurationBuilder();
+            configurationBuilder.AddInMemoryCollection(dic);
+            var config = configurationBuilder.Build();
+
+            Assert.True(config.GetSection("Boolean").Get<bool>());
+            Assert.Equal(-2, config.GetSection("Integer").Get<int>());
+            Assert.Equal(11, config.GetSection("Nested:Integer").Get<int>());
+
+            Assert.True((bool)config.GetSection("Boolean").Get(typeof(bool)));
+            Assert.Equal(-2, (int)config.GetSection("Integer").Get(typeof(int)));
+            Assert.Equal(11, (int)config.GetSection("Nested:Integer").Get(typeof(int)));
+        }
+
+        [Fact]
+        public void GetValue_ScalarNullable()
+        {
+            var dic = new Dictionary<string, string>
+            {
+                {"Integer", "-2"},
+                {"Boolean", "TRUe"},
+                {"Nested:Integer", "11"}
+            };
+            var configurationBuilder = new ConfigurationBuilder();
+            configurationBuilder.AddInMemoryCollection(dic);
+            var config = configurationBuilder.Build();
+
+            Assert.True(config.GetSection("Boolean").Get<bool?>());
+            Assert.Equal(-2, config.GetSection("Integer").Get<int?>());
+            Assert.Equal(11, config.GetSection("Nested:Integer").Get<int?>());
+
+            Assert.True(config.GetSection("Boolean").Get(typeof(bool?)) is true);
+            Assert.Equal(-2, (int)config.GetSection("Integer").Get(typeof(int?)));
+            Assert.Equal(11, (int)config.GetSection("Nested:Integer").Get(typeof(int?)));
+        }
+
+        [Fact]
         public void CanBindToObjectProperty()
         {
             var dic = new Dictionary<string, string>
@@ -1358,18 +1402,35 @@ if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launc
         [Fact]
         public void CanBindRecordStructOptions()
         {
-            var dic = new Dictionary<string, string>
+            IConfiguration config = GetConfiguration("Length", "Color");
+            Validate(config.Get<RecordStructTypeOptions>());
+            Validate(config.Get<RecordStructTypeOptions?>().Value);
+
+            config = GetConfiguration("Options.Length", "Options.Color");
+            // GetValue works for only primitives.
+            //Reflection impl handles them by honoring `TypeConverter` only.
+            // Source-gen supports based on an allow-list.
+            Assert.Equal(default(RecordStructTypeOptions), config.GetValue<RecordStructTypeOptions>("Options"));
+            Assert.False(config.GetValue<RecordStructTypeOptions?>("Options").HasValue);
+
+            static void Validate(RecordStructTypeOptions options)
             {
-                {"Length", "42"},
-                {"Color", "Green"},
-            };
-            var configurationBuilder = new ConfigurationBuilder();
-            configurationBuilder.AddInMemoryCollection(dic);
-            var config = configurationBuilder.Build();
+                Assert.Equal(42, options.Length);
+                Assert.Equal("Green", options.Color);
+            }
 
-            var options = config.Get<RecordStructTypeOptions>();
-            Assert.Equal(42, options.Length);
-            Assert.Equal("Green", options.Color);
+            static IConfiguration GetConfiguration(string key1, string key2)
+            {
+                var dic = new Dictionary<string, string>
+                {
+                    { key1, "42" },
+                    { key2, "Green" },
+                };
+
+                var configurationBuilder = new ConfigurationBuilder();
+                configurationBuilder.AddInMemoryCollection(dic);
+                return configurationBuilder.Build();
+            }
         }
 
         [Fact]