Skip to content

Commit fd880f5

Browse files
committed
add GeneratorOptions.TypeDeclarationOptions.EnableRecordTypes to improve generating record types
1 parent 56d98a8 commit fd880f5

File tree

6 files changed

+464
-18
lines changed

6 files changed

+464
-18
lines changed

src/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/Generator.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ GeneratorOptions options
5858
options ?? throw new ArgumentNullException(nameof(options))
5959
);
6060

61+
#pragma warning disable CA1502 // TODO: simplify and refactor
6162
private static IEnumerable<string> GenerateTypeDeclaration(
6263
Type t,
6364
bool generateExplicitBaseTypeAndInterfaces,
@@ -132,8 +133,9 @@ string GetSingleLineGenericParameterConstraintsDeclaration()
132133
else if (t.IsValueType) {
133134
var isReadOnly = t.IsReadOnlyValueType() ? "readonly " : string.Empty;
134135
var isByRefLike = t.IsByRefLikeValueType() ? "ref " : string.Empty;
136+
var isRecord = (options.TypeDeclaration.EnableRecordTypes && t.IsRecord()) ? "record " : string.Empty;
135137

136-
typeDeclaration = $"{modifierNew}{accessibilityList}{isReadOnly}{isByRefLike}struct {typeName}";
138+
typeDeclaration = $"{modifierNew}{accessibilityList}{isReadOnly}{isByRefLike}{isRecord}struct {typeName}";
137139
}
138140
else {
139141
string? modifier = null;
@@ -145,7 +147,9 @@ string GetSingleLineGenericParameterConstraintsDeclaration()
145147
else if (t.IsSealed)
146148
modifier = "sealed ";
147149

148-
typeDeclaration = $"{modifierNew}{accessibilityList}{modifier}class {typeName}";
150+
var isRecord = (options.TypeDeclaration.EnableRecordTypes && t.IsRecord()) ? "record " : string.Empty;
151+
152+
typeDeclaration = $"{modifierNew}{accessibilityList}{modifier}{isRecord}class {typeName}";
149153
}
150154

151155
if (!generateExplicitBaseTypeAndInterfaces) {
@@ -178,6 +182,7 @@ string GetSingleLineGenericParameterConstraintsDeclaration()
178182
}
179183
}
180184
}
185+
#pragma warning restore CA1502
181186

