Skip to content

Commit

Permalink
#42 Generic type composition root - support of type parameter constra…
Browse files Browse the repository at this point in the history
…ints
  • Loading branch information
NikolayPianikov committed Mar 14, 2024
1 parent 315fa56 commit c69c43a
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 21 deletions.
50 changes: 49 additions & 1 deletion src/Pure.DI.Core/Core/Code/RootMethodsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private void BuildRoot(CompositionCode composition, Root root)
name.Append(root.DisplayName);

var typeArgs = root.TypeDescription.TypeArgs;
var constraints = new List<string>();
if (typeArgs.Count > 0)
{
name.Append('<');
Expand All @@ -88,8 +89,55 @@ private void BuildRoot(CompositionCode composition, Root root)
}

name.Append(rootArgsStr);

code.AppendLine(name.ToString());

if (typeArgs.Count > 0)
{
using (code.Indent())
{
foreach (var typeArg in typeArgs)
{
if (typeArg.TypeParam is not { } typeParam)
{
continue;
}

var constrains = typeParam.ConstraintTypes.Select(i => typeResolver.Resolve(i).Name).ToList();
if (typeParam.HasReferenceTypeConstraint)
{
constrains.Add("class");
}

if (typeParam.HasUnmanagedTypeConstraint)
{
constrains.Add("unmanaged");
}

if (typeParam.HasNotNullConstraint)
{
constrains.Add("notnull");
}

if (typeParam.HasValueTypeConstraint)
{
constrains.Add("struct");
}

if (typeParam.HasConstructorConstraint)
{
constrains.Add("new()");
}

if (constrains.Count == 0)
{
continue;
}

code.AppendLine($"where {typeArg.Name}: {string.Join(", ", constrains)}");
}
}
}

code.AppendLine("{");
using (code.Indent())
{
Expand Down
3 changes: 2 additions & 1 deletion src/Pure.DI.Core/Core/Code/TypeDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

public readonly record struct TypeDescription(
string Name,
IReadOnlyCollection<TypeDescription> TypeArgs)
IReadOnlyCollection<TypeDescription> TypeArgs,
ITypeParameterSymbol? TypeParam)
{
public override string ToString() => Name;
};
39 changes: 20 additions & 19 deletions src/Pure.DI.Core/Core/Code/TypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,31 @@
internal class TypeResolver(IMarker marker)
: ITypeResolver
{
private readonly Dictionary<ITypeSymbol, TypeDescription> _typeParameters = new(SymbolEqualityComparer.Default);
private readonly Dictionary<ITypeSymbol, string> _names = new(SymbolEqualityComparer.Default);
private int _markerCounter;

public TypeDescription Resolve(ITypeSymbol type)
public TypeDescription Resolve(ITypeSymbol type) => Resolve(type, default);

private TypeDescription Resolve(ITypeSymbol type, ITypeParameterSymbol? typeParam)
{
if (_typeParameters.TryGetValue(type, out var description))
{
return description;
}

TypeDescription description;
switch (type)
{
case INamedTypeSymbol { IsGenericType: false }:
if (marker.IsMarker(type))
{
var typeName = _markerCounter == 0 ? "T" : $"T{_markerCounter}";
if (!_names.TryGetValue(type, out var typeName))
{
typeName = _markerCounter == 0 ? "T" : $"T{_markerCounter}";
_names.Add(type, typeName);
}

_markerCounter++;
description = new TypeDescription(typeName, ImmutableArray.Create(new TypeDescription(typeName, ImmutableArray<TypeDescription>.Empty)));
description = new TypeDescription(typeName, ImmutableArray.Create(new TypeDescription(typeName, ImmutableArray<TypeDescription>.Empty, typeParam)), typeParam);
}
else
{
description = new TypeDescription(type.ToString(), ImmutableArray<TypeDescription>.Empty);
description = new TypeDescription(type.ToString(), ImmutableArray<TypeDescription>.Empty, typeParam);
}

break;
Expand All @@ -33,29 +36,28 @@ public TypeDescription Resolve(ITypeSymbol type)
{
var elements = new List<string>();
var args = new List<TypeDescription>();
foreach (var tupleElement in tupleTypeSymbol.TupleElements)
foreach (var item in tupleTypeSymbol.TupleElements.Zip(tupleTypeSymbol.TypeParameters, (element, parameter) => (description: Resolve(element.Type, parameter), element)))
{
var tupleElementDescription = Resolve(tupleElement.Type);
elements.Add($"{tupleElementDescription} {tupleElement.Name}");
args.AddRange(tupleElementDescription.TypeArgs);
elements.Add($"{item.description} {item.element.Name}");
args.AddRange(item.description.TypeArgs);
}

description = new TypeDescription($"({string.Join(", ", elements)})", args.Distinct().ToImmutableArray());
description = new TypeDescription($"({string.Join(", ", elements)})", args.Distinct().ToImmutableArray(), typeParam);
}
break;

case INamedTypeSymbol namedTypeSymbol:
{
var types = new List<string>();
var args = new List<TypeDescription>();
foreach (var typeArgDescription in namedTypeSymbol.TypeArguments.Select(Resolve))
foreach (var typeArgDescription in namedTypeSymbol.TypeArguments.Zip(namedTypeSymbol.TypeParameters, Resolve))
{
args.AddRange(typeArgDescription.TypeArgs);
types.Add(typeArgDescription.Name);
}

var name = string.Join("", namedTypeSymbol.ToDisplayParts().TakeWhile(i => i.ToString() != "<"));
description = new TypeDescription($"{name}<{string.Join(", ", types)}>", args.Distinct().ToImmutableArray());
description = new TypeDescription($"{name}<{string.Join(", ", types)}>", args.Distinct().ToImmutableArray(), typeParam);
}
break;

Expand All @@ -65,11 +67,10 @@ public TypeDescription Resolve(ITypeSymbol type)
break;

default:
description = new TypeDescription(type.ToString(), ImmutableArray<TypeDescription>.Empty);
description = new TypeDescription(type.ToString(), ImmutableArray<TypeDescription>.Empty, typeParam);
break;
}

_typeParameters.Add(type, description);
return description;
}
}
149 changes: 149 additions & 0 deletions tests/Pure.DI.IntegrationTests/GenericRootsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ public async Task ShouldSupportGenericRoot()
namespace Sample
{
class Dep<T> { }
interface IBox<T> { T? Content { get; set; } }
class CardboardBox<T> : IBox<T>
{
public CardboardBox(Dep<T> dep)
{
}
public T? Content { get; set; }
}
Expand Down Expand Up @@ -176,4 +182,147 @@ public static void Main()
result.Success.ShouldBeTrue(result);
result.StdOut.ShouldBe(ImmutableArray.Create("Sample.Consumer`1[System.Int32]"), result);
}

