Skip to content

Commit

Permalink
introduces PropertyGenerator extracted from PropertyDeclarationVisitor
Browse files Browse the repository at this point in the history
1. Also includes some minor refactorings
2. This work is a preparation for #273 to avoid code duplication
  • Loading branch information
adrianoc committed Apr 27, 2024
1 parent 53066af commit c8dbbfa
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 131 deletions.
227 changes: 105 additions & 122 deletions Cecilifier.Core/AST/PropertyDeclarationVisitor.cs

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions Cecilifier.Core/AST/SyntaxWalkerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,15 @@ protected void LogUnsupportedSyntax(SyntaxNode node)
// Methods implementing explicit interfaces, static abstract methods from interfaces and overriden methods with covariant return types
// needs to explicitly specify which methods they override.
protected void AddToOverridenMethodsIfAppropriated(string methodVar, IMethodSymbol method)
{
var overridenMethod = GetOverridenMethod(method);
if (overridenMethod == null)
return;

WriteCecilExpression(Context, $"{methodVar}.Overrides.Add({overridenMethod});");
}

protected string GetOverridenMethod(IMethodSymbol method)
{
// first check explicit interface implementation...
var overridenMethod = method?.ExplicitInterfaceImplementations.FirstOrDefault();
Expand All @@ -1007,14 +1016,14 @@ protected void AddToOverridenMethodsIfAppropriated(string methodVar, IMethodSymb
{
// if it is not an explicit interface implementation check for abstract static method from interfaces
var lastDeclared = method.FindLastDefinition(method.ContainingType.Interfaces);
if (lastDeclared == null || SymbolEqualityComparer.Default.Equals(lastDeclared, method) || lastDeclared.ContainingType.TypeKind != TypeKind.Interface || method?.IsStatic == false)
return;
if (lastDeclared == null || SymbolEqualityComparer.Default.Equals(lastDeclared, method) || lastDeclared.ContainingType.TypeKind != TypeKind.Interface || method.IsStatic == false)
return null;

overridenMethod = lastDeclared;
}
}

WriteCecilExpression(Context, $"{methodVar}.Overrides.Add({overridenMethod.MethodResolverExpression(Context)});");
return overridenMethod.MethodResolverExpression(Context);
}
}
}
175 changes: 175 additions & 0 deletions Cecilifier.Core/CodeGeneration/Property.Generator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Cecilifier.Core.AST;
using Cecilifier.Core.Misc;
using Cecilifier.Core.Naming;
using Cecilifier.Core.Variables;
using Microsoft.CodeAnalysis;
using Mono.Cecil.Cil;

namespace Cecilifier.Core.CodeGeneration;

internal record struct PropertyGenerationData(
string DeclaringTypeNameForRegistration,
string DeclaringTypeVariable,
bool DeclaringTypeIsGeneric,
string Variable,
string Name,
IDictionary<string, string> AccessorModifiers,
bool IsStatic,
string ResolvedType,
string TypeNameForRegistration,
IReadOnlyList<ParameterSpec> Parameters,
string BackingFieldModifiers,
OpCode StoreOpCode,
OpCode LoadOpCode);

