diff --git a/Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs b/Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs index 6de783c9..3a269ad0 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using NUnit.Framework; @@ -5,7 +6,7 @@ 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 @@ -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[] 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; @@ -431,5 +441,61 @@ public void GenericInstanceMethods_AreCached() Assert.That(intVersionCount.Count, Is.EqualTo(1), cecilifiedCode); }); } + + [TestCase( + "static int M(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(List 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")] + + [TestCase("static int M(IList 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")] + + [TestCase("static int M(IEnumerator 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")] + 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 + { + private T _data; + } + """); + + var cecilifiedCode = result.GeneratedCode.ReadToEnd(); + Assert.That(cecilifiedCode, Does.Match(expectedIL)); + } } } diff --git a/Cecilifier.Core.Tests/Tests/Unit/InlineArrayTests.cs b/Cecilifier.Core.Tests/Tests/Unit/InlineArrayTests.cs index 4ae6055b..0f63fbf6 100644 --- a/Cecilifier.Core.Tests/Tests/Unit/InlineArrayTests.cs +++ b/Cecilifier.Core.Tests/Tests/Unit/InlineArrayTests.cs @@ -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($$""" diff --git a/Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs b/Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs index 2c2a001d..7af1f4c2 100644 --- a/Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs +++ b/Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs @@ -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' @@ -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; diff --git a/Cecilifier.Core/Extensions/TypeExtensions.cs b/Cecilifier.Core/Extensions/TypeExtensions.cs index ea7f5ccc..01b7c9a7 100644 --- a/Cecilifier.Core/Extensions/TypeExtensions.cs +++ b/Cecilifier.Core/Extensions/TypeExtensions.cs @@ -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