Skip to content

Commit

Permalink
Support for OnMethodArg
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Jun 12, 2024
1 parent 89cab20 commit a05722e
Show file tree
Hide file tree
Showing 22 changed files with 327 additions and 107 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ dotnet run
- [Tag Unique](readme/tag-unique.md)
- [Tag on injection site](readme/tag-on-injection-site.md)
- [Tag on a constructor argument](readme/tag-on-a-constructor-argument.md)
- [Tag on a type member](readme/tag-on-a-type-member.md)
- [Tag on a member](readme/tag-on-a-member.md)
- [Tag on a method argument](readme/tag-on-a-method-argument.md)
- [Tag on injection site with wildcards](readme/tag-on-injection-site-with-wildcards.md)
- [Dependent compositions](readme/dependent-compositions.md)
- [Accumulators](readme/accumulators.md)
Expand Down
69 changes: 4 additions & 65 deletions readme/tag-on-a-constructor-argument.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#### Tag on a constructor argument

[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/TagOnCtorArgScenario.cs)
[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/TagOnConstructorArgScenario.cs)

The wildcards ‘*’ and ‘?’ are supported.


```c#
namespace Pure.DI.UsageTests.Advanced.TagOnCtorArgScenario;
namespace Pure.DI.UsageTests.Advanced.OnConstructorArgScenario;


interface IDependency;
Expand Down Expand Up @@ -38,9 +38,9 @@ class Service(
}

DI.Setup(nameof(Composition))
.Bind(Tag.OnCtorArg<Service>("dependency1"))
.Bind(Tag.OnConstructorArg<Service>("dependency1"))
.To<AbcDependency>()
.Bind(Tag.OnCtorArg<Consumer<TT>>("myDep"))
.Bind(Tag.OnConstructorArg<Consumer<TT>>("myDep"))
.To<XyzDependency>()
.Bind().To<Consumer<TT>>()
.Bind<IService>().To<Service>()
Expand All @@ -54,66 +54,5 @@ service.Dependency1.ShouldBeOfType<AbcDependency>();
service.Dependency2.ShouldBeOfType<XyzDependency>();
```

The following partial class will be generated:

```c#
partial class Composition
{
private readonly Composition _root;

public Composition()
{
_root = this;
}

internal Composition(Composition parentScope)
{
_root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root;
}

public IService Root
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return new Service(new AbcDependency(), new Consumer<string>(new XyzDependency()));
}
}
}
```

Class diagram:

```mermaid
classDiagram
class Composition {
<<partial>>
+IService Root
}
AbcDependency --|> IDependency
class AbcDependency {
+AbcDependency()
}
XyzDependency --|> IDependency
class XyzDependency {
+XyzDependency()
}
Service --|> IService
class Service {
+Service(IDependency dependency1, ConsumerᐸStringᐳ consumer)
}
class ConsumerᐸStringᐳ {
+Consumer(IDependency myDep)
}
class IDependency {
<<interface>>
}
class IService {
<<interface>>
}
Composition ..> Service : IService Root
Service *-- AbcDependency : IDependency
Service *-- ConsumerᐸStringᐳ : ConsumerᐸStringᐳ
ConsumerᐸStringᐳ *-- XyzDependency : IDependency
```

4 changes: 2 additions & 2 deletions readme/tag-on-a-type-member.md → readme/tag-on-a-member.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#### Tag on a type member
#### Tag on a member

[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/TagOnMemberScenario.cs)

Expand Down Expand Up @@ -27,7 +27,7 @@ class Service : IService

DI.Setup(nameof(Composition))
.Bind().To<AbcDependency>()
.Bind(Tag.OnMember<Service>("Dependency"))
.Bind(Tag.OnMember<Service>(nameof(Service.Dependency)))
.To<XyzDependency>()
.Bind<IService>().To<Service>()

Expand Down
102 changes: 102 additions & 0 deletions readme/tag-on-a-method-argument.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#### Tag on a method argument

[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Advanced/TagOnMethodArgScenario.cs)

The wildcards ‘*’ and ‘?’ are supported.