182187
[Obsolete($"Use {nameof(GenerateGenericParameterConstraintDeclaration)} instead.")]
183188
public static string GenerateGenericArgumentConstraintDeclaration(
@@ -294,9 +299,15 @@ GeneratorOptions options
294299
if (options == null)
295300
throw new ArgumentNullException(nameof(options));
296301

302+
var isRecord = options.TypeDeclaration.EnableRecordTypes && options.TypeDeclaration.OmitRecordImplicitInterface && t.IsRecord();
303+
var typeOfIEquatableOfRecord = isRecord
304+
? typeof(IEquatable<>).MakeGenericType(t) // IEquatable<TRecord>
305+
: null;
306+
297307
return t
298308
.GetExplicitBaseTypeAndInterfaces()
299309
.Where(type => !(options.IgnorePrivateOrAssembly && type.IsPrivateOrAssembly()))
310+
.Where(type => type != typeOfIEquatableOfRecord)
300311
.Select(type => {
301312
referencingNamespaces?.UnionWith(CSharpFormatter.ToNamespaceList(type));
302313
return new {

src/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating/GeneratorOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class TypeDeclarationOptions {
3535
public bool WithAccessibility { get; set; } = true;
3636
public bool OmitEndOfStatement { get; set; } = false;
3737
public bool OmitEnumUnderlyingTypeIfPossible { get; set; } = false;
38+
public bool EnableRecordTypes { get; set; } = false;
39+
public bool OmitRecordImplicitInterface { get; set; } = false;
3840
#if SYSTEM_REFLECTION_NULLABILITYINFOCONTEXT
3941
public NullabilityInfoContext? NullabilityInfoContext { get; set; } = new();
4042
public object? NullabilityInfoContextLockObject { get; set; }
Lines changed: 233 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,250 @@
11
// SPDX-FileCopyrightText: 2020 smdn <smdn@smdn.jp>
22
// SPDX-License-Identifier: MIT
3+
#if NET7_0_OR_GREATER
4+
#define SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
5+
#endif
6+
7+
using System;
8+
using System.Runtime.CompilerServices;
9+
310
namespace Smdn.Reflection.ReverseGenerating.GeneratorTestCases.TypeDeclaration.Records {
411
namespace RecordClasses {
5-
[SkipTestCase("`record` types are not supported currently")]
6-
[TypeDeclarationTestCase("public record RExpected(int X, string Y);")]
7-
public record RExpected(int X, string Y);
8-
9-
[TypeDeclarationTestCase("public class R0")]
12+
[TypeDeclarationTestCase("public record class R0", TypeEnableRecordTypes = true)]
13+
[TypeDeclarationTestCase("public class R0", TypeEnableRecordTypes = false)]
1014
public record R0(int X, string Y);
1115

12-
[TypeDeclarationTestCase("public class R1")]
16+
[TypeDeclarationTestCase("public record class R1", TypeEnableRecordTypes = true)]
17+
[TypeDeclarationTestCase("public class R1", TypeEnableRecordTypes = false)]
1318
public record class R1(int X, string Y);
19+
20+
[TypeDeclarationTestCase("public record class R2", TypeEnableRecordTypes = true)]
21+
[TypeDeclarationTestCase("public class R2", TypeEnableRecordTypes = false)]
22+
public record class R2 {
23+
#if SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
24+
public required int X { get; set; }
25+
public required string Y { get; set; }
26+
#else
27+
public int X { get; set; }
28+
public string Y { get; set; }
29+
#endif
30+
}
31+
32+
[TypeDeclarationTestCase("public record class REmpty", TypeEnableRecordTypes = true)]
33+
[TypeDeclarationTestCase("public class REmpty", TypeEnableRecordTypes = false)]
34+
public record class REmpty { }
35+
36+
namespace Mimicries {
37+
[TypeDeclarationTestCase("public record class MR0", TypeEnableRecordTypes = true)]
38+
[TypeDeclarationTestCase("public class MR0", TypeEnableRecordTypes = false)]
39+
public class MR0 {
40+
[CompilerGenerated] public override bool Equals(object obj) => throw new NotImplementedException();
41+
[CompilerGenerated] public static bool operator ==(MR0 left, MR0 right) => throw new NotImplementedException();
42+
[CompilerGenerated] public static bool operator !=(MR0 left, MR0 right) => throw new NotImplementedException();
43+
44+
public override int GetHashCode() => throw new NotImplementedException();
45+
}
46+
}
47+
48+
namespace ExtendedRecordClasses {
49+
[TypeDeclarationTestCase("public record class RX0", TypeEnableRecordTypes = true)]
50+
[TypeDeclarationTestCase("public class RX0", TypeEnableRecordTypes = false)]
51+
public record RX0(int X, string Y, double Z) : R0(X, Y);
52+
53+
namespace Mimicries {
54+
[TypeDeclarationTestCase("public record class MRX0", TypeEnableRecordTypes = true)]
55+
[TypeDeclarationTestCase("public class MRX0", TypeEnableRecordTypes = false)]
56+
public class MRX0 : RecordClasses.Mimicries.MR0 {
57+
[CompilerGenerated] public override bool Equals(object obj) => throw new NotImplementedException();
58+
[CompilerGenerated] public bool Equals(R0 obj) => throw new NotImplementedException();
59+
[CompilerGenerated] public static bool operator ==(MRX0 left, MRX0 right) => throw new NotImplementedException();
60+
[CompilerGenerated] public static bool operator !=(MRX0 left, MRX0 right) => throw new NotImplementedException();
61+
62+
public override int GetHashCode() => throw new NotImplementedException();
63+
}
64+
}
65+
}
1466
}
1567

16-
namespace RecordStructs {
17-
[SkipTestCase("`record` types are not supported currently")]
18-
[TypeDeclarationTestCase("public record struct RSExpected(int X, string Y);")]
19-
public record struct RSExpected(int X, string Y);
68+
namespace NonRecordClasses {
69+
[TypeDeclarationTestCase("public class NR0", TypeEnableRecordTypes = true)]
70+
[TypeDeclarationTestCase("public class NR0", TypeEnableRecordTypes = false)]
71+
public class NR0 {
72+
#if SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
73+
public required int X { get; init; }
74+
public required string Y { get; init; }
75+
#else
76+
public int X { get; init; }
77+
public string Y { get; init; }
78+
#endif
79+
80+
public NR0(int x, string y)
81+
{
82+
X = x;
83+
Y = y;
84+
}
85+
}
86+
87+
#pragma warning disable CS0659,CS0661
88+
[TypeDeclarationTestCase("public class NR1", TypeEnableRecordTypes = true)]
89+
[TypeDeclarationTestCase("public class NR1", TypeEnableRecordTypes = false)]
90+
public class NR1 {
91+
#if SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
92+
public required int X { get; init; }
93+
public required string Y { get; init; }
94+
#else
95+
public int X { get; init; }
96+
public string Y { get; init; }
97+
#endif
98+
99+
public NR1(int x, string y)
100+
{
101+
X = x;
102+
Y = y;
103+
}
104+
105+
public override bool Equals(object obj) => throw new NotImplementedException();
106+
}
20107

21-
[TypeDeclarationTestCase("public struct RS0")]
108+
#pragma warning restore CS0659,CS0661
109+
110+
#pragma warning disable CS0660,CS0661
111+
[TypeDeclarationTestCase("public class NR2", TypeEnableRecordTypes = true)]
112+
[TypeDeclarationTestCase("public class NR2", TypeEnableRecordTypes = false)]
113+
public class NR2 {
114+
#if SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
115+
public required int X { get; init; }
116+
public required string Y { get; init; }
117+
#else
118+
public int X { get; init; }
119+
public string Y { get; init; }
120+
#endif
121+
122+
public NR2(int x, string y)
123+
{
124+
X = x;
125+
Y = y;
126+
}
127+
128+
public static bool operator ==(NR2 left, NR2 right) => throw new NotImplementedException();
129+
public static bool operator !=(NR2 left, NR2 right) => throw new NotImplementedException();
130+
}
131+
#pragma warning restore CS0660,CS0661
132+
}
133+
134+
namespace RecordStructs {
135+
[TypeDeclarationTestCase("public record struct RS0", TypeEnableRecordTypes = true)]
136+
[TypeDeclarationTestCase("public struct RS0", TypeEnableRecordTypes = false)]
22137
public record struct RS0(int X, string Y);
138+
139+
[TypeDeclarationTestCase("public record struct RS1", TypeEnableRecordTypes = true)]
140+
[TypeDeclarationTestCase("public struct RS1", TypeEnableRecordTypes = false)]
141+
public record struct RS1 {
142+
#if SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
143+
public required int X { get; set; }
144+
public required string Y { get; set; }
145+
#else
146+
public int X { get; set; }
147+
public string Y { get; set; }
148+
#endif
149+
}
150+
151+
[TypeDeclarationTestCase("public record struct RSEmpty", TypeEnableRecordTypes = true)]
152+
[TypeDeclarationTestCase("public struct RSEmpty", TypeEnableRecordTypes = false)]
153+
public record struct RSEmpty { }
154+
155+
namespace Mimicries {
156+
[TypeDeclarationTestCase("public record struct MRS0", TypeEnableRecordTypes = true)]
157+
[TypeDeclarationTestCase("public struct MRS0", TypeEnableRecordTypes = false)]
158+
public struct MRS0 {
159+
[CompilerGenerated] public override bool Equals(object obj) => throw new NotImplementedException();
160+
[CompilerGenerated] public static bool operator ==(MRS0 left, MRS0 right) => throw new NotImplementedException();
161+
[CompilerGenerated] public static bool operator !=(MRS0 left, MRS0 right) => throw new NotImplementedException();
162+
163+
public override int GetHashCode() => throw new NotImplementedException();
164+
}
165+
}
23166
}
24167

25168
namespace ReadOnlyRecordStructs {
26-
[SkipTestCase("`record` types are not supported currently")]
27-
[TypeDeclarationTestCase("public readonly record struct RRSExpected(int X, string Y);")]
28-
public readonly record struct RRSExpected(int X, string Y);
29-
30-
[TypeDeclarationTestCase("public readonly struct RRS0")]
169+
[TypeDeclarationTestCase("public readonly record struct RRS0", TypeEnableRecordTypes = true)]
170+
[TypeDeclarationTestCase("public readonly struct RRS0", TypeEnableRecordTypes = false)]
31171
public readonly record struct RRS0(int X, string Y);
172+
173+
namespace Mimicries {
174+
[TypeDeclarationTestCase("public readonly record struct MRRS0", TypeEnableRecordTypes = true)]
175+
[TypeDeclarationTestCase("public readonly struct MRRS0", TypeEnableRecordTypes = false)]
176+
public readonly struct MRRS0 {
177+
[CompilerGenerated] public override bool Equals(object obj) => throw new NotImplementedException();
178+
[CompilerGenerated] public static bool operator ==(MRRS0 left, MRRS0 right) => throw new NotImplementedException();
179+
[CompilerGenerated] public static bool operator !=(MRRS0 left, MRRS0 right) => throw new NotImplementedException();
180+
181+
public override int GetHashCode() => throw new NotImplementedException();
182+
}
183+
}
184+
}
185+
186+
namespace NonRecordStructs {
187+
[TypeDeclarationTestCase("public struct NRS0", TypeEnableRecordTypes = true)]
188+
[TypeDeclarationTestCase("public struct NRS0", TypeEnableRecordTypes = false)]
189+
public struct NRS0 {
190+
#if SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
191+
public required int X { get; init; }
192+
public required string Y { get; init; }
193+
#else
194+
public int X { get; init; }
195+
public string Y { get; init; }
196+
#endif
197+
198+
public NRS0(int x, string y)
199+
{
200+
X = x;
201+
Y = y;
202+
}
203+
}
204+
205+
#pragma warning disable IDE0251,CS0659,CS0661
206+
[TypeDeclarationTestCase("public struct NRS1", TypeEnableRecordTypes = true)]
207+
[TypeDeclarationTestCase("public struct NRS1", TypeEnableRecordTypes = false)]
208+
public struct NRS1 {
209+
#if SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
210+
public required int X { get; init; }
211+
public required string Y { get; init; }
212+
#else
213+
public int X { get; init; }
214+
public string Y { get; init; }
215+
#endif
216+
217+
public NRS1(int x, string y)
218+
{
219+
X = x;
220+
Y = y;
221+
}
222+
223+
public override bool Equals(object obj) => throw new NotImplementedException();
224+
}
225+
#pragma warning restore IDE0251,CS0659,CS0661
226+
227+
#pragma warning disable CS0660,CS0661
228+
[TypeDeclarationTestCase("public struct NRS2", TypeEnableRecordTypes = true)]
229+
[TypeDeclarationTestCase("public struct NRS2", TypeEnableRecordTypes = false)]
230+
public struct NRS2 {
231+
#if SYSTEM_RUNTIME_COMPILERSERVICES_REQUIREDMEMBERATTRIBUTE
232+
public required int X { get; init; }
233+
public required string Y { get; init; }
234+
#else
235+
public int X { get; init; }
236+
public string Y { get; init; }
237+
#endif
238+
239+
public NRS2(int x, string y)
240+
{
241+
X = x;
242+
Y = y;
243+
}
244+
245+
public static bool operator ==(NRS2 left, NRS2 right) => throw new NotImplementedException();
246+
public static bool operator !=(NRS2 left, NRS2 right) => throw new NotImplementedException();
247+
}
248+
#pragma warning restore CS0660,CS0661
32249
}
33250
}

tests/Smdn.Reflection.ReverseGenerating/Smdn.Reflection.ReverseGenerating.GeneratorTestCases/TypeDeclarationWithExplicitBaseTypeAndInterfaces.GenericTypes.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,43 @@ public class C3<TKey, TValue> :
4040
{
4141
public object Clone() => throw new NotImplementedException();
4242
}
43+
44+
[TypeDeclarationWithExplicitBaseTypeAndInterfacesTestCase(
45+
"public record class R0<TValue> where TValue : struct",
46+
TypeWithNamespace = false,
47+
TypeEnableRecordTypes = true,
48+
TypeOmitRecordImplicitInterface = true
49+
)]
50+
[TypeDeclarationWithExplicitBaseTypeAndInterfacesTestCase(
51+
"public record class R0<TValue> : IEquatable<R0<TValue>> where TValue : struct",
52+
TypeWithNamespace = false,
53+
TypeEnableRecordTypes = true,
54+
TypeOmitRecordImplicitInterface = false
55+
)]
56+
[TypeDeclarationWithExplicitBaseTypeAndInterfacesTestCase(
57+
"public class R0<TValue> : IEquatable<R0<TValue>> where TValue : struct",
58+
TypeWithNamespace = false,
59+
TypeEnableRecordTypes = false,
60+
TypeOmitRecordImplicitInterface = true
61+
)]
62+
public record R0<TValue>(TValue Value) where TValue : struct;
63+
64+
[TypeDeclarationWithExplicitBaseTypeAndInterfacesTestCase(
65+
"public record struct RS0<TValue> where TValue : struct",
66+
TypeWithNamespace = false,
67+
TypeEnableRecordTypes = true,
68+
TypeOmitRecordImplicitInterface = true
69+
)]
70+
[TypeDeclarationWithExplicitBaseTypeAndInterfacesTestCase(
71+
"public record struct RS0<TValue> : IEquatable<RS0<TValue>> where TValue : struct",
72+
TypeWithNamespace = false,
73+
TypeEnableRecordTypes = true,
74+
TypeOmitRecordImplicitInterface = false
75+
)]
76+
[TypeDeclarationWithExplicitBaseTypeAndInterfacesTestCase(
77+
"public struct RS0<TValue> : IEquatable<RS0<TValue>> where TValue : struct",
78+
TypeWithNamespace = false,
79+
TypeEnableRecordTypes = false,
80+
TypeOmitRecordImplicitInterface = true
81+
)]
82+
public record struct RS0<TValue>(TValue Value) where TValue : struct;

0 commit comments

Comments
 (0)