Skip to content

Commit

Permalink
implements operators == and != (#287)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianoc committed Jun 6, 2024
1 parent c5245ea commit 170f781
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 2 deletions.
37 changes: 35 additions & 2 deletions Cecilifier.Core.Tests/Tests/OutputBased/RecordTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,42 @@ record Base(string Name);
record Derived(int Value, string Name) : Base(Name);
""",
"True/True/False/True");
}
}
}

[TestCase("==", "True/True/False", TestName = "Equality")]
[TestCase("!=", "False/False/True", TestName = "Inequality")]
public void EqualityOperator_WhenInheritingFromObject_Works(string operatorToTest, string expectedResult)
{
AssertOutput($$"""
var r1 = new Record(42, "Foo");
var r2 = new Record(42, "Foo");
var r3 = new Record(1, "Bar");
System.Console.WriteLine($"{r1 {{operatorToTest}} r1}/{r1 {{operatorToTest}} r2}/{r1 {{operatorToTest}} r3}");
record Record(int Value, string Name);
""",
expectedResult);
}

[Test]
public void EqualityOperator_WhenInheritingFromRecord_Works()
{
AssertOutput("""
var r1 = new Derived(42, "Foo");
var r2 = new Derived(42, "Foo");
var r3 = new Derived(1, "Bar");
Base r1AsBase = r1;
System.Console.WriteLine($"{r1 == r1AsBase}");
record Base(string Name);
record Derived(int Value, string Name) : Base(Name);
""",
"True");
}
}

[Test]
public void Constructor_WhenInheritFromRecordWithProperties_CorrectArgumentsArePassed()
{
Expand Down
90 changes: 90 additions & 0 deletions Cecilifier.Core/CodeGeneration/Record.Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,96 @@ internal void AddSyntheticMembers(IVisitorContext context, string recordTypeDefi
AddToStringAndRelatedMethods(context, recordTypeDefinitionVariable, record);
AddGetHashCodeMethod(context, recordTypeDefinitionVariable, record);
AddEqualsOverloads(context, recordTypeDefinitionVariable, record);
AddEqualityOperator(context, recordTypeDefinitionVariable, record);
AddInequalityOperator(context, recordTypeDefinitionVariable, record);
}

private void AddEqualityOperator(IVisitorContext context, string recordTypeDefinitionVariable, TypeDeclarationSyntax record)
{
var recordSymbol = context.SemanticModel.GetDeclaredSymbol(record).EnsureNotNull<ISymbol, ITypeSymbol>();
const string methodName = "op_Equality";

context.WriteNewLine();
context.WriteComment("operator ==");
var equalsOperatorMethodVar = context.Naming.SyntheticVariable($"equalsOperator", ElementKind.Method);
var equalsOperatorMethodExps = CecilDefinitionsFactory.Method(
context,
recordSymbol.Name,
equalsOperatorMethodVar,
methodName,
methodName,
Constants.Cecil.PublicOverrideOperatorAttributes,
[
new ParameterSpec("left", context.TypeResolver.Resolve(recordSymbol), RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = $"{record.Identifier.ValueText}?" },
new ParameterSpec("right", context.TypeResolver.Resolve(recordSymbol), RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = $"{record.Identifier.ValueText}?" }
],
[],
ctx => ctx.TypeResolver.Bcl.System.Boolean,
out _);

context.WriteCecilExpressions(equalsOperatorMethodExps);

InstructionRepresentation[] equalsBodyInstructions =
[
OpCodes.Ldarg_0,
OpCodes.Ldarg_1,
OpCodes.Beq_S.WithBranchOperand("Equal"),
OpCodes.Ldarg_0,
OpCodes.Brfalse_S.WithBranchOperand("NotEqual"),
OpCodes.Ldarg_0,
OpCodes.Ldarg_1,
OpCodes.Callvirt.WithOperand(_recordTypeEqualsOverloadMethodVar),
OpCodes.Ret,
OpCodes.Ldc_I4_0.WithInstructionMarker("NotEqual"),
OpCodes.Ret,
OpCodes.Ldc_I4_1.WithInstructionMarker("Equal"),
OpCodes.Ret
];

var bodyExps = CecilDefinitionsFactory.MethodBody(context.Naming, methodName, equalsOperatorMethodVar, [], equalsBodyInstructions);
context.WriteCecilExpressions(bodyExps);
context.WriteCecilExpression($"{recordTypeDefinitionVariable}.Methods.Add({equalsOperatorMethodVar});");
}

private void AddInequalityOperator(IVisitorContext context, string recordTypeDefinitionVariable, TypeDeclarationSyntax record)
{
var recordSymbol = context.SemanticModel.GetDeclaredSymbol(record).EnsureNotNull<ISymbol, ITypeSymbol>();
const string methodName = "op_Inequality";

context.WriteNewLine();
context.WriteComment("operator ==");
var inequalityOperatorMethodVar = context.Naming.SyntheticVariable($"inequalityOperator", ElementKind.Method);
var inequalityOperatorMethodExps = CecilDefinitionsFactory.Method(
context,
recordSymbol.Name,
inequalityOperatorMethodVar,
methodName,
methodName,
Constants.Cecil.PublicOverrideOperatorAttributes,
[
new ParameterSpec("left", context.TypeResolver.Resolve(recordSymbol), RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = $"{record.Identifier.ValueText}?" },
new ParameterSpec("right", context.TypeResolver.Resolve(recordSymbol), RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = $"{record.Identifier.ValueText}?" }
],
[],
ctx => ctx.TypeResolver.Bcl.System.Boolean,
out _);

context.WriteCecilExpressions(inequalityOperatorMethodExps);

var equalityMethodDefinitionVariable = context.DefinitionVariables.GetMethodVariable(new MethodDefinitionVariable(recordSymbol.Name, "op_Equality", [$"{record.Identifier.ValueText}?", $"{record.Identifier.ValueText}?"], 0));
InstructionRepresentation[] inequalityBodyInstructions =
[
OpCodes.Ldarg_0,
OpCodes.Ldarg_1,
OpCodes.Call.WithOperand(equalityMethodDefinitionVariable.VariableName),
OpCodes.Ldc_I4_0,
OpCodes.Ceq,
OpCodes.Ret
];

var bodyExps = CecilDefinitionsFactory.MethodBody(context.Naming, methodName, inequalityOperatorMethodVar, [], inequalityBodyInstructions);
context.WriteCecilExpressions(bodyExps);
context.WriteCecilExpression($"{recordTypeDefinitionVariable}.Methods.Add({inequalityOperatorMethodVar});");
}

private void InitializeEqualityComparerMemberCache(IVisitorContext context, TypeDeclarationSyntax record)
Expand Down
1 change: 1 addition & 0 deletions Cecilifier.Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public struct Cecil
public const string HideBySigNewSlotVirtual = "MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual";
public const string DelegateMethodAttributes = $"MethodAttributes.Public | {HideBySigNewSlotVirtual}";
public const string PublicOverrideMethodAttributes = $"MethodAttributes.Public | {HideBySigVirtual}";
public const string PublicOverrideOperatorAttributes = $"MethodAttributes.Public | MethodAttributes.HideBySig | {MethodAttributesSpecialName} | {MethodAttributesStatic}";

public const string CtorAttributes = "MethodAttributes.RTSpecialName | MethodAttributes.SpecialName";
public const string InstanceConstructorName = "ctor";
Expand Down

0 comments on commit 170f781

Please sign in to comment.