Skip to content

Commit 10c762e

Browse files
committed
Generate equality for array properties
1 parent 967f251 commit 10c762e

11 files changed

+388
-11
lines changed

Diesel.sln

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1010
CHANGELOG.md = CHANGELOG.md
1111
LICENSE.txt = LICENSE.txt
1212
README.md = README.md
13+
TODO.md = TODO.md
1314
EndProjectSection
1415
EndProject
1516
Global

Diesel/CodeGeneration/CodeDomGenerator.cs

+3-6
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,7 @@ private static CodeTypeMember[] CreateEqualsOverloadingUsingEqualityOperator(str
195195
};
196196

197197
var compareExpressions = from property in properties
198-
select new CodeBinaryOperatorExpression(
199-
new CodePropertyReferenceExpression(
200-
new CodeThisReferenceExpression(), property.Name),
201-
CodeBinaryOperatorType.ValueEquality,
202-
new CodePropertyReferenceExpression(
203-
new CodeVariableReferenceExpression("other"), property.Name));
198+
select EqualityMethodsGenerator.ComparePropertyValueEqualityExpression(property, "other");
204199

205200
var nullGuardSeed = isValueType
206201
? (CodeExpression) new CodePrimitiveExpression(true)
@@ -231,6 +226,8 @@ private static CodeTypeMember[] CreateEqualsOverloadingUsingEqualityOperator(str
231226
return new CodeTypeMember[] { equalsTyped, equalsObject };
232227
}
233228

229+
230+
234231
private static CodeMethodInvokeExpression CreateTypeIsAssignableFrom(string typeName, CodeExpression instanceExpression)
235232
{
236233
// typeof(typeName).IsAssignableFrom(instanceExpression.GetType())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System;
2+
using System.CodeDom;
3+
using System.Linq;
4+
using Diesel.Parsing;
5+
using Diesel.Parsing.CSharp;
6+
7+
namespace Diesel.CodeGeneration
8+
{
9+
public class EqualityMethodsGenerator
10+
{
11+
/// <summary>
12+
/// Produce an expression that compares two properties by value.
13+
/// </summary>
14+
public static CodeExpression ComparePropertyValueEqualityExpression(PropertyDeclaration property, string otherVariableName)
15+
{
16+
return ComparePropertyValueEqualityExpression((dynamic)property.Type, property.Name, otherVariableName);
17+
}
18+
19+
private static CodeBinaryOperatorExpression ComparePropertyValueEqualityExpression(SimpleType propertyType, String propertyName, String otherVariableName)
20+
{
21+
return CompareValueEquality(propertyName, otherVariableName);
22+
}
23+
24+
private static CodeBinaryOperatorExpression ComparePropertyValueEqualityExpression(StringReferenceType propertyType, String propertyName, String otherVariableName)
25+
{
26+
return CompareValueEquality(propertyName, otherVariableName);
27+
}
28+
29+
/// <summary>
30+
/// this.PropertyName == other.PropertyName
31+
/// </summary>
32+
private static CodeBinaryOperatorExpression CompareValueEquality(string propertyName, string otherVariableName)
33+
{
34+
return new CodeBinaryOperatorExpression(
35+
ThisPropertyReference(propertyName),
36+
CodeBinaryOperatorType.ValueEquality,
37+
OtherPropertyReference(propertyName, otherVariableName));
38+
}
39+
40+
/// <summary>
41+
/// Object.Equals(a.Property, b.Property)
42+
/// </summary>
43+
private static CodeExpression CompareObjectEquality(string propertyName, string otherVariableName)
44+
{
45+
return new CodeMethodInvokeExpression(
46+
new CodeTypeReferenceExpression(typeof (Object)),
47+
"Equals",
48+
ThisPropertyReference(propertyName),
49+
OtherPropertyReference(propertyName, otherVariableName));
50+
}
51+
52+
private static CodeBinaryOperatorExpression ComparePropertyValueEqualityExpression(NullableType propertyType, String propertyName, String otherVariableName)
53+
{
54+
return CompareValueEquality(propertyName, otherVariableName);
55+
}
56+
57+
/// <summary>
58+
/// (this.Property.Length == other.Property.Length)
59+
/// && Enumerable.Zip(a, b, (a, b) => a == b).All(areEqual => areEqual);
60+
/// </summary>
61+
private static CodeBinaryOperatorExpression ComparePropertyValueEqualityExpression(ArrayType propertyType,
62+
String propertyName,
63+
String otherVariableName)
64+
{
65+
// TODO: this should probably be a warning in the model
66+
if (propertyType.RankSpecifiers.Ranks.Count() > 1)
67+
throw new InvalidOperationException(
68+
"Cannot generate equality for Array Types with more than one rank-specifier.");
69+
var rankSpecifier = propertyType.RankSpecifiers.Ranks.Single();
70+
if (rankSpecifier.Dimensions > 1)
71+
throw new InvalidOperationException(
72+
"Cannot generate equality for Array Type with more than one dimension");
73+
74+
// (this.Property.Length == other.Property.Length)
75+
// && Enumerable.Zip(a, b, (a, b) => Object.Equals(a, b)).All(areEqual => areEqual);
76+
77+
var thisPropertyReference = ThisPropertyReference(propertyName);
78+
var otherPropertyReference = OtherPropertyReference(propertyName, otherVariableName);
79+
80+
var sameArrayLength = new CodeBinaryOperatorExpression(
81+
new CodePropertyReferenceExpression(thisPropertyReference, "Length"),
82+
CodeBinaryOperatorType.ValueEquality,
83+
new CodePropertyReferenceExpression(otherPropertyReference, "Length"));
84+
85+
var zipExpression = new CodeMethodInvokeExpression(
86+
new CodeMethodReferenceExpression(
87+
new CodeTypeReferenceExpression(typeof (System.Linq.Enumerable)),
88+
"Zip"),
89+
thisPropertyReference,
90+
otherPropertyReference,
91+
new CodeSnippetExpression("(a, b) => Object.Equals(a,b)"));
92+
93+
var zipPairwiseEquality =
94+
new CodeMethodInvokeExpression(
95+
new CodeMethodReferenceExpression(
96+
new CodeTypeReferenceExpression(typeof (System.Linq.Enumerable)),
97+
"All"),
98+
zipExpression,
99+
new CodeSnippetExpression("areEqual => areEqual")
100+
);
101+
102+
return new CodeBinaryOperatorExpression(sameArrayLength,
103+
CodeBinaryOperatorType.BooleanAnd, zipPairwiseEquality);
104+
}
105+
106+
private static CodePropertyReferenceExpression OtherPropertyReference(string propertyName, string otherVariableName)
107+
{
108+
return new CodePropertyReferenceExpression(new CodeVariableReferenceExpression(otherVariableName), propertyName);
109+
}
110+
111+
private static CodePropertyReferenceExpression ThisPropertyReference(string propertyName)
112+
{
113+
return new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), propertyName);
114+
}
115+
116+
private static CodeExpression ComparePropertyValueEqualityExpression(TypeNameTypeNode propertyType, String propertyName, String otherVariableName)
117+
{
118+
return CompareObjectEquality(propertyName, otherVariableName);
119+
}
120+
}
121+
}

