Skip to content

Commit

Permalink
Merge pull request #254 from adrianoc/vnext
Browse files Browse the repository at this point in the history
Merging vnext to main
  • Loading branch information
adrianoc authored Oct 26, 2023
2 parents 6e47175 + 75432d6 commit 3f6109d
Show file tree
Hide file tree
Showing 20 changed files with 356 additions and 183 deletions.
2 changes: 1 addition & 1 deletion Cecilifier.Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11</LangVersion>
<AssemblyVersion>2.5.0</AssemblyVersion>
<AssemblyVersion>2.6.0</AssemblyVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
34 changes: 34 additions & 0 deletions Cecilifier.Core.Tests/Tests/Unit/AttributesTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.RegularExpressions;
using NUnit.Framework;

namespace Cecilifier.Core.Tests.Tests.Unit;
Expand Down Expand Up @@ -69,6 +70,39 @@ class Foo<TFoo> {{ }}");
@"cls_foo_\d+\.CustomAttributes\.Add\(\1\);"));
}

[TestCase("LayoutKind.Auto, Pack=1, Size=12", 1, 12)]
[TestCase("LayoutKind.Auto, Pack=1", 1, 0)]
[TestCase("LayoutKind.Auto, Size=42", 0, 42)]
[TestCase("LayoutKind.Sequential")]
public void StructLayout_ItNotEmitted(string initializationData, int expectedPack = -1, int expectedSize = -1)
{
// StructLayout attribute should not be emitted to metadata as an attribute;
// instead, the respective properties in the type definition should be set.

var result = RunCecilifier($$"""
using System.Runtime.InteropServices;
[StructLayout({{initializationData}})]
struct Foo { }
""");

var cecilifiedCode = result.GeneratedCode.ReadToEnd();

Assert.That(cecilifiedCode, Does.Not.Match(@"st_foo_\d+.CustomAttributes.Add\(attr_structLayout_\d+\);"));

if (expectedSize == -1 && expectedPack == -1)
{
Assert.That(cecilifiedCode, Does.Not.Match(@"st_foo_\d+.ClassSize = \d+;"));
Assert.That(cecilifiedCode, Does.Not.Match(@"st_foo_\d+.PackingSize = \d+;"));
}
else
{
Assert.That(cecilifiedCode, Does.Match(@$"st_foo_\d+.ClassSize = {expectedSize};"));
Assert.That(cecilifiedCode, Does.Match(@$"st_foo_\d+.PackingSize = {expectedPack};"));
}

Assert.That(cecilifiedCode, Does.Match($@"\|\s+TypeAttributes.{Regex.Match(initializationData, @"LayoutKind\.([^,$]+)").Groups[1].Value}Layout"));
}

private const string AttributeDefinition = "class MyAttribute : System.Attribute { public MyAttribute(string message) {} } ";
private const string GenericAttributeDefinition = @"
[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple =true)]
Expand Down
7 changes: 7 additions & 0 deletions Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ namespace Cecilifier.Core.Tests.Tests.Unit;
[TestFixture]
public partial class MiscellaneousStatements : CecilifierUnitTestBase
{
[Test]
public void EntryPointInTopLevelStatements()
{
var result = RunCecilifier("System.Console.WriteLine(42);");
Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(@"assembly\.EntryPoint = m_topLevelStatements_\d+"));
}

[Test]
public void BreakInForBody()
{
Expand Down
19 changes: 19 additions & 0 deletions Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,25 @@ public void TestNullConditionalOperatorOnStorageTargets(string target, Code expe
@"il_M_3.Append\(lbl_conditionEnd_7\);"));
}