[Fact]
public async Task ShouldSupportGenericRootWhenTypeConstraint()
{
// Given

// When
var result = await """
using System;
using Pure.DI;
using static Pure.DI.Lifetime;
namespace Sample
{
class Disposable: IDisposable
{
public void Dispose()
{
}
}
class Dep<T>
where T: IDisposable
{
}
interface IBox<T, TStruct>
where T: IDisposable
where TStruct: struct
{
T? Content { get; set; }
}
class CardboardBox<T, TStruct> : IBox<T, TStruct>
where T: IDisposable
where TStruct: struct
{
public CardboardBox(Dep<T> dep)
{
}
public T? Content { get; set; }
}
internal partial class Composition
{
private static void Setup()
{
DI.Setup(nameof(Composition))
.Bind<IBox<TTDisposable, TTS>>().To<CardboardBox<TTDisposable, TTS>>()
// Composition Root
.Root<IBox<TTDisposable, TTS>>("GetRoot");
}
}
public class Program
{
public static void Main()
{
var composition = new Composition();
var root = composition.GetRoot<Disposable, int>();
Console.WriteLine(root);
}
}
}
""".RunAsync(new Options(LanguageVersion.CSharp9));

// Then
result.Success.ShouldBeTrue(result);
result.StdOut.ShouldBe(ImmutableArray.Create("Sample.CardboardBox`2[Sample.Disposable,System.Int32]"), result);
}

[Fact]
public async Task ShouldSupportGenericRootWhenStructConstraint()
{
// Given

// When
var result = await """
using System;
using Pure.DI;
using static Pure.DI.Lifetime;
namespace Sample
{
interface IDependency<T> { }
class Dependency<T> : IDependency<T> { }
interface IService<T, TStruct>
where TStruct: struct
{
}
class Service<T, TStruct>: IService<T, TStruct>
where TStruct: struct
{
public Service(IDependency<T> dependency)
{
}
}
class OtherService<T, TStruct>: IService<T, TStruct>
where TStruct: struct
{
public OtherService(IDependency<T> dependency)
{
}
}
internal partial class Composition
{
private static void Setup()
{
DI.Setup(nameof(Composition))
.Bind().To<Dependency<TT>>()
.Bind().To<Service<TT, TTS>>()
.Bind("Other").To(ctx =>
{
ctx.Inject(out IDependency<TT> dependency);
return new OtherService<TT, TTS>(dependency);
})
.Root<IService<TT, TTS>>("GetMyRoot")
.Root<IService<TT, TTS>>("GetOtherService", "Other");
}
}
public class Program
{
public static void Main()
{
var composition = new Composition();
var service = composition.GetMyRoot<int, double>();
Console.WriteLine(service);
}
}
}
""".RunAsync(new Options(LanguageVersion.CSharp9));

// Then
result.Success.ShouldBeTrue(result);
result.StdOut.ShouldBe(ImmutableArray.Create("Sample.Service`2[System.Int32,System.Double]"), result);
}
}
Loading

0 comments on commit c69c43a

Please sign in to comment.