Skip to content

Commit 3f8f312

Browse files
author
Nikolay Pianikov
committed
Fix nullable reference type support. Distinguish T and T? in DI contracts and fix invalid ?? generated for nullable generic singleton dependencies.
1 parent b71d2dd commit 3f8f312

2 files changed

Lines changed: 238 additions & 1 deletion

File tree

src/Pure.DI.Core/Core/Code/Parts/FieldsBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public CompositionCode Build(CompositionCode composition)
1515
var code = composition.Code;
1616
var membersCounter = composition.MembersCount;
1717
var compilation = composition.Compilation;
18-
var nullable = compilation.Options.NullableContextOptions == NullableContextOptions.Disable ? "" : "?";
18+
var isNullableEnabled = compilation.Options.NullableContextOptions != NullableContextOptions.Disable;
1919
var isAnyConstructorEnabled = constructors.IsEnabled(composition.Source);
2020
var skipFieldsInit = isAnyConstructorEnabled && !composition.IsScopeMethod;
2121
if (isAnyConstructorEnabled && composition.Singletons.Length > 0)
@@ -62,6 +62,7 @@ public CompositionCode Build(CompositionCode composition)
6262
}
6363
else
6464
{
65+
var nullable = isNullableEnabled && singletonField.InstanceType.NullableAnnotation != NullableAnnotation.Annotated ? "?" : "";
6566
code.AppendLine($"[{Names.NonSerializedAttributeTypeName}] private {typeResolver.Resolve(composition.Setup, singletonField.InstanceType)}{nullable} {singletonField.Name};");
6667
}
6768

tests/Pure.DI.IntegrationTests/NullableReferenceTypesTests.cs

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6284,6 +6284,242 @@ public static void Main()
62846284
result.GeneratedCode.Contains("Resolve<Sample.IBox<Sample.IDependency?>>()", StringComparison.Ordinal).ShouldBeTrue(result);
62856285
}
62866286

