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; }