-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
introduces PropertyGenerator extracted from PropertyDeclarationVisitor
1. Also includes some minor refactorings 2. This work is a preparation for #273 to avoid code duplication
- Loading branch information
Showing
7 changed files
with
308 additions
and
131 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters