Skip to content

Commit

Permalink
adds support for user conversions in collection expressions/array ini…
Browse files Browse the repository at this point in the history
…tialization (#256)

also:
1. Extracts SyntaxWalkerBase.EnsureForwardedMethod() -> MethodExtensions.EnsureForwardedMethod()
2. Fixes variable names when handling constructors in some scenarios
  • Loading branch information
adrianoc committed Sep 30, 2024
1 parent 6b6a5e0 commit c4b2255
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,28 @@ public void ImplicitNumericConversions_Are_Applied([Values("List<long>", "long[]
"ReturnPtrToStack" // This is required only for spans.
);
}

[Test]
public void ImplicitTypeConversions_Are_Applied([Values("List<Foo>", "Foo[]", "Span<Foo>")] string targetType, [Values("[2, 1]", "[5, 4, 3, 2, 1]")] string items)
{
var (lengthExtractor, expectedILError) = targetType == "Span<Foo>" ? ("items.Length", "[ReturnPtrToStack]") : ("((ICollection<Foo>) items).Count", null);
AssertOutput(
$$"""
using System.Collections.Generic;
using System;
{{targetType}} items = [1, 2];
// We can´t rely on a foreach (to simplify the code) due to issue #306
for(var i = {{lengthExtractor}} - 1 ; i >= 0; i--) System.Console.Write(items[i].Value);
struct Foo
{
public Foo(int i) => Value = i;
public static implicit operator Foo(int i) => new Foo(i);
public int Value;
}
""",
"21",
expectedILError);
}
}
2 changes: 1 addition & 1 deletion Cecilifier.Core.Tests/Tests/Unit/ForEachStatementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public void OpenIEnumerable()
Assert.That(cecilifiedCode, Does.Match("""
il_run_\d+.Emit\(OpCodes.Callvirt, .+ImportReference\(.+ResolveMethod\(typeof\(System.Collections.IEnumerator\), "MoveNext",.+\)\)\);
"""));
Assert.That(cecilifiedCode, Does.Match("""var l_openget_Current_\d+ = .+ImportReference\(typeof\(.+IEnumerator<>\)\).Resolve\(\).Methods.First\(m => m.Name == "get_Current"\);"""));
Assert.That(cecilifiedCode, Does.Match("""var l_openget_Current_\d+ = .+ImportReference\(typeof\(.+IEnumerator<>\)\).Resolve\(\).Methods.First\(m => m.Name == "get_Current" && m.Parameters.Count == 0 \);"""));
}

[Test]
Expand Down
4 changes: 2 additions & 2 deletions Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public void TestParameterOfGenericType_DependingOnGenericTypeParameterOfMethod_I

var cecilifiedCode = result.GeneratedCode.ReadToEnd();

Assert.That(cecilifiedCode, Does.Match("""var l_openEquals_\d+ = .+ImportReference\(typeof\(System.IEquatable<>\)\).Resolve\(\).Methods.First\(m => m.Name == "Equals"\);"""), "method from open generic type not match");
Assert.That(cecilifiedCode, Does.Match("""var l_openEquals_\d+ = .+ImportReference\(typeof\(System.IEquatable<>\)\).Resolve\(\).Methods.First\(m => m.Name == "Equals" && m.Parameters.Count == 1 \);"""), "method from open generic type not match");
Assert.That(cecilifiedCode, Does.Match("""var r_equals_\d+ = new MethodReference\("Equals", assembly.MainModule.ImportReference\(l_openEquals_\d+\).ReturnType\)"""), "MethodReference does not match");
Assert.That(cecilifiedCode, Does.Match("""il_M_3.Emit\(OpCodes.Callvirt, r_equals_\d+\);"""), "Call to the target method does not match");
}
Expand Down Expand Up @@ -196,7 +196,7 @@ public void NonGenericMethod_OnExternalGenericTypeWithLocalTypeAsTypeArgument_Is

