Skip to content

Commit

Permalink
A special tag that allows injection by name
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Jun 11, 2024
1 parent 963cecb commit 09184b9
Show file tree
Hide file tree
Showing 36 changed files with 337 additions and 553 deletions.
1 change: 1 addition & 0 deletions Pure.DI.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=omposition/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Parallelize/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pianikov/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=pinnable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=precompiling/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=randomizer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=reentrancy/@EntryIndexedValue">True</s:Boolean>
Expand Down
1 change: 0 additions & 1 deletion build/ReadmeTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,6 @@ private static async Task AddExample(string logsDirectory, string exampleSearchP
foreach (var generatedCodeFile in Directory.GetFiles(Path.Combine(logsDirectory, "Pure.DI", "Pure.DI.SourceGenerator"), exampleSearchPattern).OrderBy(i => i))
{
var ns = string.Join('.', Path.GetFileName(generatedCodeFile).Split('.').Reverse().Skip(3).Reverse()) + ".";
var name = Path.GetFileName(generatedCodeFile).Split('.').Reverse().Skip(2).FirstOrDefault() ?? "Generated";
await examplesWriter.WriteLineAsync("The following partial class will be generated:");
await examplesWriter.WriteLineAsync("");
await examplesWriter.WriteLineAsync("```c#");
Expand Down
2 changes: 1 addition & 1 deletion readme/advanced-interception.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Service(IDependency dependency) : IService

internal partial class Composition: IInterceptor
{
private readonly List<string> _log = new();
private readonly List<string> _log = [];
private static readonly IProxyBuilder ProxyBuilder = new DefaultProxyBuilder();
private readonly IInterceptor[] _interceptors = [];

Expand Down
10 changes: 5 additions & 5 deletions readme/tag-on-injection-site-with-wildcards.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ class Service(
}

DI.Setup(nameof(Composition))
.Bind(Tag.On("*Service:dependency1", "*Consumer:myDep"))
.Bind(Tag.On("*Service:Dependency3", "*Consumer:myDep"))
.To<AbcDependency>()
.Bind(Tag.On("*Service:dependency2", "*Service:Dependency3"))
.Bind(Tag.On("*Service:dependency?"))
.To<XyzDependency>()
.Bind().To<Consumer<TT>>()
.Bind<IService>().To<Service>()
Expand All @@ -59,9 +59,9 @@ DI.Setup(nameof(Composition))

var composition = new Composition();
var service = composition.Root;
service.Dependency1.ShouldBeOfType<AbcDependency>();
service.Dependency1.ShouldBeOfType<XyzDependency>();
service.Dependency2.ShouldBeOfType<XyzDependency>();
service.Dependency3.ShouldBeOfType<XyzDependency>();
service.Dependency3.ShouldBeOfType<AbcDependency>();
service.Dependency4.ShouldBeOfType<AbcDependency>();
```

Expand Down Expand Up @@ -97,9 +97,9 @@ classDiagram
<<interface>>
}
Composition ..> Service : IService Root
Service *-- AbcDependency : IDependency
Service *-- "2 " XyzDependency : IDependency
Service *-- ConsumerᐸStringᐳ : ConsumerᐸStringᐳ
Service *-- AbcDependency : IDependency
ConsumerᐸStringᐳ *-- AbcDependency : IDependency
```

4 changes: 3 additions & 1 deletion readme/tag-on-injection-site.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

Tag on injection site is specified in a special format: `Tag.On("<namespace>.<type>.<member>[:argument]")`. The argument is specified only for the constructor and methods. For example, for namespace _MyNamespace_ and type _Class1_:
Sometimes it is necessary to determine which binding will be used to inject explicitly. To do this, use a special tag created by calling the `Tag.On()` method. Tag on injection site is specified in a special format: `Tag.On("<namespace>.<type>.<member>[:argument]")`. The argument is specified only for the constructor and methods. For example, for namespace _MyNamespace_ and type _Class1_:

- `Tag.On("MyNamespace.Class1.Class1:state1") - the tag corresponds to the constructor argument named _state_ of type _MyNamespace.Class1_
- `Tag.On("MyNamespace.Class1.DoSomething:myArg")` - the tag corresponds to the _myArg_ argument of the _DoSomething_ method
Expand Down Expand Up @@ -57,9 +57,11 @@ class Service(
DI.Setup(nameof(Composition))
.Bind(
Tag.On("MyNamespace.Service.Service:dependency1"),
// Tag on injection site for generic type
Tag.On("MyNamespace.Consumer`1.Consumer:myDep"))
.To<AbcDependency>()
.Bind(
// Combined tag
Tag.On(
"MyNamespace.Service.Service:dependency2",
"MyNamespace.Service:Dependency3"))
Expand Down
8 changes: 4 additions & 4 deletions readme/tag-unique.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,11 @@ classDiagram
+Service(IEnumerableᐸIDependencyᐸStringᐳᐳ dependencies)
}
class IEnumerableᐸIDependencyᐸStringᐳᐳ
AbcDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : Unique tag 0
AbcDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.AbcDependency<Pure.DI.TT>)
class AbcDependencyᐸStringᐳ {
+AbcDependency()
}
XyzDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : Unique tag 1
XyzDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.XyzDependency<Pure.DI.TT>)
class XyzDependencyᐸStringᐳ {
+XyzDependency()
}
Expand All @@ -219,7 +219,7 @@ classDiagram
}
Composition ..> ServiceᐸStringᐳ : IServiceᐸStringᐳ Root
ServiceᐸStringᐳ o-- "PerBlock" IEnumerableᐸIDependencyᐸStringᐳᐳ : IEnumerableᐸIDependencyᐸStringᐳᐳ
IEnumerableᐸIDependencyᐸStringᐳᐳ *-- AbcDependencyᐸStringᐳ : Unique tag 0 IDependencyᐸStringᐳ
IEnumerableᐸIDependencyᐸStringᐳᐳ *-- XyzDependencyᐸStringᐳ : Unique tag 1 IDependencyᐸStringᐳ
IEnumerableᐸIDependencyᐸStringᐳᐳ *-- AbcDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.AbcDependency<Pure.DI.TT>) IDependencyᐸStringᐳ
IEnumerableᐸIDependencyᐸStringᐳᐳ *-- XyzDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.XyzDependency<Pure.DI.TT>) IDependencyᐸStringᐳ
```

21 changes: 18 additions & 3 deletions src/Pure.DI.Core/Components/Api.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,8 @@ internal enum RootKinds
/// </summary>
internal class Tag
{
private static readonly Tag Shared = new Tag();

/// <summary>
/// Unique tag.
/// Begins the definition of the binding.
Expand All @@ -1134,7 +1136,7 @@ internal class Tag
/// </code>
/// </example>
/// </summary>
public static readonly Tag Unique = new Tag();
public static readonly Tag Unique = Shared;

/// <summary>
/// Tag of target implementation type.
Expand All @@ -1146,9 +1148,22 @@ internal class Tag
/// </code>
/// </example>
/// </summary>
public static readonly Tag Type = new Tag();
public static readonly Tag Type = Shared;

public static Tag On(params string[] names) => new Tag();
/// <summary>
/// This tag allows you to determine which binding will be used for explicit injection for a particular injection site.
/// <example>
/// <code>
/// DI.Setup("Composition")
/// .Bind(Tag.On("MyNamespace.Service.Service:dep"))
/// .To&lt;Dependency&gt;()
/// .Bind().To&lt;Service&gt;()
/// .Root&lt;IService&gt;("Root");
/// </code>
/// </example>
/// </summary>
/// <param name="injectionSites">Set of labels for inection each, must be specified in a special format: &lt;namespace&gt;.&lt;type&gt;.&lt;member&gt;[:argument]. The argument is specified only for the constructor and methods. The wildcards &apos;*&apos; and &apos;?&apos; are supported. All names are case-sensitive. The global namespace prefix &apos;global::&apos; must be omitted.</param>
public static Tag On(params string[] injectionSites) => Shared;
}

/// <summary>
Expand Down
19 changes: 7 additions & 12 deletions src/Pure.DI.Core/Core/ApiInvocationProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public void ProcessInvocation(
ImmutableArray<MdTagAttribute>.Empty,
ImmutableArray<MdOrdinalAttribute>.Empty,
ImmutableArray<MdAccumulator>.Empty,
Array.Empty<TagOn>(),
comments.FilterHints(invocationComments).ToList()));
break;

Expand Down Expand Up @@ -512,28 +513,22 @@ private static void VisitUsingDirectives(
}

// ReSharper disable once SuggestBaseTypeForParameter
private static void NotSupported(InvocationExpressionSyntax invocation)
{
private static void NotSupported(InvocationExpressionSyntax invocation) =>
throw new CompileErrorException($"The {invocation} is not supported.", invocation.GetLocation(), LogId.ErrorInvalidMetadata);
}

private static IReadOnlyList<T> BuildConstantArgs<T>(SemanticModel semanticModel, SeparatedSyntaxList<ArgumentSyntax> arguments)
{
return arguments
private static IReadOnlyList<T> BuildConstantArgs<T>(SemanticModel semanticModel, SeparatedSyntaxList<ArgumentSyntax> arguments) =>
arguments
.SelectMany(a => semanticModel.GetConstantValues<T>(a.Expression).Select(value => (value, a.Expression)))
.Select(a => a.value ?? throw new CompileErrorException(
$"{a.Expression} must be a non-null value of type {typeof(T)}.", a.Expression.GetLocation(), LogId.ErrorInvalidMetadata))
.ToList();
}

private static ImmutableArray<MdTag> BuildTags(SemanticModel semanticModel, IEnumerable<ArgumentSyntax> arguments)
{
return arguments
private static ImmutableArray<MdTag> BuildTags(SemanticModel semanticModel, IEnumerable<ArgumentSyntax> arguments) =>
arguments
.SelectMany(t => semanticModel.GetConstantValues<object>(t.Expression))
.Select((tag, i) => new MdTag(i, tag))
.ToImmutableArray();
}


private static CompositionName CreateCompositionName(string name, string ns, SyntaxNode source)
{
string className;
Expand Down
3 changes: 2 additions & 1 deletion src/Pure.DI.Core/Core/AsyncDisposableSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Pure.DI.Core;
// ReSharper disable ClassNeverInstantiated.Global
namespace Pure.DI.Core;

internal class AsyncDisposableSettings(ICache<Compilation, INamedTypeSymbol?> asyncDisposableTypes)
: IAsyncDisposableSettings
Expand Down
6 changes: 3 additions & 3 deletions src/Pure.DI.Core/Core/BindingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ 1 when
source,
setup,
semanticModel,
_contracts.Select(i => i with { Tags = i.Tags.Select(tag => BuildTag(tag, type, id, i.Tags.ToList())).ToImmutableArray()}).ToImmutableArray(),
_tags.Select(tag => BuildTag(tag, type, id, _tags)).ToImmutableArray(),
_contracts.Select(i => i with { Tags = i.Tags.Select(tag => BuildTag(tag, type, id)).ToImmutableArray()}).ToImmutableArray(),
_tags.Select(tag => BuildTag(tag, type, id)).ToImmutableArray(),
_lifetime ?? _defaultLifetime?.Lifetime,
_implementation,
_factory,
Expand All @@ -151,7 +151,7 @@ 1 when
}
}

private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy<int> id, IReadOnlyCollection<MdTag> tags)
private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy<int> id)
{
if (type is null || tag.Value is null)
{
Expand Down
6 changes: 3 additions & 3 deletions src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public LinesBuilder Build(CompositionCode composition)
lines.AppendLine($"class {composition.Source.Source.Name.ClassName} {{");
using (lines.Indent())
{
lines.AppendLine($"<<partial>>");
lines.AppendLine("<<partial>>");
foreach (var root in composition.Roots.OrderByDescending(i => i.IsPublic).ThenBy(i => i.Name))
{
lines.AppendLine($"{(root.IsPublic ? "+" : "-")}{FormatRoot(root)}");
Expand Down 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 TagOnObject ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}";
$"{(dependency.Injection.Tag is null or TagOn ? "" : FormatTag(dependency.Injection.Tag) + " ")}{FormatSymbol(dependency.Injection.Type, options)}";

private static string FormatTag(object? tag) =>
tag is null or TagOnObject
tag is null or TagOn
? ""
: 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})",
TagOnObject => defaultValue,
TagOn => defaultValue,
not null => tag.ToString(),
_ => defaultValue
};
Expand Down
6 changes: 3 additions & 3 deletions src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public CompositionCode Build(CompositionCode composition)
code.AppendLine();
}

AddDisposeAsyncPart(composition, code, false);
AddDisposeAsyncPart(code, false);
}
}

Expand Down Expand Up @@ -100,7 +100,7 @@ public CompositionCode Build(CompositionCode composition)
{
if (hasAsyncDisposable)
{
AddDisposeAsyncPart(composition, code, true);
AddDisposeAsyncPart(code, true);
}

if (hasDisposable)
Expand Down Expand Up @@ -136,7 +136,7 @@ public CompositionCode Build(CompositionCode composition)
return composition with { MembersCount = membersCounter };
}

private static void AddDisposeAsyncPart(CompositionCode composition, LinesBuilder code, bool makeAsyncCall)
private static void AddDisposeAsyncPart(LinesBuilder code, bool makeAsyncCall)
{
code.AppendLine($"case {Names.IAsyncDisposableInterfaceName} asyncDisposableInstance:");
using (code.Indent())
Expand Down
4 changes: 1 addition & 3 deletions src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ public void Build(BuildContext ctx, in DpFactory factory)
};
}

var indent = new Indent(0);
var text = syntaxNode.GetText();
foreach (var textLine in text.Lines)
{
Expand All @@ -78,7 +77,7 @@ public void Build(BuildContext ctx, in DpFactory factory)
{
// When an injection marker
var (injection, argument) = resolvers.Current;
using (code.Indent(indent.Value))
using (code.Indent())
{
ctx.StatementBuilder.Build(injectionsCtx with { Level = level, Variable = argument.Current, LockIsRequired = lockIsRequired }, argument);
code.AppendLine($"{(injection.DeclarationRequired ? $"{typeResolver.Resolve(argument.Current.Injection.Type)} " : "")}{injection.VariableName} = {ctx.BuildTools.OnInjected(ctx, argument.Current)};");
Expand All @@ -92,7 +91,6 @@ public void Build(BuildContext ctx, in DpFactory factory)
{
}

indent = len / Formatting.IndentSize;
code.AppendLine(line);
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/Pure.DI.Core/Core/Code/FactoryRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// ReSharper disable InvertIf
namespace Pure.DI.Core.Code;

using System.Runtime.CompilerServices;

internal sealed class FactoryRewriter(
IArguments arguments,
DpFactory factory,
Expand Down
1 change: 1 addition & 0 deletions src/Pure.DI.Core/Core/Code/NodeInfo.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ReSharper disable ClassNeverInstantiated.Global
namespace Pure.DI.Core.Code;

internal class NodeInfo(IAsyncDisposableSettings asyncDisposableSettings) : INodeInfo
Expand Down
2 changes: 1 addition & 1 deletion src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public CompositionCode Build(CompositionCode composition)
membersCount++;
code.AppendLine($"private const string {Names.OfTypeFieldName} = \"{Names.OfTypeMessage} \";");
membersCount++;
code.AppendLine("");
code.AppendLine();

code.AppendLine($"private class {Names.ResolverClassName}<T>: {Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, T>");
code.AppendLine("{");
Expand Down
6 changes: 3 additions & 3 deletions src/Pure.DI.Core/Core/DependencyGraphBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ public IEnumerable<DependencyNode> TryBuild(
var isGenericOk = false;
foreach (var item in map)
{
if (!Injection.EqualTags(injection.Tag, item.Key.Tag))
if (item.Key.Type is not INamedTypeSymbol { IsGenericType: true })
{
continue;
}

if (item.Key.Type is not INamedTypeSymbol { IsGenericType: true })
if (!Injection.EqualTags(injection.Tag, item.Key.Tag))
{
continue;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Pure.DI.Core/Core/ExceptionHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Pure.DI.Core;
// ReSharper disable ClassNeverInstantiated.Global
namespace Pure.DI.Core;

internal class ExceptionHandler(ILogger<ExceptionHandler> logger)
: IExceptionHandler
Expand Down
23 changes: 18 additions & 5 deletions src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,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?)) ?? CreateNameTag(field))));
GetAttribute(setup.TagAttributes, field, default(object?)) ?? CreateTagOn(setup.TagOn, field))));
}
}

Expand All @@ -157,7 +157,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?)) ?? CreateNameTag(property))));
GetAttribute(setup.TagAttributes, property, default(object?)) ?? CreateTagOn(setup.TagOn, property))));
}
}

Expand Down Expand Up @@ -251,16 +251,29 @@ private ImmutableArray<DpParameter> GetParameters(
parameter,
new Injection(
GetAttribute(setup.TypeAttributes, parameter, typeConstructor?.Construct(compilation, type) ?? type),
GetAttribute(setup.TagAttributes, parameter, default(object?)) ?? CreateNameTag(parameter))));
GetAttribute(setup.TagAttributes, parameter, default(object?)) ?? CreateTagOn(setup.TagOn, parameter))));
}

return dependenciesBuilder.MoveToImmutable();
}

private object CreateNameTag(ISymbol symbol)
private object? CreateTagOn(IReadOnlyCollection<TagOn> tagOn, ISymbol symbol)
{
var memberName = symbol.ContainingSymbol.ToDisplayString(NameTagQualifiedFormat);
return MdTag.CreateNameTagValue(wildcardMatcher, false, $"{memberName}:{symbol.Name}");
var injectionSite = $"{memberName}:{symbol.Name}";
var injectionSiteSpan = injectionSite.AsSpan();
foreach (var tagOnItem in tagOn)
{
foreach (var expression in tagOnItem.InjectionSites)
{
if (wildcardMatcher.Match(expression.AsSpan(), injectionSiteSpan))
{
return tagOnItem;
}
}
}

return default;
}

private T GetAttribute<TMdAttribute, T>(
Expand Down
Loading

0 comments on commit 09184b9

Please sign in to comment.