Skip to content

Commit

Permalink
Add support for FactoryOfMethods (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
YairHalberstadt authored Apr 7, 2021
1 parent a335d46 commit a361ba1
Show file tree
Hide file tree
Showing 13 changed files with 934 additions and 88 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/dotnet-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.102'
dotnet-version: '5.0.x'
- name: Install dependencies
run: dotnet restore
- name: Build
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.102'
dotnet-version: '5.0.x'
- name: Install dependencies
run: dotnet restore
- name: Build
Expand All @@ -83,7 +83,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.102'
dotnet-version: '5.0.x'
- name: Setup MSbuild
uses: microsoft/[email protected]
- name: Setup nuget
Expand Down
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
<PropertyGroup>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>true</WarningsAsErrors>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>CS1591</NoWarn>
<WarningsNotAsErrors>AD0001</WarningsNotAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>5</AnalysisLevel>
<StrongInjectSampleVersion>*-*</StrongInjectSampleVersion>
<StrongInjectExtensionsDependenInjectionSampleVersion>*-*</StrongInjectExtensionsDependenInjectionSampleVersion>
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Logo kindly contributed by [@onelioubov](https://github.com/onelioubov) and [@kh
- [Instance fields and properties](#instance-fields-and-properties)
- [Factories](#factories)
- [Generic Factory Methods](#generic-factory-methods)
- [Factory Of Methods](#factory-of-methods)
- [Decorators](#decorators)
- [Providing registrations at runtime or integrating with other IOC containers](#providing-registrations-at-runtime-or-integrating-with-other-ioc-containers)
- [How StrongInject picks which registration to use](#how-stronginject-picks-which-registration-to-use)
Expand Down Expand Up @@ -462,6 +463,26 @@ public class ImmutableArrayModule

Generic methods can also have constraints. StrongInject will ignore generic methods during resolution if the constraints do not match.

##### Factory Of Methods

Sometimes you have a generic factory method which you don't want to use for everything, but only for specific use cases. For example, you may want to use another IOC container to resolve specific types, but don't want to write a seperate factory method for each type. In such cases you can mark the method with any number of `FactoryOfAttribute`s, and it will act exactly like a normal factory method except it will only be used to resolve the types given in the `FactoryOfAttribute`s.

```csharp
public partial class AspNetCoreControllerContainer : IContainer<SomeAspNetCoreController>
{
private readonly IServiceProvider _serviceProvider;

public AspNetCoreControllerContainer(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}

[FactoryOf(typeof(ILogger<>), FactoryOf(typeof(IConfiguration))] private T GetService<T>() => _serviceProvider.GetRequiredService<T>();
}
```

Here `GetService` will only be used to resolve `ILogger<T>` and `IConfiguration`.

#### Decorators

A decorator is a type which exposes a service by wrapping an underlying instance of the same service. Calls may pass straight through to the underlying service, or may be intercepted and custom behaviour applied. See https://en.wikipedia.org/wiki/Decorator_pattern.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using Android.Widget;
using Android.OS;

#nullable enable

namespace StrongInject.Samples.XamarinApp.Droid
{
[Activity(Label = "StrongInject.Samples.XamarinApp", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]
Expand All @@ -23,7 +25,7 @@ protected override void OnCreate(Bundle savedInstanceState)
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
public override void OnRequestPermissionsResult(int requestCode, string[]? permissions, [GeneratedEnum] Android.Content.PM.Permission[]? grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

Expand Down
534 changes: 534 additions & 0 deletions StrongInject.Tests.Unit/GeneratorTests.cs

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions StrongInject/FactoryOfAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace StrongInject
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public sealed class FactoryOfAttribute : Attribute
{
public FactoryOfAttribute(Type type, Scope scope = Scope.InstancePerResolution)
{
Type = type;
Scope = scope;
}
public Type Type { get; }
public Scope Scope { get; }
}
}
4 changes: 2 additions & 2 deletions StrongInject/Generator/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dic
return value;
}

public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dic, TKey key, TValue defaultValue = default)
public static TValue? GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dic, TKey key, TValue? defaultValue = default)
{
if (dic.TryGetValue(key, out var value))
{
return value;
}
return defaultValue!;
return defaultValue;
}

public static void WithInstanceSource(this Dictionary<ITypeSymbol, InstanceSources> instanceSources, InstanceSource instanceSource)
Expand Down
6 changes: 6 additions & 0 deletions StrongInject/Generator/FactoryOfMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Microsoft.CodeAnalysis;

namespace StrongInject.Generator
{
internal record FactoryOfMethod(FactoryMethod Underlying, ITypeSymbol FactoryOfType){}
}
137 changes: 89 additions & 48 deletions StrongInject/Generator/GenericRegistrationsResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using static StrongInject.Generator.GenericResolutionHelpers;

Expand Down Expand Up @@ -99,39 +100,42 @@ public class Builder
{
private readonly List<Builder> _children = new();
private readonly List<FactoryMethod> _factoryMethods = new();
private readonly List<FactoryOfMethod> _factoryOfMethods = new();

public void Add(Builder child) => _children.Add(child);
public void Add(FactoryMethod factoryMethod) => _factoryMethods.Add(factoryMethod);
public void Add(FactoryOfMethod factoryOfMethod) => _factoryOfMethods.Add(factoryOfMethod);

public GenericRegistrationsResolver Build(Compilation compilation)
{
var (namedTypeBuckets, otherTypesBucket, typeParameterBucket) = Partition(this, compilation);
return new(namedTypeBuckets, otherTypesBucket, typeParameterBucket);
return new(namedTypeBuckets,
otherTypesBucket ?? new BucketBuilder().Build(compilation),
typeParameterBucket ?? new BucketBuilder().Build(compilation));

static (Dictionary<INamedTypeSymbol, Bucket> namedTypeBuckets, Bucket otherTypesBucket, Bucket typeParameterBucket) Partition(Builder builder, Compilation compilation)
static (Dictionary<INamedTypeSymbol, Bucket> namedTypeBuckets, Bucket? otherTypesBucket, Bucket? typeParameterBucket) Partition(Builder builder, Compilation compilation)
{
Dictionary<INamedTypeSymbol, (List<Bucket>? buckets, ImmutableArray<FactoryMethod>.Builder? factoryMethods)> namedTypeBucketsAndFactoryMethods = new(SymbolEqualityComparer.Default);
List<Bucket>? otherTypesBuckets = null;
List<Bucket>? typeParameterBuckets = null;
ImmutableArray<FactoryMethod>.Builder? otherTypesFactoryMethods = null;
ImmutableArray<FactoryMethod>.Builder? typeParameterFactoryMethods = null;
Dictionary<INamedTypeSymbol, BucketBuilder> namedTypeBucketBuilders = new(SymbolEqualityComparer.Default);
BucketBuilder? otherTypesBucketBuilder = null;
BucketBuilder? typeParameterBucketBuilder = null;

foreach (var (childNamedTypeBuckets, childOtherTypesBucket, childTypeParameterBucket) in builder._children.Select(x => Partition(x, compilation)))
{
foreach (var (namedType, bucket) in childNamedTypeBuckets)
{
namedTypeBucketsAndFactoryMethods.CreateOrUpdate(
namedTypeBucketBuilders.GetOrCreate(
namedType,
bucket,
static (_, b) => (new List<Bucket> { b }, null),
static (_, b, l) =>
{
l.buckets!.Add(b);
return l;
});
static _ => new BucketBuilder()).Add(bucket);
}
if (childOtherTypesBucket != null)
{
(otherTypesBucketBuilder ??= new()).Add(childOtherTypesBucket);
}

if (childTypeParameterBucket != null)
{
(typeParameterBucketBuilder ??= new()).Add(childTypeParameterBucket);
}
(otherTypesBuckets ??= new()).Add(childOtherTypesBucket);
(typeParameterBuckets ??= new()).Add(childTypeParameterBucket);
}

foreach (var factoryMethod in builder._factoryMethods)
Expand All @@ -140,57 +144,81 @@ public GenericRegistrationsResolver Build(Compilation compilation)
{
case INamedTypeSymbol namedType:
{
namedTypeBucketsAndFactoryMethods.CreateOrUpdate(
namedTypeBucketBuilders.GetOrCreate(
namedType.OriginalDefinition,
factoryMethod,
static (_, f) =>
{
var builder = ImmutableArray.CreateBuilder<FactoryMethod>();
builder.Add(f);
return (null, builder);
},
static (_, f, l) =>
{
(l.factoryMethods ??= ImmutableArray.CreateBuilder<FactoryMethod>()).Add(f);
return l;
});
static _ => new BucketBuilder()).Add(factoryMethod);
break;
}

case IArrayTypeSymbol or IFunctionPointerTypeSymbol or IPointerTypeSymbol:
(otherTypesFactoryMethods ??= ImmutableArray.CreateBuilder<FactoryMethod>()).Add(factoryMethod);
(otherTypesBucketBuilder ??= new()).Add(factoryMethod);
break;
case ITypeParameterSymbol:
(typeParameterFactoryMethods ??= ImmutableArray.CreateBuilder<FactoryMethod>()).Add(factoryMethod);
(typeParameterBucketBuilder ??= new()).Add(factoryMethod);
break;
case var type: throw new NotImplementedException(type.ToString());
}
}

foreach (var factoryOfMethod in builder._factoryOfMethods)
{
if (factoryOfMethod.FactoryOfType is not INamedTypeSymbol type)
throw new InvalidOperationException("This location is thought to be unreachable");

namedTypeBucketBuilders.GetOrCreate(
type.OriginalDefinition,
static _ => new BucketBuilder()).Add(factoryOfMethod);
}

return
(
namedTypeBucketsAndFactoryMethods.ToDictionary(
namedTypeBucketBuilders.ToDictionary(
x => x.Key,
x => new Bucket(
x.Value.buckets ?? Enumerable.Empty<Bucket>(),
x.Value.factoryMethods?.ToImmutable() ?? ImmutableArray<FactoryMethod>.Empty,
compilation)),
new Bucket(
otherTypesBuckets ?? Enumerable.Empty<Bucket>(),
otherTypesFactoryMethods?.ToImmutable() ?? ImmutableArray<FactoryMethod>.Empty,
compilation),
new Bucket(
typeParameterBuckets ?? Enumerable.Empty<Bucket>(),
typeParameterFactoryMethods?.ToImmutable() ?? ImmutableArray<FactoryMethod>.Empty,
compilation)
x => x.Value.Build(compilation)),
otherTypesBucketBuilder?.Build(compilation),
typeParameterBucketBuilder?.Build(compilation)
);
}
}

private class BucketBuilder
{
private List<Bucket>? _buckets;
private ImmutableArray<FactoryMethod>.Builder? _factoryMethods;
private ImmutableArray<FactoryOfMethod>.Builder? _factoryOfMethods;

public void Add(Bucket bucket)
{
_buckets ??= new();
_buckets.Add(bucket);
}

public void Add(FactoryMethod factoryMethod)
{
_factoryMethods ??= ImmutableArray.CreateBuilder<FactoryMethod>();
_factoryMethods.Add(factoryMethod);
}

public void Add(FactoryOfMethod factoryOfMethod)
{
_factoryOfMethods ??= ImmutableArray.CreateBuilder<FactoryOfMethod>();
_factoryOfMethods.Add(factoryOfMethod);
}

public Bucket Build(Compilation compilation)
{
return new Bucket(
_buckets ?? Enumerable.Empty<Bucket>(),
_factoryMethods?.ToImmutable() ?? ImmutableArray<FactoryMethod>.Empty,
_factoryOfMethods?.ToImmutable() ?? ImmutableArray<FactoryOfMethod>.Empty,
compilation);
}
}
}

private class Bucket
{
public Bucket(IEnumerable<Bucket> childResolvers, ImmutableArray<FactoryMethod> factoryMethods, Compilation compilation)
public Bucket(IEnumerable<Bucket> childResolvers, ImmutableArray<FactoryMethod> factoryMethods, ImmutableArray<FactoryOfMethod> factoryOfMethods, Compilation compilation)
{
var builder = ImmutableArray.CreateBuilder<Bucket>();
foreach (var childResolver in childResolvers)
Expand All @@ -206,18 +234,21 @@ public Bucket(IEnumerable<Bucket> childResolvers, ImmutableArray<FactoryMethod>
}
_childResolvers = builder.ToImmutable();
_factoryMethods = factoryMethods;
_factoryOfMethods = factoryOfMethods;
_compilation = compilation;
}

private readonly ImmutableArray<Bucket> _childResolvers;
private readonly ImmutableArray<FactoryMethod> _factoryMethods;
private readonly ImmutableArray<FactoryOfMethod> _factoryOfMethods;
private readonly Compilation _compilation;

public bool TryResolve(ITypeSymbol type, out FactoryMethod instanceSource, out bool isAmbiguous, out IEnumerable<FactoryMethod> sourcesNotMatchingConstraints)
{
instanceSource = null!;
List<FactoryMethod>? factoriesWhereConstraintsDoNotMatch = null;
foreach (var factoryMethod in _factoryMethods)

foreach (var factoryMethod in GetAllRelevantFactoryMethods(type))
{
if (CanConstructFromGenericFactoryMethod(type, factoryMethod, out var constructedFactoryMethod, out var constraintsDoNotMatch))
{
Expand Down Expand Up @@ -287,7 +318,7 @@ public bool TryResolve(ITypeSymbol type, out FactoryMethod instanceSource, out b

public void ResolveAll(ITypeSymbol type, HashSet<FactoryMethod> instanceSources)
{
foreach (var factoryMethod in _factoryMethods)
foreach (var factoryMethod in GetAllRelevantFactoryMethods(type))
{
if (CanConstructFromGenericFactoryMethod(type, factoryMethod, out var constructedFactoryMethod, out _))
{
Expand All @@ -301,6 +332,16 @@ public void ResolveAll(ITypeSymbol type, HashSet<FactoryMethod> instanceSources)
}
}

private IEnumerable<FactoryMethod> GetAllRelevantFactoryMethods(ITypeSymbol toConstruct)
{
return _factoryMethods.Concat(_factoryOfMethods.Where(x => IsRelevant(x, toConstruct)).Select(x => x.Underlying));
}

private static bool IsRelevant(FactoryOfMethod factoryOfMethod, ITypeSymbol toConstruct)
{
return factoryOfMethod.FactoryOfType.OriginalDefinition.Equals(toConstruct.OriginalDefinition, SymbolEqualityComparer.Default);
}

private bool CanConstructFromGenericFactoryMethod(ITypeSymbol toConstruct, FactoryMethod factoryMethod, out FactoryMethod constructedFactoryMethod, out bool constraintsDoNotMatch)
{
if (!CanConstructFromGenericMethodReturnType(_compilation, toConstruct, factoryMethod.FactoryOfType, factoryMethod.Method, out var constructedMethod, out constraintsDoNotMatch))
Expand Down
1 change: 1 addition & 0 deletions StrongInject/Generator/InstanceSource.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using StrongInject.Generator.Visitors;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;

Expand Down
Loading

0 comments on commit a361ba1

Please sign in to comment.