6287+
[Fact]
6288+
public async Task ShouldNotGenerateDoubleNullableForNullableGenericDependencySingleton()
6289+
{
6290+
// Given
6291+
6292+
// When
6293+
var result = await """
6294+
#nullable enable annotations
6295+
using System;
6296+
using Microsoft.Extensions.Logging;
6297+
using Pure.DI;
6298+
6299+
namespace Microsoft.Extensions.Logging
6300+
{
6301+
public interface ILogger<T>
6302+
{
6303+
}
6304+
6305+
public interface ILoggerFactory
6306+
{
6307+
ILogger<T> CreateLogger<T>();
6308+
}
6309+
6310+
public interface ILoggingBuilder
6311+
{
6312+
}
6313+
6314+
public sealed class LoggerFactory: ILoggerFactory
6315+
{
6316+
public static ILoggerFactory Create(Action<ILoggingBuilder> configure) => new LoggerFactory();
6317+
6318+
public ILogger<T> CreateLogger<T>() => new Logger<T>();
6319+
}
6320+
6321+
public sealed class Logger<T>: ILogger<T>
6322+
{
6323+
}
6324+
}
6325+
6326+
namespace PureDiNullable
6327+
{
6328+
public class Class
6329+
{
6330+
public Class(ILogger<Class>? logger) => Logger = logger;
6331+
6332+
public ILogger<Class>? Logger { get; }
6333+
}
6334+
6335+
partial class Composition
6336+
{
6337+
private void Setup() =>
6338+
DI.Setup(nameof(Composition))
6339+
.Root<Class>(nameof(Class))
6340+
.Bind<ILoggerFactory>().As(Lifetime.Singleton).To(_ => LoggerFactory.Create(builder => { }))
6341+
.Bind<ILogger<TT>>().As(Lifetime.Singleton).To(ctx =>
6342+
{
6343+
ctx.Inject<ILoggerFactory>(out var factory);
6344+
return factory.CreateLogger<TT>();
6345+
});
6346+
}
6347+
6348+
public class Program
6349+
{
6350+
public static void Main()
6351+
{
6352+
var composition = new Composition();
6353+
Console.WriteLine(composition.Class.Logger is not null);
6354+
}
6355+
}
6356+
}
6357+
""".RunAsync(new Options { LanguageVersion = LanguageVersion.CSharp10 });
6358+
6359+
// Then
6360+
result.Success.ShouldBeTrue(result);
6361+
result.StdOut.ShouldBe(["True"], result);
6362+
result.GeneratedCode.Contains("?? _singleton", StringComparison.Ordinal).ShouldBeFalse(result);
6363+
result.GeneratedCode.Contains("Microsoft.Extensions.Logging.ILogger<global::PureDiNullable.Class>? _singleton", StringComparison.Ordinal).ShouldBeTrue(result);
6364+
}
6365+
6366+
[Fact]
6367+
public async Task ShouldNotGenerateDoubleNullableForNullableGenericDependencyScoped()
6368+
{
6369+
// Given
6370+
6371+
// When
6372+
var result = await """
6373+
#nullable enable annotations
6374+
using System;
6375+
using Pure.DI;
6376+
6377+
namespace Sample;
6378+
6379+
interface IBox<T>
6380+
{
6381+
}
6382+
6383+
class Box<T>: IBox<T>
6384+
{
6385+
}
6386+
6387+
class Service
6388+
{
6389+
public Service(IBox<string>? box) => Box = box;
6390+
6391+
public IBox<string>? Box { get; }
6392+
}
6393+
6394+
static class Setup
6395+
{
6396+
private static void SetupComposition()
6397+
{
6398+
DI.Setup("Composition")
6399+
.Bind<IBox<TT>>().As(Lifetime.Scoped).To<Box<TT>>()
6400+
.Root<Service>("Service");
6401+
}
6402+
}
6403+
6404+
public class Program
6405+
{
6406+
public static void Main() => Console.WriteLine(new Composition().Service.Box is not null);
6407+
}
6408+
""".RunAsync(new Options { LanguageVersion = LanguageVersion.CSharp10 });
6409+
6410+
// Then
6411+
result.Success.ShouldBeTrue(result);
6412+
result.StdOut.ShouldBe(["True"], result);
6413+
result.GeneratedCode.Contains("?? _scoped", StringComparison.Ordinal).ShouldBeFalse(result);
6414+
result.GeneratedCode.Contains("Sample.Box<string>? _scoped", StringComparison.Ordinal).ShouldBeTrue(result);
6415+
}
6416+
6417+
[Fact]
6418+
public async Task ShouldNotGenerateDoubleNullableForNullableArrayDependencySingleton()
6419+
{
6420+
// Given
6421+
6422+
// When
6423+
var result = await """
6424+
#nullable enable annotations
6425+
using System;
6426+
using Pure.DI;
6427+
6428+
namespace Sample;
6429+
6430+
interface IDependency
6431+
{
6432+
}
6433+
6434+
class Dependency: IDependency
6435+
{
6436+
}
6437+
6438+
class Service
6439+
{
6440+
public Service(IDependency[]? dependencies) => Dependencies = dependencies;
6441+
6442+
public IDependency[]? Dependencies { get; }
6443+
}
6444+
6445+
static class Setup
6446+
{
6447+
private static void SetupComposition()
6448+
{
6449+
DI.Setup("Composition")
6450+
.Bind<IDependency>().To<Dependency>()
6451+
.Bind<IDependency[]?>().As(Lifetime.Singleton).To(ctx =>
6452+
{
6453+
ctx.Inject(out IDependency[] dependencies);
6454+
return dependencies;
6455+
})
6456+
.Root<Service>("Service");
6457+
}
6458+
}
6459+
6460+
public class Program
6461+
{
6462+
public static void Main() => Console.WriteLine(new Composition().Service.Dependencies is { Length: 1 });
6463+
}
6464+
""".RunAsync(new Options { LanguageVersion = LanguageVersion.CSharp10 });
6465+
6466+
// Then
6467+
result.Success.ShouldBeTrue(result);
6468+
result.StdOut.ShouldBe(["True"], result);
6469+
result.GeneratedCode.Contains("[]?? _singleton", StringComparison.Ordinal).ShouldBeFalse(result);
6470+
result.GeneratedCode.Contains("IDependency[]? _singleton", StringComparison.Ordinal).ShouldBeTrue(result);
6471+
}
6472+
6473+
[Fact]
6474+
public async Task ShouldGenerateNullableBackingFieldForNonNullableGenericDependencySingleton()
6475+
{
6476+
// Given
6477+
6478+
// When
6479+
var result = await """
6480+
#nullable enable annotations
6481+
using System;
6482+
using Pure.DI;
6483+
6484+
namespace Sample;
6485+
6486+
interface IBox<T>
6487+
{
6488+
}
6489+
6490+
class Box<T>: IBox<T>
6491+
{
6492+
}
6493+
6494+
class Service
6495+
{
6496+
public Service(IBox<string> box) => Box = box;
6497+
6498+
public IBox<string> Box { get; }
6499+
}
6500+
6501+
static class Setup
6502+
{
6503+
private static void SetupComposition()
6504+
{
6505+
DI.Setup("Composition")
6506+
.Bind<IBox<TT>>().As(Lifetime.Singleton).To<Box<TT>>()
6507+
.Root<Service>("Service");
6508+
}
6509+
}
6510+
6511+
public class Program
6512+
{
6513+
public static void Main() => Console.WriteLine(new Composition().Service.Box is not null);
6514+
}
6515+
""".RunAsync(new Options { LanguageVersion = LanguageVersion.CSharp10 });
6516+
6517+
// Then
6518+
result.Success.ShouldBeTrue(result);
6519+
result.StdOut.ShouldBe(["True"], result);
6520+
result.GeneratedCode.Contains("Sample.Box<string>? _singleton", StringComparison.Ordinal).ShouldBeTrue(result);
6521+
}
6522+
62876523
[Fact]
62886524
public async Task ShouldSupportNullableOnCannotResolvePartialMethod()
62896525
{

0 commit comments

Comments
 (0)