Skip to content

Commit

Permalink
adds 'CompilerGeneratedAttribute' to relevant members of records (#290)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianoc committed Jun 7, 2024
1 parent 67edeaf commit ad988e6
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 29 deletions.
34 changes: 17 additions & 17 deletions Cecilifier.Core.Tests/Tests/Unit/PropertyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ public void TestGetterOnlyInitialization_Simple()
var result = RunCecilifier("class C { public int Value { get; } public C() => Value = 42; }");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Contains.Substring(
@"il_ctor_6.Emit(OpCodes.Ldarg_0);
il_ctor_6.Emit(OpCodes.Ldc_I4, 42);
il_ctor_6.Emit(OpCodes.Stfld, fld_value_4);"));
@"il_ctor_8.Emit(OpCodes.Ldarg_0);
il_ctor_8.Emit(OpCodes.Ldc_I4, 42);
il_ctor_8.Emit(OpCodes.Stfld, fld_value_4);"));
}

[Test]
Expand All @@ -23,11 +23,11 @@ public void TestGetterOnlyInitialization_Complex()
var result = RunCecilifier("class C { public int Value { get; } public C(int n) => Value = n * 2; }");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Contains.Substring(
@"il_ctor_6.Emit(OpCodes.Ldarg_0);
il_ctor_6.Emit(OpCodes.Ldarg_1);
il_ctor_6.Emit(OpCodes.Ldc_I4, 2);
il_ctor_6.Emit(OpCodes.Mul);
il_ctor_6.Emit(OpCodes.Stfld, fld_value_4);"));
@"il_ctor_8.Emit(OpCodes.Ldarg_0);
il_ctor_8.Emit(OpCodes.Ldarg_1);
il_ctor_8.Emit(OpCodes.Ldc_I4, 2);
il_ctor_8.Emit(OpCodes.Mul);
il_ctor_8.Emit(OpCodes.Stfld, fld_value_4);"));
}

[Test]
Expand All @@ -49,14 +49,14 @@ public void TestPropertyInitializers()
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Match(
@"//int Value1 { get; } = 42;\s+" +
@"(il_ctor_C_13\.Emit\(OpCodes\.)Ldarg_0\);\s+" +
@"(il_ctor_C_17\.Emit\(OpCodes\.)Ldarg_0\);\s+" +
@"\1Ldc_I4, 42\);\s+" +
@"\1Stfld, fld_value1_4\);\s+" +
@"//int Value2 { get; } = M\(21\);\s+" +
@"(il_ctor_C_13\.Emit\(OpCodes\.)Ldarg_0\);\s+" +
@"(il_ctor_C_17\.Emit\(OpCodes\.)Ldarg_0\);\s+" +
@"\1Ldc_I4, 21\);\s+" +
@"\1Call, m_M_9\);\s+" +
@"\1Stfld, fld_value2_8\);"));
@"\1Call, m_M_13\);\s+" +
@"\1Stfld, fld_value2_10\);"));
}

[Test]
Expand All @@ -66,11 +66,11 @@ public void TestSystemIndexPropertyInitializers()
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Match(
@"//Index Value1 { get; } = \^42;\s+" +
@"il_ctor_C_6.Emit\(OpCodes.Ldarg_0\);\s+" +
@"il_ctor_C_6.Emit\(OpCodes.Ldc_I4, 42\);\s+" +
@"il_ctor_C_6.Emit\(OpCodes.Ldc_I4_1\);\s+" +
@"il_ctor_C_6.Emit\(OpCodes.Newobj,.+typeof\(System.Index\), ""\.ctor"",.+""System.Int32"", ""System.Boolean"".+\);\s+" +
@"il_ctor_C_6.Emit\(OpCodes.Stfld, fld_value1_4\);"));
@"il_ctor_C_8.Emit\(OpCodes.Ldarg_0\);\s+" +
@"il_ctor_C_8.Emit\(OpCodes.Ldc_I4, 42\);\s+" +
@"il_ctor_C_8.Emit\(OpCodes.Ldc_I4_1\);\s+" +
@"il_ctor_C_8.Emit\(OpCodes.Newobj,.+typeof\(System.Index\), ""\.ctor"",.+""System.Int32"", ""System.Boolean"".+\);\s+" +
@"il_ctor_C_8.Emit\(OpCodes.Stfld, fld_value1_4\);"));
}

