From c8dbbfaeeaa3b3d15db75e95c8adc7b771d2c760 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 27 Apr 2024 18:03:25 -0400 Subject: [PATCH] introduces PropertyGenerator extracted from PropertyDeclarationVisitor 1. Also includes some minor refactorings 2. This work is a preparation for #273 to avoid code duplication --- .../AST/PropertyDeclarationVisitor.cs | 227 ++++++++---------- Cecilifier.Core/AST/SyntaxWalkerBase.cs | 15 +- .../CodeGeneration/Property.Generator.cs | 175 ++++++++++++++ Cecilifier.Core/Constants.cs | 1 + .../Extensions/MethodExtensions.cs | 6 +- .../Extensions/SyntaxNodeExtensions.cs | 13 +- Cecilifier.Core/Misc/CecilifierContext.cs | 2 +- 7 files changed, 308 insertions(+), 131 deletions(-) create mode 100644 Cecilifier.Core/CodeGeneration/Property.Generator.cs diff --git a/Cecilifier.Core/AST/PropertyDeclarationVisitor.cs b/Cecilifier.Core/AST/PropertyDeclarationVisitor.cs index a3bcb227..b392db24 100644 --- a/Cecilifier.Core/AST/PropertyDeclarationVisitor.cs +++ b/Cecilifier.Core/AST/PropertyDeclarationVisitor.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using System.Runtime.CompilerServices; +using Cecilifier.Core.CodeGeneration; using Cecilifier.Core.Extensions; using Cecilifier.Core.Mappings; using Cecilifier.Core.Misc; @@ -14,12 +15,12 @@ using Mono.Cecil.Cil; using static Cecilifier.Core.Misc.Utils; +#nullable enable namespace Cecilifier.Core.AST { internal class PropertyDeclarationVisitor : SyntaxWalkerBase { private static readonly List NoParameters = new(); - private string backingFieldVar; public PropertyDeclarationVisitor(IVisitorContext context) : base(context) { } @@ -55,12 +56,12 @@ public override void VisitIndexerDeclaration(IndexerDeclarationSyntax node) }); } - ProcessPropertyAccessors(node, propertyDeclaringTypeVar, propName, propertyType, propDefVar, paramsVar, node.ExpressionBody); + var backingFieldVar = ProcessPropertyAccessors(node, propertyDeclaringTypeVar, propDefVar, propName, paramsVar, node.ExpressionBody); AddCecilExpression($"{propertyDeclaringTypeVar}.Properties.Add({propDefVar});"); HandleAttributesInMemberDeclaration(node.AttributeLists, TargetDoesNotMatch, SyntaxKind.FieldKeyword, propDefVar); // Normal property attrs - HandleAttributesInMemberDeclaration(node.AttributeLists, TargetMatches, SyntaxKind.FieldKeyword, backingFieldVar); // [field: attr], i.e, attr belongs to the backing field. + HandleAttributesInMemberDeclaration(node.AttributeLists, TargetMatches, SyntaxKind.FieldKeyword, backingFieldVar ?? String.Empty); // [field: attr], i.e, attr belongs to the backing field. } public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) @@ -76,17 +77,17 @@ public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) var propName = node.Identifier.ValueText; var propDefVar = AddPropertyDefinition(node, propName, propertyType); - ProcessPropertyAccessors(node, propertyDeclaringTypeVar, propName, propertyType, propDefVar, NoParameters, node.ExpressionBody); + var backingFieldVar = ProcessPropertyAccessors(node, propertyDeclaringTypeVar, propDefVar, node.Identifier.ValueText, NoParameters, node.ExpressionBody); AddCecilExpression($"{propertyDeclaringTypeVar}.Properties.Add({propDefVar});"); HandleAttributesInMemberDeclaration(node.AttributeLists, TargetDoesNotMatch, SyntaxKind.FieldKeyword, propDefVar); // Normal property attrs - HandleAttributesInMemberDeclaration(node.AttributeLists, TargetMatches, SyntaxKind.FieldKeyword, backingFieldVar); // [field: attr], i.e, attr belongs to the backing field. + HandleAttributesInMemberDeclaration(node.AttributeLists, TargetMatches, SyntaxKind.FieldKeyword, backingFieldVar ?? string.Empty); // [field: attr], i.e, attr belongs to the backing field. } private bool PropertyAlreadyProcessed(BasePropertyDeclarationSyntax node) { - var propInfo = (IPropertySymbol) Context.SemanticModel.GetDeclaredSymbol(node); + var propInfo = (IPropertySymbol?) Context.SemanticModel.GetDeclaredSymbol(node); if (propInfo == null) return false; @@ -124,110 +125,74 @@ bool AttributeAlreadyAddedForAnotherMember() } } - private void ProcessPropertyAccessors(BasePropertyDeclarationSyntax node, string propertyDeclaringTypeVar, string propName, string propertyType, string propDefVar, List parameters, ArrowExpressionClauseSyntax? arrowExpression) + private string? ProcessPropertyAccessors(BasePropertyDeclarationSyntax node, string propertyDeclaringTypeVar, string propDefVar, string propertyName, List parameters, ArrowExpressionClauseSyntax? arrowExpression) { using var _ = LineInformationTracker.Track(Context, node); var propertySymbol = Context.SemanticModel.GetDeclaredSymbol(node).EnsureNotNull(); - var accessorModifiers = node.Modifiers.MethodModifiersToCecil("MethodAttributes.SpecialName", propertySymbol.GetMethod ?? propertySymbol.SetMethod); + + PropertyGenerator generator = new (Context); + var propertyGenerationData = new PropertyGenerationData( + propertySymbol.ContainingType.ToDisplayString(), + propertyDeclaringTypeVar, + propertySymbol.ContainingSymbol is INamedTypeSymbol { IsGenericType: true} && propertySymbol.IsDefinedInCurrentAssembly(Context), + propDefVar, + propertyName, + AccessorsModifiersFor(node, propertySymbol), + propertySymbol.IsStatic, + ResolveType(node.Type), + propertySymbol.Type.ToDisplayString(), + parameters, + BackingFieldModifiersFor(node), + propertySymbol.StoreOpCodeForFieldAccess(), + propertySymbol.LoadOpCodeForFieldAccess()); if (arrowExpression != null) { - AddExpressionBodiedGetterMethod(propertySymbol); - return; + AddExpressionBodiedGetterMethod(); + return generator.BackingFieldVariable; } - + foreach (var accessor in node.AccessorList!.Accessors) { Context.WriteNewLine(); switch (accessor.Keyword.Kind()) { case SyntaxKind.GetKeyword: - AddGetterMethod(accessor, propertySymbol); + AddGetterMethod(accessor); break; case SyntaxKind.InitKeyword: - Context.WriteComment(" Init"); - var setterReturnType = $"new RequiredModifierType({Context.TypeResolver.Resolve(typeof(IsExternalInit).FullName)}, {Context.TypeResolver.Bcl.System.Void})"; - AddSetterMethod(propertySymbol, setterReturnType, accessor); - break; - case SyntaxKind.SetKeyword: - Context.WriteComment(" Setter"); - AddSetterMethod(propertySymbol, Context.TypeResolver.Bcl.System.Void, accessor); + Context.WriteComment($" {(accessor.Keyword.IsKind(SyntaxKind.InitKeyword) ? "Init": "Setter")}"); + AddSetterMethod(propertySymbol, accessor); break; default: throw new NotImplementedException($"Accessor: {accessor.Keyword}"); } } - void AddBackingFieldIfNeeded(AccessorDeclarationSyntax accessor, bool hasInitProperty) - { - if (backingFieldVar != null) - return; + return generator.BackingFieldVariable; - backingFieldVar = Context.Naming.FieldDeclaration(node); - var m = accessor.Modifiers; - if (hasInitProperty) - m = m.Add(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - - if (propertySymbol.IsStatic) - m = m.Add(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); - - var modifiers = ModifiersToCecil(m, "Private", FieldDeclarationVisitor.MapFieldAttributesFor); - var backingFieldExps = CecilDefinitionsFactory.Field(Context, propertySymbol.ContainingSymbol.ToDisplayString(), propertyDeclaringTypeVar, backingFieldVar, Utils.BackingFieldNameForAutoProperty(propName), propertyType, modifiers); - AddCecilExpressions(Context, backingFieldExps); - } - - void AddSetterMethod(IPropertySymbol property, string setterReturnType, AccessorDeclarationSyntax accessor) + void AddSetterMethod(IPropertySymbol property, AccessorDeclarationSyntax accessor) { var setMethodVar = Context.Naming.SyntheticVariable("set", ElementKind.LocalVariable); + using var methodVariableScope = generator.AddSetterMethodDeclaration( + in propertyGenerationData, + setMethodVar, + accessor.IsKind(SyntaxKind.InitAccessorDeclaration), + property.SetMethod!.ToDisplayString(), + GetOverridenMethod(propertySymbol.SetMethod)); - var completeParamList = new List(parameters); - - // Setters always have at least one `value` parameter but Roslyn does not have it explicitly listed. - completeParamList.Add(new ParameterSpec( - "value", - Context.TypeResolver.Resolve(property.Type), - RefKind.None, - Constants.ParameterAttributes.None) { RegistrationTypeName = property.Type.ToDisplayString() } ); - - var exps = CecilDefinitionsFactory.Method( - Context, - property.ContainingType.ToDisplayString(), - setMethodVar, - property.SetMethod!.ToDisplayString(), - $"set_{propName}", - $"{accessorModifiers}", - completeParamList, - [], // Properties cannot declare TypeParameters - ctx => setterReturnType, - out var methodDefinitionVariable); - - using var _ = Context.DefinitionVariables.WithCurrentMethod(methodDefinitionVariable); - Context.WriteCecilExpressions(exps); - AddToOverridenMethodsIfAppropriated(setMethodVar, property.SetMethod); - AddCecilExpression($"{propertyDeclaringTypeVar}.Methods.Add({setMethodVar});"); - - var ilSetVar = Context.Naming.ILProcessor("set"); - - AddCecilExpression($"{setMethodVar}.Body = new MethodBody({setMethodVar});"); - AddCecilExpression($"{propDefVar}.SetMethod = {setMethodVar};"); - - AddCecilExpression($"var {ilSetVar} = {setMethodVar}.Body.GetILProcessor();"); - if (propertySymbol.ContainingType.TypeKind == TypeKind.Interface) return; + var ilSetVar = Context.Naming.ILProcessor("set"); + Context.WriteCecilExpression($"var {ilSetVar} = {setMethodVar}.Body.GetILProcessor();"); + Context.WriteNewLine(); + if (accessor.Body == null && accessor.ExpressionBody == null) //is this an auto property ? { - AddBackingFieldIfNeeded(accessor, node.AccessorList.Accessors.Any(acc => acc.IsKind(SyntaxKind.InitAccessorDeclaration))); - - Context.EmitCilInstruction(ilSetVar, OpCodes.Ldarg_0); - if (!propertySymbol.IsStatic) - Context.EmitCilInstruction(ilSetVar, OpCodes.Ldarg_1); - - var operand = MakeGenericTypeIfAppropriate(Context, propertySymbol, backingFieldVar, propertyDeclaringTypeVar); - Context.EmitCilInstruction(ilSetVar, propertySymbol.StoreOpCodeForFieldAccess(), operand); + generator.AddAutoSetterMethodImplementation(in propertyGenerationData, ilSetVar); } else if (accessor.Body != null) { @@ -241,37 +206,16 @@ void AddSetterMethod(IPropertySymbol property, string setterReturnType, Accessor Context.EmitCilInstruction(ilSetVar, OpCodes.Ret); } - ScopedDefinitionVariable AddGetterMethodGuts(IPropertySymbol property, out string ilVar) + ScopedDefinitionVariable AddGetterMethodGuts(out string? ilVar) { Context.WriteComment("Getter"); - var getMethodVar = Context.Naming.SyntheticVariable("get", ElementKind.Method); - - MethodDefinitionVariable methodDefinitionVariable; - var exps = CecilDefinitionsFactory.Method( - Context, - property.ContainingType.ToDisplayString(), - getMethodVar, - property.GetMethod!.ToDisplayString(), - $"get_{propName}", - $"{accessorModifiers}", - parameters, - [], // Properties cannot declare TypeParameters - ctx => propertyType, - out methodDefinitionVariable); - - Context.WriteCecilExpressions(exps); - - var scopedVariable = Context.DefinitionVariables.WithCurrentMethod(methodDefinitionVariable); - - AddToOverridenMethodsIfAppropriated(getMethodVar, property.GetMethod); - if (property.HasCovariantGetter()) - AddCecilExpression($"{getMethodVar}.CustomAttributes.Add(new CustomAttribute(assembly.MainModule.Import(typeof(System.Runtime.CompilerServices.PreserveBaseOverridesAttribute).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null))));"); - - AddCecilExpression($"{propertyDeclaringTypeVar}.Methods.Add({getMethodVar});"); - - AddCecilExpression($"{getMethodVar}.Body = new MethodBody({getMethodVar});"); - AddCecilExpression($"{propDefVar}.GetMethod = {getMethodVar};"); + var methodVariableScope = generator.AddGetterMethodDeclaration( + in propertyGenerationData, + getMethodVar, + propertySymbol.HasCovariantGetter(), + propertySymbol.GetMethod!.ToDisplayString(), + GetOverridenMethod(propertySymbol.GetMethod)); if (propertySymbol.ContainingType.TypeKind != TypeKind.Interface) { @@ -282,32 +226,26 @@ ScopedDefinitionVariable AddGetterMethodGuts(IPropertySymbol property, out strin { ilVar = null; } - - return scopedVariable; + + return methodVariableScope; } - void AddExpressionBodiedGetterMethod(IPropertySymbol property) + void AddExpressionBodiedGetterMethod() { - using var _ = AddGetterMethodGuts(property, out var ilVar); + using var getterMethodScope = AddGetterMethodGuts(out var ilVar); + Debug.Assert(ilVar != null); ProcessExpressionBodiedGetter(ilVar, arrowExpression); } - void AddGetterMethod(AccessorDeclarationSyntax accessor, IPropertySymbol propertySymbol) + void AddGetterMethod(AccessorDeclarationSyntax accessor) { - using var _ = AddGetterMethodGuts(propertySymbol, out var ilVar); + using var getterMethodScope = AddGetterMethodGuts(out var ilVar); if (ilVar == null) return; if (accessor.Body == null && accessor.ExpressionBody == null) //is this an auto property ? { - AddBackingFieldIfNeeded(accessor, node.AccessorList.Accessors.Any(acc => acc.IsKind(SyntaxKind.InitAccessorDeclaration))); - - if (!propertySymbol.IsStatic) - Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0); - var operand = Utils.MakeGenericTypeIfAppropriate(Context, propertySymbol, backingFieldVar, propertyDeclaringTypeVar); - Context.EmitCilInstruction(ilVar, propertySymbol.LoadOpCodeForFieldAccess(), operand); - - Context.EmitCilInstruction(ilVar, OpCodes.Ret); + generator.AddAutoGetterMethodImplementation(propertyGenerationData, ilVar); } else if (accessor.Body != null) { @@ -319,13 +257,58 @@ void AddGetterMethod(AccessorDeclarationSyntax accessor, IPropertySymbol propert } } - void ProcessExpressionBodiedGetter(string ilVar, ArrowExpressionClauseSyntax expression) + void ProcessExpressionBodiedGetter(string ilVar, ArrowExpressionClauseSyntax? expression) { ExpressionVisitor.Visit(Context, ilVar, expression); Context.EmitCilInstruction(ilVar, OpCodes.Ret); } } + private IDictionary AccessorsModifiersFor(BasePropertyDeclarationSyntax node, IPropertySymbol propertySymbol) + { + if (node.AccessorList == null) + { + Debug.Assert(propertySymbol.GetMethod != null); + var accessorModifiers = node.Modifiers.MethodModifiersToCecil(Constants.Cecil.MethodAttributesSpecialName, propertySymbol.GetMethod); + return new Dictionary() + { + ["get"] = accessorModifiers + }; + } + + return new Dictionary + { + ["get"] = AccessorModifiers(SyntaxKind.GetAccessorDeclaration), + ["set"] = AccessorModifiers(SyntaxKind.SetAccessorDeclaration) ?? AccessorModifiers(SyntaxKind.InitAccessorDeclaration) + }; + + string? AccessorModifiers(SyntaxKind accessorKind) + { + var accessor = node.AccessorList.Accessors.SingleOrDefault(a => a.IsKind(accessorKind)); + if (accessor == null) + return null; + + var modifiers = accessor.Modifiers.Any() + ? node.ModifiersExcludingAccessibility().Concat(accessor.Modifiers) + : node.Modifiers; + + var accessorSymbol = accessorKind == SyntaxKind.GetAccessorDeclaration ? propertySymbol.GetMethod : propertySymbol.SetMethod; + return modifiers.MethodModifiersToCecil(Constants.Cecil.MethodAttributesSpecialName, accessorSymbol); + } + } + + private static string BackingFieldModifiersFor(BasePropertyDeclarationSyntax node) + { + var m = node.Modifiers.ExceptBy( + [SyntaxKind.PublicKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.InternalKeyword, SyntaxKind.VirtualKeyword, SyntaxKind.OverrideKeyword], + c => c.Kind()); + + if (node.AccessorList != null && node.AccessorList.Accessors.Any(acc => acc.IsKind(SyntaxKind.InitAccessorDeclaration))) + m = m.Append(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); // properties with `init` accessors are considered `readonly` + + return ModifiersToCecil(m, "Private", FieldDeclarationVisitor.MapFieldAttributesFor); + } + private string AddPropertyDefinition(BasePropertyDeclarationSyntax propertyDeclarationSyntax, string propName, string propertyType) { var propDefVar = Context.Naming.PropertyDeclaration(propertyDeclarationSyntax); diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index d0b97000..e0a6e6a4 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -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(); @@ -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); } } } diff --git a/Cecilifier.Core/CodeGeneration/Property.Generator.cs b/Cecilifier.Core/CodeGeneration/Property.Generator.cs new file mode 100644 index 00000000..b30be21e --- /dev/null +++ b/Cecilifier.Core/CodeGeneration/Property.Generator.cs @@ -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 AccessorModifiers, + bool IsStatic, + string ResolvedType, + string TypeNameForRegistration, + IReadOnlyList 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(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; + } +} diff --git a/Cecilifier.Core/Constants.cs b/Cecilifier.Core/Constants.cs index 19c2def6..1c0e742f 100644 --- a/Cecilifier.Core/Constants.cs +++ b/Cecilifier.Core/Constants.cs @@ -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"; diff --git a/Cecilifier.Core/Extensions/MethodExtensions.cs b/Cecilifier.Core/Extensions/MethodExtensions.cs index 2ea45ee5..8c56a00d 100644 --- a/Cecilifier.Core/Extensions/MethodExtensions.cs +++ b/Cecilifier.Core/Extensions/MethodExtensions.cs @@ -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 modifiers, string specificModifiers = null, IMethodSymbol methodSymbol = null) { var lastDeclaredIn = methodSymbol.FindLastDefinition(); var modifiersStr = MapExplicitModifiers(modifiers, lastDeclaredIn.ContainingType.TypeKind); @@ -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 modifiers, TypeKind typeKind) { foreach (var mod in modifiers) { @@ -278,7 +278,7 @@ private static string MapExplicitModifiers(SyntaxTokenList modifiers, TypeKind t return string.Empty; } - private static IEnumerable RemoveSourceModifiersWithNoILEquivalent(SyntaxTokenList modifiers) + private static IEnumerable RemoveSourceModifiersWithNoILEquivalent(IEnumerable modifiers) { return modifiers.Where( mod => !mod.IsKind(SyntaxKind.OverrideKeyword) diff --git a/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs b/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs index cf89945d..b27cafd4 100644 --- a/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs +++ b/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs @@ -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)); } @@ -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([NotNullIfNotNull("source")] this TSource source, [CallerArgumentExpression("source")] string exp = null) where TTarget : TSource + public static TTarget EnsureNotNull([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) { @@ -170,5 +172,12 @@ internal static bool IsUsedAsReturnValueOfType(this SyntaxNode self, SemanticMod return false; } + + internal static IEnumerable ModifiersExcludingAccessibility(this MemberDeclarationSyntax member) + { + return member.Modifiers.ExceptBy( + [SyntaxKind.PublicKeyword, SyntaxKind.PrivateKeyword, SyntaxKind.InternalKeyword, SyntaxKind.ProtectedKeyword], + c => (SyntaxKind) c.RawKind); + } } } diff --git a/Cecilifier.Core/Misc/CecilifierContext.cs b/Cecilifier.Core/Misc/CecilifierContext.cs index baa6e6e7..3e5a63f8 100644 --- a/Cecilifier.Core/Misc/CecilifierContext.cs +++ b/Cecilifier.Core/Misc/CecilifierContext.cs @@ -90,7 +90,7 @@ public void WriteCecilExpression(string expression) public void WriteCecilExpressions(IEnumerable expressions) { - foreach (var expression in expressions) + foreach (var expression in expressions.Where(exp => !string.IsNullOrWhiteSpace(exp))) { WriteCecilExpression(expression); WriteNewLine();