Skip to content

Commit f2fd35a

Browse files
committed
UseDelegateFromConstructorAttribute supports ref, out, ref readonly parameters.
1 parent 5831eec commit f2fd35a

File tree

42 files changed

+3805
-19
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3805
-19
lines changed

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,16 @@ public sealed class ParameterState : IEquatable<ParameterState>, IHashCodeComput
5555
{
5656
public string Name { get; }
5757
public string Type { get; }
58+
public RefKind RefKind { get; }
5859

59-
public ParameterState(string name, string type)
60+
public ParameterState(
61+
string name,
62+
string type,
63+
RefKind refKind)
6064
{
6165
Name = name;
6266
Type = type;
67+
RefKind = refKind;
6368
}
6469

6570
public override bool Equals(object? obj)
@@ -76,14 +81,18 @@ public bool Equals(ParameterState? other)
7681
return true;
7782

7883
return Name == other.Name
79-
&& Type == other.Type;
84+
&& Type == other.Type
85+
&& RefKind == other.RefKind;
8086
}
8187

8288
public override int GetHashCode()
8389
{
8490
unchecked
8591
{
86-
return (Name.GetHashCode() * 397) ^ Type.GetHashCode();
92+
var hashCode = Name.GetHashCode();
93+
hashCode = (hashCode * 397) ^ Type.GetHashCode();
94+
hashCode = (hashCode * 397) ^ (int)RefKind;
95+
return hashCode;
8796
}
8897
}
8998
}

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ private void GenerateEnum(CancellationToken cancellationToken)
104104
global::System.IEquatable<").AppendTypeFullyQualifiedNullAnnotated(_state).Append(@">
105105
{");
106106

107+
GenerateCustomDelegateTypes();
108+
107109
if (_state.KeyMember is not null)
108110
{
109111
GenerateModuleInitializer(_state.KeyMember);
@@ -156,13 +158,10 @@ private void GenerateEnum(CancellationToken cancellationToken)
156158

157159
cancellationToken.ThrowIfCancellationRequested();
158160

159-
if (_state.DelegateMethods.Count > 0)
161+
foreach (var method in _state.DelegateMethods)
160162
{
161-
foreach (var method in _state.DelegateMethods)
162-
{
163-
_sb.Append(@"
163+
_sb.Append(@"
164164
private readonly ").AppendDelegateType(method).Append(" _").Append(method.ArgumentName).Append(";");
165-
}
166165
}
167166

168167
GenerateConstructors();
@@ -266,6 +265,35 @@ public override int GetHashCode()
266265
}");
267266
}
268267

268+
private void GenerateCustomDelegateTypes()
269+
{
270+
for (var i = 0; i < _state.DelegateMethods.Count; i++)
271+
{
272+
var method = _state.DelegateMethods[i];
273+
274+
if (!method.NeedsCustomDelegate())
275+
continue;
276+
277+
_sb.Append(@"
278+
private delegate ").Append(method.ReturnType ?? "void").Append(" ").AppendDelegateType(method).Append("(");
279+
280+
for (var j = 0; j < method.Parameters.Count; j++)
281+
{
282+
if (j > 0)
283+
_sb.Append(", ");
284+
285+
var param = method.Parameters[j];
286+
287+
_sb.AppendRefKindParameterPrefix(param.RefKind).Append(param.Type).Append(" ").Append(param.Name);
288+
}
289+
290+
_sb.Append(");");
291+
}
292+
293+
if (_state.DelegateMethods.Count > 0)
294+
_sb.AppendLine();
295+
}
296+
269297
private void GenerateDelegatedMethods()
270298
{
271299
foreach (var method in _state.DelegateMethods)
@@ -280,19 +308,25 @@ private void GenerateDelegatedMethods()
280308
_sb.Append(", ");
281309

282310
var param = method.Parameters[i];
283-
_sb.Append(param.Type).Append(" ").Append(param.Name);
311+
_sb.AppendRefKindParameterPrefix(param.RefKind).Append(param.Type).Append(" ").Append(param.Name);
284312
}
285313

286314
_sb.Append(@")
287315
{
288-
return _").Append(method.ArgumentName).Append("(");
316+
");
317+
318+
if (method.ReturnType != null)
319+
_sb.Append("return ");
320+
321+
_sb.Append("_").Append(method.ArgumentName).Append("(");
289322

290323
for (var i = 0; i < method.Parameters.Count; i++)
291324
{
292325
if (i > 0)
293326
_sb.Append(", ");
294327

295-
_sb.Append(method.Parameters[i].Name);
328+
var param = method.Parameters[i];
329+
_sb.AppendRefKindArgumentPrefix(param.RefKind).Append(param.Name);
296330
}
297331

298332
_sb.Append(@");
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Thinktecture.CodeAnalysis;
2+
3+
namespace Thinktecture;
4+
5+
public static class DelegateMethodStateExtensions
6+
{
7+
public static bool NeedsCustomDelegate(this DelegateMethodState method)
8+
{
9+
return method.Parameters.Any(p => p.RefKind != RefKind.None);
10+
}
11+
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,10 +325,46 @@ public static StringBuilder RenderContainingTypesEnd(
325325
return sb;
326326
}
327327

328+
public static StringBuilder AppendRefKindParameterPrefix(
329+
this StringBuilder sb,
330+
RefKind refKind)
331+
{
332+
var kind = refKind switch
333+
{
334+
RefKind.Out => "out ",
335+
RefKind.Ref => "ref ",
336+
RefKind.In => "in ",
337+
RefKind.RefReadOnlyParameter => "ref readonly ",
338+
_ => null
339+
};
340+
341+
return sb.Append(kind);
342+
}
343+
344+
public static StringBuilder AppendRefKindArgumentPrefix(
345+
this StringBuilder sb,
346+
RefKind refKind)
347+
{
348+
var kind = refKind switch
349+
{
350+
RefKind.Out => "out ",
351+
RefKind.Ref => "ref ",
352+
RefKind.In => "in ",
353+
RefKind.RefReadOnlyParameter => "in ",
354+
_ => null
355+
};
356+
357+
return sb.Append(kind);
358+
}
359+
328360
public static StringBuilder AppendDelegateType(
329361
this StringBuilder sb,
330362
DelegateMethodState method)
331363
{
364+
if (method.NeedsCustomDelegate())
365+
return sb.Append(method.MethodName).Append("Delegate");
366+
367+
// Use standard delegates if no reference parameters
332368
var isFunc = method.ReturnType is not null;
333369

334370
if (isFunc)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,8 @@ public static IReadOnlyList<DelegateMethodState> GetDelegateMethods(
705705
: methodSymbol.Parameters
706706
.Select(p => new DelegateMethodState.ParameterState(
707707
p.Name,
708-
p.Type.ToFullyQualifiedDisplayString()))
708+
p.Type.ToFullyQualifiedDisplayString(),
709+
p.RefKind))
709710
.ToList();
710711

711712
var methodState = new DelegateMethodState(methodSymbol.DeclaredAccessibility, methodName, returnType, parameters);

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

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,4 +841,178 @@ await VerifyAsync(outputs,
841841
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
842842
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
843843
}
844+
845+
[Fact]
846+
public async Task Should_generate_custom_delegate_with_ref_parameter()
847+
{
848+
var source = """
849+
using System;
850+
851+
namespace Thinktecture.Tests
852+
{
853+
[SmartEnum<string>]
854+
public abstract partial class TestEnum
855+
{
856+
public static readonly TestEnum Item1 = null!;
857+
public static readonly TestEnum Item2 = null!;
858+
859+
[UseDelegateFromConstructor]
860+
public partial void Method1(ref int value);
861+
}
862+
}
863+
""";
864+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
865+
866+
await VerifyAsync(outputs,
867+
"Thinktecture.Tests.TestEnum.g.cs",
868+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
869+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
870+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
871+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
872+
}
873+
874+
[Fact]
875+
public async Task Should_generate_custom_delegate_with_in_parameter()
876+
{
877+
var source = """
878+
using System;
879+
880+
namespace Thinktecture.Tests
881+
{
882+
[SmartEnum<string>]
883+
public abstract partial class TestEnum
884+
{
885+
public static readonly TestEnum Item1 = null!;
886+
public static readonly TestEnum Item2 = null!;
887+
888+
[UseDelegateFromConstructor]
889+
public partial string Method1(in int value);
890+
}
891+
}
892+
""";
893+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
894+
895+
await VerifyAsync(outputs,
896+
"Thinktecture.Tests.TestEnum.g.cs",
897+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
898+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
899+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
900+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
901+
}
902+
903+
[Fact]
904+
public async Task Should_generate_custom_delegate_with_ref_readonly_parameter()
905+
{
906+
var source = """
907+
using System;
908+
909+
namespace Thinktecture.Tests
910+
{
911+
[SmartEnum<string>]
912+
public abstract partial class TestEnum
913+
{
914+
public static readonly TestEnum Item1 = null!;
915+
public static readonly TestEnum Item2 = null!;
916+
917+
[UseDelegateFromConstructor]
918+
public partial void Method1(ref readonly int value);
919+
}
920+
}
921+
""";
922+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
923+
924+
await VerifyAsync(outputs,
925+
"Thinktecture.Tests.TestEnum.g.cs",
926+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
927+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
928+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
929+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
930+
}
931+
932+
[Fact]
933+
public async Task Should_generate_custom_delegate_with_out_parameter()
934+
{
935+
var source = """
936+
using System;
937+
938+
namespace Thinktecture.Tests
939+
{
940+
[SmartEnum<string>]
941+
public abstract partial class TestEnum
942+
{
943+
public static readonly TestEnum Item1 = null!;
944+
public static readonly TestEnum Item2 = null!;
945+
946+
[UseDelegateFromConstructor]
947+
public partial bool Method1(out int value);
948+
}
949+
}
950+
""";
951+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
952+
953+
await VerifyAsync(outputs,
954+
"Thinktecture.Tests.TestEnum.g.cs",
955+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
956+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
957+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
958+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
959+
}
960+
961+
[Fact]
962+
public async Task Should_generate_custom_delegate_with_mixed_ref_kinds()
963+
{
964+
var source = """
965+
using System;
966+
967+
namespace Thinktecture.Tests
968+
{
969+
[SmartEnum<string>]
970+
public abstract partial class TestEnum
971+
{
972+
public static readonly TestEnum Item1 = null!;
973+
public static readonly TestEnum Item2 = null!;
974+
975+
[UseDelegateFromConstructor]
976+
public partial string Method1(string normal, ref int refValue, in double inValue, out bool outValue, ref readonly decimal readonlyValue);
977+
}
978+
}
979+
""";
980+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
981+
982+
await VerifyAsync(outputs,
983+
"Thinktecture.Tests.TestEnum.g.cs",
984+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
985+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
986+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
987+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
988+
}
989+
990+
[Fact]
991+
public async Task Should_generate_custom_delegate_with_return_type_and_ref_parameters()
992+
{
993+
var source = """
994+
using System;
995+
996+
namespace Thinktecture.Tests
997+
{
998+
[SmartEnum<string>]
999+
public abstract partial class TestEnum
1000+
{
1001+
public static readonly TestEnum Item1 = null!;
1002+
public static readonly TestEnum Item2 = null!;
1003+
1004+
[UseDelegateFromConstructor]
1005+
public partial int Method1(ref string value);
1006+
}
1007+
}
1008+
""";
1009+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
1010+
1011+
await VerifyAsync(outputs,
1012+
"Thinktecture.Tests.TestEnum.g.cs",
1013+
"Thinktecture.Tests.TestEnum.Comparable.g.cs",
1014+
"Thinktecture.Tests.TestEnum.Parsable.g.cs",
1015+
"Thinktecture.Tests.TestEnum.ComparisonOperators.g.cs",
1016+
"Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs");
1017+
}
8441018
}

0 commit comments

Comments
 (0)