Diesel/Diesel.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<Compile Include="CodeGeneration\ApplicationServiceGenerator.cs" />
4747
<Compile Include="CodeGeneration\CommandGenerator.cs" />
4848
<Compile Include="CodeGeneration\DomainEventGenerator.cs" />
49+
<Compile Include="CodeGeneration\EqualityMethodsGenerator.cs" />
4950
<Compile Include="CodeGeneration\ReadOnlyProperty.cs" />
5051
<Compile Include="CodeGeneration\SystemTypeMapper.cs" />
5152
<Compile Include="CodeGeneration\ValueTypeGenerator.cs" />

TODO.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Feature Requests
2+
3+
## Emit verbatim type-name for unknown types
4+
When a named type does not exist, emit the type name as specified
5+
rather than failing in the generation step when the corresponding System type
6+
has not been found (`CodeDomGenerator`, `SystemTypeMapper`).
7+
8+
## Validate model before generation
9+
10+
### Validate allowed types in DTO types (Commands, Events).
11+
Commands and Domain events should only be able to have properties that are primitive types,
12+
arrays of these or other Data Transfer Objects.
13+
14+
### Emit friendly error when code is not generatable
15+
E.g. for multi-dimensional arrays in Equality.
16+
17+
18+
## Multi-dimensional array equality
19+
Currently one single-dimensional arrays are supported when generating equality members.
20+
Implement these, too:
21+
22+
(namespace TestCases.TypeDeclarations
23+
(defcommand PrintArraySimple2D (int[,] Value))
24+
(defcommand PrintArraySimpleMulti (int[][,] Value))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Diesel.CodeGeneration;
2+
using Diesel.Parsing;
3+
using Diesel.Parsing.CSharp;
4+
using NUnit.Framework;
5+
6+
namespace Test.Diesel.CodeGeneration
7+
{
8+
[TestFixture]
9+
public class EqualityMethodsGeneratorTest
10+
{
11+
12+
[Test]
13+
public void ComparePropertyValueEqualityExpression_StringReferenceType_ShouldGenerateExpression()
14+
{
15+
var actual = EqualityMethodsGenerator.ComparePropertyValueEqualityExpression(
16+
new PropertyDeclaration("Text", new StringReferenceType()), "other");
17+
Assert.That(actual, Is.Not.Null);
18+
}
19+
20+
[Test]
21+
public void ComparePropertyValueEqualityExpression_SimpleType_ShouldGenerateExpression()
22+
{
23+
var actual = EqualityMethodsGenerator.ComparePropertyValueEqualityExpression(
24+
new PropertyDeclaration("Value", new SimpleType(typeof(decimal))), "other");
25+
Assert.That(actual, Is.Not.Null);
26+
}
27+
28+
[Test]
29+
public void ComparePropertyValueEqualityExpression_NullableSimpleType_ShouldGenerateExpression()
30+
{
31+
var actual = EqualityMethodsGenerator.ComparePropertyValueEqualityExpression(
32+
new PropertyDeclaration("Value", new NullableType(new SimpleType(typeof(decimal)))), "other");
33+
Assert.That(actual, Is.Not.Null);
34+
}
35+
36+
[Test]
37+
public void ComparePropertyValueEqualityExpression_NamedDateTime_ShouldGenerateExpression()
38+
{
39+
var actual = EqualityMethodsGenerator.ComparePropertyValueEqualityExpression(
40+
new PropertyDeclaration("Value", new TypeNameTypeNode(new TypeName("System.DateTime"))), "other");
41+
Assert.That(actual, Is.Not.Null);
42+
}
43+
44+
[Test]
45+
public void ComparePropertyValueEqualityExpression_ArrayType1D_ShouldGenerateExpression()
46+
{
47+
var actual = EqualityMethodsGenerator.ComparePropertyValueEqualityExpression(
48+
new PropertyDeclaration("Value",
49+
new ArrayType(new SimpleType(typeof(decimal)),
50+
new RankSpecifiers(new[] {new RankSpecifier(1)}))),
51+
"other");
52+
Assert.That(actual, Is.Not.Null);
53+
}
54+
55+
56+
}
57+
}

Test/Examples/DieselCompilerIntegrationTestCase.txt

+1-3
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
(defcommand PrintInt (int Value))
1414
(defcommand PrintNullable (float? Value))
1515
(defcommand PrintArraySimple (int[] Value))
16-
(defcommand PrintArraySimple2D (int[,] Value))
17-
(defcommand PrintArraySimpleMulti (int[][,] Value))
1816
(defcommand PrintNullableSimple (decimal? Value))
1917
(defcommand PrintString (string Value))
2018
(defcommand PrintNamedTypeQualifiedDateTime (System.DateTime Value))
2119
(defcommand PrintNamedTypeQualifiedGuid (System.Guid Value))
2220
(defcommand PrintNamedTypeUnqualifiedGuid (Guid Value))
2321
(defcommand PrintNamedTypeUnqualifiedDecimal (Decimal Value))
24-
(defcommand PrintMulti (string Text, int? NullableNumber, int[,] TwoDimArray, decimal Decimal, DateTime When)))
22+
(defcommand PrintMulti (string Text, int? NullableNumber, string[] SingleDimArray, decimal Decimal, DateTime When)))
2523

