-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathSystemIndexTests.cs
151 lines (130 loc) · 7.88 KB
/
SystemIndexTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
using System;
using Cecilifier.Core.Extensions;
using Cecilifier.Core.Tests.Tests.Unit.Framework;
using Microsoft.CodeAnalysis;
using Mono.Cecil.Cil;
using NUnit.Framework;
namespace Cecilifier.Core.Tests.Tests.Unit;
[TestFixture]
public class SystemIndexTests : CecilifierUnitTestBase
{
[TestCase("using System; class C { void M(Index index) { index = 1; } }", "Starg_S", TestName = "Parameter_IntegerAssignment")]
[TestCase("using System; class C { Index index; void M() { index = 1; } }", "Stfld", TestName = "Field_IntegerAssignment")]
[TestCase("using System; class C { void M() { Index index; index = 1; } }", "Stloc", TestName = "Local_IntegerAssignment")]
[TestCase("using System; class C { void M() { Index index = 1; } }", "Stloc", TestName = "Local_IntegerInitialization")]
public void ConversionOperator_IsCalled_OnAssignments(string code, string expectedStoreOpCode)
{
var result = RunCecilifier(code);
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Match($@"il_M_\d+.Emit\(OpCodes\.Call, {ResolvedSystemIndexOpImplicit}\);\s+"));
Assert.That(cecilifiedCode, Does.Match($@"il_M_\d+.Emit\(OpCodes\.{expectedStoreOpCode}\s?,.+\);"));
}
[Test]
public void IndexExpression_UsedToIndexArray()
{
var result = RunCecilifier(@"class C { int M(int []a) => a[^5]; }");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Match(
@"(il_M_\d\.Emit\(OpCodes\.)Ldarg_1\);\s+" +
@"\1Dup.+\s+" +
@"\1Ldlen.+\s+" +
@"\1Conv_I4\);\s+" +
@"\1Ldc_I4, 5\);\s+" +
@"\1Sub.+\s+" +
@"\1Ldelem_I4\);\s+" +
@"\1Ret.+"));
}
[Test]
public void IndexExpression_UsedToIndexSpan()
{
var result = RunCecilifier(@"class C { int M(System.Span<int> a) => a[^5]; }");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Match(
@"(il_M_\d\.Emit\(OpCodes\.)Ldarga,.+\);\s+" +
@"\1Dup.+\s+" +
@"\1Call, .+typeof\(System.Span<System.Int32>\), ""get_Length"".+\);\s+" +
@"\1Conv_I4\);\s+" +
@"\1Ldc_I4, 5\);\s+" +
@"\1Sub.+\s+" +
@"\1Call,.+typeof\(System.Span<System.Int32>\), ""get_Item"",.+\);\s+" +
@"\1Ldind_I4\);\s+" +
@"\1Ret.+"));
}
[TestCase("using System; class C { int M(int []a, Index index) => a[index]; }", TestName = "Parameter")]
[TestCase("using System; class C { static Index index; int M(int []a) => a[index]; }", TestName = "Field")]
[TestCase("using System; class C { int M(int []a) { Index index; index = 1; return a[index]; } }", TestName = "Local")]
public void IndexVariable_UsedToIndexArray_GetOffset_IsCalled(string code)
{
var result = RunCecilifier(code);
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Match(
@"il_M_\d.Emit\(OpCodes.Ldarg_1\);\s+" +
@"il_M_\d.Emit\(OpCodes.(Ldloca|Ldarga|Ldflda|Ldsflda), (p|l|fld)_index_\d\);\s+" +
@"il_M_\d.Emit\(OpCodes.Ldarg_1\);\s+" +
@"il_M_\d.Emit\(OpCodes.Ldlen\);\s+" +
@"il_M_\d.Emit\(OpCodes.Conv_I4\);\s+" +
@"il_M_\d.Emit\(OpCodes.Call, .+GetOffset.+\);\s+" +
@"il_M_\d.Emit\(OpCodes.Ldelem_I4\);\s+" +
@"il_M_\d.Emit\(OpCodes.Ret\);"));
}
[TestCase("Index field = ^1;", SymbolKind.Field, TestName = "Field Initialization")]
[TestCase("Index field; void SetField() => field = ^11;", SymbolKind.Field, TestName = "Field Assignment")]
[TestCase("void M() { Index local = ^2; }", SymbolKind.Local, TestName = "Local Variable Initialization")]
[TestCase("void M() { Index local; local = ^2; }", SymbolKind.Local, TestName = "Local Variable Assignment")]
[TestCase("void M() { var local = ^3; }", SymbolKind.Local, TestName = "Inferred Local Variable Initialization")]
[TestCase("void Parameter(Index index) => index = ^11;", SymbolKind.Parameter, TestName = "Parameter 2")]
public void IndexFromEnd_ExplicitCtorIsInvoked(string indexSnippet, SymbolKind kind)
{
// Note that the compiler only call the ctor (instead of newobj) if the `location` being assigned is not being used
// so cecilifier is not generating exactly the same code but but it is still valid.
var result = RunCecilifier($"using System; class Foo {{ {indexSnippet} }}");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Contains.Substring(kind.LoadAddressOpCode().ToString().PascalCase()), cecilifiedCode);
Assert.That(cecilifiedCode, Does.Not.Contains(kind.StoreOpCode().ToString().PascalCase()), cecilifiedCode);
Assert.That(cecilifiedCode, Does.Match(@".+OpCodes.Call, .+TypeHelpers.ResolveMethod\(typeof\(System.Index\), "".ctor"",.+""System.Int32"", ""System.Boolean""\)\)\);"), cecilifiedCode);
}
[Test]
public void IndexFromEnd_OnMemberReference_ExplicitCtorIsInvoked_WithNewObject()
{
var result = RunCecilifier("using System; class Foo { Index field; void SetField(Foo other) => other.field = ^11; }");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
var expectedSnippet =
@"(il_setField_\d\.Emit\(OpCodes\.)Ldarg_1\);\s+" +
@"\1Ldc_I4, 11.+\s+" +
@"\1Ldc_I4_1.+\s+" +
@".+OpCodes.Newobj, .+TypeHelpers.ResolveMethod\(typeof\(System.Index\), "".ctor"",.+""System.Int32"", ""System.Boolean""\)\)\);\s+" +
@"\1Stfld, fld_field_\d";
Assert.That(cecilifiedCode, Does.Match(expectedSnippet), cecilifiedCode);
}
[TestCase("Index Index => ^1;", SymbolKind.Local, TestName = "Property")]
[TestCase("Index Index2 { get => ^1; }", SymbolKind.Local, TestName = "Property 2")]
[TestCase("Index M3() { return ^3; }", SymbolKind.Local, TestName = "Method")]
[TestCase("Index M4() => ^4;", SymbolKind.Local, TestName = "Method Bodied")]
public void AsReturnValue(string indexSnippet, SymbolKind kind)
{
var result = RunCecilifier($"using System; class Foo {{ {indexSnippet} }}");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Does.Not.Contains(kind.LoadAddressOpCode().ToString().PascalCase()), cecilifiedCode);
Assert.That(cecilifiedCode, Does.Match(@".+OpCodes.Newobj, .+TypeHelpers.ResolveMethod\(typeof\(System.Index\), "".ctor"",.+""System.Int32"", ""System.Boolean""\)\)\);"), cecilifiedCode);
Console.WriteLine(cecilifiedCode);
}
public const string ResolvedSystemIndexOpImplicit = @"assembly\.MainModule\.ImportReference\(TypeHelpers\.ResolveMethod\(typeof\(System\.Index\), ""op_Implicit"",System\.Reflection\.BindingFlags\.Default\|System\.Reflection.BindingFlags.Static\|System.Reflection.BindingFlags.Public, ""System.Int32""\)\)";
public const string ResolvedSystemIndexCtor = @"assembly\.MainModule\.ImportReference\(TypeHelpers\.ResolveMethod\(typeof\(System\.Index\), "".ctor"",System\.Reflection\.BindingFlags\.Default\|System\.Reflection.BindingFlags.Instance\|System.Reflection.BindingFlags.Public, ""System.Int32"", ""System.Boolean""\)\)";
}
internal static class SymbolKindExtensions
{
public static OpCode LoadAddressOpCode(this SymbolKind self) => self switch
{
SymbolKind.Local => OpCodes.Ldloca,
SymbolKind.Parameter => OpCodes.Ldarga,
SymbolKind.Field => OpCodes.Ldflda,
_ => throw new NotSupportedException($"I don't think we can get the address of '{self}'")
};
public static OpCode StoreOpCode(this SymbolKind self) => self switch
{
SymbolKind.Local => OpCodes.Stloc,
SymbolKind.Parameter => OpCodes.Starg,
SymbolKind.Field => OpCodes.Stfld,
_ => throw new NotSupportedException($"I don't think we store data in '{self}'")
};
}