Skip to content

Commit f9c834e

Browse files
Fix readonly setup-context args in generated scope methods
1 parent 4018352 commit f9c834e

2 files changed

Lines changed: 320 additions & 0 deletions

File tree

src/Pure.DI.Core/Core/Code/Parts/ScopeConstructorBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ public CompositionCode Build(CompositionCode composition)
8080

8181
foreach (var contextArg in composition.SetupContextArgsToCopy)
8282
{
83+
if (composition.IsScopeMethod && contextArg.Kind == SetupContextKind.Argument)
84+
{
85+
// Scope methods receive an already constructed child scope.
86+
// Setup context arguments are stored in readonly fields and are initialized by that constructor.
87+
continue;
88+
}
89+
8390
code.AppendLine($"{destination}{contextArg.Name} = {source}{contextArg.Name};");
8491
}
8592

tests/Pure.DI.IntegrationTests/SetupContextTests.cs

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,319 @@ public static void Main()
133133
result.Warnings.Count(i => i.Id == LogId.WarningInstanceMemberInDependsOnSetup).ShouldBe(1, result);
134134
}
135135

136+
[Fact]
137+
public async Task ShouldSupportArgumentSetupContextWithScopeMethod()
138+
{
139+
// Given
140+
141+
// When
142+
var result = await """
143+
using System;
144+
using Pure.DI;
145+
146+
namespace Sample
147+
{
148+
internal partial class BaseComposition
149+
{
150+
internal int Value { get; set; }
151+
152+
private void Setup()
153+
{
154+
DI.Setup(nameof(BaseComposition), CompositionKind.Internal)
155+
.Bind<int>().To(_ => Value);
156+
}
157+
}
158+
159+
internal partial class Composition
160+
{
161+
private void Setup()
162+
{
163+
DI.Setup(nameof(Composition))
164+
.Hint(Hint.ScopeMethodName, "SetupScope")
165+
.DependsOn(nameof(BaseComposition), SetupContextKind.Argument, "baseContext")
166+
.Bind<IService>().As(Lifetime.Scoped).To<Service>()
167+
.Root<IService>("Service");
168+
}
169+
}
170+
171+
interface IService
172+
{
173+
int Value { get; }
174+
}
175+
176+
sealed class Service : IService
177+
{
178+
public Service(int value)
179+
{
180+
Value = value;
181+
}
182+
183+
public int Value { get; }
184+
}
185+
186+
public class Program
187+
{
188+
public static void Main()
189+
{
190+
var baseContext = new BaseComposition { Value = 41 };
191+
var composition = new Composition(baseContext);
192+
Console.WriteLine(composition.Service.Value);
193+
194+
var scope = Composition.SetupScope(composition, new Composition(baseContext));
195+
Console.WriteLine(scope.Service.Value);
196+
}
197+
}
198+
}
199+
""".RunAsync(new Options(LanguageVersion: LanguageVersion.CSharp9));
200+
201+
// Then
202+
result.Success.ShouldBeTrue(result);
203+
result.Errors.Count.ShouldBe(0, result);
204+
result.Warnings.Count.ShouldBe(0, result);
205+
result.StdOut.ShouldBe(["41", "41"], result);
206+
}
207+
208+
[Fact]
209+
public async Task ShouldSupportArgumentSetupContextWithNestedScopeMethod()
210+
{
211+
// Given
212+
213+
// When
214+
var result = await """
215+
using System;
216+
using Pure.DI;
217+
218+
namespace Sample
219+
{
220+
internal partial class BaseComposition
221+
{
222+
internal int Value { get; set; }
223+
224+
private void Setup()
225+
{
226+
DI.Setup(nameof(BaseComposition), CompositionKind.Internal)
227+
.Bind<int>().To(_ => Value);
228+
}
229+
}
230+
231+
internal partial class Composition
232+
{
233+
private void Setup()
234+
{
235+
DI.Setup(nameof(Composition))
236+
.Hint(Hint.ScopeMethodName, "SetupScope")
237+
.DependsOn(nameof(BaseComposition), SetupContextKind.Argument, "baseContext")
238+
.Bind<IService>().As(Lifetime.Scoped).To<Service>()
239+
.Root<IService>("Service");
240+
}
241+
}
242+
243+
interface IService
244+
{
245+
int Value { get; }
246+
}
247+
248+
sealed class Service : IService
249+
{
250+
public Service(int value)
251+
{
252+
Value = value;
253+
}
254+
255+
public int Value { get; }
256+
}
257+
258+
public class Program
259+
{
260+
public static void Main()
261+
{
262+
var baseContext = new BaseComposition { Value = 41 };
263+
var composition = new Composition(baseContext);
264+
var scope1 = Composition.SetupScope(composition, new Composition(baseContext));
265+
Console.WriteLine(scope1.Service.Value);
266+
267+
var scope2 = Composition.SetupScope(scope1, new Composition(baseContext));
268+
Console.WriteLine(scope2.Service.Value);
269+
}
270+
}
271+
}
272+
""".RunAsync(new Options(LanguageVersion: LanguageVersion.CSharp9));
273+
274+
// Then
275+
result.Success.ShouldBeTrue(result);
276+
result.Errors.Count.ShouldBe(0, result);
277+
result.Warnings.Count.ShouldBe(0, result);
278+
result.StdOut.ShouldBe(["41", "41"], result);
279+
}
280+
281+
[Fact]
282+
public async Task ShouldSupportArgumentSetupContextWithScopeMethodAndCompositionArguments()
283+
{
284+
// Given
285+
286+
// When
287+
var result = await """
288+
using System;
289+
using Pure.DI;
290+
291+
namespace Sample
292+
{
293+
internal partial class BaseComposition
294+
{
295+
internal int Value { get; set; }
296+
297+
private void Setup()
298+
{
299+
DI.Setup(nameof(BaseComposition), CompositionKind.Internal)
300+
.Bind<int>().To(_ => Value);
301+
}
302+
}
303+
304+
interface IService
305+
{
306+
string Message { get; }
307+
}
308+
309+
sealed class Service : IService
310+
{
311+
public Service(int value, string tenant)
312+
{
313+
Message = $"{tenant}:{value}";
314+
}
315+
316+
public string Message { get; }
317+
}
318+
319+
internal partial class Composition
320+
{
321+
private void Setup()
322+
{
323+
DI.Setup(nameof(Composition))
324+
.Hint(Hint.ScopeMethodName, "SetupScope")
325+
.Arg<string>("tenant")
326+
.DependsOn(nameof(BaseComposition), SetupContextKind.Argument, "baseContext")
327+
.Bind<IService>().As(Lifetime.Scoped).To<Service>()
328+
.Root<IService>("Service");
329+
}
330+
}
331+
332+
public class Program
333+
{
334+
public static void Main()
335+
{
336+
var baseContext = new BaseComposition { Value = 41 };
337+
var composition = new Composition(tenant: "EU", baseContext: baseContext);
338+
Console.WriteLine(composition.Service.Message);
339+
340+
var scope = Composition.SetupScope(
341+
composition,
342+
new Composition(tenant: "EU", baseContext: baseContext));
343+
Console.WriteLine(scope.Service.Message);
344+
}
345+
}
346+
}
347+
""".RunAsync(new Options(LanguageVersion: LanguageVersion.CSharp9));
348+
349+
// Then
350+
result.Success.ShouldBeTrue(result);
351+
result.Errors.Count.ShouldBe(0, result);
352+
result.Warnings.Count.ShouldBe(0, result);
353+
result.StdOut.ShouldBe(["EU:41", "EU:41"], result);
354+
}
355+
356+
[Fact]
357+
public async Task ShouldSupportArgumentSetupContextWithScopeMethodAndSingleton()
358+
{
359+
// Given
360+
361+
// When
362+
var result = await """
363+
using System;
364+
using Pure.DI;
365+
366+
namespace Sample
367+
{
368+
internal partial class BaseComposition
369+
{
370+
internal int Value { get; set; }
371+
372+
private void Setup()
373+
{
374+
DI.Setup(nameof(BaseComposition), CompositionKind.Internal)
375+
.Bind<int>().To(_ => Value);
376+
}
377+
}
378+
379+
interface ISingletonService
380+
{
381+
int Value { get; }
382+
}
383+
384+
sealed class SingletonService : ISingletonService
385+
{
386+
public SingletonService(int value)
387+
{
388+
Value = value;
389+
}
390+
391+
public int Value { get; }
392+
}
393+
394+
interface IService
395+
{
396+
ISingletonService Singleton { get; }
397+
}
398+
399+
sealed class Service : IService
400+
{
401+
public Service(ISingletonService singleton)
402+
{
403+
Singleton = singleton;
404+
}
405+
406+
public ISingletonService Singleton { get; }
407+
}
408+
409+
internal partial class Composition
410+
{
411+
private void Setup()
412+
{
413+
DI.Setup(nameof(Composition))
414+
.Hint(Hint.ScopeMethodName, "SetupScope")
415+
.DependsOn(nameof(BaseComposition), SetupContextKind.Argument, "baseContext")
416+
.Bind<ISingletonService>().As(Lifetime.Singleton).To<SingletonService>()
417+
.Bind<IService>().As(Lifetime.Scoped).To<Service>()
418+
.Root<IService>("Service");
419+
}
420+
}
421+
422+
public class Program
423+
{
424+
public static void Main()
425+
{
426+
var baseContext = new BaseComposition { Value = 41 };
427+
var composition = new Composition(baseContext);
428+
var rootSingleton = composition.Service.Singleton;
429+
Console.WriteLine(rootSingleton.Value);
430+
431+
var scope = Composition.SetupScope(composition, new Composition(baseContext));
432+
var scopeSingleton1 = scope.Service.Singleton;
433+
var scopeSingleton2 = scope.Service.Singleton;
434+
Console.WriteLine(scopeSingleton1.Value);
435+
Console.WriteLine(rootSingleton == scopeSingleton1);
436+
Console.WriteLine(scopeSingleton1 == scopeSingleton2);
437+
}
438+
}
439+
}
440+
""".RunAsync(new Options(LanguageVersion: LanguageVersion.CSharp9));
441+
442+
// Then
443+
result.Success.ShouldBeTrue(result);
444+
result.Errors.Count.ShouldBe(0, result);
445+
result.Warnings.Count.ShouldBe(0, result);
446+
result.StdOut.ShouldBe(["41", "41", "True", "True"], result);
447+
}
448+
136449
[Fact]
137450
public async Task ShouldSupportRootArgumentWithSimpleFactory()
138451
{

0 commit comments

Comments
 (0)