Skip to content

Commit f785a8b

Browse files
More precise definition of cyclic dependencies
1 parent 29373ed commit f785a8b

4 files changed

Lines changed: 83 additions & 9 deletions

File tree

src/Pure.DI.Core/Core/CyclicDependenciesValidator.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33
namespace Pure.DI.Core;
44

55
sealed class CyclicDependenciesValidator(
6-
IGraphWalker<HashSet<object>, ImmutableArray<Dependency>> graphWalker,
7-
[Tag(typeof(CyclicDependencyValidatorVisitor))] IGraphVisitor<HashSet<object>, ImmutableArray<Dependency>> visitor,
6+
IGraphWalker<CyclicDependenciesValidatorContext, ImmutableArray<Dependency>> graphWalker,
7+
[Tag(typeof(CyclicDependencyValidatorVisitor))] IGraphVisitor<CyclicDependenciesValidatorContext, ImmutableArray<Dependency>> visitor,
88
CancellationToken cancellationToken)
99
: IValidator<DependencyGraph>
1010
{
1111
public bool Validate(DependencyGraph dependencyGraph)
1212
{
1313
var graph = dependencyGraph.Graph;
1414
var errors = new HashSet<object>();
15+
var ctx = new CyclicDependenciesValidatorContext(dependencyGraph, errors);
1516
foreach (var root in dependencyGraph.Roots)
1617
{
1718
graphWalker.Walk(
18-
errors,
19+
ctx,
1920
graph,
2021
root.Node,
2122
visitor,

src/Pure.DI.Core/Core/CyclicDependencyValidatorVisitor.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
namespace Pure.DI.Core;
22

3+
using static Lifetime;
4+
35
sealed class CyclicDependencyValidatorVisitor(
46
ILogger logger,
57
INodeInfo nodeInfo,
6-
ILocationProvider locationProvider)
7-
: IGraphVisitor<HashSet<object>, ImmutableArray<Dependency>>
8+
ILocationProvider locationProvider,
9+
ITypeResolver typeResolver)
10+
: IGraphVisitor<CyclicDependenciesValidatorContext, ImmutableArray<Dependency>>
811
{
912
public ImmutableArray<Dependency> Create(
1013
IGraph<DependencyNode, Dependency> graph,
@@ -21,7 +24,7 @@ public ImmutableArray<Dependency> Append(
2124
: parent.Add(dependency);
2225

2326
public bool Visit(
24-
HashSet<object> errors,
27+
CyclicDependenciesValidatorContext ctx,
2528
IGraph<DependencyNode, Dependency> graph,
2629
in ImmutableArray<Dependency> path)
2730
{
@@ -35,7 +38,7 @@ public bool Visit(
3538
foreach (var dependency in path)
3639
{
3740
var source = dependency.Source;
38-
if (nodeInfo.IsLazy(source))
41+
if (source.Lifetime is Singleton or Scoped or PerResolve or PerBlock && nodeInfo.IsLazy(source))
3942
{
4043
nodes.Clear();
4144
}
@@ -45,12 +48,12 @@ public bool Visit(
4548
continue;
4649
}
4750

48-
if (!errors.Add(path))
51+
if (!ctx.Errors.Add(path))
4952
{
5053
continue;
5154
}
5255

53-
var pathStr = string.Join(" <-- ", path.Select(i => i.Source.Type));
56+
var pathStr = string.Join(" <-- ", path.Select(i => typeResolver.Resolve(ctx.DependencyGraph.Source, i.Source.Type).Name.Replace("global::", "")));
5457
var locations = (dependency.Injection.Locations.IsDefault ? ImmutableArray<Location>.Empty : dependency.Injection.Locations)
5558
.AddRange(path.SelectMany(i => i.Injection.Locations.IsDefault ? ImmutableArray<Location>.Empty : i.Injection.Locations))
5659
.Add(locationProvider.GetLocation(source.Binding.Source));
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace Pure.DI.Core.Models;
2+
3+
record CyclicDependenciesValidatorContext(
4+
DependencyGraph DependencyGraph,
5+
HashSet<object> Errors);

tests/Pure.DI.IntegrationTests/ErrorsAndWarningsTests.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,71 @@ public class Program { public static void Main() { } }
515515
result.Errors.Count(i => i.Id == LogId.ErrorCyclicDependency && i.Locations.FirstOrDefault().GetSource() == "dep").ShouldBe(1, result);
516516
}
517517

518+
[Fact]
519+
public async Task ShouldHandleCyclicDependenciesWhenFactory()
520+
{
521+
// Given
522+
523+
// When
524+
var result = await """
525+
namespace Sample
526+
{
527+
using System;
528+
using System.Collections.Generic;
529+
using Pure.DI;
530+
using Sample;
531+
532+
public interface IStartupStep
533+
{
534+
void Start();
535+
}
536+
537+
public class StartupStep : IStartupStep
538+
{
539+
public void Start()
540+
{
541+
}
542+
}
543+
544+
public class StepsConsumer : IStartupStep
545+
{
546+
private readonly IEnumerable<Func<IStartupStep>> _stepsFactory;
547+
548+
public StepsConsumer(IEnumerable<Func<IStartupStep>> stepsFactory)
549+
{
550+
_stepsFactory = stepsFactory;
551+
}
552+
553+
public void Start()
554+
{
555+
foreach (var step in _stepsFactory)
556+
{
557+
step();
558+
}
559+
}
560+
}
561+
562+
internal partial class Composition
563+
{
564+
private void Setup() =>
565+
DI.Setup("Composition")
566+
.Bind().To<StartupStep>()
567+
.Bind().To(ctx => {
568+
ctx.Inject(out Func<IStartupStep> factory);
569+
return factory;
570+
})
571+
.Root<StepsConsumer>("Consumer");
572+
}
573+
574+
public class Program { public static void Main() { } }
575+
}
576+
""".RunAsync();
577+
578+
// Then
579+
result.Success.ShouldBeFalse(result);
580+
result.Errors.Count(i => i.Id == LogId.ErrorCyclicDependency && i.Locations.FirstOrDefault().GetSource() == "Func<IStartupStep> factory").ShouldBe(1, result);
581+
}
582+
518583
[Fact]
519584
public async Task ShouldShowErrorWhenUnresolvedDependency()
520585
{

0 commit comments

Comments
 (0)