2624

2725
(namespace TestCases.Defvaluetype

Test/Generated/Example.dsl

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
(defvaluetype EmailAddress string)
66
(defvaluetype EmployeeName (string FirstName, string LastName))
77
(defvaluetype EmployeeMetadata (string Source, int? SourceId))
8+
(defvaluetype EmployeeRatings (int EmployeeNumber, int[] Ratings))
9+
(defvaluetype EmployeeRoles (int EmployeeNumber, string[] Roles))
810
(defdomainevent EmployeeImported (Guid Id, int EmployeeNumber, string FirstName, string LastName, int? SourceId))
911
(defapplicationservice ImportService
1012
(defcommand ImportEmployee (Guid CommandId, int EmployeeNumber, string FirstName, string LastName, int? SourceId))

Test/Generated/GenerateExamples.cs

+104-2
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,108 @@ public override bool Equals(object obj) {
194194
}
195195
}
196196

197+
[System.SerializableAttribute()]
198+
public partial struct EmployeeRatings : System.IEquatable<EmployeeRatings> {
199+
200+
private int _employeeNumber;
201+
202+
private int[] _ratings;
203+
204+
public EmployeeRatings(int employeeNumber, int[] ratings) {
205+
this._employeeNumber = employeeNumber;
206+
this._ratings = ratings;
207+
}
208+
209+
public int EmployeeNumber {
210+
get {
211+
return this._employeeNumber;
212+
}
213+
}
214+
215+
public int[] Ratings {
216+
get {
217+
return this._ratings;
218+
}
219+
}
220+
221+
public static bool operator ==(EmployeeRatings left, EmployeeRatings right) {
222+
return left.Equals(right);
223+
}
224+
225+
public static bool operator !=(EmployeeRatings left, EmployeeRatings right) {
226+
return (false == left.Equals(right));
227+
}
228+
229+
public override int GetHashCode() {
230+
return (0 + this.EmployeeNumber.GetHashCode());
231+
}
232+
233+
public bool Equals(EmployeeRatings other) {
234+
return ((true
235+
&& (this.EmployeeNumber == other.EmployeeNumber))
236+
&& ((this.Ratings.Length == other.Ratings.Length)
237+
&& System.Linq.Enumerable.All(System.Linq.Enumerable.Zip(this.Ratings, other.Ratings, (a, b) => Object.Equals(a,b)), areEqual => areEqual)));
238+
}
239+
240+
public override bool Equals(object obj) {
241+
if (object.ReferenceEquals(null, obj)) {
242+
return false;
243+
}
244+
return (typeof(EmployeeRatings).IsAssignableFrom(obj.GetType()) && this.Equals(((EmployeeRatings)(obj))));
245+
}
246+
}
247+
248+
[System.SerializableAttribute()]
249+
public partial struct EmployeeRoles : System.IEquatable<EmployeeRoles> {
250+
251+
private int _employeeNumber;
252+
253+
private string[] _roles;
254+
255+
public EmployeeRoles(int employeeNumber, string[] roles) {
256+
this._employeeNumber = employeeNumber;
257+
this._roles = roles;
258+
}
259+
260+
public int EmployeeNumber {
261+
get {
262+
return this._employeeNumber;
263+
}
264+
}
265+
266+
public string[] Roles {
267+
get {
268+
return this._roles;
269+
}
270+
}
271+
272+
public static bool operator ==(EmployeeRoles left, EmployeeRoles right) {
273+
return left.Equals(right);
274+
}
275+
276+
public static bool operator !=(EmployeeRoles left, EmployeeRoles right) {
277+
return (false == left.Equals(right));
278+
}
279+
280+
public override int GetHashCode() {
281+
return (0 + this.EmployeeNumber.GetHashCode());
282+
}
283+
284+
public bool Equals(EmployeeRoles other) {
285+
return ((true
286+
&& (this.EmployeeNumber == other.EmployeeNumber))
287+
&& ((this.Roles.Length == other.Roles.Length)
288+
&& System.Linq.Enumerable.All(System.Linq.Enumerable.Zip(this.Roles, other.Roles, (a, b) => Object.Equals(a,b)), areEqual => areEqual)));
289+
}
290+
291+
public override bool Equals(object obj) {
292+
if (object.ReferenceEquals(null, obj)) {
293+
return false;
294+
}
295+
return (typeof(EmployeeRoles).IsAssignableFrom(obj.GetType()) && this.Equals(((EmployeeRoles)(obj))));
296+
}
297+
}
298+
197299
[System.Runtime.Serialization.DataContractAttribute(Name="EmployeeImported")]
198300
[System.SerializableAttribute()]
199301
public sealed partial class EmployeeImported : System.IEquatable<EmployeeImported>, Test.Diesel.IDomainEvent {
@@ -273,7 +375,7 @@ public override int GetHashCode() {
273375

274376
public bool Equals(EmployeeImported other) {
275377
return ((((((false == object.ReferenceEquals(null, other))
276-
&& (this.Id == other.Id))
378+
&& object.Equals(this.Id, other.Id))
277379
&& (this.EmployeeNumber == other.EmployeeNumber))
278380
&& (this.FirstName == other.FirstName))
279381
&& (this.LastName == other.LastName))
@@ -374,7 +476,7 @@ public override int GetHashCode() {
374476

375477
public bool Equals(ImportEmployee other) {
376478
return ((((((false == object.ReferenceEquals(null, other))
377-
&& (this.CommandId == other.CommandId))
479+
&& object.Equals(this.CommandId, other.CommandId))
378480
&& (this.EmployeeNumber == other.EmployeeNumber))
379481
&& (this.FirstName == other.FirstName))
380482
&& (this.LastName == other.LastName))

0 commit comments

Comments
 (0)