internal class PropertyGenerator
{
private string _backingFieldVar;

public PropertyGenerator(IVisitorContext context)
{
Context = context;
}

private IVisitorContext Context { get; init; }
public string BackingFieldVariable => _backingFieldVar;

internal ScopedDefinitionVariable AddSetterMethodDeclaration(ref readonly PropertyGenerationData property, string accessorMethodVar, bool isInitOnly, string nameForRegistration, string overridenMethod)
{
var completeParamList = new List<ParameterSpec>(property.Parameters)
{
// Setters always have at least one `value` parameter but Roslyn does not have it explicitly listed.
new(
"value",
property.ResolvedType,
RefKind.None,
Constants.ParameterAttributes.None) { RegistrationTypeName = property.TypeNameForRegistration }
};

var exps = CecilDefinitionsFactory.Method(
Context,
property.DeclaringTypeNameForRegistration,
accessorMethodVar,
nameForRegistration,
$"set_{property.Name}",
property.AccessorModifiers["set"],
completeParamList,
[], // Properties cannot declare TypeParameters
ctx => isInitOnly ? $"new RequiredModifierType({ctx.TypeResolver.Resolve(typeof(IsExternalInit).FullName)}, {ctx.TypeResolver.Bcl.System.Void})" : ctx.TypeResolver.Bcl.System.Void,
out var methodDefinitionVariable);

var methodVariableScope = Context.DefinitionVariables.WithCurrentMethod(methodDefinitionVariable);
Context.WriteCecilExpressions(exps);
AddToOverridenMethodsIfAppropriated(accessorMethodVar, overridenMethod);

Context.WriteCecilExpressions([
$"{property.DeclaringTypeVariable}.Methods.Add({accessorMethodVar});",
$"{accessorMethodVar}.Body = new MethodBody({accessorMethodVar});",
$"{property.Variable}.SetMethod = {accessorMethodVar};" ]);

return methodVariableScope;
}

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

Context.EmitCilInstruction(ilSetVar, OpCodes.Ldarg_0);
if (!property.IsStatic)
Context.EmitCilInstruction(ilSetVar, OpCodes.Ldarg_1);

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

internal ScopedDefinitionVariable AddGetterMethodDeclaration(ref readonly PropertyGenerationData property, string accessorMethodVar, bool hasCovariantReturn, string nameForRegistration, string overridenMethod)
{
var propertyResolvedType = property.ResolvedType;
var exps = CecilDefinitionsFactory.Method(
Context,
property.DeclaringTypeNameForRegistration,
accessorMethodVar,
nameForRegistration,
$"get_{property.Name}",
property.AccessorModifiers["get"],
property.Parameters,
[], // Properties cannot declare TypeParameters
_ => propertyResolvedType,
out var methodDefinitionVariable);

Context.WriteCecilExpressions(exps);

var scopedVariable = Context.DefinitionVariables.WithCurrentMethod(methodDefinitionVariable);

AddToOverridenMethodsIfAppropriated(accessorMethodVar, overridenMethod);

Context.WriteCecilExpressions([
hasCovariantReturn ?
$"{accessorMethodVar}.CustomAttributes.Add(new CustomAttribute(assembly.MainModule.Import(typeof(System.Runtime.CompilerServices.PreserveBaseOverridesAttribute).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null))));"
: String.Empty,
$"{property.DeclaringTypeVariable}.Methods.Add({accessorMethodVar});",
$"{accessorMethodVar}.Body = new MethodBody({accessorMethodVar});",
$"{property.Variable}.GetMethod = {accessorMethodVar};" ]);

return scopedVariable;
}

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

if (!propertyGenerationData.IsStatic)
Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0);

Debug.Assert(_backingFieldVar != null);
var operand = propertyGenerationData.DeclaringTypeIsGeneric ? MakeGenericType(in propertyGenerationData) : _backingFieldVar;
Context.EmitCilInstruction(ilVar, propertyGenerationData.LoadOpCode, operand);
Context.EmitCilInstruction(ilVar, OpCodes.Ret);
}

private void AddToOverridenMethodsIfAppropriated(string accessorMethodVar, string overridenMethod)
{
if (string.IsNullOrWhiteSpace(overridenMethod))
return;

Context.WriteCecilExpression($"{accessorMethodVar}.Overrides.Add({overridenMethod});");
Context.WriteNewLine();
}

private void AddBackingFieldIfNeeded(ref readonly PropertyGenerationData property)
{
if (_backingFieldVar != null)
return;

_backingFieldVar = Context.Naming.SyntheticVariable(property.Name, ElementKind.Field);

var backingFieldExps = CecilDefinitionsFactory.Field(
Context,
property.DeclaringTypeNameForRegistration,
property.DeclaringTypeVariable,
_backingFieldVar,
Utils.BackingFieldNameForAutoProperty(property.Name),
property.ResolvedType,
property.BackingFieldModifiers);

Context.WriteCecilExpressions(backingFieldExps);
}

private string MakeGenericType(ref readonly PropertyGenerationData property)
{
//TODO: Register the following variable?
var genTypeVar = Context.Naming.SyntheticVariable(property.Name, ElementKind.GenericInstance);
var fieldRefVar = Context.Naming.MemberReference("fld_");

Context.WriteCecilExpressions(
[
$"var {genTypeVar} = {property.DeclaringTypeVariable}.MakeGenericInstanceType({property.DeclaringTypeVariable}.GenericParameters.ToArray());",
$"var {fieldRefVar} = new FieldReference({_backingFieldVar}.Name, {_backingFieldVar}.FieldType, {genTypeVar});"
]);
return fieldRefVar;
}
}
1 change: 1 addition & 0 deletions Cecilifier.Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public struct Cecil
public const string StaticClassAttributes = $"TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | {StaticTypeAttributes}";
public const string InterfaceMethodDefinitionAttributes = "MethodAttributes.NewSlot | MethodAttributes.Virtual"; // Some common method attributes (like HideBySig) will be explicitly added.
public const string MethodAttributesSpecialName = "MethodAttributes.SpecialName";
public const string MethodAttributesStatic = "MethodAttributes.Static";
public const string DelegateMethodAttributes = "MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual";