Assert.That(cecilified, Does.Match("""
var (l_iEnumerable_\d+) = .+ImportReference\(typeof\(.+IEnumerable<>\)\).MakeGenericInstanceType\(cls_foo_\d+\);
\s+var (l_openGetEnumerator_\d+) = .+ImportReference\(typeof\(.+IEnumerable<>\)\).Resolve\(\).Methods.First\(m => m.Name == "GetEnumerator"\);
\s+var (l_openGetEnumerator_\d+) = .+ImportReference\(typeof\(.+IEnumerable<>\)\).Resolve\(\).Methods.First\(m => m.Name == "GetEnumerator" && m.Parameters.Count == 0 \);
\s+var r_getEnumerator_\d+ = new MethodReference\("GetEnumerator", .+ImportReference\(\2\).ReturnType\)
\s+{
\s+DeclaringType = \1,
Expand Down
26 changes: 24 additions & 2 deletions Cecilifier.Core/AST/ArrayInitializationProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,34 @@ internal static void InitializeUnoptimized<TElement>(ExpressionVisitor visitor,
{
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Box, context.TypeResolver.Resolve(itemType.Type));
}
else if(parentOperation != null && parentOperation is ICollectionExpressionOperation ceo && ceo.Elements[i] is IConversionOperation conversion)
else
{
context.TryApplyNumericConversion(visitor.ILVariable, conversion.Operand.Type, conversion.Type);
var conversionInfo = GetConversionInfo(parentOperation, i);
if (conversionInfo?.ConvertionMethod != null)
{
context.AddCallToMethod(conversionInfo?.ConvertionMethod, visitor.ILVariable);
}
else if (conversionInfo != null)
context.TryApplyNumericConversion(visitor.ILVariable, conversionInfo?.Source, conversionInfo?.Destination);
}

context.EmitCilInstruction(visitor.ILVariable, stelemOpCode, stelemOpCode == OpCodes.Stelem_Any ? resolvedElementType : null);
}

(ITypeSymbol Source, ITypeSymbol Destination, IMethodSymbol ConvertionMethod)? GetConversionInfo(IOperation operation, int index)
{
var conversionOperation = operation switch
{
ICollectionExpressionOperation ceo => ceo.Elements[index],
IArrayInitializerOperation initializerOperation => initializerOperation.ElementValues[index],
_ => null
} as IConversionOperation;

if (conversionOperation == null)
return null;

return (conversionOperation.Operand.Type, conversionOperation.Type, conversionOperation.OperatorMethod);
}
}

internal static void InitializeOptimized<TElement>(ExpressionVisitor visitor, ITypeSymbol elementType, SeparatedSyntaxList<TElement> elements) where TElement : SyntaxNode
Expand All @@ -63,3 +84,4 @@ internal static void InitializeOptimized<TElement>(ExpressionVisitor visitor, IT
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Call, initializeArrayHelper);
}
}

11 changes: 7 additions & 4 deletions Cecilifier.Core/AST/AssignmentVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private void AddCallToOpImplicitIfRequired(IdentifierNameSyntax node)
if (conversion.IsImplicit && conversion.MethodSymbol != null
&& !conversion.IsMethodGroup) // method group to delegate conversions should not call the method being converted...
{
AddMethodCall(ilVar, conversion.MethodSymbol);
Context.AddCallToMethod(conversion.MethodSymbol, ilVar);
}
}

Expand Down Expand Up @@ -199,14 +199,14 @@ bool HandleIndexer(SyntaxNode node, LinkedListNode<string> lastInstructionLoadin
//
// so we emit the call to get_item() and then move the instructions generated for `CalculateValue()`
// bellow it.
AddMethodCall(ilVar, propertySymbol.GetMethod);
Context.AddCallToMethod(propertySymbol.GetMethod, ilVar, MethodDispatchInformation.MostLikelyVirtual);
Context.MoveLinesToEnd(InstructionPrecedingValueToLoad, lastInstructionLoadingRhs);
EmitIndirectStore(propertySymbol.Type);
}
else
{
Context.MoveLinesToEnd(InstructionPrecedingValueToLoad, lastInstructionLoadingRhs);
AddMethodCall(ilVar, propertySymbol.SetMethod);
Context.AddCallToMethod(propertySymbol.SetMethod, ilVar, MethodDispatchInformation.MostLikelyVirtual);
}

return true;
Expand All @@ -229,7 +229,10 @@ private void PropertyAssignment(IdentifierNameSyntax node, IPropertySymbol prope
Context.EmitCilInstruction(ilVar, OpCodes.Stfld, found.VariableName);
}
else
AddMethodCall(ilVar, property.SetMethod, node.Parent.MethodDispatchInformation());
{
MethodDispatchInformation dispatchInformation = node.Parent.MethodDispatchInformation();
Context.AddCallToMethod(property.SetMethod, ilVar, dispatchInformation);
}
}

private void FieldAssignment(IFieldSymbol field, IdentifierNameSyntax name)
Expand Down
29 changes: 17 additions & 12 deletions Cecilifier.Core/AST/CollectionExpressionProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using Cecilifier.Core.Naming;
using Cecilifier.Core.Variables;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
using Mono.Cecil.Cil;
Expand Down Expand Up @@ -75,6 +74,7 @@ private static void HandleAssignmentToList(ExpressionVisitor visitor, Collection
var index = 0;
var spanGetItemMethod = GetSpanIndexerGetMethod(context, resolvedListTypeArgument);
var stindOpCode = listOfTTypeSymbol.TypeArguments[0].StindOpCodeFor();
var targetElementType = stindOpCode == OpCodes.Stobj ? resolvedListTypeArgument : null; // Stobj expects the type of the object being stored.

var collectionExpressionOperation = context.SemanticModel.GetOperation(node).EnsureNotNull<IOperation, ICollectionExpressionOperation>();
foreach (var element in node.Elements)
Expand All @@ -83,8 +83,8 @@ private static void HandleAssignmentToList(ExpressionVisitor visitor, Collection
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, index);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Call, spanGetItemMethod);
visitor.Visit(element);
ApplyNumericConversions(context, visitor.ILVariable, collectionExpressionOperation.Elements[index]);
context.EmitCilInstruction(visitor.ILVariable, stindOpCode);
ApplyConversions(context, visitor.ILVariable, collectionExpressionOperation.Elements[index]);
context.EmitCilInstruction(visitor.ILVariable, stindOpCode, targetElementType);
index++;
}
}
Expand Down Expand Up @@ -112,6 +112,7 @@ private static void HandleAssignmentToSpan(ExpressionVisitor visitor, Collection
.MakeGenericInstanceMethod(context, "InlineArrayElementRef", [$"{inlineArrayLocalVar}.VariableType", context.TypeResolver.Resolve(spanTypeSymbol.TypeArguments[0])]);

var storeOpCode = inlineArrayElementType.StindOpCodeFor();
var targetElementType = storeOpCode == OpCodes.Stobj ? context.TypeResolver.Resolve(inlineArrayElementType) : null; // Stobj expects the type of the object being stored.
var collectionExpressionOperation = context.SemanticModel.GetOperation(node).EnsureNotNull<IOperation, ICollectionExpressionOperation>();
var index = 0;
foreach (var element in node.Elements)
Expand All @@ -120,8 +121,8 @@ private static void HandleAssignmentToSpan(ExpressionVisitor visitor, Collection
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, index);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Call, inlineArrayElementRefMethodVar);
visitor.Visit(element);
ApplyNumericConversions(context, visitor.ILVariable, collectionExpressionOperation.Elements[index]);
context.EmitCilInstruction(visitor.ILVariable, storeOpCode, storeOpCode == OpCodes.Ldobj ? context.TypeResolver.Resolve(inlineArrayElementType) : null);
ApplyConversions(context, visitor.ILVariable, collectionExpressionOperation.Elements[index]);
context.EmitCilInstruction(visitor.ILVariable, storeOpCode, targetElementType);
index++;
}

Expand Down Expand Up @@ -215,13 +216,17 @@ private static string GetSpanIndexerGetMethod(IVisitorContext context, string ty
return methodVar;
}

static void ApplyNumericConversions(IVisitorContext context, string ilVar, IOperation operation)
static void ApplyConversions(IVisitorContext context, string ilVar, IOperation operation)
{
if (operation is not IConversionOperation { Conversion.IsNumeric: true } elementConversion)
return;

var result = context.TryApplyNumericConversion(ilVar, operation.Type, elementConversion.Type);
if (!result)
throw new Exception();
if (operation is IConversionOperation { Conversion.IsNumeric: true } elementConversion)
{
var result = context.TryApplyNumericConversion(ilVar, operation.Type, elementConversion.Type);
if (!result)
throw new Exception();
}
else if (operation is IConversionOperation { OperatorMethod: not null } conversion)
{
context.AddCallToMethod(conversion.OperatorMethod, ilVar);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public override void VisitElementAccessExpression(ElementAccessExpressionSyntax
node.ArgumentList.Accept(this); // Visit the argument list with ourselves.

var sliceMethod = elementAccessExpressionType.GetMembers("Slice").OfType<IMethodSymbol>().Single(candidate => candidate.Parameters.Length == 2); // Slice(int, int)
AddMethodCall(_ilVar, sliceMethod);
Context.AddCallToMethod(sliceMethod, _ilVar, MethodDispatchInformation.MostLikelyVirtual);
}

// This will handle usages like s[1..^3], i.e, RangeExpressions used in the argument
Expand Down Expand Up @@ -74,7 +74,7 @@ public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax no
private void ProcessIndexerExpressionWithRangeAsArgument(ExpressionSyntax node)
{
using var _ = LineInformationTracker.Track(Context, node);
AddMethodCall(_ilVar, _targetSpanType.GetMembers().OfType<IPropertySymbol>().Single(p => p.Name == "Length").GetMethod);
Context.AddCallToMethod(_targetSpanType.GetMembers().OfType<IPropertySymbol>().Single(p => p.Name == "Length").GetMethod, _ilVar, MethodDispatchInformation.MostLikelyVirtual);
var spanLengthVar = CodeGenerationHelpers.StoreTopOfStackInLocalVariable(Context, _ilVar, "spanLengthVar", Context.RoslynTypeSystem.SystemInt32).VariableName;

node.Accept(_expressionVisitor);
Expand All @@ -84,23 +84,23 @@ private void ProcessIndexerExpressionWithRangeAsArgument(ExpressionSyntax node)
var rangeVar = CodeGenerationHelpers.StoreTopOfStackInLocalVariable(Context, _ilVar, "rangeVar", systemRange).VariableName;

Context.EmitCilInstruction(_ilVar, OpCodes.Ldloca, rangeVar);
AddMethodCall(_ilVar, systemRange.GetMembers().OfType<IPropertySymbol>().Single(p => p.Name == "Start").GetMethod);
Context.AddCallToMethod(systemRange.GetMembers().OfType<IPropertySymbol>().Single(p => p.Name == "Start").GetMethod, _ilVar, MethodDispatchInformation.MostLikelyVirtual);
var indexVar = CodeGenerationHelpers.StoreTopOfStackInLocalVariable(Context, _ilVar, "index", systemIndex).VariableName;

Context.EmitCilInstruction(_ilVar, OpCodes.Ldloca, indexVar);
Context.EmitCilInstruction(_ilVar, OpCodes.Ldloc, spanLengthVar);
AddMethodCall(_ilVar, systemIndex.GetMembers().OfType<IMethodSymbol>().Single(p => p.Name == "GetOffset"));
Context.AddCallToMethod(systemIndex.GetMembers().OfType<IMethodSymbol>().Single(p => p.Name == "GetOffset"), _ilVar, MethodDispatchInformation.MostLikelyVirtual);

var startIndexVar = CodeGenerationHelpers.StoreTopOfStackInLocalVariable(Context, _ilVar, "startIndex", Context.RoslynTypeSystem.SystemInt32).VariableName;

// Calculate number of elements to slice.
Context.EmitCilInstruction(_ilVar, OpCodes.Ldloca, rangeVar);
AddMethodCall(_ilVar, systemRange.GetMembers().OfType<IPropertySymbol>().Single(p => p.Name == "End").GetMethod);
Context.AddCallToMethod(systemRange.GetMembers().OfType<IPropertySymbol>().Single(p => p.Name == "End").GetMethod, _ilVar, MethodDispatchInformation.MostLikelyVirtual);
Context.EmitCilInstruction(_ilVar, OpCodes.Stloc, indexVar);

Context.EmitCilInstruction(_ilVar, OpCodes.Ldloca, indexVar);
Context.EmitCilInstruction(_ilVar, OpCodes.Ldloc, spanLengthVar);
AddMethodCall(_ilVar, systemIndex.GetMembers().OfType<IMethodSymbol>().Single(p => p.Name == "GetOffset"));
Context.AddCallToMethod(systemIndex.GetMembers().OfType<IMethodSymbol>().Single(p => p.Name == "GetOffset"), _ilVar, MethodDispatchInformation.MostLikelyVirtual);
Context.EmitCilInstruction(_ilVar, OpCodes.Ldloc, startIndexVar);
Context.EmitCilInstruction(_ilVar, OpCodes.Sub);
var elementCountVar = CodeGenerationHelpers.StoreTopOfStackInLocalVariable(Context, _ilVar, "elementCount", Context.RoslynTypeSystem.SystemInt32).VariableName;
Expand Down
6 changes: 3 additions & 3 deletions Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void InjectRequiredConversions(ExpressionSyntax expression, Action loadAr

if (conversion.MethodSymbol != null)
{
AddMethodCall(ilVar, conversion.MethodSymbol, MethodDispatchInformation.MostLikelyVirtual);
Context.AddCallToMethod(conversion.MethodSymbol, ilVar, MethodDispatchInformation.MostLikelyVirtual);
}
}

Expand All @@ -54,11 +54,11 @@ public void InjectRequiredConversions(ExpressionSyntax expression, Action loadAr
loadArrayIntoStack();
var indexedType = Context.SemanticModel.GetTypeInfo(expression.Ancestors().OfType<ElementAccessExpressionSyntax>().Single().Expression).Type.EnsureNotNull();
if (indexedType.Name == "Span")
AddMethodCall(ilVar, ((IPropertySymbol) indexedType.GetMembers("Length").Single()).GetMethod);
Context.AddCallToMethod(((IPropertySymbol) indexedType.GetMembers("Length").Single()).GetMethod, ilVar, MethodDispatchInformation.MostLikelyVirtual);
else
Context.EmitCilInstruction(ilVar, OpCodes.Ldlen);
Context.EmitCilInstruction(ilVar, OpCodes.Conv_I4);
AddMethodCall(ilVar, (IMethodSymbol) typeInfo.Type.GetMembers().Single(m => m.Name == "GetOffset"));
Context.AddCallToMethod((IMethodSymbol) typeInfo.Type.GetMembers().Single(m => m.Name == "GetOffset"), ilVar, MethodDispatchInformation.MostLikelyVirtual);
}

// Empirically (verified in generated IL), expressions of type parameter used as:
Expand Down
Loading

0 comments on commit c4b2255

Please sign in to comment.