From b70cb7e338d0d36cf4e98fd9af86bb8a5d5c9c03 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Fri, 10 Oct 2025 07:07:00 +0200 Subject: [PATCH 1/2] Roslyn 5.0.0-2.final --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 01739d7a7d..99129e660b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,8 +14,8 @@ - - + + From b45eaca7f62efb26dd9d92e281c886c60d7bdc37 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 23 Oct 2025 17:32:35 +0200 Subject: [PATCH 2/2] Add tests for ExtensionEncodingV1 and ExtensionEncodingV2. --- .../Helpers/Tester.cs | 2 +- .../ICSharpCode.Decompiler.Tests.csproj | 4 + .../ILPrettyTestRunner.cs | 16 + .../TestCases/ILPretty/ExtensionEncodingV1.cs | 38 ++ .../TestCases/ILPretty/ExtensionEncodingV1.il | 253 ++++++++++++++ .../TestCases/ILPretty/ExtensionEncodingV2.cs | 33 ++ .../TestCases/ILPretty/ExtensionEncodingV2.il | 328 ++++++++++++++++++ .../CSharp/CSharpDecompiler.cs | 39 ++- .../TypeSystem/ExtensionInfo.cs | 84 ++++- 9 files changed, 778 insertions(+), 19 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV1.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV1.il create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV2.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV2.il diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 8ce3117975..9311ed1fde 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -133,7 +133,7 @@ internal static async Task Initialize() await vswhereToolset.Fetch().ConfigureAwait(false); await RefAssembliesToolset.Fetch("5.0.0", sourcePath: "ref/net5.0").ConfigureAwait(false); - await RefAssembliesToolset.Fetch("10.0.0-preview.4.25258.110", sourcePath: "ref/net10.0").ConfigureAwait(false); + await RefAssembliesToolset.Fetch("10.0.0-rc.2.25502.107", sourcePath: "ref/net10.0").ConfigureAwait(false); #if DEBUG diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index de9ba4ba42..faa9ac373c 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -96,6 +96,8 @@ + + @@ -145,6 +147,8 @@ + + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 50fdeb3d9e..6ffefade4c 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -323,6 +323,22 @@ public async Task MonoFixed() await Run(); } + [Test] + public async Task ExtensionEncodingV1() + { + // uses Microsoft.Net.Compilers.Toolset 5.0.0-2.25380.108 + // see ExtensionEncodingV1.il for details + await Run(); + } + + [Test] + public async Task ExtensionEncodingV2() + { + // uses Microsoft.Net.Compilers.Toolset 5.0.0-2.25451.107 + // see ExtensionEncodingV2.il for details + await Run(); + } + async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null, AssemblerOptions assemblerOptions = AssemblerOptions.Library) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV1.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV1.cs new file mode 100644 index 0000000000..dbd9037727 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV1.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty +{ + internal static class ExtensionPropertiesV1 + { + extension(ICollection collection) where T : notnull + { + public bool IsEmpty => collection.Count == 0; + + public int Test { + get { + return 42; + } + set { + } + } + + public void AddIfNotNull(T item) + { + if (item != null) + { + collection.Add(item); + } + } + + public T2 Cast(int index) where T2 : T + { + return (T2)(object)collection.ElementAt(index); + } + + public static void StaticExtension() + { + } + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV1.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV1.il new file mode 100644 index 0000000000..419dd097f5 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV1.il @@ -0,0 +1,253 @@ +// To extend use: +// see https://lab.razor.fyi/#fVHNbhMxEFYRqrBP0CcYbpsDVlioVDU_UrSNUBCqImVPnHC8k8TgtRd7tiSqcuMh-gbceQBeihdA66bJNlK5WDPzfTPffGP-65TzqXdLL0uhwtndaR20XcJsEwjLHm9nInPGoCLtbBAf0KLX6j-MSVnWJOcGjzhXWi6tC6RVeBoRmStwZKXZBH1M-6Tt96NSvvIoC22XT9VFLsO30OPcyhJDJRXCJJutpK8aIXGFypWVNuhFjoFCfDMZMIipR6INv-VMW0JvpYFAkrQCZWQIMF4T2qCdnXpXoSeNgbNbzhg-AP18mEwOl-nnQ1D7rAM_VugRcrgE68jWxnAW-1lVz41WMHfOwCSMy4o2MGj3iszVlmAwgG6Ptzq0JWgMQBzDlvgQMY9Uewvv017Mt_ENezzm2_aoG6cLGBXFZHHt6Lo2JslBE5adhnPfpBeQNCV4PYBm_QjtMNZadlQUkddpaT_SylPIZKB-ng6TxoK2Ba7390nhEvKD6s5JkqedxM2_oqJOS2tssERLI0ruh0TJR2K7P4z-ZjHef2RyMNcsueVsy7f87NW56Irum1Sk5-8uuuJt9-LjM20-sxd3P__-_rN4-fzLyfrkHw +// created using https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-tools/NuGet/Microsoft.Net.Compilers.Toolset/overview/5.0.0-2.25380.108 + +.class private auto ansi abstract sealed beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV1 + extends [System.Runtime]System.Object +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 00 00 00 + ) + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + // Nested Types + .class nested public auto ansi sealed specialname beforefieldinit '<>E__0`1' + extends [System.Runtime]System.Object + { + // Methods + .method private hidebysig specialname static + void '$' ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x209d + // Header size: 1 + // Code size: 1 (0x1) + .maxstack 8 + + IL_0000: ret + } // end of method '<>E__0`1'::'$' + + .method public hidebysig specialname + instance bool get_IsEmpty () cil managed + { + // Method begins at RVA 0x209f + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '<>E__0`1'::get_IsEmpty + + .method public hidebysig specialname + instance int32 get_Test () cil managed + { + // Method begins at RVA 0x209f + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '<>E__0`1'::get_Test + + .method public hidebysig specialname + instance void set_Test ( + int32 'value' + ) cil managed + { + // Method begins at RVA 0x209f + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '<>E__0`1'::set_Test + + .method public hidebysig + instance void AddIfNotNull ( + !T item + ) cil managed + { + // Method begins at RVA 0x209f + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '<>E__0`1'::AddIfNotNull + + .method public hidebysig + instance !!T2 Cast<(!T) T2> ( + int32 index + ) cil managed + { + .param type T2 + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = ( + 01 00 00 00 00 + ) + // Method begins at RVA 0x209f + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '<>E__0`1'::Cast + + .method public hidebysig static + void StaticExtension () cil managed + { + // Method begins at RVA 0x209f + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '<>E__0`1'::StaticExtension + + // Properties + .property instance bool IsEmpty() + { + .get instance bool ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV1/'<>E__0`1'::get_IsEmpty() + } + .property instance int32 Test() + { + .get instance int32 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV1/'<>E__0`1'::get_Test() + .set instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV1/'<>E__0`1'::set_Test(int32) + } + + } // end of class <>E__0`1 + + + // Methods + .method public hidebysig static + bool get_IsEmpty ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection + ) cil managed + { + // Method begins at RVA 0x2050 + // Header size: 1 + // Code size: 10 (0xa) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: callvirt instance int32 class [System.Runtime]System.Collections.Generic.ICollection`1::get_Count() + IL_0006: ldc.i4.0 + IL_0007: ceq + IL_0009: ret + } // end of method ExtensionProperties::get_IsEmpty + + .method public hidebysig static + int32 get_Test ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection + ) cil managed + { + // Method begins at RVA 0x205b + // Header size: 1 + // Code size: 4 (0x4) + .maxstack 8 + + IL_0000: nop + IL_0001: ldc.i4.s 42 + IL_0003: ret + } // end of method ExtensionProperties::get_Test + + .method public hidebysig static + void set_Test ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection, + int32 'value' + ) cil managed + { + // Method begins at RVA 0x2060 + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: nop + IL_0001: ret + } // end of method ExtensionProperties::set_Test + + .method public hidebysig static + void AddIfNotNull ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection, + !!T item + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x2064 + // Header size: 12 + // Code size: 25 (0x19) + .maxstack 2 + .locals init ( + [0] bool + ) + + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: box !!T + IL_0007: ldnull + IL_0008: cgt.un + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: brfalse.s IL_0018 + + IL_000e: nop + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: callvirt instance void class [System.Runtime]System.Collections.Generic.ICollection`1::Add(!0) + IL_0016: nop + IL_0017: nop + + IL_0018: ret + } // end of method ExtensionProperties::AddIfNotNull + + .method public hidebysig static + !!T2 Cast ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection, + int32 index + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x2089 + // Header size: 1 + // Code size: 19 (0x13) + .maxstack 8 + + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call !!0 [System.Linq]System.Linq.Enumerable::ElementAt(class [System.Runtime]System.Collections.Generic.IEnumerable`1, int32) + IL_0008: box !!T + IL_000d: unbox.any !!T2 + IL_0012: ret + } // end of method ExtensionProperties::Cast + + .method public hidebysig static + void StaticExtension () cil managed + { + // Method begins at RVA 0x2060 + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: nop + IL_0001: ret + } // end of method ExtensionProperties::StaticExtension + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV1 diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV2.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV2.cs new file mode 100644 index 0000000000..effd8aa5be --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV2.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; +namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty +{ + internal static class ExtensionPropertiesV2 + { + extension(ICollection collection) where T : notnull + { + public bool IsEmpty => collection.Count == 0; + public int Test { + get { + return 42; + } + set { + } + } + public void AddIfNotNull(T item) + { + if (item != null) + { + collection.Add(item); + } + } + public T2 Cast(int index) where T2 : T + { + return (T2)(object)collection.ElementAt(index); + } + public static void StaticExtension() + { + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV2.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV2.il new file mode 100644 index 0000000000..996199657a --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/ExtensionEncodingV2.il @@ -0,0 +1,328 @@ +// To extend use: +// see https://lab.razor.fyi/#fVHBbtNAEFUQArwn6BcMPdkSrFyrFVKbVIrcgIKqKKKrHkBIbOyps3S9a3bHtFEViQvfwy9w4Qf4D66ckd00dSOVy2pn3nvz5u2y748ZmzpbOFnyzG_9fVR7ZQo4WXjC8oB1K55arTEjZY3nb9CgU9l_GOOyrEnONG5wjpQsjPWkMn8_wlOb49BIvfBqk3aszJeNlpg7lLkyxX19LqQ_3xz0rjakSuSn6LyyppWzD9J7LGd6sQ9CugLptZMlXlh3Hm7zyUik1uGwql6sRIOdmMfb0UfGjCzRVzJDGKcnc-mqJgI_wsyWldLouEBPvj1T6dHz8fHUIdGCXbFAGUJnpAZPklQGmZbew-iS0DQuU2crdKTQnyYsuGJBgDdQXxyG49t374tDyNZVBBdzdAgC9sFYMrXWLGj1QVXPtMpgZq2GsR-VFS1g0NXy1NaGYDCA-IB1FMoQNCGgHRMUeHMLHFLtDOwmB229bE-_xtt62R311aochnk-PptYmtRahwIUYRk1nGuROoOwacHzATTrt9AKCzrLDvO85UUd7zteIoFUeuqL5DBsIiiT4-X6fRLYB3HrukoSiiQK7ewzZhR1vEYaSzQ0pPB6SGt5x2z1i22-k_a-_srwNlyz5JIFS7ZkW8_2eMzjlwlP9nb3dvhO_OrtA6XfB09-__n14-fZ04efevPeZe9br_cP +// created using https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-tools/NuGet/Microsoft.Net.Compilers.Toolset/overview/5.0.0-2.25451.107 + +.assembly TestProject +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = { + } + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = { + int32(8) + } + .custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = { + property bool WrapNonExceptionThrows = bool(true) + } + .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = { + int32(263) + } + .custom instance void [System.Runtime]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = { + string('.NETCoreApp,Version=10.0') + } + .permissionset reqmin = { + [System.Runtime]System.Security.Permissions.SecurityPermissionAttribute = { + property bool SkipVerification = bool(true) + } + } + .hash algorithm 0x00008004 // SHA1 + .ver 0:0:0:0 +} + +.class private auto ansi '' +{ +} // end of class + +.class private auto ansi abstract sealed beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV2 + extends [System.Runtime]System.Object +{ + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = { + uint8(1) + } + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = { + uint8(0) + } + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = { + } + // Nested Types + .class nested public auto ansi sealed specialname '$847CB318C385471B1F4E7BD0A197DBCA'<$T0> + extends [System.Runtime]System.Object + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = { + } + // Nested Types + .class nested public auto ansi abstract sealed specialname '$6A79ECC07A7731C57E8EB4E3D9C8B38B' + extends [System.Runtime]System.Object + { + // Methods + .method public hidebysig specialname static + void '$' ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = { + } + // Method begins at RVA 0x20a0 + // Header size: 1 + // Code size: 1 (0x1) + .maxstack 8 + + IL_0000: ret + } // end of method '$6A79ECC07A7731C57E8EB4E3D9C8B38B'::'$' + + } // end of class $6A79ECC07A7731C57E8EB4E3D9C8B38B + + + // Methods + .method public hidebysig specialname + instance bool get_IsEmpty () cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = { + string('$6A79ECC07A7731C57E8EB4E3D9C8B38B') + } + // Method begins at RVA 0x209d + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '$847CB318C385471B1F4E7BD0A197DBCA'::get_IsEmpty + + .method public hidebysig specialname + instance int32 get_Test () cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = { + string('$6A79ECC07A7731C57E8EB4E3D9C8B38B') + } + // Method begins at RVA 0x209d + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '$847CB318C385471B1F4E7BD0A197DBCA'::get_Test + + .method public hidebysig specialname + instance void set_Test ( + int32 'value' + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = { + string('$6A79ECC07A7731C57E8EB4E3D9C8B38B') + } + // Method begins at RVA 0x209d + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '$847CB318C385471B1F4E7BD0A197DBCA'::set_Test + + .method public hidebysig + instance void AddIfNotNull ( + !$T0 item + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = { + string('$6A79ECC07A7731C57E8EB4E3D9C8B38B') + } + // Method begins at RVA 0x209d + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '$847CB318C385471B1F4E7BD0A197DBCA'::AddIfNotNull + + .method public hidebysig + instance !!T2 Cast<(!$T0) T2> ( + int32 index + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = { + string('$6A79ECC07A7731C57E8EB4E3D9C8B38B') + } + .param type T2 + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = { + uint8(0) + } + // Method begins at RVA 0x209d + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '$847CB318C385471B1F4E7BD0A197DBCA'::Cast + + .method public hidebysig static + void StaticExtension () cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = { + string('$6A79ECC07A7731C57E8EB4E3D9C8B38B') + } + // Method begins at RVA 0x209d + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: ldnull + IL_0001: throw + } // end of method '$847CB318C385471B1F4E7BD0A197DBCA'::StaticExtension + + // Properties + .property instance bool IsEmpty() + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = { + string('$6A79ECC07A7731C57E8EB4E3D9C8B38B') + } + .get instance bool ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV2/'$847CB318C385471B1F4E7BD0A197DBCA'::get_IsEmpty() + } + .property instance int32 Test() + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = { + string('$6A79ECC07A7731C57E8EB4E3D9C8B38B') + } + .get instance int32 ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV2/'$847CB318C385471B1F4E7BD0A197DBCA'::get_Test() + .set instance void ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV2/'$847CB318C385471B1F4E7BD0A197DBCA'::set_Test(int32) + } + + } // end of class $847CB318C385471B1F4E7BD0A197DBCA + + + // Methods + .method public hidebysig static + bool get_IsEmpty ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection + ) cil managed + { + // Method begins at RVA 0x2050 + // Header size: 1 + // Code size: 10 (0xa) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: callvirt instance int32 class [System.Runtime]System.Collections.Generic.ICollection`1::get_Count() + IL_0006: ldc.i4.0 + IL_0007: ceq + IL_0009: ret + } // end of method ExtensionPropertiesV2::get_IsEmpty + + .method public hidebysig static + int32 get_Test ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection + ) cil managed + { + // Method begins at RVA 0x205b + // Header size: 1 + // Code size: 4 (0x4) + .maxstack 8 + + IL_0000: nop + IL_0001: ldc.i4.s 42 + IL_0003: ret + } // end of method ExtensionPropertiesV2::get_Test + + .method public hidebysig static + void set_Test ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection, + int32 'value' + ) cil managed + { + // Method begins at RVA 0x2060 + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: nop + IL_0001: ret + } // end of method ExtensionPropertiesV2::set_Test + + .method public hidebysig static + void AddIfNotNull ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection, + !!T item + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = { + } + // Method begins at RVA 0x2064 + // Header size: 12 + // Code size: 25 (0x19) + .maxstack 2 + .locals init ( + [0] bool + ) + + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: box !!T + IL_0007: ldnull + IL_0008: cgt.un + IL_000a: stloc.0 + IL_000b: ldloc.0 + IL_000c: brfalse.s IL_0018 + + IL_000e: nop + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: callvirt instance void class [System.Runtime]System.Collections.Generic.ICollection`1::Add(!0) + IL_0016: nop + IL_0017: nop + + IL_0018: ret + } // end of method ExtensionPropertiesV2::AddIfNotNull + + .method public hidebysig static + !!T2 Cast ( + class [System.Runtime]System.Collections.Generic.ICollection`1 collection, + int32 index + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = { + } + // Method begins at RVA 0x2089 + // Header size: 1 + // Code size: 19 (0x13) + .maxstack 8 + + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call !!0 [System.Linq]System.Linq.Enumerable::ElementAt(class [System.Runtime]System.Collections.Generic.IEnumerable`1, int32) + IL_0008: box !!T + IL_000d: unbox.any !!T2 + IL_0012: ret + } // end of method ExtensionPropertiesV2::Cast + + .method public hidebysig static + void StaticExtension () cil managed + { + // Method begins at RVA 0x2060 + // Header size: 1 + // Code size: 2 (0x2) + .maxstack 8 + + IL_0000: nop + IL_0001: ret + } // end of method ExtensionPropertiesV2::StaticExtension + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.ILPretty.ExtensionPropertiesV2 + +// references +.assembly extern System.Runtime +{ + .publickeytoken = ( + b0 3f 5f 7f 11 d5 0a 3a + ) + .ver 10:0:0:0 +} +.assembly extern System.Linq +{ + .publickeytoken = ( + b0 3f 5f 7f 11 d5 0a 3a + ) + .ver 10:0:0:0 +} diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 2fdba305e6..90436d94be 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1367,13 +1367,16 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun foreach (var group in typeDef.ExtensionInfo?.GetGroups() ?? []) { var ext = new ExtensionDeclaration(); - ext.TypeParameters.AddRange(group.Key.DeclaringTypeDefinition.TypeParameters.Select(tp => typeSystemAstBuilder.ConvertTypeParameter(tp))); - ext.ReceiverParameters.Add(typeSystemAstBuilder.ConvertParameter(group.Key.Parameters.Single())); - ext.Constraints.AddRange(group.Key.DeclaringTypeDefinition.TypeParameters.Select(c => typeSystemAstBuilder.ConvertTypeParameterConstraint(c))); + ITypeParameter[] typeParameters = group.Key.TypeParameters; + var subst = new TypeParameterSubstitution(typeParameters, null); + ext.TypeParameters.AddRange(typeParameters.Select(tp => typeSystemAstBuilder.ConvertTypeParameter(tp))); + var marker = group.Key.Marker.Specialize(subst); + ext.ReceiverParameters.Add(typeSystemAstBuilder.ConvertParameter(marker.Parameters.Single())); + ext.Constraints.AddRange(typeParameters.Select(c => typeSystemAstBuilder.ConvertTypeParameterConstraint(c))); foreach (var member in group) { - IMember extMember = member.ExtensionMember; + IMember extMember = member.ExtensionMember.Specialize(subst); if (member.ExtensionMember.IsAccessor) { extMember = member.ExtensionMember.AccessorOwner; @@ -1508,9 +1511,15 @@ void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler, Partia return; } - if (settings.ExtensionMembers && extensionInfo != null && entity is IMethod m && extensionInfo.InfoOfImplementationMember(m).HasValue) + if (settings.ExtensionMembers && extensionInfo != null) { - return; + switch (entity) + { + case ITypeDefinition td when extensionInfo.IsExtensionGroupingType(td): + return; + case IMethod m when extensionInfo.InfoOfImplementationMember(m).HasValue: + return; + } } EntityDeclaration entityDecl; @@ -1584,9 +1593,21 @@ private EntityDeclaration DoDecompileExtensionMember(IMember extMember, Extensio switch (extMember) { case IProperty p: - return DoDecompile(p, decompileRun, decompilationContext.WithCurrentMember(p), info); + var prop = DoDecompile(p, decompileRun, decompilationContext.WithCurrentMember(p), info); + RemoveAttribute(prop, KnownAttribute.ExtensionMarker); + if (p.Getter != null) + { + RemoveAttribute(prop.GetChildByRole(PropertyDeclaration.GetterRole), KnownAttribute.ExtensionMarker); + } + if (p.Setter != null) + { + RemoveAttribute(prop.GetChildByRole(PropertyDeclaration.SetterRole), KnownAttribute.ExtensionMarker); + } + return prop; case IMethod m: - return DoDecompile(m, decompileRun, decompilationContext.WithCurrentMember(m), info); + var meth = DoDecompile(m, decompileRun, decompilationContext.WithCurrentMember(m), info); + RemoveAttribute(meth, KnownAttribute.ExtensionMarker); + return meth; } throw new NotSupportedException($"Extension member {extMember} is not supported for decompilation."); @@ -1762,7 +1783,7 @@ void DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun de { if (!method.IsStatic) parameterOffset = 1; // implementation method has an additional receiver parameter - method = extensionInfo.InfoOfExtensionMember(method).Value.ImplementationMethod; + method = extensionInfo.InfoOfExtensionMember((IMethod)method.MemberDefinition).Value.ImplementationMethod; } var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken); diff --git a/ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs b/ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs index dfe82d774f..c2e28cac68 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs @@ -20,9 +20,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; +using ICSharpCode.Decompiler.TypeSystem.Implementation; + namespace ICSharpCode.Decompiler.TypeSystem { public class ExtensionInfo @@ -59,6 +62,7 @@ bool TryEncodingV1(ITypeDefinition extGroup) IMethod? marker = null; bool hasMultipleMarkers = false; List extensionMethods = []; + ITypeParameter[] extensionGroupTypeParameters = extGroup.TypeParameters.ToArray(); // For easier access to accessors we use SRM foreach (var h in td.GetMethods()) @@ -82,7 +86,7 @@ bool TryEncodingV1(ITypeDefinition extGroup) if (marker == null || hasMultipleMarkers) return false; - CollectImplementationMethods(extGroup, marker, extensionMethods); + CollectImplementationMethods(extGroup, marker, extensionMethods, extensionGroupTypeParameters); return true; } @@ -102,6 +106,7 @@ bool TryEncodingV2(ITypeDefinition extGroup) TypeDefinition td = metadata.GetTypeDefinition((TypeDefinitionHandle)extGroup.MetadataToken); List extensionMethods = []; + ITypeParameter[] extensionGroupTypeParameters = new ITypeParameter[extGroup.TypeParameterCount]; // For easier access to accessors we use SRM foreach (var h in td.GetMethods()) @@ -121,12 +126,16 @@ bool TryEncodingV2(ITypeDefinition extGroup) extensionMethods.Add(method); } - CollectImplementationMethods(extGroup, marker, extensionMethods); + CollectImplementationMethods(extGroup, marker, extensionMethods, extensionGroupTypeParameters); return true; } - void CollectImplementationMethods(ITypeDefinition extGroup, IMethod marker, List extensionMethods) + void CollectImplementationMethods(ITypeDefinition extGroup, IMethod marker, List extensionMethods, ITypeParameter[] extensionGroupTypeParameters) { + List<(IMethod extension, IMethod implementation)> implementations = []; + + string[] typeParameterNames = new string[extGroup.TypeParameterCount]; + foreach (var extension in extensionMethods) { int expectedTypeParameterCount = extension.TypeParameters.Count + extGroup.TypeParameterCount; @@ -165,15 +174,62 @@ bool IsMatchingImplementation(IMethod impl) ); } + IMethod? foundImpl = null; + foreach (var impl in extensionContainer.Methods) { if (!IsMatchingImplementation(impl)) continue; - var emi = new ExtensionMemberInfo(marker, extension, impl); - extensionMemberMap[extension] = emi; - implementationMemberMap[impl] = emi; + Debug.Assert(foundImpl == null, "Multiple matching implementations found"); + foundImpl = impl; + } + + Debug.Assert(foundImpl != null, "No matching implementation found"); + + implementations.Add((extension, foundImpl)); + } + + foreach (var (extension, implementation) in implementations) + { + for (int i = 0; i < extensionGroupTypeParameters.Length; i++) + { + if (typeParameterNames[i] == null) + { + typeParameterNames[i] = implementation.TypeParameters[i].Name; + } + else if (typeParameterNames[i] != implementation.TypeParameters[i].Name) + { + // TODO: Handle name conflicts properly + typeParameterNames[i] = $"T{i + 1}"; + } } } + + for (int i = 0; i < extensionGroupTypeParameters.Length; i++) + { + var originalTypeParameter = extGroup.TypeParameters[i]; + if (extensionGroupTypeParameters[i] == null) + { + extensionGroupTypeParameters[i] = new DefaultTypeParameter( + extGroup, i, typeParameterNames[i], + VarianceModifier.Invariant, + attributes: originalTypeParameter.GetAttributes().ToArray(), + originalTypeParameter.HasValueTypeConstraint, + originalTypeParameter.HasReferenceTypeConstraint, + originalTypeParameter.HasDefaultConstructorConstraint, + originalTypeParameter.TypeConstraints.Select(c => c.Type).ToArray(), + originalTypeParameter.NullabilityConstraint + ); + } + } + + foreach (var (extension, implementation) in implementations) + { + var info = new ExtensionMemberInfo(marker, extension, implementation, extensionGroupTypeParameters); + this.extensionMemberMap[extension] = info; + this.implementationMemberMap[implementation] = info; + } + } } @@ -187,13 +243,18 @@ bool IsMatchingImplementation(IMethod impl) return this.implementationMemberMap.TryGetValue(method, out var value) ? value : null; } - public IEnumerable> GetGroups() + public IEnumerable> GetGroups() + { + return this.extensionMemberMap.Values.GroupBy(x => (x.ExtensionMarkerMethod, x.ExtensionGroupingTypeParameters)); + } + + public bool IsExtensionGroupingType(ITypeDefinition type) { - return this.extensionMemberMap.Values.GroupBy(x => x.ExtensionMarkerMethod); + return this.extensionMemberMap.Values.Any(x => x.ExtensionGroupingType.Equals(type)); } } - public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IMethod implementation) + public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IMethod implementation, ITypeParameter[] extensionGroupingTypeParameters) { /// /// Metadata-only method called '<Extension>$'. Has the C# signature for the extension declaration. @@ -223,6 +284,11 @@ public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IM /// public ITypeDefinition ExtensionGroupingType => ExtensionMember.DeclaringTypeDefinition!; + /// + /// This is the array of type parameters for the extension declaration. + /// + public ITypeParameter[] ExtensionGroupingTypeParameters => extensionGroupingTypeParameters; + /// /// This class holds the type parameters for the extension declaration with full fidelity of C# constraints. ///