public const string CtorAttributes = "MethodAttributes.RTSpecialName | MethodAttributes.SpecialName";
Expand Down
6 changes: 3 additions & 3 deletions Cecilifier.Core/Extensions/MethodExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public static MethodDefinitionVariable AsMethodDefinitionVariable(this IMethodSy
variableName);
}

public static string MethodModifiersToCecil(this SyntaxTokenList modifiers, string specificModifiers = null, IMethodSymbol methodSymbol = null)
public static string MethodModifiersToCecil(this IEnumerable<SyntaxToken> modifiers, string specificModifiers = null, IMethodSymbol methodSymbol = null)
{
var lastDeclaredIn = methodSymbol.FindLastDefinition();
var modifiersStr = MapExplicitModifiers(modifiers, lastDeclaredIn.ContainingType.TypeKind);
Expand Down Expand Up @@ -256,7 +256,7 @@ private static bool IsExplicitMethodImplementation(this IMethodSymbol methodSymb
return methodSymbol.ExplicitInterfaceImplementations.Any();
}

private static string MapExplicitModifiers(SyntaxTokenList modifiers, TypeKind typeKind)
private static string MapExplicitModifiers(IEnumerable<SyntaxToken> modifiers, TypeKind typeKind)
{
foreach (var mod in modifiers)
{
Expand All @@ -278,7 +278,7 @@ private static string MapExplicitModifiers(SyntaxTokenList modifiers, TypeKind t
return string.Empty;
}

private static IEnumerable<SyntaxToken> RemoveSourceModifiersWithNoILEquivalent(SyntaxTokenList modifiers)
private static IEnumerable<SyntaxToken> RemoveSourceModifiersWithNoILEquivalent(IEnumerable<SyntaxToken> modifiers)
{
return modifiers.Where(
mod => !mod.IsKind(SyntaxKind.OverrideKeyword)
Expand Down
13 changes: 11 additions & 2 deletions Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static bool IsOperatorOnCustomUserType(this SyntaxNode self, SemanticMode
// (as opposed to their own operator overloads) whence the check for IsUnmanagedType; the extra check (SpecialType) is meant to filter out
// custom structs (non primitives) which may be deemed as unmanaged (see the link above for more details)
return method is { Parameters: { Length: > 0 } }
&& method.Parameters[0].Type?.BaseType?.SpecialType != SpecialType.System_MulticastDelegate
&& method.Parameters[0].Type.BaseType?.SpecialType != SpecialType.System_MulticastDelegate
&& ((!method.Parameters[0].Type.IsUnmanagedType && method.Parameters[0].Type.SpecialType != SpecialType.System_String)
|| (method.Parameters[0].Type.SpecialType == SpecialType.None && method.Parameters[0].Type.Kind != SymbolKind.PointerType));
}
Expand Down Expand Up @@ -118,14 +118,16 @@ public static MethodDispatchInformation MethodDispatchInformation(this SyntaxNod
return nodeBeingAccessed.IsKind(SyntaxKind.NameColon) ? AST.MethodDispatchInformation.MostLikelyVirtual : AST.MethodDispatchInformation.MostLikelyNonVirtual ;
}

#nullable enable
[return: NotNull]
public static TTarget EnsureNotNull<TSource, TTarget>([NotNullIfNotNull("source")] this TSource source, [CallerArgumentExpression("source")] string exp = null) where TTarget : TSource
public static TTarget EnsureNotNull<TSource, TTarget>([NotNullIfNotNull("source")] this TSource? source, [CallerArgumentExpression("source")] string exp = null) where TTarget : TSource
{
if (source == null)
throw new ArgumentNullException(exp);

return (TTarget) source;
}
#nullable restore

internal static bool IsPassedAsInParameter(this ArgumentSyntax toBeChecked, IVisitorContext context)
{
Expand Down Expand Up @@ -170,5 +172,12 @@ internal static bool IsUsedAsReturnValueOfType(this SyntaxNode self, SemanticMod

return false;
}

internal static IEnumerable<SyntaxToken> ModifiersExcludingAccessibility(this MemberDeclarationSyntax member)
{
return member.Modifiers.ExceptBy(
[SyntaxKind.PublicKeyword, SyntaxKind.PrivateKeyword, SyntaxKind.InternalKeyword, SyntaxKind.ProtectedKeyword],
c => (SyntaxKind) c.RawKind);
}
}
}
2 changes: 1 addition & 1 deletion Cecilifier.Core/Misc/CecilifierContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public void WriteCecilExpression(string expression)

public void WriteCecilExpressions(IEnumerable<string> expressions)
{
foreach (var expression in expressions)
foreach (var expression in expressions.Where(exp => !string.IsNullOrWhiteSpace(exp)))
{
WriteCecilExpression(expression);
WriteNewLine();
Expand Down

0 comments on commit c8dbbfa

Please sign in to comment.