Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ bool TryRegisterCore()
{
foreach (PropertySpec property in objectSpec.Properties!)
{
// Skip types reachable only through non-bindable properties, unless the
// property backs a constructor parameter. Constructor parameters are always
// bound in the Initialize method, so their types must still be registered.
if (!_typeIndex.ShouldBindTo(property) && property.MatchingCtorParam is null)
Comment thread
rosebyte marked this conversation as resolved.
Comment thread
rosebyte marked this conversation as resolved.
{
continue;
}

TryRegisterTransitiveTypesForMethodGen(property.TypeRef);

if (_typeIndex.GetTypeSpec(property.TypeRef) is ComplexTypeSpec)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,152 @@ public static void Main()
AssertCanCreateAssemblyImage(result.OutputCompilation);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
public async Task ListOfTupleWithComplexElementInInternalPropertyTest()
{
string source = """
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;

public class Program
{
public static void Main()
{
ConfigurationBuilder configurationBuilder = new();
IConfiguration config = configurationBuilder.Build();
ExampleOptions options = new();
config.Bind(options);
}
}

public class ExampleOptions
{
public List<string> ExampleCollection { get; set; } = new();

internal List<(string, ICollection<string>?)> UsesCollection =>
[
("Label-1", ExampleCollection)
];
}
""";

ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder), typeof(List<>)));
Assert.NotNull(result.GeneratedSource);
Assert.Empty(result.Diagnostics);

AssertCanCreateAssemblyImage(result.OutputCompilation);
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
[InlineData("IReadOnlyList")]
[InlineData("IReadOnlyCollection")]
[InlineData("IReadOnlySet")]
[InlineData("IEnumerable")]
public async Task ReadOnlyCollectionConstructorParameterIsBindable(string collectionType)
{
string source = $$"""
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;

public class Program
{
public static void Main()
{
ConfigurationBuilder configurationBuilder = new();
IConfiguration config = configurationBuilder.Build();
Options options = config.Get<Options>();
}
}

public record Options(string Name, {{collectionType}}<string> Values);
""";

ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder), typeof(List<>)));
Assert.NotNull(result.GeneratedSource);
Assert.Empty(result.Diagnostics);

// The collection type is only reachable through a read-only property, so its BindCore
// helper must still be generated for the constructor parameter.
AssertCanCreateAssemblyImage(result.OutputCompilation);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
public async Task ComplexReadOnlyListConstructorParameterIsBindable()
{
string source = """
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;

public class Program
{
public static void Main()
{
ConfigurationBuilder configurationBuilder = new();
IConfiguration config = configurationBuilder.Build();
Options options = config.Get<Options>();
}
}

public class Child
{
public string Value { get; set; }
}

public record Options(string Name, IReadOnlyList<Child> Items);
""";

ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder), typeof(List<>)));
Assert.NotNull(result.GeneratedSource);
Assert.Empty(result.Diagnostics);

AssertCanCreateAssemblyImage(result.OutputCompilation);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNetCore))]
public async Task TypeReachableOnlyThroughNonBindablePropertyIsNotEmitted()
{
string source = """
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;

public class Program
{
public static void Main()
{
ConfigurationBuilder configurationBuilder = new();
IConfiguration config = configurationBuilder.Build();
ExampleOptions options = new();
config.Bind(options);
}
}

public class ExampleOptions
{
public List<string> ExampleCollection { get; set; } = new();

// Non-bindable internal property. UnreachableChild is only reachable through it,
// so the generator must not emit any binding code that references it.
internal List<UnreachableChild> UsesCollection => new();
}

public class UnreachableChild
{
public string Value { get; set; }
}
""";

ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source, assemblyReferences: GetAssemblyRefsWithAdditional(typeof(ConfigurationBuilder), typeof(List<>)));
Assert.NotNull(result.GeneratedSource);
Assert.Empty(result.Diagnostics);

// The type is reachable only through a non-bindable property, so the generator must
// not register or emit any binding code that references it.
Assert.DoesNotContain("UnreachableChild", result.GeneratedSource.Value.SourceText.ToString());

AssertCanCreateAssemblyImage(result.OutputCompilation);
}

[Fact]
public async Task BindingToCollectionOnlyTest()
{
Expand Down
Loading