Skip to content

Commit 3d14049

Browse files
#88 Support for contextual/scoped dependency overrides
1 parent a33d8f9 commit 3d14049

38 files changed

Lines changed: 895 additions & 302 deletions

samples/Clock/ViewModels/ClockViewModel.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// ReSharper disable once ClassNeverInstantiated.Global
66
class ClockViewModel : ViewModel, IClockViewModel, IDisposable, IObserver<Tick>
77
{
8-
98
public ClockViewModel(
109
ILog<ClockViewModel> log,
1110
IClock clock,

samples/Clock/ViewModels/ViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
abstract class ViewModel
66
: INotifyPropertyChanged
77
{
8-
98
public required IDispatcher Dispatcher { private get; init; }
109

1110
public required ILog<ViewModel> Log { private get; init; }
11+
1212
public event PropertyChangedEventHandler? PropertyChanged;
1313

1414
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)

samples/WinFormsAppNetCore/FormMain.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
partial class FormMain : Form
77
{
8-
98
internal FormMain(IClockViewModel clockViewModel)
109
{
1110
_clockViewModel = clockViewModel;

src/Pure.DI.Core/Components/Api.g.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2994,8 +2994,6 @@ internal interface IContext
29942994
/// var service = new Service();
29952995
/// // Initialize an instance with all necessary dependencies
29962996
/// ctx.BuildUp(service);
2997-
///
2998-
///
29992997
/// return service;
30002998
/// })
30012999
/// </code>
@@ -3006,7 +3004,7 @@ internal interface IContext
30063004
/// <seealso cref="IBinding.To{T}(System.Func{Pure.DI.IContext,T})"/>
30073005
void BuildUp<T>(T value);
30083006

3009-
void Override<T>(T value, object tag = null);
3007+
void Override<T>(T value, params object[] tags);
30103008
}
30113009

30123010
/// <summary>

src/Pure.DI.Core/Core/ApiInvocationProcessor.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ sealed class ApiInvocationProcessor(
2323
: IApiInvocationProcessor
2424
{
2525
private static readonly char[] TypeNamePartsSeparators = ['.'];
26+
private static object NullTag = new object();
2627

2728
public void ProcessInvocation(
2829
IMetadataVisitor metadataVisitor,
@@ -817,12 +818,12 @@ private MdOverride CreateOverride(
817818
ref bool hasContextTag)
818819
{
819820
var invocation = @override.Expression;
820-
if (invocation.ArgumentList.Arguments is not { Count: 1 or 2 })
821+
if (invocation.ArgumentList.Arguments.Count == 0)
821822
{
822823
return default;
823824
}
824825

825-
var args = arguments.GetArgs(invocation.ArgumentList, "value", "tag");
826+
var args = arguments.GetArgs(invocation.ArgumentList, "value", "tags");
826827
if (args[0] is not {} valueArg)
827828
{
828829
return default;
@@ -834,19 +835,22 @@ private MdOverride CreateOverride(
834835
return default;
835836
}
836837

837-
var tag = args[1]?.Expression;
838-
var hasCtx = HasContextTag(tag, contextParameter);
839-
hasContextTag |= hasCtx;
840-
var tagValue = hasCtx ? MdTag.ContextTag : tag is null ? null : semantic.GetConstantValue<object>(semanticModel, tag);
841-
var resolverTag = new MdTag(0, tagValue);
838+
var tagArguments = invocation.ArgumentList.Arguments.Skip(1).ToList();
839+
var hasCtx = tagArguments.Aggregate(false, (current, tag) => current | HasContextTag(tag.Expression, contextParameter));
840+
var tags = BuildTags(semanticModel, tagArguments)
841+
.AsEnumerable()
842+
.Select(tag => tag with { Value = tag.Value ?? (hasCtx ? MdTag.ContextTag : null) })
843+
.DefaultIfEmpty(new MdTag(0, hasCtx ? MdTag.ContextTag : null))
844+
.ToImmutableArray();
845+
842846
var valueExpression = (ExpressionSyntax)localVariableRenamingRewriter.Rewrite(semanticModel, false, true, valueArg.Expression);
843847
return new MdOverride(
844848
semanticModel,
845849
invocation,
846-
overrideIdProvider.GetId(argType, tagValue),
850+
overrideIdProvider.GetId(argType, tags.Select(tag => tag.Value ?? NullTag).ToImmutableHashSet()),
847851
@override.Position,
848852
argType,
849-
resolverTag,
853+
tags,
850854
valueExpression);
851855
}
852856

src/Pure.DI.Core/Core/ArgDependencyNodeBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public IEnumerable<DependencyNode> Build(DependencyNodeBuildContext ctx)
1313
continue;
1414
}
1515

16-
yield return new DependencyNode(0, binding, Arg: new DpArg(arg, binding));
16+
yield return new DependencyNode(0, binding, ctx.TypeConstructor, Arg: new DpArg(arg, binding));
1717
}
1818
}
1919
}

src/Pure.DI.Core/Core/Code/BlockCodeBuilder.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,7 @@ public void Build(BuildContext ctx, in Block block)
7272
var content = new LinesBuilder();
7373
foreach (var statement in block.Statements)
7474
{
75-
var curVar = statement.Current;
76-
if (ctx.IsFactory || curVar.Injection.Kind != InjectionKind.FactoryInjection || curVar.Node.Lifetime != Lifetime.Transient)
77-
{
78-
ctx.StatementBuilder.Build(ctx with { Variable = curVar, Code = content, IsFactory = false }, statement);
79-
}
75+
ctx.StatementBuilder.Build(ctx with { Variable = statement.Current, Code = content }, statement);
8076
}
8177

8278
if (content.Count == 0)
@@ -141,7 +137,8 @@ public void Build(BuildContext ctx, in Block block)
141137
{
142138
if (block.Parent is not null
143139
&& info is { PerBlockRefCount: > 1 }
144-
&& code.Count > 11)
140+
&& code.Count > 11
141+
&& block.Statements.All(i => !i.Current.Info.HasLocalMethod))
145142
{
146143
var localMethodCode = ctx.LocalFunctionsCode;
147144
if (compilations.GetLanguageVersion(compilation) >= LanguageVersion.CSharp9)

src/Pure.DI.Core/Core/Code/CompositionBuilder.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ public CompositionCode Build(DependencyGraph graph)
4141
foreach (var perResolveVar in map.GetPerResolves())
4242
{
4343
ctx.Code.AppendLine($"var {perResolveVar.VariableName} = default({typeResolver.Resolve(graph.Source, perResolveVar.InstanceType)});");
44-
if (perResolveVar.Info.RefCount > 0 && perResolveVar.InstanceType.IsValueType)
44+
if (perResolveVar.Info.RefCount > 0
45+
&& perResolveVar.InstanceType.IsValueType
46+
&& perResolveVar.Node.Construct is not { Source.Kind: MdConstructKind.Override })
4547
{
4648
ctx.Code.AppendLine($"var {perResolveVar.VariableName}Created = false;");
4749
}

src/Pure.DI.Core/Core/Code/CompositionClassBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ public CompositionCode Build(CompositionCode composition)
2222
if (composition.Compilation.Options.NullableContextOptions != NullableContextOptions.Disable)
2323
{
2424
code.AppendLine("#nullable enable annotations");
25-
code.AppendLine("#pragma warning disable CS0219");
2625
}
2726

27+
code.AppendLine("#pragma warning disable CS0219");
2828
code.AppendLine();
2929
composition = usingDeclarations.Build(composition);
3030

src/Pure.DI.Core/Core/Code/FactoryCodeBuilder.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public void Build(BuildContext ctx, in DpFactory factory)
101101
var argType = typeConstructor.ConstructReversed(typeArg);
102102
if (binding.TypeConstructor is {} bindingTypeConstructor)
103103
{
104-
argType = bindingTypeConstructor.Construct(setup, binding.SemanticModel.Compilation, argType);
104+
argType = bindingTypeConstructor.Construct(setup, argType);
105105
}
106106

107107
var typeName = symbolNames.GetGlobalName(argType);
@@ -224,14 +224,16 @@ public void Build(BuildContext ctx, in DpFactory factory)
224224
.GetEnumerator();
225225

226226
var initializationArgsEnum = initializationArgs.Select(i => i.Current).GetEnumerator();
227+
ctx = ctx with { LockIsRequired = lockIsRequired, Level = level };
227228
var injectionsCtx = ctx;
228229
if (variable.IsLazy && variable.Node.Accumulators.Count > 0)
229230
{
230231
injectionsCtx = injectionsCtx with
231232
{
232-
Accumulators = injectionsCtx.Accumulators.AddRange(
233-
variable.Node.Accumulators
234-
.Select(accumulator => accumulator with { IsDeclared = false }))
233+
Accumulators = injectionsCtx.Accumulators
234+
.AsEnumerable()
235+
.Select(i => i with { IsDeclared = i.IsRoot && i.IsDeclared })
236+
.ToImmutableArray()
235237
};
236238
}
237239

@@ -272,7 +274,12 @@ public void Build(BuildContext ctx, in DpFactory factory)
272274
code.AppendLine($"{variableNameProvider.GetOverrideVariableName(@override.Source)} = {@override.Source.ValueExpression};");
273275
}
274276

275-
ctx.StatementBuilder.Build(injectionsCtx with { Level = level, Variable = argument.Current, LockIsRequired = lockIsRequired, IsFactory = true }, argument);
277+
foreach (var argStatement in GetArgsStatements(argument))
278+
{
279+
ctx.StatementBuilder.Build(ctx with { Variable = argStatement.Current }, argStatement);
280+
}
281+
282+
ctx.StatementBuilder.Build(injectionsCtx with { Variable = argument.Current }, argument);
276283
code.AppendLine($"{(injection.DeclarationRequired ? $"{typeResolver.Resolve(ctx.DependencyGraph.Source, argument.Current.Injection.Type)} " : "")}{injection.VariableName} = {ctx.BuildTools.OnInjected(ctx, argument.Current)};");
277284
}
278285

@@ -287,6 +294,12 @@ public void Build(BuildContext ctx, in DpFactory factory)
287294
code.AppendLine($"{variableNameProvider.GetOverrideVariableName(@override.Source)} = {@override.Source.ValueExpression};");
288295
}
289296

297+
foreach (var argument in initializationArgs)
298+
foreach (var argStatement in GetArgsStatements(argument))
299+
{
300+
ctx.StatementBuilder.Build(ctx with { Variable = argStatement.Current }, argStatement);
301+
}
302+
290303
var initializersWalker = initializersWalkerFactory().Ininitialize(initialization.VariableName, initializationArgsEnum);
291304
initializersWalker.VisitInitializer(injectionsCtx, initializer);
292305
continue;
@@ -313,4 +326,27 @@ public void Build(BuildContext ctx, in DpFactory factory)
313326

314327
ctx.Code.AppendLines(ctx.BuildTools.OnCreated(ctx, variable));
315328
}
329+
330+
private static IEnumerable<IStatement> GetArgsStatements(IStatement statement)
331+
{
332+
var result = new Stack<IStatement>();
333+
var statements = new Stack<IStatement>();
334+
statements.Push(statement);
335+
while (statements.TryPop(out var nextStatement))
336+
{
337+
var variable = nextStatement.Current;
338+
if (variable.HasCycledReference && variable.Node.Lifetime == Lifetime.PerBlock)
339+
{
340+
continue;
341+
}
342+
343+
result.Push(nextStatement);
344+
foreach (var arg in variable.Args)
345+
{
346+
statements.Push(arg);
347+
}
348+
}
349+
350+
return result;
351+
}
316352
}

0 commit comments

Comments
 (0)