Skip to content

Commit 287103c

Browse files
committed
UseDelegateFromConstructorAttribute supports custom delegate type name.
1 parent f2fd35a commit 287103c

File tree

28 files changed

+2516
-9
lines changed

28 files changed

+2516
-9
lines changed

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/DelegateMethodState.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@ public sealed class DelegateMethodState : IEquatable<DelegateMethodState>, IHash
77
public string? ReturnType { get; }
88
public IReadOnlyList<ParameterState> Parameters { get; }
99
public string ArgumentName { get; }
10+
public string? DelegateName { get; }
1011

1112
public DelegateMethodState(
1213
Accessibility accessibility,
1314
string methodName,
1415
string? returnType,
15-
IReadOnlyList<ParameterState> parameters)
16+
IReadOnlyList<ParameterState> parameters,
17+
string? delegateName)
1618
{
1719
Accessibility = accessibility;
1820
MethodName = methodName;
1921
ReturnType = returnType;
2022
Parameters = parameters;
21-
ArgumentName = methodName.MakeArgumentName();
23+
DelegateName = delegateName;
24+
ArgumentName = (delegateName ?? methodName).MakeArgumentName();
2225
}
2326

2427
public bool Equals(DelegateMethodState? other)
@@ -31,7 +34,8 @@ public bool Equals(DelegateMethodState? other)
3134
return Accessibility == other.Accessibility
3235
&& MethodName == other.MethodName
3336
&& ReturnType == other.ReturnType
34-
&& Parameters.SequenceEqual(other.Parameters);
37+
&& Parameters.SequenceEqual(other.Parameters)
38+
&& DelegateName == other.DelegateName;
3539
}
3640

3741
public override bool Equals(object? obj)
@@ -47,6 +51,7 @@ public override int GetHashCode()
4751
hashCode = (hashCode * 397) ^ MethodName.GetHashCode();
4852
hashCode = (hashCode * 397) ^ ReturnType?.GetHashCode() ?? 0;
4953
hashCode = (hashCode * 397) ^ Parameters.ComputeHashCode();
54+
hashCode = (hashCode * 397) ^ DelegateName?.GetHashCode() ?? 0;
5055
return hashCode;
5156
}
5257
}

src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ public static class AttributeDataExtensions
99
return GetStringParameterValue(attributeData, "DefaultInstancePropertyName");
1010
}
1111

12+
public static string? FindDelegateName(this AttributeData attributeData)
13+
{
14+
return GetStringParameterValue(attributeData, "DelegateName");
15+
}
16+
1217
public static bool? FindSkipKeyMember(this AttributeData attributeData)
1318
{
1419
return GetBooleanParameterValue(attributeData, Constants.Attributes.Properties.SKIP_KEY_MEMBER);

src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/DelegateMethodStateExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ public static class DelegateMethodStateExtensions
66
{
77
public static bool NeedsCustomDelegate(this DelegateMethodState method)
88
{
9-
return method.Parameters.Any(p => p.RefKind != RefKind.None);
9+
return method.DelegateName != null || method.Parameters.Any(p => p.RefKind != RefKind.None);
1010
}
1111
}

src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/StringBuilderExtensions.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,11 @@ public static StringBuilder AppendDelegateType(
362362
DelegateMethodState method)
363363
{
364364
if (method.NeedsCustomDelegate())
365-
return sb.Append(method.MethodName).Append("Delegate");
365+
{
366+
return method.DelegateName is not null
367+
? sb.Append(method.DelegateName)
368+
: sb.Append(method.MethodName).Append("Delegate");
369+
}
366370

367371
// Use standard delegates if no reference parameters
368372
var isFunc = method.ReturnType is not null;

src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/TypeSymbolExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,14 @@ public static IReadOnlyList<DelegateMethodState> GetDelegateMethods(
709709
p.RefKind))
710710
.ToList();
711711

712-
var methodState = new DelegateMethodState(methodSymbol.DeclaredAccessibility, methodName, returnType, parameters);
712+
var customDelegateName = useDelegateFromConstructorAttribute.FindDelegateName();
713+
714+
var methodState = new DelegateMethodState(
715+
methodSymbol.DeclaredAccessibility,
716+
methodName,
717+
returnType,
718+
parameters,
719+
customDelegateName);
713720

714721
(methodStates ??= []).Add(methodState);
715722
}

