Skip to content

Commit e1e6f22

Browse files
authored
Merge pull request #304 from distantcam/feature/keyed-services
2 parents cc4ce1f + 21de06d commit e1e6f22

File tree

11 files changed

+152
-18
lines changed

11 files changed

+152
-18
lines changed
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.Diagnostics;
22
using static System.AttributeTargets;
33

4+
#pragma warning disable CS9113 // Parameter is unread.
5+
46
namespace AutoCtor;
57

68
public enum GuardSetting
@@ -10,16 +12,18 @@ public enum GuardSetting
1012
Enabled
1113
}
1214

13-
[AttributeUsage(Class | Struct, AllowMultiple = false, Inherited = false)]
15+
[AttributeUsage(Class | Struct, Inherited = false)]
1416
[Conditional("AUTOCTOR_USAGES")]
15-
#pragma warning disable CS9113 // Parameter is unread.
1617
public sealed class AutoConstructAttribute(GuardSetting guard = GuardSetting.Default) : Attribute;
17-
#pragma warning restore CS9113 // Parameter is unread.
1818

19-
[AttributeUsage(Method, AllowMultiple = false, Inherited = false)]
19+
[AttributeUsage(Method, Inherited = false)]
2020
[Conditional("AUTOCTOR_USAGES")]
2121
public sealed class AutoPostConstructAttribute : Attribute;
2222

23-
[AttributeUsage(Field | Property, AllowMultiple = false, Inherited = false)]
23+
[AttributeUsage(Field | Property, Inherited = false)]
2424
[Conditional("AUTOCTOR_USAGES")]
2525
public sealed class AutoConstructIgnoreAttribute : Attribute;
26+
27+
[AttributeUsage(Field | Property, Inherited = false)]
28+
[Conditional("AUTOCTOR_USAGES")]
29+
public sealed class FromKeyedServicesAttribute(object? key) : Attribute;

src/AutoCtor.Shared/AutoConstructSourceGenerator/Emitter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public static void GenerateSource(
4545
type.BaseTypeParameters.Value,
4646
type.BaseTypeArguments.Value);
4747

48-
baseParameterList.Add(new(RefKind.None, bp.Name, new(bpType)));
48+
baseParameterList.Add(
49+
new(RefKind.None, bp.Name, bp.KeyedService, new(bpType)));
4950
}
5051
baseParameters = baseParameterList;
5152
}

src/AutoCtor.Shared/Models/AttributeNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
public const string AutoConstruct = "AutoCtor.AutoConstructAttribute";
44
public const string AutoPostConstruct = "AutoCtor.AutoPostConstructAttribute";
55
public const string AutoConstructIgnore = "AutoCtor.AutoConstructIgnoreAttribute";
6+
public const string FromKeyedServices = "AutoCtor.FromKeyedServicesAttribute";
67
}

src/AutoCtor.Shared/Models/MemberModel.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
23

34
internal readonly record struct MemberModel(
45
EquatableTypeSymbol Type,
56
string FriendlyName,
67
string IdentifierName,
8+
string? KeyedService,
79

810
bool IsReferenceType,
911
bool IsNullableAnnotated
@@ -19,6 +21,7 @@ public static MemberModel Create(IFieldSymbol field)
1921
Type: new(field.Type),
2022
FriendlyName: friendlyName,
2123
IdentifierName: "this." + field.Name.EscapeKeywordIdentifier(),
24+
KeyedService: GetServiceKey(field),
2225

2326
IsReferenceType: field.Type.IsReferenceType,
2427
IsNullableAnnotated: field.Type.NullableAnnotation == NullableAnnotation.Annotated
@@ -33,9 +36,22 @@ public static MemberModel Create(IPropertySymbol property)
3336
Type: new(property.Type),
3437
FriendlyName: friendlyName,
3538
IdentifierName: "this." + property.Name.EscapeKeywordIdentifier(),
39+
KeyedService: GetServiceKey(property),
3640

3741
IsReferenceType: property.Type.IsReferenceType,
3842
IsNullableAnnotated: property.Type.NullableAnnotation == NullableAnnotation.Annotated
3943
);
4044
}
45+
46+
private static string? GetServiceKey(ISymbol symbol)
47+
{
48+
var keyedService = symbol.GetAttributes()
49+
.Where(a => a.AttributeClass?.ToDisplayString() == AttributeNames.FromKeyedServices)
50+
.FirstOrDefault();
51+
52+
if (keyedService != null)
53+
return keyedService.ConstructorArguments[0].ToCSharpString();
54+
55+
return null;
56+
}
4157
}

