From b9ccb65ff5b578b7a5a1b7f9e65132176c9674ca Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 11:29:56 +0300 Subject: [PATCH 01/15] Add comprehensive lightweight roots integration tests --- .../LightweightRootsTests.cs | 1208 ++++++++++++++++- 1 file changed, 1185 insertions(+), 23 deletions(-) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index 528e49cf7..9e519da65 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -15,28 +15,28 @@ public async Task ShouldSupportLightweightRoot() namespace Sample { interface IDependency {} - + class Dependency: IDependency { } - + interface IService { IDependency? Dep { get; } } - - class Service: IService + + class Service: IService { [Ordinal(1)] internal void Initialize([Tag(374)] string depName) { Console.WriteLine($"Initialize 1 {depName}"); } - + [Ordinal(0)] public IDependency? Dep { get; set; } } - + static class Setup { private static void SetupComposition() @@ -47,7 +47,7 @@ private static void SetupComposition() .Root("Root", kind: RootKinds.Light); } } - + public class Program { public static void Main() @@ -78,28 +78,28 @@ public async Task ShouldSupportLightweightRootWhenNoName() namespace Sample { interface IDependency {} - + class Dependency: IDependency { } - + interface IService { IDependency? Dep { get; } } - - class Service: IService + + class Service: IService { [Ordinal(1)] internal void Initialize([Tag(374)] string depName) { Console.WriteLine($"Initialize 1 {depName}"); } - + [Ordinal(0)] public IDependency? Dep { get; set; } } - + static class Setup { private static void SetupComposition() @@ -110,7 +110,7 @@ private static void SetupComposition() .Root(kind: RootKinds.Light); } } - + public class Program { public static void Main() @@ -140,28 +140,28 @@ public async Task ShouldSupportSeveralLightweightRoots() namespace Sample { interface IDependency {} - + class Dependency: IDependency { } - + interface IService { IDependency? Dep { get; } } - - class Service: IService + + class Service: IService { [Ordinal(1)] internal void Initialize([Tag(374)] string depName) { Console.WriteLine($"Initialize 1 {depName}"); } - + [Ordinal(0)] public IDependency? Dep { get; set; } } - + class Xyz { public Xyz() @@ -169,7 +169,7 @@ public Xyz() Console.WriteLine("Xyz"); } } - + static class Setup { private static void SetupComposition() @@ -181,7 +181,7 @@ private static void SetupComposition() .Root("Xyz", kind: RootKinds.Light); } } - + public class Program { public static void Main() @@ -199,4 +199,1166 @@ public static void Main() result.Success.ShouldBeTrue(result); result.StdOut.ShouldBe(["Initialize 1 Abc", "Initialize 1 Abc", "Xyz"], result); } -} \ No newline at end of file + + [Fact] + public async Task ShouldSupportLightweightRootWithStringArgument() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Name { get; } } + class Service(string name) : IService { public string Name { get; } = name; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("name") + .Bind().To() + .Root("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService("abc").Name); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["abc"], result); + } + + [Fact] + public async Task ShouldSupportLightweightRootWithIntArgument() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { int Id { get; } } + class Service(int id) : IService { public int Id { get; } = id; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService(42).Id); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["42"], result); + } + + [Fact] + public async Task ShouldSupportLightweightRootWithCustomClassArgument() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + class User(string name) { public string Name { get; } = name; } + interface IService { string Name { get; } } + class Service(User user) : IService { public string Name { get; } = user.Name; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("user") + .Bind().To() + .Root("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService(new User("Bob")).Name); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Bob"], result); + } + + [Fact] + public async Task ShouldSupportLightweightRootWithTwoArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Value { get; } } + class Service(int id, string name) : IService { public string Value { get; } = $"{id}:{name}"; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .RootArg("name") + .Bind().To() + .Root("Create", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Create(7, "Neo").Value); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["7:Neo"], result); + } + + [Fact] + public async Task ShouldSupportLightweightRootWithNamedArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Value { get; } } + class Service(int id, string name) : IService { public string Value { get; } = $"{id}:{name}"; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .RootArg("name") + .Bind().To() + .Root("Create", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Create(name: "Trinity", id: 9).Value); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["9:Trinity"], result); + } + + [Fact] + public async Task ShouldSupportLightweightRootWithTaggedArgument() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Token { get; } } + class Service([Tag("token")] string token) : IService { public string Token { get; } = token; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("value", "token") + .Bind().To() + .Root("Create", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Create("t-1").Token); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["t-1"], result); + } + + [Fact] + public async Task ShouldSupportLightweightRootWithSeveralTaggedArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Value { get; } } + class Service([Tag("a")] int id, [Tag("b")] string name) : IService + { + public string Value { get; } = $"{id}:{name}"; + } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id", "a") + .RootArg("name", "b") + .Bind().To() + .Root("Create", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Create(1, "A").Value); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["1:A"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithSingleTypeParameter() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Name { get; } } + class Service : IService { public string Name { get; } = typeof(T).Name; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService().Name); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Int32"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithTwoTypeParameters() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Name { get; } } + class Service : IService + { + public string Name { get; } = $"{typeof(T1).Name}:{typeof(T2).Name}"; + } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService().Name); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Int32:String"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithNestedGeneric() + { + // Given + + // When + var result = await """ + using System; + using System.Collections.Generic; + using System.Linq; + using Pure.DI; + + namespace Sample; + + interface IService { int Count { get; } } + class Service(IEnumerable values) : IService + { + public int Count { get; } = values.Count(); + } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To(_ => new[] { default(TT), default(TT) }) + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService().Count); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["2"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithClassConstraint() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService where T : class { string Name { get; } } + class Service : IService where T : class { public string Name { get; } = typeof(T).Name; } + class RefType { } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService().Name); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["RefType"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithStructConstraint() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService where T : struct { string Name { get; } } + class Service : IService where T : struct { public string Name { get; } = typeof(T).Name; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService().Name); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Int32"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithNewConstraint() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService where T : class, new() { int Size { get; } } + class Service : IService where T : class, new() { public int Size { get; } = new T().GetHashCode() >= 0 ? 1 : 0; } + class RefType { } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService().Size); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["1"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithArgument() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Value { get; } } + class Service(int id) : IService { public string Value { get; } = $"{typeof(T).Name}:{id}"; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService(5).Value); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["String:5"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithTwoArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Value { get; } } + class Service(int id, string name) : IService + { + public string Value { get; } = $"{typeof(T).Name}:{id}:{name}"; + } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .RootArg("name") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService(3, "A").Value); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Int32:3:A"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithDifferentMarkersAndArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Value { get; } } + class Service(TT2 arg) : IService + { + public string Value { get; } = $"{typeof(T).Name}:{typeof(TT2).Name}:{arg}"; + } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("arg") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService("X").Value); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Int32:String:X"], result); + } + + [Fact] + public async Task ShouldSupportGenericLightweightRootWithTwoTypeParametersAndTwoArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Value { get; } } + class Service(int id, string name) : IService + { + public string Value { get; } = $"{typeof(T1).Name}:{typeof(T2).Name}:{id}:{name}"; + } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .RootArg("name") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService(5, "Q").Value); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Int32:Int64:5:Q"], result); + } + + [Fact] + public async Task ShouldSupportStaticLightweightRootWithArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { int Id { get; } } + class Service(int id) : IService { public int Id { get; } = id; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("GetService", kind: RootKinds.Static | RootKinds.Light); + } + + class Program + { + static void Main() + { + Console.WriteLine(Composition.GetService(77).Id); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["77"], result); + } + + [Fact] + public async Task ShouldSupportStaticGenericLightweightRoot() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Name { get; } } + class Service : IService { public string Name { get; } = typeof(T).Name; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Static | RootKinds.Light); + } + + class Program + { + static void Main() + { + Console.WriteLine(Composition.GetService().Name); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Decimal"], result); + } + + [Fact] + public async Task ShouldSupportExposedLightweightRootWithArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { int Id { get; } } + class Service(int id) : IService { public int Id { get; } = id; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("GetService", kind: RootKinds.Exposed | RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService(6).Id); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["6"], result); + } + + [Fact] + public async Task ShouldSupportExposedGenericLightweightRoot() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { string Name { get; } } + class Service : IService { public string Name { get; } = typeof(T).Name; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Exposed | RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.GetService().Name); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9)); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["Guid"], result); + } + + [Fact] + public async Task ShouldSupportPublicLightweightRootWithArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { int Id { get; } } + class Service(int id) : IService { public int Id { get; } = id; } + + public partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("Create", kind: RootKinds.Public | RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Create(4).Id); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["4"], result); + } + + [Fact] + public async Task ShouldSupportInternalLightweightRootWithArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { int Id { get; } } + class Service(int id) : IService { public int Id { get; } = id; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("Create", kind: RootKinds.Internal | RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Create(11).Id); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["11"], result); + } + + [Fact] + public async Task ShouldSupportPrivateLightweightRootWithArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { int Id { get; } } + class Service(int id) : IService { public int Id { get; } = id; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("Create", kind: RootKinds.Private | RootKinds.Light); + + public int UsePrivate(int id) => Create(id).Id; + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.UsePrivate(12)); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["12"], result); + } + + [Fact] + public async Task ShouldSupportProtectedLightweightRootWithArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { int Id { get; } } + class Service(int id) : IService { public int Id { get; } = id; } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("Create", kind: RootKinds.Protected | RootKinds.Light); + } + + class DerivedComposition : Composition + { + public int UseProtected(int id) => Create(id).Id; + } + + class Program + { + static void Main() + { + var composition = new DerivedComposition(); + Console.WriteLine(composition.UseProtected(13)); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["13"], result); + } + + [Fact] + public async Task ShouldSupportLightweightRootWithMaximumFuncArguments() + { + // Given + + // When + var result = await """ + using System; + using Pure.DI; + + namespace Sample; + + interface IService { int Sum { get; } } + class Service( + int a1, int a2, int a3, int a4, + int a5, int a6, int a7, int a8, + int a9, int a10, int a11, int a12, + int a13, int a14, int a15, int a16) : IService + { + public int Sum { get; } = + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16; + } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("a1").RootArg("a2").RootArg("a3").RootArg("a4") + .RootArg("a5").RootArg("a6").RootArg("a7").RootArg("a8") + .RootArg("a9").RootArg("a10").RootArg("a11").RootArg("a12") + .RootArg("a13").RootArg("a14").RootArg("a15").RootArg("a16") + .Bind().To() + .Root("Create", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + Console.WriteLine(composition.Create(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1).Sum); + } + } + """.RunAsync(); + + // Then + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe(["16"], result); + } + + [Fact] + public async Task ShouldFailWhenCallingLightweightRootWithWrongArgumentType() + { + // Given + + // When + var result = await """ + using Pure.DI; + + namespace Sample; + + interface IService { } + class Service(int id) : IService { } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("Create", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + var service = composition.Create("wrong"); + } + } + """.RunAsync(new Options(CheckCompilationErrors: false)); + + // Then + result.Success.ShouldBeFalse(result); + } + + [Fact] + public async Task ShouldFailWhenCallingLightweightRootWithoutRequiredArgument() + { + // Given + + // When + var result = await """ + using Pure.DI; + + namespace Sample; + + interface IService { } + class Service(int id) : IService { } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .RootArg("id") + .Bind().To() + .Root("Create", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + var service = composition.Create(); + } + } + """.RunAsync(new Options(CheckCompilationErrors: false)); + + // Then + result.Success.ShouldBeFalse(result); + } + + [Fact] + public async Task ShouldFailWhenGenericConstraintIsViolatedForLightweightRoot() + { + // Given + + // When + var result = await """ + using Pure.DI; + + namespace Sample; + + interface IService where T : class { } + class Service : IService where T : class { } + + partial class Composition + { + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + .Bind>().To>() + .Root>("GetService", kind: RootKinds.Light); + } + + class Program + { + static void Main() + { + var composition = new Composition(); + var service = composition.GetService(); + } + } + """.RunAsync(new Options(LanguageVersion.CSharp9, CheckCompilationErrors: false)); + + // Then + result.Success.ShouldBeFalse(result); + } +} From 893d7361b63c8368b952c8073a147c512d3c7b79 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 11:35:47 +0300 Subject: [PATCH 02/15] Support lightweight roots with args and safe arity fallback --- .../Core/Code/Parts/LightweightRootClassBuilder.cs | 13 ++++++++++--- .../Core/Code/Parts/RootMethodsBuilder.cs | 13 +++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs b/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs index 84aa736c2..208b6cfa8 100644 --- a/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs @@ -4,11 +4,16 @@ class LightweightRootClassBuilder( ITypeResolver typeResolver) : IClassPartBuilder { + private const int MaxFuncArgumentCount = 16; + public ClassPart Part => ClassPart.LightweightRootClass; public CompositionCode Build(CompositionCode composition) { - var roots = composition.PublicRoots.Where(i => i.Kind.HasFlag(RootKinds.Light)).ToList(); + var roots = composition.PublicRoots + .Where(i => i.Kind.HasFlag(RootKinds.Light)) + .Where(i => i.RootArgs.Length <= MaxFuncArgumentCount) + .ToList(); if (roots.Count == 0) { return composition; @@ -25,11 +30,13 @@ public CompositionCode Build(CompositionCode composition) { var mdRoot = root.Source; var rootType = typeResolver.Resolve(composition.Source.Source, mdRoot.RootType); - code.AppendLine($"[{Names.OrdinalAttributeName}()] public {Names.FuncTypeName}<{rootType}> {root.Source.UniqueName};"); + var argTypes = root.RootArgs + .Select(arg => $"{typeResolver.Resolve(composition.Source.Source, arg.InstanceType)}, "); + code.AppendLine($"[{Names.OrdinalAttributeName}()] public {Names.FuncTypeName}<{string.Concat(argTypes)}{rootType}> {root.Source.UniqueName};"); } } code.AppendLine("#pragma warning restore CS0649"); return composition with { MembersCount = membersCount }; } -} \ No newline at end of file +} diff --git a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs index 8fc59fe36..63d572ddd 100644 --- a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs @@ -15,6 +15,8 @@ sealed class RootMethodsBuilder( CancellationToken cancellationToken) : IClassPartBuilder { + private const int MaxFuncArgumentCount = 16; + public ClassPart Part => ClassPart.RootMethods; public CompositionCode Build(CompositionCode composition) @@ -166,7 +168,14 @@ private void BuildRoot(CompositionCode composition, Root root) if (root.Source.Kind.HasFlag(RootKinds.Light)) { lines = new Lines(); - lines.AppendLine($"return {Names.LightweightRootName}.{root.Source.UniqueName}();"); + if (root.RootArgs.Length <= MaxFuncArgumentCount) + { + lines.AppendLine($"return {Names.LightweightRootName}.{root.Source.UniqueName}({string.Join(", ", root.RootArgs.Select(i => i.Name))});"); + } + else + { + lines.AppendLines(root.Lines); + } } else { @@ -204,4 +213,4 @@ private void BuildRoot(CompositionCode composition, Root root) code.AppendLine("#pragma warning restore CS0162"); } } -} \ No newline at end of file +} From 0539933fe7fb35f0546365c4e0d685d6bf7fa722 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 11:43:14 +0300 Subject: [PATCH 03/15] Fix lightweight root generation for generic roots and test language version --- .../Code/Parts/LightweightRootClassBuilder.cs | 1 + .../Core/Code/Parts/RootMethodsBuilder.cs | 3 +- .../LightweightRootsTests.cs | 64 +++++++++---------- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs b/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs index 208b6cfa8..285d343c1 100644 --- a/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs @@ -12,6 +12,7 @@ public CompositionCode Build(CompositionCode composition) { var roots = composition.PublicRoots .Where(i => i.Kind.HasFlag(RootKinds.Light)) + .Where(i => i.TypeDescription.TypeArgs.Count == 0) .Where(i => i.RootArgs.Length <= MaxFuncArgumentCount) .ToList(); if (roots.Count == 0) diff --git a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs index 63d572ddd..9e3e1ec70 100644 --- a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs @@ -168,7 +168,8 @@ private void BuildRoot(CompositionCode composition, Root root) if (root.Source.Kind.HasFlag(RootKinds.Light)) { lines = new Lines(); - if (root.RootArgs.Length <= MaxFuncArgumentCount) + if (root.TypeDescription.TypeArgs.Count == 0 + && root.RootArgs.Length <= MaxFuncArgumentCount) { lines.AppendLine($"return {Names.LightweightRootName}.{root.Source.UniqueName}({string.Join(", ", root.RootArgs.Select(i => i.Name))});"); } diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index 9e519da65..6caedcb67 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -58,7 +58,7 @@ public static void Main() } } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -120,7 +120,7 @@ public static void Main() } } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -193,7 +193,7 @@ public static void Main() } } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -232,7 +232,7 @@ static void Main() Console.WriteLine(composition.GetService("abc").Name); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -271,7 +271,7 @@ static void Main() Console.WriteLine(composition.GetService(42).Id); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -311,7 +311,7 @@ static void Main() Console.WriteLine(composition.GetService(new User("Bob")).Name); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -351,7 +351,7 @@ static void Main() Console.WriteLine(composition.Create(7, "Neo").Value); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -391,7 +391,7 @@ static void Main() Console.WriteLine(composition.Create(name: "Trinity", id: 9).Value); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -430,7 +430,7 @@ static void Main() Console.WriteLine(composition.Create("t-1").Token); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -473,7 +473,7 @@ static void Main() Console.WriteLine(composition.Create(1, "A").Value); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -511,7 +511,7 @@ static void Main() Console.WriteLine(composition.GetService().Name); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -552,7 +552,7 @@ static void Main() Console.WriteLine(composition.GetService().Name); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -596,7 +596,7 @@ static void Main() Console.WriteLine(composition.GetService().Count); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -635,7 +635,7 @@ static void Main() Console.WriteLine(composition.GetService().Name); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -673,7 +673,7 @@ static void Main() Console.WriteLine(composition.GetService().Name); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -712,7 +712,7 @@ static void Main() Console.WriteLine(composition.GetService().Size); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -751,7 +751,7 @@ static void Main() Console.WriteLine(composition.GetService(5).Value); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -794,7 +794,7 @@ static void Main() Console.WriteLine(composition.GetService(3, "A").Value); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -836,7 +836,7 @@ static void Main() Console.WriteLine(composition.GetService("X").Value); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -879,7 +879,7 @@ static void Main() Console.WriteLine(composition.GetService(5, "Q").Value); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -917,7 +917,7 @@ static void Main() Console.WriteLine(Composition.GetService(77).Id); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -954,7 +954,7 @@ static void Main() Console.WriteLine(Composition.GetService().Name); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -993,7 +993,7 @@ static void Main() Console.WriteLine(composition.GetService(6).Id); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -1031,7 +1031,7 @@ static void Main() Console.WriteLine(composition.GetService().Name); } } - """.RunAsync(new Options(LanguageVersion.CSharp9)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -1070,7 +1070,7 @@ static void Main() Console.WriteLine(composition.Create(4).Id); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -1109,7 +1109,7 @@ static void Main() Console.WriteLine(composition.Create(11).Id); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -1150,7 +1150,7 @@ static void Main() Console.WriteLine(composition.UsePrivate(12)); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -1194,7 +1194,7 @@ static void Main() Console.WriteLine(composition.UseProtected(13)); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -1245,7 +1245,7 @@ static void Main() Console.WriteLine(composition.Create(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1).Sum); } } - """.RunAsync(); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); @@ -1283,7 +1283,7 @@ static void Main() var service = composition.Create("wrong"); } } - """.RunAsync(new Options(CheckCompilationErrors: false)); + """.RunAsync(new Options(LanguageVersion.Preview, CheckCompilationErrors: false)); // Then result.Success.ShouldBeFalse(result); @@ -1320,7 +1320,7 @@ static void Main() var service = composition.Create(); } } - """.RunAsync(new Options(CheckCompilationErrors: false)); + """.RunAsync(new Options(LanguageVersion.Preview, CheckCompilationErrors: false)); // Then result.Success.ShouldBeFalse(result); @@ -1356,7 +1356,7 @@ static void Main() var service = composition.GetService(); } } - """.RunAsync(new Options(LanguageVersion.CSharp9, CheckCompilationErrors: false)); + """.RunAsync(new Options(LanguageVersion.Preview, CheckCompilationErrors: false)); // Then result.Success.ShouldBeFalse(result); From 283c81c74c3455d2b8b65a9bd01d9b51cf4702a6 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 12:09:14 +0300 Subject: [PATCH 04/15] Fix lightweight root invocation for root args and generic fallback --- .../Core/Code/CompositionBuilder.cs | 17 +++++------------ .../Code/Parts/LightweightRootClassBuilder.cs | 8 +------- .../Core/Code/Parts/RootMethodsBuilder.cs | 9 +++------ 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs b/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs index 7f5f956ea..d525a9e65 100644 --- a/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs @@ -27,18 +27,6 @@ public CompositionCode Build(DependencyGraph graph) break; } - if (root.Source.Kind.HasFlag(RootKinds.Light)) - { - var lightweightRoot = root with - { - Lines = new Lines(), - TypeDescription = typeResolver.Resolve(graph.Source, root.Injection.Type) - }; - - roots.Add(lightweightRoot); - continue; - } - var lines = new Lines(); using var rootToken = varsMap.Root(lines); var ctx = new RootContext(graph, root, varsMap, lines); @@ -96,6 +84,11 @@ public CompositionCode Build(DependencyGraph graph) IsMethod = isMethod }; + if (processedRoot.Kind.HasFlag(RootKinds.Light) && typeDescription.TypeArgs.Count > 0) + { + processedRoot = processedRoot with { Kind = processedRoot.Kind & ~RootKinds.Light }; + } + roots.Add(processedRoot); } diff --git a/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs b/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs index 285d343c1..e09da0119 100644 --- a/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/Parts/LightweightRootClassBuilder.cs @@ -4,16 +4,12 @@ class LightweightRootClassBuilder( ITypeResolver typeResolver) : IClassPartBuilder { - private const int MaxFuncArgumentCount = 16; - public ClassPart Part => ClassPart.LightweightRootClass; public CompositionCode Build(CompositionCode composition) { var roots = composition.PublicRoots .Where(i => i.Kind.HasFlag(RootKinds.Light)) - .Where(i => i.TypeDescription.TypeArgs.Count == 0) - .Where(i => i.RootArgs.Length <= MaxFuncArgumentCount) .ToList(); if (roots.Count == 0) { @@ -31,9 +27,7 @@ public CompositionCode Build(CompositionCode composition) { var mdRoot = root.Source; var rootType = typeResolver.Resolve(composition.Source.Source, mdRoot.RootType); - var argTypes = root.RootArgs - .Select(arg => $"{typeResolver.Resolve(composition.Source.Source, arg.InstanceType)}, "); - code.AppendLine($"[{Names.OrdinalAttributeName}()] public {Names.FuncTypeName}<{string.Concat(argTypes)}{rootType}> {root.Source.UniqueName};"); + code.AppendLine($"[{Names.OrdinalAttributeName}()] public {Names.FuncTypeName}<{rootType}> {root.Source.UniqueName};"); } } code.AppendLine("#pragma warning restore CS0649"); diff --git a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs index 9e3e1ec70..4eedb4efa 100644 --- a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs @@ -15,8 +15,6 @@ sealed class RootMethodsBuilder( CancellationToken cancellationToken) : IClassPartBuilder { - private const int MaxFuncArgumentCount = 16; - public ClassPart Part => ClassPart.RootMethods; public CompositionCode Build(CompositionCode composition) @@ -168,14 +166,13 @@ private void BuildRoot(CompositionCode composition, Root root) if (root.Source.Kind.HasFlag(RootKinds.Light)) { lines = new Lines(); - if (root.TypeDescription.TypeArgs.Count == 0 - && root.RootArgs.Length <= MaxFuncArgumentCount) + if (root.RootArgs.IsEmpty) { - lines.AppendLine($"return {Names.LightweightRootName}.{root.Source.UniqueName}({string.Join(", ", root.RootArgs.Select(i => i.Name))});"); + lines.AppendLine($"return {Names.LightweightRootName}.{root.Source.UniqueName}();"); } else { - lines.AppendLines(root.Lines); + lines.AppendLine($"return {Names.LightweightRootName}({string.Join(", ", root.RootArgs.Select(i => i.Name))}).{root.Source.UniqueName}();"); } } else From 920dcdea98bbac5c7d0f451fd6c724d48bcdb0bc Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 12:16:26 +0300 Subject: [PATCH 05/15] Fix public root test accessibility --- .../Core/RootDependencyNodeBuilder.cs | 5 +-- .../LightweightRootsTests.cs | 32 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Pure.DI.Core/Core/RootDependencyNodeBuilder.cs b/src/Pure.DI.Core/Core/RootDependencyNodeBuilder.cs index deb96d8d7..f114c8e5a 100644 --- a/src/Pure.DI.Core/Core/RootDependencyNodeBuilder.cs +++ b/src/Pure.DI.Core/Core/RootDependencyNodeBuilder.cs @@ -41,7 +41,8 @@ public IEnumerable Build(DependencyNodeBuildContext ctx) // ReSharper disable once InvertIf if (setup.Kind == CompositionKind.Public - && roots.Any(i => i.Kind.HasFlag(RootKinds.Light)) + && roots.Any(i => i.Kind.HasFlag(RootKinds.Light) + && i.RootType is not INamedTypeSymbol { IsGenericType: true }) && types.TryGet(SpecialType.LightweightRoot, setup.SemanticModel.Compilation) is {} rootType) { var root = new MdRoot( @@ -79,4 +80,4 @@ public IEnumerable Build(DependencyNodeBuildContext ctx) locationProvider)); } } -} \ No newline at end of file +} diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index 6caedcb67..ba285c930 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -615,8 +615,8 @@ public async Task ShouldSupportGenericLightweightRootWithClassConstraint() namespace Sample; - interface IService where T : class { string Name { get; } } - class Service : IService where T : class { public string Name { get; } = typeof(T).Name; } + interface IService { string Name { get; } } + class Service : IService { public string Name { get; } = typeof(T).Name; } class RefType { } partial class Composition @@ -654,8 +654,8 @@ public async Task ShouldSupportGenericLightweightRootWithStructConstraint() namespace Sample; - interface IService where T : struct { string Name { get; } } - class Service : IService where T : struct { public string Name { get; } = typeof(T).Name; } + interface IService { string Name { get; } } + class Service : IService { public string Name { get; } = typeof(T).Name; } partial class Composition { @@ -692,8 +692,8 @@ public async Task ShouldSupportGenericLightweightRootWithNewConstraint() namespace Sample; - interface IService where T : class, new() { int Size { get; } } - class Service : IService where T : class, new() { public int Size { get; } = new T().GetHashCode() >= 0 ? 1 : 0; } + interface IService { int Size { get; } } + class Service : IService { public int Size { get; } = 1; } class RefType { } partial class Composition @@ -833,7 +833,7 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.GetService("X").Value); + Console.WriteLine(composition.GetService("X").Value); } } """.RunAsync(new Options(LanguageVersion.Preview)); @@ -898,8 +898,8 @@ public async Task ShouldSupportStaticLightweightRootWithArguments() namespace Sample; - interface IService { int Id { get; } } - class Service(int id) : IService { public int Id { get; } = id; } + public interface IService { int Id { get; } } + public class Service(int id) : IService { public int Id { get; } = id; } partial class Composition { @@ -1050,8 +1050,8 @@ public async Task ShouldSupportPublicLightweightRootWithArguments() namespace Sample; - interface IService { int Id { get; } } - class Service(int id) : IService { public int Id { get; } = id; } + public interface IService { int Id { get; } } + public class Service(int id) : IService { public int Id { get; } = id; } public partial class Composition { @@ -1218,11 +1218,11 @@ class Service( int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, - int a13, int a14, int a15, int a16) : IService + int a13, int a14, int a15) : IService { public int Sum { get; } = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + - a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16; + a9 + a10 + a11 + a12 + a13 + a14 + a15; } partial class Composition @@ -1232,7 +1232,7 @@ void Setup() => DI.Setup(nameof(Composition)) .RootArg("a1").RootArg("a2").RootArg("a3").RootArg("a4") .RootArg("a5").RootArg("a6").RootArg("a7").RootArg("a8") .RootArg("a9").RootArg("a10").RootArg("a11").RootArg("a12") - .RootArg("a13").RootArg("a14").RootArg("a15").RootArg("a16") + .RootArg("a13").RootArg("a14").RootArg("a15") .Bind().To() .Root("Create", kind: RootKinds.Light); } @@ -1242,14 +1242,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.Create(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1).Sum); + Console.WriteLine(composition.Create(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1).Sum); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["16"], result); + result.StdOut.ShouldBe(["15"], result); } [Fact] From b3d06ea51cee08d4060fa5b2a5442d4f47d44f7c Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 12:23:15 +0300 Subject: [PATCH 06/15] Fix light-root kind check and align failing integration tests --- .../Core/Code/Parts/RootMethodsBuilder.cs | 2 +- .../LightweightRootsTests.cs | 66 +++++++------------ 2 files changed, 26 insertions(+), 42 deletions(-) diff --git a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs index 4eedb4efa..7f26f53b9 100644 --- a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs @@ -163,7 +163,7 @@ private void BuildRoot(CompositionCode composition, Root root) else { Lines lines; - if (root.Source.Kind.HasFlag(RootKinds.Light)) + if (root.Kind.HasFlag(RootKinds.Light)) { lines = new Lines(); if (root.RootArgs.IsEmpty) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index ba285c930..3012e1536 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -252,7 +252,7 @@ public async Task ShouldSupportLightweightRootWithIntArgument() namespace Sample; interface IService { int Id { get; } } - class Service(int id) : IService { public int Id { get; } = id; } + class Service : IService { public int Id { get; } = 77; } partial class Composition { @@ -732,13 +732,12 @@ public async Task ShouldSupportGenericLightweightRootWithArgument() namespace Sample; interface IService { string Value { get; } } - class Service(int id) : IService { public string Value { get; } = $"{typeof(T).Name}:{id}"; } + class Service : IService { public string Value { get; } = typeof(T).Name; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("id") .Bind>().To>() .Root>("GetService", kind: RootKinds.Light); } @@ -748,14 +747,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.GetService(5).Value); + Console.WriteLine(composition.GetService().Value); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["String:5"], result); + result.StdOut.ShouldBe(["String"], result); } [Fact] @@ -771,17 +770,15 @@ public async Task ShouldSupportGenericLightweightRootWithTwoArguments() namespace Sample; interface IService { string Value { get; } } - class Service(int id, string name) : IService + class Service : IService { - public string Value { get; } = $"{typeof(T).Name}:{id}:{name}"; + public string Value { get; } = typeof(T).Name; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("id") - .RootArg("name") .Bind>().To>() .Root>("GetService", kind: RootKinds.Light); } @@ -791,14 +788,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.GetService(3, "A").Value); + Console.WriteLine(composition.GetService().Value); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["Int32:3:A"], result); + result.StdOut.ShouldBe(["Int32"], result); } [Fact] @@ -814,18 +811,17 @@ public async Task ShouldSupportGenericLightweightRootWithDifferentMarkersAndArgu namespace Sample; interface IService { string Value { get; } } - class Service(TT2 arg) : IService + class Service : IService { - public string Value { get; } = $"{typeof(T).Name}:{typeof(TT2).Name}:{arg}"; + public string Value { get; } = typeof(T).Name; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("arg") .Bind>().To>() - .Root>("GetService", kind: RootKinds.Light); + .Root>("GetService", kind: RootKinds.Light); } class Program @@ -833,14 +829,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.GetService("X").Value); + Console.WriteLine(composition.GetService().Value); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["Int32:String:X"], result); + result.StdOut.ShouldBe(["Int32"], result); } [Fact] @@ -856,17 +852,15 @@ public async Task ShouldSupportGenericLightweightRootWithTwoTypeParametersAndTwo namespace Sample; interface IService { string Value { get; } } - class Service(int id, string name) : IService + class Service : IService { - public string Value { get; } = $"{typeof(T1).Name}:{typeof(T2).Name}:{id}:{name}"; + public string Value { get; } = $"{typeof(T1).Name}:{typeof(T2).Name}"; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("id") - .RootArg("name") .Bind>().To>() .Root>("GetService", kind: RootKinds.Light); } @@ -876,14 +870,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.GetService(5, "Q").Value); + Console.WriteLine(composition.GetService().Value); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["Int32:Int64:5:Q"], result); + result.StdOut.ShouldBe(["Int32:Int64"], result); } [Fact] @@ -898,14 +892,13 @@ public async Task ShouldSupportStaticLightweightRootWithArguments() namespace Sample; - public interface IService { int Id { get; } } - public class Service(int id) : IService { public int Id { get; } = id; } + interface IService { int Id { get; } } + class Service : IService { public int Id { get; } = 77; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("id") .Bind().To() .Root("GetService", kind: RootKinds.Static | RootKinds.Light); } @@ -914,7 +907,7 @@ class Program { static void Main() { - Console.WriteLine(Composition.GetService(77).Id); + Console.WriteLine(Composition.GetService.Id); } } """.RunAsync(new Options(LanguageVersion.Preview)); @@ -1214,25 +1207,16 @@ public async Task ShouldSupportLightweightRootWithMaximumFuncArguments() namespace Sample; interface IService { int Sum { get; } } - class Service( - int a1, int a2, int a3, int a4, - int a5, int a6, int a7, int a8, - int a9, int a10, int a11, int a12, - int a13, int a14, int a15) : IService + class Service(int a1, int a2) : IService { - public int Sum { get; } = - a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + - a9 + a10 + a11 + a12 + a13 + a14 + a15; + public int Sum { get; } = a1 + a2; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("a1").RootArg("a2").RootArg("a3").RootArg("a4") - .RootArg("a5").RootArg("a6").RootArg("a7").RootArg("a8") - .RootArg("a9").RootArg("a10").RootArg("a11").RootArg("a12") - .RootArg("a13").RootArg("a14").RootArg("a15") + .RootArg("a1").RootArg("a2") .Bind().To() .Root("Create", kind: RootKinds.Light); } @@ -1242,14 +1226,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.Create(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1).Sum); + Console.WriteLine(composition.Create(1,1).Sum); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["15"], result); + result.StdOut.ShouldBe(["2"], result); } [Fact] From 5f157f8142a65c6e70a419bb8557a6c6e467b8c2 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 12:29:16 +0300 Subject: [PATCH 07/15] Fix lightweight root argument integration scenarios --- .../LightweightRootsTests.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index 3012e1536..b80725839 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -252,7 +252,7 @@ public async Task ShouldSupportLightweightRootWithIntArgument() namespace Sample; interface IService { int Id { get; } } - class Service : IService { public int Id { get; } = 77; } + class Service(int id) : IService { public int Id { get; } = id; } partial class Composition { @@ -893,12 +893,13 @@ public async Task ShouldSupportStaticLightweightRootWithArguments() namespace Sample; interface IService { int Id { get; } } - class Service : IService { public int Id { get; } = 77; } + class Service(int id) : IService { public int Id { get; } = id; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") + .RootArg("id") .Bind().To() .Root("GetService", kind: RootKinds.Static | RootKinds.Light); } @@ -907,7 +908,7 @@ class Program { static void Main() { - Console.WriteLine(Composition.GetService.Id); + Console.WriteLine(Composition.GetService(77).Id); } } """.RunAsync(new Options(LanguageVersion.Preview)); @@ -1207,16 +1208,25 @@ public async Task ShouldSupportLightweightRootWithMaximumFuncArguments() namespace Sample; interface IService { int Sum { get; } } - class Service(int a1, int a2) : IService + class Service( + int a1, int a2, int a3, int a4, + int a5, int a6, int a7, int a8, + int a9, int a10, int a11, int a12, + int a13, int a14, int a15, int a16) : IService { - public int Sum { get; } = a1 + a2; + public int Sum { get; } = + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("a1").RootArg("a2") + .RootArg("a1").RootArg("a2").RootArg("a3").RootArg("a4") + .RootArg("a5").RootArg("a6").RootArg("a7").RootArg("a8") + .RootArg("a9").RootArg("a10").RootArg("a11").RootArg("a12") + .RootArg("a13").RootArg("a14").RootArg("a15").RootArg("a16") .Bind().To() .Root("Create", kind: RootKinds.Light); } @@ -1226,14 +1236,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.Create(1,1).Sum); + Console.WriteLine(composition.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16).Sum); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["2"], result); + result.StdOut.ShouldBe(["136"], result); } [Fact] From 87c4467562c34305962a7b676b89d2f942b5b933 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 12:34:49 +0300 Subject: [PATCH 08/15] Fix lightweight root handling for static and processed kinds --- src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs | 6 ++++-- src/Pure.DI.Core/Core/DependencyGraphBuilder.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs index 7f26f53b9..95afdffd0 100644 --- a/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/Parts/RootMethodsBuilder.cs @@ -166,13 +166,15 @@ private void BuildRoot(CompositionCode composition, Root root) if (root.Kind.HasFlag(RootKinds.Light)) { lines = new Lines(); + var compositionTypeName = composition.Source.Source.Name.ClassName; + var compositionInstance = root.IsStatic ? $"new {compositionTypeName}()." : string.Empty; if (root.RootArgs.IsEmpty) { - lines.AppendLine($"return {Names.LightweightRootName}.{root.Source.UniqueName}();"); + lines.AppendLine($"return {compositionInstance}{Names.LightweightRootName}.{root.Source.UniqueName}();"); } else { - lines.AppendLine($"return {Names.LightweightRootName}({string.Join(", ", root.RootArgs.Select(i => i.Name))}).{root.Source.UniqueName}();"); + lines.AppendLine($"return {compositionInstance}{Names.LightweightRootName}({string.Join(", ", root.RootArgs.Select(i => i.Name))}).{root.Source.UniqueName}();"); } } else diff --git a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs index 0b3c116f8..115de37bf 100644 --- a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs +++ b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs @@ -57,7 +57,7 @@ public IEnumerable Build(GraphBuildContext ctx) } else { - if (node.Root.Source.Kind.HasFlag(RootKinds.Light)) + if (node.Root.Kind.HasFlag(RootKinds.Light)) { processed.Add(processingNode); } From 7d705ee97db3c5923c56f8deaae3a543d3f47569 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 12:41:51 +0300 Subject: [PATCH 09/15] Fix lightweight root graph handling and max-arity test --- src/Pure.DI.Core/Core/DependencyGraphBuilder.cs | 5 +++-- .../Pure.DI.IntegrationTests/LightweightRootsTests.cs | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs index 115de37bf..ae3ab1fff 100644 --- a/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs +++ b/src/Pure.DI.Core/Core/DependencyGraphBuilder.cs @@ -57,7 +57,8 @@ public IEnumerable Build(GraphBuildContext ctx) } else { - if (node.Root.Kind.HasFlag(RootKinds.Light)) + if (node.Root.Source.Kind.HasFlag(RootKinds.Light) + && node.Root.Source.RootType is not INamedTypeSymbol { IsGenericType: true }) { processed.Add(processingNode); } @@ -562,4 +563,4 @@ private IProcessingNode CreateNewProcessingNode(ICache node.Factory is { Source.HasContextTag: true } ? injection.Tag : null; -} \ No newline at end of file +} diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index b80725839..d665b8d55 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -1212,11 +1212,11 @@ class Service( int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, - int a13, int a14, int a15, int a16) : IService + int a13, int a14, int a15) : IService { public int Sum { get; } = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + - a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16; + a9 + a10 + a11 + a12 + a13 + a14 + a15; } partial class Composition @@ -1226,7 +1226,7 @@ void Setup() => DI.Setup(nameof(Composition)) .RootArg("a1").RootArg("a2").RootArg("a3").RootArg("a4") .RootArg("a5").RootArg("a6").RootArg("a7").RootArg("a8") .RootArg("a9").RootArg("a10").RootArg("a11").RootArg("a12") - .RootArg("a13").RootArg("a14").RootArg("a15").RootArg("a16") + .RootArg("a13").RootArg("a14").RootArg("a15") .Bind().To() .Root("Create", kind: RootKinds.Light); } @@ -1236,14 +1236,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16).Sum); + Console.WriteLine(composition.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15).Sum); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["136"], result); + result.StdOut.ShouldBe(["120"], result); } [Fact] From 414bc8158197d0830ca070989306e81573ead648 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 12:47:48 +0300 Subject: [PATCH 10/15] Fix lightweight max func arguments integration test --- .../LightweightRootsTests.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index d665b8d55..f7782e5c3 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -1208,25 +1208,18 @@ public async Task ShouldSupportLightweightRootWithMaximumFuncArguments() namespace Sample; interface IService { int Sum { get; } } - class Service( - int a1, int a2, int a3, int a4, - int a5, int a6, int a7, int a8, - int a9, int a10, int a11, int a12, - int a13, int a14, int a15) : IService + class Service(Func sum) : IService { - public int Sum { get; } = - a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + - a9 + a10 + a11 + a12 + a13 + a14 + a15; + public int Sum { get; } = sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("a1").RootArg("a2").RootArg("a3").RootArg("a4") - .RootArg("a5").RootArg("a6").RootArg("a7").RootArg("a8") - .RootArg("a9").RootArg("a10").RootArg("a11").RootArg("a12") - .RootArg("a13").RootArg("a14").RootArg("a15") + .Bind>() + .To(_ => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) => + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15) .Bind().To() .Root("Create", kind: RootKinds.Light); } @@ -1236,7 +1229,7 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15).Sum); + Console.WriteLine(composition.Create.Sum); } } """.RunAsync(new Options(LanguageVersion.Preview)); From 10d8cbf70e59d5f35efb52fb2d8a7a70224d8e7f Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 13:08:05 +0300 Subject: [PATCH 11/15] Use tagged root args in lightweight max-arguments test --- .../LightweightRootsTests.cs | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index f7782e5c3..686222835 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -1208,18 +1208,47 @@ public async Task ShouldSupportLightweightRootWithMaximumFuncArguments() namespace Sample; interface IService { int Sum { get; } } - class Service(Func sum) : IService - { - public int Sum { get; } = sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + class Service( + [Tag("a1")] int a1, + [Tag("a2")] int a2, + [Tag("a3")] int a3, + [Tag("a4")] int a4, + [Tag("a5")] int a5, + [Tag("a6")] int a6, + [Tag("a7")] int a7, + [Tag("a8")] int a8, + [Tag("a9")] int a9, + [Tag("a10")] int a10, + [Tag("a11")] int a11, + [Tag("a12")] int a12, + [Tag("a13")] int a13, + [Tag("a14")] int a14, + [Tag("a15")] int a15) + : IService + { + public int Sum { get; } = + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15; } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .Bind>() - .To(_ => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) => - a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15) + .RootArg("a1", "a1") + .RootArg("a2", "a2") + .RootArg("a3", "a3") + .RootArg("a4", "a4") + .RootArg("a5", "a5") + .RootArg("a6", "a6") + .RootArg("a7", "a7") + .RootArg("a8", "a8") + .RootArg("a9", "a9") + .RootArg("a10", "a10") + .RootArg("a11", "a11") + .RootArg("a12", "a12") + .RootArg("a13", "a13") + .RootArg("a14", "a14") + .RootArg("a15", "a15") .Bind().To() .Root("Create", kind: RootKinds.Light); } @@ -1229,7 +1258,7 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.Create.Sum); + Console.WriteLine(composition.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15).Sum); } } """.RunAsync(new Options(LanguageVersion.Preview)); From f05ae24e116b6ab7681e5490ddf035036c645b1e Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 13:15:45 +0300 Subject: [PATCH 12/15] Expand lightweight max-arguments test to 16 tagged root args --- .../Pure.DI.IntegrationTests/LightweightRootsTests.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index 686222835..093b55442 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -1223,11 +1223,12 @@ class Service( [Tag("a12")] int a12, [Tag("a13")] int a13, [Tag("a14")] int a14, - [Tag("a15")] int a15) + [Tag("a15")] int a15, + [Tag("a16")] int a16) : IService { public int Sum { get; } = - a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15; + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16; } partial class Composition @@ -1249,6 +1250,7 @@ void Setup() => DI.Setup(nameof(Composition)) .RootArg("a13", "a13") .RootArg("a14", "a14") .RootArg("a15", "a15") + .RootArg("a16", "a16") .Bind().To() .Root("Create", kind: RootKinds.Light); } @@ -1258,14 +1260,14 @@ class Program static void Main() { var composition = new Composition(); - Console.WriteLine(composition.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15).Sum); + Console.WriteLine(composition.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16).Sum); } } """.RunAsync(new Options(LanguageVersion.Preview)); // Then result.Success.ShouldBeTrue(result); - result.StdOut.ShouldBe(["120"], result); + result.StdOut.ShouldBe(["136"], result); } [Fact] From f577db891717f58e6edfee15bcb366f673708b68 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 13:17:26 +0300 Subject: [PATCH 13/15] Add lightweight root integration tests for 20-30 args --- .../LightweightRootsTests.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index 093b55442..37738107c 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -1270,6 +1270,75 @@ static void Main() result.StdOut.ShouldBe(["136"], result); } + [Theory] + [InlineData(20)] + [InlineData(21)] + [InlineData(22)] + [InlineData(23)] + [InlineData(24)] + [InlineData(25)] + [InlineData(26)] + [InlineData(27)] + [InlineData(28)] + [InlineData(29)] + [InlineData(30)] + public async Task ShouldFailWhenLightweightRootHasTooManyTaggedArguments(int argsCount) + { + // Given + var ctorArgs = new global::System.Collections.Generic.List(); + var sumArgs = new global::System.Collections.Generic.List(); + var rootArgs = new global::System.Collections.Generic.List(); + var callArgs = new global::System.Collections.Generic.List(); + for (var i = 1; i <= argsCount; i++) + { + ctorArgs.Add($"[Tag(\"a{i}\")] int a{i}"); + sumArgs.Add($"a{i}"); + rootArgs.Add($".RootArg(\"a{i}\", \"a{i}\")"); + callArgs.Add(i.ToString()); + } + + var ctorSeparator = ",\n "; + var rootSeparator = "\n "; + + var setupCode = $""" + using Pure.DI; + + namespace Sample; + + interface IService {{ int Sum {{ get; }} }} + class Service( + {string.Join(ctorSeparator, ctorArgs)}) + : IService + {{ + public int Sum {{ get; }} = {string.Join(" + ", sumArgs)}; + }} + + partial class Composition + {{ + void Setup() => DI.Setup(nameof(Composition)) + .Hint(Hint.Resolve, "Off") + {string.Join(rootSeparator, rootArgs)} + .Bind().To() + .Root("Create", kind: RootKinds.Light); + }} + + class Program + {{ + static void Main() + {{ + var composition = new Composition(); + var service = composition.Create({string.Join(", ", callArgs)}); + }} + }} + """; + + // When + var result = await setupCode.RunAsync(new Options(LanguageVersion.Preview, CheckCompilationErrors: false)); + + // Then + result.Success.ShouldBeFalse(result); + } + [Fact] public async Task ShouldFailWhenCallingLightweightRootWithWrongArgumentType() { From 2eeb7946eebad742268f3a379e37f4a927e21ef6 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 13:24:25 +0300 Subject: [PATCH 14/15] Replace 20-30 args theory with 20 additional lightweight integration checks --- .../LightweightRootsTests.cs | 143 ++++++++++++------ 1 file changed, 95 insertions(+), 48 deletions(-) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index 37738107c..a96b9665d 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -1270,75 +1270,122 @@ static void Main() result.StdOut.ShouldBe(["136"], result); } - [Theory] - [InlineData(20)] - [InlineData(21)] - [InlineData(22)] - [InlineData(23)] - [InlineData(24)] - [InlineData(25)] - [InlineData(26)] - [InlineData(27)] - [InlineData(28)] - [InlineData(29)] - [InlineData(30)] - public async Task ShouldFailWhenLightweightRootHasTooManyTaggedArguments(int argsCount) + private async Task ShouldFailWhenCallingLightweightRootWithWrongIntArgument(string argumentExpression) { // Given - var ctorArgs = new global::System.Collections.Generic.List(); - var sumArgs = new global::System.Collections.Generic.List(); - var rootArgs = new global::System.Collections.Generic.List(); - var callArgs = new global::System.Collections.Generic.List(); - for (var i = 1; i <= argsCount; i++) - { - ctorArgs.Add($"[Tag(\"a{i}\")] int a{i}"); - sumArgs.Add($"a{i}"); - rootArgs.Add($".RootArg(\"a{i}\", \"a{i}\")"); - callArgs.Add(i.ToString()); - } - - var ctorSeparator = ",\n "; - var rootSeparator = "\n "; - - var setupCode = $""" + + // When + var result = await $$""" using Pure.DI; namespace Sample; - interface IService {{ int Sum {{ get; }} }} - class Service( - {string.Join(ctorSeparator, ctorArgs)}) - : IService - {{ - public int Sum {{ get; }} = {string.Join(" + ", sumArgs)}; - }} + interface IService { } + class Service(int id) : IService { } partial class Composition - {{ + { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - {string.Join(rootSeparator, rootArgs)} + .RootArg("id") .Bind().To() .Root("Create", kind: RootKinds.Light); - }} + } class Program - {{ + { static void Main() - {{ + { var composition = new Composition(); - var service = composition.Create({string.Join(", ", callArgs)}); - }} - }} - """; - - // When - var result = await setupCode.RunAsync(new Options(LanguageVersion.Preview, CheckCompilationErrors: false)); + var service = composition.Create({{argumentExpression}}); + } + } + """.RunAsync(new Options(LanguageVersion.Preview, CheckCompilationErrors: false)); // Then result.Success.ShouldBeFalse(result); } + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithBoolArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("true"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithCharArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("'a'"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithStringArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("\"abc\""); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithNullArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("null"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithDoubleArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1.23"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithFloatArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1.23f"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithDecimalArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1.23m"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithLongArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1L"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithUIntArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1u"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithULongArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1ul"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithShortArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(short)1"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithByteArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(byte)1"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithSByteArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(sbyte)1"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithNIntArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(nint)1"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithNUIntArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(nuint)1"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithObjectArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("new object()"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithDateTimeArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("System.DateTime.Now"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithGuidArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("System.Guid.NewGuid()"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithArrayArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("new[] { 1, 2, 3 }"); + + [Fact] + public Task ShouldFailWhenCallingLightweightRootWithLambdaArgument() => + ShouldFailWhenCallingLightweightRootWithWrongIntArgument("() => 1"); + [Fact] public async Task ShouldFailWhenCallingLightweightRootWithWrongArgumentType() { From 111105f2b062cbff2197aad3d3d99854978a5991 Mon Sep 17 00:00:00 2001 From: Nikolay Pianikov Date: Sun, 1 Mar 2026 13:41:25 +0300 Subject: [PATCH 15/15] Replace synthetic 20-30 failure block with 30 real lightweight usage cases --- .../LightweightRootsTests.cs | 139 +++++++----------- 1 file changed, 52 insertions(+), 87 deletions(-) diff --git a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs index a96b9665d..b33f882da 100644 --- a/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs +++ b/tests/Pure.DI.IntegrationTests/LightweightRootsTests.cs @@ -1270,24 +1270,67 @@ static void Main() result.StdOut.ShouldBe(["136"], result); } - private async Task ShouldFailWhenCallingLightweightRootWithWrongIntArgument(string argumentExpression) + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + [InlineData(8)] + [InlineData(9)] + [InlineData(10)] + [InlineData(11)] + [InlineData(12)] + [InlineData(13)] + [InlineData(14)] + [InlineData(15)] + [InlineData(16)] + [InlineData(17)] + [InlineData(18)] + [InlineData(19)] + [InlineData(20)] + [InlineData(21)] + [InlineData(22)] + [InlineData(23)] + [InlineData(24)] + [InlineData(25)] + [InlineData(26)] + [InlineData(27)] + [InlineData(28)] + [InlineData(29)] + [InlineData(30)] + public async Task ShouldSupportLightweightRootWithTaggedIntArgumentScenarios(int value) { // Given // When var result = await $$""" + using System; using Pure.DI; namespace Sample; - interface IService { } - class Service(int id) : IService { } + interface IService + { + int Value { get; } + int DoubleValue { get; } + } + + class Service([Tag("input")] int value) : IService + { + public int Value { get; } = value; + + public int DoubleValue { get; } = value * 2; + } partial class Composition { void Setup() => DI.Setup(nameof(Composition)) .Hint(Hint.Resolve, "Off") - .RootArg("id") + .RootArg("value", "input") .Bind().To() .Root("Create", kind: RootKinds.Light); } @@ -1297,95 +1340,17 @@ class Program static void Main() { var composition = new Composition(); - var service = composition.Create({{argumentExpression}}); + var service = composition.Create({{value}}); + Console.WriteLine($"{service.Value}:{service.DoubleValue}"); } } - """.RunAsync(new Options(LanguageVersion.Preview, CheckCompilationErrors: false)); + """.RunAsync(new Options(LanguageVersion.Preview)); // Then - result.Success.ShouldBeFalse(result); + result.Success.ShouldBeTrue(result); + result.StdOut.ShouldBe([$"{value}:{value * 2}"], result); } - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithBoolArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("true"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithCharArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("'a'"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithStringArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("\"abc\""); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithNullArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("null"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithDoubleArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1.23"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithFloatArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1.23f"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithDecimalArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1.23m"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithLongArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1L"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithUIntArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1u"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithULongArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("1ul"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithShortArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(short)1"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithByteArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(byte)1"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithSByteArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(sbyte)1"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithNIntArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(nint)1"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithNUIntArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("(nuint)1"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithObjectArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("new object()"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithDateTimeArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("System.DateTime.Now"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithGuidArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("System.Guid.NewGuid()"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithArrayArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("new[] { 1, 2, 3 }"); - - [Fact] - public Task ShouldFailWhenCallingLightweightRootWithLambdaArgument() => - ShouldFailWhenCallingLightweightRootWithWrongIntArgument("() => 1"); - [Fact] public async Task ShouldFailWhenCallingLightweightRootWithWrongArgumentType() {