diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 1fb5cf12..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-language: csharp
-mono: none
-sudo: required
-dist: focal
-dotnet: 7.0
-script:
-- dotnet restore
-- dotnet test --filter 'TestCategory !~ Issues' --collect:"XPlat Code Coverage"
-after_success:
-- bash <(curl -s https://codecov.io/bash)
-env:
- global:
- secure: bwOamiuG8vjh6fnbyd6b4goHLw5RtCuSaq+zboKt7qYu+htyjrRHBanoiIgxfpSXVlZ16PKkm47E5E56gC5e0msoT1dIX5EPCiS1mjsYRFGeNMjWLp1ikM/bJkLkm1nwMJfjm7iziHAWQMq51t/J5lJ13hGEI6Pe2YHWRnoCVwqYYbURDxMMl1aRC5s2GfvEpSAZRTbQZjUdEM1zsVwIBdI5GNbyxyd0NIEbDs7nnStb5g38RPdm4phUO1LY3SJBwGHPreIdPYjCEsMr7U6BA0OXMbUikHmPq8/fWNuDQgjgcfOe1+AKLLBWUs3xp11jkj0SRWUY44yPfymAungjFA+HXnUC/RjkWD82fOhYWRaNFqjmHPnIkdEbf6WmOD+o/o8iOd5lU19jug5WCRG/1aVhF3j9IT0OsYi8HdjEWGvwkhlLCvKRz9tkuc9zQiqLWg/ySneL1RseMzUzwvTLI3hP2FtaAIYGGYP2Gg7j3wRaef3UHYzfEU0PqI8MBupxKV3prozbpArGn9lL+IT//u1nM05b06DSluxc181laZoTJkz1gYvVJj9oIWgisfWsKQBPJJ66NdUVTd5Hn954tiy5tuFjwpLGnZcPxQtZvfWKEcanJLZGMJrDnKWfXNrSv9JR5GelvEQ922mVkqfScMYXE7zXg/OdaKMYzgAv4Ig=
diff --git a/Cecilifier.Common.props b/Cecilifier.Common.props
index 5941b61e..979256e0 100644
--- a/Cecilifier.Common.props
+++ b/Cecilifier.Common.props
@@ -2,7 +2,7 @@
net7.0
11
- 2.4.1
+ 2.5.0
true
\ No newline at end of file
diff --git a/Cecilifier.CommonPackages.props b/Cecilifier.CommonPackages.props
index ef71c12d..60fac959 100644
--- a/Cecilifier.CommonPackages.props
+++ b/Cecilifier.CommonPackages.props
@@ -1,6 +1,6 @@
- 4.6.0-1.final
+ 4.7.0-2.final
0.11.5
\ No newline at end of file
diff --git a/Cecilifier.Core.Tests/Cecilifier.Core.Tests.csproj b/Cecilifier.Core.Tests/Cecilifier.Core.Tests.csproj
index 27fd50d8..1bd4bb19 100644
--- a/Cecilifier.Core.Tests/Cecilifier.Core.Tests.csproj
+++ b/Cecilifier.Core.Tests/Cecilifier.Core.Tests.csproj
@@ -6,9 +6,9 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
-
+
diff --git a/Cecilifier.Core.Tests/TestResources/Integration/Statements/SwitchStatement.cs.il.txt b/Cecilifier.Core.Tests/TestResources/Integration/Statements/SwitchStatement.cs.il.txt
index 84fde240..61fc5ab1 100644
--- a/Cecilifier.Core.Tests/TestResources/Integration/Statements/SwitchStatement.cs.il.txt
+++ b/Cecilifier.Core.Tests/TestResources/Integration/Statements/SwitchStatement.cs.il.txt
@@ -5,31 +5,30 @@ IL_0006: add
IL_0007: stloc V_0
IL_000b: ldloc V_0
IL_000f: ldc.i4 0
-IL_0014: beq.s IL_0036
+IL_0014: beq.s IL_0031
IL_0016: ldloc V_0
IL_001a: ldc.i4 1
-IL_001f: beq.s IL_0036
+IL_001f: beq.s IL_0031
IL_0021: ldloc V_0
IL_0025: ldc.i4 2
-IL_002a: beq.s IL_004f
-IL_002c: br IL_0057
-IL_0031: br IL_0064
-IL_0036: nop
-IL_0037: ldarg.1
-IL_0038: ldc.i4 2
-IL_003d: add
-IL_003e: call System.Void System.Console::WriteLine(System.Int32)
-IL_0043: ldc.i4 2
-IL_0048: starg.s 1
-IL_004a: br IL_0064
-IL_004f: nop
-IL_0050: ldc.i4 1
-IL_0055: neg
-IL_0056: ret
-IL_0057: nop
-IL_0058: ldc.i4 3
-IL_005d: starg.s 1
-IL_005f: br IL_0064
-IL_0064: nop
-IL_0065: ldarg.1
-IL_0066: ret
+IL_002a: beq.s IL_004a
+IL_002c: br IL_0052
+IL_0031: nop
+IL_0032: ldarg.1
+IL_0033: ldc.i4 2
+IL_0038: add
+IL_0039: call System.Void System.Console::WriteLine(System.Int32)
+IL_003e: ldc.i4 2
+IL_0043: starg.s 1
+IL_0045: br IL_005f
+IL_004a: nop
+IL_004b: ldc.i4 1
+IL_0050: neg
+IL_0051: ret
+IL_0052: nop
+IL_0053: ldc.i4 3
+IL_0058: starg.s 1
+IL_005a: br IL_005f
+IL_005f: nop
+IL_0060: ldarg.1
+IL_0061: ret
diff --git a/Cecilifier.Core.Tests/Tests/Unit/ArrayTests.cs b/Cecilifier.Core.Tests/Tests/Unit/ArrayTests.cs
index f5bb095c..463cd989 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/ArrayTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/ArrayTests.cs
@@ -163,4 +163,34 @@ public void InitializationOptimized(string code)
\s+\4.Emit\(OpCodes.Ldtoken, \3\);
"""));
}
+
+ [TestCase("Property", "Call, m_get_2", TestName = "Property")]
+ [TestCase("Method()", "Call, m_method_8", TestName = "Method")]
+ [TestCase("Field", "Ldfld, fld_field_7", TestName = "Field")]
+ public void MemberAccessOnElementAccessOnValueTypeArray_LoadsElementAddress(string member, string expectedILMemberRef)
+ {
+ var result = RunCecilifier($$"""int M(S[] sa) => sa[0].{{member}}; struct S { public int Property { get; set; } public int Field; public int Method() => 1; }""");
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match($"""
+ (il_M_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \s+\1Ldc_I4, 0\);
+ \s+\1Ldelema, st_S_0\);
+ \s+\1{expectedILMemberRef}\);
+ \s+\1Ret\);
+ """));
+ }
+
+ [TestCase("Property", "Callvirt, m_get_2", TestName = "Property")]
+ [TestCase("Method()", "Callvirt, m_method_8", TestName = "Method")]
+ [TestCase("Field", "Ldfld, fld_field_7", TestName = "Field")]
+ public void MemberAccessOnElementAccessOnReferenceTypeArray_LoadsElementByReference(string member, string expectedILMemberRef)
+ {
+ var result = RunCecilifier($$"""int M(S[] sa) => sa[0].{{member}}; class S { public int Property { get; set; } public int Field; public int Method() => 1; }""");
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match($"""
+ (il_M_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \s+\1Ldc_I4, 0\);
+ \s+\1Ldelem_Ref\);
+ \s+\1{expectedILMemberRef}\);
+ \s+\1Ret\);
+ """));
+ }
}
diff --git a/Cecilifier.Core.Tests/Tests/Unit/CastTests.cs b/Cecilifier.Core.Tests/Tests/Unit/CastTests.cs
new file mode 100644
index 00000000..a0bff555
--- /dev/null
+++ b/Cecilifier.Core.Tests/Tests/Unit/CastTests.cs
@@ -0,0 +1,28 @@
+using NUnit.Framework;
+
+namespace Cecilifier.Core.Tests.Tests.Unit;
+
+[TestFixture]
+public class CastTests : CecilifierUnitTestBase
+{
+ [Test]
+ public void Unbox()
+ {
+ var result = RunCecilifier("int UnboxIt(object o) => (int) o;");
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match("""
+ (il_unboxIt_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \s+\1Unbox_Any, assembly.MainModule.TypeSystem.Int32\);
+ """));
+ }
+
+ [TestCase("i", TestName = "Implicit boxing")]
+ [TestCase("(object) i", TestName = "Explicit boxing")]
+ public void Box(string expression)
+ {
+ var result = RunCecilifier($"object BoxIt(int i) => {expression};");
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match("""
+ (il_boxIt_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \s+\1Box, assembly.MainModule.TypeSystem.Int32\);
+ """));
+ }
+}
diff --git a/Cecilifier.Core.Tests/Tests/Unit/CustomValueTypesInstantiationTests.cs b/Cecilifier.Core.Tests/Tests/Unit/CustomValueTypesInstantiationTests.cs
index 195261c9..a9c9627a 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/CustomValueTypesInstantiationTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/CustomValueTypesInstantiationTests.cs
@@ -32,7 +32,7 @@ public void SimpleStoreToOutStructParameter(string toStore)
Assert.That(result.GeneratedCode.ReadToEnd(), Contains.Substring(
@"il_M_3.Emit(OpCodes.Ldarg_1);
il_M_3.Emit(OpCodes.Ldarg_2);
- il_M_3.Emit(OpCodes.Stobj);"));
+ il_M_3.Emit(OpCodes.Stobj, st_myStruct_0);"));
}
[Test]
@@ -62,18 +62,9 @@ public void ArrayElementAssignment(string arrayVariable)
result.GeneratedCode.ReadToEnd(), Does.Match(
@"il_M_4.Emit\(OpCodes.(?:Ldarg_1|Ldfld, fld_f_2)\);
il_M_4.Emit\(OpCodes.Ldc_I4, 1\);
- il_M_4.Emit\(OpCodes.Ldelema\);
+ il_M_4.Emit\(OpCodes.Ldelema, st_myStruct_0\);
il_M_4.Emit\(OpCodes.Initobj, st_myStruct_0\);"));
}
-
- [Test]
- public void TestParameterlessStructInstantiation()
- {
- var result = RunCecilifier("struct Foo { static Foo Create() => new Foo(); public Foo() { } }");
- var cecilifiedCode = result.GeneratedCode.ReadToEnd();
-
- Assert.That(cecilifiedCode, Does.Match(@"il_create_2\.Emit\(OpCodes.Newobj, ctor_foo_3\);"));
- }
[TestCaseSource(nameof(InvocationOnObjectCreationExpressionTestScenarios))]
public void InvocationOnObjectCreationExpression(string invocationStatement, string expectedILSnippet)
@@ -112,6 +103,42 @@ public Test(int i) {}
Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expectedGeneratedSnippet));
}
+
+ [TestCase("var x = new S(); struct S {}",
+ """
+ il_topLevelMain_4.Emit\(OpCodes.Ldloca_S, l_x_7\);
+ \s+il_topLevelMain_4.Emit\(OpCodes.Initobj, st_S_0\);
+ """,
+ TestName = "Implicit parameterless ctor")]
+
+ [TestCase("var x = new S(); struct S { public S() {} }",
+ """
+ (il_topLevelMain_\d+.Emit\(OpCodes\.)Newobj, ctor_S_1\);
+ \s+\1Stloc, l_x_9\);
+ """,
+ TestName = "Explicit parameterless ctor")]
+
+ [TestCase("var x = new S(42, true); struct S { public S(int i, bool b) {} }",
+ """
+ (il_topLevelMain_\d+.Emit\(OpCodes\.)Ldc_I4, 42\);
+ (\s+\1)Ldc_I4, 1\);
+ \2Newobj, ctor_S_1\);
+ """,
+ TestName = "Ctor with parameters")]
+
+ [TestCase("var x = new S(42, true); struct S { public S(int i, bool b) {} }",
+ """
+ (il_topLevelMain_\d+.Emit\(OpCodes\.)Ldc_I4, 42\);
+ (\s+\1)Ldc_I4, 1\);
+ \2Newobj, ctor_S_1\);
+ """,
+ TestName = "Ctor with parameters")]
+ public void Instantiation_EmitsCorrectOpcode(string code, string expected)
+ {
+ var result = RunCecilifier(code);
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expected));
+ }
+
static TestCaseData[] InvocationExpressionAsParametersTestScenarios()
{
return new[]
diff --git a/Cecilifier.Core.Tests/Tests/Unit/FieldsTests.cs b/Cecilifier.Core.Tests/Tests/Unit/FieldsTests.cs
index c1554c59..a34296b4 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/FieldsTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/FieldsTests.cs
@@ -26,7 +26,7 @@ public void TestExternalFields()
[TestCase(
"class Foo { int Value; void M() => Value = 42; }",
- "Append(ldarg_0_4);", // Load this
+ "Emit(OpCodes.Ldarg_0);", // Load this
"Emit(OpCodes.Ldc_I4, 42);", // Load 42
"Emit(OpCodes.Stfld, fld_value_1);", // Store in Value
TestName = "Implicit This")]
diff --git a/Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs b/Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs
index 97bd9e69..e0ff0aec 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs
@@ -288,11 +288,10 @@ public void GenericParameter_IsUsed_InsteadOfTypeOf_Issue240()
"fieldIDisp = value",
"""
//fieldIDisp = value;
- \s+var ldarg_0_\d+ = (il_test_\d+\.)Create\(OpCodes.Ldarg_0\);
- \s+\1Append\(ldarg_0_\d+\);
- (\s+\1Emit\(OpCodes\.)Ldarg_1\);
- \2Box, gp_T_\d+\);
- \2Stfld, fld_fieldIDisp_\d+\);
+ (\s+il_test_\d+\.Emit\(OpCodes\.)Ldarg_0\);
+ \1Ldarg_1\);
+ \1Box, gp_T_\d+\);
+ \1Stfld, fld_fieldIDisp_\d+\);
""")]
[TestCase(
@@ -320,7 +319,7 @@ public void GenericParameter_IsUsed_InsteadOfTypeOf_Issue240()
\1Ldstr, "Ola Mundo"\);
\1Call, \2\);
\1Stloc, l_o_\d+\);
- """)]
+ """)]
[TestCase(
"""Foo f = new Foo(); object o; o = f.M(value)""",
diff --git a/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs b/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs
index b460d10c..b6d211e6 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.Statements.cs
@@ -113,35 +113,34 @@ void M()
\s+//case 1: \(condition\)
\s+il_M_7.Emit\(OpCodes.Ldloc, l_switchCondition_13\);
\s+il_M_7.Emit\(OpCodes.Ldc_I4, 1\);
- \s+il_M_7.Emit\(OpCodes.Beq_S, nop_15\);
+ \s+il_M_7.Emit\(OpCodes.Beq_S, lbl_caseCode_0_15\);
\s+//case 2: \(condition\)
\s+il_M_7.Emit\(OpCodes.Ldloc, l_switchCondition_13\);
\s+il_M_7.Emit\(OpCodes.Ldc_I4, 2\);
- \s+il_M_7.Emit\(OpCodes.Beq_S, nop_16\);
+ \s+il_M_7.Emit\(OpCodes.Beq_S, lbl_caseCode_1_16\);
+ \s+il_M_7.Emit\(OpCodes.Br, lbl_caseCode_2_17\);
- \s+il_M_7.Emit\(OpCodes.Br, nop_17\);
- \s+il_M_7.Emit\(OpCodes.Br, nop_14\);
\s+//case 1: \(code\)
- \s+il_M_7.Append\(nop_15\);
+ \s+il_M_7.Append\(lbl_caseCode_0_15\);
\s+il_M_7.Emit\(OpCodes.Ldstr, "C1"\);
\s+il_M_7.Emit\(OpCodes.Call, .+System.Console.+WriteLine.+\);
- \s+il_M_7.Emit\(OpCodes.Br, nop_14\);
+ \s+il_M_7.Emit\(OpCodes.Br, lbl_endOfSwitch_14\);
\s+//case 2: \(code\)
- \s+il_M_7.Append\(nop_16\);
+ \s+il_M_7.Append\(lbl_caseCode_1_16\);
\s+il_M_7.Emit\(OpCodes.Ldstr, "C2"\);
\s+il_M_7.Emit\(OpCodes.Call, .+System.Console.+WriteLine.+\);
- \s+il_M_7.Emit\(OpCodes.Br, nop_14\);
+ \s+il_M_7.Emit\(OpCodes.Br, lbl_endOfSwitch_14\);
\s+//default: \(code\)
- \s+il_M_7.Append\(nop_17\);
+ \s+il_M_7.Append\(lbl_caseCode_2_17\);
\s+il_M_7.Emit\(OpCodes.Ldstr, "CD"\);
\s+il_M_7.Emit\(OpCodes.Call, .+System.Console.+WriteLine.+\);
- \s+il_M_7.Emit\(OpCodes.Br, nop_14\);
+ \s+il_M_7.Emit\(OpCodes.Br, lbl_endOfSwitch_14\);
\s+//End of switch
- \s+il_M_7.Append\(nop_14\);
+ \s+il_M_7.Append\(lbl_endOfSwitch_14\);
"""));
}
diff --git a/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.cs b/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.cs
index 82511092..c9862345 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/Miscellaneous.cs
@@ -231,12 +231,11 @@ public void TestCallerArgumentExpressionAttribute_InvalidParameterName(string de
[TestCase(
"C c = new() { Value = 42 }; class C { public int Value; }",
"""
- (il_topLevelMain_\d+).Emit\(OpCodes.Newobj, ctor_C_\d+\);
- \s+var (dup_\d+) = \1.Create\(OpCodes.Dup\);
- \s+\1.Append\(\2\);
- \s+\1.Emit\(OpCodes.Ldc_I4, 42\);
- \s+\1.Emit\(OpCodes.Stfld, fld_value_\d+\);
- \s+\1.Emit\(OpCodes.Stloc, l_c_\d+\);
+ (il_topLevelMain_\d+.Emit\(OpCodes\.)Newobj, ctor_C_\d+\);
+ \s+\1Dup\);
+ \s+\1Ldc_I4, 42\);
+ \s+\1Stfld, fld_value_\d+\);
+ \s+\1Stloc, l_c_\d+\);
""",
TestName = "Object Initializer")]
public void TestImplicitObjectCreation(string code, params string[] expectations)
diff --git a/Cecilifier.Core.Tests/Tests/Unit/NullableTests.cs b/Cecilifier.Core.Tests/Tests/Unit/NullableTests.cs
new file mode 100644
index 00000000..ebc12459
--- /dev/null
+++ b/Cecilifier.Core.Tests/Tests/Unit/NullableTests.cs
@@ -0,0 +1,105 @@
+using System.Text.RegularExpressions;
+using NUnit.Framework;
+
+namespace Cecilifier.Core.Tests.Tests.Unit;
+
+[TestFixture]
+public class NullableTests : CecilifierUnitTestBase
+{
+ [TestCase("object", TestName = "object")]
+ [TestCase("Foo", TestName = "Foo")]
+ [TestCase("IFoo", TestName = "IFoo")]
+ [TestCase("int", TestName = "int")]
+ public void MethodsWithNullableParameters_AreRegistered_Once(string parameterType)
+ {
+ var result = RunCecilifier(
+ $$"""
+ interface IFoo {}
+ class Foo : IFoo
+ {
+ void M({{parameterType}}? o) {}
+ void M2({{parameterType}} p) => M(p);
+ }
+ """);
+
+ var actual = result.GeneratedCode.ReadToEnd();
+
+ Assert.That(
+ Regex.Count(actual, """var .+ = new MethodDefinition\("M", MethodAttributes.Private \| MethodAttributes.HideBySig, assembly.MainModule.TypeSystem.Void\);\n"""),
+ Is.EqualTo(1),
+ "Only only declaration for method M() is expected.");
+ }
+
+ [TestCase("object", @".+TypeSystem\.Object", TestName = "object")]
+ [TestCase("Foo", "cls_foo_1", TestName = "Foo")]
+ [TestCase("IFoo", "itf_iFoo_0", TestName = "IFoo")]
+ [TestCase("int", @".+ImportReference\(typeof\(System.Nullable<>\)\).MakeGenericInstanceType\(.+Int32\)", TestName = "int")]
+ public void MethodsWithNullableReturnTypes_AreRegistered_Once(string returnType, string expectedReturnTypeInDeclaration)
+ {
+ var result = RunCecilifier(
+ $$"""
+ interface IFoo {}
+ class Foo : IFoo
+ {
+ {{returnType}}? M() => default({{returnType}});
+ void M2() => M();
+ }
+ """);
+
+ var actual = result.GeneratedCode.ReadToEnd();
+ Assert.That(
+ Regex.Count(actual, $$"""var m_M_\d+ = new MethodDefinition\("M", MethodAttributes.+, {{expectedReturnTypeInDeclaration}}\);\n"""),
+ Is.EqualTo(1),
+ actual);
+ }
+
+ [TestCase(
+ """
+ class Foo
+ {
+ int Bar(int? i) => i.Value;
+ int? Test(int i1) { return Bar(i1); } // i1 should be converted to Nullable and Bar() return also.
+ }
+ """,
+
+ """
+ //return Bar\(i1\);
+ (\s+il_test_\d+\.Emit\(OpCodes\.)Ldarg_0\);
+ \1Ldarg_1\);
+ (?\1Newobj, .+ImportReference\(typeof\(System.Nullable<>\).MakeGenericType\(typeof\(System.Int32\)\).GetConstructors\(\).+;)
+ \1Call, m_bar_1\);
+ \k
+ \1Ret\);
+ """,
+ TestName = "Method parameter and return value"
+ )]
+
+ [TestCase(
+ """
+ class Foo
+ {
+ void Bar(int? p)
+ {
+ p = 41;
+
+ int ?lp;
+ lp = 42;
+ }
+ }
+ """,
+
+ """
+ //p = 41;
+ (\s+il_bar_\d+\.Emit\(OpCodes\.)Ldc_I4, 41\);
+ \1Newobj, .+ImportReference\(typeof\(System.Nullable<>\).MakeGenericType\(typeof\(System.Int32\)\).GetConstructors\(\).+;
+ \1Starg_S, p_p_3\);
+ """,
+ TestName = "Variable assignment"
+ )]
+ public void ImplicitNullableConversions_AreApplied(string code, string expectedSnippet)
+ {
+ //https://github.com/adrianoc/cecilifier/issues/251
+ var result = RunCecilifier(code);
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expectedSnippet));
+ }
+}
diff --git a/Cecilifier.Core.Tests/Tests/Unit/ObjectInitializerTests.cs b/Cecilifier.Core.Tests/Tests/Unit/ObjectInitializerTests.cs
index 5f84eead..a2b535c1 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/ObjectInitializerTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/ObjectInitializerTests.cs
@@ -12,16 +12,16 @@ public void ObjectInitializer()
var code = @"class Foo { public int Value; public bool B; } class Bar { public void M() { var x = new Foo { Value = 42, B = true }; } }";
var result = RunCecilifier(code);
- var expected = @"(il_M_\d+\.Emit\(OpCodes\.)Newobj, ctor_foo_3\);\s+" +
- @"var (dup_\d+) = il_M_\d+.Create\(OpCodes.Dup\);\s+" +
- @"il_M_\d+.Append\(\2\);\s+" +
- @"\1Ldc_I4, 42\);\s+" +
- @"\1Stfld, fld_value_\d+\);\s+" +
- @"var (dup_\d+) = il_M_\d+.Create\(OpCodes.Dup\);\s+" +
- @"il_M_\d+.Append\(\3\);\s+" +
- @"\1Ldc_I4, 1\);\s+" +
- @"\1Stfld, fld_B_\d+\);\s+" +
- @"\1Stloc, l_x_\d+\);";
+ var expected = """
+ (il_M_\d+\.Emit\(OpCodes\.)Newobj, ctor_foo_3\);
+ (\s+\1)Dup\);
+ \2Ldc_I4, 42\);
+ \2Stfld, fld_value_\d+\);
+ \2Dup\);
+ \2Ldc_I4, 1\);
+ \2Stfld, fld_B_\d+\);
+ \2Stloc, l_x_\d+\);
+ """;
Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expected));
}
@@ -121,7 +121,7 @@ public void Add(char ch) {}
}
[Test]
- public void CustomCollectionInitializerAddMethodBountToExtensionMethod()
+ public void CustomCollectionInitializerAddMethodBoundToExtensionMethod()
{
var code = """
static class Ext { public static void Add(this Foo self, string s) {} }
@@ -148,4 +148,114 @@ static void Main()
Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expected));
}
+
+ [TestCase(
+ "M(new Bar() { Value = 1 } , new Bar() { Value = 2 } );",
+ """
+ m_topLevelStatements_\d+.Body.Variables.Add\(l_vt_\d+\);
+ (\s+il_topLevelMain_\d+\.Emit\(OpCodes\.)Ldloca_S, (l_vt_\d+)\);
+ \1Initobj, st_bar_0\);
+ \1Ldloca_S, \2\);
+ \1Dup\);
+ \1Ldc_I4, 1\);
+ \1Call, l_set_11\);
+ \1Pop\);
+ \1Ldloc, \2\);
+ \s+var (l_vt_\d+) = new VariableDefinition\(st_bar_0\);
+ \s+m_topLevelStatements_\d+.Body.Variables.Add\(\3\);
+ \1Ldloca_S, \3\);
+ \1Initobj, st_bar_0\);
+ \1Ldloca_S, \3\);
+ \1Dup\);
+ \1Ldc_I4, 2\);
+ \1Call, l_set_11\);
+ \1Pop\);
+ \1Ldloca, \3\);
+ \1Call, m_M_19\);
+ """,
+ TestName = "As method argument")]
+
+ [TestCase("""var x = new Bar { Name = "A", Value = 3 };""",
+ """
+ var (l_x_\d+) = new VariableDefinition\(st_bar_0\);
+ \s+m_topLevelStatements_14.Body.Variables.Add\(\1\);
+ (\s+il_topLevelMain_\d+\.Emit\(OpCodes\.)Ldloca_S, \1\);
+ \2Initobj, st_bar_0\);
+ \2Ldloca_S, \1\);
+ \2Dup\);
+ \2Ldstr, "A"\);
+ \2Call, l_set_5\);
+ \2Dup\);
+ \2Ldc_I4, 3\);
+ \2Call, l_set_11\);
+ \2Pop\);
+ """,
+ TestName = "in variable initializer")]
+
+ [TestCase("Bar b; b = new Bar { Value = 3 };",
+ """
+ (\s+il_topLevelMain_\d+\.Emit\(OpCodes\.)Ldloca_S, (l_b_\d+)\);
+ \1Dup\);
+ \1Initobj, st_bar_0\);
+ \1Dup\);
+ \1Ldc_I4, 3\);
+ \1Call, l_set_11\);
+ \1Pop\);
+ """,
+ TestName = "in assignment")]
+
+ [TestCase("""var z = new Bar() { Name = "123", Value = 6 }.Value;""",
+ """
+ var (l_vt_\d+) = new VariableDefinition\((st_bar_\d+)\);
+ \s+(m_topLevelStatements_\d+).Body.Variables.Add\(\1\);
+ (\s+il_topLevelMain_\d+.Emit\(OpCodes\.)Ldloca_S, \1\);
+ \4Initobj, \2\);
+ \4Ldloca_S, \1\);
+ \4Dup\);
+ \4Ldstr, "123"\);
+ \4Call, l_set_5\);
+ \4Dup\);
+ \4Ldc_I4, 6\);
+ \4Call, l_set_11\);
+ \4Pop\);
+ \4Ldloca, \1\);
+ \4Call, m_get_8\);
+ """,
+ TestName = "in member access expression")]
+
+ [TestCase("Bar []ba = new Bar[1]; ba[0] = new Bar { Value = 3 };",
+ """
+ var l_ba_19 = new VariableDefinition\(st_bar_0.MakeArrayType\(\)\);
+ \s+m_topLevelStatements_14.Body.Variables.Add\(l_ba_19\);
+ (\s+il_topLevelMain_\d+\.Emit\(OpCodes\.)Ldc_I4, 1\);
+ \1Newarr, st_bar_0\);
+ \1Stloc, l_ba_19\);
+ \1Ldloc, l_ba_19\);
+ \1Ldc_I4, 0\);
+ \1Ldelema, st_bar_0\);
+ \1Dup\);
+ \1Initobj, st_bar_0\);
+ \1Dup\);
+ \1Ldc_I4, 3\);
+ \1Call, l_set_11\);
+ \1Pop\);
+ """,
+ TestName = "in assignment to array element")]
+ public void ObjectInitializers_AreHandled_InValueTypes(string statementToTest, string expectedRegex)
+ {
+ var result = RunCecilifier($$"""
+ {{statementToTest}}
+
+ void M(Bar bar, in Bar ib) { }
+
+ struct Bar
+ {
+ public string Name {get; set; }
+ public int Value { get; set; }
+ }
+ """);
+
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expectedRegex));
+ }
+
}
diff --git a/Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs b/Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs
index d94ec3eb..cc2432a2 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/OperatorsTests.cs
@@ -133,7 +133,7 @@ public void TestNullConditionalOperatorOnStorageTargets(string target, Code expe
TestName = "Overloaded +")]
[TestCase(
"var c = new C(); c.M(c); System.Console.WriteLine(c.Value); class C { public int Value {get; set;} public void M(C other) { Value += 1; other.Value += 2; } }",
- @"//Value \+= 1;\s+var (ldarg_0_\d+) = (il_M_\d+).Create\(OpCodes.Ldarg_0\);\s+\2.Append\(\1\);\s+(il_M_\d+\.Emit\(OpCodes\.)Ldarg_0\);\s+\3Call, m_get_\d+\);\s+\3Ldc_I4, 1\);\s+\3Add\);\s+\3Call, l_set_\d+\);\s+",
+ @"//Value \+= 1;(\s+il_M_\d+\.Emit\(OpCodes\.)Ldarg_0\);\1Ldarg_0\);\1Call, m_get_\d+\);\1Ldc_I4, 1\);\1Add\);\1Call, l_set_\d+\);",
TestName = "Properties (numeric)")]
public void TestCompoundAssignment(string code, string expected)
{
@@ -216,6 +216,45 @@ public void TestNullConditionalOperatorOnComplexTargets(string target, string ex
Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expected));
}
+ [TestCase("bool Foo(object o) => o != null;",
+ """
+ var (p_o_\d+) = new ParameterDefinition\("o", ParameterAttributes.None, assembly.MainModule.TypeSystem.Object\);
+ \s+m_foo_\d+.Parameters.Add\(\1\);
+ (\s+il_foo_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \2Ldnull\);
+ \2Ceq\);
+ \2Ldc_I4_0\);
+ \2Ceq\);
+ \2Ret\);
+ """,
+ TestName = "Object !=")]
+
+ [TestCase("bool Foo(object o) => o == null;",
+ """
+ var (p_o_\d+) = new ParameterDefinition\("o", ParameterAttributes.None, assembly.MainModule.TypeSystem.Object\);
+ \s+m_foo_\d+.Parameters.Add\(\1\);
+ (\s+il_foo_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \2Ldnull\);
+ \2Ceq\);
+ \2Ret\);
+ """, TestName = "Object ==")]
+
+ [TestCase("bool Foo(T o) => o == null;",
+ """
+ var (p_o_\d+) = new ParameterDefinition\("o", ParameterAttributes.None, gp_T_7\);
+ \s+m_foo_\d+.Parameters.Add\(\1\);
+ (\s+il_foo_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \2Box, gp_T_7\);
+ \2Ldnull\);
+ \2Ceq\);
+ \2Ret\);
+ """, TestName = "Type Parameter ==")]
+ public void EqualityAndInequalityOperators_AgainstNull_DoesNotInvokeOverloadOnSystemObject(string code, string expected)
+ {
+ var result = RunCecilifier(code);
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expected));
+ }
+
[TestCase("bool M(object o) => o is System.IDisposable;", "System.IDisposable")]
[TestCase("bool M(object o) => o is object;", "Object")]
public void TestSimpleIsPatternExpression(string code, string expectedType)
diff --git a/Cecilifier.Core.Tests/Tests/Unit/PointerTypeTests.cs b/Cecilifier.Core.Tests/Tests/Unit/PointerTypeTests.cs
index 6f575160..9214fd42 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/PointerTypeTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/PointerTypeTests.cs
@@ -56,7 +56,7 @@ private static IEnumerable PointerAssignmentTypesToTest()
yield return new TestCaseData("ulong", "Stind_I8").SetName("ulong");
yield return new TestCaseData("float", "Stind_R4").SetName("float");
yield return new TestCaseData("double", "Stind_R8").SetName("double");
- yield return new TestCaseData("S", "Stobj").SetName("custom-struct");
+ yield return new TestCaseData("S", "Stobj, st_S_0").SetName("custom-struct");
}
}
}
diff --git a/Cecilifier.Core.Tests/Tests/Unit/PropertyTests.cs b/Cecilifier.Core.Tests/Tests/Unit/PropertyTests.cs
index c640b854..15a61168 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/PropertyTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/PropertyTests.cs
@@ -11,7 +11,7 @@ public void TestGetterOnlyInitialization_Simple()
var result = RunCecilifier("class C { public int Value { get; } public C() => Value = 42; }");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Contains.Substring(
- @"il_ctor_6.Append(ldarg_0_7);
+ @"il_ctor_6.Emit(OpCodes.Ldarg_0);
il_ctor_6.Emit(OpCodes.Ldc_I4, 42);
il_ctor_6.Emit(OpCodes.Stfld, fld_value_4);"));
}
@@ -22,7 +22,7 @@ public void TestGetterOnlyInitialization_Complex()
var result = RunCecilifier("class C { public int Value { get; } public C(int n) => Value = n * 2; }");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
Assert.That(cecilifiedCode, Contains.Substring(
- @"il_ctor_6.Append(ldarg_0_8);
+ @"il_ctor_6.Emit(OpCodes.Ldarg_0);
il_ctor_6.Emit(OpCodes.Ldarg_1);
il_ctor_6.Emit(OpCodes.Ldc_I4, 2);
il_ctor_6.Emit(OpCodes.Mul);
diff --git a/Cecilifier.Core.Tests/Tests/Unit/RefAssignmentTests.cs b/Cecilifier.Core.Tests/Tests/Unit/RefAssignmentTests.cs
index 059ca2a7..acae2782 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/RefAssignmentTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/RefAssignmentTests.cs
@@ -57,7 +57,7 @@ public void TestRefParameterAssignment(string assignmentExpression, string expec
}
[Test]
- public void TesRefParameterDeference()
+ public void TestRefParameterDeference()
{
var result = RunCecilifier("void M(ref int r) { int i; i = r + 42; }");
var cecilifiedCode = result.GeneratedCode.ReadToEnd();
@@ -74,6 +74,50 @@ public void TesRefParameterDeference()
"""));
}
+ [Test]
+ public void TestAssign_NewInstanceOfValueType_ToParameter()
+ {
+ var result = RunCecilifier("void M(ref S s) { s = new S(); } struct S { public S() {} }");
+ var cecilifiedCode = result.GeneratedCode.ReadToEnd();
+
+ Assert.That(cecilifiedCode, Does.Match("""
+ //s = new S\(\);
+ (\s+il_M_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \1Newobj, ctor_S_1\);
+ \1Stobj, st_S_0\);
+ """));
+ }
+
+ [Test]
+ public void TestAssign_NewInstanceOfValueType_ByRefIndexer()
+ {
+ var result = RunCecilifier("""
+ struct S { public S() {} }
+ class Foo
+ {
+ S s;
+ ref S this[int i] => ref s;
+
+ void DoIt(Foo other)
+ {
+ other[0] = new S();
+ }
+ }
+ """);
+
+ var cecilifiedCode = result.GeneratedCode.ReadToEnd();
+
+ Assert.That(cecilifiedCode, Does.Match("""
+ //other\[0\] = new S\(\);
+ (\s+il_doIt_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \1Ldc_I4, 0\);
+ \1Callvirt, m_get_9\);
+ \1Newobj, ctor_S_1\);
+ \1Stobj, st_S_0\);
+ \1Ret\);
+ """));
+ }
+
[Test]
public void TesRefFieldAssignment()
{
@@ -84,10 +128,9 @@ public void TesRefFieldAssignment()
Does.Match(
"""
//refInt = ref r;
- \s+var (ldarg_\d+_\d+) = (il_ctor_\d+).Create\(OpCodes.Ldarg_0\);
- \s+\2.Append\(\1\);
- \s+\2.Emit\(OpCodes.Ldarg_1\);
- \s+\2.Emit\(OpCodes.Stfld, fld_refInt_\d+\);
+ (\s+il_ctor_\d+.Emit\(OpCodes\.)Ldarg_0\);
+ \1Ldarg_1\);
+ \1Stfld, fld_refInt_\d+\);
"""),
"Ref Assignment");
diff --git a/Cecilifier.Core.Tests/Tests/Unit/StructSpecificTests.cs b/Cecilifier.Core.Tests/Tests/Unit/StructSpecificTests.cs
index 49f222b1..acefc20e 100644
--- a/Cecilifier.Core.Tests/Tests/Unit/StructSpecificTests.cs
+++ b/Cecilifier.Core.Tests/Tests/Unit/StructSpecificTests.cs
@@ -207,4 +207,246 @@ public Foo(int i) {}
\s+\1Call, .+ImportReference\(.+ResolveMethod\(typeof\(System.Int32\), "ToString",.+\)\)\);
"""));
}
+
+ [TestCase("""
+ struct S { }
+
+ class Foo
+ {
+ S TernaryOperators(int i) => i == 2 ? new S(): new S();
+ }
+ """,
+ """
+ //Parameters of 'S TernaryOperators\(int i\) => i == 2 \? new S\(\): new S\(\);'
+ \s+var p_i_4 = new ParameterDefinition\("i", ParameterAttributes.None, assembly.MainModule.TypeSystem.Int32\);
+ \s+m_ternaryOperators_2.Parameters.Add\(p_i_4\);
+ \s+var lbl_conditionEnd_5 = il_ternaryOperators_3.Create\(OpCodes.Nop\);
+ \s+var lbl_whenFalse_6 = il_ternaryOperators_3.Create\(OpCodes.Nop\);
+ (\s+il_ternaryOperators_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \1Ldc_I4, 2\);
+ \1Ceq\);
+ \1Brfalse_S, lbl_whenFalse_6\);
+ \s+var (l_vt_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_ternaryOperators_2.Body.Variables.Add\(\2\);
+ \1Ldloca_S, \2\);
+ \1Initobj, st_S_0\);
+ \1Ldloc, l_vt_7\);
+ \1Br_S, lbl_conditionEnd_5\);
+ \s+il_ternaryOperators_3.Append\(lbl_whenFalse_6\);
+ \s+var (l_vt_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_ternaryOperators_2.Body.Variables.Add\(\3\);
+ \1Ldloca_S, \3\);
+ \1Initobj, st_S_0\);
+ \1Ldloc, \3\);
+ \s+il_ternaryOperators_3.Append\(lbl_conditionEnd_5\);
+ \1Ret\);
+ """,
+ TestName = "Branch/Branch used in expression introduces variable")]
+
+ [TestCase("""
+ struct S { }
+
+ class Foo
+ {
+ void TernaryOperators(int i) { var x = i == 2 ? new S(): new S(); }
+ }
+ """,
+ """
+ //var x = i == 2 \? new S\(\): new S\(\);
+ \s+var (?l_x_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_ternaryOperators_2.Body.Variables.Add\(\k\);
+ \s+var (lbl_.+) = il_ternaryOperators_3.Create\(OpCodes.Nop\);
+ \s+var lbl_.+ = il_ternaryOperators_3.Create\(OpCodes.Nop\);
+ (\s+il_ternaryOperators_\d+\.Emit\(OpCodes\.)Ldarg_1\);
+ \2Ldc_I4, 2\);
+ \2Ceq\);
+ \2Brfalse_S, lbl_whenFalse_7\);
+ \2Ldloca_S, \k\);
+ \2Initobj, st_S_0\);
+ \2Br_S, lbl_conditionEnd_6\);
+ \s+il_ternaryOperators_3.Append\(lbl_whenFalse_7\);
+ \2Ldloca_S, \k\);
+ \2Initobj, st_S_0\);
+ \s+il_ternaryOperators_3.Append\(lbl_conditionEnd_6\);
+ \2Ret\);
+ """,
+ TestName = "Branch/Branch used in variable declaration initializes declared variable")]
+
+ [TestCase("""
+ struct S { public int P { get; set; } }
+
+ class Foo
+ {
+ int TernaryOperators(int i) => i == 2 ? new S().P : new S().P;
+ }
+ """,
+ """
+ (il_ternaryOperators_\d+\.Emit\(OpCodes\.)Brfalse_S, lbl_whenFalse_12\);
+ \s+var (?l_vt_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_ternaryOperators_8.Body.Variables.Add\(\k\);
+ (\s+\1)Ldloca_S, \k\);
+ \2Initobj, st_S_0\);
+ \2Ldloca, \k\);
+ \2Call, m_get_2\);
+ \2Br_S, lbl_conditionEnd_11\);
+ \s+il_ternaryOperators_9.Append\(lbl_whenFalse_12\);
+ \s+var (?l_vt_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_ternaryOperators_8.Body.Variables.Add\(\k\);
+ \2Ldloca_S, \k\);
+ \2Initobj, st_S_0\);
+ \2Ldloca, \k\);
+ \2Call, m_get_2\);
+ \s+il_ternaryOperators_9.Append\(lbl_conditionEnd_11\);
+ \2Ret\);
+ """,
+ TestName = "Mae/Mae, introduces two variables")]
+
+ [TestCase("""
+ struct S { public int P { get; set; } }
+
+ class Foo
+ {
+ S M(S s) => s;
+ S TernaryOperators(int i) => i == 2 ? new S() : M(new S());
+ }
+ """,
+ """
+ var (?l_vt_\d+) = new VariableDefinition\((?st_S_\d+)\);
+ \s+m_ternaryOperators_11.Body.Variables.Add\(\k\);
+ (\s+il_ternaryOperators_\d+\.Emit\(OpCodes\.)Ldloca_S, \k\);
+ \1Initobj, \k\);
+ \1Ldloc, \k\);
+ \1Br_S, lbl_conditionEnd_14\);
+ \s+il_ternaryOperators_12.Append\(lbl_whenFalse_15\);
+ \1Ldarg_0\);
+ \s+var (?l_vt_\d+) = new VariableDefinition\(\k\);
+ \s+m_ternaryOperators_11.Body.Variables.Add\(\k\);
+ \1Ldloca_S, \k\);
+ \1Initobj, \k\);
+ \1Ldloc, \k\);
+ \1Call, m_M_8\);
+ \s+il_ternaryOperators_12.Append\(lbl_conditionEnd_14\);
+ \1Ret\);
+ """,
+ TestName = "Branch/Argument, introduces local variable")]
+
+ [TestCase("""
+ struct S { public int P { get; set; } }
+
+ class Foo
+ {
+ S M(S s) => s;
+ void TernaryOperators(int i) { var l = i == 2 ? new S() : M(new S()); }
+ }
+ """,
+ """
+ (il_ternaryOperators_\d+\.Emit\(OpCodes\.)Brfalse_S, lbl_whenFalse_16\);
+ \s+var (l_vt_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_ternaryOperators_11.Body.Variables.Add\(\2\);
+ (\s+\1)Ldloca_S, \2\);
+ \3Initobj, st_S_0\);
+ \3Ldloc, \2\);
+ \3Br_S, lbl_conditionEnd_15\);
+ \s+il_ternaryOperators_12.Append\(lbl_whenFalse_16\);
+ \3Ldarg_0\);
+ \s+var (l_vt_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_ternaryOperators_11.Body.Variables.Add\(\4\);
+ \3Ldloca_S, \4\);
+ \3Initobj, st_S_0\);
+ \3Ldloc, \4\);
+ \3Call, m_M_8\);
+ \s+il_ternaryOperators_12.Append\(lbl_conditionEnd_15\);
+ \3Stloc, l_l_14\);
+ """,
+ TestName = "Branch/Argument, in variable declaration, introduces local variable")]
+
+ [TestCase("""
+ struct S { public int P { get; set; } }
+
+ class Foo
+ {
+ int TernaryOperators(int i) => new S().P == 2 ? 1 : 2;
+ }
+ """,
+ """
+ var (?l_vt_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_ternaryOperators_8.Body.Variables.Add\(\k\);
+ (\s+il_ternaryOperators_\d+\.Emit\(OpCodes\.)Ldloca_S, \k\);
+ \1Initobj, st_S_0\);
+ \1Ldloca, \k\);
+ \1Call, m_get_2\);
+ \1Ldc_I4, 2\);
+ \1Ceq\);
+ \1Brfalse_S, lbl_whenFalse_12\);
+ """,
+ TestName = "Target of a member access in condition")]
+
+ [TestCase("""
+ struct S
+ {
+ public static implicit operator bool(S s) => false;
+ }
+
+ class Foo
+ {
+ int TernaryOperators() => new S() ? 1 : 2;
+ }
+ """,
+ @"//Simple value type instantiation \('new S\(\) '\) is not supported as the condition of a ternary operator in the expression: new S\(\) \? 1 : 2",
+ TestName = "As condition (implicit conversion)")]
+ public void InstantiatingThroughParameterlessCtor_InTernaryOperator(string source, string expected = "XXXX")
+ {
+ var result = RunCecilifier(source);
+
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expected));
+ }
+
+ [TestCaseSource(nameof(NonInstantiationValueTypeVariableInitializationTestScenarios))]
+ public void NonInstantiation_ValueTypeVariableInitialization_SetsAddedVariable(string expression, string expected)
+ {
+ var result = RunCecilifier(
+ $$"""
+ struct S { }
+ class Foo
+ {
+ S M(S s) => s;
+ void Bar(S p)
+ {
+ var s = {{expression}};
+ }
+ }
+ """);
+ Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(expected));
+ }
+
+ static IEnumerable NonInstantiationValueTypeVariableInitializationTestScenarios()
+ {
+ yield return new TestCaseData(
+ "M(new S())",
+ """
+ //var s = M\(new S\(\)\);
+ \s+var (l_s_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_bar_5.Body.Variables.Add\(\1\);
+ (\s+il_bar_6.Emit\(OpCodes\.)Ldarg_0\);
+ \s+var l_vt_9 = new VariableDefinition\(st_S_0\);
+ \s+m_bar_5.Body.Variables.Add\(l_vt_9\);
+ \2Ldloca_S, l_vt_9\);
+ \2Initobj, st_S_0\);
+ \2Ldloc, l_vt_9\);
+ \2Call, m_M_2\);
+ \2Stloc, \1\);
+ \2Ret\);
+ """).SetName("From Method");
+
+ yield return new TestCaseData(
+ "p",
+ """
+ //var s = p;
+ \s+var (l_s_\d+) = new VariableDefinition\(st_S_0\);
+ \s+m_bar_5.Body.Variables.Add\(\1\);
+ (\s+il_bar_6.Emit\(OpCodes\.)Ldarg_1\);
+ \2Stloc, \1\);
+ \2Ret\);
+ """).SetName("From Parameter");
+ }
}
diff --git a/Cecilifier.Core/AST/AssignmentVisitor.cs b/Cecilifier.Core/AST/AssignmentVisitor.cs
index 5b2e7257..fe73638e 100644
--- a/Cecilifier.Core/AST/AssignmentVisitor.cs
+++ b/Cecilifier.Core/AST/AssignmentVisitor.cs
@@ -155,7 +155,7 @@ private void AddCallToOpImplicitIfRequired(IdentifierNameSyntax node)
}
}
- // push `implicit this` (target of the assignment) to the stack if needed.
+ // push `implicit this` (target of the assignment) or target reference in an object initializer expression to the stack if needed.
void LoadImplicitTargetForMemberReference(IdentifierNameSyntax node, ISymbol memberSymbol)
{
if (memberSymbol is IFieldSymbol { RefKind: not RefKind.None } && !assignment.Right.IsKind(SyntaxKind.RefExpression))
@@ -175,7 +175,7 @@ void LoadImplicitTargetForMemberReference(IdentifierNameSyntax node, ISymbol mem
? OpCodes.Dup
: OpCodes.Ldarg_0;
- InsertCilInstructionAfter(InstructionPrecedingValueToLoad, ilVar, loadOpCode);
+ Context.WriteCilInstructionAfter(ilVar, loadOpCode, null, null, InstructionPrecedingValueToLoad);
}
}
@@ -197,8 +197,7 @@ bool HandleIndexer(SyntaxNode node, LinkedListNode lastInstructionLoadin
// bellow it.
AddMethodCall(ilVar, propertySymbol.GetMethod);
Context.MoveLinesToEnd(InstructionPrecedingValueToLoad, lastInstructionLoadingRhs);
- OpCode opCode = propertySymbol.Type.Stind();
- Context.EmitCilInstruction(ilVar, opCode);
+ EmitIndirectStore(propertySymbol.Type);
}
else
{
@@ -209,6 +208,12 @@ bool HandleIndexer(SyntaxNode node, LinkedListNode lastInstructionLoadin
return true;
}
+ private void EmitIndirectStore(ITypeSymbol typeBeingStored)
+ {
+ var indirectStoreOpCode = typeBeingStored.Stind();
+ Context.EmitCilInstruction(ilVar, indirectStoreOpCode, indirectStoreOpCode == OpCodes.Stobj ? Context.TypeResolver.Resolve(typeBeingStored.ElementTypeSymbolOf()) : null);
+ }
+
private void PropertyAssignment(IdentifierNameSyntax node, IPropertySymbol property)
{
property.EnsurePropertyExists(Context, node);
@@ -249,7 +254,7 @@ private void MemberAssignment(ITypeSymbol memberType, RefKind memberRefKind, Def
{
if (NeedsIndirectStore(memberType, memberRefKind))
{
- Context.EmitCilInstruction(ilVar, memberType.Stind());
+ EmitIndirectStore(memberType);
}
else
{
diff --git a/Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs b/Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs
new file mode 100644
index 00000000..2c2a001d
--- /dev/null
+++ b/Cecilifier.Core/AST/ExpressionVisitor.Conversions.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using Cecilifier.Core.Extensions;
+using Cecilifier.Core.Misc;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Mono.Cecil.Cil;
+
+namespace Cecilifier.Core.AST;
+
+partial class ExpressionVisitor
+{
+ private void InjectRequiredConversions(ExpressionSyntax expression, Action loadArrayIntoStack = null)
+ {
+ var typeInfo = ModelExtensions.GetTypeInfo(Context.SemanticModel, expression);
+ if (typeInfo.Type == null) return;
+ var conversion = Context.SemanticModel.GetConversion(expression);
+ if (conversion.IsImplicit)
+ {
+ if (conversion.IsNullable)
+ {
+ Context.EmitCilInstruction(
+ ilVar,
+ OpCodes.Newobj,
+ $"assembly.MainModule.ImportReference(typeof(System.Nullable<>).MakeGenericType(typeof({typeInfo.Type.FullyQualifiedName()})).GetConstructors().Single(ctor => ctor.GetParameters().Length == 1))");
+ return;
+ }
+
+ if (conversion.IsNumeric)
+ {
+ Debug.Assert(typeInfo.ConvertedType != null);
+ switch (typeInfo.ConvertedType.SpecialType)
+ {
+ case SpecialType.System_Single:
+ Context.EmitCilInstruction(ilVar, OpCodes.Conv_R4);
+ return;
+ case SpecialType.System_Double:
+ Context.EmitCilInstruction(ilVar, OpCodes.Conv_R8);
+ return;
+ case SpecialType.System_Byte:
+ Context.EmitCilInstruction(ilVar, OpCodes.Conv_I1);
+ return;
+ case SpecialType.System_Int16:
+ Context.EmitCilInstruction(ilVar, OpCodes.Conv_I2);
+ return;
+ case SpecialType.System_Int32:
+ // byte/char are pushed as Int32 by the runtime
+ if (typeInfo.Type.SpecialType != SpecialType.System_SByte && typeInfo.Type.SpecialType != SpecialType.System_Byte && typeInfo.Type.SpecialType != SpecialType.System_Char)
+ Context.EmitCilInstruction(ilVar, OpCodes.Conv_I4);
+ return;
+ case SpecialType.System_Int64:
+ var convOpCode = typeInfo.Type.SpecialType == SpecialType.System_Char || typeInfo.Type.SpecialType == SpecialType.System_Byte ? OpCodes.Conv_U8 : OpCodes.Conv_I8;
+ Context.EmitCilInstruction(ilVar, convOpCode);
+ return;
+ case SpecialType.System_Decimal:
+ var operand = typeInfo.ConvertedType.GetMembers().OfType()
+ .Single(m => m.MethodKind == MethodKind.Constructor && m.Parameters.Length == 1 && m.Parameters[0].Type.SpecialType == typeInfo.Type.SpecialType);
+ Context.EmitCilInstruction(ilVar, OpCodes.Newobj, operand.MethodResolverExpression(Context));
+ return;
+ default:
+ throw new Exception($"Conversion from {typeInfo.Type} to {typeInfo.ConvertedType} not implemented.");
+ }
+ }
+
+ if (conversion.MethodSymbol != null)
+ {
+ AddMethodCall(ilVar, conversion.MethodSymbol, false);
+ }
+ }
+
+ if (conversion.IsImplicit && (conversion.IsBoxing || NeedsBoxing(Context, expression, typeInfo.Type)))
+ {
+ AddCilInstruction(ilVar, OpCodes.Box, typeInfo.Type);
+ }
+ else if (conversion.IsIdentity && typeInfo.Type.Name == "Index" && !expression.IsKind(SyntaxKind.IndexExpression) && loadArrayIntoStack != null)
+ {
+ // We are indexing an array/indexer (this[]) using a System.Index variable; In this case
+ // we need to convert from System.Index to *int* which is done through
+ // the method System.Index::GetOffset(int32)
+ loadArrayIntoStack();
+ var indexed = ModelExtensions.GetTypeInfo(Context.SemanticModel, expression.Ancestors().OfType().Single().Expression);
+ Utils.EnsureNotNull(indexed.Type, "Cannot be null.");
+ if (indexed.Type.Name == "Span")
+ AddMethodCall(ilVar, ((IPropertySymbol) indexed.Type.GetMembers("Length").Single()).GetMethod);
+ else
+ Context.EmitCilInstruction(ilVar, OpCodes.Ldlen);
+ Context.EmitCilInstruction(ilVar, OpCodes.Conv_I4);
+ AddMethodCall(ilVar, (IMethodSymbol) typeInfo.Type.GetMembers().Single(m => m.Name == "GetOffset"));
+ }
+
+ // Empirically (verified in generated IL), expressions of type parameter used as:
+ // 1. Target of a call, 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'
+ // 2. Source of assignment (or variable initialization) to a reference type
+ // 3. Argument for a reference type parameter
+ // requires boxing, but for some reason, the conversion returned by GetConversion() does not reflects that.
+ static bool NeedsBoxing(IVisitorContext context, ExpressionSyntax expression, ITypeSymbol type)
+ {
+ var needsBoxing = type.TypeKind == TypeKind.TypeParameter && (NeedsBoxingUsedAsTargetOfReference(context, expression) || AssignmentExpressionNeedsBoxing(context, expression, type) ||
+ TypeIsReferenceType(context, expression, type) || expression.Parent.IsArgumentPassedToReferenceTypeParameter(context, type) ||
+ expression.Parent is BinaryExpressionSyntax binaryExpressionSyntax && binaryExpressionSyntax.OperatorToken.IsKind(SyntaxKind.IsKeyword));
+ return needsBoxing;
+
+ bool TypeIsReferenceType(IVisitorContext context, ExpressionSyntax expression, ITypeSymbol rightType)
+ {
+ if (expression.Parent is not EqualsValueClauseSyntax equalsValueClauseSyntax) return false;
+ Debug.Assert(expression.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator));
+
+ // get the type of the declared variable. For instance, in `int x = 10;`, expression='10',
+ // expression.Parent.Parent = 'x=10' (VariableDeclaratorSyntax)
+ var leftType = context.SemanticModel.GetDeclaredSymbol(expression.Parent.Parent).GetMemberType();
+ return !SymbolEqualityComparer.Default.Equals(leftType, rightType) && leftType.IsReferenceType;
+ }
+
+ static bool AssignmentExpressionNeedsBoxing(IVisitorContext context, ExpressionSyntax expression, ITypeSymbol rightType)
+ {
+ if (expression.Parent is not AssignmentExpressionSyntax assignment) return false;
+ var leftType = ModelExtensions.GetTypeInfo(context.SemanticModel, assignment.Left).Type;
+ return !SymbolEqualityComparer.Default.Equals(leftType, rightType) && leftType.IsReferenceType;
+ }
+
+ static bool NeedsBoxingUsedAsTargetOfReference(IVisitorContext context, ExpressionSyntax expression)
+ {
+ if (((CSharpSyntaxNode) expression.Parent).Accept(UsageVisitor.GetInstance(context)) != UsageKind.CallTarget) 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;
+ ITypeParameterSymbol typeParameter = null;
+ if (symbol == null)
+ {
+ typeParameter = context.GetTypeInfo(expression).Type as ITypeParameterSymbol;
+ }
+ else
+ {
+ // 'expression' represents a local variable, parameters, etc.. so we get its element type
+ typeParameter = symbol.GetMemberType() as ITypeParameterSymbol;
+ }
+
+ if (typeParameter == null) return false;
+ if (typeParameter.HasValueTypeConstraint) return false;
+ return typeParameter.HasReferenceTypeConstraint || (typeParameter.ConstraintTypes.Length > 0 && typeParameter.ConstraintTypes.Any(candidate => candidate.TypeKind != TypeKind.Interface));
+ }
+ }
+ }
+}
diff --git a/Cecilifier.Core/AST/ExpressionVisitor.cs b/Cecilifier.Core/AST/ExpressionVisitor.cs
index aaf25864..48d261bf 100644
--- a/Cecilifier.Core/AST/ExpressionVisitor.cs
+++ b/Cecilifier.Core/AST/ExpressionVisitor.cs
@@ -240,6 +240,9 @@ public override void VisitElementAccessExpression(ElementAccessExpressionSyntax
}
else
{
+ if (HandleLoadAddress(ilVar, targetType, node, OpCodes.Ldelema, Context.TypeResolver.Resolve(targetType)))
+ return;
+
var ldelemOpCodeToUse = targetType.LdelemOpCode();
Context.EmitCilInstruction(ilVar, ldelemOpCodeToUse, ldelemOpCodeToUse == OpCodes.Ldelem_Any ? Context.TypeResolver.Resolve(targetType) : null);
}
@@ -692,6 +695,10 @@ public override void VisitCastExpression(CastExpressionSyntax node)
{
AddCilInstruction(ilVar, OpCodes.Box, castSource.Type);
}
+ else if (conversion.IsUnboxing)
+ {
+ AddCilInstruction(ilVar, OpCodes.Unbox_Any, castTarget.Type);
+ }
else if (conversion.IsExplicit)
{
AddMethodCall(ilVar, conversion.MethodSymbol);
@@ -1045,7 +1052,7 @@ private bool TryProcessInvocationOnParameterlessImplicitCtorOnValueType(BaseObje
if (ctorInfo.Symbol == null || ctorInfo.Symbol.IsImplicitlyDeclared == false || ctorInfo.Symbol.ContainingType.IsReferenceType)
return false;
- var visitor = new ValueTypeNoArgCtorInvocationVisitor(Context, ilVar, ctorInfo);
+ var visitor = new ValueTypeNoArgCtorInvocationVisitor(Context, ilVar, node, ctorInfo);
visitor.Visit(node.Parent);
skipLeftSideVisitingInAssignment = visitor.TargetOfAssignmentIsValueType;
@@ -1328,161 +1335,6 @@ TypeParameterSyntax[] TypeParameterSyntaxFor(IMethodSymbol method)
return declarationNode.TypeParameterList.Parameters.ToArray();
}
-
- private void InjectRequiredConversions(ExpressionSyntax expression, Action loadArrayIntoStack = null)
- {
- var typeInfo = Context.SemanticModel.GetTypeInfo(expression);
- if (typeInfo.Type == null)
- return;
-
- var conversion = Context.SemanticModel.GetConversion(expression);
- if (conversion.IsImplicit)
- {
- if (conversion.IsNumeric)
- {
- Debug.Assert(typeInfo.ConvertedType != null);
- switch (typeInfo.ConvertedType.SpecialType)
- {
- case SpecialType.System_Single:
- Context.EmitCilInstruction(ilVar, OpCodes.Conv_R4);
- return;
-
- case SpecialType.System_Double:
- Context.EmitCilInstruction(ilVar, OpCodes.Conv_R8);
- return;
-
- case SpecialType.System_Byte:
- Context.EmitCilInstruction(ilVar, OpCodes.Conv_I1);
- return;
-
- case SpecialType.System_Int16:
- Context.EmitCilInstruction(ilVar, OpCodes.Conv_I2);
- return;
-
- case SpecialType.System_Int32:
- // byte/char are pushed as Int32 by the runtime
- if (typeInfo.Type.SpecialType != SpecialType.System_SByte && typeInfo.Type.SpecialType != SpecialType.System_Byte && typeInfo.Type.SpecialType != SpecialType.System_Char)
- Context.EmitCilInstruction(ilVar, OpCodes.Conv_I4);
- return;
-
- case SpecialType.System_Int64:
- var convOpCode = typeInfo.Type.SpecialType == SpecialType.System_Char || typeInfo.Type.SpecialType == SpecialType.System_Byte ? OpCodes.Conv_U8 : OpCodes.Conv_I8;
- Context.EmitCilInstruction(ilVar, convOpCode);
- return;
-
- case SpecialType.System_Decimal:
- var operand = typeInfo.ConvertedType.GetMembers().OfType().Single(m => m.MethodKind == MethodKind.Constructor && m.Parameters.Length == 1 && m.Parameters[0].Type.SpecialType == typeInfo.Type.SpecialType);
- Context.EmitCilInstruction(ilVar, OpCodes.Newobj, operand.MethodResolverExpression(Context));
- return;
-
- default:
- throw new Exception($"Conversion from {typeInfo.Type} to {typeInfo.ConvertedType} not implemented.");
- }
- }
-
- if (conversion.MethodSymbol != null)
- {
- AddMethodCall(ilVar, conversion.MethodSymbol, false);
- }
- }
-
- if (conversion.IsImplicit && (conversion.IsBoxing || NeedsBoxing(Context, expression, typeInfo.Type)))
- {
- AddCilInstruction(ilVar, OpCodes.Box, typeInfo.Type);
- }
- else if (conversion.IsIdentity && typeInfo.Type.Name == "Index" && !expression.IsKind(SyntaxKind.IndexExpression) && loadArrayIntoStack != null)
- {
- // We are indexing an array/indexer (this[]) using a System.Index variable; In this case
- // we need to convert from System.Index to *int* which is done through
- // the method System.Index::GetOffset(int32)
- loadArrayIntoStack();
-
- var indexed = Context.SemanticModel.GetTypeInfo(expression.Ancestors().OfType().Single().Expression);
- Utils.EnsureNotNull(indexed.Type, "Cannot be null.");
- if (indexed.Type.Name == "Span")
- AddMethodCall(ilVar, ((IPropertySymbol) indexed.Type.GetMembers("Length").Single()).GetMethod);
- else
- Context.EmitCilInstruction(ilVar, OpCodes.Ldlen);
-
- Context.EmitCilInstruction(ilVar, OpCodes.Conv_I4);
- AddMethodCall(ilVar, (IMethodSymbol) typeInfo.Type.GetMembers().Single(m => m.Name == "GetOffset"));
- }
-
- // Empirically (verified in generated IL), expressions of type parameter used as:
- // 1. Target of a call, 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'
- // 2. Source of assignment (or variable initialization) to a reference type
- // 3. Argument for a reference type parameter
- // requires boxing, but for some reason, the conversion returned by GetConversion() does not reflects that.
- static bool NeedsBoxing(IVisitorContext context, ExpressionSyntax expression, ITypeSymbol type)
- {
- var needsBoxing = type.TypeKind == TypeKind.TypeParameter &&
- (NeedsBoxingUsedAsTargetOfReference(context, expression)
- || AssignmentExpressionNeedsBoxing(context, expression, type)
- || TypeIsReferenceType(context, expression, type)
- || expression.Parent.IsArgumentPassedToReferenceTypeParameter(context, type)
- || expression.Parent is BinaryExpressionSyntax binaryExpressionSyntax && binaryExpressionSyntax.OperatorToken.IsKind(SyntaxKind.IsKeyword)
- );
-
- return needsBoxing;
-
- bool TypeIsReferenceType(IVisitorContext context, ExpressionSyntax expression, ITypeSymbol rightType)
- {
- if (expression.Parent is not EqualsValueClauseSyntax equalsValueClauseSyntax)
- return false;
-
- Debug.Assert(expression.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator));
-
- // get the type of the declared variable. For instance, in `int x = 10;`, expression='10',
- // expression.Parent.Parent = 'x=10' (VariableDeclaratorSyntax)
- var leftType = context.SemanticModel.GetDeclaredSymbol(expression.Parent.Parent).GetMemberType();
- return !SymbolEqualityComparer.Default.Equals(leftType, rightType) && leftType.IsReferenceType;
- }
-
- static bool AssignmentExpressionNeedsBoxing(IVisitorContext context, ExpressionSyntax expression, ITypeSymbol rightType)
- {
- if (expression.Parent is not AssignmentExpressionSyntax assignment)
- return false;
-
- var leftType = context.SemanticModel.GetTypeInfo(assignment.Left).Type;
-
- return !SymbolEqualityComparer.Default.Equals(leftType, rightType) && leftType.IsReferenceType;
- }
-
- static bool NeedsBoxingUsedAsTargetOfReference(IVisitorContext context, ExpressionSyntax expression)
- {
- if (((CSharpSyntaxNode) expression.Parent).Accept(UsageVisitor.GetInstance(context)) != UsageKind.CallTarget)
- return false;
-
- var symbol = context.SemanticModel.GetSymbolInfo(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;
-
- ITypeParameterSymbol typeParameter = null;
- if (symbol == null)
- {
- typeParameter = context.GetTypeInfo(expression).Type as ITypeParameterSymbol;
- }
- else
- {
- // 'expression' represents a local variable, parameters, etc.. so we get its element type
- typeParameter = symbol.GetMemberType() as ITypeParameterSymbol;
- }
-
- if (typeParameter == null)
- return false;
-
- if (typeParameter.HasValueTypeConstraint)
- return false;
-
- return typeParameter.HasReferenceTypeConstraint
- || (typeParameter.ConstraintTypes.Length > 0 && typeParameter.ConstraintTypes.Any(candidate => candidate.TypeKind != TypeKind.Interface));
- }
- }
- }
private BinaryOperatorHandler OperatorHandlerFor(SyntaxToken operatorToken)
{
diff --git a/Cecilifier.Core/AST/IVisitorContext.cs b/Cecilifier.Core/AST/IVisitorContext.cs
index 75d4f46a..ed736174 100644
--- a/Cecilifier.Core/AST/IVisitorContext.cs
+++ b/Cecilifier.Core/AST/IVisitorContext.cs
@@ -31,7 +31,8 @@ internal interface IVisitorContext
void WriteCecilExpression(string msg);
void WriteComment(string comment);
void WriteNewLine();
-
+
+ void WriteCilInstructionAfter(string ilVar, OpCode opCode, T operand, string comment, LinkedListNode after);
void MoveLineAfter(LinkedListNode instruction, LinkedListNode after);
void MoveLinesToEnd(LinkedListNode start, LinkedListNode end);
diff --git a/Cecilifier.Core/AST/MethodDeclarationVisitor.cs b/Cecilifier.Core/AST/MethodDeclarationVisitor.cs
index 9c2c2dc4..2d38563d 100644
--- a/Cecilifier.Core/AST/MethodDeclarationVisitor.cs
+++ b/Cecilifier.Core/AST/MethodDeclarationVisitor.cs
@@ -165,7 +165,7 @@ private void ProcessMethodDeclarationInternal(
// the later is a `mangled name` and any reference to the method will use its `unmangled name` for lookups which would fail
// should we use `methodName` as the registered name.
var nameUsedInRegisteredVariable = methodSymbol.MethodKind == MethodKind.LocalFunction ? simpleName : methodName;
- WithCurrentMethod(declaringTypeName, methodVar, nameUsedInRegisteredVariable, parameters.Select(p => Context.GetTypeInfo(p.Type).Type.ToDisplayString()).ToArray(), runWithCurrent);
+ WithCurrentMethod(declaringTypeName, methodVar, nameUsedInRegisteredVariable, parameters.Select(p => Context.SemanticModel.GetDeclaredSymbol(p).Type.ToDisplayString()).ToArray(), runWithCurrent);
if (!methodSymbol.IsAbstract && !node.DescendantNodes().Any(n => n.IsKind(SyntaxKind.ReturnStatement)))
{
Context.EmitCilInstruction(ilVar, OpCodes.Ret);
diff --git a/Cecilifier.Core/AST/NoArgsValueTypeObjectCreatingInAssignmentVisitor.cs b/Cecilifier.Core/AST/NoArgsValueTypeObjectCreatingInAssignmentVisitor.cs
index 58120d34..e965c170 100644
--- a/Cecilifier.Core/AST/NoArgsValueTypeObjectCreatingInAssignmentVisitor.cs
+++ b/Cecilifier.Core/AST/NoArgsValueTypeObjectCreatingInAssignmentVisitor.cs
@@ -12,12 +12,15 @@ internal class NoArgsValueTypeObjectCreatingInAssignmentVisitor : SyntaxWalkerBa
private readonly string ilVar;
private readonly string resolvedInstantiatedType;
private readonly Func tempValueTypeDeclarer;
+ private readonly BaseObjectCreationExpressionSyntax objectCreationExpression;
- internal NoArgsValueTypeObjectCreatingInAssignmentVisitor(IVisitorContext ctx, string ilVar, string resolvedInstantiatedType, Func tempValueTypeDeclarer) : base(ctx)
+ internal NoArgsValueTypeObjectCreatingInAssignmentVisitor(IVisitorContext ctx, string ilVar, string resolvedInstantiatedType, Func tempValueTypeDeclarer,
+ BaseObjectCreationExpressionSyntax objectCreationExpression) : base(ctx)
{
this.ilVar = ilVar;
this.resolvedInstantiatedType = resolvedInstantiatedType;
this.tempValueTypeDeclarer = tempValueTypeDeclarer;
+ this.objectCreationExpression = objectCreationExpression;
}
public bool TargetOfAssignmentIsValueType { get; private set; } = true;
@@ -33,8 +36,8 @@ public override void VisitElementAccessExpression(ElementAccessExpressionSyntax
//...since we have an `assignment` to an array element which is of type
//struct, we need to load the element address instead.
- Context.EmitCilInstruction(ilVar, OpCodes.Ldelema);
- Context.EmitCilInstruction(ilVar, OpCodes.Initobj, resolvedInstantiatedType);
+ Context.EmitCilInstruction(ilVar, OpCodes.Ldelema, resolvedInstantiatedType);
+ InitializeAndProcessObjectInitializerExpression();
}
public override void VisitIdentifierName(IdentifierNameSyntax node)
@@ -71,7 +74,6 @@ public override void VisitIdentifierName(IdentifierNameSyntax node)
Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0);
Context.EmitCilInstruction(ilVar, OpCodes.Ldflda, fieldResolverExpression);
}
-
break;
case SymbolKind.Parameter:
@@ -87,7 +89,21 @@ public override void VisitIdentifierName(IdentifierNameSyntax node)
Context.EmitCilInstruction(ilVar, opCode, parameterSymbol.Ordinal); // TODO: Static / Instance methods handling...
break;
}
+
+ InitializeAndProcessObjectInitializerExpression();
+ }
+
+ private void InitializeAndProcessObjectInitializerExpression()
+ {
+ if (objectCreationExpression.Initializer != null)
+ {
+ // See comment in ValueTypeNoArgCtorInvocationVisitor.InitValueTypeLocalVariable() for an explanation on why we
+ // duplicate the top of the stack.
+ Context.EmitCilInstruction(ilVar, OpCodes.Dup);
+ }
+
Context.EmitCilInstruction(ilVar, OpCodes.Initobj, resolvedInstantiatedType);
+ ValueTypeNoArgCtorInvocationVisitor.ProcessInitializerIfNotNull(Context, ilVar, objectCreationExpression.Initializer);
}
}
}
diff --git a/Cecilifier.Core/AST/StatementVisitor.cs b/Cecilifier.Core/AST/StatementVisitor.cs
index 8be55535..df936fd1 100644
--- a/Cecilifier.Core/AST/StatementVisitor.cs
+++ b/Cecilifier.Core/AST/StatementVisitor.cs
@@ -79,50 +79,62 @@ public override void VisitSwitchStatement(SwitchStatementSyntax node)
Context.EmitCilInstruction(_ilVar, OpCodes.Stloc, evaluatedExpressionVariable); // stores evaluated expression in local var
// Add label to end of switch
- var endOfSwitchLabel = CreateCilInstruction(_ilVar, OpCodes.Nop);
+ var endOfSwitchLabel = CreateCilInstruction(_ilVar, Context.Naming.Label("endOfSwitch"), OpCodes.Nop);
breakToInstructionVars.Push(endOfSwitchLabel);
// Write the switch conditions.
- var nextTestLabels = node.Sections.Select(_ => CreateCilInstruction(_ilVar, OpCodes.Nop)).ToArray();
+ var nextTestLabels = node.Sections.Select( (_, index) =>
+ {
+ var labelName = Context.Naming.Label($"caseCode_{index}");
+ CreateCilInstruction(_ilVar, labelName, OpCodes.Nop);
+ return labelName;
+ }).ToArray();
+
+ var hasDefault = false;
var currentLabelIndex = 0;
foreach (var switchSection in node.Sections)
{
if (switchSection.Labels.First().Kind() == SyntaxKind.DefaultSwitchLabel)
{
Context.EmitCilInstruction(_ilVar, OpCodes.Br, nextTestLabels[currentLabelIndex]);
+ hasDefault = true;
continue;
}
foreach (var sectionLabel in switchSection.Labels)
{
+ using var _ = LineInformationTracker.Track(Context, sectionLabel);
+
+ Context.WriteNewLine();
Context.WriteComment($"{sectionLabel.ToString()} (condition)");
Context.EmitCilInstruction(_ilVar, OpCodes.Ldloc, evaluatedExpressionVariable);
ExpressionVisitor.Visit(Context, _ilVar, sectionLabel);
- string operand = nextTestLabels[currentLabelIndex];
- Context.EmitCilInstruction(_ilVar, OpCodes.Beq_S, operand);
- Context.WriteNewLine();
+ Context.EmitCilInstruction(_ilVar, OpCodes.Beq_S, nextTestLabels[currentLabelIndex]);
}
currentLabelIndex++;
}
-
- // if at runtime the code hits this point it means none of the labels matched.
- // so, just jump to the end of the switch.
- Context.EmitCilInstruction(_ilVar, OpCodes.Br, endOfSwitchLabel);
+
+ // if at runtime the code hits this point and the switch does not have a default section
+ // it means none of the labels matched so just jump to the end of the switch.
+ if (!hasDefault)
+ Context.EmitCilInstruction(_ilVar, OpCodes.Br, endOfSwitchLabel);
// Write the statements for each switch section...
currentLabelIndex = 0;
foreach (var switchSection in node.Sections)
{
+ using var _ = LineInformationTracker.Track(Context, switchSection);
+ Context.WriteNewLine();
Context.WriteComment($"{switchSection.Labels.First().ToString()} (code)");
AddCecilExpression($"{_ilVar}.Append({nextTestLabels[currentLabelIndex]});");
foreach (var statement in switchSection.Statements)
{
statement.Accept(this);
}
- Context.WriteNewLine();
currentLabelIndex++;
}
+ Context.WriteNewLine();
Context.WriteComment("End of switch");
AddCecilExpression($"{_ilVar}.Append({endOfSwitchLabel});");
diff --git a/Cecilifier.Core/AST/SyntaxWalkerBase.cs b/Cecilifier.Core/AST/SyntaxWalkerBase.cs
index 7b7648ce..321fd058 100644
--- a/Cecilifier.Core/AST/SyntaxWalkerBase.cs
+++ b/Cecilifier.Core/AST/SyntaxWalkerBase.cs
@@ -78,15 +78,6 @@ protected void AddMethodCall(string ilVar, IMethodSymbol method, bool isAccessOn
}
}
- protected void InsertCilInstructionAfter(LinkedListNode instruction, string ilVar, OpCode opCode, T arg = default)
- {
- var instVar = CreateCilInstruction(ilVar, opCode, arg);
- Context.MoveLineAfter(Context.CurrentLine, instruction);
-
- AddCecilExpression($"{ilVar}.Append({instVar});");
- Context.MoveLineAfter(Context.CurrentLine, instruction.Next);
- }
-
protected void AddCilInstruction(string ilVar, OpCode opCode, ITypeSymbol type)
{
var operand = Context.TypeResolver.Resolve(type);
@@ -110,10 +101,11 @@ protected string CreateCilInstruction(string ilVar, OpCode opCode, object operan
return instVar;
}
- protected void CreateCilInstruction(string ilVar, string instVar, OpCode opCode, object operand = null)
+ protected string CreateCilInstruction(string ilVar, string instVar, OpCode opCode, object operand = null)
{
var operandStr = operand == null ? string.Empty : $", {operand}";
AddCecilExpression($"var {instVar} = {ilVar}.Create({opCode.ConstantName()}{operandStr});");
+ return instVar;
}
protected void LoadLiteralValue(string ilVar, ITypeSymbol type, string value, bool isTargetOfCall, SyntaxNode parent)
@@ -463,10 +455,8 @@ protected string ResolveType(TypeSyntax type)
protected void ProcessParameter(string ilVar, SimpleNameSyntax node, IParameterSymbol paramSymbol)
{
- var parent = (CSharpSyntaxNode) node.Parent;
var method = (IMethodSymbol) paramSymbol.ContainingSymbol;
-
- if (HandleLoadAddress(ilVar, paramSymbol.Type, parent, OpCodes.Ldarga, paramSymbol.Name, VariableMemberKind.Parameter, (method.AssociatedSymbol ?? method).ToDisplayString()))
+ if (HandleLoadAddressOnStorage(ilVar, paramSymbol.Type, node, OpCodes.Ldarga, paramSymbol.Name, VariableMemberKind.Parameter, (method.AssociatedSymbol ?? method).ToDisplayString()))
return;
Utils.EnsureNotNull(node.Parent);
@@ -513,7 +503,7 @@ protected void ProcessField(string ilVar, SimpleNameSyntax node, IFieldSymbol fi
if (!fieldSymbol.IsStatic && !isTargetOfQualifiedAccess)
Context.EmitCilInstruction(ilVar, OpCodes.Ldarg_0);
- if (HandleLoadAddress(ilVar, fieldSymbol.Type, (CSharpSyntaxNode) node.Parent, fieldSymbol.IsStatic ? OpCodes.Ldsflda : OpCodes.Ldflda, fieldSymbol.Name, VariableMemberKind.Field, fieldSymbol.ContainingType.ToDisplayString()))
+ if (HandleLoadAddressOnStorage(ilVar, fieldSymbol.Type, node, fieldSymbol.IsStatic ? OpCodes.Ldsflda : OpCodes.Ldflda, fieldSymbol.Name, VariableMemberKind.Field, fieldSymbol.ContainingType.ToDisplayString()))
{
return;
}
@@ -533,8 +523,7 @@ protected void ProcessField(string ilVar, SimpleNameSyntax node, IFieldSymbol fi
protected void ProcessLocalVariable(string ilVar, SimpleNameSyntax localVarSyntax, ILocalSymbol symbol)
{
- var localVar = (CSharpSyntaxNode) localVarSyntax.Parent;
- if (HandleLoadAddress(ilVar, symbol.Type, localVar, OpCodes.Ldloca, symbol.Name, VariableMemberKind.LocalVariable))
+ if (HandleLoadAddressOnStorage(ilVar, symbol.Type, localVarSyntax, OpCodes.Ldloca, symbol.Name, VariableMemberKind.LocalVariable))
return;
var operand = Context.DefinitionVariables.GetVariable(symbol.Name, VariableMemberKind.LocalVariable).VariableName;
@@ -552,8 +541,15 @@ private void HandlePotentialFixedLoad(string ilVar, ILocalSymbol symbol)
Context.EmitCilInstruction(ilVar, OpCodes.Conv_U);
}
- private bool HandleLoadAddress(string ilVar, ITypeSymbol symbol, CSharpSyntaxNode node, OpCode opCode, string symbolName, VariableMemberKind variableMemberKind, string parentName = null)
+ private bool HandleLoadAddressOnStorage(string ilVar, ITypeSymbol symbol, CSharpSyntaxNode node, OpCode opCode, string symbolName, VariableMemberKind variableMemberKind, string parentName = null)
+ {
+ var operand = Context.DefinitionVariables.GetVariable(symbolName, variableMemberKind, parentName).VariableName;
+ return HandleLoadAddress(ilVar, symbol, node, opCode, operand);
+ }
+
+ protected bool HandleLoadAddress(string ilVar, ITypeSymbol symbol, CSharpSyntaxNode node, OpCode opCode, string operand)
{
+ var parentNode = (CSharpSyntaxNode)node.Parent;
return HandleCallOnTypeParameter() || HandleCallOnValueType() || HandleRefAssignment() || HandleParameter();
bool HandleCallOnValueType()
@@ -563,13 +559,12 @@ bool HandleCallOnValueType()
// in this case we need to call System.Index.GetOffset(int32) on a value type (System.Index)
// which requires the address of the value type.
- var isSystemIndexUsedAsIndex = IsSystemIndexUsedAsIndex(symbol, node);
- var usageResult = node.Accept(UsageVisitor.GetInstance(Context));
- if (isSystemIndexUsedAsIndex || node.IsKind(SyntaxKind.AddressOfExpression) || IsPseudoAssignmentToValueType() || usageResult.Kind == UsageKind.CallTarget)
+ var isSystemIndexUsedAsIndex = IsSystemIndexUsedAsIndex(symbol, parentNode);
+ var usageResult = parentNode.Accept(UsageVisitor.GetInstance(Context));
+ if (isSystemIndexUsedAsIndex || parentNode.IsKind(SyntaxKind.AddressOfExpression) || IsPseudoAssignmentToValueType() || node.IsMemberAccessOnElementAccess() || usageResult.Kind == UsageKind.CallTarget)
{
- var operand = Context.DefinitionVariables.GetVariable(symbolName, variableMemberKind, parentName).VariableName;
Context.EmitCilInstruction(ilVar, opCode, operand);
- if (!Context.HasFlag(Constants.ContextFlags.Fixed) && node.IsKind(SyntaxKind.AddressOfExpression))
+ if (!Context.HasFlag(Constants.ContextFlags.Fixed) && parentNode.IsKind(SyntaxKind.AddressOfExpression))
Context.EmitCilInstruction(ilVar, OpCodes.Conv_U);
// calls to virtual methods on custom value types needs to be constrained (don't know why, but the generated IL for such scenarios does `constrains`).
@@ -590,10 +585,9 @@ bool HandleCallOnTypeParameter()
if (typeParameter.HasReferenceTypeConstraint || typeParameter.IsReferenceType)
return false;
- if (node.Accept(UsageVisitor.GetInstance(Context)) != UsageKind.CallTarget)
+ if (parentNode.Accept(UsageVisitor.GetInstance(Context)) != UsageKind.CallTarget)
return false;
- var operand = Context.DefinitionVariables.GetVariable(symbolName, variableMemberKind, parentName).VariableName;
Context.EmitCilInstruction(ilVar, opCode, operand);
Context.EmitCilInstruction(ilVar, OpCodes.Constrained, Context.TypeResolver.Resolve(symbol));
return true;
@@ -601,26 +595,24 @@ bool HandleCallOnTypeParameter()
bool HandleRefAssignment()
{
- if (!(node is RefExpressionSyntax refExpression))
+ if (!(parentNode is RefExpressionSyntax refExpression))
return false;
var assignedValueSymbol = Context.SemanticModel.GetSymbolInfo(refExpression.Expression);
if (assignedValueSymbol.Symbol.IsByRef())
return false;
- string operand = Context.DefinitionVariables.GetVariable(symbolName, variableMemberKind, parentName).VariableName;
Context.EmitCilInstruction(ilVar, opCode, operand);
return true;
}
bool HandleParameter()
{
- if (!(node is ArgumentSyntax argument) || !argument.RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword))
+ if (!(parentNode is ArgumentSyntax argument) || !argument.RefOrOutKeyword.IsKind(SyntaxKind.RefKeyword))
return false;
if (Context.SemanticModel.GetSymbolInfo(argument.Expression).Symbol?.IsByRef() == false)
{
- string operand = Context.DefinitionVariables.GetVariable(symbolName, variableMemberKind, parentName).VariableName;
Context.EmitCilInstruction(ilVar, opCode, operand);
return true;
}
@@ -739,7 +731,7 @@ protected OpCode LoadIndirectOpCodeFor(SpecialType type)
};
}
- private bool IsSystemIndexUsedAsIndex(ITypeSymbol symbol, CSharpSyntaxNode node)
+ private bool IsSystemIndexUsedAsIndex(ITypeSymbol symbol, SyntaxNode node)
{
if (symbol.MetadataToken != Context.RoslynTypeSystem.SystemIndex.MetadataToken)
return false;
diff --git a/Cecilifier.Core/AST/ValueTypeNoArgCtorInvocationVisitor.cs b/Cecilifier.Core/AST/ValueTypeNoArgCtorInvocationVisitor.cs
index 8bd3269c..70cbe513 100644
--- a/Cecilifier.Core/AST/ValueTypeNoArgCtorInvocationVisitor.cs
+++ b/Cecilifier.Core/AST/ValueTypeNoArgCtorInvocationVisitor.cs
@@ -2,24 +2,45 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Cecilifier.Core.Variables;
+using Microsoft.CodeAnalysis.CSharp;
using Mono.Cecil.Cil;
using static Cecilifier.Core.Misc.CodeGenerationHelpers;
namespace Cecilifier.Core.AST
{
+ // Visitor used to handle instantiation of value types when an implicit, parameterless ctor is used,
+ // i.e, 'new MyValueType()'.
+ //
+ // It is used to abstract the fact that, in this scenario, the instructions emitted when newing a value type
+ // are very different from the ones used to newing a reference type.
+ //
+ // A good part of this visitor deals with the case in which we need to introduce a temporary variable; this is most
+ // the case when the result of the 'new T()' expression is not used as a) a direct assignment to a value type variable
+ // or b) as the initializer in the declaration of the value type variable.
+ //
+ // This visitor expects visit the parent of the ObjectCreationExpression, for instance, in 'M(new T())', this visitor
+ // should be called to visit the method invocation.
+ //
+ // Note that the generated code will hardly match the generated code by a C# compiler. Empirically we observed that
+ // the later may decide to emit either a 'newobj' or 'call ctor' depending on release/debug mode, whether there's
+ // already storage or not allocated to store the value type, etc.
+ //
+ // For simplicity the only differentiation we have is based on whether the invoked constructor is an implicit,
+ // parameterless one in which case this visitor is used; in all other cases the same code used to handle constructor
+ // invocations on reference types is used.
internal class ValueTypeNoArgCtorInvocationVisitor : SyntaxWalkerBase
{
private readonly SymbolInfo ctorInfo;
private readonly string ilVar;
+ private readonly BaseObjectCreationExpressionSyntax objectCreationExpressionSyntax;
- internal ValueTypeNoArgCtorInvocationVisitor(IVisitorContext ctx, string ilVar, SymbolInfo ctorInfo) : base(ctx)
+ internal ValueTypeNoArgCtorInvocationVisitor(IVisitorContext ctx, string ilVar, BaseObjectCreationExpressionSyntax objectCreationExpressionSyntax, SymbolInfo ctorInfo) : base(ctx)
{
this.ctorInfo = ctorInfo;
this.ilVar = ilVar;
+ this.objectCreationExpressionSyntax = objectCreationExpressionSyntax;
}
- public bool TargetOfAssignmentIsValueType { get; private set; } = true;
-
public override void VisitUsingStatement(UsingStatementSyntax node)
{
// our direct parent is a using statement, which means we have something like:
@@ -30,14 +51,14 @@ public override void VisitUsingStatement(UsingStatementSyntax node)
public override void VisitEqualsValueClause(EqualsValueClauseSyntax node)
{
- //local variable assignment
+ //local variable declaration initialized through a initializer.
+ //i.e, var x = new MyStruct();
var firstAncestorOrSelf = node.FirstAncestorOrSelf();
var varName = firstAncestorOrSelf?.Variables[0].Identifier.ValueText;
var operand = Context.DefinitionVariables.GetVariable(varName, VariableMemberKind.LocalVariable).VariableName;
- Context.EmitCilInstruction(ilVar, OpCodes.Ldloca_S, operand);
- var type = Context.TypeResolver.Resolve(ctorInfo.Symbol.ContainingType);
- Context.EmitCilInstruction(ilVar, OpCodes.Initobj, type);
+ InitValueTypeLocalVariable(operand);
+ TargetOfAssignmentIsValueType = true;
}
public override void VisitReturnStatement(ReturnStatementSyntax node)
@@ -56,13 +77,41 @@ public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax no
{
var valueTypeLocalVariable = DeclareAndInitializeValueTypeLocalVariable();
Context.EmitCilInstruction(ilVar, OpCodes.Ldloca, valueTypeLocalVariable.VariableName);
- var accessedMember = Context.SemanticModel.GetSymbolInfo(node).Symbol.EnsureNotNull();
+ var accessedMember = ModelExtensions.GetSymbolInfo(Context.SemanticModel, node).Symbol.EnsureNotNull();
if (accessedMember.ContainingType.SpecialType == SpecialType.System_ValueType)
{
Context.EmitCilInstruction(ilVar, OpCodes.Constrained, ResolvedStructType());
}
}
+ public override void VisitConditionalExpression(ConditionalExpressionSyntax node)
+ {
+ if (node.Condition == objectCreationExpressionSyntax)
+ {
+ Context.WriteComment("");
+ Context.WriteComment($"Simple value type instantiation ('{objectCreationExpressionSyntax.ToFullString()}') is not supported as the condition of a ternary operator in the expression: {node.ToFullString()}");
+ Context.WriteComment("");
+ return;
+ }
+
+ if (node.WhenTrue != objectCreationExpressionSyntax && node.WhenFalse != objectCreationExpressionSyntax)
+ return;
+
+ if (node.WhenFalse is ObjectCreationExpressionSyntax falseExpression && (falseExpression.ArgumentList == null || falseExpression.ArgumentList.Arguments.Count == 0) &&
+ node.WhenTrue is ObjectCreationExpressionSyntax trueExpression && (trueExpression.ArgumentList == null || trueExpression.ArgumentList?.Arguments.Count == 0))
+ {
+ // both branches are object creation expressions for parameterless value types; lets visit the base to decide whether there are already storage allocated
+ // or not and take appropriate action.
+ ((CSharpSyntaxNode) node.Parent).Accept(this);
+ return;
+ }
+
+ // one of the branches are not an object creation expression so we need to add a variable (for that at least),
+ // initialize it and load it to the stack to be consumed, for instance as an argument of a method call.
+ var valueTypeLocalVariable = DeclareAndInitializeValueTypeLocalVariable();
+ Context.EmitCilInstruction(ilVar, OpCodes.Ldloc, valueTypeLocalVariable.VariableName);
+ }
+
public override void VisitCastExpression(CastExpressionSyntax node)
{
var valueTypeLocalVariable = DeclareAndInitializeValueTypeLocalVariable();
@@ -72,7 +121,7 @@ public override void VisitCastExpression(CastExpressionSyntax node)
public override void VisitAssignmentExpression(AssignmentExpressionSyntax node)
{
var instantiatedType = ResolvedStructType();
- var visitor = new NoArgsValueTypeObjectCreatingInAssignmentVisitor(Context, ilVar, instantiatedType, DeclareAndInitializeValueTypeLocalVariable);
+ var visitor = new NoArgsValueTypeObjectCreatingInAssignmentVisitor(Context, ilVar, instantiatedType, DeclareAndInitializeValueTypeLocalVariable, objectCreationExpressionSyntax);
node.Left.Accept(visitor);
TargetOfAssignmentIsValueType = visitor.TargetOfAssignmentIsValueType;
}
@@ -84,6 +133,8 @@ public override void VisitArgument(ArgumentSyntax node)
Context.EmitCilInstruction(ilVar, loadOpCode, valueTypeLocalVariable.VariableName);
}
+ public bool TargetOfAssignmentIsValueType { get; private set; }
+
private DefinitionVariable DeclareAndInitializeValueTypeLocalVariable()
{
var resolvedVarType = ResolvedStructType();
@@ -120,8 +171,29 @@ private void InitValueTypeLocalVariable(string localVariable)
{
Context.EmitCilInstruction(ilVar, OpCodes.Ldloca_S, localVariable);
Context.EmitCilInstruction(ilVar, OpCodes.Initobj, ResolvedStructType());
+
+ if (objectCreationExpressionSyntax.Initializer is not null)
+ {
+ // To process an InitializerExpressionSyntax, ExpressionVisitor expects the top of the stack to contain
+ // the reference of the object to be set.
+ // Since initialisation of value types through a parameterless ctor uses the `initobj` instruction
+ // at this point there's no object reference in the stack (it was consumed by the `Initobj` instruction)
+ // so we push the address of the variable that we just initialised again. Notice that after processing
+ // the initializer we need to pop this reference from the stack again.
+ Context.EmitCilInstruction(ilVar, OpCodes.Ldloca_S, localVariable);
+ ProcessInitializerIfNotNull(Context, ilVar, objectCreationExpressionSyntax.Initializer);
+ }
}
-
+
+ internal static void ProcessInitializerIfNotNull(IVisitorContext context, string ilVar, InitializerExpressionSyntax initializer)
+ {
+ if (initializer == null)
+ return;
+
+ ExpressionVisitor.Visit(context, ilVar, initializer);
+ context.EmitCilInstruction(ilVar, OpCodes.Pop);
+ }
+
private string ResolvedStructType() => Context.TypeResolver.Resolve(ctorInfo.Symbol.ContainingType);
}
}
diff --git a/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs b/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs
index 643a26f6..4ea5415c 100644
--- a/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs
+++ b/Cecilifier.Core/Extensions/SyntaxNodeExtensions.cs
@@ -38,9 +38,16 @@ public static bool IsOperatorOnCustomUserType(this SyntaxNode self, SemanticMode
var symbolInfo = semanticModel.GetSymbolInfo(self);
method = symbolInfo.Symbol as IMethodSymbol;
+ if (method?.ContainingType?.SpecialType == SpecialType.System_Object)
+ {
+ // special case for object: == & != are handled by its respective overloaded operators.
+ // observe below that `string` behaves exactly the opposite of this.
+ return method.Name != "op_Equality" && method.Name != "op_Inequality";
+ }
+
if (method?.ContainingType?.SpecialType == SpecialType.System_String)
{
- // for strings, == & != are handled by its respective operators.
+ // for strings, == & != are handled by its respective overloaded operators.
// other operators, like +, are mapped to a specific method call
return method.Name == "op_Equality" || method.Name == "op_Inequality";
}
@@ -117,5 +124,7 @@ internal static bool IsArgumentPassedToReferenceTypeParameter(this SyntaxNode to
return typeToNotMatch == null || !SymbolEqualityComparer.Default.Equals(argumentOperation.Parameter.Type, typeToNotMatch);
}
+
+ internal static bool IsMemberAccessOnElementAccess(this SyntaxNode self) => self.IsKind(SyntaxKind.ElementAccessExpression) && self.Parent is MemberAccessExpressionSyntax mae && mae.Expression == self;
}
}
diff --git a/Cecilifier.Core/Extensions/TypeExtensions.cs b/Cecilifier.Core/Extensions/TypeExtensions.cs
index 6fb7a18c..184a65d0 100644
--- a/Cecilifier.Core/Extensions/TypeExtensions.cs
+++ b/Cecilifier.Core/Extensions/TypeExtensions.cs
@@ -48,8 +48,7 @@ public static string MakeGenericInstanceType(this string type, string genericTyp
INamedTypeSymbol { IsGenericType: true, OriginalDefinition: { ContainingNamespace.Name: "System", Name: "Span" } } ns => ns.TypeArguments[0],
IPointerTypeSymbol ptr => ptr.PointedAtType,
IArrayTypeSymbol array => array.ElementType,
-
- _ => throw new ArgumentException($"{type.Name} not supported.", nameof(type))
+ _ => type
};
public static uint SizeofArrayLikeItemElement(this ITypeSymbol type)
diff --git a/Cecilifier.Core/Misc/CecilifierContext.cs b/Cecilifier.Core/Misc/CecilifierContext.cs
index e9c72ded..549012f5 100644
--- a/Cecilifier.Core/Misc/CecilifierContext.cs
+++ b/Cecilifier.Core/Misc/CecilifierContext.cs
@@ -106,6 +106,15 @@ public void WriteNewLine()
CecilifiedLineNumber++;
}
+ public void WriteCilInstructionAfter(string ilVar, OpCode opCode, T operand, string comment, LinkedListNode after)
+ {
+ var operandStr = operand == null ? string.Empty : $", {operand}";
+ var toBeWritten = $"{ilVar}.Emit({opCode.ConstantName()}{operandStr});{(comment != null ? $" // {comment}" : string.Empty)}\n";
+
+ output.AddAfter(after, $"{identation}{toBeWritten}");
+ CecilifiedLineNumber += toBeWritten.CountNewLines();
+ }
+
public void MoveLineAfter(LinkedListNode instruction, LinkedListNode after)
{
if (instruction == after)
diff --git a/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs b/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs
index e0623675..2db825f0 100644
--- a/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs
+++ b/Cecilifier.Core/TypeSystem/RoslynTypeSystem.cs
@@ -41,6 +41,7 @@ public RoslynTypeSystem(IVisitorContext ctx)
SystemCollectionsGenericIEnumeratorOfT = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerator_T);
SystemCollectionsIEnumerable = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerable);
SystemCollectionsGenericIEnumerableOfT = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T);
+ SystemNullableOfT = ctx.SemanticModel.Compilation.GetSpecialType(SpecialType.System_Nullable_T);
}
public ITypeSymbol SystemIndex { get; }
@@ -68,4 +69,5 @@ public RoslynTypeSystem(IVisitorContext ctx)
public ITypeSymbol SystemValueType { get; }
public ITypeSymbol SystemRuntimeCompilerServicesRuntimeHelpers { get; }
+ public ITypeSymbol SystemNullableOfT { get; }
}
diff --git a/Cecilifier.Core/TypeSystem/SystemTypeSystem.cs b/Cecilifier.Core/TypeSystem/SystemTypeSystem.cs
index 67cabe91..ff8837c8 100644
--- a/Cecilifier.Core/TypeSystem/SystemTypeSystem.cs
+++ b/Cecilifier.Core/TypeSystem/SystemTypeSystem.cs
@@ -21,6 +21,7 @@ public SystemTypeSystem(ITypeResolver typeResolver, IVisitorContext context)
[SpecialType.System_MulticastDelegate] = typeResolver.Resolve("System.MulticastDelegate"),
[SpecialType.System_AsyncCallback] = typeResolver.Resolve("System.AsyncCallback"),
[SpecialType.System_IAsyncResult] = typeResolver.Resolve("System.IAsyncResult"),
+ [SpecialType.System_Nullable_T] = typeResolver.Resolve("System.Nullable<>"),
};
}
@@ -35,6 +36,7 @@ public SystemTypeSystem(ITypeResolver typeResolver, IVisitorContext context)
public string MulticastDelegate => _resolvedTypes[SpecialType.System_MulticastDelegate];
public string AsyncCallback => _resolvedTypes[SpecialType.System_AsyncCallback];
public string IAsyncResult => _resolvedTypes[SpecialType.System_IAsyncResult];
+ public string NullableOfT => _resolvedTypes[SpecialType.System_Nullable_T];
private readonly IReadOnlyDictionary _resolvedTypes;
}
diff --git a/Cecilifier.Web/CecilifierRestHandler.cs b/Cecilifier.Web/CecilifierRestHandler.cs
index 4d8a85c5..d3c5d1fc 100644
--- a/Cecilifier.Web/CecilifierRestHandler.cs
+++ b/Cecilifier.Web/CecilifierRestHandler.cs
@@ -204,7 +204,7 @@ private static async Task ExecuteReadOnlyGitHubApiAuthenticated(HttpContext cont
if (string.IsNullOrWhiteSpace(_cecilifierGitHubToken))
{
_logger.LogWarning($"CECILIFIER_GITHUB_TOKEN not defined. Retrieval of list of bugs fixed in staging/latest release info will not be available.");
- context.Response.WriteAsync("N/A");
+ await context.Response.WriteAsync("N/A");
return;
}