Skip to content

Commit

Permalink
fixes field access through MemberAccessExpressions on 'T[]', 'List<T>…
Browse files Browse the repository at this point in the history
…', etc (#274)
  • Loading branch information
adrianoc committed Apr 4, 2024
1 parent 25fb5ec commit 3affe66
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 9 deletions.
70 changes: 68 additions & 2 deletions Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using NUnit.Framework;

namespace Cecilifier.Core.Tests.Tests.Unit
{
/*
* Some generic types/methods related features are covered in other tests (for example,
* generic local functions are covered in LocalFunctionTests.cs
* generic local functions are covered in LocalFunctionTests.cs)
*/
[TestFixture]
public class GenericTests : CecilifierUnitTestBase
Expand Down Expand Up @@ -380,7 +381,16 @@ public void GenericParameter_IsUsed_InsteadOfTypeOf_Issue240()
(\s+il_test_\d+\.Emit\(OpCodes\.)Ldarg_1\);
\2Stloc, l_lt_\d+\);
""", TestName = "Constrained: Local variable initializer does not box")]
public void GenericType_Boxing(string statementToUse, string expectedILSnippet)

[TestCase(
"static T1 M<T1>(T1[] a) where T1 : Foo => a[0]",
"""
(\s+il_M_\d+\.Emit\(OpCodes\.)Ldarg_0\);
\1Ldc_I4, 0\);
\1Ldelem_Any, gp_t1_\d+\);
\1Ret\);
""", TestName = "Array")]
public void GenericType_Boxing(string statementToUse, [StringSyntax(StringSyntaxAttribute.Regex)] string expectedILSnippet)
{
var result = RunCecilifier($$"""
using System;
Expand Down Expand Up @@ -431,5 +441,61 @@ public void GenericInstanceMethods_AreCached()
Assert.That(intVersionCount.Count, Is.EqualTo(1), cecilifiedCode);
});
}

[TestCase(
"static int M<T>(T[] b) where T : Foo => b[0].data;",
"""
(\s+il_M_16\.Emit\(OpCodes\.)Ldarg_0\);
\1Ldc_I4, 0\);
\1Ldelem_Any, gp_T_\d+\);
\1Box, gp_T_\d+\);
\1Ldfld, fld_data_\d+\);
""",
TestName = "Array")]

[TestCase("static int M<T>(List<T> b) where T : Foo => b[0].data;",
"""
(\s+il_M_\d+\.Emit\(OpCodes\.)Callvirt, r_get_Item_\d+\);
\1Box, gp_T_\d+\);
\1Ldfld, fld_data_\d+\);
""",
TestName = "List<T>")]

[TestCase("static int M<T>(IList<T> b) where T : Foo => b[0].data;",
"""
(\s+il_M_\d+\.Emit\(OpCodes\.)Callvirt, r_get_Item_\d+\);
\1Box, gp_T_\d+\);
\1Ldfld, fld_data_\d+\);
""",
TestName = "IList<T>")]

[TestCase("static int M<T>(IEnumerator<T> b) where T : Foo => b.Current.data;",
"""
(\s+il_M_\d+\.Emit\(OpCodes\.)Callvirt, r_get_Current_\d+\);
\1Box, gp_T_\d+\);
\1Ldfld, fld_data_\d+\);
""",
TestName = "IEnumerator<T>")]
public void MemberAccess_OnGenericCollections(string snippet, string expectedIL)
{
var result = RunCecilifier($$"""
using System.Collections.Generic;
{{snippet}}

class Foo
{
public int data;
}

[System.Runtime.CompilerServices.InlineArray(2)]
struct Buffer<T>
{
private T _data;
}
""");

var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Match(expectedIL));
}
}
}
7 changes: 4 additions & 3 deletions Cecilifier.Core.Tests/Tests/Unit/InlineArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,10 @@ public struct Buffer { private CustomStruct _element0; }
"""
(gi_inlineArrayFirstElementRef_\d+).GenericArguments.Add\((gp_T_\d+)\);
(\s+il_M_\d+\.Emit\(OpCodes\.)Call, \1\);
xxxx\3Constrained, \2\);
\3Callvirt, m_get_\d+\);
""", IgnoreReason = "https://github.com/adrianoc/cecilifier/issues/274", TestName = "Field access on class")]
\3Ldobj, \2\);
\3Box, \2\);
\3Ldfld, fld_value_\d+\);
""", TestName = "Field access on class")]
public void InlineArray_ElementAccess_OnGenericType(string toBeTested, string expecetdIL)
{
var result = RunCecilifier($$"""
Expand Down
6 changes: 4 additions & 2 deletions Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private void InjectRequiredConversions(ExpressionSyntax expression, Action loadA
}

// Empirically (verified in generated IL), expressions of type parameter used as:
// 1. Target of a call, unless the type parameter
// 1. Target of a member reference, unless the type parameter
// - is unconstrained (i.e, method being invoked comes from System.Object) or
// - is constrained to an interface, but not to a reference type or
// - is constrained to 'struct'
Expand Down Expand Up @@ -125,7 +125,9 @@ static bool AssignmentExpressionNeedsBoxing(IVisitorContext context, ExpressionS

static bool NeedsBoxingUsedAsTargetOfReference(IVisitorContext context, ExpressionSyntax expression)
{
if (((CSharpSyntaxNode) expression.Parent).Accept(UsageVisitor.GetInstance(context)) != UsageKind.CallTarget) return false;
if (!expression.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression))
return false;

var symbol = ModelExtensions.GetSymbolInfo(context.SemanticModel, expression).Symbol;
// only triggers when expression `T` used in T.Method() (i.e, abstract static methods from an interface)
if (symbol is { Kind: SymbolKind.TypeParameter }) return false;
Expand Down
3 changes: 1 addition & 2 deletions Cecilifier.Core/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,11 @@ public static OpCode LdelemOpCode(this ITypeSymbol type) =>
SpecialType.System_Int64 => OpCodes.Ldelem_I8,
SpecialType.System_Single => OpCodes.Ldelem_R4,
SpecialType.System_Double => OpCodes.Ldelem_R8,
SpecialType.None => type.IsValueType ? OpCodes.Ldelem_Any : OpCodes.Ldelem_Ref, // Any => Custom structs, Ref => class.
SpecialType.None => (type.IsValueType || type.TypeKind == TypeKind.TypeParameter) ? OpCodes.Ldelem_Any : OpCodes.Ldelem_Ref, // Any => Custom structs, Ref => class.
SpecialType.System_String => OpCodes.Ldelem_Ref,
SpecialType.System_Object => OpCodes.Ldelem_Ref,
_ => type.IsValueType ? OpCodes.Ldelem_Any : throw new Exception($"Element type {type.Name} not supported.")
};


public static bool IsTypeParameterOrIsGenericTypeReferencingTypeParameter(this ITypeSymbol type) =>
type.TypeKind == TypeKind.TypeParameter
Expand Down

0 comments on commit 3affe66

Please sign in to comment.