src/Thinktecture.Runtime.Extensions/UseDelegateFromConstructorAttribute.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,11 @@ namespace Thinktecture;
1919
/// </code>
2020
/// </example>
2121
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
22-
public sealed class UseDelegateFromConstructorAttribute : Attribute;
22+
public sealed class UseDelegateFromConstructorAttribute : Attribute
23+
{
24+
/// <summary>
25+
/// Gets or sets the custom name for the delegate type.
26+
/// If provided, a custom delegate type will always be generated regardless of parameter ref-kinds.
27+
/// </summary>
28+
public string? DelegateName { get; set; }
29+
}

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,4 +1015,127 @@ await VerifyAsync(outputs,
10151015
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
10161016
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
10171017
}
1018+
1019+
[Fact]
1020+
public async Task Should_generate_delegate_with_custom_name()
1021+
{
1022+
var source = """
1023+
using System;
1024+
1025+
namespace Thinktecture.Tests
1026+
{
1027+
[SmartEnum<string>]
1028+
public abstract partial class TestEnum
1029+
{
1030+
public static readonly TestEnum Item1 = null!;
1031+
public static readonly TestEnum Item2 = null!;
1032+
1033+
[UseDelegateFromConstructor(DelegateName = "CustomProcessDelegate")]
1034+
public partial string Process(int value);
1035+
}
1036+
}
1037+
""";
1038+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
1039+
1040+
await VerifyAsync(outputs,
1041+
"Thinktecture.Tests.TestEnum.g.cs",
1042+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
1043+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
1044+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
1045+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
1046+
}
1047+
1048+
[Fact]
1049+
public async Task Should_generate_multiple_delegates_with_custom_names()
1050+
{
1051+
var source = """
1052+
using System;
1053+
1054+
namespace Thinktecture.Tests
1055+
{
1056+
[SmartEnum<string>]
1057+
public abstract partial class TestEnum
1058+
{
1059+
public static readonly TestEnum Item1 = null!;
1060+
public static readonly TestEnum Item2 = null!;
1061+
1062+
[UseDelegateFromConstructor(DelegateName = "StringProcessDelegate")]
1063+
public partial string Process(string value);
1064+
1065+
[UseDelegateFromConstructor(DelegateName = "IntProcessDelegate")]
1066+
public partial int Process(int value);
1067+
1068+
[UseDelegateFromConstructor(DelegateName = "BoolProcessDelegate")]
1069+
public partial void Process(bool value);
1070+
}
1071+
}
1072+
""";
1073+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
1074+
1075+
await VerifyAsync(outputs,
1076+
"Thinktecture.Tests.TestEnum.g.cs",
1077+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
1078+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
1079+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
1080+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
1081+
}
1082+
1083+
[Fact]
1084+
public async Task Should_generate_delegate_with_custom_name_and_complex_parameters()
1085+
{
1086+
var source = """
1087+
using System;
1088+
using System.Collections.Generic;
1089+
1090+
namespace Thinktecture.Tests
1091+
{
1092+
[SmartEnum<string>]
1093+
public abstract partial class TestEnum
1094+
{
1095+
public static readonly TestEnum Item1 = null!;
1096+
public static readonly TestEnum Item2 = null!;
1097+
1098+
[UseDelegateFromConstructor(DelegateName = "ComplexProcessDelegate")]
1099+
public partial Dictionary<string, List<int>>? Process(Dictionary<int, string>? input, ref List<string>? refList, in HashSet<int> inSet, out Dictionary<string, object?>? outDict);
1100+
}
1101+
}
1102+
""";
1103+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
1104+
1105+
await VerifyAsync(outputs,
1106+
"Thinktecture.Tests.TestEnum.g.cs",
1107+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
1108+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
1109+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
1110+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
1111+
}
1112+
1113+
[Fact]
1114+
public async Task Should_generate_delegate_with_custom_name_and_nullable_value_types()
1115+
{
1116+
var source = """
1117+
using System;
1118+
1119+
namespace Thinktecture.Tests
1120+
{
1121+
[SmartEnum<string>]
1122+
public abstract partial class TestEnum
1123+
{
1124+
public static readonly TestEnum Item1 = null!;
1125+
public static readonly TestEnum Item2 = null!;
1126+
1127+
[UseDelegateFromConstructor(DelegateName = "NullableValueTypeDelegate")]
1128+
public partial int? Process(DateTime? dateTime, ref Guid? refGuid, in decimal? inDecimal, out bool? outBool);
1129+
}
1130+
}
1131+
""";
1132+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
1133+
1134+
await VerifyAsync(outputs,
1135+
"Thinktecture.Tests.TestEnum.g.cs",
1136+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
1137+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
1138+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
1139+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
1140+
}
10181141
}

0 commit comments

Comments
 (0)