Skip to content

Commit

Permalink
adds support for initializing List<T> with collection expressions (#256)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianoc committed Sep 26, 2024
1 parent de35c9a commit b63e097
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,15 @@ public void SpanAsParameter()
// generates the same error.
);
}

[Test]
public void ListOfT()
{
AssertOutput(
"""
System.Collections.Generic.List<char> list = ['C', 'E', 'C', 'I', 'L'];
foreach(var c in list.ToArray()) System.Console.Write(c);
""",
"CECIL");
}
}
74 changes: 74 additions & 0 deletions Cecilifier.Core/AST/CollectionExpressionProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Cecilifier.Core.CodeGeneration;
using Cecilifier.Core.Extensions;
using Cecilifier.Core.Misc;
Expand All @@ -22,12 +24,84 @@ public static void Process(ExpressionVisitor visitor, CollectionExpressionSyntax
{
HandleAssignmentToArray(visitor, node, arrayType);
}
else if (SymbolEqualityComparer.Default.Equals(targetTypeSymbol.OriginalDefinition, visitor.Context.SemanticModel.Compilation.GetTypeByMetadataName(typeof(List<>).FullName!)))
{
HandleAssignmentToList(visitor, node, (INamedTypeSymbol) targetTypeSymbol);
}
else
{
HandleAssignmentToSpan(visitor, node, (INamedTypeSymbol) targetTypeSymbol);
}
}

private static void HandleAssignmentToList(ExpressionVisitor visitor, CollectionExpressionSyntax node, INamedTypeSymbol listOfTTypeSymbol)
{
var context = visitor.Context;
var resolvedListTypeArgument = context.TypeResolver.Resolve(listOfTTypeSymbol.TypeArguments[0]);

context.WriteNewLine();
context.WriteComment("Instantiates a List<T> passing the # of elements in the collection expression to its ctor.");
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, node.Elements.Count);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Newobj, listOfTTypeSymbol.Constructors.First(ctor => ctor.Parameters.Length == 1).MethodResolverExpression(visitor.Context));

// Pushes an extra copy of the reference to the list instance into the stack
// to avoid introducing a local variable. This will be left at the top of the stack
// when the initialization code finishes.
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Dup);

// Calls 'CollectionsMarshal.SetCount(list, num)' on the list.
var collectionMarshalTypeSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName(typeof(CollectionsMarshal).FullName!);
var setCountMethod = collectionMarshalTypeSymbol.GetMembers("SetCount").OfType<IMethodSymbol>().Single().MethodResolverExpression(context).MakeGenericInstanceMethod(context, "SetCount", [ resolvedListTypeArgument ]);

context.EmitCilInstruction(visitor.ILVariable, OpCodes.Dup);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, node.Elements.Count);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Call, setCountMethod);

context.WriteNewLine();
context.WriteComment("Add a Span<T> local variable and initialize it with `CollectionsMarshal.AsSpan(list)`");
var spanToList = context.AddLocalVariableToCurrentMethod(
"listSpan",
context.TypeResolver.Resolve(context.RoslynTypeSystem.SystemSpan).MakeGenericInstanceType(resolvedListTypeArgument));

context.EmitCilInstruction(visitor.ILVariable,
OpCodes.Call,
collectionMarshalTypeSymbol.GetMembers("AsSpan").OfType<IMethodSymbol>().Single().MethodResolverExpression(context).MakeGenericInstanceMethod(context, "AsSpan", [ resolvedListTypeArgument ]));
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Stloc, spanToList.VariableName);

context.WriteNewLine();
context.WriteComment($"Initialize each list element through the span (variable '{spanToList.VariableName}')");
var index = 0;
var spanGetItemMethod = GetMethod(context, resolvedListTypeArgument);
var stindOpCode = listOfTTypeSymbol.TypeArguments[0].StindOpCodeFor();

foreach (var element in node.Elements)
{
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldloca_S, spanToList.VariableName);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Ldc_I4, index);
context.EmitCilInstruction(visitor.ILVariable, OpCodes.Call, spanGetItemMethod);
visitor.Visit(element);
//TODO: Inject conversions if needed. Test with
// List<object> l = [1, 2, 3];
// List<long> l = [1, 2, 3];
context.EmitCilInstruction(visitor.ILVariable, stindOpCode);
index++;
}
}

private static string GetMethod(IVisitorContext context, string typeArgument)
{
var methodVar = context.Naming.SyntheticVariable("getItem", ElementKind.Method);
var declaringType = context.TypeResolver.Resolve(context.RoslynTypeSystem.SystemSpan).MakeGenericInstanceType(typeArgument);
context.WriteCecilExpression($$"""var {{methodVar}} = new MethodReference("get_Item", {{context.TypeResolver.Bcl.System.Void}}, {{declaringType}}) { HasThis = true, ExplicitThis = false };""");
context.WriteNewLine();
context.WriteCecilExpression($"{methodVar}.Parameters.Add(new ParameterDefinition({context.TypeResolver.Bcl.System.Int32}));");
context.WriteNewLine();
context.WriteCecilExpression($"""{methodVar}.ReturnType = ((GenericInstanceType) {methodVar}.DeclaringType).ElementType.GenericParameters[0].MakeByReferenceType();""");
context.WriteNewLine();

return methodVar;
}

private static void HandleAssignmentToSpan(ExpressionVisitor visitor, CollectionExpressionSyntax node, INamedTypeSymbol spanTypeSymbol)
{
Debug.Assert(SymbolEqualityComparer.Default.Equals(spanTypeSymbol.OriginalDefinition, visitor.Context.RoslynTypeSystem.SystemSpan));
Expand Down

0 comments on commit b63e097

Please sign in to comment.