src/AutoCtor.Shared/Models/ParameterList.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ public ParameterList Build()
3535
&& (p.RefKind == RefKind.Ref || p.RefKind == RefKind.Out)))
3636
continue;
3737

38-
var p = new ParameterModel(RefKind.None, m.FriendlyName, m.Type);
38+
var p = new ParameterModel(RefKind.None, m.FriendlyName, m.KeyedService, m.Type);
3939
var name = GetUniqueName(p, nameHash, uniqueNames);
4040
parameterModels.Add(p);
4141

4242
parametersMap.Add(m, name);
4343
}
4444
foreach (var m in properties)
4545
{
46-
var p = new ParameterModel(RefKind.None, m.FriendlyName, m.Type);
46+
var p = new ParameterModel(RefKind.None, m.FriendlyName, m.KeyedService, m.Type);
4747
var name = GetUniqueName(p, nameHash, uniqueNames);
4848
parameterModels.Add(p);
4949

@@ -68,7 +68,7 @@ public ParameterList Build()
6868
? $"{p.RefKind.ToParameterPrefix()} {name}" : name);
6969
}
7070

71-
var constructorParameters = uniqueNames.Select(u => $"{u.Key.Type} {u.Value}").ToList();
71+
var constructorParameters = uniqueNames.Select(ConstructorParameterCSharp).ToList();
7272

7373
return new(
7474
constructorParameters,
@@ -79,6 +79,14 @@ public ParameterList Build()
7979
);
8080
}
8181