```c#
namespace Pure.DI.UsageTests.Advanced.TagOnMethodArgScenario;


interface IDependency;

class AbcDependency : IDependency;

class XyzDependency : IDependency;

interface IService
{
IDependency? Dependency { get; }
}

class Service : IService
{
[Ordinal(1)]
public void Initialize(IDependency dep) =>
Dependency = dep;

public IDependency? Dependency { get; private set; }
}

DI.Setup(nameof(Composition))
.Bind().To<AbcDependency>()
.Bind(Tag.OnMethodArg<Service>(nameof(Service.Initialize), "dep"))
.To<XyzDependency>()
.Bind<IService>().To<Service>()

// Specifies to create the composition root named "Root"
.Root<IService>("Root");

var composition = new Composition();
var service = composition.Root;
service.Dependency.ShouldBeOfType<XyzDependency>();
```

The following partial class will be generated:

```c#
partial class Composition
{
private readonly Composition _root;

public Composition()
{
_root = this;
}

internal Composition(Composition parentScope)
{
_root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root;
}

public IService Root
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Service transientService0 = new Service();
transientService0.Initialize(new XyzDependency());
return transientService0;
}
}
}
```

Class diagram:

```mermaid
classDiagram
class Composition {
<<partial>>
+IService Root
}
XyzDependency --|> IDependency
class XyzDependency {
+XyzDependency()
}
Service --|> IService
class Service {
+Service()
+Initialize(IDependency dep) : Void
}
class IDependency {
<<interface>>
}
class IService {
<<interface>>
}
Composition ..> Service : IService Root
Service *-- XyzDependency : IDependency
```

18 changes: 17 additions & 1 deletion src/Pure.DI.Core/Components/Api.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,7 @@ internal class Tag
public static Tag OnConstructorArg<T>(string argName) => Shared;

/// <summary>
/// This tag allows you to determine which binding will be used for explicit injection for a particular constructor argument.
/// This tag allows you to define which binding will be used for explicit injection for property or field of the type.
/// <example>
/// <code>
/// DI.Setup("Composition")
Expand All @@ -1194,6 +1194,22 @@ internal class Tag
/// </summary>
/// <param name="memberName">The name of the type member.</param>
public static Tag OnMember<T>(string memberName) => Shared;

/// <summary>
/// This tag allows you to determine which binding will be used for explicit injection for a particular method argument.
/// <example>
/// <code>
/// DI.Setup("Composition")
/// .Bind(Tag.OnMethodArg&lt;Service&gt;("DoSomething", "state"))
/// .To&lt;Dependency&gt;()
/// .Bind().To&lt;Service&gt;()
/// .Root&lt;IService&gt;("Root");
/// </code>
/// </example>
/// </summary>
/// <param name="methodName">The name of the type method.</param>
/// <param name="argName">The name of the method argument.</param>
public static Tag OnMethodArg<T>(string methodName, string argName) => Shared;
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Pure.DI.Core/Core/ApiInvocationProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public void ProcessInvocation(
ImmutableArray<MdTagAttribute>.Empty,
ImmutableArray<MdOrdinalAttribute>.Empty,
ImmutableArray<MdAccumulator>.Empty,
Array.Empty<TagOn>(),
Array.Empty<TagOnSites>(),
comments.FilterHints(invocationComments).ToList()));
break;

Expand Down
4 changes: 2 additions & 2 deletions src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,10 @@ private static string FormatCardinality(int count, Lifetime lifetime)
}

private string FormatDependency(Dependency dependency, FormatOptions options) =>
$"{(dependency.Injection.Tag is null or TagOn ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}";
$"{(dependency.Injection.Tag is null or TagOnSites ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}";

private static string FormatTag(object? tag) =>
tag is null or TagOn
tag is null or TagOnSites
? ""
: EscapeTag(tag) + " ";

Expand Down
2 changes: 1 addition & 1 deletion src/Pure.DI.Core/Core/Code/CodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static string ValueToString(this object? tag, string defaultValue = "null
char ch => $"'{ch.ToString()}'",
Enum en => $"{en.GetType()}.{en}",
ITypeSymbol => $"typeof({tag})",
TagOn => defaultValue,
TagOnSites => defaultValue,
not null => tag.ToString(),
_ => defaultValue
};
Expand Down
3 changes: 1 addition & 2 deletions src/Pure.DI.Core/Core/Formatting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ namespace Pure.DI.Core;

internal static class Formatting
{
public const int IndentSize = 1;
private const int IndentsCount = 256;
private static readonly string[] Indents;

Expand All @@ -27,5 +26,5 @@ public static string IndentPrefix(Indent indent) =>
};

private static string IndentInternal(int count = 1) =>
string.Intern(new string('\t', count * IndentSize));
string.Intern(new string('\t', count));
}
2 changes: 2 additions & 0 deletions src/Pure.DI.Core/Core/ISemantic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