[TestCase("f", "Ldfld, fld_f_6", TestName = "Field")]
[TestCase("P", "Call, m_get_2", TestName = "Property")]
[TestCase("M()", "Call, m_M_4", TestName = "Method")]
public void TestNullConditionalOperator_OnQualifiedMemberAccess_DoesNoLoadThis(string member, string expected)
{
var result = RunCecilifier($$"""class Foo { private int P => 0; private int M() => 0; private int f; object Bar(Foo p) => p?.{{member}}; }""");
Assert.That(
result.GeneratedCode.ReadToEnd(),
Does.Match(
$"""
(il_bar_\d+\.Emit\(OpCodes\.)Ldarg_1\);
(\s+\1){expected}\);
\2Newobj, .+ImportReference\(.+ResolveMethod\(typeof\(System\.Nullable<System.Int32>\), ".ctor",.+, "System.Int32"\)\)\);
\s+il_bar_\d+.Append\(lbl_conditionEnd_\d+\);
\2Box,.+ImportReference\(typeof\(System.Nullable<>\)\)\.MakeGenericInstanceType\(assembly.MainModule.TypeSystem.Int32\)\);
\2Ret\);
"""));
}

[TestCase(
"int i = 42; i -= 10;",
@"(il_topLevelMain_\d\.Emit\(OpCodes\.)Ldc_I4, 42\);\s+\1Stloc, (l_i_\d+)\);\s+\1Ldloc, \2\);\s+\1Ldc_I4, 10\);\s+\1Sub\);\s+\1Stloc, \2\);\s+m_topLevelStatements_1\.Body\.Instructions\.Add\(.+OpCodes\.Ret\)\);",
Expand Down
8 changes: 3 additions & 5 deletions Cecilifier.Core/AST/CompilationUnitVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public override void VisitGlobalStatement(GlobalStatementSyntax node)
{
Context.WriteComment("Begin of global statements.");
_globalStatementHandler = new GlobalStatementHandler(Context, node);
MainMethodDefinitionVariable = _globalStatementHandler.MainMethodDefinitionVariable;
}

if (_globalStatementHandler.HandleGlobalStatement(node))
Expand Down Expand Up @@ -63,9 +64,6 @@ private void VisitDeclaredTypesSortedByDependencies()

private void UpdateTypeInformation(BaseTypeDeclarationSyntax node)
{
if (mainType == null)
mainType = node;

var typeSymbol = ModelExtensions.GetDeclaredSymbol(Context.SemanticModel, node) as ITypeSymbol;
if (typeSymbol == null)
return;
Expand All @@ -77,8 +75,8 @@ private void UpdateTypeInformation(BaseTypeDeclarationSyntax node)
MainMethodDefinitionVariable = Context.DefinitionVariables.GetMethodVariable(mainMethod.AsMethodDefinitionVariable());
}

var mainTypeSymbol = (ITypeSymbol) ModelExtensions.GetDeclaredSymbol(Context.SemanticModel, mainType);
if (typeSymbol.GetMembers().Length > mainTypeSymbol?.GetMembers().Length)
var mainTypeSymbol = (ITypeSymbol) Context.SemanticModel.GetDeclaredSymbol(mainType ?? node);
if (mainType == null || typeSymbol.GetMembers().Length > mainTypeSymbol?.GetMembers().Length)
{
mainType = node;
}
Expand Down
25 changes: 1 addition & 24 deletions Cecilifier.Core/AST/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@ private void ProcessProperty(SimpleNameSyntax node, IPropertySymbol propertySymb
propertySymbol.EnsurePropertyExists(Context, node);

var isAccessOnThisOrObjectCreation = node.Parent.IsAccessOnThisOrObjectCreation();
if (IsUnqualifiedAccess(node, propertySymbol))
if (!propertySymbol.IsStatic && !node.IsQualifiedAccessToTypeOrMember())
{
// if this is an *unqualified* access we need to load *this*
Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0);
Expand Down Expand Up @@ -1212,29 +1212,6 @@ private void HandlePropertyGetAccess(SimpleNameSyntax node, IPropertySymbol prop
}
}

/// <summary>
/// Checks whether the given <paramref name="node"/> represents an unqualified reference (i.e, only a type name) or
/// a qualified one. For instance in `Foo f = new NS.Foo();`,
/// 1. Foo => Unqualified
/// 2. NS.Foo => Qualified
///
/// </summary>
/// <param name="node">Node with the name of the property being accessed.</param>
/// <param name="propertySymbol">Property symbol representing the property being accessed.</param>
/// <returns>true if <paramref name="node"/> represents an unqualified access to <paramref name="propertySymbol"/>, false otherwise</returns>
/// <remarks>
/// A NameColon syntax represents the `Length: 42` in an expression like `o as string { Length: 42 }`
/// A MemberBindExpression represents the null coalescing operator
/// </remarks>
private static bool IsUnqualifiedAccess(SimpleNameSyntax node, IPropertySymbol propertySymbol)
{
var parentMae = node.Parent as MemberAccessExpressionSyntax;
return (parentMae == null || parentMae.Expression == node)
&& !node.Parent.IsKind(SyntaxKind.NameColon)
&& !node.Parent.IsKind(SyntaxKind.MemberBindingExpression)
&& !propertySymbol.IsStatic;
}

private void ProcessMethodReference(SimpleNameSyntax node, IMethodSymbol method)
{
var invocationParent = node.Ancestors().OfType<InvocationExpressionSyntax>()
Expand Down
2 changes: 2 additions & 0 deletions Cecilifier.Core/AST/GlobalStatementHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ bool IsLastGlobalStatement(CompilationUnitSyntax compilation, int index)
}
}

public string MainMethodDefinitionVariable => methodVar;

private void WriteCecilExpressions(IEnumerable<string> expressions)
{
foreach (var exp in expressions)
Expand Down
2 changes: 1 addition & 1 deletion Cecilifier.Core/AST/IVisitorContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal interface IVisitorContext

void EmitCilInstruction(string ilVar, OpCode opCode);
void EmitCilInstruction<T>(string ilVar, OpCode opCode, T operand, string comment = null);
void WriteCecilExpression(string msg);
void WriteCecilExpression(string expression);
void WriteComment(string comment);
void WriteNewLine();

Expand Down
2 changes: 1 addition & 1 deletion Cecilifier.Core/AST/StatementVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ private void ProcessVariableInitialization(VariableDeclaratorSyntax localVar, IT
var valueBeingAssignedIsByRef = Context.SemanticModel.GetSymbolInfo(localVar.Initializer.Value).Symbol.IsByRef();
if (!variableType.IsByRef() && valueBeingAssignedIsByRef)
{
OpCode opCode = LoadIndirectOpCodeFor(variableType.SpecialType);
OpCode opCode = variableType.LoadIndirectOpCodeFor();
Context.EmitCilInstruction(_ilVar, opCode);
}

Expand Down
26 changes: 26 additions & 0 deletions Cecilifier.Core/AST/SyntaxWalkerBase.Private.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.CodeAnalysis;

namespace Cecilifier.Core.AST;

// Helper types / members useful only to SyntaxWalkerBase.
// Some of these may be refactored and made public/internal if needed in the future.
internal partial class SyntaxWalkerBase
{
internal enum AttributeKind
{
DllImport,
StructLayout,
Ordinary
}
}

public static class PrivateExtensions
{
internal static SyntaxWalkerBase.AttributeKind AttributeKind(this ITypeSymbol self) => (self.ContainingNamespace.ToString(), self.Name) switch
{
("System.Runtime.InteropServices", "DllImportAttribute") => SyntaxWalkerBase.AttributeKind.DllImport,
("System.Runtime.InteropServices", "StructLayoutAttribute") => SyntaxWalkerBase.AttributeKind.StructLayout,
_ => SyntaxWalkerBase.AttributeKind.Ordinary,
};
}

111 changes: 32 additions & 79 deletions Cecilifier.Core/AST/SyntaxWalkerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

namespace Cecilifier.Core.AST
{
internal class SyntaxWalkerBase : CSharpSyntaxWalker
internal partial class SyntaxWalkerBase : CSharpSyntaxWalker
{
internal SyntaxWalkerBase(IVisitorContext ctx)
{
Expand Down Expand Up @@ -224,54 +224,12 @@ private bool LoadDefaultValueForTypeParameter(string ilVar, ITypeSymbol type, Sy

private void LoadLiteralToStackHandlingCallOnValueTypeLiterals(string ilVar, ITypeSymbol literalType, object literalValue, bool isTargetOfCall)
{
var opCode = LoadOpCodeFor(literalType);
var opCode = literalType.LoadOpCodeFor();
Context.EmitCilInstruction(ilVar, opCode, literalValue);
if (isTargetOfCall)
StoreTopOfStackInLocalVariableAndLoadItsAddress(ilVar, literalType);
}

private OpCode LoadOpCodeFor(ITypeSymbol type)
{
switch (type.SpecialType)
{
case SpecialType.System_IntPtr:
case SpecialType.System_UIntPtr:
return OpCodes.Ldc_I4;

case SpecialType.System_Single:
return OpCodes.Ldc_R4;

case SpecialType.System_Double:
return OpCodes.Ldc_R8;

case SpecialType.System_Byte:
case SpecialType.System_SByte:
case SpecialType.System_Int16:
case SpecialType.System_Int32:
case SpecialType.System_UInt16:
case SpecialType.System_UInt32:
return OpCodes.Ldc_I4;

case SpecialType.System_UInt64:
case SpecialType.System_Int64:
return OpCodes.Ldc_I8;

case SpecialType.System_Char:
return OpCodes.Ldc_I4;

case SpecialType.System_Boolean:
return OpCodes.Ldc_I4;

case SpecialType.System_String:
return OpCodes.Ldstr;

case SpecialType.None:
return OpCodes.Ldnull;
}

throw new ArgumentException($"Literal type {type} not supported.", nameof(type));
}

private void StoreTopOfStackInLocalVariableAndLoadItsAddress(string ilVar, ITypeSymbol type, string variableName = "tmp")
{
var tempLocalName = StoreTopOfStackInLocalVariable(Context, ilVar, variableName, type).VariableName;
Expand Down Expand Up @@ -365,7 +323,7 @@ private static void AppendStructLayoutTo(ITypeSymbol typeSymbol, StringBuilder t
_ => throw new ArgumentException($"Invalid StructLayout value for {typeSymbol.Name}")
};

typeAttributes.AppendModifier($"TypeAttributes.{specifiedLayout}");
typeAttributes.AppendModifier(specifiedLayout);
}
}

Expand Down Expand Up @@ -499,8 +457,7 @@ protected void ProcessField(string ilVar, SimpleNameSyntax node, IFieldSymbol fi

var fieldDeclarationVariable = fieldSymbol.EnsureFieldExists(Context, node);

var isTargetOfQualifiedAccess = (node.Parent is MemberAccessExpressionSyntax mae) && mae.Name == node;
if (!fieldSymbol.IsStatic && !isTargetOfQualifiedAccess)
if (!fieldSymbol.IsStatic && !node.IsQualifiedAccessToTypeOrMember())
Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0);

if (HandleLoadAddressOnStorage(ilVar, fieldSymbol.Type, node, fieldSymbol.IsStatic ? OpCodes.Ldsflda : OpCodes.Ldflda, fieldSymbol.Name, VariableMemberKind.Field, fieldSymbol.ContainingType.ToDisplayString()))
Expand Down Expand Up @@ -665,8 +622,7 @@ protected void HandlePotentialRefLoad(string ilVar, SyntaxNode expression, IType

if (needsLoadIndirect)
{
OpCode opCode = LoadIndirectOpCodeFor(type.SpecialType);
Context.EmitCilInstruction(ilVar, opCode);
Context.EmitCilInstruction(ilVar, type.LoadIndirectOpCodeFor());
}
}

Expand Down Expand Up @@ -708,29 +664,6 @@ private IParameterSymbol ParameterSymbolFromArgumentSyntax(ArgumentSyntax argume
return null;
}

protected OpCode LoadIndirectOpCodeFor(SpecialType type)
{
return type switch
{
SpecialType.System_Single => OpCodes.Ldind_R4,
SpecialType.System_Double => OpCodes.Ldind_R8,
SpecialType.System_SByte => OpCodes.Ldind_I1,
SpecialType.System_Byte => OpCodes.Ldind_U1,
SpecialType.System_Int16 => OpCodes.Ldind_I2,
SpecialType.System_UInt16 => OpCodes.Ldind_U2,
SpecialType.System_Int32 => OpCodes.Ldind_I4,
SpecialType.System_UInt32 => OpCodes.Ldind_U4,
SpecialType.System_Int64 => OpCodes.Ldind_I8,
SpecialType.System_UInt64 => OpCodes.Ldind_I8,
SpecialType.System_Char => OpCodes.Ldind_U2,
SpecialType.System_Boolean => OpCodes.Ldind_U1,
SpecialType.System_Object => OpCodes.Ldind_Ref,
SpecialType.None => OpCodes.Ldind_Ref,

_ => throw new ArgumentException($"Literal type {type} not supported.", nameof(type))
};
}

private bool IsSystemIndexUsedAsIndex(ITypeSymbol symbol, SyntaxNode node)
{
if (symbol.MetadataToken != Context.RoslynTypeSystem.SystemIndex.MetadataToken)
Expand Down Expand Up @@ -789,20 +722,20 @@ protected static void HandleAttributesInTypeParameter(IVisitorContext context, I

private static void HandleAttributesInMemberDeclaration(IVisitorContext context, IEnumerable<AttributeListSyntax> attributeLists, string targetDeclarationVar)
{
if (!attributeLists.Any())
return;

foreach (var attribute in attributeLists.SelectMany(al => al.Attributes))
{
var type = context.SemanticModel.GetSymbolInfo(attribute).Symbol.EnsureNotNull<ISymbol, IMethodSymbol>().ContainingType;

//TODO: Pass the correct list of type parameters when C# supports generic attributes.
TypeDeclarationVisitor.EnsureForwardedTypeDefinition(context, type, Array.Empty<TypeParameterSyntax>());

var attrsExp = context.SemanticModel.GetSymbolInfo(attribute.Name).Symbol.IsDllImportCtor()
? ProcessDllImportAttribute(context, attribute, targetDeclarationVar)
: ProcessNormalMemberAttribute(context, attribute, targetDeclarationVar);

var attrsExp = type.AttributeKind() switch
{
AttributeKind.DllImport => ProcessDllImportAttribute(context, attribute, targetDeclarationVar),
AttributeKind.StructLayout => ProcessStructLayoutAttribute(attribute, targetDeclarationVar),
_ => ProcessNormalMemberAttribute(context, attribute, targetDeclarationVar)
};

AddCecilExpressions(context, attrsExp);
}
}
Expand Down Expand Up @@ -913,6 +846,26 @@ string AttributePropertyOrDefaultValue(AttributeSyntax attr, string propertyName
}
}

private static IEnumerable<string> ProcessStructLayoutAttribute(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<string>();

return new[]
{
$"{typeVar}.ClassSize = { AssignedValue(attribute, "Size") };",
$"{typeVar}.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.
var parameterAssignmentExpression = attribute.ArgumentList?.Arguments.FirstOrDefault(a => a.NameEquals?.Name.Identifier.Text == parameterName)?.Expression;
return parameterAssignmentExpression != null ? Int32.Parse(((LiteralExpressionSyntax) parameterAssignmentExpression).Token.Text) : 0;
}
}

private static string CallingConventionToCecil(CallingConvention callingConvention)
{
var pinvokeAttribute = callingConvention switch
Expand Down
Loading

0 comments on commit 3f6109d

Please sign in to comment.