Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/AutoCtor.Attributes/AutoConstructAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Diagnostics;
using static System.AttributeTargets;

#pragma warning disable CS9113 // Parameter is unread.

namespace AutoCtor;

public enum GuardSetting
Expand All @@ -10,16 +12,18 @@ public enum GuardSetting
Enabled
}

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

[AttributeUsage(Method, AllowMultiple = false, Inherited = false)]
[AttributeUsage(Method, Inherited = false)]
[Conditional("AUTOCTOR_USAGES")]
public sealed class AutoPostConstructAttribute : Attribute;

[AttributeUsage(Field | Property, AllowMultiple = false, Inherited = false)]
[AttributeUsage(Field | Property, Inherited = false)]
[Conditional("AUTOCTOR_USAGES")]
public sealed class AutoConstructIgnoreAttribute : Attribute;

[AttributeUsage(Field | Property, Inherited = false)]
[Conditional("AUTOCTOR_USAGES")]
public sealed class FromKeyedServicesAttribute(object? key) : Attribute;
3 changes: 2 additions & 1 deletion src/AutoCtor.Shared/AutoConstructSourceGenerator/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public static void GenerateSource(
type.BaseTypeParameters.Value,
type.BaseTypeArguments.Value);

baseParameterList.Add(new(RefKind.None, bp.Name, new(bpType)));
baseParameterList.Add(
new(RefKind.None, bp.Name, bp.KeyedService, new(bpType)));
}
baseParameters = baseParameterList;
}
Expand Down
1 change: 1 addition & 0 deletions src/AutoCtor.Shared/Models/AttributeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
public const string AutoConstruct = "AutoCtor.AutoConstructAttribute";
public const string AutoPostConstruct = "AutoCtor.AutoPostConstructAttribute";
public const string AutoConstructIgnore = "AutoCtor.AutoConstructIgnoreAttribute";
public const string FromKeyedServices = "AutoCtor.FromKeyedServicesAttribute";
}
16 changes: 16 additions & 0 deletions src/AutoCtor.Shared/Models/MemberModel.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

internal readonly record struct MemberModel(
EquatableTypeSymbol Type,
string FriendlyName,
string IdentifierName,
string? KeyedService,

bool IsReferenceType,
bool IsNullableAnnotated
Expand All @@ -19,6 +21,7 @@ public static MemberModel Create(IFieldSymbol field)
Type: new(field.Type),
FriendlyName: friendlyName,
IdentifierName: "this." + field.Name.EscapeKeywordIdentifier(),
KeyedService: GetServiceKey(field),

IsReferenceType: field.Type.IsReferenceType,
IsNullableAnnotated: field.Type.NullableAnnotation == NullableAnnotation.Annotated
Expand All @@ -33,9 +36,22 @@ public static MemberModel Create(IPropertySymbol property)
Type: new(property.Type),
FriendlyName: friendlyName,
IdentifierName: "this." + property.Name.EscapeKeywordIdentifier(),
KeyedService: GetServiceKey(property),

IsReferenceType: property.Type.IsReferenceType,
IsNullableAnnotated: property.Type.NullableAnnotation == NullableAnnotation.Annotated
);
}

private static string? GetServiceKey(ISymbol symbol)
{
var keyedService = symbol.GetAttributes()
.Where(a => a.AttributeClass?.ToDisplayString() == AttributeNames.FromKeyedServices)
.FirstOrDefault();

if (keyedService != null)
return keyedService.ConstructorArguments[0].ToCSharpString();

return null;
}
}
14 changes: 11 additions & 3 deletions src/AutoCtor.Shared/Models/ParameterList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ public ParameterList Build()
&& (p.RefKind == RefKind.Ref || p.RefKind == RefKind.Out)))
continue;

var p = new ParameterModel(RefKind.None, m.FriendlyName, m.Type);
var p = new ParameterModel(RefKind.None, m.FriendlyName, m.KeyedService, m.Type);
var name = GetUniqueName(p, nameHash, uniqueNames);
parameterModels.Add(p);

parametersMap.Add(m, name);
}
foreach (var m in properties)
{
var p = new ParameterModel(RefKind.None, m.FriendlyName, m.Type);
var p = new ParameterModel(RefKind.None, m.FriendlyName, m.KeyedService, m.Type);
var name = GetUniqueName(p, nameHash, uniqueNames);
parameterModels.Add(p);

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

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

return new(
constructorParameters,
Expand All @@ -79,6 +79,14 @@ public ParameterList Build()
);
}

private static string ConstructorParameterCSharp(KeyValuePair<ParameterModel, string> u)
{
if (u.Key.KeyedService != null)
return $"[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices({u.Key.KeyedService})] {u.Key.Type} {u.Value}";

return $"{u.Key.Type} {u.Value}";
}

private string GetUniqueName(ParameterModel p, HashSet<string> nameHash, Dictionary<ParameterModel, string> uniqueNames)
{
if (uniqueNames.TryGetValue(p, out var name))
Expand Down
2 changes: 2 additions & 0 deletions src/AutoCtor.Shared/Models/ParameterModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
internal readonly record struct ParameterModel(
RefKind RefKind,
string Name,
string? KeyedService,
EquatableTypeSymbol Type)
{
public static ParameterModel Create(IParameterSymbol parameter) =>
new(
parameter.RefKind,
parameter.Name.EscapeKeywordIdentifier(),
null,
new(parameter.Type)
);

Expand Down
13 changes: 10 additions & 3 deletions src/AutoCtor.Tests/ExampleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class ExampleTests
[MemberData(nameof(GetExamples))]
public async Task ExamplesGeneratedCode(CodeFileTheoryData theoryData)
{
var builder = CreateCompilation<AutoConstructAttribute>(theoryData);
var builder = CreateCompilation(theoryData);
var compilation = await builder.Build(nameof(ExampleTests));
var driver = new GeneratorDriverBuilder()
.AddGenerator(new AutoConstructSourceGenerator())
Expand All @@ -28,7 +28,7 @@ await Verify(driver)
[MemberData(nameof(GetExamples))]
public async Task CodeCompilesProperly(CodeFileTheoryData theoryData)
{
var builder = CreateCompilation<AutoConstructAttribute>(theoryData);
var builder = CreateCompilation(theoryData);
var compilation = await builder.Build(nameof(ExampleTests));
new GeneratorDriverBuilder()
.AddGenerator(new AutoConstructSourceGenerator())
Expand All @@ -44,7 +44,7 @@ public async Task CodeCompilesProperly(CodeFileTheoryData theoryData)
[MemberData(nameof(GetExamples))]
public async Task EnsureRunsAreCachedCorrectly(CodeFileTheoryData theoryData)
{
var builder = CreateCompilation<AutoConstructAttribute>(theoryData);
var builder = CreateCompilation(theoryData);
var compilation = await builder.Build(nameof(ExampleTests));

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

// ----------------------------------------------------------------------------------------

private static CompilationBuilder CreateCompilation(CodeFileTheoryData theoryData)
{
return CreateCompilation<AutoConstructAttribute>(theoryData)
.AddNugetReference(
"Microsoft.Extensions.DependencyInjection.Abstractions", "9.0.4");
}

private static DirectoryInfo? BaseDir { get; } = new DirectoryInfo(Environment.CurrentDirectory)?.Parent?.Parent;

private static IEnumerable<string> GetExamplesFiles(string path) => Directory.GetFiles(Path.Combine(BaseDir?.FullName ?? "", path), "*.cs").Where(e => !e.Contains(".g."));
Expand Down
30 changes: 30 additions & 0 deletions src/AutoCtor.Tests/Examples/KeyedServicesTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
public interface IService { }
public interface IService<T> { }

public enum Keys
{
Default
}

[AutoCtor.AutoConstruct]
public partial class KeyedServicesTest<T>
{
[AutoCtor.FromKeyedServicesAttribute(null)]
private readonly IService _null;
[AutoCtor.FromKeyedServicesAttribute("key")]
private readonly IService _string;
[AutoCtor.FromKeyedServicesAttribute(0)]
private readonly IService _int;
[AutoCtor.FromKeyedServicesAttribute(true)]
private readonly IService _bool;
[AutoCtor.FromKeyedServicesAttribute(Keys.Default)]
private readonly IService _enum;
[AutoCtor.FromKeyedServicesAttribute("generic")]
private readonly IService<T> _generic;
private readonly IService _notKeyed;
}

[AutoCtor.AutoConstruct]
public partial class ChildKeyedServicesTest : KeyedServicesTest<int>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//HintName: ChildKeyedServicesTest.g.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/distantcam/AutoCtor
// </auto-generated>
//------------------------------------------------------------------------------

partial class ChildKeyedServicesTest
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public ChildKeyedServicesTest(
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(null)] global::IService @null,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("key")] global::IService @string,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(0)] global::IService @int,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(true)] global::IService @bool,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(Keys.Default)] global::IService @enum,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("generic")] global::IService<int> generic,
global::IService notKeyed
) : base(
@null,
@string,
@int,
@bool,
@enum,
generic,
notKeyed
)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//HintName: KeyedServicesTest[T].g.cs
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by https://github.com/distantcam/AutoCtor
// </auto-generated>
//------------------------------------------------------------------------------

partial class KeyedServicesTest<T>
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public KeyedServicesTest(
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(null)] global::IService @null,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("key")] global::IService @string,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(0)] global::IService @int,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(true)] global::IService @bool,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(Keys.Default)] global::IService @enum,
[global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("generic")] global::IService<T> generic,
global::IService notKeyed
)
{
this._null = @null;
this._string = @string;
this._int = @int;
this._bool = @bool;
this._enum = @enum;
this._generic = generic;
this._notKeyed = notKeyed;
}
}
16 changes: 10 additions & 6 deletions src/AutoCtor.Tests/Utilities/CompilationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ internal class CompilationBuilder
private CSharpParseOptions _parseOptions;
private CSharpCompilationOptions _compilationOptions;

private string _defaultTargetFramework = "net9.0";

public CSharpParseOptions ParseOptions => _parseOptions;

public CompilationBuilder()
Expand Down Expand Up @@ -47,24 +49,26 @@ public async Task<CSharpCompilation> Build(string assemblyName)
.WithOptions(_compilationOptions);
}

public CompilationBuilder AddNugetReference(string targetFramework, string id, string version, string path)
public CompilationBuilder AddNugetReference(string id, string version, string targetFramework = "", string path = "")
{
var framework = string.IsNullOrEmpty(targetFramework) ? _defaultTargetFramework : targetFramework;

return new(this)
{
_nugetReferences = _nugetReferences.Add(new(
targetFramework,
framework,
new(id, version),
path
Path.Join(string.IsNullOrEmpty(path) ? "lib" : path, framework)
))
};
}

public CompilationBuilder AddNetCoreReference(string targetFramework = "net9.0", string version = "9.0.2")
public CompilationBuilder AddNetCoreReference(string targetFramework = "", string version = "9.0.4")
=> AddNugetReference(
targetFramework,
"Microsoft.NETCore.App.Ref",
version,
Path.Join("ref", targetFramework));
targetFramework,
"ref");

public CompilationBuilder AddAssemblyReference<T>()
{
Expand Down