[TestCase("get { int l = 42; return l; }", "m_get_2", TestName = "Getter")]
Expand Down
20 changes: 20 additions & 0 deletions Cecilifier.Core.Tests/Tests/Unit/TypeTests.Records.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,26 @@ public void InheritingFromRecord_BaseConstructor_IsInvoked()
"""));
}

[Test]
public void Members_HaveCompilerGeneratedAttribute_Added()
{
var result = RunCecilifier("public record Record(int Value);");

var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Match( """m_deconstruct_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "Deconstruct() method.");
Assert.That(cecilifiedCode, Does.Match( """prop_equalityContract_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "EqualityContract");
Assert.That(cecilifiedCode, Does.Match( """m_equals_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "Equals()");
Assert.That(cecilifiedCode, Does.Match( """m_printMembers_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "PrintMembers()");
Assert.That(cecilifiedCode, Does.Match( """m_toString_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "ToString()");
Assert.That(cecilifiedCode, Does.Match( """m_getHashCode_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "GetHashCode()");
Assert.That(cecilifiedCode, Does.Match( """m_equalsObjectOverload_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "Equals(object)");
Assert.That(cecilifiedCode, Does.Match( """m_equalsOperator_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "Operator ==");
Assert.That(cecilifiedCode, Does.Match( """m_inequalityOperator_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "Operator !=");
Assert.That(cecilifiedCode, Does.Match( """m_getValue_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "Value property getter");
Assert.That(cecilifiedCode, Does.Match( """m_setValue_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "Value property setter");
Assert.That(cecilifiedCode, Does.Match( """fld_value_\d+.CustomAttributes.Add\(attr_compilerGenerated_\d+\);"""), "Value property setter");
}

private static void AssertPropertiesFromPrimaryConstructor(string[] expectedNameTypePairs, string cecilifiedCode)
{
Span<Range> ranges = stackalloc Range[2];
Expand Down
13 changes: 7 additions & 6 deletions Cecilifier.Core/AST/PropertyDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ void AddSetterMethod(IPropertySymbol property, AccessorDeclarationSyntax accesso

if (accessor.Body == null && accessor.ExpressionBody == null) //is this an auto property ?
{
generator.AddAutoSetterMethodImplementation(in propertyGenerationData, ilSetVar);
generator.AddAutoSetterMethodImplementation(in propertyGenerationData, ilSetVar, setMethodVar);
}
else if (accessor.Body != null)
{
Expand All @@ -206,10 +206,9 @@ void AddSetterMethod(IPropertySymbol property, AccessorDeclarationSyntax accesso
Context.EmitCilInstruction(ilSetVar, OpCodes.Ret);
}

ScopedDefinitionVariable AddGetterMethodGuts(out string? ilVar)
ScopedDefinitionVariable AddGetterMethodGuts(string getMethodVar, out string? ilVar)
{
Context.WriteComment("Getter");
var getMethodVar = Context.Naming.SyntheticVariable("get", ElementKind.Method);
var methodVariableScope = generator.AddGetterMethodDeclaration(
in propertyGenerationData,
getMethodVar,
Expand All @@ -232,20 +231,22 @@ ScopedDefinitionVariable AddGetterMethodGuts(out string? ilVar)

void AddExpressionBodiedGetterMethod()
{
using var getterMethodScope = AddGetterMethodGuts(out var ilVar);
var getMethodVar = Context.Naming.SyntheticVariable("get", ElementKind.Method);
using var getterMethodScope = AddGetterMethodGuts(getMethodVar, out var ilVar);
Debug.Assert(ilVar != null);
ProcessExpressionBodiedGetter(ilVar, arrowExpression);
}

void AddGetterMethod(AccessorDeclarationSyntax accessor)
{
using var getterMethodScope = AddGetterMethodGuts(out var ilVar);
var getMethodVar = Context.Naming.SyntheticVariable("get", ElementKind.Method);
using var getterMethodScope = AddGetterMethodGuts(getMethodVar, out var ilVar);
if (ilVar == null)
return;

if (accessor.Body == null && accessor.ExpressionBody == null) //is this an auto property ?
{
generator.AddAutoGetterMethodImplementation(propertyGenerationData, ilVar);
generator.AddAutoGetterMethodImplementation(ref propertyGenerationData, ilVar, getMethodVar);
}
else if (accessor.Body != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void AddGetter()
var ilVar = context.Naming.ILProcessor($"get{propertyData.Name}");
context.WriteCecilExpressions([$"var {ilVar} = {getMethodVar}.Body.GetILProcessor();"]);

propertyGenerator.AddAutoGetterMethodImplementation(in propertyData, ilVar);
propertyGenerator.AddAutoGetterMethodImplementation(in propertyData, ilVar, getMethodVar);
}
}

Expand All @@ -100,7 +100,7 @@ void AddInit()
var ilVar = context.Naming.ILProcessor($"set{propertyData.Name}");
context.WriteCecilExpressions([$"var {ilVar} = {setMethodVar}.Body.GetILProcessor();"]);

propertyGenerator.AddAutoSetterMethodImplementation(in propertyData, ilVar);
propertyGenerator.AddAutoSetterMethodImplementation(in propertyData, ilVar, setMethodVar);
context.EmitCilInstruction(ilVar, OpCodes.Ret);
}
}
Expand Down
16 changes: 14 additions & 2 deletions Cecilifier.Core/CodeGeneration/Property.Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Cecilifier.Core.AST;
using Cecilifier.Core.Extensions;
using Cecilifier.Core.Misc;
using Cecilifier.Core.Naming;
using Cecilifier.Core.Variables;
Expand Down Expand Up @@ -74,7 +75,7 @@ internal ScopedDefinitionVariable AddSetterMethodDeclaration(ref readonly Proper
return methodVariableScope;
}

internal void AddAutoSetterMethodImplementation(ref readonly PropertyGenerationData property, string ilSetVar)
internal void AddAutoSetterMethodImplementation(ref readonly PropertyGenerationData property, string ilSetVar, string setMethodVar)
{
AddBackingFieldIfNeeded(in property);

Expand All @@ -84,6 +85,7 @@ internal void AddAutoSetterMethodImplementation(ref readonly PropertyGenerationD

var operand = property.DeclaringTypeIsGeneric ? MakeGenericType(in property) : _backingFieldVar;
Context.EmitCilInstruction(ilSetVar, property.StoreOpCode, operand);
AddCompilerGeneratedAttributeTo(Context, setMethodVar);
}

internal ScopedDefinitionVariable AddGetterMethodDeclaration(ref readonly PropertyGenerationData property, string accessorMethodVar, bool hasCovariantReturn, string nameForRegistration, string overridenMethod)
Expand Down Expand Up @@ -118,7 +120,7 @@ internal ScopedDefinitionVariable AddGetterMethodDeclaration(ref readonly Proper
return scopedVariable;
}

internal void AddAutoGetterMethodImplementation(ref readonly PropertyGenerationData propertyGenerationData, string ilVar)
internal void AddAutoGetterMethodImplementation(ref readonly PropertyGenerationData propertyGenerationData, string ilVar, string getMethodVar)
{
AddBackingFieldIfNeeded(in propertyGenerationData);

Expand All @@ -129,6 +131,8 @@ internal void AddAutoGetterMethodImplementation(ref readonly PropertyGenerationD
var operand = propertyGenerationData.DeclaringTypeIsGeneric ? MakeGenericType(in propertyGenerationData) : _backingFieldVar;
Context.EmitCilInstruction(ilVar, propertyGenerationData.LoadOpCode, operand);
Context.EmitCilInstruction(ilVar, OpCodes.Ret);

AddCompilerGeneratedAttributeTo(Context, getMethodVar);
}

private void AddToOverridenMethodsIfAppropriated(string accessorMethodVar, string overridenMethod)
Expand Down Expand Up @@ -157,8 +161,16 @@ private void AddBackingFieldIfNeeded(ref readonly PropertyGenerationData propert
property.BackingFieldModifiers);

Context.WriteCecilExpressions(backingFieldExps);
AddCompilerGeneratedAttributeTo(Context, _backingFieldVar);
}

private void AddCompilerGeneratedAttributeTo(IVisitorContext context, string memberVariable)
{
var compilerGeneratedAttributeCtor = context.RoslynTypeSystem.SystemRuntimeCompilerServicesCompilerGeneratedAttribute.Ctor();
var exps = CecilDefinitionsFactory.Attribute(memberVariable, context, compilerGeneratedAttributeCtor.MethodResolverExpression(context));
context.WriteCecilExpressions(exps);
}

private string MakeGenericType(ref readonly PropertyGenerationData property)
{
//TODO: Register the following variable?
Expand Down
24 changes: 22 additions & 2 deletions Cecilifier.Core/CodeGeneration/Record.Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ private void AddEqualityOperator(IVisitorContext context, string recordTypeDefin
var bodyExps = CecilDefinitionsFactory.MethodBody(context.Naming, methodName, equalsOperatorMethodVar, [], equalsBodyInstructions);
context.WriteCecilExpressions(bodyExps);
context.WriteCecilExpression($"{recordTypeDefinitionVariable}.Methods.Add({equalsOperatorMethodVar});");
AddCompilerGeneratedAttributeTo(context, equalsOperatorMethodVar);
}

private void AddInequalityOperator(IVisitorContext context, string recordTypeDefinitionVariable, TypeDeclarationSyntax record)
Expand Down Expand Up @@ -123,6 +124,7 @@ private void AddInequalityOperator(IVisitorContext context, string recordTypeDef
var bodyExps = CecilDefinitionsFactory.MethodBody(context.Naming, methodName, inequalityOperatorMethodVar, [], inequalityBodyInstructions);
context.WriteCecilExpressions(bodyExps);
context.WriteCecilExpression($"{recordTypeDefinitionVariable}.Methods.Add({inequalityOperatorMethodVar});");
AddCompilerGeneratedAttributeTo(context, inequalityOperatorMethodVar);
}

private void InitializeEqualityComparerMemberCache(IVisitorContext context, TypeDeclarationSyntax record)
Expand Down Expand Up @@ -214,6 +216,8 @@ private void AddGetHashCodeMethod(IVisitorContext context, string recordTypeDefi
context.WriteNewLine();
context.WriteCecilExpression($"{recordTypeDefinitionVariable}.Methods.Add({getHashCodeMethodVar});");
context.WriteNewLine();

AddCompilerGeneratedAttributeTo(context, getHashCodeMethodVar);
}

private void AddToStringAndRelatedMethods(IVisitorContext context, string recordTypeDefinitionVariable, TypeDeclarationSyntax record)
Expand Down Expand Up @@ -317,6 +321,7 @@ void AddPrintMembersMethod()
OpCodes.Ret
]);
context.WriteCecilExpressions(printMemberBodyExps);
AddCompilerGeneratedAttributeTo(context, printMembersVar);
}

void AddToStringMethod()
Expand Down Expand Up @@ -376,6 +381,7 @@ void AddToStringMethod()
OpCodes.Ret
]);
context.WriteCecilExpressions(toStringBodyExps);
AddCompilerGeneratedAttributeTo(context, toStringMethodVar);
}

// Returns a `MethodReference` for the PrintMembers() method to be invoked
Expand Down Expand Up @@ -474,6 +480,8 @@ [new ParameterSpec("other", recordTypeDefinitionVariable, RefKind.None, Constant
var equalsExps = CecilDefinitionsFactory.MethodBody(context.Naming, "Equals",_recordTypeEqualsOverloadMethodVar, ilVar, [], instructions.ToArray());
context.WriteCecilExpressions(equalsExps);
}

AddCompilerGeneratedAttributeTo(context, _recordTypeEqualsOverloadMethodVar);
}

private IDictionary<string, (string GetDefaultMethodVar, string EqualsMethodVar, string GetHashCodeMethodVar)> GenerateEqualityComparerMethods(IVisitorContext context, IReadOnlyList<ITypeSymbol> targetTypes)
Expand Down Expand Up @@ -605,6 +613,8 @@ private void AddEqualityContractPropertyIfNeeded(IVisitorContext context, string
context.EmitCilInstruction(getterIlVar, OpCodes.Ldtoken, recordTypeDefinitionVariable);
context.EmitCilInstruction(getterIlVar, OpCodes.Call, getTypeFromHandleSymbol.MethodResolverExpression(context));
context.EmitCilInstruction(getterIlVar, OpCodes.Ret);

AddCompilerGeneratedAttributeTo(context, propertyData.Variable);
}

/// <summary>
Expand Down Expand Up @@ -652,7 +662,8 @@ [new ParameterSpec("other", context.TypeResolver.Bcl.System.Object, RefKind.None
var bodyExps = CecilDefinitionsFactory.MethodBody(context.Naming, methodName, equalsObjectOverloadMethodVar, [], equalsBodyInstructions);
context.WriteCecilExpressions(bodyExps);
context.WriteCecilExpression($"{recordTypeDefinitionVariable}.Methods.Add({equalsObjectOverloadMethodVar});");

AddCompilerGeneratedAttributeTo(context, equalsObjectOverloadMethodVar);

if (!HasBaseRecord(record))
return;

Expand Down Expand Up @@ -684,11 +695,12 @@ [new ParameterSpec("other", context.TypeResolver.Resolve(recordSymbol.BaseType),
var bodyExps2 = CecilDefinitionsFactory.MethodBody(context.Naming, methodName, equalsBaseOverloadMethodVar, [], equalsBodyInstructions2);
context.WriteCecilExpressions(bodyExps2);
context.WriteCecilExpression($"{recordTypeDefinitionVariable}.Methods.Add({equalsBaseOverloadMethodVar});");
AddCompilerGeneratedAttributeTo(context, equalsBaseOverloadMethodVar);
}

private void AddDeconstructMethod(IVisitorContext context, string recordTypeDefinitionVariable, TypeDeclarationSyntax record)
{
if (record.ParameterList?.Parameters.Count == 0)
if (record.ParameterList?.Parameters.Count is null or 0)
return;

var recordSymbol = context.SemanticModel.GetDeclaredSymbol(record).EnsureNotNull<ISymbol, ITypeSymbol>();
Expand Down Expand Up @@ -731,6 +743,7 @@ private void AddDeconstructMethod(IVisitorContext context, string recordTypeDefi
var bodyExps = CecilDefinitionsFactory.MethodBody(context.Naming, methodName, deconstructMethodVar, [], deconstructInstructions.ToArray());
context.WriteCecilExpressions(bodyExps);
context.WriteCecilExpression($"{recordTypeDefinitionVariable}.Methods.Add({deconstructMethodVar});");
AddCompilerGeneratedAttributeTo(context, deconstructMethodVar);

string GetGetterMethodVar(ITypeSymbol candidate, string propertyName)
{
Expand Down Expand Up @@ -760,6 +773,13 @@ candidate is RecordDeclarationSyntax candidateBase

return found != null;
}

private void AddCompilerGeneratedAttributeTo(IVisitorContext context, string memberVariable)
{
var compilerGeneratedAttributeCtor = context.RoslynTypeSystem.SystemRuntimeCompilerServicesCompilerGeneratedAttribute.Ctor();
var exps = CecilDefinitionsFactory.Attribute(memberVariable, context, compilerGeneratedAttributeCtor.MethodResolverExpression(context));
context.WriteCecilExpressions(exps);
}

private static string TypeEqualityOperator(IVisitorContext context)
{
Expand Down
18 changes: 18 additions & 0 deletions Cecilifier.Core/Misc/CecilDefinitionsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,24 @@ void ProcessAttributeNamedArguments(SymbolKind symbolKind, string container)
}
}
}

public static string[] Attribute(string memberVariable, IVisitorContext context, string resolvedCtor, params (string ResolvedType, string Value)[] parameters)
{
var attributeVar = context.Naming.SyntheticVariable("compilerGenerated", ElementKind.Attribute);

var exps = new string[2 + parameters.Length];
int expIndex = 0;
exps[expIndex++] = $"var {attributeVar} = new CustomAttribute({resolvedCtor});";

for(int i = 0; i < parameters.Length; i++)
{
var attributeArgument = $"new CustomAttributeArgument({parameters[i].ResolvedType}, {parameters[i].Value})";
exps[expIndex++] = $"{attributeVar}.ConstructorArguments.Add({attributeArgument});";
}
exps[expIndex] = $"{memberVariable}.CustomAttributes.Add({attributeVar});";

return exps;
}

private static void ProcessGenericTypeParameters(string memberDefVar, IVisitorContext context, IList<TypeParameterSyntax> typeParamList, IList<string> exps)
{
Expand Down
Loading

0 comments on commit ad988e6

Please sign in to comment.