From 81e5d426e3c493c564b24ae3a94b2a6a917a7724 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Thu, 17 Sep 2026 22:45:42 -0400 Subject: [PATCH 01/12] fix: generated assembly crash when expression bodied member fails to consume the lambda expression return value (#338) --- .../Tests/OutputBased/StatementTests.cs | 11 +++++++++++ Cecilifier.Core/AST/ExpressionVisitor.cs | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Cecilifier.Core.Tests/Tests/OutputBased/StatementTests.cs b/Cecilifier.Core.Tests/Tests/OutputBased/StatementTests.cs index d1433903..b9fb2fca 100644 --- a/Cecilifier.Core.Tests/Tests/OutputBased/StatementTests.cs +++ b/Cecilifier.Core.Tests/Tests/OutputBased/StatementTests.cs @@ -25,4 +25,15 @@ public void SimpleCallToConsoleWriteLine() } """, "No Arguments|@Else"); } + + [Test] + public void Unconsumed_ReturnType_DoesNotCrash_Issue338() + { + AssertOutput(""" + M("X"); + System.Console.Write("Reached this point"); + + void M(string s) => s.ToUpper(); + """, "Reached this point"); + } } diff --git a/Cecilifier.Core/AST/ExpressionVisitor.cs b/Cecilifier.Core/AST/ExpressionVisitor.cs index 208049be..ce51d4c7 100644 --- a/Cecilifier.Core/AST/ExpressionVisitor.cs +++ b/Cecilifier.Core/AST/ExpressionVisitor.cs @@ -227,7 +227,16 @@ public override void VisitArrowExpressionClause(ArrowExpressionClauseSyntax node using var _ = LineInformationTracker.Track(Context, node); Context.WriteComment(node.Expression.ToString()); node.Expression.Accept(this); - node.Expression.InjectRequiredConversions(Context, ilVar); + + var declaringMethod = Context.SemanticModel.GetDeclaredSymbol(node.Parent!).EnsureNotNull(); + if (declaringMethod.GetMemberType().SpecialType == SpecialType.System_Void && PopIfNotConsumed(Context, ilVar, node.Expression)) + { + Context.WriteNewLine(); + } + else + { + node.Expression.InjectRequiredConversions(Context, ilVar); + } } public override void VisitInitializerExpression(InitializerExpressionSyntax node) @@ -1608,7 +1617,7 @@ string EmitTargetLabel(string relatedToName) return labelVariable; } - private static void PopIfNotConsumed(IVisitorContext ctx, IlContext ilVar, ExpressionSyntax node) + private static bool PopIfNotConsumed(IVisitorContext ctx, IlContext ilVar, ExpressionSyntax node) { var nodeType = ctx.GetTypeInfo(node).Type.EnsureNotNull(); if (!node.IsKind(SyntaxKind.SimpleAssignmentExpression) @@ -1616,7 +1625,10 @@ private static void PopIfNotConsumed(IVisitorContext ctx, IlContext ilVar, Expre && nodeType.SpecialType != SpecialType.System_Void) { ctx.ApiDriver.WriteCilInstruction(ctx, ilVar, OpCodes.Pop); + return true; } + + return false; } private static void HandleModulusExpression(IVisitorContext context, IlContext ilVar, ITypeSymbol lhs, ITypeSymbol rhs) From 510ba9c5360227378ad758de8c8da93d79b1946e Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Thu, 17 Sep 2026 22:46:23 -0400 Subject: [PATCH 02/12] test: adds test to verify that types inheriting from generic types works (#346) issue has been fixed by some other previous fix --- .../Tests/OutputBased/GenericTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Cecilifier.Core.Tests/Tests/OutputBased/GenericTests.cs b/Cecilifier.Core.Tests/Tests/OutputBased/GenericTests.cs index 80a59a77..b4d1998b 100644 --- a/Cecilifier.Core.Tests/Tests/OutputBased/GenericTests.cs +++ b/Cecilifier.Core.Tests/Tests/OutputBased/GenericTests.cs @@ -75,4 +75,23 @@ class TestSubject """, "42 42"); } + + [TestCase("Bar")] + [TestCase("System.Collections.Generic.List")] + public void Class_InheritingFromGenericType_DoesNotThrow_Issue346(string baseType) + { + AssertOutput($$""" + using System; + + new Foo().Baz(); + + class Bar {} + + class Foo : {{baseType}} + { + public void Baz() => Console.WriteLine("Class with generic base type"); + } + """, + "Class with generic base type"); + } } From 35da5a42523070590ae7d50a9b5dcab5e1d078e7 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Wed, 16 Sep 2026 15:13:51 -0400 Subject: [PATCH 03/12] feat: improves APIs to abstract a couple operations to remove code hardwired to Mono.Cecil (#358) for instance: 1. Parameters definition 2. Type size/packing sizes --- .../MonoCecilDefinitionsFactory.cs | 60 +++++++++++++++---- .../TypeSystem/MonoCecilMemberResolver.cs | 2 +- ...temReflectionMetadataDefinitionsFactory.cs | 22 +++++++ .../Tests/Unit/AttributesTest.cs | 2 + .../Tests/Unit/DelegateTests.cs | 9 ++- .../Tests/Unit/MethodTests.cs | 2 + .../Tests/Unit/Miscellaneous.cs | 10 ++-- .../Tests/Unit/ParamsTests.cs | 2 + .../Tests/Unit/TypeTests.Records.cs | 6 ++ .../AST/DefaultParameterExtractorVisitor.cs | 15 ++--- Cecilifier.Core/AST/ExpressionVisitor.cs | 3 +- .../AST/NonCapturingLambdaProcessor.cs | 2 +- Cecilifier.Core/AST/SyntaxWalkerBase.cs | 23 +++---- .../AST/TypeDeclarationVisitor.Delegate.cs | 58 ++++++++++-------- .../IApiDriverDefinitionsFactory.cs | 5 ++ Cecilifier.Core/ApiDriver/MemberOptions.cs | 1 + .../PrimaryConstructor.Generator.cs | 6 +- .../PrivateImplementationDetails.Generator.cs | 2 +- .../CodeGeneration/Record.Generator.cs | 6 +- .../Extensions/CecilifierContextExtensions.cs | 2 +- .../Extensions/ISymbolExtensions.cs | 21 ++++--- .../Misc/CecilDefinitionsFactory.cs | 37 +----------- Cecilifier.Core/TypeSystem/ParameterSpec.cs | 11 +++- 23 files changed, 190 insertions(+), 117 deletions(-) diff --git a/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs b/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs index e56a69a1..6d9f26b3 100644 --- a/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs +++ b/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs @@ -147,20 +147,19 @@ public IEnumerable Method(IVisitorContext context, if (typeParameters.Count > 0) exps.Add($"{definitionContext.Member.DefinitionVariable}.ReturnType = {returnTypeResolver(context)};"); + if (definitionContext.Options.HasFlag(MemberOptions.IsRuntime)) + { + exps.Add($"{definitionContext.Member.DefinitionVariable}.IsRuntime = true;"); + } + + exps.Add($"{definitionContext.Member.DefinitionVariable}.HasThis = {(!definitionContext.Options.HasFlag(MemberOptions.Static)).ToKeyword()};"); + exps.Add($"{definitionContext.Member.DefinitionVariable}.IsStatic = {definitionContext.Options.HasFlag(MemberOptions.Static).ToKeyword()};"); + foreach (var parameter in parameters) { var paramVar = context.Naming.SyntheticVariable(parameter.Name, ElementKind.Parameter); - var parameterExp = CecilDefinitionsFactory.Parameter( - context, - parameter.Name, - parameter.RefKind, - parameter.ParamsAttributeName, // for now,the only callers for this method don't have any `params` parameters. - definitionContext.Member.DefinitionVariable, - paramVar, - parameter.ElementTypeResolver != null ? parameter.ElementTypeResolver(context, parameter) : parameter.ElementType, - parameter.Attributes, - (parameter.DefaultValue, parameter.DefaultValue != null)); - + var parameterExp = Parameter(context, parameter, definitionContext.Member.DefinitionVariable, paramVar); + context.DefinitionVariables.RegisterNonMethod(definitionContext.Member.Identifier, parameter.Name, VariableMemberKind.Parameter, paramVar); exps.AddRange(parameterExp); } @@ -180,6 +179,25 @@ public IEnumerable Method(IVisitorContext context, return exps; } + public IEnumerable Parameter(IVisitorContext context, ParameterSpec parameterSpec, string memberVar, string paramVar) + { + var exps = new List(); + + var parameterType = parameterSpec.ElementTypeResolver != null ? parameterSpec.ElementTypeResolver(context, parameterSpec) : parameterSpec.ElementType; + exps.Add($"var {paramVar} = {ParameterDoesNotHandleParamsKeywordOrDefaultValue(context.TypeResolver, parameterSpec.Name, parameterSpec.RefKind, parameterType, parameterSpec.Attributes)};"); + if (!string.IsNullOrWhiteSpace(parameterSpec.ParamsAttributeName)) + { + exps.Add($"{paramVar}.CustomAttributes.Add(new CustomAttribute(assembly.MainModule.Import(typeof({parameterSpec.ParamsAttributeName}).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null))));"); + } + + if (parameterSpec.DefaultValue.Present) + exps.Add($"{paramVar}.Constant = {parameterSpec.DefaultValue.Value ?? "null" };"); + + exps.Add($"{memberVar}.Parameters.Add({paramVar});"); + + return exps; + } + public IEnumerable FieldReference(IVisitorContext context, string fieldReferenceVariable, string fieldName, ResolvedType fieldType, in ResolvedType declaringType) { return @@ -496,6 +514,15 @@ static string CallingConventionToCecil(CallingConvention callingConvention) } } + public IEnumerable SetStructLayoutAttribute(IVisitorContext context, string structDefinitionVariable, TypeLayoutProperty[] properties) + { + return + [ + $"{structDefinitionVariable}.ClassSize = { properties.SingleOrDefault(p => p.Kind == TypeLayoutPropertyKind.ClassSize).Value };", + $"{structDefinitionVariable}.PackingSize = { properties.SingleOrDefault(p => p.Kind == TypeLayoutPropertyKind.PackingSize).Value };", + ]; + } + private static string CustomAttributeArgumentValueFor(IVisitorContext context, object argument) { if (argument is Array array) @@ -636,4 +663,15 @@ private ResolvedType ProcessRequiredModifiers(IVisitorContext context, ResolvedT return id; } + + private static string ParameterDoesNotHandleParamsKeywordOrDefaultValue(ITypeResolver resolver, string name, RefKind byRef, ResolvedType resolvedType, string? paramAttributes = null) + { + paramAttributes ??= Constants.ParameterAttributes.None; + if (RefKind.None != byRef) + { + resolvedType = resolver.MakeByRefType(resolvedType); + } + + return $"new ParameterDefinition(\"{name}\", {paramAttributes}, {resolvedType})"; + } } diff --git a/Cecilifier.ApiDriver.MonoCecil/TypeSystem/MonoCecilMemberResolver.cs b/Cecilifier.ApiDriver.MonoCecil/TypeSystem/MonoCecilMemberResolver.cs index 1b9a9d69..1d15081d 100644 --- a/Cecilifier.ApiDriver.MonoCecil/TypeSystem/MonoCecilMemberResolver.cs +++ b/Cecilifier.ApiDriver.MonoCecil/TypeSystem/MonoCecilMemberResolver.cs @@ -100,7 +100,7 @@ public string ResolveMethod(IMethodSymbol method) foreach (var parameter in method.Parameters) { var tempParamVar = context.Naming.SyntheticVariable(parameter.Name, ElementKind.Parameter); - var exps = CecilDefinitionsFactory.Parameter(context, parameter.OriginalDefinition, tempMethodVar, tempParamVar); + var exps = context.ApiDefinitionsFactory.Parameter(context, parameter.OriginalDefinition.ToParameterSpec(context, tempMethodVar), tempMethodVar, tempParamVar); context.Generate(exps); } diff --git a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs index e6634c8c..8db11e04 100644 --- a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs +++ b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs @@ -135,6 +135,18 @@ void DefineDelayed(SystemReflectionMetadataContext ctx, ref TypeDefinitionRecord } } + public void SetStructLayoutAttribute(IVisitorContext context, string structDefinitionVariable, TypeLayoutProperty[] properties) + { + if (properties.Length == 0) + return; + + var packingSize = properties.SingleOrDefault(p => p.Kind == TypeLayoutPropertyKind.PackingSize).Value; + var clasSize = properties.SingleOrDefault(p => p.Kind == TypeLayoutPropertyKind.ClassSize).Value; + + context.Generate($"metadata.AddTypeLayout({structDefinitionVariable}, {packingSize}, {clasSize});"); + context.WriteNewLine(); + } + public void UpdateBaseTypeIfNeeded(IVisitorContext context, ITypeSymbol typeSymbol, string typeDefinitionVariable) { // No op on SRM. @@ -337,6 +349,11 @@ public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefi }); } + public IEnumerable Parameter(IVisitorContext context, ParameterSpec parameterSpec, string memberVar, string paramVar) + { + throw new NotImplementedException(); + } + public IEnumerable Field(IVisitorContext context, in MemberDefinitionContext definitionContext, ISymbol fieldOrEvent, ITypeSymbol fieldType, string fieldAttributes, bool isVolatile, bool isByRef, in FieldInitializationData initializer = default) { var resolvedType = context.TypeResolver.Resolve(fieldType, new TypeResolutionContext(ResolveTargetKind.Field, fieldType.ElementTypeSymbolOf().IsValueType ? TypeResolutionOptions.IsValueType : TypeResolutionOptions.None)); @@ -626,6 +643,11 @@ static DefinitionVariable GetOrRegisterModuleReference(IVisitorContext context, } } + IEnumerable IApiDriverDefinitionsFactory.SetStructLayoutAttribute(IVisitorContext context, string structDefinitionVariable, TypeLayoutProperty[] properties) + { + return []; + } + private SystemReflectionMetadataContext TypedContext(IVisitorContext context) => ((SystemReflectionMetadataContext) context); static void EmitLocalVariables(SystemReflectionMetadataContext context, string methodName, ref readonly MethodDefinitionRecord methodRecord) diff --git a/Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs b/Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs index d2210b48..4f250d70 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs @@ -162,6 +162,8 @@ class MyGenericAttribute : System.Attribute \s+\k.GenericParameters.Add\(gp_T_\d+\); \s+assembly.MainModule.Types.Add\(\k\); \s+var ctor_myGenericAttribute_4 = new MethodDefinition\(".ctor",.+TypeSystem.Void\); + \s+ctor_myGenericAttribute_4.HasThis = true; + \s+ctor_myGenericAttribute_4.IsStatic = false; \s+ctor_myGenericAttribute_4\.Body\.InitLocals = true; \s+var (?attr_myGenericAttribute_\d+) = new CustomAttribute\(new MethodReference\(ctor_myGenericAttribute_4.Name, ctor_myGenericAttribute_4.ReturnType\) {.+DeclaringType = \k.MakeGenericInstanceType\(.+Int32\).+}\); \s+\k.CustomAttributes.Add\(\k\); diff --git a/Cecilifier.Core.Tests/Tests/Unit/DelegateTests.cs b/Cecilifier.Core.Tests/Tests/Unit/DelegateTests.cs index ade43898..bcd39561 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/DelegateTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/DelegateTests.cs @@ -20,8 +20,13 @@ public void InnerDelegateDeclaration() Assert.That(cecilifiedCode, Does.Match(@"var m_invoke_\d+ = new MethodDefinition\(""Invoke"", .+assembly.MainModule.TypeSystem.Int32\)")); Assert.That(cecilifiedCode, Does.Match(@"var m_beginInvoke_\d+ = new MethodDefinition\(""BeginInvoke"",.+ImportReference\(typeof\(System\.IAsyncResult\)\)\).*")); - Assert.That(cecilifiedCode, Does.Match(@"m_beginInvoke_\d+.Parameters.Add\(new ParameterDefinition\(assembly.MainModule.ImportReference\(typeof\(System.AsyncCallback\)\)\)\);\s+")); - Assert.That(cecilifiedCode, Does.Match(@"m_beginInvoke_\d+.Parameters.Add\(new ParameterDefinition\(assembly.MainModule.TypeSystem.Object\)\);\s+")); + + Assert.That(cecilifiedCode, Does.Match(""" + \s+var (?p_asyncCallback_\d+) = new ParameterDefinition\("asyncCallback", ParameterAttributes.None, .+ImportReference\(typeof\(System.AsyncCallback\)\)\); + \s+m_beginInvoke_\d+.Parameters.Add\(\k\); + \s+var (?p_target_\d+) = new ParameterDefinition\("target", ParameterAttributes.None, assembly.MainModule.TypeSystem.Object\); + \s+m_beginInvoke_\d+.Parameters.Add\(\k\); + """)); Assert.That(cecilifiedCode, Does.Match(@"var m_endInvoke_\d+ = new MethodDefinition\(""EndInvoke"",.+assembly.MainModule.TypeSystem.Int32\);\s+")); Assert.That(cecilifiedCode, Does.Match(@"var p_ar_\d+ = new ParameterDefinition\(""ar"",.+ImportReference\(typeof\(System.IAsyncResult\)\)\);")); diff --git a/Cecilifier.Core.Tests/Tests/Unit/MethodTests.cs b/Cecilifier.Core.Tests/Tests/Unit/MethodTests.cs index 1807caed..3c2c5a14 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/MethodTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/MethodTests.cs @@ -22,6 +22,8 @@ public class Derived : Base { public override Derived P => this; } Assert.That(cecilifiedCode, Does.Match( """ \s+var (m_get_\d+) = new MethodDefinition\("get_P",.+(cls_derived_\d+)\); + \s+\1\.HasThis = true; + \s+\1\.IsStatic = false; \s+\2.Methods.Add\(m_get_\d+\); \s+\1.Overrides.Add\(m_get_2\); """)); diff --git a/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.cs b/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.cs index d4884ec2..2e971df9 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.cs @@ -38,10 +38,12 @@ public static class ObjectMaker { public static bool Test(CustomDelegate cd) => cd == null; }"; - var expectedCecilifiedCode = @"il_test_10.Emit\(OpCodes.Ldarg_0\).+\s+" + - @"il_test_10.Emit\(OpCodes.Ldnull\).+\s+" + - @"il_test_10.Emit\(OpCodes.Ceq\).+\s+" + - @"il_test_10.Emit\(OpCodes.Ret\);"; + var expectedCecilifiedCode = """ + (?\s+il_test_\d+.Emit\(OpCodes\.)Ldarg_0\); + \kLdnull\); + \kCeq\); + \kRet\); + """; var result = RunCecilifier(code); var cecilifiedCode = result.GeneratedCode.ReadToEnd(); diff --git a/Cecilifier.Core.Tests/Tests/Unit/ParamsTests.cs b/Cecilifier.Core.Tests/Tests/Unit/ParamsTests.cs index afe491e7..ec620c56 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/ParamsTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/ParamsTests.cs @@ -91,6 +91,8 @@ void M(params {{paramsType}} items) { } Does.Match($""" //M\(1, 2, 3\); \s+var (?m_M_\d+) = new MethodDefinition\(".+g__M\|0_0", MethodAttributes.Private, assembly.MainModule.TypeSystem.Void\); + \s+\k\.HasThis = true; + \s+\k\.IsStatic = false; \s+var (?p_items_\d+) = new ParameterDefinition\("items", ParameterAttributes.None,{actualCecilParameterType}.?\); \s+\k\.CustomAttributes\.Add\(new CustomAttribute\(.+{paramsAttribute}\)\.GetConstructor\(.+\)\)\)\); \s+\k\.Parameters\.Add\(\k\); diff --git a/Cecilifier.Core.Tests/Tests/Unit/TypeTests.Records.cs b/Cecilifier.Core.Tests/Tests/Unit/TypeTests.Records.cs index 3ba02c43..96b412a1 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/TypeTests.Records.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/TypeTests.Records.cs @@ -77,6 +77,8 @@ public void RecordType_Implements_IEquatable(string classOrStruct) Assert.That(cecilifiedCode, Does.Match(""" //IEquatable<>.Equals\(TheRecord other\) \s+var (?m_equals_\d+) = new MethodDefinition\("Equals", (MethodAttributes.)Public \| \1HideBySig \| \1NewSlot \| \1Virtual, .+TypeSystem.Boolean\); + \s+\k\.HasThis = true; + \s+\k\.IsStatic = false; \s+var (?p_other_\d+) = new ParameterDefinition\("other", ParameterAttributes.None, (?rec_theRecord_\d+)\); \s+\k.Parameters.Add\(\k\); \s+\k.Methods.Add\(\k\); @@ -186,6 +188,8 @@ private static void AssertPropertiesFromPrimaryConstructor(string[] expectedName Assert.That(cecilifiedCode, Does.Match($""" //{propertyName} getter \s+var (m_get{propertyName}_\d+) = new MethodDefinition\("get_{propertyName}", (MethodAttributes\.)Public \| \2HideBySig \| \2SpecialName, {propertyType}\); + \s+\1\.HasThis = true; + \s+\1\.IsStatic = false; \s+rec_theRecord_\d+.Methods.Add\(\1\); """)); @@ -193,6 +197,8 @@ private static void AssertPropertiesFromPrimaryConstructor(string[] expectedName Assert.That(cecilifiedCode, Does.Match($""" //{propertyName} init \s+var (m_set{propertyName}_\d+) = new MethodDefinition\("set_{propertyName}", (MethodAttributes\.)Public \| \2HideBySig \| \2SpecialName, new RequiredModifierType\(.+ImportReference\(typeof\(.+IsExternalInit\)\), .+Void\)\); + \s+\1.HasThis = true; + \s+\1.IsStatic = false; \s+var (p_value_\d+) = new ParameterDefinition\("value", ParameterAttributes.None, {propertyType}\); \s+\1.Parameters.Add\(\3\); \s+rec_theRecord_\d+.Methods.Add\(\1\); diff --git a/Cecilifier.Core/AST/DefaultParameterExtractorVisitor.cs b/Cecilifier.Core/AST/DefaultParameterExtractorVisitor.cs index e4708bc8..0840cbd5 100644 --- a/Cecilifier.Core/AST/DefaultParameterExtractorVisitor.cs +++ b/Cecilifier.Core/AST/DefaultParameterExtractorVisitor.cs @@ -1,11 +1,12 @@ using Cecilifier.Core.Extensions; +using Cecilifier.Core.TypeSystem; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Cecilifier.Core.AST; -internal class DefaultParameterExtractorVisitor : CSharpSyntaxVisitor +internal class DefaultParameterExtractorVisitor : CSharpSyntaxVisitor { public static DefaultParameterExtractorVisitor Instance { get; private set; } @@ -20,20 +21,20 @@ private DefaultParameterExtractorVisitor(IVisitorContext context) this.context = context; } - public override string VisitParameter(ParameterSyntax node) + public override DefaultValue VisitParameter(ParameterSyntax node) { if (node.Default == null) - return null; + return default; return node.Default.Value.Accept(this); } - public override string VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node) + public override DefaultValue VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node) { - return $"{node.OperatorToken}{node.Operand.Accept(this)}"; + return $"{node.OperatorToken}{node.Operand.Accept(this).Value}"; } - public override string VisitLiteralExpression(LiteralExpressionSyntax node) + public override DefaultValue VisitLiteralExpression(LiteralExpressionSyntax node) { if (node.IsKind(SyntaxKind.DefaultLiteralExpression)) return context.GetTypeInfo(node).Type.ValueForDefaultLiteral() ?? "null"; @@ -45,7 +46,7 @@ public override string VisitLiteralExpression(LiteralExpressionSyntax node) return literalValue; } - public override string VisitDefaultExpression(DefaultExpressionSyntax node) => context.GetTypeInfo(node.Type).Type.ValueForDefaultLiteral(); + public override DefaultValue VisitDefaultExpression(DefaultExpressionSyntax node) => context.GetTypeInfo(node.Type).Type.ValueForDefaultLiteral(); private readonly IVisitorContext context; } diff --git a/Cecilifier.Core/AST/ExpressionVisitor.cs b/Cecilifier.Core/AST/ExpressionVisitor.cs index ce51d4c7..a3c55d84 100644 --- a/Cecilifier.Core/AST/ExpressionVisitor.cs +++ b/Cecilifier.Core/AST/ExpressionVisitor.cs @@ -1549,7 +1549,8 @@ private string ArgumentValueToUseForDefaultParameter(IParameterSymbol arg, Immut return arguments[expressionParameter.Ordinal].Expression.ToFullString(); } - return arg.ExplicitDefaultValue(rawString: true).Value; + var defaultValue = arg.ExplicitDefaultValue(rawString: true); + return defaultValue.Value!; } private void HandleIdentifier(SimpleNameSyntax node) diff --git a/Cecilifier.Core/AST/NonCapturingLambdaProcessor.cs b/Cecilifier.Core/AST/NonCapturingLambdaProcessor.cs index db0a8c90..1b3c12c4 100644 --- a/Cecilifier.Core/AST/NonCapturingLambdaProcessor.cs +++ b/Cecilifier.Core/AST/NonCapturingLambdaProcessor.cs @@ -73,7 +73,7 @@ private static void InjectSyntheticMethodsFor(IVisitorContext context, string de // if/when we decide to support lambdas that captures variables/fields/params/etc we will probably need to revisit this. var methodExps = context.ApiDefinitionsFactory.Method( context, - new BodiedMemberDefinitionContext(syntheticMethodName,methodVar, declaringTypeVarName, MemberOptions.None, IlContext.None), + new BodiedMemberDefinitionContext(syntheticMethodName,methodVar, declaringTypeVarName, MemberOptions.Static, IlContext.None), lambda.ToString(), "MethodAttributes.Public | MethodAttributes.Static", lambda.ParameterList().Select(ParamSpecFor).ToArray(), diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index 001363f2..32edd600 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -5,6 +5,7 @@ using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; +using Cecilifier.Core.ApiDriver; using Cecilifier.Core.ApiDriver.Handles; using Cecilifier.Core.Extensions; using Cecilifier.Core.Misc; @@ -715,7 +716,7 @@ private static void HandleAttributesInMemberDeclaration(IVisitorContext context, var attrsExp = attrType.AttributeKind() switch { AttributeKind.DllImport => ProcessDllImportAttribute(context, memberName, attribute, targetDeclarationVar), - AttributeKind.StructLayout => ProcessStructLayoutAttribute(attribute, targetDeclarationVar), + AttributeKind.StructLayout => ProcessStructLayoutAttribute(context, attribute, targetDeclarationVar), _ => ProcessNormalMemberAttribute(context, attribute, targetDeclarationVar, targetKind) }; @@ -729,18 +730,20 @@ private static IEnumerable ProcessDllImportAttribute(IVisitorContext con return context.ApiDefinitionsFactory.PInvoke(context, moduleName, methodVar, methodName, attribute.ArgumentList.ToCustomAttributeArguments(context).ToArray()); } - private static IEnumerable ProcessStructLayoutAttribute(AttributeSyntax attribute, string typeVar) + private static IEnumerable ProcessStructLayoutAttribute(IVisitorContext context, AttributeSyntax attribute, string typeVar) { Debug.Assert(attribute.ArgumentList != null); if (attribute.ArgumentList.Arguments.Count == 0 || attribute.ArgumentList.Arguments.All(a => a.NameEquals == null)) - return Array.Empty(); - - return new[] - { - $"{typeVar}.ClassSize = { AssignedValue(attribute, "Size") };", - $"{typeVar}.PackingSize = { AssignedValue(attribute, "Pack") };", - }; - + return []; + + return context.ApiDefinitionsFactory.SetStructLayoutAttribute( + context, + typeVar, + [ + new TypeLayoutProperty(TypeLayoutPropertyKind.ClassSize, AssignedValue(attribute, "Size") + ""), + new TypeLayoutProperty(TypeLayoutPropertyKind.PackingSize, AssignedValue(attribute, "Pack") + "") + ]); + static int AssignedValue(AttributeSyntax attribute, string parameterName) { // whenever Size/Pack are omitted the corresponding property should be set to 0. See Ecma-335 II 22.8. diff --git a/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs b/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs index c3a0aa12..fe789cec 100644 --- a/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs +++ b/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs @@ -57,11 +57,16 @@ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) paramTypes, "IsRuntime = true"); Context.Generate(exps); - AddCecilExpression($"{ctorLocalVar}.Parameters.Add(new ParameterDefinition({Context.TypeResolver.Bcl.System.Object}));"); - AddCecilExpression($"{ctorLocalVar}.Parameters.Add(new ParameterDefinition({Context.TypeResolver.Bcl.System.IntPtr}));"); + + exps = Context.ApiDefinitionsFactory.Parameter(Context, new ParameterSpec("target", Context.TypeResolver.Bcl.System.Object, RefKind.None, Constants.ParameterAttributes.None), ctorLocalVar, Context.Naming.Parameter("target")); + Context.Generate(exps); + + exps = Context.ApiDefinitionsFactory.Parameter(Context, new ParameterSpec("method", Context.TypeResolver.Bcl.System.IntPtr, RefKind.None, Constants.ParameterAttributes.None), ctorLocalVar, Context.Naming.Parameter("method")); + Context.Generate(exps); // Invoke() method AddDelegateMethod( + node.Identifier.Text, typeVar, "Invoke", ResolveType(node.ReturnType, ResolveTargetKind.ReturnType), @@ -70,49 +75,50 @@ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) // BeginInvoke() method var beginInvokeMethodVar = AddDelegateMethod( - typeVar, - "BeginInvoke", - Context.TypeResolver.Bcl.System.IAsyncResult, - node.ParameterList.Parameters, - (methodVar, param) => CecilDefinitionsFactory.Parameter(Context, param, methodVar, Context.Naming.Parameter(param))); + node.Identifier.Text, + typeVar, + "BeginInvoke", + Context.TypeResolver.Bcl.System.IAsyncResult, + node.ParameterList.Parameters, + (methodVar, param) => CecilDefinitionsFactory.Parameter(Context, param, methodVar, Context.Naming.Parameter(param))); - AddCecilExpression($"{beginInvokeMethodVar}.Parameters.Add(new ParameterDefinition({Context.TypeResolver.Bcl.System.AsyncCallback}));"); - AddCecilExpression($"{beginInvokeMethodVar}.Parameters.Add(new ParameterDefinition({Context.TypeResolver.Bcl.System.Object}));"); + exps = Context.ApiDefinitionsFactory.Parameter(Context, new ParameterSpec("asyncCallback", Context.TypeResolver.Bcl.System.AsyncCallback, RefKind.None, Constants.ParameterAttributes.None), beginInvokeMethodVar, Context.Naming.Parameter("asyncCallback")); + Context.Generate(exps); + + exps = Context.ApiDefinitionsFactory.Parameter(Context, new ParameterSpec("target", Context.TypeResolver.Bcl.System.Object, RefKind.None, Constants.ParameterAttributes.None), beginInvokeMethodVar, Context.Naming.Parameter("target")); + Context.Generate(exps); // EndInvoke() method var endInvokeMethodVar = Context.Naming.SyntheticVariable("EndInvoke", ElementKind.Method); var endInvokeExps = Context.ApiDefinitionsFactory.Method( Context, - new BodiedMemberDefinitionContext("EndInvoke", endInvokeMethodVar, typeVar, MemberOptions.None, IlContext.None), + new BodiedMemberDefinitionContext("EndInvoke", endInvokeMethodVar, typeVar, MemberOptions.IsRuntime, IlContext.None), "declaringTypeName", Constants.Cecil.DelegateMethodAttributes, [new ParameterSpec("ar", Context.TypeResolver.Bcl.System.IAsyncResult, RefKind.None, Constants.ParameterAttributes.None)], [], ctx => ctx.TypeResolver.Resolve(Context.GetTypeInfo(node.ReturnType).Type, ResolveTargetKind.ReturnType), out var _); - - endInvokeExps = endInvokeExps.Concat([$"{endInvokeMethodVar}.HasThis = true;", $"{endInvokeMethodVar}.IsRuntime = true;"]); AddCecilExpressions(Context, endInvokeExps); base.VisitDelegateDeclaration(node); } - string AddDelegateMethod(string typeLocalVar, string methodName, ResolvedType returnType, in SeparatedSyntaxList parameters, Func> parameterHandler) + string AddDelegateMethod(string delegateName, string delegateTypeVariable, string methodName, ResolvedType returnType, in SeparatedSyntaxList parameters, Func> parameterHandler) { var methodLocalVar = Context.Naming.SyntheticVariable(methodName, ElementKind.Method); - AddCecilExpression( - $@"var {methodLocalVar} = new MethodDefinition(""{methodName}"", {Constants.Cecil.DelegateMethodAttributes}, {returnType}) - {{ - HasThis = true, - IsRuntime = true, - }};"); - - foreach (var param in parameters) - { - AddCecilExpressions(Context, parameterHandler(methodLocalVar, param)); - } - - AddCecilExpression($"{typeLocalVar}.Methods.Add({methodLocalVar});"); + BodiedMemberDefinitionContext methodDefinitionContext = new(methodName, methodLocalVar, delegateTypeVariable, MemberOptions.IsRuntime, Context.ApiDriver.NewIlContext(Context, methodName, methodLocalVar)); + var methodToAdd = Context.ApiDefinitionsFactory.Method( + Context, + methodDefinitionContext, + delegateName, + Constants.Cecil.DelegateMethodAttributes, + parameters.Select(param => Context.SemanticModel.GetDeclaredSymbol(param).EnsureNotNull().ToParameterSpec(Context, methodLocalVar)).ToArray(), + [], + ctx => returnType, + out var methodDefinitionVariable); + Context.Generate(methodToAdd); + return methodLocalVar; } } diff --git a/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs b/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs index aa8e9d18..6b8d8901 100644 --- a/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs +++ b/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs @@ -76,6 +76,9 @@ public IEnumerable Method(IVisitorContext context, ); public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, string[] paramTypes, string? methodDefinitionPropertyValues = null); + + public IEnumerable Parameter(IVisitorContext context, ParameterSpec parameterSpec, string memberVar, string paramVar); + public IEnumerable Field(IVisitorContext context, in MemberDefinitionContext definitionContext, ISymbol fieldOrEvent, ITypeSymbol fieldType, string fieldAttributes, bool isVolatile, bool isByRef, in FieldInitializationData initializer = default); public IEnumerable Field(IVisitorContext context, MemberDefinitionContext definitionContext, string declaringTypeName, ResolvedType fieldType, string fieldAttributes, bool isVolatile, bool isByRef, FieldInitializationData initializer = default); public IEnumerable FieldReference(IVisitorContext context, string fieldReferenceVariable, string fieldName, ResolvedType fieldType, in ResolvedType declaringType); @@ -98,4 +101,6 @@ public IEnumerable Method(IVisitorContext context, void OverrideBaseMethod(IVisitorContext context, string overriderMethodVar, string? overridenMethod); IEnumerable PInvoke(IVisitorContext context, string moduleName, string methodVar, string methodName, ReadOnlySpan customAttributeArguments); + + IEnumerable SetStructLayoutAttribute(IVisitorContext context, string structDefinitionVariable, TypeLayoutProperty[] properties); } diff --git a/Cecilifier.Core/ApiDriver/MemberOptions.cs b/Cecilifier.Core/ApiDriver/MemberOptions.cs index c5a944bc..c7a79b95 100644 --- a/Cecilifier.Core/ApiDriver/MemberOptions.cs +++ b/Cecilifier.Core/ApiDriver/MemberOptions.cs @@ -9,4 +9,5 @@ public enum MemberOptions None = 0, Static = 0x1 << 0, InitOnly = 0x1 << 1, + IsRuntime = 0x1 << 2, } diff --git a/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs b/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs index a1da81d1..ed2e9fb8 100644 --- a/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs +++ b/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs @@ -49,8 +49,6 @@ private static void AddPropertyFor(IVisitorContext context, ParameterSyntax para var exps = context.ApiDefinitionsFactory.Property(context, definitionContext, declaringTypeVariable.MemberName, [], context.TypeResolver.Resolve(paramSymbol.Type, ResolveTargetKind.None)); context.Generate(exps); - context.Generate($"{typeDefinitionVariable}.Properties.Add({propDefVar});"); - context.WriteNewLine(); context.WriteNewLine(); var publicPropertyMethodAttributes = "MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName"; @@ -144,8 +142,8 @@ internal static void AddPrimaryConstructor(IVisitorContext context, string recor { context.WriteComment($"Parameter: {parameter.Identifier}"); var paramVar = context.Naming.Parameter(parameter); - var parameterType = context.TypeResolver.Resolve(ModelExtensions.GetTypeInfo(context.SemanticModel, parameter.Type!).Type, ResolveTargetKind.Parameter); - var paramExps = CecilDefinitionsFactory.Parameter(context, parameter.Identifier.ValueText, RefKind.None, null, ctorVar, paramVar, parameterType, Constants.ParameterAttributes.None, ("", false)); + var parameterType = context.TypeResolver.Resolve(ModelExtensions.GetTypeInfo(context.SemanticModel, parameter.Type!).Type!, ResolveTargetKind.Parameter); + var paramExps = context.ApiDefinitionsFactory.Parameter(context, new ParameterSpec(parameter.Identifier.ValueText, parameterType, RefKind.None, Constants.ParameterAttributes.None), ctorVar, paramVar); context.Generate(paramExps); if (!uniqueParameters.Contains(parameter)) diff --git a/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.cs b/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.cs index 4ae9ab97..0b630c6c 100644 --- a/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.cs +++ b/Cecilifier.Core/CodeGeneration/PrivateImplementationDetails.Generator.cs @@ -69,7 +69,7 @@ private static DefinitionVariable GetOrEmmitInlineArrayAsSpanLikeMethod(IVisitor var methodTypeQualifiedName = $"{privateImplementationDetailsVar.MemberName}.{methodName}"; string declaringTypeName = $"{privateImplementationDetailsVar.MemberName}"; IReadOnlyList parameters = [ - new("buffer", "TBuffer", RefKind.Ref, Constants.ParameterAttributes.None, null, (context, paramSpec) => ResolveOwnedGenericParameter(context, paramSpec.ElementType.Expression, TypeParameterKind.Method, ResolveTargetKind.Parameter, methodTypeQualifiedName)), + new("buffer", "TBuffer", RefKind.Ref, Constants.ParameterAttributes.None, default, (context, paramSpec) => ResolveOwnedGenericParameter(context, paramSpec.ElementType.Expression, TypeParameterKind.Method, ResolveTargetKind.Parameter, methodTypeQualifiedName)), new("length", context.TypeResolver.Resolve(context.RoslynTypeSystem.SystemInt32, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None) ]; Func returnTypeResolver = ctx => diff --git a/Cecilifier.Core/CodeGeneration/Record.Generator.cs b/Cecilifier.Core/CodeGeneration/Record.Generator.cs index 3f793f28..ef04a842 100644 --- a/Cecilifier.Core/CodeGeneration/Record.Generator.cs +++ b/Cecilifier.Core/CodeGeneration/Record.Generator.cs @@ -131,7 +131,7 @@ private void AddCopyConstructor() context.Generate( [ ..exps, - ..CecilDefinitionsFactory.Parameter(context, "other", RefKind.None, null, copyCtorVar, context.Naming.Parameter("other"), context.TypeResolver.Resolve(_recordSymbol, ResolveTargetKind.Parameter), Constants.ParameterAttributes.None, (null, false)) + ..context.ApiDefinitionsFactory.Parameter(context, new ParameterSpec("other", context.TypeResolver.Resolve(_recordSymbol, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None), copyCtorVar, context.Naming.Parameter("other")) ]); } else @@ -208,7 +208,7 @@ private void AddEqualityOperator() Func returnTypeResolver = ctx => ctx.TypeResolver.Bcl.System.Boolean; var equalsOperatorMethodExps = context.ApiDefinitionsFactory.Method( context, - new BodiedMemberDefinitionContext(methodName, equalsOperatorMethodVar, recordTypeDefinitionVariable, MemberOptions.None, IlContext.None), + new BodiedMemberDefinitionContext(methodName, equalsOperatorMethodVar, recordTypeDefinitionVariable, MemberOptions.Static, IlContext.None), declaringTypeName, Constants.Cecil.PublicOverrideOperatorAttributes, parameters, @@ -267,7 +267,7 @@ private void AddInequalityOperator() Func returnTypeResolver = ctx => ctx.TypeResolver.Bcl.System.Boolean; var inequalityOperatorMethodExps = context.ApiDefinitionsFactory.Method( context, - new BodiedMemberDefinitionContext(methodName, inequalityOperatorMethodVar, recordTypeDefinitionVariable, MemberOptions.None, IlContext.None), + new BodiedMemberDefinitionContext(methodName, inequalityOperatorMethodVar, recordTypeDefinitionVariable, MemberOptions.Static, IlContext.None), declaringTypeName, Constants.Cecil.PublicOverrideOperatorAttributes, parameters, diff --git a/Cecilifier.Core/Extensions/CecilifierContextExtensions.cs b/Cecilifier.Core/Extensions/CecilifierContextExtensions.cs index 6c7d7130..4a1dd2d5 100644 --- a/Cecilifier.Core/Extensions/CecilifierContextExtensions.cs +++ b/Cecilifier.Core/Extensions/CecilifierContextExtensions.cs @@ -202,7 +202,7 @@ public static void EnsureForwardedMethod(this IVisitorContext context, IMethodSy new BodiedMemberDefinitionContext(methodName, methodNameForVariableRegistration,methodDeclarationVar, null, MemberOptions.None, IlContext.None), null, method.MethodsModifier(), - method.Parameters.Select( p => new ParameterSymbolParameterSpec(p, context)).ToArray(), + method.Parameters.Select( p => new ParameterSymbolParameterSpec(p, context, methodDeclarationVar)).ToArray(), method.GetTypeParameterSyntax().Select(tps => tps.Identifier.Text).ToArray(), ctx => method.ReturnsByRef ? context.TypeResolver.MakeByRefType(resolvedReturnType) diff --git a/Cecilifier.Core/Extensions/ISymbolExtensions.cs b/Cecilifier.Core/Extensions/ISymbolExtensions.cs index 4c0db5fd..ec22b506 100644 --- a/Cecilifier.Core/Extensions/ISymbolExtensions.cs +++ b/Cecilifier.Core/Extensions/ISymbolExtensions.cs @@ -180,23 +180,23 @@ public static string AsParameterAttribute(this IParameterSymbol symbol) return refRelatedAttr.AppendEnumFlag(optionalAttribute); } - public static (string Value, bool Present) ExplicitDefaultValue(this IParameterSymbol symbol, bool rawString = true) + public static DefaultValue ExplicitDefaultValue(this IParameterSymbol symbol, bool rawString = true) { if (!symbol.HasExplicitDefaultValue) - return (null, false); + return default; if (symbol.ExplicitDefaultValue == null) - return (null, true); + return new (null, true); if (symbol.Type.SpecialType == SpecialType.System_String && rawString) - return ((string)symbol.ExplicitDefaultValue, true); + return new((string)symbol.ExplicitDefaultValue, true); var value = SymbolDisplay.FormatPrimitive(symbol.ExplicitDefaultValue, !rawString, false); return symbol.Type.SpecialType switch { - SpecialType.System_Single => ($"{value}f", true), - SpecialType.System_Double => ($"{value}d", true), - _ => (value, true) + SpecialType.System_Single => new ($"{value}f", true), + SpecialType.System_Double => new ($"{value}d", true), + _ => new (value, true) }; } @@ -326,7 +326,12 @@ public static bool HasTypeArgumentOfTypeFromCecilifiedCodeTransitive(this INamed || (type.ContainingType != null && (SymbolEqualityComparer.Default.Equals(type.ContainingType, type) ? false : HasTypeArgumentOfTypeFromCecilifiedCodeTransitive(type.ContainingType, context))); } - public static TypeResolutionOptions GetTypeResolutionOptions(this ITypeSymbol type) => type.IsValueType ? TypeResolutionOptions.IsValueType : TypeResolutionOptions.None; + public static TypeResolutionOptions GetTypeResolutionOptions(this ITypeSymbol type) => type.IsValueType ? TypeResolutionOptions.IsValueType : TypeResolutionOptions.None; + + public static ParameterSpec ToParameterSpec(this IParameterSymbol parameter, IVisitorContext context, string methodVar) => new ParameterSymbolParameterSpec(parameter, context, methodVar) + { + DefaultValue = parameter.ExplicitDefaultValue(rawString: false), Attributes = parameter.AsParameterAttribute() + }; internal static ExpandedParamsArgumentHandler? CreateExpandedParamsUsageHandler(this IMethodSymbol methodSymbol, ExpressionVisitor expressionVisitor, IlContext ilVar, ArgumentListSyntax argumentList) { diff --git a/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs b/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs index 53f40453..499aa82c 100644 --- a/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs +++ b/Cecilifier.Core/Misc/CecilDefinitionsFactory.cs @@ -51,43 +51,12 @@ public static string ParameterDoesNotHandleParamsKeywordOrDefaultValue(ITypeReso return $"new ParameterDefinition(\"{name}\", {paramAttributes}, {resolvedType})"; } - public static IEnumerable Parameter(IVisitorContext ctx, string name, RefKind byRef, string? paramsAttributeTypeName, string methodVar, string paramVar, ResolvedType resolvedType, string paramAttributes, (string? Value, bool Present) defaultParameterValue) - { - var exps = new List(); - - exps.Add($"var {paramVar} = {ParameterDoesNotHandleParamsKeywordOrDefaultValue(ctx.TypeResolver, name, byRef, resolvedType, paramAttributes)};"); - if (!string.IsNullOrWhiteSpace(paramsAttributeTypeName)) - { - exps.Add($"{paramVar}.CustomAttributes.Add(new CustomAttribute(assembly.MainModule.Import(typeof({paramsAttributeTypeName}).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null))));"); - } - - if (defaultParameterValue.Present) - exps.Add($"{paramVar}.Constant = {defaultParameterValue.Value ?? "null" };"); - - exps.Add($"{methodVar}.Parameters.Add({paramVar});"); - - return exps; - } - public static IEnumerable Parameter(IVisitorContext context, ParameterSyntax node, string methodVar, string paramVar) { + // This has no ApiDriver specific APIs calls so for now I'll leave it in this class (even though it is not Cecil specific) var paramSymbol = context.SemanticModel.GetDeclaredSymbol(node); - TypeDeclarationVisitor.EnsureForwardedTypeDefinition(context, paramSymbol!.Type, Array.Empty()); - return Parameter(context, paramSymbol, methodVar, paramVar); - } - - public static IEnumerable Parameter(IVisitorContext context, IParameterSymbol paramSymbol, string methodVar, string paramVar) - { - return Parameter( - context, - paramSymbol.Name, - paramSymbol.RefKind, - paramSymbol.ParamsAttributeMatchingType(), - methodVar, - paramVar, - context.TypeResolver.Resolve(paramSymbol.Type, ResolveTargetKind.Parameter.ToTypeResolutionContext(methodVar)), - paramSymbol.AsParameterAttribute(), - paramSymbol.ExplicitDefaultValue(rawString: false)); + TypeDeclarationVisitor.EnsureForwardedTypeDefinition(context, paramSymbol!.Type, []); + return context.ApiDefinitionsFactory.Parameter(context, paramSymbol.ToParameterSpec(context, methodVar), methodVar, paramVar); } public static string DefaultTypeAttributeFor(TypeKind typeKind, bool hasStaticCtor) diff --git a/Cecilifier.Core/TypeSystem/ParameterSpec.cs b/Cecilifier.Core/TypeSystem/ParameterSpec.cs index 53ab254c..49c63fdd 100644 --- a/Cecilifier.Core/TypeSystem/ParameterSpec.cs +++ b/Cecilifier.Core/TypeSystem/ParameterSpec.cs @@ -6,16 +6,21 @@ namespace Cecilifier.Core.TypeSystem; -public record ParameterSpec(string Name, ResolvedType ElementType, RefKind RefKind, string Attributes, string? DefaultValue = null, Func? ElementTypeResolver = null) +public record ParameterSpec(string Name, ResolvedType ElementType, RefKind RefKind, string Attributes, DefaultValue DefaultValue = default, Func? ElementTypeResolver = null) { public virtual ResolvedType ElementType { get; } = ElementType; public string? RegistrationTypeName { get; init; } public virtual string? ParamsAttributeName { get; init; } } -public record ParameterSymbolParameterSpec(IParameterSymbol Parameter, IVisitorContext Context) : ParameterSpec(Parameter.Name, string.Empty, Parameter.RefKind, Constants.ParameterAttributes.None) +public record ParameterSymbolParameterSpec(IParameterSymbol Parameter, IVisitorContext Context, string MethodVariable) : ParameterSpec(Parameter.Name, string.Empty, Parameter.RefKind, Constants.ParameterAttributes.None) { - public override ResolvedType ElementType => Context.TypeResolver.Resolve(Parameter.Type, ResolveTargetKind.Parameter); + public override ResolvedType ElementType => Context.TypeResolver.Resolve(Parameter.Type, new TypeResolutionContext(ResolveTargetKind.Parameter, TypeResolutionOptions.None, MethodVariable)); public override string? ParamsAttributeName => Parameter.ParamsAttributeMatchingType(); } + +public record struct DefaultValue(string? Value, bool Present) +{ + public static implicit operator DefaultValue(string value) => new DefaultValue(value, true); +} From 94589e824211e71e2b1c9c09d5e14e901ecb2d50 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Thu, 17 Sep 2026 23:09:33 -0400 Subject: [PATCH 04/12] chore: bumps version to 2.29 --- Cecilifier.Common.props | 2 +- Cecilifier.Runtime/Cecilifier.Runtime.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cecilifier.Common.props b/Cecilifier.Common.props index e877ca5a..ee7651b5 100644 --- a/Cecilifier.Common.props +++ b/Cecilifier.Common.props @@ -2,7 +2,7 @@ net10.0 13 - 2.28.0 + 2.29.0 true true diff --git a/Cecilifier.Runtime/Cecilifier.Runtime.csproj b/Cecilifier.Runtime/Cecilifier.Runtime.csproj index 40ae6724..f891d9db 100644 --- a/Cecilifier.Runtime/Cecilifier.Runtime.csproj +++ b/Cecilifier.Runtime/Cecilifier.Runtime.csproj @@ -2,7 +2,7 @@ net9.0 13 - 2.22.0 + 2.29.0 true From 4b50eb849fbf06a8b86a47c65d4390bc10de1cb7 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 19 Sep 2026 00:29:32 -0400 Subject: [PATCH 05/12] feat: continue moving the code handling delegate declaration/usage towards an implementation based on ApiDriver APIs (#370) --- .../MonoCecilDefinitionsFactory.cs | 15 ++++- ...temReflectionMetadataDefinitionsFactory.cs | 2 +- .../Integration/Miscelaneous/MiscTestCase.cs | 1 + .../OutputBased/CollectionExpressionTests.cs | 2 +- .../AST/TypeDeclarationVisitor.Delegate.cs | 59 ++++++++++--------- .../IApiDriverDefinitionsFactory.cs | 2 +- .../PrimaryConstructor.Generator.cs | 15 ++--- .../CodeGeneration/Record.Generator.cs | 13 ++-- .../Extensions/SyntaxNodeExtensions.cs | 3 + Cecilifier.Core/TypeSystem/ParameterSpec.cs | 10 +++- 10 files changed, 69 insertions(+), 53 deletions(-) diff --git a/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs b/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs index 6d9f26b3..59a1639d 100644 --- a/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs +++ b/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs @@ -265,10 +265,10 @@ string OperandFor(InstructionRepresentation inst) } } - public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, string[] paramTypes, string? methodDefinitionPropertyValues = null) + public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters, string? methodDefinitionPropertyValues = null) { var ctorName = Utils.ConstructorMethodName(isStatic); - context.DefinitionVariables.RegisterMethod(typeName, ctorName, paramTypes, [], definitionContext.Member.DefinitionVariable); + context.DefinitionVariables.RegisterMethod(typeName, ctorName, parameters.Select(p => p.RegistrationTypeName).ToArray()!, [], definitionContext.Member.DefinitionVariable); var exp = $@"var {definitionContext.Member.DefinitionVariable} = new MethodDefinition(""{ctorName}"", {methodAccessibility} | MethodAttributes.HideBySig | {Constants.Cecil.CtorAttributes}, assembly.MainModule.TypeSystem.Void)"; if (methodDefinitionPropertyValues != null) @@ -276,7 +276,16 @@ public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefi exp = exp + $"{{ {methodDefinitionPropertyValues} }}"; } - return [exp + ";", $"{definitionContext.Member.ParentDefinitionVariable}.Methods.Add({definitionContext.Member.DefinitionVariable});"]; + var exps = new List { exp + ";" }; + foreach (var parameter in parameters) + { + var paramVar = context.Naming.Parameter("ctor"); + exps.AddRange(Parameter(context, parameter, definitionContext.Member.DefinitionVariable, paramVar)); + context.DefinitionVariables.RegisterNonMethod(definitionContext.Member.Identifier, parameter.Name, VariableMemberKind.Parameter, paramVar); + } + exps.Add($"{definitionContext.Member.ParentDefinitionVariable}.Methods.Add({definitionContext.Member.DefinitionVariable});"); + + return exps; } public IEnumerable Field(IVisitorContext context, in MemberDefinitionContext definitionContext, ISymbol fieldOrEvent, ITypeSymbol fieldType, string fieldAttributes, bool isVolatile, bool isByRef, in FieldInitializationData initializer = default) diff --git a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs index 8db11e04..cea7e472 100644 --- a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs +++ b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs @@ -314,7 +314,7 @@ public IEnumerable Method(IVisitorContext context, return []; } - public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, string[] paramTypes, string? methodDefinitionPropertyValues = null) + public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters, string? methodDefinitionPropertyValues = null) { var nameAsIdentifier = typeName.ToValidIdentifier(); var parameterlessCtorSignatureVar = context.Naming.SyntheticVariable($"{nameAsIdentifier}_ctorSignature", ElementKind.MemberReference); diff --git a/Cecilifier.Core.Tests/Tests/Integration/Miscelaneous/MiscTestCase.cs b/Cecilifier.Core.Tests/Tests/Integration/Miscelaneous/MiscTestCase.cs index 88770cbc..f630551f 100644 --- a/Cecilifier.Core.Tests/Tests/Integration/Miscelaneous/MiscTestCase.cs +++ b/Cecilifier.Core.Tests/Tests/Integration/Miscelaneous/MiscTestCase.cs @@ -13,6 +13,7 @@ namespace Cecilifier.Core.Tests.Integration nameof(TestDelegateInvocation), nameof(TestAccessibilityModifiers), nameof(TestNamespaces), + nameof(TestPointerTypes), nameof(AttributesOnMembers), nameof(TestAttributesOnExplicitTargets), nameof(TestAttributeWithArrayInitializer), diff --git a/Cecilifier.Core.Tests/Tests/OutputBased/CollectionExpressionTests.cs b/Cecilifier.Core.Tests/Tests/OutputBased/CollectionExpressionTests.cs index f1f14393..a1036ad9 100644 --- a/Cecilifier.Core.Tests/Tests/OutputBased/CollectionExpressionTests.cs +++ b/Cecilifier.Core.Tests/Tests/OutputBased/CollectionExpressionTests.cs @@ -12,7 +12,7 @@ namespace Cecilifier.Core.Tests.OutputBased; [TestFixture(typeof(MonoCecilContext), TestName = "Mono.Cecil")] [TestFixture(typeof(SystemReflectionMetadataContext), TestName = "System.Reflection.Metadata")] -[EnableForContext("ArrayWith3OrMoreElements", "ArrayWith2OrMoreElements", IgnoreReason = "Not implemented yet")] +[EnableForContext( nameof(ArrayWith3OrMoreElements), nameof(ArrayWith2OrLessElements), nameof(ImplicitNumericConversions_Are_Applied), nameof(BoxConversions_Are_Applied), IgnoreReason = "Not implemented yet")] public class CollectionExpressionTests : OutputBasedTestBase where TContext : IVisitorContext { [Test] diff --git a/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs b/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs index fe789cec..a95da9a5 100644 --- a/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs +++ b/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs @@ -47,46 +47,48 @@ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) var ctorLocalVar = Context.Naming.Delegate(node); // Delegate ctor - string[] paramTypes = ["System.Object", "System.IntPtr"]; + var parameters = new ParameterSpec[] + { + new("target", Context.TypeResolver.Bcl.System.Object, RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = "System.Object" }, + new("method", Context.TypeResolver.Bcl.System.IntPtr, RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = "System.IntPtr" }, + }; var exps = Context.ApiDefinitionsFactory.Constructor( Context, new BodiedMemberDefinitionContext("ctor", ctorLocalVar, typeVar, MemberOptions.None, IlContext.None), node.Identifier.Text, false, "MethodAttributes.FamANDAssem | MethodAttributes.Family", - paramTypes, + parameters, "IsRuntime = true"); Context.Generate(exps); - - exps = Context.ApiDefinitionsFactory.Parameter(Context, new ParameterSpec("target", Context.TypeResolver.Bcl.System.Object, RefKind.None, Constants.ParameterAttributes.None), ctorLocalVar, Context.Naming.Parameter("target")); - Context.Generate(exps); - - exps = Context.ApiDefinitionsFactory.Parameter(Context, new ParameterSpec("method", Context.TypeResolver.Bcl.System.IntPtr, RefKind.None, Constants.ParameterAttributes.None), ctorLocalVar, Context.Naming.Parameter("method")); - Context.Generate(exps); + var invokeMethodVar = Context.Naming.SyntheticVariable("Invoke", ElementKind.Method); + var invokeParameters = node.ParameterList.Parameters.Select(p => p.ToParameterSpec(Context, invokeMethodVar)).ToArray(); // Invoke() method AddDelegateMethod( node.Identifier.Text, typeVar, "Invoke", + invokeMethodVar, ResolveType(node.ReturnType, ResolveTargetKind.ReturnType), - node.ParameterList.Parameters, - (methodVar, param) => CecilDefinitionsFactory.Parameter(Context, param, methodVar, Context.Naming.Parameter(param))); - + invokeParameters); + // BeginInvoke() method - var beginInvokeMethodVar = AddDelegateMethod( - node.Identifier.Text, - typeVar, - "BeginInvoke", - Context.TypeResolver.Bcl.System.IAsyncResult, - node.ParameterList.Parameters, - (methodVar, param) => CecilDefinitionsFactory.Parameter(Context, param, methodVar, Context.Naming.Parameter(param))); - - exps = Context.ApiDefinitionsFactory.Parameter(Context, new ParameterSpec("asyncCallback", Context.TypeResolver.Bcl.System.AsyncCallback, RefKind.None, Constants.ParameterAttributes.None), beginInvokeMethodVar, Context.Naming.Parameter("asyncCallback")); - Context.Generate(exps); + var beginInvokeMethodVar = Context.Naming.SyntheticVariable("BeginInvoke", ElementKind.Method); + IReadOnlyList beginInvokeParameters = + [ + ..node.ParameterList.Parameters.Select(p => p.ToParameterSpec(Context, beginInvokeMethodVar)), + new("asyncCallback", Context.TypeResolver.Bcl.System.AsyncCallback, RefKind.None, Constants.ParameterAttributes.None), + new("target", Context.TypeResolver.Bcl.System.Object, RefKind.None, Constants.ParameterAttributes.None) + ]; - exps = Context.ApiDefinitionsFactory.Parameter(Context, new ParameterSpec("target", Context.TypeResolver.Bcl.System.Object, RefKind.None, Constants.ParameterAttributes.None), beginInvokeMethodVar, Context.Naming.Parameter("target")); - Context.Generate(exps); + AddDelegateMethod( + node.Identifier.Text, + typeVar, + "BeginInvoke", + beginInvokeMethodVar, + Context.TypeResolver.Bcl.System.IAsyncResult, + beginInvokeParameters); // EndInvoke() method var endInvokeMethodVar = Context.Naming.SyntheticVariable("EndInvoke", ElementKind.Method); @@ -104,22 +106,21 @@ [new ParameterSpec("ar", Context.TypeResolver.Bcl.System.IAsyncResult, RefKind.N base.VisitDelegateDeclaration(node); } - string AddDelegateMethod(string delegateName, string delegateTypeVariable, string methodName, ResolvedType returnType, in SeparatedSyntaxList parameters, Func> parameterHandler) + return; + + void AddDelegateMethod(string delegateName, string delegateTypeVariable, string methodName, string methodVar, ResolvedType returnType, IReadOnlyList parameters) { - var methodLocalVar = Context.Naming.SyntheticVariable(methodName, ElementKind.Method); - BodiedMemberDefinitionContext methodDefinitionContext = new(methodName, methodLocalVar, delegateTypeVariable, MemberOptions.IsRuntime, Context.ApiDriver.NewIlContext(Context, methodName, methodLocalVar)); + BodiedMemberDefinitionContext methodDefinitionContext = new(methodName, methodVar, delegateTypeVariable, MemberOptions.IsRuntime, Context.ApiDriver.NewIlContext(Context, methodName, methodVar)); var methodToAdd = Context.ApiDefinitionsFactory.Method( Context, methodDefinitionContext, delegateName, Constants.Cecil.DelegateMethodAttributes, - parameters.Select(param => Context.SemanticModel.GetDeclaredSymbol(param).EnsureNotNull().ToParameterSpec(Context, methodLocalVar)).ToArray(), + parameters, [], ctx => returnType, out var methodDefinitionVariable); Context.Generate(methodToAdd); - - return methodLocalVar; } } diff --git a/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs b/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs index 6b8d8901..ca416af8 100644 --- a/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs +++ b/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs @@ -75,7 +75,7 @@ public IEnumerable Method(IVisitorContext context, out MethodDefinitionVariable methodDefinitionVariable // we can't use the method name in some scenarios (indexers, for instance) ); - public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, string[] paramTypes, string? methodDefinitionPropertyValues = null); + public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters, string? methodDefinitionPropertyValues = null); public IEnumerable Parameter(IVisitorContext context, ParameterSpec parameterSpec, string memberVar, string paramVar); diff --git a/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs b/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs index ed2e9fb8..e44bd3d3 100644 --- a/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs +++ b/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs @@ -111,14 +111,14 @@ internal static void AddPrimaryConstructor(IVisitorContext context, string recor var ctorVar = context.Naming.Constructor(typeDeclaration, false); string typeName = typeSymbol.OriginalDefinition.ToDisplayString(); - string[] paramTypes = typeDeclaration.ParameterList?.Parameters.Select(p => p.Type?.ToString()).ToArray() ?? []; + var parameters = typeDeclaration.ParameterList?.Parameters.Select(p => p.ToParameterSpec(context, ctorVar)).ToArray() ?? []; var exps = context.ApiDefinitionsFactory.Constructor( context, new BodiedMemberDefinitionContext("ctor", ctorVar, recordTypeDefinitionVariable, MemberOptions.None, IlContext.None), typeName, false, "MethodAttributes.Public", - paramTypes, + parameters, null); var ctorExp = exps; context.Generate(ctorExp); @@ -140,17 +140,14 @@ internal static void AddPrimaryConstructor(IVisitorContext context, string recor var uniqueParameters = typeDeclaration.GetUniqueParameters(context).ToHashSet(); foreach (var parameter in typeDeclaration.ParameterList.Parameters) { - context.WriteComment($"Parameter: {parameter.Identifier}"); - var paramVar = context.Naming.Parameter(parameter); - var parameterType = context.TypeResolver.Resolve(ModelExtensions.GetTypeInfo(context.SemanticModel, parameter.Type!).Type!, ResolveTargetKind.Parameter); - var paramExps = context.ApiDefinitionsFactory.Parameter(context, new ParameterSpec(parameter.Identifier.ValueText, parameterType, RefKind.None, Constants.ParameterAttributes.None), ctorVar, paramVar); - context.Generate(paramExps); - if (!uniqueParameters.Contains(parameter)) continue; + var paramVar = context.DefinitionVariables.GetVariable(parameter.Identifier.ValueText, VariableMemberKind.Parameter, "ctor"); + paramVar.ThrowIfVariableIsNotValid(); + context.ApiDriver.WriteCilInstruction(context, ilContext, OpCodes.Ldarg_0); - context.ApiDriver.WriteCilInstruction(context, ilContext, OpCodes.Ldarg, paramVar); + context.ApiDriver.WriteCilInstruction(context, ilContext, OpCodes.Ldarg, paramVar.VariableName); var backingFieldVar = context.DefinitionVariables.GetVariable(Utils.BackingFieldNameForAutoProperty(parameter.Identifier.ValueText), VariableMemberKind.Field, typeSymbol.OriginalDefinition.ToDisplayString()); if (!backingFieldVar.IsValid) diff --git a/Cecilifier.Core/CodeGeneration/Record.Generator.cs b/Cecilifier.Core/CodeGeneration/Record.Generator.cs index ef04a842..0f7bc346 100644 --- a/Cecilifier.Core/CodeGeneration/Record.Generator.cs +++ b/Cecilifier.Core/CodeGeneration/Record.Generator.cs @@ -125,14 +125,12 @@ private void AddCopyConstructor() new BodiedMemberDefinitionContext("ctor", copyCtorVar, recordTypeDefinitionVariable, MemberOptions.None, IlContext.None), _recordSymbol.OriginalDefinition.ToDisplayString(), false, - Constants.Cecil.CtorAttributes.AppendEnumFlag("MethodAttributes.Family | MethodAttributes.HideBySig"), - new[] { _recordSymbol.ToDisplayString() }); + Constants.Cecil.CtorAttributes.AppendEnumFlag("MethodAttributes.Family | MethodAttributes.HideBySig"), + [ + new ParameterSpec("other", context.TypeResolver.Resolve(_recordSymbol, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = _recordSymbol.ToDisplayString() } + ]); - context.Generate( - [ - ..exps, - ..context.ApiDefinitionsFactory.Parameter(context, new ParameterSpec("other", context.TypeResolver.Resolve(_recordSymbol, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None), copyCtorVar, context.Naming.Parameter("other")) - ]); + context.Generate(exps); } else context.Generate($"{recordTypeDefinitionVariable}.Methods.Add({copyCtorVar});"); @@ -243,7 +241,6 @@ private void AddEqualityOperator() var bodyExps = context.ApiDefinitionsFactory.MethodBody(context, methodName, context.ApiDriver.NewIlContext(context, methodName, equalsOperatorMethodVar), [], equalsBodyInstructions); context.Generate(bodyExps); - context.Generate($"{recordTypeDefinitionVariable}.Methods.Add({equalsOperatorMethodVar});"); AddCompilerGeneratedAttributeTo(context, equalsOperatorMethodVar, VariableMemberKind.Method); if (!_recordSymbol.IsValueType) diff --git a/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs b/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs index 0e7175e7..bb04d3a9 100644 --- a/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs +++ b/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs @@ -120,6 +120,9 @@ public static MethodDispatchInformation MethodDispatchInformation(this SyntaxNod return nodeBeingAccessed.IsKind(SyntaxKind.NameColon) ? AST.MethodDispatchInformation.MostLikelyVirtual : AST.MethodDispatchInformation.MostLikelyNonVirtual ; } + + public static ParameterSpec ToParameterSpec(this ParameterSyntax parameter, IVisitorContext context, string methodVar) => + context.SemanticModel.GetDeclaredSymbol(parameter)?.ToParameterSpec(context, methodVar); #nullable enable [return: NotNull] diff --git a/Cecilifier.Core/TypeSystem/ParameterSpec.cs b/Cecilifier.Core/TypeSystem/ParameterSpec.cs index 49c63fdd..9b79cdaa 100644 --- a/Cecilifier.Core/TypeSystem/ParameterSpec.cs +++ b/Cecilifier.Core/TypeSystem/ParameterSpec.cs @@ -9,14 +9,22 @@ namespace Cecilifier.Core.TypeSystem; public record ParameterSpec(string Name, ResolvedType ElementType, RefKind RefKind, string Attributes, DefaultValue DefaultValue = default, Func? ElementTypeResolver = null) { public virtual ResolvedType ElementType { get; } = ElementType; - public string? RegistrationTypeName { get; init; } + public virtual string? RegistrationTypeName { get; init; } public virtual string? ParamsAttributeName { get; init; } } public record ParameterSymbolParameterSpec(IParameterSymbol Parameter, IVisitorContext Context, string MethodVariable) : ParameterSpec(Parameter.Name, string.Empty, Parameter.RefKind, Constants.ParameterAttributes.None) { + private readonly string? _registrationTypeName; + public override ResolvedType ElementType => Context.TypeResolver.Resolve(Parameter.Type, new TypeResolutionContext(ResolveTargetKind.Parameter, TypeResolutionOptions.None, MethodVariable)); + public override string? RegistrationTypeName + { + get => _registrationTypeName ?? Parameter.Type.ToDisplayString(); + init => _registrationTypeName = value; + } + public override string? ParamsAttributeName => Parameter.ParamsAttributeMatchingType(); } From f43293c87759c246b9a6aebf08390d6514ffb4d8 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 19 Sep 2026 13:43:03 -0400 Subject: [PATCH 06/12] refact: remove duplicated code --- Cecilifier.Core/AST/ConstructorDeclarationVisitor.cs | 2 +- Cecilifier.Core/AST/EnumDeclarationVisitor.cs | 6 +++--- Cecilifier.Core/AST/EventDeclarationVisitor.cs | 2 +- Cecilifier.Core/AST/FieldDeclarationVisitor.cs | 2 +- Cecilifier.Core/AST/MethodDeclarationVisitor.cs | 4 ++-- Cecilifier.Core/AST/SyntaxWalkerBase.cs | 10 +--------- Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs | 4 ++-- Cecilifier.Core/AST/TypeDeclarationVisitor.cs | 2 +- 8 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Cecilifier.Core/AST/ConstructorDeclarationVisitor.cs b/Cecilifier.Core/AST/ConstructorDeclarationVisitor.cs index d2832a9a..80401c7b 100644 --- a/Cecilifier.Core/AST/ConstructorDeclarationVisitor.cs +++ b/Cecilifier.Core/AST/ConstructorDeclarationVisitor.cs @@ -169,7 +169,7 @@ private IlContext AddOrUpdateParameterlessCtorDefinition(string typeName, string var ilContext = Context.ApiDriver.NewIlContext(Context, $"{ctorName}_{normalizedTypeName}", ctorLocalVar); var definitionContext = new BodiedMemberDefinitionContext(ctorName, ctorName, ctorLocalVar, typeDefVar, MemberOptions.None, ilContext); var exps = Context.ApiDefinitionsFactory.Constructor(Context, definitionContext, typeName, isStatic, ctorAccessibility, []); - AddCecilExpressions(Context, exps); + Context.Generate(exps); return ilContext; } diff --git a/Cecilifier.Core/AST/EnumDeclarationVisitor.cs b/Cecilifier.Core/AST/EnumDeclarationVisitor.cs index 47cecaae..d345d472 100644 --- a/Cecilifier.Core/AST/EnumDeclarationVisitor.cs +++ b/Cecilifier.Core/AST/EnumDeclarationVisitor.cs @@ -40,7 +40,7 @@ public override void VisitEnumDeclaration(EnumDeclarationSyntax node) [], [], []); - AddCecilExpressions(Context, typeDef); + Context.Generate(typeDef); var parentName = _enumSymbol.ContainingSymbol.ToDisplayString(); string declaringTypeName = _enumSymbol.ToDisplayString(); @@ -50,7 +50,7 @@ public override void VisitEnumDeclaration(EnumDeclarationSyntax node) var fieldVar = Context.Naming.LocalVariable(node); var definitionContext = new MemberDefinitionContext("value__", "value__", fieldVar, enumTypeVariable); var valueFieldExp = Context.ApiDefinitionsFactory.Field(Context, definitionContext, declaringTypeName, Context.TypeResolver.Resolve(Context.RoslynTypeSystem.SystemInt32, ResolveTargetKind.Field), "FieldAttributes.SpecialName | FieldAttributes.RTSpecialName | FieldAttributes.Public", false, false); - AddCecilExpressions(Context, valueFieldExp); + Context.Generate(valueFieldExp); HandleAttributesInMemberDeclaration(node.Identifier.Text, node.AttributeLists, enumTypeVariable, VariableMemberKind.Type); @@ -75,7 +75,7 @@ public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node // Re-resolve the enum type in a way that it will return a valid reference to be used as a field type. var enumType = Context.TypeResolver.Resolve(Context.SemanticModel.GetDeclaredSymbol(node.Parent).EnsureNotNull(), new TypeResolutionContext(ResolveTargetKind.Field, TypeResolutionOptions.IsValueType)); var exp = Context.ApiDefinitionsFactory.Field(Context, new MemberDefinitionContext(node.Identifier.ValueText, fieldVar, enumVarDef.VariableName), declaringTypeName, enumType, "FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.Public | FieldAttributes.HasDefault", false, false, enumMemberValue); - AddCecilExpressions(Context, exp); + Context.Generate(exp); HandleAttributesInMemberDeclaration(node.Identifier.Text, node.AttributeLists, fieldVar, VariableMemberKind.Field); diff --git a/Cecilifier.Core/AST/EventDeclarationVisitor.cs b/Cecilifier.Core/AST/EventDeclarationVisitor.cs index 3ec82129..afd0b6a6 100644 --- a/Cecilifier.Core/AST/EventDeclarationVisitor.cs +++ b/Cecilifier.Core/AST/EventDeclarationVisitor.cs @@ -135,7 +135,7 @@ private MethodDefinitionVariable AddAccessor(MemberDeclarationSyntax node, IEven ctx => ctx.TypeResolver.Resolve(Context.RoslynTypeSystem.SystemVoid, ResolveTargetKind.ReturnType), out var eventAccessorMethodVar); - AddCecilExpressions(Context, methodExps.Concat(methodBodyExpressions)); + Context.Generate(methodExps.Concat(methodBodyExpressions)); return eventAccessorMethodVar; } diff --git a/Cecilifier.Core/AST/FieldDeclarationVisitor.cs b/Cecilifier.Core/AST/FieldDeclarationVisitor.cs index 99d2faa9..412dd011 100644 --- a/Cecilifier.Core/AST/FieldDeclarationVisitor.cs +++ b/Cecilifier.Core/AST/FieldDeclarationVisitor.cs @@ -80,7 +80,7 @@ private IEnumerable HandleFieldDeclaration(MemberDeclarationSyntax node, modifiers.Any(m => m.IsKind(SyntaxKind.VolatileKeyword)), isByRef, constant.Value.ValueText()); - AddCecilExpressions(Context, exps); + Context.Generate(exps); HandleAttributesInMemberDeclaration(fieldSymbol.Name, node.AttributeLists, fieldVar, VariableMemberKind.Field); } diff --git a/Cecilifier.Core/AST/MethodDeclarationVisitor.cs b/Cecilifier.Core/AST/MethodDeclarationVisitor.cs index 59d3d140..194166e3 100644 --- a/Cecilifier.Core/AST/MethodDeclarationVisitor.cs +++ b/Cecilifier.Core/AST/MethodDeclarationVisitor.cs @@ -101,7 +101,7 @@ public override void VisitParameter(ParameterSyntax node) Context.DefinitionVariables.RegisterNonMethod(containingSymbol.OriginalDefinition.ToDisplayString(), node.Identifier.ValueText, VariableMemberKind.Parameter, paramVar); var exps = CecilDefinitionsFactory.Parameter(Context, node, methodVar.VariableName, paramVar); - AddCecilExpressions(Context, exps); + Context.Generate(exps); } HandleAttributesInMemberDeclaration(node.Identifier.Text, node.AttributeLists, paramVar, VariableMemberKind.Parameter); @@ -240,7 +240,7 @@ private void AddMethodDefinition( var declaringTypeVarName = context.DefinitionVariables.GetLastOf(VariableMemberKind.Type).VariableName; var exps = context.ApiDefinitionsFactory.Method(context, methodSymbol, new BodiedMemberDefinitionContext(methodName, simpleName, methodVar, declaringTypeVarName, MemberOptions.None, ilVar), methodName, methodModifiers, typeParameters); - AddCecilExpressions(context, exps); + context.Generate(exps); HandleAttributesInTypeParameter(context, typeParameters); } diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index 32edd600..aaf411af 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -30,14 +30,6 @@ internal SyntaxWalkerBase(IVisitorContext ctx) public IVisitorContext Context { get; } - protected static void AddCecilExpressions(IVisitorContext context, IEnumerable exps) - { - foreach (var exp in exps) - { - WriteCecilExpression(context, exp); - } - } - protected void AddCecilExpression(string exp) { WriteCecilExpression(Context, exp); @@ -720,7 +712,7 @@ private static void HandleAttributesInMemberDeclaration(IVisitorContext context, _ => ProcessNormalMemberAttribute(context, attribute, targetDeclarationVar, targetKind) }; - AddCecilExpressions(context, attrsExp); + context.Generate(attrsExp); } } diff --git a/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs b/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs index a95da9a5..8d18d6f7 100644 --- a/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs +++ b/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs @@ -39,7 +39,7 @@ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) node.TypeParameterList?.Parameters, []); - AddCecilExpressions(Context, typeDef); + Context.Generate(typeDef); HandleAttributesInMemberDeclaration(node.Identifier.Text, node.AttributeLists, typeVar, VariableMemberKind.Type); using (Context.DefinitionVariables.WithCurrent(delegateSymbol.ContainingSymbol?.OriginalDefinition.ToDisplayString() ?? string.Empty, delegateSymbol.OriginalDefinition.ToDisplayString(), VariableMemberKind.Type, typeVar)) @@ -101,7 +101,7 @@ [new ParameterSpec("ar", Context.TypeResolver.Bcl.System.IAsyncResult, RefKind.N [], ctx => ctx.TypeResolver.Resolve(Context.GetTypeInfo(node.ReturnType).Type, ResolveTargetKind.ReturnType), out var _); - AddCecilExpressions(Context, endInvokeExps); + Context.Generate(endInvokeExps); base.VisitDelegateDeclaration(node); } diff --git a/Cecilifier.Core/AST/TypeDeclarationVisitor.cs b/Cecilifier.Core/AST/TypeDeclarationVisitor.cs index 6b098570..8b57d936 100644 --- a/Cecilifier.Core/AST/TypeDeclarationVisitor.cs +++ b/Cecilifier.Core/AST/TypeDeclarationVisitor.cs @@ -205,7 +205,7 @@ private static void AddTypeDefinition(IVisitorContext context, string typeDeclar typeParameters, outerTypeParameters); - AddCecilExpressions(context, typeDefinitionExp); + context.Generate(typeDefinitionExp); context.ApiDefinitionsFactory.UpdateBaseTypeIfNeeded(context, typeSymbol, typeDeclarationVar); From 3c43094a80f923b9e69f4e7c4734730e98c903bd Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Mon, 21 Sep 2026 11:33:22 -0400 Subject: [PATCH 07/12] feat: implement delegate support on SRM (#370) this includes declaring, instantiating and executing delegates --- .../MonoCecilDefinitionsFactory.cs | 11 +++--- ...temReflectionMetadataDefinitionsFactory.cs | 39 ++++++++++++------- .../Integration/Miscelaneous/MiscTestCase.cs | 2 - .../Tests/Integration/Types/DelegatesTests.cs | 4 +- Cecilifier.Core/AST/SyntaxWalkerBase.cs | 14 ++++++- .../AST/TypeDeclarationVisitor.Delegate.cs | 32 ++++++++------- .../IApiDriverDefinitionsFactory.cs | 2 +- .../PrimaryConstructor.Generator.cs | 3 +- .../Extensions/CecilifierExtensions.cs | 13 +++++++ .../TypeSystem/RoslynTypeSystem.cs | 2 + 10 files changed, 78 insertions(+), 44 deletions(-) diff --git a/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs b/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs index 59a1639d..e6b33589 100644 --- a/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs +++ b/Cecilifier.ApiDriver.MonoCecil/MonoCecilDefinitionsFactory.cs @@ -265,18 +265,19 @@ string OperandFor(InstructionRepresentation inst) } } - public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters, string? methodDefinitionPropertyValues = null) + public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters) { var ctorName = Utils.ConstructorMethodName(isStatic); context.DefinitionVariables.RegisterMethod(typeName, ctorName, parameters.Select(p => p.RegistrationTypeName).ToArray()!, [], definitionContext.Member.DefinitionVariable); - var exp = $@"var {definitionContext.Member.DefinitionVariable} = new MethodDefinition(""{ctorName}"", {methodAccessibility} | MethodAttributes.HideBySig | {Constants.Cecil.CtorAttributes}, assembly.MainModule.TypeSystem.Void)"; - if (methodDefinitionPropertyValues != null) + var exps = new List(); + exps.Add($@"var {definitionContext.Member.DefinitionVariable} = new MethodDefinition(""{ctorName}"", {methodAccessibility} | MethodAttributes.HideBySig | {Constants.Cecil.CtorAttributes}, assembly.MainModule.TypeSystem.Void);"); + if (definitionContext.Options.HasFlag(MemberOptions.IsRuntime)) { - exp = exp + $"{{ {methodDefinitionPropertyValues} }}"; + exps[^1] = exps[^1].Substring(0, exps[^1].Length - 1); // removes the last `;` + exps.Add("{ IsRuntime = true };"); } - var exps = new List { exp + ";" }; foreach (var parameter in parameters) { var paramVar = context.Naming.Parameter("ctor"); diff --git a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs index cea7e472..8bf1837e 100644 --- a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs +++ b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs @@ -285,7 +285,7 @@ public IEnumerable Method(IVisitorContext context, ctx.Generate($""" var {methodDefVar} = metadata.AddMethodDefinition( {methodModifiers}, - MethodImplAttributes.IL | MethodImplAttributes.Managed, + { "MethodImplAttributes.IL | MethodImplAttributes.Managed".AppendEnumFlagConditional("MethodImplAttributes.Runtime", definitionContext.Options.HasFlag(MemberOptions.IsRuntime)) }, metadata.GetOrAddString("{definitionContext.Member.Name}"), {methodSignatureVar.VariableName}, {methodBodyOffset}, @@ -314,16 +314,21 @@ public IEnumerable Method(IVisitorContext context, return []; } - public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters, string? methodDefinitionPropertyValues = null) + public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters) { var nameAsIdentifier = typeName.ToValidIdentifier(); - var parameterlessCtorSignatureVar = context.Naming.SyntheticVariable($"{nameAsIdentifier}_ctorSignature", ElementKind.MemberReference); + var parameterCtorSignatureVar = context.Naming.SyntheticVariable($"{nameAsIdentifier}_ctorSignature", ElementKind.MemberReference); yield return Format( $$""" - var {{parameterlessCtorSignatureVar}} = new BlobBuilder(); - new BlobEncoder({{parameterlessCtorSignatureVar}}) + var {{parameterCtorSignatureVar}} = new BlobBuilder(); + new BlobEncoder({{parameterCtorSignatureVar}}) .MethodSignature(isInstanceMethod: {{ (!isStatic).ToKeyword()}}) - .Parameters(0, returnType => returnType.Void(), parameters => { }); + .Parameters({{parameters.Length}}, returnType => returnType.Void(), parameters => + { + {{ + string.Join('\n', parameters.Select(p => $"""parameters.AddParameter().{p.ElementType};""")) + }} + }); """); var parentDefinitionVariable = definitionContext.Member.ParentDefinitionVariable ?? throw new ArgumentNullException(nameof(definitionContext.Member.ParentDefinitionVariable)); @@ -331,17 +336,21 @@ public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefi { EmitLocalVariables(ctx, "ctor", in methodRecord); - Debug.Assert(definitionContext.IlContext != null); + var ctorBodyOffset = definitionContext.IlContext != null + ? $"methodBodyStream.AddMethodBody({definitionContext.IlContext.VariableName}, localVariablesSignature: {methodRecord.LocalSignatureHandleVariable})" + : "-1"; // ilcontext is null meaning the method don't have a body whence we need to set offset to -1 + var ctorDefVar = ctx.Naming.SyntheticVariable($"{nameAsIdentifier}_Ctor", ElementKind.MemberReference); + var ctorAttributes = "MethodImplAttributes.IL | MethodImplAttributes.Managed".AppendEnumFlagConditional("MethodImplAttributes.Runtime", definitionContext.Options.HasFlag(MemberOptions.IsRuntime)); ctx.Generate($""" - var {ctorDefVar} = metadata.AddMethodDefinition( - {(isStatic ? "MethodAttributes.Private | MethodAttributes.Static" : methodAccessibility)} | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, - MethodImplAttributes.IL | MethodImplAttributes.Managed, - metadata.GetOrAddString("{(isStatic ? ".cctor" : ".ctor")}"), - metadata.GetOrAddBlob({parameterlessCtorSignatureVar}), - methodBodyStream.AddMethodBody({definitionContext.IlContext.VariableName}, localVariablesSignature: {methodRecord.LocalSignatureHandleVariable}), - parameterList: {methodRecord.FirstParameterHandle}); - """); + var {ctorDefVar} = metadata.AddMethodDefinition( + {(isStatic ? "MethodAttributes.Private | MethodAttributes.Static" : methodAccessibility)} | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, + {ctorAttributes}, + metadata.GetOrAddString("{(isStatic ? ".cctor" : ".ctor")}"), + metadata.GetOrAddBlob({parameterCtorSignatureVar}), + {ctorBodyOffset}, + parameterList: {methodRecord.FirstParameterHandle}); + """); ctx.WriteNewLine(); ctx.WriteNewLine(); diff --git a/Cecilifier.Core.Tests/Tests/Integration/Miscelaneous/MiscTestCase.cs b/Cecilifier.Core.Tests/Tests/Integration/Miscelaneous/MiscTestCase.cs index f630551f..9979b273 100644 --- a/Cecilifier.Core.Tests/Tests/Integration/Miscelaneous/MiscTestCase.cs +++ b/Cecilifier.Core.Tests/Tests/Integration/Miscelaneous/MiscTestCase.cs @@ -26,7 +26,6 @@ public class MiscTestCase : ResourceTestBase where TResour [TestCase("Parameters2")] [TestCase("LocalVariables")] [TestCase("OnFields")] - [ParameterizedResourceFilter("Parameters2")] public void TestDelegateInvocation(string storageType) { AssertResourceTest($"Misc/DelegateInvocation_{storageType}"); @@ -86,7 +85,6 @@ public void TestFunctionPointer(string testName) [TestCase("InterfaceAndMembers")] [TestCase("EnumAndMembers")] [TestCase("StructAndMembers")] - [ParameterizedResourceFilter("ClassAndMembers", "InterfaceAndMembers", "StructAndMembers", "EnumAndMembers")] public void AttributesOnMembers(string typeKind) { AssertResourceTest($"Misc/Attributes/AttributesOn{typeKind}"); diff --git a/Cecilifier.Core.Tests/Tests/Integration/Types/DelegatesTests.cs b/Cecilifier.Core.Tests/Tests/Integration/Types/DelegatesTests.cs index c1b1ffdd..31ab235b 100644 --- a/Cecilifier.Core.Tests/Tests/Integration/Types/DelegatesTests.cs +++ b/Cecilifier.Core.Tests/Tests/Integration/Types/DelegatesTests.cs @@ -2,21 +2,19 @@ using Cecilifier.ApiDriver.SystemReflectionMetadata; using Cecilifier.Core.AST; using Cecilifier.Core.Tests.Framework; -using Cecilifier.Core.Tests.Framework.Attributes; using NUnit.Framework; namespace Cecilifier.Core.Tests.Integration.Types { [TestFixture(typeof(MonoCecilContext))] [TestFixture(typeof(SystemReflectionMetadataContext))] - [EnableForContext(IgnoreReason = "Not Supported")] public class DelegatesTests : ResourceTestBase where TResource : IVisitorContext { [TestCase("CustomDelegateMultipleParameters")] [TestCase("ParameterlessDelegates")] public void NumberOfParameters(string testName) { - AssertResourceTest(@$"Types/Delegates/{testName}"); + AssertResourceTest($"Types/Delegates/{testName}"); } } } diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index aaf411af..66eb39e8 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -657,11 +657,23 @@ private void HandlePotentialDelegateInvocationOn(SimpleNameSyntax node, ITypeSym } var localDelegateDeclaration = Context.TypeResolver.ResolveLocalVariableType(typeSymbol, ResolveTargetKind.TypeReference); - //TODO: Cecil dependent code. This assumes that local variable is a MethodReference or similar. Can't we simply look for the method variable instead? + + //TODO: FIX THIS HACK. We are checking localDelegateDeclarion var twice var resolvedMethod = localDelegateDeclaration != null ? $"{localDelegateDeclaration}.Methods.Single(m => m.Name == \"Invoke\")" : ((IMethodSymbol) typeSymbol.GetMembers("Invoke").SingleOrDefault()).MethodResolverExpression(Context); + if (localDelegateDeclaration != null) + { + var target = Context.SemanticModel.GetSymbolInfo(node).Symbol.EnsureNotNull().GetMemberType(); + resolvedMethod = ((IMethodSymbol) target.GetMembers("Invoke").SingleOrDefault()).MethodResolverExpression(Context); + } + + // //TODO: Cecil dependent code. This assumes that local variable is a MethodReference or similar. Can't we simply look for the method variable instead? + // var resolvedMethod = localDelegateDeclaration != null + // ? $"{localDelegateDeclaration}.Methods.Single(m => m.Name == \"Invoke\")" + // : ((IMethodSymbol) typeSymbol.GetMembers("Invoke").SingleOrDefault()).MethodResolverExpression(Context); + OnLastInstructionLoadingTargetOfInvocation(); Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Callvirt, resolvedMethod.AsToken()); } diff --git a/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs b/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs index 8d18d6f7..18aaf740 100644 --- a/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs +++ b/Cecilifier.Core/AST/TypeDeclarationVisitor.Delegate.cs @@ -22,7 +22,7 @@ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) Context.WriteComment($"Delegate: {node.Identifier.Text}"); var typeVar = Context.Naming.Delegate(node); - var delegateSymbol = Context.SemanticModel.GetDeclaredSymbol(node).EnsureNotNull(); + var delegateSymbol = ModelExtensions.GetDeclaredSymbol(Context.SemanticModel, node).EnsureNotNull(); var accessibility = TypeModifiersToCecil(delegateSymbol, node.Modifiers); EnsureContainingTypeForwarded(node, delegateSymbol); @@ -49,17 +49,17 @@ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) // Delegate ctor var parameters = new ParameterSpec[] { - new("target", Context.TypeResolver.Bcl.System.Object, RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = "System.Object" }, - new("method", Context.TypeResolver.Bcl.System.IntPtr, RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = "System.IntPtr" }, + new("target", Context.TypeResolver.Resolve(Context.RoslynTypeSystem.SystemObject, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = "System.Object" }, + new("method", Context.TypeResolver.Resolve(Context.RoslynTypeSystem.SystemIntPtr, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None) { RegistrationTypeName = "System.IntPtr" }, }; + var exps = Context.ApiDefinitionsFactory.Constructor( Context, - new BodiedMemberDefinitionContext("ctor", ctorLocalVar, typeVar, MemberOptions.None, IlContext.None), + new BodiedMemberDefinitionContext("ctor", ctorLocalVar, typeVar, MemberOptions.IsRuntime, null), node.Identifier.Text, false, "MethodAttributes.FamANDAssem | MethodAttributes.Family", - parameters, - "IsRuntime = true"); + parameters); Context.Generate(exps); var invokeMethodVar = Context.Naming.SyntheticVariable("Invoke", ElementKind.Method); @@ -78,39 +78,41 @@ public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) IReadOnlyList beginInvokeParameters = [ ..node.ParameterList.Parameters.Select(p => p.ToParameterSpec(Context, beginInvokeMethodVar)), - new("asyncCallback", Context.TypeResolver.Bcl.System.AsyncCallback, RefKind.None, Constants.ParameterAttributes.None), - new("target", Context.TypeResolver.Bcl.System.Object, RefKind.None, Constants.ParameterAttributes.None) + new("asyncCallback", Context.TypeResolver.Resolve(Context.RoslynTypeSystem.SystemAsyncCallback, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None), + new("target", Context.TypeResolver.Resolve(Context.RoslynTypeSystem.SystemObject, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None) ]; - - AddDelegateMethod( + + var asyncResultTypeSymbol = Context.RoslynTypeSystem.ForType(); + AddDelegateMethod( node.Identifier.Text, typeVar, "BeginInvoke", beginInvokeMethodVar, - Context.TypeResolver.Bcl.System.IAsyncResult, + Context.TypeResolver.Resolve(asyncResultTypeSymbol, ResolveTargetKind.ReturnType), beginInvokeParameters); // EndInvoke() method var endInvokeMethodVar = Context.Naming.SyntheticVariable("EndInvoke", ElementKind.Method); var endInvokeExps = Context.ApiDefinitionsFactory.Method( Context, - new BodiedMemberDefinitionContext("EndInvoke", endInvokeMethodVar, typeVar, MemberOptions.IsRuntime, IlContext.None), + new BodiedMemberDefinitionContext("EndInvoke", endInvokeMethodVar, typeVar, MemberOptions.IsRuntime, null), "declaringTypeName", Constants.Cecil.DelegateMethodAttributes, - [new ParameterSpec("ar", Context.TypeResolver.Bcl.System.IAsyncResult, RefKind.None, Constants.ParameterAttributes.None)], + [new ParameterSpec("ar", Context.TypeResolver.Resolve(asyncResultTypeSymbol, ResolveTargetKind.Parameter), RefKind.None, Constants.ParameterAttributes.None)], [], ctx => ctx.TypeResolver.Resolve(Context.GetTypeInfo(node.ReturnType).Type, ResolveTargetKind.ReturnType), out var _); Context.Generate(endInvokeExps); base.VisitDelegateDeclaration(node); + Context.OnFinishedTypeDeclaration(delegateSymbol); } return; void AddDelegateMethod(string delegateName, string delegateTypeVariable, string methodName, string methodVar, ResolvedType returnType, IReadOnlyList parameters) { - BodiedMemberDefinitionContext methodDefinitionContext = new(methodName, methodVar, delegateTypeVariable, MemberOptions.IsRuntime, Context.ApiDriver.NewIlContext(Context, methodName, methodVar)); + BodiedMemberDefinitionContext methodDefinitionContext = new(methodName, methodVar, delegateTypeVariable, MemberOptions.IsRuntime, null); var methodToAdd = Context.ApiDefinitionsFactory.Method( Context, methodDefinitionContext, @@ -119,7 +121,7 @@ void AddDelegateMethod(string delegateName, string delegateTypeVariable, string parameters, [], ctx => returnType, - out var methodDefinitionVariable); + out var _); Context.Generate(methodToAdd); } } diff --git a/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs b/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs index ca416af8..67491f6c 100644 --- a/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs +++ b/Cecilifier.Core/ApiDriver/DefinitionsFactory/IApiDriverDefinitionsFactory.cs @@ -75,7 +75,7 @@ public IEnumerable Method(IVisitorContext context, out MethodDefinitionVariable methodDefinitionVariable // we can't use the method name in some scenarios (indexers, for instance) ); - public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters, string? methodDefinitionPropertyValues = null); + public IEnumerable Constructor(IVisitorContext context, BodiedMemberDefinitionContext definitionContext, string typeName, bool isStatic, string methodAccessibility, ParameterSpec[] parameters); public IEnumerable Parameter(IVisitorContext context, ParameterSpec parameterSpec, string memberVar, string paramVar); diff --git a/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs b/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs index e44bd3d3..d4901fd4 100644 --- a/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs +++ b/Cecilifier.Core/CodeGeneration/PrimaryConstructor.Generator.cs @@ -118,8 +118,7 @@ internal static void AddPrimaryConstructor(IVisitorContext context, string recor typeName, false, "MethodAttributes.Public", - parameters, - null); + parameters); var ctorExp = exps; context.Generate(ctorExp); diff --git a/Cecilifier.Core/Extensions/CecilifierExtensions.cs b/Cecilifier.Core/Extensions/CecilifierExtensions.cs index af823163..5e1804ba 100644 --- a/Cecilifier.Core/Extensions/CecilifierExtensions.cs +++ b/Cecilifier.Core/Extensions/CecilifierExtensions.cs @@ -46,6 +46,19 @@ public static string AppendEnumFlag(this string to, string flag) return $"{to} | {flag}"; } + + public static string AppendEnumFlagConditional(this string to, string flag, bool condition) + { + if (!condition) return to; + + if (string.IsNullOrWhiteSpace(flag)) + return to; + + if (string.IsNullOrEmpty(to)) + return flag; + + return $"{to} | {flag}"; + } public static StringBuilder AppendEnumFlag(this StringBuilder to, string flag) { diff --git a/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs b/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs index 6db69f4b..5834b070 100644 --- a/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs +++ b/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs @@ -53,6 +53,7 @@ public RoslynTypeSystem(IVisitorContext ctx) SystemRuntimeCompilerServicesCompilerGeneratedAttribute = ctx.SemanticModel.Compilation.GetTypeByMetadataName(typeof(CompilerGeneratedAttribute).FullName); SystemEnum = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Enum); SystemMulticastDelegate = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_MulticastDelegate); + SystemAsyncCallback = ForType(); } public ITypeSymbol SystemIndex { get; } @@ -91,6 +92,7 @@ public RoslynTypeSystem(IVisitorContext ctx) public ITypeSymbol SystemCollectionsGenericICollectionOfT { get; } public ITypeSymbol SystemEnum { get; } public ITypeSymbol SystemMulticastDelegate { get; } + public ITypeSymbol SystemAsyncCallback { get; } public readonly ITypeSymbol ForType() { From efef22eab1f25dd3e77fafdaa253de461193e207 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 26 Sep 2026 16:16:43 -0400 Subject: [PATCH 08/12] fix: pre/pos incrementing ref like variables throws NullReferenceException (#376) --- ...temReflectionMetadataDefinitionsFactory.cs | 4 +- .../Tests/OutputBased/OperatorsTests.cs | 36 +++++ Cecilifier.Core/AST/AssignmentVisitor.cs | 10 +- Cecilifier.Core/AST/ExpressionVisitor.cs | 133 +++++++++++++++--- Cecilifier.Core/AST/SyntaxWalkerBase.cs | 2 +- .../Extensions/ExpressionExtensions.cs | 2 +- .../Extensions/ISymbolExtensions.cs | 11 +- 7 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 Cecilifier.Core.Tests/Tests/OutputBased/OperatorsTests.cs diff --git a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs index 8bf1837e..09e7c2e6 100644 --- a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs +++ b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs @@ -159,7 +159,9 @@ public IEnumerable Method(IVisitorContext context, IMethodSymbol methodS // Resolve the method to make sure there's a method ref available (this will be used to fulfill any references to this method) context.MemberResolver.ResolveMethod(methodSymbol); - var paramIndexOffset = methodSymbol.IsStatic ? 0 : 1; + //TODO: Introduce ISymbolExtensions.AdjustedParameterIndex(this IParameterSymbol parameter) and removed duplication (search for MethodKind.LocalFunction) + var paramIndexOffset = (methodSymbol.IsStatic || methodSymbol.MethodKind == MethodKind.LocalFunction)? 0 : 1; // Local functions are always handled as static + // register all parameters so we can reference them when emitting the method body foreach (var parameter in methodSymbol.Parameters) { diff --git a/Cecilifier.Core.Tests/Tests/OutputBased/OperatorsTests.cs b/Cecilifier.Core.Tests/Tests/OutputBased/OperatorsTests.cs new file mode 100644 index 00000000..8976d7a5 --- /dev/null +++ b/Cecilifier.Core.Tests/Tests/OutputBased/OperatorsTests.cs @@ -0,0 +1,36 @@ +using Cecilifier.ApiDriver.MonoCecil; +using Cecilifier.ApiDriver.SystemReflectionMetadata; +using Cecilifier.Core.AST; +using Cecilifier.Core.Tests.Framework; +using NUnit.Framework; + +namespace Cecilifier.Core.Tests.OutputBased; + +[TestFixture(typeof(MonoCecilContext))] +[TestFixture(typeof(SystemReflectionMetadataContext))] +public class OperatorsTests : OutputBasedTestBase where TContext : IVisitorContext +{ + [Test] + public void PrePostIncrement_OnRefLikeLocations_Works() + { + AssertOutput(""" + int v = 2; + DoIt(ref v); + + + void DoIt(ref int x) + { + int n = ++x; + System.Console.Write($"{n} "); + + n = x--; + System.Console.Write($"{n} {x} "); + + ref int r = ref x; + n = --r; + System.Console.Write($"{n} {x}"); + } + """, + "3 3 2 1 1"); + } +} diff --git a/Cecilifier.Core/AST/AssignmentVisitor.cs b/Cecilifier.Core/AST/AssignmentVisitor.cs index aa34fdf1..0d209338 100644 --- a/Cecilifier.Core/AST/AssignmentVisitor.cs +++ b/Cecilifier.Core/AST/AssignmentVisitor.cs @@ -16,7 +16,7 @@ namespace Cecilifier.Core.AST internal class AssignmentVisitor : SyntaxWalkerBase { private readonly IlContext ilVar; - private readonly AssignmentExpressionSyntax assignment; + private readonly AssignmentExpressionSyntax assignment; // null if handling a pre/post increment internal AssignmentVisitor(IVisitorContext ctx, IlContext ilVar, AssignmentExpressionSyntax node) : base(ctx) { @@ -25,12 +25,13 @@ internal AssignmentVisitor(IVisitorContext ctx, IlContext ilVar, AssignmentExpre PreProcessRefOutAssignments(node.Left); } - - internal AssignmentVisitor(IVisitorContext ctx, IlContext ilVar) : base(ctx) + private AssignmentVisitor(IVisitorContext ctx, IlContext ilVar) : base(ctx) { this.ilVar = ilVar; } + internal static AssignmentVisitor ForPrePostOperators(IVisitorContext ctx, IlContext ilVar) => new AssignmentVisitor(ctx, ilVar); + public LinkedListNode InstructionPrecedingValueToLoad { get; set; } /* @@ -283,6 +284,9 @@ private void MemberAssignment(ITypeSymbol memberType, RefKind memberRe private bool NeedsIndirectStore(ITypeSymbol assignmentTargetMemberType, RefKind assignmentTargetMemberRefKind) { + if (assignment == null) + return assignmentTargetMemberRefKind != RefKind.None; + return (assignmentTargetMemberType is IPointerTypeSymbol && !assignment.Right.IsKind(SyntaxKind.AddressOfExpression) && Context.SemanticModel.GetTypeInfo(assignment.Right).Type!.Kind != SymbolKind.PointerType) || assignmentTargetMemberRefKind != RefKind.None && !assignment.Right.IsKind(SyntaxKind.RefExpression); } diff --git a/Cecilifier.Core/AST/ExpressionVisitor.cs b/Cecilifier.Core/AST/ExpressionVisitor.cs index a3c55d84..da81bf7a 100644 --- a/Cecilifier.Core/AST/ExpressionVisitor.cs +++ b/Cecilifier.Core/AST/ExpressionVisitor.cs @@ -1058,32 +1058,68 @@ private bool TryProcessTypeParameterInstantiation(BaseObjectCreationExpressionSy private void ProcessPrefixPostfixOperators(ExpressionSyntax operand, OpCode opCode, bool isPrefix) { - using var _ = LineInformationTracker.Track(Context, operand); + using var _ = LineInformationTracker.Track(Context, operand); Visit(operand); - operand.InjectRequiredConversions(Context, ilVar); - var assignmentVisitor = new AssignmentVisitor(Context, ilVar); + var assignmentVisitor = AssignmentVisitor.ForPrePostOperators(Context, ilVar); var operandInfo = Context.SemanticModel.GetSymbolInfo(operand); - if (operandInfo.Symbol != null && operandInfo.Symbol.Kind != SymbolKind.Field && operandInfo.Symbol.Kind != SymbolKind.Property) // Fields / Properties requires more complex handling to load the owning reference. + if (operandInfo.Symbol != null && (operandInfo.Symbol.Kind == SymbolKind.Local || operandInfo.Symbol.Kind == SymbolKind.Parameter)) // Fields / Properties / Events requires more complex handling to load the owning reference. { - if (!isPrefix) // For *postfix* operators we duplicate the value *before* applying the operator... + if (operandInfo.Symbol.IsByRef()) { - Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); + if (isPrefix) + { + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, operandInfo.Symbol.GetMemberType().LdindOpCodeFor()); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); + + //assign (top of stack to the operand) + assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; + operand.Accept(assignmentVisitor); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, operandInfo.Symbol.GetMemberType().LdindOpCodeFor()); + } + else + { + var targetVariable = Context.DefinitionVariables.GetVariable(operandInfo.Symbol.Name, operandInfo.Symbol.ToVariableMemberKind(), operandInfo.Symbol.ContainingSymbol.ToDisplayString()); + targetVariable.ThrowIfVariableIsNotValid(); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, operandInfo.Symbol.LoadOpCodeFor(), targetVariable.VariableName.AsToken()); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, operandInfo.Symbol.GetMemberType().LdindOpCodeFor()); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); + + //assign (top of stack to the operand) + assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; + operand.Accept(assignmentVisitor); + } } - - Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); - Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); - - if (isPrefix) // For prefix operators we duplicate the value *after* applying the operator... + else { - Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); - } - - //assign (top of stack to the operand) - assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; - operand.Accept(assignmentVisitor); + if (isPrefix) + { + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); + + //assign (top of stack to the operand) + assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; + operand.Accept(assignmentVisitor); + } + else + { + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); + Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); + //assign (top of stack to the operand) + assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; + operand.Accept(assignmentVisitor); + } + } + return; } @@ -1108,6 +1144,69 @@ private void ProcessPrefixPostfixOperators(ExpressionSyntax operand, OpCode opCo // assign (top of stack to the operand) operand.Accept(assignmentVisitor); + + // using var _ = LineInformationTracker.Track(Context, operand); + // Visit(operand); + // operand.InjectRequiredConversions(Context, ilVar); + // + // var assignmentVisitor = AssignmentVisitor.ForPrePostOperators(Context, ilVar); + // + // var operandInfo = Context.SemanticModel.GetSymbolInfo(operand); + // if (operandInfo.Symbol != null && operandInfo.Symbol.Kind != SymbolKind.Field && operandInfo.Symbol.Kind != SymbolKind.Property) // Fields / Properties requires more complex handling to load the owning reference. + // { + // // if (operandInfo.Symbol.IsByRef()) + // // { + // // Context.ApiDriver.WriteCilInstruction(Context, ilVar, operandInfo.Symbol.GetMemberType().LdindOpCodeFor()); + // // Visit(operand); + // // operand.InjectRequiredConversions(Context, ilVar); + // // } + // + // if (!isPrefix) // For *postfix* operators we duplicate the value *before* applying the operator... + // { + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); + // } + // + // // if (operandInfo.Symbol.IsByRef()) + // // { + // // Context.ApiDriver.WriteCilInstruction(Context, ilVar, operandInfo.Symbol.GetMemberType().LdindOpCodeFor()); + // // } + // + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); + // + // if (isPrefix) // For prefix operators we duplicate the value *after* applying the operator... + // { + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); + // } + // + // //assign (top of stack to the operand) + // assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; + // operand.Accept(assignmentVisitor); + // + // return; + // } + // + // if (isPrefix) + // { + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); + // } + // + // var type = Context.SemanticModel.GetTypeInfo(operand).Type; + // var tempLocalName = StoreTopOfStackInLocalVariable(Context, ilVar, "tmp", type).VariableName; + // + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldloc, tempLocalName.AsLocalVariable()); + // assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldloc, tempLocalName.AsLocalVariable()); + // + // if (!isPrefix) + // { + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); + // Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); + // } + // + // // assign (top of stack to the operand) + // operand.Accept(assignmentVisitor); } /* diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index 66eb39e8..a9772859 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -563,7 +563,7 @@ protected void HandlePotentialRefLoad(IlContext ilVar, SyntaxNode expression, IT needsLoadIndirect = assigment.Left != expression && (assigment.Right == expression && sourceIsByRef && !targetIsByRef // simple assignment like: nonRef = ref; - || sourceIsByRef && !assigment.Right.IsKind(SyntaxKind.RefExpression)); // complex assignment like: nonRef = ref + 10; + || sourceIsByRef && (!assigment.Right.IsKind(SyntaxKind.RefExpression) && !assigment.Right.IsKind(SyntaxKind.PreDecrementExpression) && !assigment.Right.IsKind(SyntaxKind.PreIncrementExpression))); // complex assignment like: nonRef = ref + 10; } else if (argument != null) { diff --git a/Cecilifier.Core/Extensions/ExpressionExtensions.cs b/Cecilifier.Core/Extensions/ExpressionExtensions.cs index c682981a..f6983b81 100644 --- a/Cecilifier.Core/Extensions/ExpressionExtensions.cs +++ b/Cecilifier.Core/Extensions/ExpressionExtensions.cs @@ -70,7 +70,7 @@ public static void InjectRequiredConversions(this ExpressionSyntax expression, I // - is constrained to 'struct' // 2. Source of assignment (or variable initialization) to a reference type // 3. Argument for a reference type parameter - // requires boxing, but for some reason, the conversion returned by GetConversion() does not reflects that. + // requires boxing, but for some reason, the conversion returned by GetConversion() does not reflect that. static bool NeedsBoxing(IVisitorContext context, ExpressionSyntax expression, ITypeSymbol type) { var needsBoxing = type.TypeKind == TypeKind.TypeParameter && diff --git a/Cecilifier.Core/Extensions/ISymbolExtensions.cs b/Cecilifier.Core/Extensions/ISymbolExtensions.cs index ec22b506..b26dfdb8 100644 --- a/Cecilifier.Core/Extensions/ISymbolExtensions.cs +++ b/Cecilifier.Core/Extensions/ISymbolExtensions.cs @@ -249,7 +249,16 @@ public static void EnsurePropertyExists(this IPropertySymbol propertySymbol, IVi static object AsLocalVariable(string expression) => expression.AsLocalVariable(); static object AsToken(string expression) => expression.AsToken(); - + + public static OpCode LoadOpCodeFor(this ISymbol member) => member switch + { + IParameterSymbol => OpCodes.Ldarg, + IFieldSymbol => OpCodes.Ldfld, + ILocalSymbol => OpCodes.Ldloc, + _ => throw new InvalidOperationException($"Unsupported symbol type {member.GetType().Namespace} for {member.Name}") + }; + + //TODO: Rename to LoadOpcodeForLiteral public static OpCode LoadOpCodeFor(this ITypeSymbol type) { return type.SpecialType switch From 27e65b3e42cf7d151a8428a7fb39b9fd2eabd3f7 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 26 Sep 2026 18:20:27 -0400 Subject: [PATCH 09/12] refact: removed code duplication and fixed related test expectation changes --- .../TypeSystem/MonoCecilMemberResolver.cs | 2 +- ...temReflectionMetadataDefinitionsFactory.cs | 7 +-- .../Tests/Unit/NullCoalescingTests.cs | 4 +- .../Tests/Unit/NullableTests.cs | 51 ++++++++++++++++--- .../AST/MethodDeclarationVisitor.cs | 4 +- Cecilifier.Core/AST/SyntaxWalkerBase.cs | 6 +-- .../Extensions/CecilifierContextExtensions.cs | 29 +++++------ .../Extensions/ISymbolExtensions.cs | 10 +++- 8 files changed, 74 insertions(+), 39 deletions(-) diff --git a/Cecilifier.ApiDriver.MonoCecil/TypeSystem/MonoCecilMemberResolver.cs b/Cecilifier.ApiDriver.MonoCecil/TypeSystem/MonoCecilMemberResolver.cs index 1d15081d..cde3bc0e 100644 --- a/Cecilifier.ApiDriver.MonoCecil/TypeSystem/MonoCecilMemberResolver.cs +++ b/Cecilifier.ApiDriver.MonoCecil/TypeSystem/MonoCecilMemberResolver.cs @@ -143,7 +143,7 @@ public string ResolveMethod(string declaringTypeName, string declaringTypeVariab HasThis = {{methodWithIncorrectDeclaringType}}.HasThis, CallingConvention = {{methodWithIncorrectDeclaringType}}.CallingConvention, ExplicitThis = {{methodWithIncorrectDeclaringType}}.ExplicitThis, - DeclaringType = {{methodWithIncorrectDeclaringType}}.DeclaringType.MakeGenericInstanceType({{correctDeclaringType}}.GenericArguments.ToArray()), + DeclaringType = {{correctDeclaringType}}, }; """); context.WriteNewLine(); diff --git a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs index 09e7c2e6..91d05058 100644 --- a/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs +++ b/Cecilifier.ApiDriver.SystemReflectionMetadata/SystemReflectionMetadataDefinitionsFactory.cs @@ -158,17 +158,14 @@ public IEnumerable Method(IVisitorContext context, IMethodSymbol methodS // Resolve the method to make sure there's a method ref available (this will be used to fulfill any references to this method) context.MemberResolver.ResolveMethod(methodSymbol); - - //TODO: Introduce ISymbolExtensions.AdjustedParameterIndex(this IParameterSymbol parameter) and removed duplication (search for MethodKind.LocalFunction) - var paramIndexOffset = (methodSymbol.IsStatic || methodSymbol.MethodKind == MethodKind.LocalFunction)? 0 : 1; // Local functions are always handled as static - + // register all parameters so we can reference them when emitting the method body foreach (var parameter in methodSymbol.Parameters) { // This is a hack. SRM accesses parameters by index, and Cecilifier does not have a way to pass that index around; it only has variable names, // so we record the `index` of the parameter as the variable name. // Code that emits Ldarg/Starg/etc will use this `name` (actually the parameter index) as its operand (this is similar to the way we handle local variables) - context.DefinitionVariables.RegisterNonMethod(methodSymbol.ToDisplayString(), parameter.Name, VariableMemberKind.Parameter, (parameter.Ordinal + paramIndexOffset).ToString()); + context.DefinitionVariables.RegisterNonMethod(methodSymbol.ToDisplayString(), parameter.Name, VariableMemberKind.Parameter, parameter.AdjustedParameterIndex().ToString()); } var memberParentDefinitionVariable = bodiedMemberDefinitionContext.Member.ParentDefinitionVariable ?? throw new ArgumentNullException(nameof(bodiedMemberDefinitionContext.Member.ParentDefinitionVariable)); diff --git a/Cecilifier.Core.Tests/Tests/Unit/NullCoalescingTests.cs b/Cecilifier.Core.Tests/Tests/Unit/NullCoalescingTests.cs index 9a998176..4f863eda 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/NullCoalescingTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/NullCoalescingTests.cs @@ -100,10 +100,10 @@ public void MixedNullableValueType_AndReferenceType() \s+HasThis = \k.HasThis, \s+CallingConvention = \k.CallingConvention, \s+ExplicitThis = \k.ExplicitThis, - \s+DeclaringType = \k.DeclaringType.MakeGenericInstanceType\(\k.GenericArguments.ToArray\(\)\), + \s+DeclaringType = \k, \s+}; \s+r_genericMethod_\d+.Parameters.Add\(new ParameterDefinition\(\k.Parameters\[0\].Name, \k.Parameters\[0\].Attributes, \k.Parameters\[0\].ParameterType\)\); - \kNewobj, r_genericMethod_18\); + \kNewobj, r_genericMethod_21\); \kRet\); \s+il_m3_\d+\.Append\(\k\); \kLdloc, l_leftValue_\d+\); diff --git a/Cecilifier.Core.Tests/Tests/Unit/NullableTests.cs b/Cecilifier.Core.Tests/Tests/Unit/NullableTests.cs index 98f7ab17..98976397 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/NullableTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/NullableTests.cs @@ -67,9 +67,29 @@ class Foo //return Bar\(i1\); (\s+il_test_\d+\.Emit\(OpCodes\.)Ldarg_0\); \1Ldarg_1\); - (?\1Newobj, .+ImportReference\(typeof\(System.Nullable<>\).MakeGenericType\(typeof\(System.Int32\)\).GetConstructors\(\).+;) - \1Call, m_bar_1\); - \k + \s+var m_declaringType_7 = assembly.MainModule.ImportReference\(typeof\(System.Nullable<>\)\).MakeGenericInstanceType\(assembly.MainModule.TypeSystem.Int32\); + \s+var r_tmpMethod_8 = assembly.MainModule.ImportReference\(m_declaringType_7.ElementType.Resolve\(\).Methods.Single\(m => m.Name == ".ctor" && m.Parameters.Count == 1\)\); + \s+var r_genericMethod_9 = new MethodReference\(r_tmpMethod_8.Name, r_tmpMethod_8.ReturnType\) + \s+{ + \s+HasThis = r_tmpMethod_8.HasThis, + \s+CallingConvention = r_tmpMethod_8.CallingConvention, + \s+ExplicitThis = r_tmpMethod_8.ExplicitThis, + \s+DeclaringType = m_declaringType_7, + \s+}; + \s+r_genericMethod_9.Parameters.Add\(new ParameterDefinition\(r_tmpMethod_8.Parameters\[0\].Name, r_tmpMethod_8.Parameters\[0\].Attributes, r_tmpMethod_8.Parameters\[0\].ParameterType\)\); + \s+il_test_5.Emit\(OpCodes.Newobj, r_genericMethod_9\); + \s+il_test_5.Emit\(OpCodes.Call, m_bar_1\); + \s+var m_declaringType_10 = assembly.MainModule.ImportReference\(typeof\(System.Nullable<>\)\).MakeGenericInstanceType\(assembly.MainModule.TypeSystem.Int32\); + \s+var r_tmpMethod_11 = assembly.MainModule.ImportReference\(m_declaringType_10.ElementType.Resolve\(\).Methods.Single\(m => m.Name == ".ctor" && m.Parameters.Count == 1\)\); + \s+var r_genericMethod_12 = new MethodReference\(r_tmpMethod_11.Name, r_tmpMethod_11.ReturnType\) + \s+{ + \s+HasThis = r_tmpMethod_11.HasThis, + \s+CallingConvention = r_tmpMethod_11.CallingConvention, + \s+ExplicitThis = r_tmpMethod_11.ExplicitThis, + \s+DeclaringType = m_declaringType_10, + \s+}; + \s+r_genericMethod_12.Parameters.Add\(new ParameterDefinition\(r_tmpMethod_11.Parameters\[0\].Name, r_tmpMethod_11.Parameters\[0\].Attributes, r_tmpMethod_11.Parameters\[0\].ParameterType\)\); + \1Newobj, r_genericMethod_12\); \1Ret\); """, TestName = "Method parameter and return value" @@ -92,7 +112,17 @@ void Bar(int? p) """ //p = 41; (\s+il_bar_\d+\.Emit\(OpCodes\.)Ldc_I4, 41\); - \1Newobj, .+ImportReference\(typeof\(System.Nullable<>\).MakeGenericType\(typeof\(System.Int32\)\).GetConstructors\(\).+; + \s+var m_declaringType_4 = .+ImportReference\(typeof\(System.Nullable<>\)\).MakeGenericInstanceType\(assembly.MainModule.TypeSystem.Int32\); + \s+var r_tmpMethod_5 = .+ImportReference\(m_declaringType_4.ElementType.Resolve\(\).Methods.Single\(m => m.Name == ".ctor" && m.Parameters.Count == 1\)\); + \s+var r_genericMethod_6 = new MethodReference\(r_tmpMethod_5.Name, r_tmpMethod_5.ReturnType\) + \s+{ + \s+HasThis = r_tmpMethod_5.HasThis, + \s+CallingConvention = r_tmpMethod_5.CallingConvention, + \s+ExplicitThis = r_tmpMethod_5.ExplicitThis, + \s+DeclaringType = m_declaringType_4, + \s+}; + \s+r_genericMethod_6.Parameters.Add\(new ParameterDefinition\(.+Parameters\[0\].Name, .+Parameters\[0\].Attributes, .+.Parameters\[0\].ParameterType\)\); + \1Newobj, r_genericMethod_6\); \1Starg_S, p_p_3\); """, TestName = "Variable assignment" @@ -113,8 +143,17 @@ public void ConstructorIsInvokedAfterCast() \s+var il_M_\d+ = m_M_\d+.Body.GetILProcessor\(\); (?\s+il_M_\d+\.Emit\(OpCodes\.)Ldarg_0\); \kUnbox_Any, assembly.MainModule.TypeSystem.Int32\); - \kNewobj,.+ImportReference\(typeof\(System.Nullable<>\)\.MakeGenericType\(typeof\(System.Int32\)\)\.GetConstructors\(\).+\); - \kRet\); + \s+var m_declaringType_9 = assembly.MainModule.ImportReference\(typeof\(System.Nullable<>\)\).MakeGenericInstanceType\(.+Int32\); + \s+var r_tmpMethod_10 = assembly.MainModule.ImportReference\(m_declaringType_9.ElementType.Resolve\(\).Methods.Single\(m => m.Name == ".ctor" && m.Parameters.Count == 1\)\); + \s+var r_genericMethod_11 = new MethodReference\(r_tmpMethod_10.Name, r_tmpMethod_10.ReturnType\) + \s+{ + \s+HasThis = r_tmpMethod_10.HasThis, + \s+CallingConvention = r_tmpMethod_10.CallingConvention, + \s+ExplicitThis = r_tmpMethod_10.ExplicitThis, + \s+DeclaringType = m_declaringType_9, + \s+}; + \s+r_genericMethod_11.Parameters.Add\(new ParameterDefinition\(.+Parameters\[0\].Name, .+Parameters\[0\].Attributes, .+Parameters\[0\].ParameterType\)\); + \kNewobj, r_genericMethod_11\); """)); } diff --git a/Cecilifier.Core/AST/MethodDeclarationVisitor.cs b/Cecilifier.Core/AST/MethodDeclarationVisitor.cs index 194166e3..4ba3ad6a 100644 --- a/Cecilifier.Core/AST/MethodDeclarationVisitor.cs +++ b/Cecilifier.Core/AST/MethodDeclarationVisitor.cs @@ -189,9 +189,7 @@ private string AddOrUpdateMethodDefinition(IMethodSymbol methodSymbol, string va AddCecilExpression("{0}.Attributes = {1};", found.VariableName, methodModifiers); AddCecilExpression("{0}.HasThis = !{0}.IsStatic;", found.VariableName); - - //TODO: Temporary hack to set `ilVar` until we change that to `IlContext` and assign `NewIlContext()` call - // inside AddMethodDefinition() call bellow. + if (ilVar == null && !methodSymbol.IsExtern) { ilVar = Context.ApiDriver.NewIlContext(Context, simpleName, found.VariableName); diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs index a9772859..fe79bfc5 100644 --- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs +++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs @@ -194,7 +194,7 @@ private bool LoadDefaultValueForTypeParameter(IlContext ilVar, ITypeSymbol type, private void LoadLiteralToStackHandlingCallOnValueTypeLiterals(IlContext ilVar, ITypeSymbol literalType, object literalValue, UsageResult usageResult) { - var opCode = literalType.LoadOpCodeFor(); + var opCode = literalType.LoadOpcodeForLiteral(); Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode, literalType.ToCilOperandValue(literalValue)); if (usageResult.Kind == UsageKind.CallTarget) { @@ -341,9 +341,7 @@ protected void ProcessParameter(IlContext ilVar, SimpleNameSyntax node, IParamet return; node.Parent.EnsureNotNull(); - // We only support non-capturing lambda expressions so we handle those as static (even if the code does not mark them explicitly as such) - // if/when we decide to support lambdas that captures variables/fields/params/etc we will probably need to revisit this. - var adjustedParameterIndex = paramSymbol.Ordinal + (method.IsStatic || method.MethodKind == MethodKind.AnonymousFunction || method.MethodKind == MethodKind.LocalFunction ? 0 : 1); + var adjustedParameterIndex = paramSymbol.AdjustedParameterIndex(); if (adjustedParameterIndex > 3) { Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldarg, adjustedParameterIndex); diff --git a/Cecilifier.Core/Extensions/CecilifierContextExtensions.cs b/Cecilifier.Core/Extensions/CecilifierContextExtensions.cs index 4a1dd2d5..0d94a74a 100644 --- a/Cecilifier.Core/Extensions/CecilifierContextExtensions.cs +++ b/Cecilifier.Core/Extensions/CecilifierContextExtensions.cs @@ -51,33 +51,30 @@ internal static bool TryApplyConversions(this IVisitorContext context, IlContext } else if (operation is IConversionOperation { Conversion.IsNullable: true } nullableConversion && !nullableConversion.Syntax.IsKind(SyntaxKind.CoalesceExpression)) { - //TODO: This code is very similar to the one handling coalescing operator below (before that code got fixed to handle SRM). Most likely we will want to extract some - // common code and reuse. - context.ApiDriver.WriteCilInstruction(context, - ilVar, - OpCodes.Newobj, - $"assembly.MainModule.ImportReference(typeof(System.Nullable<>).MakeGenericType(typeof({nullableConversion.Operand.Type.FullyQualifiedName()})).GetConstructors().Single(ctor => ctor.GetParameters().Length == 1))"); + EmitNewobjOpcodeFor(context, in ilVar, nullableConversion.Operand.Type); } else if (operation is ICoalesceOperation coalesce && !SymbolEqualityComparer.Default.Equals(coalesce.Type?.OriginalDefinition, context.RoslynTypeSystem.SystemNullableOfT) && SymbolEqualityComparer.Default.Equals(coalesce.Value.Type?.OriginalDefinition, context.RoslynTypeSystem.SystemNullableOfT) ) { - var targetType = context.TypeResolver.Resolve(coalesce.Type, ResolveTargetKind.GenericTypeArgument); - var closedNullable = context.TypeResolver.MakeGenericInstanceType(context.RoslynTypeSystem.SystemNullableOfT.NameIncludingTypeParametersAndArguments(), context.TypeResolver.Bcl.System.NullableOfT, [targetType], new TypeResolutionContext(ResolveTargetKind.Instruction, TypeResolutionOptions.IsValueType)); - var returnType = context.TypeResolver.Resolve(context.RoslynTypeSystem.SystemVoid, ResolveTargetKind.ReturnType); - var resolvedCtor = context.MemberResolver.ResolveMethod("", closedNullable.Expression, ".ctor", returnType, [NullableTypeParameter(context)], [], MemberOptions.None); - - context.ApiDriver.WriteCilInstruction(context, - ilVar, - OpCodes.Newobj, - resolvedCtor.AsToken()); + EmitNewobjOpcodeFor(context, in ilVar, coalesce.Type); } else return false; return true; - + + static void EmitNewobjOpcodeFor(IVisitorContext context, in IlContext ilContext, ITypeSymbol type) + { + var resolvedType = context.TypeResolver.Resolve(type, ResolveTargetKind.GenericTypeArgument); + var closedNullable = context.TypeResolver.MakeGenericInstanceType(context.RoslynTypeSystem.SystemNullableOfT.NameIncludingTypeParametersAndArguments(), context.TypeResolver.Bcl.System.NullableOfT, [resolvedType], new TypeResolutionContext(ResolveTargetKind.Instruction, TypeResolutionOptions.IsValueType)); + var returnType = context.TypeResolver.Resolve(context.RoslynTypeSystem.SystemVoid, ResolveTargetKind.ReturnType); + var resolvedCtor = context.MemberResolver.ResolveMethod("", closedNullable.Expression, ".ctor", returnType, [NullableTypeParameter(context)], [], MemberOptions.None); + + context.ApiDriver.WriteCilInstruction(context, ilContext, OpCodes.Newobj, resolvedCtor.AsToken()); + } + static ParameterSpec NullableTypeParameter(IVisitorContext context) { var typeParameterSymbol = context.RoslynTypeSystem.SystemNullableOfT.TypeParameters[0]; diff --git a/Cecilifier.Core/Extensions/ISymbolExtensions.cs b/Cecilifier.Core/Extensions/ISymbolExtensions.cs index b26dfdb8..a87ae762 100644 --- a/Cecilifier.Core/Extensions/ISymbolExtensions.cs +++ b/Cecilifier.Core/Extensions/ISymbolExtensions.cs @@ -258,8 +258,7 @@ public static void EnsurePropertyExists(this IPropertySymbol propertySymbol, IVi _ => throw new InvalidOperationException($"Unsupported symbol type {member.GetType().Namespace} for {member.Name}") }; - //TODO: Rename to LoadOpcodeForLiteral - public static OpCode LoadOpCodeFor(this ITypeSymbol type) + public static OpCode LoadOpcodeForLiteral(this ITypeSymbol type) { return type.SpecialType switch { @@ -310,6 +309,13 @@ public static OpCode LoadOpCodeFor(this ITypeSymbol type) _ => throw new ArgumentOutOfRangeException(nameof(literalType), literalType, null) }; + // We only support non-capturing lambda expressions so we handle those as static (even if the code does not mark them explicitly as such) + // if/when we decide to support lambdas that captures variables/fields/params/etc we will probably need to revisit this. + public static int AdjustedParameterIndex(this IParameterSymbol parameter) => parameter.Ordinal + + (parameter.ContainingSymbol.IsStatic + || ((IMethodSymbol) parameter.ContainingSymbol).MethodKind == MethodKind.LocalFunction + || ((IMethodSymbol) parameter.ContainingSymbol).MethodKind == MethodKind.AnonymousFunction ? 0 : 1); // Local functions are always handled as static; + public static IMethodSymbol ParameterlessCtor(this ITypeSymbol self) => self.GetMembers(".ctor").OfType().Single(ctor => ctor.Parameters.Length == 0); public static IMethodSymbol Ctor(this ITypeSymbol self, params ITypeSymbol[] parameters) => self.GetMembers(".ctor") .OfType() From 73619331497094f3970b903bcbb9303b64c10806 Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Sat, 26 Sep 2026 18:21:02 -0400 Subject: [PATCH 10/12] refact: removes leftover commented code --- Cecilifier.Core/AST/ExpressionVisitor.cs | 63 ------------------------ 1 file changed, 63 deletions(-) diff --git a/Cecilifier.Core/AST/ExpressionVisitor.cs b/Cecilifier.Core/AST/ExpressionVisitor.cs index da81bf7a..8d9e43aa 100644 --- a/Cecilifier.Core/AST/ExpressionVisitor.cs +++ b/Cecilifier.Core/AST/ExpressionVisitor.cs @@ -1144,69 +1144,6 @@ private void ProcessPrefixPostfixOperators(ExpressionSyntax operand, OpCode opCo // assign (top of stack to the operand) operand.Accept(assignmentVisitor); - - // using var _ = LineInformationTracker.Track(Context, operand); - // Visit(operand); - // operand.InjectRequiredConversions(Context, ilVar); - // - // var assignmentVisitor = AssignmentVisitor.ForPrePostOperators(Context, ilVar); - // - // var operandInfo = Context.SemanticModel.GetSymbolInfo(operand); - // if (operandInfo.Symbol != null && operandInfo.Symbol.Kind != SymbolKind.Field && operandInfo.Symbol.Kind != SymbolKind.Property) // Fields / Properties requires more complex handling to load the owning reference. - // { - // // if (operandInfo.Symbol.IsByRef()) - // // { - // // Context.ApiDriver.WriteCilInstruction(Context, ilVar, operandInfo.Symbol.GetMemberType().LdindOpCodeFor()); - // // Visit(operand); - // // operand.InjectRequiredConversions(Context, ilVar); - // // } - // - // if (!isPrefix) // For *postfix* operators we duplicate the value *before* applying the operator... - // { - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); - // } - // - // // if (operandInfo.Symbol.IsByRef()) - // // { - // // Context.ApiDriver.WriteCilInstruction(Context, ilVar, operandInfo.Symbol.GetMemberType().LdindOpCodeFor()); - // // } - // - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); - // - // if (isPrefix) // For prefix operators we duplicate the value *after* applying the operator... - // { - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Dup); - // } - // - // //assign (top of stack to the operand) - // assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; - // operand.Accept(assignmentVisitor); - // - // return; - // } - // - // if (isPrefix) - // { - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); - // } - // - // var type = Context.SemanticModel.GetTypeInfo(operand).Type; - // var tempLocalName = StoreTopOfStackInLocalVariable(Context, ilVar, "tmp", type).VariableName; - // - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldloc, tempLocalName.AsLocalVariable()); - // assignmentVisitor.InstructionPrecedingValueToLoad = Context.CurrentLine; - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldloc, tempLocalName.AsLocalVariable()); - // - // if (!isPrefix) - // { - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, OpCodes.Ldc_I4_1); - // Context.ApiDriver.WriteCilInstruction(Context, ilVar, opCode); - // } - // - // // assign (top of stack to the operand) - // operand.Accept(assignmentVisitor); } /* From 365f6730d247044368b838f858bd11a536a25c5f Mon Sep 17 00:00:00 2001 From: Adriano Carlos Verona Date: Fri, 2 Oct 2026 13:27:21 -0400 Subject: [PATCH 11/12] configure Asp.NET to include version when serving js/css files to avoid false cache hits --- Cecilifier.Web/Pages/Shared/_Layout.cshtml | 26 +++++++++++----------- Cecilifier.Web/wwwroot/js/cecilifier.js | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cecilifier.Web/Pages/Shared/_Layout.cshtml b/Cecilifier.Web/Pages/Shared/_Layout.cshtml index d7dbc1bb..e20020b4 100644 --- a/Cecilifier.Web/Pages/Shared/_Layout.cshtml +++ b/Cecilifier.Web/Pages/Shared/_Layout.cshtml @@ -1,13 +1,13 @@ - - - - - - - + + + + + + + @@ -17,10 +17,10 @@ - - - - + + + + @@ -43,7 +43,7 @@ - + - +