Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
carl-andersson-at-westermo committed Mar 11, 2024
2 parents ba41600 + 08366b0 commit 1c7c454
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 30 deletions.
4 changes: 3 additions & 1 deletion FactoryGenerator.Attributes/IContainer.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;

namespace FactoryGenerator;

public interface IContainer
public interface IContainer : IDisposable
{
T Resolve<T>();
object Resolve(Type type);
Expand Down Expand Up @@ -38,4 +39,5 @@ bool TryResolve<T>([NotNullWhen(true)] out T resolved)
}

bool IsRegistered(Type type) => TryResolve(type, out _);
bool IsRegistered<T>() => IsRegistered(typeof(T));
}
21 changes: 15 additions & 6 deletions FactoryGenerator/FactoryGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,11 @@ public object Resolve(Type type)
var declarations = new Dictionary<string, string>();
var availableInterfaces = interfaceInjectors.Keys.ToImmutableArray();
var constructorParameters = new List<IParameterSymbol>();
var disposables = new List<Injection>();

foreach (var injection in ordered)
{
if (injection.Disposable && injection.Singleton) disposables.Add(injection);
declarations[injection.Name] = injection.Declaration(availableInterfaces);
HashSet<IParameterSymbol>? missing = null;
injection.GetBestConstructor(availableInterfaces, ref missing);
Expand Down Expand Up @@ -268,8 +270,12 @@ public object Resolve(Type type)
allArguments.Select(arg => arg.Split(' ').Last()).Select(arg => $"this.{arg} = {arg};"));
var dictSize = interfaceInjectors.Count + localizedParameters.Count + requested.Count +
constructorParameters.Count;
yield return Constructor(usingStatements, constructorFields, constructor, constructorAssignments, dictSize, interfaceInjectors, localizedParameters, requested, constructorParameters);
yield return Declarations(usingStatements, declarations);
yield return Constructor(usingStatements, constructorFields,
constructor, constructorAssignments,
dictSize, interfaceInjectors,
localizedParameters, requested,
constructorParameters);
yield return Declarations(usingStatements, declarations, disposables);
yield return ArrayDeclarations(usingStatements, arrayDeclarations);
}

Expand All @@ -291,7 +297,8 @@ public DependencyInjectionContainer{constructor}
{MakeDictionary(requested)}
{MakeDictionary(constructorParameters)}
}};
}}
}}
}}";
}

Expand All @@ -304,12 +311,16 @@ public partial class DependencyInjectionContainer
}}";
}

private static string Declarations(string usingStatements, Dictionary<string, string> declarations)
private static string Declarations(string usingStatements, Dictionary<string, string> declarations, List<Injection> disposables)
{
return $@"{usingStatements}
public partial class DependencyInjectionContainer
{{
{string.Join("\n\t", declarations.Values)}
public void Dispose()
{{
{string.Join("\n\t", disposables.Select(d => d.DisposeCall))}
}}
}}";
}

Expand All @@ -330,8 +341,6 @@ private static void MakeArray(Dictionary<string, string> declarations, string na
return source;
}}";
}

var functor = function ? "()" : "";
if (function)
{
declarations[name] = $@"
Expand Down
56 changes: 34 additions & 22 deletions FactoryGenerator/Injection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ public class Injection
{
public INamedTypeSymbol Type { get; }
public ImmutableArray<INamedTypeSymbol> Interfaces { get; }
private bool Singleton { get; }
public bool Singleton { get; }
public BooleanInjection? BooleanInjection { get; }
private ISymbol? Lambda { get; }
private string LazyName => "m_" + Name.Replace("()", "");
public string Name => SymbolUtility.MemberName(Type).Replace("()", "") + (Lambda is not null ? Lambda.Name : string.Empty) + "()";
public bool Disposable { get; }

public string Declaration(ImmutableArray<INamedTypeSymbol> availableParameters)
{
var creationCall = CreationCall(availableParameters);
return Singleton ? SymbolUtility.SingletonFactory(Type, Name, LazyName, creationCall) : $"private {Type} {Name} => {creationCall};";
}

public string DisposeCall => LazyName + "?.Dispose();";

private string CreationCall(ImmutableArray<INamedTypeSymbol> availableParameters)
{
HashSet<IParameterSymbol>? missing = null;
Expand All @@ -31,15 +35,19 @@ private string CreationCall(ImmutableArray<INamedTypeSymbol> availableParameters
case IMethodSymbol methodSymbol:
if (!availableParameters.Contains(methodSymbol.ContainingType))
{
throw new Exception($"Could not find any [Inject]ed implementations of {methodSymbol.ContainingType} to use as the source for the injection of {methodSymbol.ContainingType}.{methodSymbol.Name}. Please provide at least one injection of the type {methodSymbol.ContainingType}.");
throw new Exception(
$"Could not find any [Inject]ed implementations of {methodSymbol.ContainingType} to use as the source for the injection of {methodSymbol.ContainingType}.{methodSymbol.Name}. Please provide at least one injection of the type {methodSymbol.ContainingType}.");
}

creationCall = $"{SymbolUtility.MemberName(methodSymbol.ContainingType)}.{methodSymbol.Name}{MakeMethodCall(methodSymbol, missing)}";
break;
case IPropertySymbol parameterSymbol:
if (!availableParameters.Contains(parameterSymbol.ContainingType))
{
throw new Exception($"Could not find any [Inject]ed implementations of {parameterSymbol.ContainingType} to use as the source for the injection of {parameterSymbol.ContainingType}.{parameterSymbol.Name}. Please provide at least one injection of the type {parameterSymbol.ContainingType}.");
throw new Exception(
$"Could not find any [Inject]ed implementations of {parameterSymbol.ContainingType} to use as the source for the injection of {parameterSymbol.ContainingType}.{parameterSymbol.Name}. Please provide at least one injection of the type {parameterSymbol.ContainingType}.");
}

creationCall = $"{SymbolUtility.MemberName(parameterSymbol.ContainingType)}.{parameterSymbol.Name}";
break;
default:
Expand Down Expand Up @@ -126,16 +134,19 @@ private string MakeMethodCall(IMethodSymbol? constructor, HashSet<IParameterSymb
return chosenConstructor;
}

private Injection(INamedTypeSymbol type, ImmutableArray<INamedTypeSymbol> interfaces, bool singleton, BooleanInjection? booleanInjection = null, ISymbol? lambda = null)
private Injection(INamedTypeSymbol type, ImmutableArray<INamedTypeSymbol> interfaces,
bool singleton,
bool disposable,
BooleanInjection? booleanInjection, ISymbol? lambda)
{
Type = type;
Interfaces = interfaces;
Singleton = singleton;
Disposable = disposable;
BooleanInjection = booleanInjection;
Lambda = lambda;
}

public string Name => SymbolUtility.MemberName(Type).Replace("()", "") + (Lambda is not null ? Lambda.Name : string.Empty) + "()";

public static Injection? Create(ISymbol symbol, ImmutableArray<AttributeData> attributes, CancellationToken token)
{
Expand All @@ -144,24 +155,24 @@ private Injection(INamedTypeSymbol type, ImmutableArray<INamedTypeSymbol> interf
switch (symbol)
{
case INamedTypeSymbol nts:
{
namedTypeSymbol = nts;
if (namedTypeSymbol.TypeKind == TypeKind.Interface) return null;
if (namedTypeSymbol.IsAbstract) return null;
break;
}
{
namedTypeSymbol = nts;
if (namedTypeSymbol.TypeKind == TypeKind.Interface) return null;
if (namedTypeSymbol.IsAbstract) return null;
break;
}
case IMethodSymbol methodSymbol:
{
namedTypeSymbol = methodSymbol.ReturnType as INamedTypeSymbol;
lambda = methodSymbol;
break;
}
{
namedTypeSymbol = methodSymbol.ReturnType as INamedTypeSymbol;
lambda = methodSymbol;
break;
}
case IPropertySymbol property:
{
namedTypeSymbol = property.Type as INamedTypeSymbol;
lambda = property;
break;
}
{
namedTypeSymbol = property.Type as INamedTypeSymbol;
lambda = property;
break;
}
}

if (namedTypeSymbol is null) return null;
Expand Down Expand Up @@ -215,6 +226,7 @@ private Injection(INamedTypeSymbol type, ImmutableArray<INamedTypeSymbol> interf
}

interfaces = interfaces.AddRange(attributedInterfaces);
var isDisposable = namedTypeSymbol.AllInterfaces.Any(i => i.Name.Equals("IDisposable"));
var disposable = interfaces.FirstOrDefault(interfaceSymbol => interfaceSymbol.Name.Contains("IDisposable"));

if (disposable is not null)
Expand All @@ -225,7 +237,7 @@ private Injection(INamedTypeSymbol type, ImmutableArray<INamedTypeSymbol> interf
interfaces = interfaces.RemoveRange(preventedInterfaces).Distinct(SymbolEqualityComparer.Default).Cast<INamedTypeSymbol>().ToImmutableArray();


return new Injection(namedTypeSymbol, interfaces, singleInstance, boolean, lambda);
return new Injection(namedTypeSymbol, interfaces, singleInstance, isDisposable, boolean, lambda);
}

private static BooleanInjection? HandleBoolean(AttributeData attributeData)
Expand Down
33 changes: 33 additions & 0 deletions Tests/FactoryGenerator.Tests/InjectionDetectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,39 @@ public void InheritorsOverride()
m_container.Resolve<IOverridable>().ShouldBeOfType<Overrider>();
}


[Fact]
public void DisposingContainerDisposesSingletons()
{
ISingletonDisposer singleton;
{
using var myContainer = new DependencyInjectionContainer(false, default!);
singleton = myContainer.Resolve<ISingletonDisposer>();
singleton.ShouldBeOfType<DisposableSingleton>();
}
((DisposableSingleton) singleton).WasDisposed.ShouldBeTrue();
}

[Fact]
public void DisposingContainerDoesNotDisposeUntrackedInstances()
{
IDisposer singleton;
{
using var myContainer = new DependencyInjectionContainer(false, default!);
singleton = myContainer.Resolve<IDisposer>();
singleton.ShouldBeOfType<DisposableNonSingleton>();
}
((DisposableNonSingleton) singleton).WasDisposed.ShouldBeFalse();
((DisposableNonSingleton) singleton).Dispose();
((DisposableNonSingleton) singleton).WasDisposed.ShouldBeTrue();
}

[Fact]
public void DisposingContainerDoesNotDisposesUnreferencedSingletons()
{
using var myContainer = new DependencyInjectionContainer(false, default!);
}

[Fact]
public void ArrayExpressionsCollect()
{
Expand Down
28 changes: 27 additions & 1 deletion Tests/TestData/Inherited/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,30 @@ public class RequestedArray1 : IRequestedArray;
public class RequestedArray2 : IRequestedArray;

[Inject]
public class RequestedArray3 : IRequestedArray;
public class RequestedArray3 : IRequestedArray;

public interface IDisposer;

[Inject]
public class DisposableNonSingleton : IDisposer, IDisposable
{
public bool WasDisposed { get; private set; }

public void Dispose()
{
WasDisposed = true;
}
}

public interface ISingletonDisposer;

[Inject, Singleton]
public class DisposableSingleton : ISingletonDisposer, IDisposable
{
public bool WasDisposed { get; private set; }

public void Dispose()
{
WasDisposed = true;
}
}

0 comments on commit 1c7c454

Please sign in to comment.