Skip to content

Commit cb3f507

Browse files
authored
Fix config source gen with value tuple (#115233)
* Fix config source gen with ValueTuple * Fix comment * Remove un-needed comment
1 parent 27604b5 commit cb3f507

File tree

5 files changed

+63
-3
lines changed

5 files changed

+63
-3
lines changed

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Suppressor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
1212
public sealed partial class ConfigurationBindingGenerator
1313
{
1414
/// <summary>
15-
/// Supresses false-positive diagnostics emitted by the linker
15+
/// Suppresses false-positive diagnostics emitted by the linker
1616
/// when analyzing binding invocations that we have intercepted.
1717
/// Workaround for https://github.com/dotnet/roslyn/issues/68669.
1818
/// </summary>

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,10 @@ private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, Initi
11801180

11811181
if (strategy is ObjectInstantiationStrategy.ParameterlessConstructor)
11821182
{
1183-
initExpr = $"new {typeFQN}()";
1183+
// value tuple types will be declared with syntax like:
1184+
// (int, int) value = default;
1185+
// This is to avoid using invalid syntax calling the parameterless constructor
1186+
initExpr = type.IsValueTuple ? "default" : $"new {typeFQN}()";
11841187
}
11851188
else
11861189
{
@@ -1195,7 +1198,11 @@ private bool EmitObjectInit(ComplexTypeSpec type, string memberAccessExpr, Initi
11951198
case InitializationKind.Declaration:
11961199
{
11971200
Debug.Assert(!memberAccessExpr.Contains('.'));
1198-
_writer.WriteLine($"var {memberAccessExpr} = {initExpr};");
1201+
// value tuple will be declared with syntax like:
1202+
// (int, int) value = default;
1203+
// We need to specify the typeFQN as we assign the variable to default value.
1204+
string declarationType = type.IsValueTuple ? typeFQN : $"var";
1205+
_writer.WriteLine($"{declarationType} {memberAccessExpr} = {initExpr};");
11991206
}
12001207
break;
12011208
case InitializationKind.AssignmentWithNullCheck:

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/Types/TypeSpec.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public TypeSpec(ITypeSymbol type)
1717
(DisplayString, FullName) = type.GetTypeNames();
1818
IdentifierCompatibleSubstring = type.ToIdentifierCompatibleSubstring();
1919
IsValueType = type.IsValueType;
20+
IsValueTuple = type is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsTupleType;
2021
}
2122

2223
public TypeRef TypeRef { get; }
@@ -36,6 +37,8 @@ public TypeSpec(ITypeSymbol type)
3637
public string IdentifierCompatibleSubstring { get; }
3738

3839
public bool IsValueType { get; }
40+
41+
public bool IsValueTuple { get; }
3942
}
4043

4144
public abstract record ComplexTypeSpec : TypeSpec

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,18 @@ private static HashSet<Assembly> GetFilteredAssemblyRefs(IEnumerable<Type> exclu
202202
}
203203
return assemblies;
204204
}
205+
206+
public static byte[] CreateAssemblyImage(Compilation compilation)
207+
{
208+
MemoryStream ms = new MemoryStream();
209+
var emitResult = compilation.Emit(ms);
210+
if (!emitResult.Success)
211+
{
212+
// Explicit failures to include in the test output.
213+
string errorMessage = string.Join(Environment.NewLine, emitResult.Diagnostics.Select(d => d.ToString()));
214+
throw new InvalidOperationException(errorMessage);
215+
}
216+
return ms.ToArray();
217+
}
205218
}
206219
}

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,43 @@ async Task Test(bool expectOutput)
257257
}
258258
}
259259

260+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
261+
public async Task ListOfTupleTest()
262+
{
263+
string source = """
264+
using Microsoft.Extensions.Configuration;
265+
using System;
266+
using System.Collections.Generic;
267+
268+
public class Program
269+
{
270+
public static void Main()
271+
{
272+
ConfigurationBuilder configurationBuilder = new();
273+
IConfiguration config = configurationBuilder.Build();
274+
275+
var settingsSection = config.GetSection("Settings");
276+
277+
Settings options = settingsSection.Get<Settings>()!;
278+
}
279+
}
280+
281+
public class Settings
282+
{
283+
public List<(string Item1, string? Item2)>? Items { get; set; }
284+
}
285+
""";
286+
287+
ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder), typeof(List<>)));
288+
Assert.NotNull(result.GeneratedSource);
289+
Assert.Empty(result.Diagnostics);
290+
291+
// Ensure the generated code can be compiled.
292+
// If there is any compilation error, exception will be thrown with the list of the errors in the exception message.
293+
byte[] emittedAssemblyImage = CreateAssemblyImage(result.OutputCompilation);
294+
Assert.NotNull(emittedAssemblyImage);
295+
}
296+
260297
/// <summary>
261298
/// We binding the type "SslClientAuthenticationOptions" which has a property "CipherSuitesPolicy" of type "CipherSuitesPolicy". We can't bind this type.
262299
/// This test is to ensure not including the property "CipherSuitesPolicy" in the generated code caused a build break.

0 commit comments

Comments
 (0)