82+
private static string ConstructorParameterCSharp(KeyValuePair<ParameterModel, string> u)
83+
{
84+
if (u.Key.KeyedService != null)
85+
return $"[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices({u.Key.KeyedService})] {u.Key.Type} {u.Value}";
86+
87+
return $"{u.Key.Type} {u.Value}";
88+
}
89+
8290
private string GetUniqueName(ParameterModel p, HashSet<string> nameHash, Dictionary<ParameterModel, string> uniqueNames)
8391
{
8492
if (uniqueNames.TryGetValue(p, out var name))

src/AutoCtor.Shared/Models/ParameterModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
internal readonly record struct ParameterModel(
44
RefKind RefKind,
55
string Name,
6+
string? KeyedService,
67
EquatableTypeSymbol Type)
78
{
89
public static ParameterModel Create(IParameterSymbol parameter) =>
910
new(
1011
parameter.RefKind,
1112
parameter.Name.EscapeKeywordIdentifier(),
13+
null,
1214
new(parameter.Type)
1315
);
1416

src/AutoCtor.Tests/ExampleTests.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class ExampleTests
1111
[MemberData(nameof(GetExamples))]
1212
public async Task ExamplesGeneratedCode(CodeFileTheoryData theoryData)
1313
{
14-
var builder = CreateCompilation<AutoConstructAttribute>(theoryData);
14+
var builder = CreateCompilation(theoryData);
1515
var compilation = await builder.Build(nameof(ExampleTests));
1616
var driver = new GeneratorDriverBuilder()
1717
.AddGenerator(new AutoConstructSourceGenerator())
@@ -28,7 +28,7 @@ await Verify(driver)
2828
[MemberData(nameof(GetExamples))]
2929
public async Task CodeCompilesProperly(CodeFileTheoryData theoryData)
3030
{
31-
var builder = CreateCompilation<AutoConstructAttribute>(theoryData);
31+
var builder = CreateCompilation(theoryData);
3232
var compilation = await builder.Build(nameof(ExampleTests));
3333
new GeneratorDriverBuilder()
3434
.AddGenerator(new AutoConstructSourceGenerator())
@@ -44,7 +44,7 @@ public async Task CodeCompilesProperly(CodeFileTheoryData theoryData)
4444
[MemberData(nameof(GetExamples))]
4545
public async Task EnsureRunsAreCachedCorrectly(CodeFileTheoryData theoryData)
4646
{
47-
var builder = CreateCompilation<AutoConstructAttribute>(theoryData);
47+
var builder = CreateCompilation(theoryData);
4848
var compilation = await builder.Build(nameof(ExampleTests));
4949

5050
var driver = new GeneratorDriverBuilder()
@@ -71,6 +71,13 @@ public async Task EnsureRunsAreCachedCorrectly(CodeFileTheoryData theoryData)
7171

7272
// ----------------------------------------------------------------------------------------
7373

74+
private static CompilationBuilder CreateCompilation(CodeFileTheoryData theoryData)
75+
{
76+
return CreateCompilation<AutoConstructAttribute>(theoryData)
77+
.AddNugetReference(
78+
"Microsoft.Extensions.DependencyInjection.Abstractions", "9.0.4");
79+
}
80+
7481
private static DirectoryInfo? BaseDir { get; } = new DirectoryInfo(Environment.CurrentDirectory)?.Parent?.Parent;
7582

7683
private static IEnumerable<string> GetExamplesFiles(string path) => Directory.GetFiles(Path.Combine(BaseDir?.FullName ?? "", path), "*.cs").Where(e => !e.Contains(".g."));
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
public interface IService { }
2+
public interface IService<T> { }
3+
4+
public enum Keys
5+
{
6+
Default
7+
}
8+
9+
[AutoCtor.AutoConstruct]
10+
public partial class KeyedServicesTest<T>
11+
{
12+
[AutoCtor.FromKeyedServicesAttribute(null)]
13+
private readonly IService _null;
14+
[AutoCtor.FromKeyedServicesAttribute("key")]
15+
private readonly IService _string;
16+
[AutoCtor.FromKeyedServicesAttribute(0)]
17+
private readonly IService _int;
18+
[AutoCtor.FromKeyedServicesAttribute(true)]
19+
private readonly IService _bool;
20+
[AutoCtor.FromKeyedServicesAttribute(Keys.Default)]
21+
private readonly IService _enum;
22+
[AutoCtor.FromKeyedServicesAttribute("generic")]
23+
private readonly IService<T> _generic;
24+
private readonly IService _notKeyed;
25+
}
26+
27+
[AutoCtor.AutoConstruct]
28+
public partial class ChildKeyedServicesTest : KeyedServicesTest<int>
29+
{
30+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//HintName: ChildKeyedServicesTest.g.cs
2+
//------------------------------------------------------------------------------
3+
// <auto-generated>
4+
// This code was generated by https://github.com/distantcam/AutoCtor
5+
// </auto-generated>
6+
//------------------------------------------------------------------------------
7+
8+
partial class ChildKeyedServicesTest
9+
{
10+
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
11+
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
12+
public ChildKeyedServicesTest(
13+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(null)] global::IService @null,
14+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("key")] global::IService @string,
15+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(0)] global::IService @int,
16+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(true)] global::IService @bool,
17+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(Keys.Default)] global::IService @enum,
18+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("generic")] global::IService<int> generic,
19+
global::IService notKeyed
20+
) : base(
21+
@null,
22+
@string,
23+
@int,
24+
@bool,
25+
@enum,
26+
generic,
27+
notKeyed
28+
)
29+
{
30+
}
31+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//HintName: KeyedServicesTest[T].g.cs
2+
//------------------------------------------------------------------------------
3+
// <auto-generated>
4+
// This code was generated by https://github.com/distantcam/AutoCtor
5+
// </auto-generated>
6+
//------------------------------------------------------------------------------
7+
8+
partial class KeyedServicesTest<T>
9+
{
10+
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
11+
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
12+
public KeyedServicesTest(
13+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(null)] global::IService @null,
14+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("key")] global::IService @string,
15+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(0)] global::IService @int,
16+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(true)] global::IService @bool,
17+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(Keys.Default)] global::IService @enum,
18+
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("generic")] global::IService<T> generic,
19+
global::IService notKeyed
20+
)
21+
{
22+
this._null = @null;
23+
this._string = @string;
24+
this._int = @int;
25+
this._bool = @bool;
26+
this._enum = @enum;
27+
this._generic = generic;
28+
this._notKeyed = notKeyed;
29+
}
30+
}

0 commit comments

Comments
 (0)