internal interface ISemantic
{
bool IsAccessible(ISymbol symbol);

T? TryGetTypeSymbol<T>(SemanticModel semanticModel, SyntaxNode node)
where T : ITypeSymbol;

Expand Down
21 changes: 12 additions & 9 deletions src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ internal sealed class ImplementationDependencyNodeBuilder(
ILogger<ImplementationDependencyNodeBuilder> logger,
IBuilder<DpImplementation, IEnumerable<DpImplementation>> implementationVariantsBuilder,
IWildcardMatcher wildcardMatcher,
IInjectionSiteFactory injectionSiteFactory)
IInjectionSiteFactory injectionSiteFactory,
ISemantic semantic)
: IBuilder<MdSetup, IEnumerable<DependencyNode>>
{
public IEnumerable<DependencyNode> Build(MdSetup setup)
Expand Down Expand Up @@ -73,7 +74,7 @@ public IEnumerable<DependencyNode> Build(MdSetup setup)

foreach (var member in GetMembers(implementationType))
{
if (member.IsStatic || member.DeclaredAccessibility is not (Accessibility.Internal or Accessibility.Public or Accessibility.Friend))
if (!semantic.IsAccessible(member))
{
continue;
}
Expand Down Expand Up @@ -109,7 +110,7 @@ public IEnumerable<DependencyNode> Build(MdSetup setup)
ordinal,
new Injection(
GetAttribute(setup.TypeAttributes, field, setup.TypeConstructor?.Construct(compilation, type) ?? type),
GetAttribute(setup.TagAttributes, field, default(object?)) ?? CreateTagOn(setup.TagOn, field))));
GetAttribute(setup.TagAttributes, field, default(object?)) ?? TryCreateTagOnSite(setup.TagOn, field))));
}
}

Expand All @@ -128,7 +129,7 @@ public IEnumerable<DependencyNode> Build(MdSetup setup)
ordinal,
new Injection(
GetAttribute(setup.TypeAttributes, property, setup.TypeConstructor?.Construct(compilation, type) ?? type),
GetAttribute(setup.TagAttributes, property, default(object?)) ?? CreateTagOn(setup.TagOn, property))));
GetAttribute(setup.TagAttributes, property, default(object?)) ?? TryCreateTagOnSite(setup.TagOn, property))));
}
}

Expand Down Expand Up @@ -222,23 +223,25 @@ private ImmutableArray<DpParameter> GetParameters(
parameter,
new Injection(
GetAttribute(setup.TypeAttributes, parameter, typeConstructor?.Construct(compilation, type) ?? type),
GetAttribute(setup.TagAttributes, parameter, default(object?)) ?? CreateTagOn(setup.TagOn, parameter))));
GetAttribute(setup.TagAttributes, parameter, default(object?)) ?? TryCreateTagOnSite(setup.TagOn, parameter))));
}

return dependenciesBuilder.MoveToImmutable();
}

private object? CreateTagOn(IReadOnlyCollection<TagOn> tagOn, ISymbol symbol)
private object? TryCreateTagOnSite(
IReadOnlyCollection<TagOnSites> tagOn,
ISymbol symbol)
{
var injectionSite = injectionSiteFactory.Create(symbol.ContainingSymbol, symbol.Name);
var injectionSiteSpan = injectionSite.AsSpan();
foreach (var tagOnItem in tagOn.Reverse())
foreach (var tagOnSite in tagOn)
{
foreach (var expression in tagOnItem.InjectionSites)
foreach (var expression in tagOnSite.InjectionSites)
{
if (wildcardMatcher.Match(expression.AsSpan(), injectionSiteSpan))
{
return tagOnItem;
return tagOnSite;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Pure.DI.Core/Core/MetadataBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ private void MergeSetups(IEnumerable<MdSetup> setups, out MdSetup mergedSetup, b
var tagOn = bindings
.OrderBy(i => i.Id)
.SelectMany(i => i.Contracts)
.SelectMany(binding => binding.Tags.Select(i => i.Value).OfType<TagOn>())
.SelectMany(binding => binding.Tags.Select(i => i.Value).OfType<TagOnSites>())
.Where(i => i.InjectionSites.Length > 0)
.Distinct()
.Reverse()
.ToList();

mergedSetup = new MdSetup(
Expand Down
Loading

0 comments on commit a05722e

Please sign in to comment.