diff --git a/Pure.DI.sln.DotSettings b/Pure.DI.sln.DotSettings index d5059bcb0..72627da10 100644 --- a/Pure.DI.sln.DotSettings +++ b/Pure.DI.sln.DotSettings @@ -287,6 +287,7 @@ True True True + True True True True diff --git a/build/ReadmeTarget.cs b/build/ReadmeTarget.cs index 591f9703b..258d45360 100644 --- a/build/ReadmeTarget.cs +++ b/build/ReadmeTarget.cs @@ -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#"); diff --git a/readme/advanced-interception.md b/readme/advanced-interception.md index dc1cabf43..3965467fb 100644 --- a/readme/advanced-interception.md +++ b/readme/advanced-interception.md @@ -32,7 +32,7 @@ class Service(IDependency dependency) : IService internal partial class Composition: IInterceptor { - private readonly List _log = new(); + private readonly List _log = []; private static readonly IProxyBuilder ProxyBuilder = new DefaultProxyBuilder(); private readonly IInterceptor[] _interceptors = []; diff --git a/readme/tag-on-injection-site-with-wildcards.md b/readme/tag-on-injection-site-with-wildcards.md index deb52b28c..dfa638835 100644 --- a/readme/tag-on-injection-site-with-wildcards.md +++ b/readme/tag-on-injection-site-with-wildcards.md @@ -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() - .Bind(Tag.On("*Service:dependency2", "*Service:Dependency3")) + .Bind(Tag.On("*Service:dependency?")) .To() .Bind().To>() .Bind().To() @@ -59,9 +59,9 @@ DI.Setup(nameof(Composition)) var composition = new Composition(); var service = composition.Root; -service.Dependency1.ShouldBeOfType(); +service.Dependency1.ShouldBeOfType(); service.Dependency2.ShouldBeOfType(); -service.Dependency3.ShouldBeOfType(); +service.Dependency3.ShouldBeOfType(); service.Dependency4.ShouldBeOfType(); ``` @@ -97,9 +97,9 @@ classDiagram <> } Composition ..> Service : IService Root - Service *-- AbcDependency : IDependency Service *-- "2 " XyzDependency : IDependency Service *-- ConsumerᐸStringᐳ : ConsumerᐸStringᐳ + Service *-- AbcDependency : IDependency ConsumerᐸStringᐳ *-- AbcDependency : IDependency ``` diff --git a/readme/tag-on-injection-site.md b/readme/tag-on-injection-site.md index 09e6dddf5..0d90cdfea 100644 --- a/readme/tag-on-injection-site.md +++ b/readme/tag-on-injection-site.md @@ -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("..[: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("..[: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 @@ -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() .Bind( + // Combined tag Tag.On( "MyNamespace.Service.Service:dependency2", "MyNamespace.Service:Dependency3")) diff --git a/readme/tag-unique.md b/readme/tag-unique.md index e48d2a0b7..e5dc2fab6 100644 --- a/readme/tag-unique.md +++ b/readme/tag-unique.md @@ -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) class AbcDependencyᐸStringᐳ { +AbcDependency() } - XyzDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : Unique tag 1 + XyzDependencyᐸStringᐳ --|> IDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.XyzDependency) class XyzDependencyᐸStringᐳ { +XyzDependency() } @@ -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) IDependencyᐸStringᐳ + IEnumerableᐸIDependencyᐸStringᐳᐳ *-- XyzDependencyᐸStringᐳ : typeof(Pure.DI.UsageTests.Advanced.TagUniqueScenario.XyzDependency) IDependencyᐸStringᐳ ``` diff --git a/src/Pure.DI.Core/Components/Api.g.cs b/src/Pure.DI.Core/Components/Api.g.cs index b21817bba..b6e5448f3 100644 --- a/src/Pure.DI.Core/Components/Api.g.cs +++ b/src/Pure.DI.Core/Components/Api.g.cs @@ -1122,6 +1122,8 @@ internal enum RootKinds /// internal class Tag { + private static readonly Tag Shared = new Tag(); + /// /// Unique tag. /// Begins the definition of the binding. @@ -1134,7 +1136,7 @@ internal class Tag /// /// /// - public static readonly Tag Unique = new Tag(); + public static readonly Tag Unique = Shared; /// /// Tag of target implementation type. @@ -1146,9 +1148,22 @@ internal class Tag /// /// /// - public static readonly Tag Type = new Tag(); + public static readonly Tag Type = Shared; - public static Tag On(params string[] names) => new Tag(); + /// + /// This tag allows you to determine which binding will be used for explicit injection for a particular injection site. + /// + /// + /// DI.Setup("Composition") + /// .Bind(Tag.On("MyNamespace.Service.Service:dep")) + /// .To<Dependency>() + /// .Bind().To<Service>() + /// .Root<IService>("Root"); + /// + /// + /// + /// Set of labels for inection each, must be specified in a special format: <namespace>.<type>.<member>[:argument]. The argument is specified only for the constructor and methods. The wildcards '*' and '?' are supported. All names are case-sensitive. The global namespace prefix 'global::' must be omitted. + public static Tag On(params string[] injectionSites) => Shared; } /// diff --git a/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs b/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs index b20b2a516..65383f20e 100644 --- a/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs +++ b/src/Pure.DI.Core/Core/ApiInvocationProcessor.cs @@ -135,6 +135,7 @@ public void ProcessInvocation( ImmutableArray.Empty, ImmutableArray.Empty, ImmutableArray.Empty, + Array.Empty(), comments.FilterHints(invocationComments).ToList())); break; @@ -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 BuildConstantArgs(SemanticModel semanticModel, SeparatedSyntaxList arguments) - { - return arguments + private static IReadOnlyList BuildConstantArgs(SemanticModel semanticModel, SeparatedSyntaxList arguments) => + arguments .SelectMany(a => semanticModel.GetConstantValues(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 BuildTags(SemanticModel semanticModel, IEnumerable arguments) - { - return arguments + private static ImmutableArray BuildTags(SemanticModel semanticModel, IEnumerable arguments) => + arguments .SelectMany(t => semanticModel.GetConstantValues(t.Expression)) .Select((tag, i) => new MdTag(i, tag)) .ToImmutableArray(); - } - + private static CompositionName CreateCompositionName(string name, string ns, SyntaxNode source) { string className; diff --git a/src/Pure.DI.Core/Core/AsyncDisposableSettings.cs b/src/Pure.DI.Core/Core/AsyncDisposableSettings.cs index eb0570c48..55b0c0869 100644 --- a/src/Pure.DI.Core/Core/AsyncDisposableSettings.cs +++ b/src/Pure.DI.Core/Core/AsyncDisposableSettings.cs @@ -1,4 +1,5 @@ -namespace Pure.DI.Core; +// ReSharper disable ClassNeverInstantiated.Global +namespace Pure.DI.Core; internal class AsyncDisposableSettings(ICache asyncDisposableTypes) : IAsyncDisposableSettings diff --git a/src/Pure.DI.Core/Core/BindingBuilder.cs b/src/Pure.DI.Core/Core/BindingBuilder.cs index d44749e31..a18b8f297 100644 --- a/src/Pure.DI.Core/Core/BindingBuilder.cs +++ b/src/Pure.DI.Core/Core/BindingBuilder.cs @@ -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, @@ -151,7 +151,7 @@ 1 when } } - private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy id, IReadOnlyCollection tags) + private static MdTag BuildTag(MdTag tag, ITypeSymbol? type, Lazy id) { if (type is null || tag.Value is null) { diff --git a/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs b/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs index 1b7b40706..01a72ada7 100644 --- a/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/ClassDiagramBuilder.cs @@ -26,7 +26,7 @@ public LinesBuilder Build(CompositionCode composition) lines.AppendLine($"class {composition.Source.Source.Name.ClassName} {{"); using (lines.Indent()) { - lines.AppendLine($"<>"); + lines.AppendLine("<>"); foreach (var root in composition.Roots.OrderByDescending(i => i.IsPublic).ThenBy(i => i.Name)) { lines.AppendLine($"{(root.IsPublic ? "+" : "-")}{FormatRoot(root)}"); @@ -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) + " "; diff --git a/src/Pure.DI.Core/Core/Code/CodeExtensions.cs b/src/Pure.DI.Core/Core/Code/CodeExtensions.cs index 8543e3f1d..999fadbf0 100644 --- a/src/Pure.DI.Core/Core/Code/CodeExtensions.cs +++ b/src/Pure.DI.Core/Core/Code/CodeExtensions.cs @@ -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 }; diff --git a/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs b/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs index e15e3c963..fbb0abbd4 100644 --- a/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/DisposeMethodBuilder.cs @@ -50,7 +50,7 @@ public CompositionCode Build(CompositionCode composition) code.AppendLine(); } - AddDisposeAsyncPart(composition, code, false); + AddDisposeAsyncPart(code, false); } } @@ -100,7 +100,7 @@ public CompositionCode Build(CompositionCode composition) { if (hasAsyncDisposable) { - AddDisposeAsyncPart(composition, code, true); + AddDisposeAsyncPart(code, true); } if (hasDisposable) @@ -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()) diff --git a/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs b/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs index 5bd8d7122..edf3e8227 100644 --- a/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs @@ -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) { @@ -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)};"); @@ -92,7 +91,6 @@ public void Build(BuildContext ctx, in DpFactory factory) { } - indent = len / Formatting.IndentSize; code.AppendLine(line); } } diff --git a/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs b/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs index f1683262d..c97e021be 100644 --- a/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs +++ b/src/Pure.DI.Core/Core/Code/FactoryRewriter.cs @@ -2,8 +2,6 @@ // ReSharper disable InvertIf namespace Pure.DI.Core.Code; -using System.Runtime.CompilerServices; - internal sealed class FactoryRewriter( IArguments arguments, DpFactory factory, diff --git a/src/Pure.DI.Core/Core/Code/NodeInfo.cs b/src/Pure.DI.Core/Core/Code/NodeInfo.cs index 11316a76d..c74bc72b2 100644 --- a/src/Pure.DI.Core/Core/Code/NodeInfo.cs +++ b/src/Pure.DI.Core/Core/Code/NodeInfo.cs @@ -1,3 +1,4 @@ +// ReSharper disable ClassNeverInstantiated.Global namespace Pure.DI.Core.Code; internal class NodeInfo(IAsyncDisposableSettings asyncDisposableSettings) : INodeInfo diff --git a/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs b/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs index 074832e13..af253b4c6 100644 --- a/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/ResolverClassesBuilder.cs @@ -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}: {Names.ResolverInterfaceName}<{composition.Source.Source.Name.ClassName}, T>"); code.AppendLine("{"); diff --git a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs index ecfeba0cc..fd7667f84 100644 --- a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs +++ b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs @@ -111,12 +111,12 @@ public IEnumerable 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; } diff --git a/src/Pure.DI.Core/Core/ExceptionHandler.cs b/src/Pure.DI.Core/Core/ExceptionHandler.cs index 49b37b961..59eb9bfdf 100644 --- a/src/Pure.DI.Core/Core/ExceptionHandler.cs +++ b/src/Pure.DI.Core/Core/ExceptionHandler.cs @@ -1,4 +1,5 @@ -namespace Pure.DI.Core; +// ReSharper disable ClassNeverInstantiated.Global +namespace Pure.DI.Core; internal class ExceptionHandler(ILogger logger) : IExceptionHandler diff --git a/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs b/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs index 36e3cff2c..62e6dc3d5 100644 --- a/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs +++ b/src/Pure.DI.Core/Core/ImplementationDependencyNodeBuilder.cs @@ -138,7 +138,7 @@ public IEnumerable 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)))); } } @@ -157,7 +157,7 @@ public IEnumerable 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)))); } } @@ -251,16 +251,29 @@ private ImmutableArray 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, 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( diff --git a/src/Pure.DI.Core/Core/LinesBuilder.cs b/src/Pure.DI.Core/Core/LinesBuilder.cs index 203d9baa0..423f2717c 100644 --- a/src/Pure.DI.Core/Core/LinesBuilder.cs +++ b/src/Pure.DI.Core/Core/LinesBuilder.cs @@ -55,20 +55,19 @@ public void AppendLine(string line = "") } } - public IDisposable Indent(int value = 1) + public IDisposable Indent() { - - IncIndent(value); - return Disposables.Create(() => DecIndent(value)); + IncIndent(); + return Disposables.Create(DecIndent); } - public void IncIndent(int value = 1) + public void IncIndent() { FlushLines(); _indent = new Indent(_indent.Value + 1); } - public void DecIndent(int value = 1) + public void DecIndent() { FlushLines(); _indent = new Indent(_indent.Value - 1); diff --git a/src/Pure.DI.Core/Core/MetadataBuilder.cs b/src/Pure.DI.Core/Core/MetadataBuilder.cs index 35f299ae0..28e199fdf 100644 --- a/src/Pure.DI.Core/Core/MetadataBuilder.cs +++ b/src/Pure.DI.Core/Core/MetadataBuilder.cs @@ -148,6 +148,15 @@ private void MergeSetups(IEnumerable setups, out MdSetup mergedSetup, b cancellationToken.ThrowIfCancellationRequested(); } + var bindings = bindingsBuilder.ToImmutable(); + + var tagOn = bindings + .SelectMany(i => i.Contracts) + .SelectMany(binding => binding.Tags.Select(i => i.Value).OfType()) + .Where(i => i.InjectionSites.Length > 0) + .Distinct() + .ToList(); + mergedSetup = new MdSetup( lastSetup?.SemanticModel!, lastSetup?.Source!, @@ -155,13 +164,14 @@ private void MergeSetups(IEnumerable setups, out MdSetup mergedSetup, b usingDirectives.ToImmutableArray(), kind, settings, - bindingsBuilder.ToImmutable(), + bindings, rootsBuilder.ToImmutable(), resolveDependsOn ? ImmutableArray.Empty : dependsOnBuilder.ToImmutable(), typeAttributesBuilder.ToImmutable(), tagAttributesBuilder.ToImmutable(), ordinalAttributesBuilder.ToImmutable(), accumulators.ToImmutable(), + tagOn, comments); } } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Models/Injection.cs b/src/Pure.DI.Core/Core/Models/Injection.cs index 11b11afd9..a50b41277 100644 --- a/src/Pure.DI.Core/Core/Models/Injection.cs +++ b/src/Pure.DI.Core/Core/Models/Injection.cs @@ -7,7 +7,7 @@ internal readonly record struct Injection( ITypeSymbol Type, object? Tag) { - public override string ToString() => $"{Type}{(Tag != default && Tag is not TagOnObject ? $"({Tag.ValueToString()})" : "")}"; + public override string ToString() => $"{Type}{(Tag != default && Tag is not TagOn ? $"({Tag.ValueToString()})" : "")}"; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Injection other) => @@ -19,9 +19,10 @@ public override int GetHashCode() => [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool EqualTags(object? tag, object? otherTag) => - ReferenceEquals(tag, MdTag.ContextTag) - || ReferenceEquals(otherTag, MdTag.ContextTag) - || (tag is TagOnObject tagOn && tagOn.Equals(otherTag)) - || (otherTag is TagOnObject otherTagOn && otherTagOn.Equals(tag)) + SpecialEqualTags(tag, otherTag) || SpecialEqualTags(otherTag, tag) || Equals(tag, otherTag); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool SpecialEqualTags(object? tag, object? otherTag) => + ReferenceEquals(tag, MdTag.ContextTag) || (tag is TagOn tagOn && tagOn.Equals(otherTag)); } \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Models/MdSetup.cs b/src/Pure.DI.Core/Core/Models/MdSetup.cs index 4e1b14ec9..f0c0fdc63 100644 --- a/src/Pure.DI.Core/Core/Models/MdSetup.cs +++ b/src/Pure.DI.Core/Core/Models/MdSetup.cs @@ -17,5 +17,6 @@ internal record MdSetup( in ImmutableArray TagAttributes, in ImmutableArray OrdinalAttributes, in ImmutableArray Accumulators, + IReadOnlyCollection TagOn, IReadOnlyCollection Comments, ITypeConstructor? TypeConstructor = default); \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Models/MdTag.cs b/src/Pure.DI.Core/Core/Models/MdTag.cs index e11eca52b..47014e040 100644 --- a/src/Pure.DI.Core/Core/Models/MdTag.cs +++ b/src/Pure.DI.Core/Core/Models/MdTag.cs @@ -7,21 +7,24 @@ internal readonly record struct MdTag( { public static readonly object ContextTag = new ContextTagObject(); - public static MdTag CreateTypeTag(MdTag tag, ITypeSymbol? type) => - tag with { Value = type }; + public static MdTag CreateTypeTag(MdTag baseTag, ITypeSymbol? type) => + baseTag with { Value = type }; - public static MdTag CreateUniqueTag(MdTag tag, int id) => - tag with { Value = new UniqueTag(id) }; + public static MdTag CreateUniqueTag(MdTag baseTag, int id) => + baseTag with { Value = CreateUniqueTagValue(id) }; - public static object CreateNameTagValue(IWildcardMatcher wildcardMatcher, bool isMetadata, params string[] names) => - new TagOnObject(wildcardMatcher, isMetadata, names); + private static object CreateUniqueTagValue(int id) => + new UniqueTag(id); + + public static object CreateTagOnValue(params string[] sites) => + new TagOn(sites); public override string ToString() => Value switch { null => "null", string => $"\"{Value}\"", - TagOnObject => "", + TagOn => "", _ => Value.ToString() }; diff --git a/src/Pure.DI.Core/Core/Models/TagOn.cs b/src/Pure.DI.Core/Core/Models/TagOn.cs new file mode 100644 index 000000000..d0abcbe72 --- /dev/null +++ b/src/Pure.DI.Core/Core/Models/TagOn.cs @@ -0,0 +1,17 @@ +// ReSharper disable ConvertIfStatementToReturnStatement +namespace Pure.DI.Core.Models; + +internal record TagOn(params string[] InjectionSites) +{ + public override string ToString() => $"TagOn(\"{string.Join(", ", InjectionSites.Select(i => $"{i}"))}\")"; + + public virtual bool Equals(TagOn? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return StructuralComparisons.StructuralEqualityComparer.Equals(this, other.InjectionSites); + } + + public override int GetHashCode() => + StructuralComparisons.StructuralEqualityComparer.GetHashCode(InjectionSites); +} \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/Models/TagOnObject.cs b/src/Pure.DI.Core/Core/Models/TagOnObject.cs deleted file mode 100644 index b6bf08d89..000000000 --- a/src/Pure.DI.Core/Core/Models/TagOnObject.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Pure.DI.Core.Models; - -internal class TagOnObject(IWildcardMatcher wildcardMatcher, bool isMetadata, params string[] names) -{ - private bool IsMetadata => isMetadata; - private string[] Names => names; - - public override string ToString() => $"NameTag(\"{string.Join(", ", names.Select(i => $"{i}"))}\")"; - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return true; - if (ReferenceEquals(this, obj)) return true; - return obj switch - { - TagOnObject nameTag => Equals(nameTag), - _ => IsMetadata - }; - } - - private bool Equals(TagOnObject nameTag) - { - if (!IsMetadata && !nameTag.IsMetadata) - { - return true; - } - - return isMetadata - ? nameTag.Names.Length == 1 && Names.Any(name => wildcardMatcher.Match(name.AsSpan(), nameTag.Names[0].AsSpan())) - : Names.Length == 1 && nameTag.Names.Any(name => wildcardMatcher.Match(name.AsSpan(), Names[0].AsSpan())); - } - - public override int GetHashCode() => 0; -} \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/SemanticModelExtensions.cs b/src/Pure.DI.Core/Core/SemanticModelExtensions.cs index 680a577da..165f70eee 100644 --- a/src/Pure.DI.Core/Core/SemanticModelExtensions.cs +++ b/src/Pure.DI.Core/Core/SemanticModelExtensions.cs @@ -116,15 +116,17 @@ when memberAccessExpressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression switch (invocationExpressionSyntax.Expression) { case MemberAccessExpressionSyntax { Name.Identifier.Text: nameof(Tag.On), Expression: IdentifierNameSyntax { Identifier.Text: nameof(Tag) } }: - if (invocationExpressionSyntax.ArgumentList.Arguments is { } nameArgs) + if (invocationExpressionSyntax.ArgumentList.Arguments is var nameArgs) { var names = nameArgs .Select(nameArg => GetConstantValue(semanticModel, nameArg.Expression)) .Where(i => !string.IsNullOrWhiteSpace(i)).OfType() .ToArray(); - return (T)MdTag.CreateNameTagValue(new WildcardMatcher(), true, names); + return (T)MdTag.CreateTagOnValue(names); } + + // ReSharper disable once HeuristicUnreachableCode break; } diff --git a/src/Pure.DI.Core/Core/ValueStringBuilder.cs b/src/Pure.DI.Core/Core/ValueStringBuilder.cs deleted file mode 100644 index 1d9aab5a6..000000000 --- a/src/Pure.DI.Core/Core/ValueStringBuilder.cs +++ /dev/null @@ -1,311 +0,0 @@ -namespace Pure.DI.Core; - -using System.Buffers; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -internal ref struct ValueStringBuilder -{ - private char[]? _arrayToReturnToPool; - private Span _chars; - private int _pos; - - public ValueStringBuilder(Span initialBuffer) - { - _arrayToReturnToPool = null; - _chars = initialBuffer; - _pos = 0; - } - - public ValueStringBuilder(int initialCapacity) - { - _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity); - _chars = _arrayToReturnToPool; - _pos = 0; - } - - public int Length - { - get => _pos; - set - { - Debug.Assert(value >= 0); - Debug.Assert(value <= _chars.Length); - _pos = value; - } - } - - public int Capacity => _chars.Length; - - public void EnsureCapacity(int capacity) - { - // This is not expected to be called this with negative capacity - Debug.Assert(capacity >= 0); - - // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception. - if ((uint)capacity > (uint)_chars.Length) - Grow(capacity - _pos); - } - - /// - /// Get a pinnable reference to the builder. - /// Does not ensure there is a null char after - /// This overload is pattern matched in the C# 7.3+ compiler so you can omit - /// the explicit method call, and write eg "fixed (char* c = builder)" - /// - public ref char GetPinnableReference() - { - return ref MemoryMarshal.GetReference(_chars); - } - - /// - /// Get a pinnable reference to the builder. - /// - /// Ensures that the builder has a null char after - public ref char GetPinnableReference(bool terminate) - { - if (terminate) - { - EnsureCapacity(Length + 1); - _chars[Length] = '\0'; - } - return ref MemoryMarshal.GetReference(_chars); - } - - public ref char this[int index] - { - get - { - Debug.Assert(index < _pos); - return ref _chars[index]; - } - } - - public override string ToString() - { - string s = _chars.Slice(0, _pos).ToString(); - Dispose(); - return s; - } - - /// Returns the underlying storage of the builder. - public Span RawChars => _chars; - - /// - /// Returns a span around the contents of the builder. - /// - /// Ensures that the builder has a null char after - public ReadOnlySpan AsSpan(bool terminate) - { - if (terminate) - { - EnsureCapacity(Length + 1); - _chars[Length] = '\0'; - } - return _chars.Slice(0, _pos); - } - - public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos); - public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start); - public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length); - - public bool TryCopyTo(Span destination, out int charsWritten) - { - if (_chars.Slice(0, _pos).TryCopyTo(destination)) - { - charsWritten = _pos; - Dispose(); - return true; - } - else - { - charsWritten = 0; - Dispose(); - return false; - } - } - - public void Insert(int index, char value, int count) - { - if (_pos > _chars.Length - count) - { - Grow(count); - } - - int remaining = _pos - index; - _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); - _chars.Slice(index, count).Fill(value); - _pos += count; - } - - public void Insert(int index, string? s) - { - if (s == null) - { - return; - } - - int count = s.Length; - - if (_pos > (_chars.Length - count)) - { - Grow(count); - } - - int remaining = _pos - index; - _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); - s -#if !NET - .AsSpan() -#endif - .CopyTo(_chars.Slice(index)); - _pos += count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(char c) - { - int pos = _pos; - Span chars = _chars; - if ((uint)pos < (uint)chars.Length) - { - chars[pos] = c; - _pos = pos + 1; - } - else - { - GrowAndAppend(c); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(string? s) - { - if (s == null) - { - return; - } - - int pos = _pos; - if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc. - { - _chars[pos] = s[0]; - _pos = pos + 1; - } - else - { - AppendSlow(s); - } - } - - private void AppendSlow(string s) - { - int pos = _pos; - if (pos > _chars.Length - s.Length) - { - Grow(s.Length); - } - - s -#if !NET - .AsSpan() -#endif - .CopyTo(_chars.Slice(pos)); - _pos += s.Length; - } - - public void Append(char c, int count) - { - if (_pos > _chars.Length - count) - { - Grow(count); - } - - Span dst = _chars.Slice(_pos, count); - for (int i = 0; i < dst.Length; i++) - { - dst[i] = c; - } - _pos += count; - } - - public void Append(scoped ReadOnlySpan value) - { - int pos = _pos; - if (pos > _chars.Length - value.Length) - { - Grow(value.Length); - } - - value.CopyTo(_chars.Slice(_pos)); - _pos += value.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span AppendSpan(int length) - { - int origPos = _pos; - if (origPos > _chars.Length - length) - { - Grow(length); - } - - _pos = origPos + length; - return _chars.Slice(origPos, length); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void GrowAndAppend(char c) - { - Grow(1); - Append(c); - } - - /// - /// Resize the internal buffer either by doubling current buffer size or - /// by adding to - /// whichever is greater. - /// - /// - /// Number of chars requested beyond current position. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private void Grow(int additionalCapacityBeyondPos) - { - Debug.Assert(additionalCapacityBeyondPos > 0); - Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed."); - - const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength - - // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try - // to double the size if possible, bounding the doubling to not go beyond the max array length. - int newCapacity = (int)Math.Max( - (uint)(_pos + additionalCapacityBeyondPos), - Math.Min((uint)_chars.Length * 2, ArrayMaxLength)); - - // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative. - // This could also go negative if the actual required length wraps around. - char[] poolArray = ArrayPool.Shared.Rent(newCapacity); - - _chars.Slice(0, _pos).CopyTo(poolArray); - - char[]? toReturn = _arrayToReturnToPool; - _chars = _arrayToReturnToPool = poolArray; - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - char[]? toReturn = _arrayToReturnToPool; - this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again - if (toReturn != null) - { - ArrayPool.Shared.Return(toReturn); - } - } -} \ No newline at end of file diff --git a/src/Pure.DI.Core/Core/WildcardMatcher.cs b/src/Pure.DI.Core/Core/WildcardMatcher.cs index 71ae87f22..92bca9c48 100644 --- a/src/Pure.DI.Core/Core/WildcardMatcher.cs +++ b/src/Pure.DI.Core/Core/WildcardMatcher.cs @@ -225,81 +225,80 @@ public bool Match( // characters (i.e. the epsilon transition). goto MatchZero; } - else - { - // The remaining expression characters all match by consuming a character, - // so we need to force the expression and state forward. - currentState += 2; - switch (useExtendedWildcards) + // The remaining expression characters all match by consuming a character, + // so we need to force the expression and state forward. + currentState += 2; + + switch (useExtendedWildcards) + { + case true when expressionChar == '>': { - case true when expressionChar == '>': + // '>' (DOS_QM) is the most complicated. If the name is finished, + // we can match zero characters. If this name is a '.', we + // don't match, but look at the next expression. Otherwise + // we match a single character. + if (nameFinished || nameChar == '.') + goto NextExpressionCharacter; + + currentMatches[currentMatch++] = currentState; + goto ExpressionFinished; + } + + case true when expressionChar == '"': + { + // A '"' (DOS_DOT) can match either a period, or zero characters + // beyond the end of name. + if (nameFinished) { - // '>' (DOS_QM) is the most complicated. If the name is finished, - // we can match zero characters. If this name is a '.', we - // don't match, but look at the next expression. Otherwise - // we match a single character. - if (nameFinished || nameChar == '.') - goto NextExpressionCharacter; + goto NextExpressionCharacter; + } + if (nameChar == '.') + { currentMatches[currentMatch++] = currentState; - goto ExpressionFinished; } + goto ExpressionFinished; + } - case true when expressionChar == '"': + default: + { + if (expressionChar == '\\') { - // A '"' (DOS_DOT) can match either a period, or zero characters - // beyond the end of name. - if (nameFinished) - { - goto NextExpressionCharacter; - } - else if (nameChar == '.') + // Escape character, try to move the expression forward again and match literally. + if (++expressionOffset == expression.Length) { - currentMatches[currentMatch++] = currentState; + currentMatches[currentMatch++] = maxState; + goto ExpressionFinished; } - goto ExpressionFinished; + + currentState = expressionOffset * 2 + 2; + expressionChar = expression[expressionOffset]; } - - default: - { - if (expressionChar == '\\') - { - // Escape character, try to move the expression forward again and match literally. - if (++expressionOffset == expression.Length) - { - currentMatches[currentMatch++] = maxState; - goto ExpressionFinished; - } - - currentState = expressionOffset * 2 + 2; - expressionChar = expression[expressionOffset]; - } - // From this point on a name character is required to even - // continue, let alone make a match. - if (nameFinished) - { - goto ExpressionFinished; - } + // From this point on a name character is required to even + // continue, let alone make a match. + if (nameFinished) + { + goto ExpressionFinished; + } - if (expressionChar == '?') + if (expressionChar == '?') + { + // If this expression was a '?' we can match it once. + currentMatches[currentMatch++] = currentState; + } + else { + if (ignoreCase + ? char.ToUpperInvariant(expressionChar) == char.ToUpperInvariant(nameChar) + : expressionChar == nameChar) { - // If this expression was a '?' we can match it once. + // Matched a non-wildcard character currentMatches[currentMatch++] = currentState; } - else { - if (ignoreCase - ? char.ToUpperInvariant(expressionChar) == char.ToUpperInvariant(nameChar) - : expressionChar == nameChar) - { - // Matched a non-wildcard character - currentMatches[currentMatch++] = currentState; - } - } - - goto ExpressionFinished; } + + goto ExpressionFinished; } } diff --git a/tests/Pure.DI.IntegrationTests/TagsTests.cs b/tests/Pure.DI.IntegrationTests/TagsTests.cs index 7f6d632e9..8e70c0fbb 100644 --- a/tests/Pure.DI.IntegrationTests/TagsTests.cs +++ b/tests/Pure.DI.IntegrationTests/TagsTests.cs @@ -590,6 +590,55 @@ public static void Main() result.StdOut.ShouldBe(["Sample.Service"], result); } + [Fact] + public async Task ShouldSupportTagOnWhenDecorator() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + internal interface IService { } + + internal class BaseService: IService + { + } + + internal class Service: IService + { + public Service(IService baseService) { } + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind(Tag.On("*.Service:baseService")).To() + .Bind().To() + .Root("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Root); + } + } +} +""".RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.Service"], result); + } + [Fact] public async Task ShouldSupportTagOnWhenGenericInCtor() { @@ -945,5 +994,100 @@ public static void Main() result.Success.ShouldBeTrue(result); result.StdOut.ShouldBe(["Sample.Service", "Sample.Dep"], result); } + + [Fact] + public async Task ShouldSupportTagOnForComplex() + { + // Given + + // When + var result = await """ +namespace Sample +{ + using System; + using Pure.DI; + using Sample; + + interface IDependency { }; + + class AbcDependency : IDependency { }; + + class XyzDependency : IDependency { }; + + class Consumer + { + public Consumer(IDependency myDep) + { + Dependency = myDep; + } + + public IDependency Dependency { get; } + } + + interface IService + { + IDependency Dependency1 { get; } + + IDependency Dependency2 { get; } + + IDependency Dependency3 { get; } + + IDependency Dependency4 { get; } + } + + class Service : IService + { + private readonly Consumer _consumer; + + public Service(IDependency dependency1, + IDependency dependency2, + Consumer consumer) + { + _consumer = consumer; + Dependency1 = dependency1; + Dependency2 = dependency2; + } + + public IDependency Dependency1 { get; } + + public IDependency Dependency2 { get; } + + public required IDependency Dependency3 { init; get; } + + public IDependency Dependency4 => _consumer.Dependency; + } + + internal partial class Composition + { + void Setup() => + DI.Setup("Composition") + .Bind(Tag.On("*Service:Dependency3", "*Consumer:myDep")) + .To() + .Bind(Tag.On("*Service:dependency?")) + .To() + .Bind().To>() + .Bind().To() + .Root("Root"); + } + + public class Program + { + public static void Main() + { + var composition = new Composition(); + var service = composition.Root; + Console.WriteLine(service.Dependency1); + Console.WriteLine(service.Dependency2); + Console.WriteLine(service.Dependency3); + Console.WriteLine(service.Dependency4); + } + } +} +""".RunAsync(new Options(LanguageVersion.CSharp11)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Sample.XyzDependency", "Sample.XyzDependency", "Sample.AbcDependency", "Sample.AbcDependency"], result); + } #endif } \ No newline at end of file diff --git a/tests/Pure.DI.Tests/InjectionTests.cs b/tests/Pure.DI.Tests/InjectionTests.cs deleted file mode 100644 index a051f007b..000000000 --- a/tests/Pure.DI.Tests/InjectionTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -namespace Pure.DI.Tests; - -using System.Diagnostics.CodeAnalysis; -using Core; -using Core.Models; -using Microsoft.CodeAnalysis; -using Moq; - -public class InjectionTests -{ - private static readonly ITypeSymbol TypeSymbol1 = Mock.Of(); - private static readonly ITypeSymbol TypeSymbol2 = Mock.Of(); - private static readonly WildcardMatcherStub WildcardMatcher = new(); - - [Theory] - [MemberData(nameof(GetData))] - internal void ShouldImplementEqualityMethods(int id, Injection injection1, Injection injection2, bool expectedEqual) - { - // Given - // ReSharper disable once RedundantAssignment - id++; - - // When - var hashCode1 = injection1.GetHashCode(); - var hashCode2 = injection2.GetHashCode(); - var actualEqual = Equals(injection1, injection2); - - // Then - actualEqual.ShouldBe(expectedEqual); - if (expectedEqual) - { - hashCode1.ShouldBe(hashCode2); - } - } - - public static IEnumerable GetData() - { - yield return [0, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, null), true]; - yield return [1, new Injection(TypeSymbol1, 1), new Injection(TypeSymbol1, 1), true]; - yield return [2, new Injection(TypeSymbol1, "Abc"), new Injection(TypeSymbol1, "Abc"), true]; - yield return [3, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, MdTag.ContextTag), true]; - yield return [4, new Injection(TypeSymbol1, "Abc"), new Injection(TypeSymbol1, MdTag.ContextTag), true]; - yield return [5, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), true]; - yield return [6, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), true]; - yield return [7, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "*")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), true]; - yield return [8, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, MdTag.ContextTag), true]; - yield return [9, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Xyz", "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), true]; - yield return [10, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Xyz")), true]; - - yield return [50, new Injection(TypeSymbol1, null), new Injection(TypeSymbol2, 1), false]; - yield return [51, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, 1), false]; - yield return [52, new Injection(TypeSymbol1, null), new Injection(TypeSymbol1, "Abc"), false]; - yield return [53, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Xyz")), false]; - yield return [54, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol2, MdTag.ContextTag), false]; - yield return [55, new Injection(TypeSymbol1, "Xyz"), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), false]; - yield return [56, new Injection(TypeSymbol1, null), new Injection(TypeSymbol2, null), false]; - yield return [57, new Injection(TypeSymbol1, "*"), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Abc")), false]; - yield return [58, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, false, "Xyz")), false]; - yield return [59, new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Abc")), new Injection(TypeSymbol1, new TagOnObject(WildcardMatcher, true, "Xyz")), false]; - } - - private class WildcardMatcherStub: IWildcardMatcher - { - [SuppressMessage("Performance", "CA1822:Mark members as static")] - public bool Match( - ReadOnlySpan expression, - ReadOnlySpan name, - bool ignoreCase = false, - bool useExtendedWildcards = false) => - (expression.Length == 1 && expression[0] == '*') || expression.SequenceEqual(name); - } -} \ No newline at end of file diff --git a/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs index 7e22b05bd..a7142ae23 100644 --- a/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteScenario.cs @@ -2,7 +2,7 @@ $v=true $p=5 $d=Tag on injection site -$h=Tag on injection site is specified in a special format: `Tag.On("..[:argument]")`. The argument is specified only for the constructor and methods. For example, for namespace _MyNamespace_ and type _Class1_: +$h=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("..[:argument]")`. The argument is specified only for the constructor and methods. For example, for namespace _MyNamespace_ and type _Class1_: $h= $h=- `Tag.On("MyNamespace.Class1.Class1:state1") - the tag corresponds to the constructor argument named _state_ of type _MyNamespace.Class1_ $h=- `Tag.On("MyNamespace.Class1.DoSomething:myArg")` - the tag corresponds to the _myArg_ argument of the _DoSomething_ method @@ -85,7 +85,7 @@ public void Run() Tag.On("MyNamespace.Consumer`1.Consumer:myDep")) .To() .Bind( - // Combined tag on injection sites + // Combined tag Tag.On( "MyNamespace.Service.Service:dependency2", "MyNamespace.Service:Dependency3")) diff --git a/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs index 6dccde808..36a571ad5 100644 --- a/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs +++ b/tests/Pure.DI.UsageTests/Advanced/TagOnInjectionSiteWithWildcardsScenario.cs @@ -26,7 +26,7 @@ namespace MyNamespace2; interface IDependency; class AbcDependency : IDependency; - + class XyzDependency : IDependency; class Consumer(IDependency myDep) diff --git a/tests/Pure.DI.UsageTests/Attributes/CustomAttributesScenario.cs b/tests/Pure.DI.UsageTests/Attributes/CustomAttributesScenario.cs index a01bd4b79..70baf218e 100644 --- a/tests/Pure.DI.UsageTests/Attributes/CustomAttributesScenario.cs +++ b/tests/Pure.DI.UsageTests/Attributes/CustomAttributesScenario.cs @@ -15,6 +15,7 @@ // ReSharper disable ClassNeverInstantiated.Global // ReSharper disable ArrangeTypeModifiers // ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedTypeParameter #pragma warning disable CS9113 // Parameter is unread. namespace Pure.DI.UsageTests.Attributes.CustomAttributesScenario; diff --git a/tests/Pure.DI.UsageTests/Interception/AdvancedInterceptionScenario.cs b/tests/Pure.DI.UsageTests/Interception/AdvancedInterceptionScenario.cs index 6f4aede63..45d323844 100644 --- a/tests/Pure.DI.UsageTests/Interception/AdvancedInterceptionScenario.cs +++ b/tests/Pure.DI.UsageTests/Interception/AdvancedInterceptionScenario.cs @@ -45,7 +45,7 @@ public void ServiceCall() { } internal partial class Composition: IInterceptor { - private readonly List _log = new(); + private readonly List _log = []; private static readonly IProxyBuilder ProxyBuilder = new DefaultProxyBuilder(); private readonly IInterceptor[] _interceptors = [];