diff --git a/BuildTools/update-assemblyinfo.ps1 b/BuildTools/update-assemblyinfo.ps1
index f021a331d..dbfa5f911 100644
--- a/BuildTools/update-assemblyinfo.ps1
+++ b/BuildTools/update-assemblyinfo.ps1
@@ -53,7 +53,7 @@ function gitCommitHash() {
if (No-Git) {
return "0000000000000000000000000000000000000000";
}
- return (git rev-list "$baseCommit..HEAD") | Select -First 1;
+ return (git rev-list --max-count 1 HEAD);
}
function gitBranch() {
@@ -150,7 +150,7 @@ try {
$out = $out.Replace('$INSERTVERSIONNAMEPOSTFIX$', $postfixVersionName);
$out = $out.Replace('$INSERTBUILDCONFIG$', $buildConfig);
- if (((Get-Content $file.Input) -Join [System.Environment]::NewLine) -ne $out) {
+ if ((-not (Test-File $file.Output)) -or (((Get-Content $file.Output) -Join [System.Environment]::NewLine) -ne $out)) {
$out | Out-File -Encoding utf8 $file.Output;
}
}
@@ -174,7 +174,7 @@ try {
$out = $out.Replace('$INSERTVERSIONNAMEPOSTFIX$', $postfixVersionName);
$out = $out.Replace('$INSERTBUILDCONFIG$', $buildConfig);
- if (((Get-Content $file.Input) -Join [System.Environment]::NewLine) -ne $out) {
+ if ((-not (Test-File $file.Output)) -or (((Get-Content $file.Output) -Join [System.Environment]::NewLine) -ne $out)) {
$out | Out-File -Encoding utf8 $file.Output;
}
}
diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
index ebd186188..ac9c66bf6 100644
--- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
@@ -202,6 +202,12 @@ namespace ICSharpCode.Decompiler.Tests
RunCS(options: options);
}
+ [Test]
+ public void DeconstructionTests([ValueSource("roslynOnlyOptions")] CompilerOptions options)
+ {
+ RunCS(options: options);
+ }
+
[Test]
public void BitNot([Values(false, true)] bool force32Bit)
{
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 62b9b5535..93cbbe251 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -91,7 +91,9 @@
+
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 48bbeba88..5754913a9 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -500,6 +500,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(cscOptions: cscOptions);
}
+ [Test]
+ public void DeconstructionTests([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
+ {
+ RunForLibrary(cscOptions: cscOptions);
+ }
+
void RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null)
{
Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings);
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs
new file mode 100644
index 000000000..f27372aca
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs
@@ -0,0 +1,249 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
+{
+ class DeconstructionTests
+ {
+ public static void Main()
+ {
+ new DeconstructionTests().Test();
+ }
+
+ public struct MyInt
+ {
+ public static implicit operator int(MyInt x)
+ {
+ Console.WriteLine("int op_Implicit(MyInt)");
+ return 0;
+ }
+
+ public static implicit operator MyInt(int x)
+ {
+ Console.WriteLine("MyInt op_Implicit(int)");
+ return default(MyInt);
+ }
+ }
+
+ private class DeconstructionSource
+ {
+ public int Dummy {
+ get;
+ set;
+ }
+
+ public void Deconstruct(out T a, out T2 b)
+ {
+ Console.WriteLine("Deconstruct");
+ a = default(T);
+ b = default(T2);
+ }
+ }
+
+ private class AssignmentTargets
+ {
+ int id;
+
+ public AssignmentTargets(int id)
+ {
+ this.id = id;
+ }
+
+ public int IntField;
+
+ public int? NullableIntField;
+
+ public MyInt MyIntField;
+
+ public MyInt? NullableMyIntField;
+
+ public MyInt My {
+ get {
+ Console.WriteLine($"{id}.get_My()");
+ return default;
+ }
+ set {
+ Console.WriteLine($"{id}.set_My({value})");
+ }
+ }
+
+ public MyInt? NMy {
+ get {
+ Console.WriteLine($"{id}.get_NMy()");
+ return default;
+ }
+ set {
+ Console.WriteLine($"{id}.set_NMy({value})");
+ }
+ }
+
+ public int IntProperty {
+ get {
+ Console.WriteLine($"{id}.get_IntProperty()");
+ return default;
+ }
+ set {
+ Console.WriteLine($"{id}.set_IntProperty({value})");
+ }
+ }
+
+ public uint UIntProperty {
+ get {
+ Console.WriteLine($"{id}.get_UIntProperty()");
+ return default;
+ }
+ set {
+ Console.WriteLine($"{id}.set_UIntProperty({value})");
+ }
+ }
+ }
+
+ private DeconstructionSource GetSource()
+ {
+ Console.WriteLine("GetSource()");
+ return new DeconstructionSource();
+ }
+
+ private (T, T2) GetTuple()
+ {
+ Console.WriteLine("GetTuple()");
+ return default(ValueTuple);
+ }
+
+ private (T, T2, T3) GetTuple()
+ {
+ Console.WriteLine("GetTuple()");
+ return default(ValueTuple);
+ }
+
+ private AssignmentTargets Get(int i)
+ {
+ Console.WriteLine($"Get({i})");
+ return new AssignmentTargets(i);
+ }
+
+ public void Test()
+ {
+ Property_NoDeconstruction_SwappedAssignments();
+ Property_NoDeconstruction_SwappedInits();
+ Property_IntToUIntConversion();
+ NoDeconstruction_NotUsingConver();
+ NoDeconstruction_NotUsingConver_Tuple();
+ NullReferenceException_Field_Deconstruction(out _);
+ NullReferenceException_RefLocalReferencesField_Deconstruction(out _);
+ NullReferenceException_RefLocalReferencesArrayElement_Deconstruction(out _, null);
+ DeconstructTupleSameVar(("a", "b"));
+ DeconstructTupleListForEachSameVar(new List<(string, string)> { ("a", "b") });
+ }
+
+ public void Property_NoDeconstruction_SwappedAssignments()
+ {
+ Console.WriteLine("Property_NoDeconstruction_SwappedAssignments:");
+ AssignmentTargets customDeconstructionAndConversion = Get(0);
+ AssignmentTargets customDeconstructionAndConversion2 = Get(1);
+ GetSource().Deconstruct(out MyInt? x, out MyInt y);
+ MyInt myInt2 = customDeconstructionAndConversion2.My = y;
+ MyInt? myInt4 = customDeconstructionAndConversion.NMy = x;
+ }
+
+ public void Property_NoDeconstruction_SwappedInits()
+ {
+ Console.WriteLine("Property_NoDeconstruction_SwappedInits:");
+ AssignmentTargets customDeconstructionAndConversion = Get(1);
+ (Get(0).NMy, customDeconstructionAndConversion.My) = GetSource();
+ }
+
+ public void Property_IntToUIntConversion()
+ {
+ Console.WriteLine("Property_IntToUIntConversion:");
+ AssignmentTargets t0 = Get(0);
+ AssignmentTargets t1 = Get(1);
+ int a;
+ uint b;
+ GetSource().Deconstruct(out a, out b);
+ t0.UIntProperty = (uint)a;
+ t1.IntProperty = (int)b;
+ }
+
+ public void NoDeconstruction_NotUsingConver()
+ {
+ Console.WriteLine("NoDeconstruction_NotUsingConver:");
+ AssignmentTargets t0 = Get(0);
+ int a;
+ uint b;
+ GetSource().Deconstruct(out a, out b);
+ long c = a;
+ t0.IntProperty = a;
+ t0.UIntProperty = b;
+ Console.WriteLine(c);
+ }
+
+ public void NoDeconstruction_NotUsingConver_Tuple()
+ {
+ Console.WriteLine("NoDeconstruction_NotUsingConver_Tuple:");
+ AssignmentTargets t0 = Get(0);
+ var t = GetTuple();
+ long c = t.Item1;
+ t0.IntProperty = t.Item1;
+ t0.UIntProperty = t.Item2;
+ Console.WriteLine(c);
+ }
+
+ public void NullReferenceException_Field_Deconstruction(out int a)
+ {
+ try {
+ AssignmentTargets t0 = null;
+ (t0.IntField, a) = GetSource();
+ } catch (Exception ex) {
+ a = 0;
+ Console.WriteLine(ex.GetType().FullName);
+ }
+ }
+
+ public void NullReferenceException_RefLocalReferencesField_Deconstruction(out int a)
+ {
+ try {
+ AssignmentTargets t0 = null;
+ ref int i = ref t0.IntField;
+ (i, a) = GetSource();
+ } catch (Exception ex) {
+ a = 0;
+ Console.WriteLine(ex.GetType().FullName);
+ }
+ }
+
+ public void NullReferenceException_RefLocalReferencesArrayElement_Deconstruction(out int a, int[] arr)
+ {
+ try {
+ ref int i = ref arr[0];
+ (i, a) = GetSource();
+ } catch (Exception ex) {
+ a = 0;
+ Console.WriteLine(ex.GetType().FullName);
+ }
+ }
+
+ public void DeconstructTupleSameVar((string, string) tuple)
+ {
+ Console.WriteLine("DeconstructTupleSameVar:");
+ string a;
+ a = tuple.Item1;
+ a = tuple.Item2;
+ Console.WriteLine(a);
+ }
+
+ public void DeconstructTupleListForEachSameVar(List<(string, string)> tuples)
+ {
+ Console.WriteLine("DeconstructTupleListForEachSameVar:");
+ foreach (var tuple in tuples) {
+ string a;
+ a = tuple.Item1;
+ a = tuple.Item2;
+ Console.WriteLine(a);
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs
new file mode 100644
index 000000000..566a56c3e
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs
@@ -0,0 +1,596 @@
+// Copyright (c) 2020 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+{
+ public static class DeconstructionExt
+ {
+ public static void Deconstruct(this KeyValuePair pair, out K key, out V value)
+ {
+ key = pair.Key;
+ value = pair.Value;
+ }
+ }
+
+ internal class DeconstructionTests
+ {
+ [StructLayout(LayoutKind.Sequential, Size = 1)]
+ public struct MyInt
+ {
+ public static implicit operator int(MyInt x)
+ {
+ return 0;
+ }
+
+ public static implicit operator MyInt(int x)
+ {
+ return default(MyInt);
+ }
+ }
+
+ private class DeconstructionSource
+ {
+ public int Dummy {
+ get;
+ set;
+ }
+
+ public void Deconstruct(out T a, out T2 b)
+ {
+ a = default(T);
+ b = default(T2);
+ }
+ }
+
+ private class DeconstructionSource
+ {
+ public int Dummy {
+ get;
+ set;
+ }
+
+ public void Deconstruct(out T a, out T2 b, out T3 c)
+ {
+ a = default(T);
+ b = default(T2);
+ c = default(T3);
+ }
+ }
+
+ private class AssignmentTargets
+ {
+ public int IntField;
+ public long LongField;
+ public float FloatField;
+ public double DoubleField;
+ public decimal DecimalField;
+
+ public MyInt MyField;
+ public MyInt? NMyField;
+
+ public string StringField;
+ public object ObjectField;
+ public dynamic DynamicField;
+
+ public int? NullableIntField;
+
+ public MyInt MyIntField;
+
+ public MyInt? NullableMyIntField;
+
+ public int Int {
+ get;
+ set;
+ }
+
+ public long Long {
+ get;
+ set;
+ }
+
+ public float Float {
+ get;
+ set;
+ }
+
+ public double Double {
+ get;
+ set;
+ }
+
+ public decimal Decimal {
+ get;
+ set;
+ }
+
+ public string String {
+ get;
+ set;
+ }
+
+ public object Object {
+ get;
+ set;
+ }
+
+ public dynamic Dynamic {
+ get;
+ set;
+ }
+
+ public int? NInt {
+ get;
+ set;
+ }
+
+ public MyInt My {
+ get;
+ set;
+ }
+
+ public MyInt? NMy {
+ get;
+ set;
+ }
+
+ public static MyInt StaticMy {
+ get;
+ set;
+ }
+
+ public static MyInt? StaticNMy {
+ get;
+ set;
+ }
+ }
+
+ private DeconstructionSource GetSource()
+ {
+ return null;
+ }
+
+ private DeconstructionSource GetSource()
+ {
+ return null;
+ }
+
+ private ref T GetRef()
+ {
+ throw new NotImplementedException();
+ }
+
+ private (T, T2) GetTuple()
+ {
+ return default((T, T2));
+ }
+
+ private (T, T2, T3) GetTuple()
+ {
+ return default((T, T2, T3));
+ }
+
+ private AssignmentTargets Get(int i)
+ {
+ return null;
+ }
+
+ public void LocalVariable_NoConversion_Custom()
+ {
+ var (myInt3, x) = GetSource();
+ Console.WriteLine(myInt3);
+ Console.WriteLine(x);
+ }
+
+ public void LocalVariable_NoConversion_Tuple()
+ {
+ var (myInt, x) = GetTuple();
+ Console.WriteLine(myInt);
+ Console.WriteLine(x);
+ }
+
+ public void LocalVariable_NoConversion_Custom_DiscardFirst()
+ {
+ var (_, x, value) = GetSource();
+ Console.WriteLine(x);
+ Console.WriteLine(value);
+ }
+
+ // currently we detect deconstruction, iff the first element is not discarded
+ //public void LocalVariable_NoConversion_Tuple_DiscardFirst()
+ //{
+ // var (_, x, value) = GetTuple();
+ // Console.WriteLine(x);
+ // Console.WriteLine(value);
+ //}
+
+ public void LocalVariable_NoConversion_Custom_DiscardLast()
+ {
+ var (myInt3, x, _) = GetSource();
+ Console.WriteLine(myInt3);
+ Console.WriteLine(x);
+ }
+
+ public void LocalVariable_NoConversion_Tuple_DiscardLast()
+ {
+ var (myInt, x, _) = GetTuple();
+ Console.WriteLine(myInt);
+ Console.WriteLine(x);
+ }
+
+ public void LocalVariable_NoConversion_Custom_DiscardSecond()
+ {
+ var (myInt3, _, value) = GetSource();
+ Console.WriteLine(myInt3);
+ Console.WriteLine(value);
+ }
+
+ public void LocalVariable_NoConversion_Tuple_DiscardSecond()
+ {
+ var (myInt, _, value) = GetTuple();
+ Console.WriteLine(myInt);
+ Console.WriteLine(value);
+ }
+
+ public void LocalVariable_NoConversion_Custom_ReferenceTypes()
+ {
+ var (value, value2) = GetSource();
+ Console.WriteLine(value);
+ Console.WriteLine(value2);
+ }
+
+ public void LocalVariable_NoConversion_Tuple_ReferenceTypes()
+ {
+ var (value, value2) = GetTuple();
+ Console.WriteLine(value);
+ Console.WriteLine(value2);
+ }
+
+ public void LocalVariable_IntToLongConversion_Custom()
+ {
+ int value;
+ long value2;
+ (value, value2) = GetSource();
+ Console.WriteLine(value);
+ Console.WriteLine(value2);
+ }
+
+ public void LocalVariable_IntToLongConversion_Tuple()
+ {
+ int value;
+ long value2;
+ (value, value2) = GetTuple();
+ Console.WriteLine(value);
+ Console.WriteLine(value2);
+ }
+
+ public void LocalVariable_FloatToDoubleConversion_Custom()
+ {
+ int value;
+ double value2;
+ (value, value2) = GetSource();
+ Console.WriteLine(value);
+ Console.WriteLine(value2);
+ }
+
+ public void LocalVariable_FloatToDoubleConversion_Tuple()
+ {
+ int value;
+ double value2;
+ (value, value2) = GetTuple();
+ Console.WriteLine(value);
+ Console.WriteLine(value2);
+ }
+
+ // dynamic conversion is currently not supported
+ //public void LocalVariable_ImplicitReferenceConversion_Custom()
+ //{
+ // object value;
+ // dynamic value2;
+ // (value, value2) = GetSource();
+ // Console.WriteLine(value);
+ // value2.UseMe();
+ //}
+
+ //public void LocalVariable_ImplicitReferenceConversion_Tuple()
+ //{
+ // object value;
+ // dynamic value2;
+ // (value, value2) = GetTuple();
+ // Console.WriteLine(value);
+ // value2.UseMe();
+ //}
+
+ public void LocalVariable_NoConversion_ComplexValue_Custom()
+ {
+ var (myInt3, x) = new DeconstructionSource {
+ Dummy = 3
+ };
+ Console.WriteLine(myInt3);
+ Console.WriteLine(x);
+ }
+
+ public void Property_NoConversion_Custom()
+ {
+ (Get(0).NMy, Get(1).My) = GetSource();
+ }
+
+ public void Property_IntToLongConversion_Custom()
+ {
+ (Get(0).Int, Get(1).Long) = GetSource();
+ }
+
+ public void Property_FloatToDoubleConversion_Custom()
+ {
+ (Get(0).Int, Get(1).Double) = GetSource();
+ }
+
+ // dynamic conversion is not supported
+ //public void Property_ImplicitReferenceConversion_Custom()
+ //{
+ // (Get(0).Object, Get(1).Dynamic) = GetSource();
+ //}
+
+ public void Property_NoConversion_Custom_DiscardFirst()
+ {
+ (_, Get(1).My) = GetSource();
+ }
+
+ public void Property_NoConversion_Custom_DiscardLast()
+ {
+ (Get(0).NMy, _) = GetSource();
+ }
+
+ public void Property_NoConversion_Tuple()
+ {
+ (Get(0).NMy, Get(1).My) = GetTuple();
+ }
+
+ public void Property_NoConversion_Tuple_DiscardLast()
+ {
+ (Get(0).NMy, Get(1).My, _) = GetTuple();
+ }
+
+ // currently we detect deconstruction, iff the first element is not discarded
+ //public void Property_NoConversion_Tuple_DiscardFirst()
+ //{
+ // (_, Get(1).My, Get(2).Int) = GetTuple();
+ //}
+
+ public void Property_NoConversion_Custom_DiscardSecond()
+ {
+ (Get(0).NMy, _, Get(2).Int) = GetSource();
+ }
+
+ public void Property_NoConversion_Tuple_DiscardSecond()
+ {
+ (Get(0).NMy, _, Get(2).Int) = GetTuple();
+ }
+
+ public void Property_NoConversion_Custom_ReferenceTypes()
+ {
+ (Get(0).String, Get(1).String) = GetSource();
+ }
+
+ public void Property_NoConversion_Tuple_ReferenceTypes()
+ {
+ (Get(0).String, Get(1).String) = GetTuple();
+ }
+
+ public void Property_IntToLongConversion_Tuple()
+ {
+ (Get(0).Int, Get(1).Long) = GetTuple();
+ }
+
+ public void Property_FloatToDoubleConversion_Tuple()
+ {
+ (Get(0).Int, Get(1).Double) = GetTuple();
+ }
+
+ public void RefLocal_NoConversion_Custom(out double a)
+ {
+ (a, GetRef()) = GetSource();
+ }
+
+ public void RefLocal_NoConversion_Tuple(out double a)
+ {
+ (a, GetRef()) = GetTuple();
+ }
+
+ public void RefLocal_FloatToDoubleConversion_Custom(out double a)
+ {
+ (a, GetRef()) = GetSource();
+ }
+
+ public void RefLocal_FloatToDoubleConversion_Custom2(out double a)
+ {
+ (a, GetRef()) = GetSource();
+ }
+
+ public void RefLocal_FloatToDoubleConversion_Tuple(out double a)
+ {
+ (a, GetRef()) = GetTuple();
+ }
+
+ public void RefLocal_NoConversion_Custom(out MyInt? a)
+ {
+ (a, GetRef()) = GetSource();
+ }
+
+ public void RefLocal_IntToLongConversion_Custom(out long a)
+ {
+ (a, GetRef()) = GetSource();
+ }
+
+ // dynamic conversion is not supported
+ //public void RefLocal_ImplicitReferenceConversion_Custom(out object a)
+ //{
+ // (a, GetRef()) = GetSource();
+ //}
+
+ public void RefLocal_NoConversion_Custom_DiscardFirst()
+ {
+ (_, GetRef()) = GetSource();
+ }
+
+ public void RefLocal_NoConversion_Custom_DiscardLast(out MyInt? a)
+ {
+ (a, _) = GetSource();
+ }
+
+ public void RefLocal_NoConversion_Tuple(out MyInt? a)
+ {
+ (a, GetRef()) = GetTuple();
+ }
+
+ public void RefLocal_NoConversion_Tuple_DiscardLast(out MyInt? a)
+ {
+ (a, GetRef(), _) = GetTuple();
+ }
+
+ // currently we detect deconstruction, iff the first element is not discarded
+ //public void RefLocal_NoConversion_Tuple_DiscardFirst(out var a)
+ //{
+ // (_, GetRef(), GetRef()) = GetTuple();
+ //}
+
+ public void RefLocal_NoConversion_Custom_DiscardSecond(out MyInt? a)
+ {
+ (a, _, GetRef()) = GetSource();
+ }
+
+ public void RefLocal_NoConversion_Tuple_DiscardSecond(out MyInt? a)
+ {
+ (a, _, GetRef()) = GetTuple();
+ }
+
+ public void RefLocal_NoConversion_Custom_ReferenceTypes(out string a)
+ {
+ (a, GetRef()) = GetSource();
+ }
+
+ public void RefLocal_NoConversion_Tuple_ReferenceTypes(out string a)
+ {
+ (a, GetRef()) = GetTuple();
+ }
+
+ public void RefLocal_IntToLongConversion_Tuple(out long a)
+ {
+ (a, GetRef()) = GetTuple();
+ }
+
+ //public void ArrayAssign_FloatToDoubleConversion_Custom(double[] arr)
+ //{
+ // (arr[0], arr[1], arr[2]) = GetSource();
+ //}
+
+ public void Field_NoConversion_Custom()
+ {
+ (Get(0).IntField, Get(1).IntField) = GetSource();
+ }
+
+ public void Field_NoConversion_Tuple()
+ {
+ (Get(0).IntField, Get(1).IntField) = GetTuple();
+ }
+
+ public void Field_IntToLongConversion_Custom()
+ {
+ (Get(0).IntField, Get(1).LongField) = GetSource();
+ }
+
+ public void Field_IntToLongConversion_Tuple()
+ {
+ (Get(0).IntField, Get(1).LongField) = GetTuple();
+ }
+
+ public void Field_FloatToDoubleConversion_Custom()
+ {
+ (Get(0).DoubleField, Get(1).DoubleField) = GetSource();
+ }
+
+ public void Field_FloatToDoubleConversion_Tuple()
+ {
+ (Get(0).DoubleField, Get(1).DoubleField) = GetTuple();
+ }
+
+ // dynamic conversion is not supported
+ //public void Field_ImplicitReferenceConversion_Custom()
+ //{
+ // (Get(0).ObjectField, Get(1).DynamicField) = GetSource();
+ //}
+
+ public void Field_NoConversion_Custom_DiscardFirst()
+ {
+ (_, Get(1).MyField) = GetSource();
+ }
+
+ public void Field_NoConversion_Custom_DiscardLast()
+ {
+ (Get(0).NMyField, _) = GetSource();
+ }
+
+ public void Field_NoConversion_Tuple_DiscardLast()
+ {
+ (Get(0).NMyField, Get(1).MyField, _) = GetTuple();
+ }
+
+ // currently we detect deconstruction, iff the first element is not discarded
+ //public void Field_NoConversion_Tuple_DiscardFirst()
+ //{
+ // (_, Get(1).MyField, Get(2).IntField) = GetTuple();
+ //}
+
+ public void Field_NoConversion_Custom_DiscardSecond()
+ {
+ (Get(0).NMyField, _, Get(2).IntField) = GetSource();
+ }
+
+ public void Field_NoConversion_Tuple_DiscardSecond()
+ {
+ (Get(0).NMyField, _, Get(2).IntField) = GetTuple();
+ }
+
+ public void Field_NoConversion_Custom_ReferenceTypes()
+ {
+ (Get(0).StringField, Get(1).StringField) = GetSource();
+ }
+
+ public void Field_NoConversion_Tuple_ReferenceTypes()
+ {
+ (Get(0).StringField, Get(1).StringField) = GetTuple();
+ }
+
+ public void DeconstructDictionaryForEach(Dictionary dictionary)
+ {
+ foreach (var (str, num2) in dictionary) {
+ Console.WriteLine(str + ": " + num2);
+ }
+ }
+
+ public void DeconstructTupleListForEach(List<(string, int)> tuples)
+ {
+ foreach (var (str, num) in tuples) {
+ Console.WriteLine(str + ": " + num);
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 40517bfc8..c09132e42 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -144,6 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformCollectionAndObjectInitializers(),
new TransformExpressionTrees(),
new IndexRangeTransform(),
+ new DeconstructionTransform(),
new NamedArgumentTransform(),
new UserDefinedLogicTransform()
),
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index edbdf911d..189c94744 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -3386,6 +3386,88 @@ namespace ICSharpCode.Decompiler.CSharp
return invocation.WithRR(new ResolveResult(inst.ReturnType)).WithILInstruction(inst);
}
+ protected internal override TranslatedExpression VisitDeconstructInstruction(DeconstructInstruction inst, TranslationContext context)
+ {
+ IType rhsType = inst.Pattern.Variable.Type;
+ var rhs = Translate(inst.Pattern.TestedOperand, rhsType);
+ rhs = rhs.ConvertTo(rhsType, this); // TODO allowImplicitConversion
+ var assignments = inst.Assignments.Instructions;
+ int assignmentPos = 0;
+ var inits = inst.Init;
+ int initPos = 0;
+
+ Dictionary conversionMapping = new Dictionary();
+
+ foreach (var conv in inst.Conversions.Instructions) {
+ if (!DeconstructInstruction.IsConversionStLoc(conv, out var outputVariable, out var inputVariable))
+ continue;
+ conversionMapping.Add(inputVariable, outputVariable);
+ }
+
+
+ var lhs = ConstructTuple(inst.Pattern);
+ return new AssignmentExpression(lhs, rhs)
+ .WithILInstruction(inst)
+ .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Void)));
+
+ TupleExpression ConstructTuple(MatchInstruction matchInstruction)
+ {
+ var expr = new TupleExpression();
+ foreach (var subPattern in matchInstruction.SubPatterns.Cast()) {
+ if (subPattern.IsVar) {
+ if (subPattern.HasDesignator) {
+ if (!conversionMapping.TryGetValue(subPattern.Variable, out ILVariable value)) {
+ value = subPattern.Variable;
+ }
+ expr.Elements.Add(ConstructAssignmentTarget(assignments[assignmentPos], value));
+ assignmentPos++;
+ } else
+ expr.Elements.Add(new IdentifierExpression("_"));
+ } else {
+ expr.Elements.Add(ConstructTuple(subPattern));
+ }
+ }
+ return expr;
+ }
+
+ TranslatedExpression ConstructAssignmentTarget(ILInstruction assignment, ILVariable value)
+ {
+ switch (assignment) {
+ case StLoc stloc:
+ Debug.Assert(stloc.Value.MatchLdLoc(value));
+ break;
+ case CallInstruction call:
+ for (int i = 0; i < call.Arguments.Count - 1; i++) {
+ ReplaceAssignmentTarget(call.Arguments[i]);
+ }
+ Debug.Assert(call.Arguments.Last().MatchLdLoc(value));
+ break;
+ case StObj stobj:
+ var target = stobj.Target;
+ while (target.MatchLdFlda(out var nestedTarget, out _))
+ target = nestedTarget;
+ ReplaceAssignmentTarget(target);
+ Debug.Assert(stobj.Value.MatchLdLoc(value));
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+ var expr = Translate(assignment);
+ return expr.UnwrapChild(((AssignmentExpression)expr).Left);
+ }
+
+ void ReplaceAssignmentTarget(ILInstruction target)
+ {
+ if (target.MatchLdLoc(out var v)
+ && v.Kind == VariableKind.DeconstructionInitTemporary)
+ {
+ Debug.Assert(inits[initPos].Variable == v);
+ target.ReplaceWith(inits[initPos].Value);
+ initPos++;
+ }
+ }
+ }
+
protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context)
{
string message = "Error";
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index 30caec5dc..50295d933 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -791,6 +791,17 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(directionExpression);
}
+ public virtual void VisitDeclarationExpression(DeclarationExpression declarationExpression)
+ {
+ StartNode(declarationExpression);
+
+ declarationExpression.Type.AcceptVisitor(this);
+ Space();
+ declarationExpression.Designation.AcceptVisitor(this);
+
+ EndNode(declarationExpression);
+ }
+
public virtual void VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression)
{
StartNode(outVarDeclarationExpression);
@@ -1551,7 +1562,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
Space(policy.SpacesWithinForeachParentheses);
foreachStatement.VariableType.AcceptVisitor(this);
Space();
- WriteIdentifier(foreachStatement.VariableNameToken);
+ foreachStatement.VariableDesignation.AcceptVisitor(this);
+ Space();
WriteKeyword(ForeachStatement.InKeywordRole);
Space();
foreachStatement.InExpression.AcceptVisitor(this);
@@ -2410,6 +2422,22 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(primitiveType);
}
+ public virtual void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation)
+ {
+ StartNode(singleVariableDesignation);
+ writer.WriteIdentifier(singleVariableDesignation.IdentifierToken);
+ EndNode(singleVariableDesignation);
+ }
+
+ public virtual void VisitParenthesizedVariableDesignation(ParenthesizedVariableDesignation parenthesizedVariableDesignation)
+ {
+ StartNode(parenthesizedVariableDesignation);
+ LPar();
+ WriteCommaSeparatedList(parenthesizedVariableDesignation.VariableDesignations);
+ RPar();
+ EndNode(parenthesizedVariableDesignation);
+ }
+
public virtual void VisitComment(Comment comment)
{
writer.StartNode(comment);
diff --git a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs
index 7993932c9..072424663 100644
--- a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs
@@ -269,7 +269,7 @@ namespace ICSharpCode.Decompiler.CSharp
StartSequencePoint(foreachStatement);
AddToSequencePoint(foreachInfo.GetCurrentCall);
- EndSequencePoint(foreachStatement.VariableType.StartLocation, foreachStatement.VariableNameToken.EndLocation);
+ EndSequencePoint(foreachStatement.VariableType.StartLocation, foreachStatement.VariableDesignation.EndLocation);
VisitAsSequencePoint(foreachStatement.EmbeddedStatement);
}
diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
index b1d6dd1eb..d9a461566 100644
--- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
@@ -504,12 +504,9 @@ namespace ICSharpCode.Decompiler.CSharp
if (enumeratorVar2 != enumeratorVar)
return null;
// Detect which foreach-variable transformation is necessary/possible.
- var transformation = DetectGetCurrentTransformation(container, body, enumeratorVar, conditionInst, out var singleGetter, out var foreachVariable);
+ var transformation = DetectGetCurrentTransformation(container, body, loopContainer, enumeratorVar, conditionInst, out var singleGetter, out var foreachVariable);
if (transformation == RequiredGetCurrentTransformation.NoForeach)
return null;
- // The existing foreach variable, if found, can only be used in the loop container.
- if (foreachVariable != null && !(foreachVariable.CaptureScope == null || foreachVariable.CaptureScope == loopContainer))
- return null;
// Extract in-expression
var collectionExpr = m.Get("collection").Single();
// Special case: foreach (var item in this) is decompiled as foreach (var item in base)
@@ -546,6 +543,8 @@ namespace ICSharpCode.Decompiler.CSharp
break;
}
+ VariableDesignation designation = null;
+
// Handle the required foreach-variable transformation:
switch (transformation) {
case RequiredGetCurrentTransformation.UseExistingVariable:
@@ -575,7 +574,18 @@ namespace ICSharpCode.Decompiler.CSharp
body.Instructions.Insert(0, new StLoc(localCopyVariable, new LdLoc(foreachVariable)));
body.Instructions.Insert(0, new StLoc(foreachVariable, instToReplace));
break;
+ case RequiredGetCurrentTransformation.Deconstruction:
+ useVar = true;
+ designation = TranslateDeconstructionDesignation((DeconstructInstruction)body.Instructions[0], isForeach: true);
+ break;
+ }
+
+ if (designation == null) {
+ designation = new SingleVariableDesignation { Identifier = foreachVariable.Name };
+ // Add the variable annotation for highlighting
+ designation.AddAnnotation(new ILVariableResolveResult(foreachVariable, foreachVariable.Type));
}
+
// Convert the modified body to C# AST:
var whileLoop = (WhileStatement)ConvertAsBlock(container).First();
BlockStatement foreachBody = (BlockStatement)whileLoop.EmbeddedStatement.Detach();
@@ -595,12 +605,10 @@ namespace ICSharpCode.Decompiler.CSharp
// Construct the foreach loop.
var foreachStmt = new ForeachStatement {
VariableType = useVar ? new SimpleType("var") : exprBuilder.ConvertType(foreachVariable.Type),
- VariableName = foreachVariable.Name,
+ VariableDesignation = designation,
InExpression = collectionExpr.Detach(),
EmbeddedStatement = foreachBody
};
- // Add the variable annotation for highlighting (TokenTextWriter expects it directly on the ForeachStatement).
- foreachStmt.AddAnnotation(new ILVariableResolveResult(foreachVariable, foreachVariable.Type));
foreachStmt.AddAnnotation(new ForeachAnnotation(inst.ResourceExpression, conditionInst, singleGetter));
foreachStmt.CopyAnnotationsFrom(whileLoop);
// If there was an optional return statement, return it as well.
@@ -615,6 +623,38 @@ namespace ICSharpCode.Decompiler.CSharp
return foreachStmt;
}
+ internal static VariableDesignation TranslateDeconstructionDesignation(DeconstructInstruction inst, bool isForeach)
+ {
+ var assignments = inst.Assignments.Instructions;
+ int assignmentPos = 0;
+
+ return ConstructDesignation(inst.Pattern);
+
+ VariableDesignation ConstructDesignation(MatchInstruction matchInstruction)
+ {
+ var designations = new ParenthesizedVariableDesignation();
+ foreach (var subPattern in matchInstruction.SubPatterns.Cast()) {
+ if (subPattern.IsVar) {
+ var designation = new SingleVariableDesignation();
+ if (subPattern.HasDesignator) {
+ ILVariable v = ((StLoc)assignments[assignmentPos]).Variable;
+ if (isForeach)
+ v.Kind = VariableKind.ForeachLocal;
+ designation.Identifier = v.Name;
+ designation.AddAnnotation(new ILVariableResolveResult(v));
+ assignmentPos++;
+ } else {
+ designation.Identifier = "_";
+ }
+ designations.VariableDesignations.Add(designation);
+ } else {
+ designations.VariableDesignations.Add(ConstructDesignation(subPattern));
+ }
+ }
+ return designations;
+ }
+ }
+
static bool EqualErasedType(IType a, IType b)
{
return NormalizeTypeVisitor.TypeErasure.EquivalentTypes(a, b);
@@ -703,7 +743,12 @@ namespace ICSharpCode.Decompiler.CSharp
/// ... (ldloca copy) ...
///
///
- IntroduceNewVariableAndLocalCopy
+ IntroduceNewVariableAndLocalCopy,
+ ///
+ /// call get_Current() is the tested operand of a deconstruct instruction.
+ /// and the deconstruct instruction is the first statement in the loop body.
+ ///
+ Deconstruction,
}
///
@@ -716,11 +761,14 @@ namespace ICSharpCode.Decompiler.CSharp
/// Returns the call instruction invoking Current's getter.
/// Returns the the foreach variable, if a suitable was found. This variable is only assigned once and its assignment is the first statement in .
/// for details.
- RequiredGetCurrentTransformation DetectGetCurrentTransformation(BlockContainer usingContainer, Block loopBody, ILVariable enumerator, ILInstruction moveNextUsage, out CallInstruction singleGetter, out ILVariable foreachVariable)
+ RequiredGetCurrentTransformation DetectGetCurrentTransformation(BlockContainer usingContainer, Block loopBody, BlockContainer loopContainer, ILVariable enumerator, ILInstruction moveNextUsage, out CallInstruction singleGetter, out ILVariable foreachVariable)
{
singleGetter = null;
foreachVariable = null;
- var loads = (enumerator.LoadInstructions.OfType().Concat(enumerator.AddressInstructions.OfType())).Where(ld => !ld.IsDescendantOf(moveNextUsage)).ToArray();
+ var loads = enumerator.LoadInstructions.OfType()
+ .Concat(enumerator.AddressInstructions.OfType())
+ .Where(ld => !ld.IsDescendantOf(moveNextUsage))
+ .ToArray();
// enumerator is used in multiple locations or not in conjunction with get_Current
// => no foreach
if (loads.Length != 1 || !ParentIsCurrentGetter(loads[0]))
@@ -728,8 +776,17 @@ namespace ICSharpCode.Decompiler.CSharp
singleGetter = (CallInstruction)loads[0].Parent;
// singleGetter is not part of the first instruction in body or cannot be uninlined
// => no foreach
- if (!(singleGetter.IsDescendantOf(loopBody.Instructions[0]) && ILInlining.CanUninline(singleGetter, loopBody.Instructions[0])))
+ if (!(singleGetter.IsDescendantOf(loopBody.Instructions[0])
+ && ILInlining.CanUninline(singleGetter, loopBody.Instructions[0])))
+ {
return RequiredGetCurrentTransformation.NoForeach;
+ }
+ if (loopBody.Instructions[0] is DeconstructInstruction deconstruction
+ && singleGetter == deconstruction.Pattern.TestedOperand
+ && CanBeDeconstructedInForeach(deconstruction, usingContainer, loopContainer))
+ {
+ return RequiredGetCurrentTransformation.Deconstruction;
+ }
ILInstruction inst = singleGetter;
// in some cases, i.e. foreach variable with explicit type different from the collection-item-type,
// the result of call get_Current is casted.
@@ -738,7 +795,7 @@ namespace ICSharpCode.Decompiler.CSharp
// One variable was found.
if (inst.Parent is StLoc stloc && (stloc.Variable.Kind == VariableKind.Local || stloc.Variable.Kind == VariableKind.StackSlot)) {
// Must be a plain assignment expression and variable must only be used in 'body' + only assigned once.
- if (stloc.Parent == loopBody && VariableIsOnlyUsedInBlock(stloc, usingContainer)) {
+ if (stloc.Parent == loopBody && VariableIsOnlyUsedInBlock(stloc, usingContainer, loopContainer)) {
foreachVariable = stloc.Variable;
return RequiredGetCurrentTransformation.UseExistingVariable;
}
@@ -752,13 +809,42 @@ namespace ICSharpCode.Decompiler.CSharp
return RequiredGetCurrentTransformation.IntroduceNewVariable;
}
+ bool CanBeDeconstructedInForeach(DeconstructInstruction deconstruction, BlockContainer usingContainer, BlockContainer loopContainer)
+ {
+ if (deconstruction.Init.Count > 0)
+ return false;
+ if (deconstruction.Conversions.Instructions.Count > 0)
+ return false;
+ var operandType = deconstruction.Pattern.TestedOperand.InferType(this.typeSystem);
+ var expectedType = deconstruction.Pattern.Variable.Type;
+ if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(operandType, expectedType))
+ return false;
+ var usedVariables = new HashSet(ILVariableEqualityComparer.Instance);
+ foreach (var item in deconstruction.Assignments.Instructions) {
+ if (!item.MatchStLoc(out var v, out var value))
+ return false;
+ expectedType = ((LdLoc)value).Variable.Type;
+ if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(v.Type, expectedType))
+ return false;
+ if (!(v.Kind == VariableKind.StackSlot || v.Kind == VariableKind.Local))
+ return false;
+ if (!VariableIsOnlyUsedInBlock((StLoc)item, usingContainer, loopContainer))
+ return false;
+ if (!(v.CaptureScope == null || v.CaptureScope == usingContainer))
+ return false;
+ if (!usedVariables.Add(v))
+ return false;
+ }
+ return true;
+ }
+
///
/// Determines whether storeInst.Variable is only assigned once and used only inside .
/// Loads by reference (ldloca) are only allowed in the context of this pointer in call instructions,
/// or as target of ldobj.
/// (This only applies to value types.)
///
- bool VariableIsOnlyUsedInBlock(StLoc storeInst, BlockContainer usingContainer)
+ bool VariableIsOnlyUsedInBlock(StLoc storeInst, BlockContainer usingContainer, BlockContainer loopContainer)
{
if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(usingContainer)))
return false;
@@ -766,6 +852,8 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (storeInst.Variable.StoreInstructions.OfType().Any(st => st != storeInst))
return false;
+ if (!(storeInst.Variable.CaptureScope == null || storeInst.Variable.CaptureScope == loopContainer))
+ return false;
return true;
bool AddressUseAllowed(LdLoca la)
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
index f543cf611..610fabbe3 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
@@ -501,9 +501,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
VisitChildren (objectCreateExpression);
}
- public virtual void VisitOutVarDeclarationExpression (OutVarDeclarationExpression outVarDeclarationExpression)
+ public virtual void VisitDeclarationExpression(DeclarationExpression declarationExpression)
{
- VisitChildren (outVarDeclarationExpression);
+ VisitChildren(declarationExpression);
+ }
+
+ public virtual void VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression)
+ {
+ VisitChildren(outVarDeclarationExpression);
}
public virtual void VisitAnonymousTypeCreateExpression(AnonymousTypeCreateExpression anonymousTypeCreateExpression)
@@ -666,6 +671,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
VisitChildren (namedExpression);
}
+ public virtual void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation)
+ {
+ VisitChildren(singleVariableDesignation);
+ }
+
+ public virtual void VisitParenthesizedVariableDesignation(ParenthesizedVariableDesignation parenthesizedVariableDesignation)
+ {
+ VisitChildren(parenthesizedVariableDesignation);
+ }
+
public virtual void VisitErrorNode(AstNode errorNode)
{
VisitChildren(errorNode);
@@ -1153,7 +1168,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren (objectCreateExpression);
}
- public virtual T VisitOutVarDeclarationExpression (OutVarDeclarationExpression outVarDeclarationExpression)
+ public virtual T VisitDeclarationExpression(DeclarationExpression declarationExpression)
+ {
+ return VisitChildren(declarationExpression);
+ }
+
+ public virtual T VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression)
{
return VisitChildren(outVarDeclarationExpression);
}
@@ -1318,6 +1338,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren (namedExpression);
}
+ public virtual T VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation)
+ {
+ return VisitChildren(singleVariableDesignation);
+ }
+
+ public virtual T VisitParenthesizedVariableDesignation(ParenthesizedVariableDesignation parenthesizedVariableDesignation)
+ {
+ return VisitChildren(parenthesizedVariableDesignation);
+ }
+
public virtual T VisitErrorNode(AstNode errorNode)
{
return VisitChildren(errorNode);
@@ -1805,9 +1835,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren (objectCreateExpression, data);
}
+ public virtual S VisitDeclarationExpression(DeclarationExpression declarationExpression, T data)
+ {
+ return VisitChildren(declarationExpression, data);
+ }
+
public virtual S VisitOutVarDeclarationExpression(OutVarDeclarationExpression outVarDeclarationExpression, T data)
{
- return VisitChildren (outVarDeclarationExpression, data);
+ return VisitChildren(outVarDeclarationExpression, data);
}
public virtual S VisitAnonymousTypeCreateExpression(AnonymousTypeCreateExpression anonymousTypeCreateExpression, T data)
@@ -1970,6 +2005,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren (namedExpression, data);
}
+ public virtual S VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation, T data)
+ {
+ return VisitChildren(singleVariableDesignation, data);
+ }
+
+ public virtual S VisitParenthesizedVariableDesignation(ParenthesizedVariableDesignation parenthesizedVariableDesignation, T data)
+ {
+ return VisitChildren(parenthesizedVariableDesignation, data);
+ }
+
public virtual S VisitErrorNode(AstNode errorNode, T data)
{
return VisitChildren(errorNode, data);
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs
new file mode 100644
index 000000000..0e8585233
--- /dev/null
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs
@@ -0,0 +1,58 @@
+// Copyright (c) 2020 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
+
+namespace ICSharpCode.Decompiler.CSharp.Syntax
+{
+ ///
+ /// TypeName VariableDesignation
+ ///
+ public class DeclarationExpression : Expression
+ {
+ public AstType Type {
+ get { return GetChildByRole(Roles.Type); }
+ set { SetChildByRole(Roles.Type, value); }
+ }
+
+ public VariableDesignation Designation {
+ get { return GetChildByRole(Roles.VariableDesignationRole); }
+ set { SetChildByRole(Roles.VariableDesignationRole, value); }
+ }
+
+ public override void AcceptVisitor(IAstVisitor visitor)
+ {
+ visitor.VisitDeclarationExpression(this);
+ }
+
+ public override T AcceptVisitor(IAstVisitor visitor)
+ {
+ return visitor.VisitDeclarationExpression(this);
+ }
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
+ {
+ return visitor.VisitDeclarationExpression(this, data);
+ }
+
+ protected internal override bool DoMatch(AstNode other, Match match)
+ {
+ return other is DeclarationExpression o && Designation.DoMatch(o.Designation, match);
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs
index fd3690ed2..0c5839886 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs
@@ -42,10 +42,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
SetChildByRole(Roles.Identifier, Decompiler.CSharp.Syntax.Identifier.Create (identifier, location));
}
-// public Identifier IdentifierToken {
-// get { return GetChildByRole (Roles.Identifier); }
-// }
-
public string Identifier {
get {
return GetChildByRole (Roles.Identifier).Name;
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
index ef73b991d..701041316 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
@@ -35,6 +35,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
void VisitCastExpression(CastExpression castExpression);
void VisitCheckedExpression(CheckedExpression checkedExpression);
void VisitConditionalExpression(ConditionalExpression conditionalExpression);
+ void VisitDeclarationExpression(DeclarationExpression declarationExpression);
void VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression);
void VisitDirectionExpression(DirectionExpression directionExpression);
void VisitIdentifierExpression(IdentifierExpression identifierExpression);
@@ -155,7 +156,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
void VisitInterpolation(Interpolation interpolation);
void VisitInterpolatedStringText(InterpolatedStringText interpolatedStringText);
-
+
+ void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation);
+ void VisitParenthesizedVariableDesignation(ParenthesizedVariableDesignation parenthesizedVariableDesignation);
+
void VisitNullNode(AstNode nullNode);
void VisitErrorNode(AstNode errorNode);
void VisitPatternPlaceholder(AstNode placeholder, PatternMatching.Pattern pattern);
@@ -177,6 +181,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitCastExpression(CastExpression castExpression);
S VisitCheckedExpression(CheckedExpression checkedExpression);
S VisitConditionalExpression(ConditionalExpression conditionalExpression);
+ S VisitDeclarationExpression(DeclarationExpression declarationExpression);
S VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression);
S VisitDirectionExpression(DirectionExpression directionExpression);
S VisitIdentifierExpression(IdentifierExpression identifierExpression);
@@ -298,6 +303,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitInterpolation(Interpolation interpolation);
S VisitInterpolatedStringText(InterpolatedStringText interpolatedStringText);
+ S VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation);
+ S VisitParenthesizedVariableDesignation(ParenthesizedVariableDesignation parenthesizedVariableDesignation);
+
S VisitNullNode(AstNode nullNode);
S VisitErrorNode(AstNode errorNode);
S VisitPatternPlaceholder(AstNode placeholder, PatternMatching.Pattern pattern);
@@ -319,6 +327,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitCastExpression(CastExpression castExpression, T data);
S VisitCheckedExpression(CheckedExpression checkedExpression, T data);
S VisitConditionalExpression(ConditionalExpression conditionalExpression, T data);
+ S VisitDeclarationExpression(DeclarationExpression declarationExpression, T data);
S VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression, T data);
S VisitDirectionExpression(DirectionExpression directionExpression, T data);
S VisitIdentifierExpression(IdentifierExpression identifierExpression, T data);
@@ -440,6 +449,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitInterpolation(Interpolation interpolation, T data);
S VisitInterpolatedStringText(InterpolatedStringText interpolatedStringText, T data);
+ S VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation, T data);
+ S VisitParenthesizedVariableDesignation(ParenthesizedVariableDesignation parenthesizedVariableDesignation, T data);
+
S VisitNullNode(AstNode nullNode, T data);
S VisitErrorNode(AstNode errorNode, T data);
S VisitPatternPlaceholder(AstNode placeholder, PatternMatching.Pattern pattern, T data);
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
index 872f977aa..b45935814 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
@@ -46,11 +46,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public static readonly Role Variable = new Role ("Variable", VariableInitializer.Null);
public static readonly Role EmbeddedStatement = new Role ("EmbeddedStatement", Statement.Null);
public readonly static Role TypeMemberRole = new Role ("TypeMember");
-
+
+ public static readonly Role VariableDesignationRole = new Role("VariableDesignation", VariableDesignation.Null);
// public static readonly TokenRole Keyword = new TokenRole ("Keyword", CSharpTokenNode.Null);
-// public static readonly TokenRole InKeyword = new TokenRole ("InKeyword", CSharpTokenNode.Null);
-
+ // public static readonly TokenRole InKeyword = new TokenRole ("InKeyword", CSharpTokenNode.Null);
+
// some pre defined constants for most used punctuation
public static readonly TokenRole LPar = new TokenRole ("(");
public static readonly TokenRole RPar = new TokenRole (")");
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/ForeachStatement.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/ForeachStatement.cs
index 4c1d037c2..d2bd3f3d3 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/ForeachStatement.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/ForeachStatement.cs
@@ -47,23 +47,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
get { return GetChildByRole (Roles.Type); }
set { SetChildByRole (Roles.Type, value); }
}
-
- public string VariableName {
- get {
- return GetChildByRole (Roles.Identifier).Name;
- }
- set {
- SetChildByRole(Roles.Identifier, Identifier.Create (value));
- }
- }
-
- public Identifier VariableNameToken {
- get {
- return GetChildByRole (Roles.Identifier);
- }
- set {
- SetChildByRole(Roles.Identifier, value);
- }
+
+ public VariableDesignation VariableDesignation {
+ get { return GetChildByRole(Roles.VariableDesignationRole); }
+ set { SetChildByRole(Roles.VariableDesignationRole, value); }
}
public CSharpTokenNode InToken {
@@ -102,7 +89,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
ForeachStatement o = other as ForeachStatement;
- return o != null && this.VariableType.DoMatch(o.VariableType, match) && MatchString(this.VariableName, o.VariableName)
+ return o != null && this.VariableType.DoMatch(o.VariableType, match) && this.VariableDesignation.DoMatch(o.VariableDesignation, match)
&& this.InExpression.DoMatch(o.InExpression, match) && this.EmbeddedStatement.DoMatch(o.EmbeddedStatement, match);
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/VariableDesignation.cs b/ICSharpCode.Decompiler/CSharp/Syntax/VariableDesignation.cs
new file mode 100644
index 000000000..8d7991dec
--- /dev/null
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/VariableDesignation.cs
@@ -0,0 +1,137 @@
+// Copyright (c) 2020 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
+
+namespace ICSharpCode.Decompiler.CSharp.Syntax
+{
+ public abstract class VariableDesignation : AstNode
+ {
+ public override NodeType NodeType => NodeType.Unknown;
+
+ #region Null
+ public new static readonly VariableDesignation Null = new NullVariableDesignation();
+
+ sealed class NullVariableDesignation : VariableDesignation
+ {
+ public override bool IsNull {
+ get {
+ return true;
+ }
+ }
+
+ public override void AcceptVisitor(IAstVisitor visitor)
+ {
+ visitor.VisitNullNode(this);
+ }
+
+ public override T AcceptVisitor(IAstVisitor visitor)
+ {
+ return visitor.VisitNullNode(this);
+ }
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
+ {
+ return visitor.VisitNullNode(this, data);
+ }
+
+ protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
+ {
+ return other == null || other.IsNull;
+ }
+ }
+ #endregion
+
+ }
+
+ ///
+ /// Identifier
+ ///
+ public class SingleVariableDesignation : VariableDesignation
+ {
+
+ public string Identifier {
+ get { return GetChildByRole(Roles.Identifier).Name; }
+ set { SetChildByRole(Roles.Identifier, Syntax.Identifier.Create(value)); }
+ }
+
+ public Identifier IdentifierToken {
+ get { return GetChildByRole(Roles.Identifier); }
+ set { SetChildByRole(Roles.Identifier, value); }
+ }
+
+ public override void AcceptVisitor(IAstVisitor visitor)
+ {
+ visitor.VisitSingleVariableDesignation(this);
+ }
+
+ public override T AcceptVisitor(IAstVisitor visitor)
+ {
+ return visitor.VisitSingleVariableDesignation(this);
+ }
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
+ {
+ return visitor.VisitSingleVariableDesignation(this, data);
+ }
+
+ protected internal override bool DoMatch(AstNode other, Match match)
+ {
+ return other is SingleVariableDesignation o && MatchString(this.Identifier, o.Identifier);
+ }
+ }
+
+ ///
+ /// ( VariableDesignation (, VariableDesignation)* )
+ ///
+ public class ParenthesizedVariableDesignation : VariableDesignation
+ {
+
+ public CSharpTokenNode LParToken {
+ get { return GetChildByRole(Roles.LPar); }
+ }
+
+ public AstNodeCollection VariableDesignations {
+ get { return GetChildrenByRole(Roles.VariableDesignationRole); }
+ }
+
+ public CSharpTokenNode RParToken {
+ get { return GetChildByRole(Roles.RPar); }
+ }
+
+ public override void AcceptVisitor(IAstVisitor visitor)
+ {
+ visitor.VisitParenthesizedVariableDesignation(this);
+ }
+
+ public override T AcceptVisitor(IAstVisitor visitor)
+ {
+ return visitor.VisitParenthesizedVariableDesignation(this);
+ }
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
+ {
+ return visitor.VisitParenthesizedVariableDesignation(this, data);
+ }
+
+ protected internal override bool DoMatch(AstNode other, Match match)
+ {
+ return other is ParenthesizedVariableDesignation o && VariableDesignations.DoMatch(o.VariableDesignations, match);
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
index ba79f6709..b7f1932c9 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
@@ -102,6 +102,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public VariableToDeclare ReplacementDueToCollision;
public bool InvolvedInCollision;
public bool RemovedDueToCollision => ReplacementDueToCollision != null;
+ public bool DeclaredInDeconstruction;
public VariableToDeclare(ILVariable variable, bool defaultInitialization, InsertionPoint insertionPoint, IdentifierExpression firstUse, int sourceOrder)
{
@@ -126,6 +127,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
EnsureExpressionStatementsAreValid(rootNode);
FindInsertionPoints(rootNode, 0);
ResolveCollisions();
+ InsertDeconstructionVariableDeclarations();
InsertVariableDeclarations(context);
UpdateAnnotations(rootNode);
} finally {
@@ -133,7 +135,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
variableDict.Clear();
}
}
-
///
/// Analyze the input AST (containing undeclared variables)
/// for where those variables would be declared by this transform.
@@ -425,6 +426,44 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
}
+ private void InsertDeconstructionVariableDeclarations()
+ {
+ var usedVariables = new HashSet();
+ foreach (var g in variableDict.Values.GroupBy(v => v.InsertionPoint.nextNode)) {
+ if (!(g.Key is ExpressionStatement { Expression: AssignmentExpression { Left: TupleExpression left, Operator: AssignmentOperatorType.Assign } assignment }))
+ continue;
+ usedVariables.Clear();
+ var deconstruct = assignment.Annotation();
+ if (deconstruct == null || deconstruct.Init.Count > 0 || deconstruct.Conversions.Instructions.Count > 0)
+ continue;
+ if (!deconstruct.Assignments.Instructions.All(IsDeclarableVariable))
+ continue;
+
+ var designation = StatementBuilder.TranslateDeconstructionDesignation(deconstruct, isForeach: false);
+ left.ReplaceWith(new DeclarationExpression { Type = new SimpleType("var"), Designation = designation });
+
+ foreach (var v in usedVariables) {
+ variableDict[v].DeclaredInDeconstruction = true;
+ }
+
+ bool IsDeclarableVariable(ILInstruction inst)
+ {
+ if (!inst.MatchStLoc(out var v, out var value))
+ return false;
+ if (!g.Any(vd => vd.ILVariable == v && !vd.RemovedDueToCollision))
+ return false;
+ if (!usedVariables.Add(v))
+ return false;
+ var expectedType = ((LdLoc)value).Variable.Type;
+ if (!v.Type.Equals(expectedType))
+ return false;
+ if (!(v.Kind == VariableKind.StackSlot || v.Kind == VariableKind.Local))
+ return false;
+ return true;
+ }
+ }
+ }
+
bool IsMatchingAssignment(VariableToDeclare v, out AssignmentExpression assignment)
{
assignment = v.InsertionPoint.nextNode as AssignmentExpression;
@@ -454,7 +493,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{
var replacements = new List<(AstNode, AstNode)>();
foreach (var (ilVariable, v) in variableDict) {
- if (v.RemovedDueToCollision)
+ if (v.RemovedDueToCollision || v.DeclaredInDeconstruction)
continue;
if (CombineDeclarationAndInitializer(v, context) && IsMatchingAssignment(v, out AssignmentExpression assignment)) {
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
index 899934023..c65d02d75 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
@@ -337,14 +337,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
body.Statements.Add(statement.Detach());
var foreachStmt = new ForeachStatement {
VariableType = context.Settings.AnonymousTypes && itemVariable.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(itemVariable.Type),
- VariableName = itemVariable.Name,
+ VariableDesignation = new SingleVariableDesignation { Identifier = itemVariable.Name },
InExpression = m.Get("arrayVariable").Single().Detach(),
EmbeddedStatement = body
};
foreachStmt.CopyAnnotationsFrom(forStatement);
itemVariable.Kind = IL.VariableKind.ForeachLocal;
// Add the variable annotation for highlighting (TokenTextWriter expects it directly on the ForeachStatement).
- foreachStmt.AddAnnotation(new ILVariableResolveResult(itemVariable, itemVariable.Type));
+ foreachStmt.VariableDesignation.AddAnnotation(new ILVariableResolveResult(itemVariable, itemVariable.Type));
// TODO : add ForeachAnnotation
forStatement.ReplaceWith(foreachStmt);
return foreachStmt;
@@ -495,7 +495,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
body.Statements.Add(statement.Detach());
var foreachStmt = new ForeachStatement {
VariableType = context.Settings.AnonymousTypes && itemVariable.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(itemVariable.Type),
- VariableName = itemVariable.Name,
+ VariableDesignation = new SingleVariableDesignation { Identifier = itemVariable.Name },
InExpression = m.Get("collection").Single().Detach(),
EmbeddedStatement = body
};
@@ -504,7 +504,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
//foreachStmt.CopyAnnotationsFrom(forStatement);
itemVariable.Kind = IL.VariableKind.ForeachLocal;
// Add the variable annotation for highlighting (TokenTextWriter expects it directly on the ForeachStatement).
- foreachStmt.AddAnnotation(new ILVariableResolveResult(itemVariable, itemVariable.Type));
+ foreachStmt.VariableDesignation.AddAnnotation(new ILVariableResolveResult(itemVariable, itemVariable.Type));
// TODO : add ForeachAnnotation
expressionStatement.ReplaceWith(foreachStmt);
return foreachStmt;
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index df41aa008..fa80261cd 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -93,6 +93,7 @@ namespace ICSharpCode.Decompiler
tupleConversions = false;
discards = false;
localFunctions = false;
+ deconstruction = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7_2) {
introduceReadonlyAndInModifiers = false;
@@ -1162,6 +1163,23 @@ namespace ICSharpCode.Decompiler
}
}
+ bool deconstruction = true;
+
+ ///
+ /// Gets/Sets whether C# 7.0 deconstruction should be detected.
+ ///
+ [Category("C# 7.0 / VS 2017")]
+ [Description("DecompilerSettings.Deconstruction")]
+ public bool Deconstruction {
+ get { return deconstruction; }
+ set {
+ if (deconstruction != value) {
+ deconstruction = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
bool staticLocalFunctions = true;
///
@@ -1182,7 +1200,7 @@ namespace ICSharpCode.Decompiler
bool ranges = true;
///
- /// Gets/Sets whether C# 8.0 static local functions should be transformed.
+ /// Gets/Sets whether C# 8.0 index and range syntax should be used.
///
[Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.Ranges")]
diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
index a43ac19e6..ed5d01918 100644
--- a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
+++ b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
@@ -646,6 +646,8 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
} else {
return (state, bottomState.Clone());
}
+ } else if (inst is MatchInstruction match) {
+ return EvaluateMatch(match);
} else {
// other kind of condition
inst.AcceptVisitor(this);
@@ -653,6 +655,45 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
}
}
+ protected internal override void VisitMatchInstruction(MatchInstruction inst)
+ {
+ var (onTrue, onFalse) = EvaluateMatch(inst);
+ state = onTrue;
+ state.JoinWith(onFalse);
+ }
+
+ ///
+ /// Evaluates a match instruction.
+ ///
+ ///
+ /// A pair of:
+ /// * The state after the pattern matches
+ /// * The state after the pattern fails to match
+ ///
+ ///
+ /// this.state is invalid after this function was called, and must be overwritten
+ /// with one of the return values.
+ ///
+ (State OnTrue, State OnFalse) EvaluateMatch(MatchInstruction inst)
+ {
+ DebugStartPoint(inst);
+ inst.TestedOperand.AcceptVisitor(this);
+ State onFalse = state.Clone();
+ if (!inst.CheckNotNull && !inst.CheckType) {
+ onFalse.ReplaceWithBottom();
+ }
+ HandleMatchStore(inst);
+ foreach (var subPattern in inst.SubPatterns) {
+ var (subTrue, subFalse) = EvaluateCondition(subPattern);
+ onFalse.JoinWith(subFalse);
+ state = subTrue;
+ }
+ DebugEndPoint(inst);
+ return (state, onFalse);
+ }
+
+ protected abstract void HandleMatchStore(MatchInstruction inst);
+
protected internal override void VisitNullCoalescingInstruction(NullCoalescingInstruction inst)
{
HandleBinaryWithOptionalEvaluation(inst, inst.ValueInst, inst.FallbackInst);
diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
index 5b465b735..726cef1f7 100644
--- a/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
+++ b/ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
@@ -168,7 +168,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
inst.Value.AcceptVisitor(this);
HandleStore(inst.Variable);
}
-
+
+ protected override void HandleMatchStore(MatchInstruction inst)
+ {
+ HandleStore(inst.Variable);
+ }
+
protected override void BeginTryCatchHandler(TryCatchHandler inst)
{
HandleStore(inst.Variable);
diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs
index 8b6d22a24..08bf2e4f2 100644
--- a/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs
+++ b/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs
@@ -380,7 +380,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
inst.Value.AcceptVisitor(this);
HandleStore(inst, inst.Variable);
}
-
+
+ protected override void HandleMatchStore(MatchInstruction inst)
+ {
+ HandleStore(inst, inst.Variable);
+ }
+
protected override void BeginTryCatchHandler(TryCatchHandler inst)
{
base.BeginTryCatchHandler(inst);
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index e10626114..b8ae5a34d 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -70,9 +70,15 @@
+
+
+
+
+
+
@@ -634,13 +640,25 @@
+
+
+
powershell -NoProfile -ExecutionPolicy Bypass -File BuildTools/update-assemblyinfo.ps1 $(Configuration)
+ git rev-parse HEAD^^{commit}
pwsh -NoProfile -ExecutionPolicy Bypass -File BuildTools/update-assemblyinfo.ps1 $(Configuration)
+ git rev-parse HEAD^{commit}
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs
index 607f0b47b..fb833467d 100644
--- a/ICSharpCode.Decompiler/IL/ILVariable.cs
+++ b/ICSharpCode.Decompiler/IL/ILVariable.cs
@@ -75,6 +75,14 @@ namespace ICSharpCode.Decompiler.IL
/// Local variable that holds the display class used for lambdas within this function.
///
DisplayClassLocal,
+ ///
+ /// Local variable declared within a pattern match.
+ ///
+ PatternLocal,
+ ///
+ /// Temporary variable declared in a deconstruction init section.
+ ///
+ DeconstructionInitTemporary,
}
static class VariableKindExtensions
@@ -433,6 +441,12 @@ namespace ICSharpCode.Decompiler.IL
case VariableKind.DisplayClassLocal:
output.Write("display_class local ");
break;
+ case VariableKind.PatternLocal:
+ output.Write("pattern local ");
+ break;
+ case VariableKind.DeconstructionInitTemporary:
+ output.Write("deconstruction init temporary ");
+ break;
default:
throw new ArgumentOutOfRangeException();
}
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index a292afb07..66af1937e 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -220,6 +220,8 @@ namespace ICSharpCode.Decompiler.IL
DynamicInvokeInstruction,
/// ILAst representation of a call to the Binder.IsEvent method inside a dynamic expression.
DynamicIsEventInstruction,
+ /// ILAst representation of C# patterns
+ MatchInstruction,
/// Push a typed reference of type class onto the stack.
MakeRefAny,
/// Push the type token stored in a typed reference.
@@ -230,6 +232,10 @@ namespace ICSharpCode.Decompiler.IL
YieldReturn,
/// C# await operator.
Await,
+ /// Deconstruction statement
+ DeconstructInstruction,
+ /// Represents a deconstructed value
+ DeconstructResultInstruction,
/// Matches any node
AnyNode,
}
@@ -6085,6 +6091,147 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
+{
+ /// ILAst representation of C# patterns
+ public sealed partial class MatchInstruction : ILInstruction, IStoreInstruction, IInstructionWithMethodOperand
+ {
+ public MatchInstruction(ILVariable variable, IMethod method, ILInstruction testedOperand, params ILInstruction[] subPatterns) : base(OpCode.MatchInstruction)
+ {
+ Debug.Assert(variable != null);
+ this.variable = variable;
+ this.method = method;
+ this.TestedOperand = testedOperand;
+ this.SubPatterns = new InstructionCollection(this, 1);
+ this.SubPatterns.AddRange(subPatterns);
+ }
+ ILVariable variable;
+ public ILVariable Variable {
+ get { return variable; }
+ set {
+ Debug.Assert(value != null);
+ if (IsConnected)
+ variable.RemoveStoreInstruction(this);
+ variable = value;
+ if (IsConnected)
+ variable.AddStoreInstruction(this);
+ }
+ }
+
+ public int IndexInStoreInstructionList { get; set; } = -1;
+
+ int IInstructionWithVariableOperand.IndexInVariableInstructionMapping {
+ get { return ((IStoreInstruction)this).IndexInStoreInstructionList; }
+ set { ((IStoreInstruction)this).IndexInStoreInstructionList = value; }
+ }
+
+ protected override void Connected()
+ {
+ base.Connected();
+ variable.AddStoreInstruction(this);
+ }
+
+ protected override void Disconnected()
+ {
+ variable.RemoveStoreInstruction(this);
+ base.Disconnected();
+ }
+
+ readonly IMethod method;
+ /// Returns the method operand.
+ public IMethod Method { get { return method; } }
+ public bool IsDeconstructCall;
+ public bool IsDeconstructTuple;
+ public bool CheckType;
+ public bool CheckNotNull;
+ public static readonly SlotInfo TestedOperandSlot = new SlotInfo("TestedOperand", canInlineInto: true);
+ ILInstruction testedOperand;
+ public ILInstruction TestedOperand {
+ get { return this.testedOperand; }
+ set {
+ ValidateChild(value);
+ SetChildInstruction(ref this.testedOperand, value, 0);
+ }
+ }
+ public static readonly SlotInfo SubPatternsSlot = new SlotInfo("SubPatterns");
+ public InstructionCollection SubPatterns { get; private set; }
+ protected sealed override int GetChildCount()
+ {
+ return 1 + SubPatterns.Count;
+ }
+ protected sealed override ILInstruction GetChild(int index)
+ {
+ switch (index) {
+ case 0:
+ return this.testedOperand;
+ default:
+ return this.SubPatterns[index - 1];
+ }
+ }
+ protected sealed override void SetChild(int index, ILInstruction value)
+ {
+ switch (index) {
+ case 0:
+ this.TestedOperand = value;
+ break;
+ default:
+ this.SubPatterns[index - 1] = (ILInstruction)value;
+ break;
+ }
+ }
+ protected sealed override SlotInfo GetChildSlot(int index)
+ {
+ switch (index) {
+ case 0:
+ return TestedOperandSlot;
+ default:
+ return SubPatternsSlot;
+ }
+ }
+ public sealed override ILInstruction Clone()
+ {
+ var clone = (MatchInstruction)ShallowClone();
+ clone.TestedOperand = this.testedOperand.Clone();
+ clone.SubPatterns = new InstructionCollection(clone, 1);
+ clone.SubPatterns.AddRange(this.SubPatterns.Select(arg => (ILInstruction)arg.Clone()));
+ return clone;
+ }
+ public override StackType ResultType { get { return StackType.I4; } }
+ protected override InstructionFlags ComputeFlags()
+ {
+ return InstructionFlags.MayWriteLocals | testedOperand.Flags | SubPatterns.Aggregate(InstructionFlags.None, (f, arg) => f | arg.Flags) | InstructionFlags.SideEffect | InstructionFlags.MayThrow | InstructionFlags.ControlFlow;
+ }
+ public override InstructionFlags DirectFlags {
+ get {
+ return InstructionFlags.MayWriteLocals | InstructionFlags.SideEffect | InstructionFlags.MayThrow | InstructionFlags.ControlFlow;
+ }
+ }
+ public override void AcceptVisitor(ILVisitor visitor)
+ {
+ visitor.VisitMatchInstruction(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor)
+ {
+ return visitor.VisitMatchInstruction(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor, C context)
+ {
+ return visitor.VisitMatchInstruction(this, context);
+ }
+ protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
+ {
+ var o = other as MatchInstruction;
+ return o != null && variable == o.variable && object.Equals(method, o.method) && this.IsDeconstructCall == o.IsDeconstructCall && this.IsDeconstructTuple == o.IsDeconstructTuple && this.CheckType == o.CheckType && this.CheckNotNull == o.CheckNotNull && this.testedOperand.PerformMatch(o.testedOperand, ref match) && Patterns.ListMatch.DoMatch(this.SubPatterns, o.SubPatterns, ref match);
+ }
+ internal override void CheckInvariant(ILPhase phase)
+ {
+ base.CheckInvariant(phase);
+ Debug.Assert(phase <= ILPhase.InILReader || this.IsDescendantOf(variable.Function));
+ Debug.Assert(phase <= ILPhase.InILReader || variable.Function.Variables[variable.IndexInFunction] == variable);
+ AdditionalInvariants();
+ }
+ }
+}
+namespace ICSharpCode.Decompiler.IL
{
/// Push a typed reference of type class onto the stack.
public sealed partial class MakeRefAny : UnaryInstruction
@@ -6397,6 +6544,61 @@ namespace ICSharpCode.Decompiler.IL
}
}
}
+namespace ICSharpCode.Decompiler.IL
+{
+ /// Deconstruction statement
+ public sealed partial class DeconstructInstruction : ILInstruction
+ {
+ public override StackType ResultType { get { return StackType.Void; } }
+ public override void AcceptVisitor(ILVisitor visitor)
+ {
+ visitor.VisitDeconstructInstruction(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor)
+ {
+ return visitor.VisitDeconstructInstruction(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor, C context)
+ {
+ return visitor.VisitDeconstructInstruction(this, context);
+ }
+ protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
+ {
+ var o = other as DeconstructInstruction;
+ return o != null;
+ }
+ }
+}
+namespace ICSharpCode.Decompiler.IL
+{
+ /// Represents a deconstructed value
+ public sealed partial class DeconstructResultInstruction : UnaryInstruction
+ {
+
+ public override void AcceptVisitor(ILVisitor visitor)
+ {
+ visitor.VisitDeconstructResultInstruction(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor)
+ {
+ return visitor.VisitDeconstructResultInstruction(this);
+ }
+ public override T AcceptVisitor(ILVisitor visitor, C context)
+ {
+ return visitor.VisitDeconstructResultInstruction(this, context);
+ }
+ protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
+ {
+ var o = other as DeconstructResultInstruction;
+ return o != null && this.Argument.PerformMatch(o.Argument, ref match);
+ }
+ internal override void CheckInvariant(ILPhase phase)
+ {
+ base.CheckInvariant(phase);
+ AdditionalInvariants();
+ }
+ }
+}
namespace ICSharpCode.Decompiler.IL.Patterns
{
/// Matches any node
@@ -6808,6 +7010,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
+ protected internal virtual void VisitMatchInstruction(MatchInstruction inst)
+ {
+ Default(inst);
+ }
protected internal virtual void VisitMakeRefAny(MakeRefAny inst)
{
Default(inst);
@@ -6828,6 +7034,14 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
+ protected internal virtual void VisitDeconstructInstruction(DeconstructInstruction inst)
+ {
+ Default(inst);
+ }
+ protected internal virtual void VisitDeconstructResultInstruction(DeconstructResultInstruction inst)
+ {
+ Default(inst);
+ }
}
///
@@ -7194,6 +7408,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
+ protected internal virtual T VisitMatchInstruction(MatchInstruction inst)
+ {
+ return Default(inst);
+ }
protected internal virtual T VisitMakeRefAny(MakeRefAny inst)
{
return Default(inst);
@@ -7214,6 +7432,14 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
+ protected internal virtual T VisitDeconstructInstruction(DeconstructInstruction inst)
+ {
+ return Default(inst);
+ }
+ protected internal virtual T VisitDeconstructResultInstruction(DeconstructResultInstruction inst)
+ {
+ return Default(inst);
+ }
}
///
@@ -7580,6 +7806,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
+ protected internal virtual T VisitMatchInstruction(MatchInstruction inst, C context)
+ {
+ return Default(inst, context);
+ }
protected internal virtual T VisitMakeRefAny(MakeRefAny inst, C context)
{
return Default(inst, context);
@@ -7600,6 +7830,14 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
+ protected internal virtual T VisitDeconstructInstruction(DeconstructInstruction inst, C context)
+ {
+ return Default(inst, context);
+ }
+ protected internal virtual T VisitDeconstructResultInstruction(DeconstructResultInstruction inst, C context)
+ {
+ return Default(inst, context);
+ }
}
partial class InstructionOutputExtensions
@@ -7694,11 +7932,14 @@ namespace ICSharpCode.Decompiler.IL
"dynamic.invokeconstructor",
"dynamic.invoke",
"dynamic.isevent",
+ "match",
"mkrefany",
"refanytype",
"refanyval",
"yield.return",
"await",
+ "deconstruct",
+ "deconstruct.result",
"AnyNode",
};
}
@@ -8255,6 +8496,20 @@ namespace ICSharpCode.Decompiler.IL
right = default(ILInstruction);
return false;
}
+ public bool MatchMatchInstruction(out ILVariable variable, out IMethod method, out ILInstruction testedOperand)
+ {
+ var inst = this as MatchInstruction;
+ if (inst != null) {
+ variable = inst.Variable;
+ method = inst.Method;
+ testedOperand = inst.TestedOperand;
+ return true;
+ }
+ variable = default(ILVariable);
+ method = default(IMethod);
+ testedOperand = default(ILInstruction);
+ return false;
+ }
public bool MatchMakeRefAny(out ILInstruction argument, out IType type)
{
var inst = this as MakeRefAny;
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index 92b30e6f9..627733750 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -335,6 +335,13 @@
new OpCode("dynamic.isevent", "ILAst representation of a call to the Binder.IsEvent method inside a dynamic expression.",
CustomClassName("DynamicIsEventInstruction"), Dynamic, CustomArguments(("argument", new[] { "O" })), CustomWriteTo),
+ new OpCode("match", "ILAst representation of C# patterns",
+ CustomClassName("MatchInstruction"), HasVariableOperand("Store"), HasMethodOperand,
+ BoolFlag("IsDeconstructCall"), BoolFlag("IsDeconstructTuple"), BoolFlag("CheckType"), BoolFlag("CheckNotNull"),
+ CustomChildren(new []{
+ new ChildInfo("testedOperand") { CanInlineInto = true },
+ new ChildInfo("subPatterns") { IsCollection = true }
+ }), ResultType("I4"), CustomWriteTo, SideEffect, MayThrow, ControlFlow, CustomInvariant("AdditionalInvariants();")),
new OpCode("mkrefany", "Push a typed reference of type class onto the stack.",
CustomClassName("MakeRefAny"), Unary, HasTypeOperand, ResultType("O")),
@@ -352,6 +359,12 @@
SideEffect, // other code can run with arbitrary side effects while we're waiting
CustomArguments(("value", null)), ResultType("GetResultMethod?.ReturnType.GetStackType() ?? StackType.Unknown")),
+ new OpCode("deconstruct", "Deconstruction statement",
+ CustomClassName("DeconstructInstruction"), CustomConstructor, ResultType("Void"), CustomWriteTo),
+ new OpCode("deconstruct.result", "Represents a deconstructed value",
+ CustomClassName("DeconstructResultInstruction"), CustomConstructor, CustomInvariant("AdditionalInvariants();"),
+ Unary, CustomWriteTo),
+
// patterns
new OpCode("AnyNode", "Matches any node", Pattern, CustomArguments(), CustomConstructor),
};
diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs
index c7a029506..99fe130ea 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs
@@ -171,6 +171,12 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(Instructions[i] is StLoc || AccessPathElement.GetAccessPath(Instructions[i], type2).Kind != IL.Transforms.AccessPathKind.Invalid);
}
break;
+ case BlockKind.DeconstructionConversions:
+ Debug.Assert(this.SlotInfo == DeconstructInstruction.ConversionsSlot);
+ break;
+ case BlockKind.DeconstructionAssignments:
+ Debug.Assert(this.SlotInfo == DeconstructInstruction.AssignmentsSlot);
+ break;
}
}
@@ -420,5 +426,13 @@ namespace ICSharpCode.Decompiler.IL
/// }
///
CallWithNamedArgs,
+ ///
+ ///
+ ///
+ DeconstructionConversions,
+ ///
+ ///
+ ///
+ DeconstructionAssignments,
}
}
diff --git a/ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs
new file mode 100644
index 000000000..62ba9aae1
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs
@@ -0,0 +1,300 @@
+// Copyright (c) 2020 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+using ICSharpCode.Decompiler.TypeSystem;
+
+namespace ICSharpCode.Decompiler.IL
+{
+ partial class DeconstructInstruction
+ {
+ public static readonly SlotInfo InitSlot = new SlotInfo("Init", canInlineInto: true, isCollection: true);
+ public static readonly SlotInfo PatternSlot = new SlotInfo("Pattern", canInlineInto: true);
+ public static readonly SlotInfo ConversionsSlot = new SlotInfo("Conversions");
+ public static readonly SlotInfo AssignmentsSlot = new SlotInfo("Assignments");
+
+ public DeconstructInstruction()
+ : base(OpCode.DeconstructInstruction)
+ {
+ this.Init = new InstructionCollection(this, 0);
+ }
+
+ public readonly InstructionCollection Init;
+
+ MatchInstruction pattern;
+ public MatchInstruction Pattern {
+ get { return this.pattern; }
+ set {
+ ValidateChild(value);
+ SetChildInstruction(ref this.pattern, value, Init.Count);
+ }
+ }
+
+ Block conversions;
+ public Block Conversions {
+ get { return this.conversions; }
+ set {
+ ValidateChild(value);
+ SetChildInstruction(ref this.conversions, value, Init.Count + 1);
+ }
+ }
+
+ Block assignments;
+ public Block Assignments {
+ get { return this.assignments; }
+ set {
+ ValidateChild(value);
+ SetChildInstruction(ref this.assignments, value, Init.Count + 2);
+ }
+ }
+
+ protected sealed override int GetChildCount()
+ {
+ return Init.Count + 3;
+ }
+
+ protected sealed override ILInstruction GetChild(int index)
+ {
+ switch (index - Init.Count) {
+ case 0:
+ return this.pattern;
+ case 1:
+ return this.conversions;
+ case 2:
+ return this.assignments;
+ default:
+ return this.Init[index];
+ }
+ }
+
+ protected sealed override void SetChild(int index, ILInstruction value)
+ {
+ switch (index - Init.Count) {
+ case 0:
+ this.Pattern = (MatchInstruction)value;
+ break;
+ case 1:
+ this.Conversions = (Block)value;
+ break;
+ case 2:
+ this.Assignments = (Block)value;
+ break;
+ default:
+ this.Init[index] = (StLoc)value;
+ break;
+ }
+ }
+
+ protected sealed override SlotInfo GetChildSlot(int index)
+ {
+ switch (index - Init.Count) {
+ case 0:
+ return PatternSlot;
+ case 1:
+ return ConversionsSlot;
+ case 2:
+ return AssignmentsSlot;
+ default:
+ return InitSlot;
+ }
+ }
+
+ public sealed override ILInstruction Clone()
+ {
+ var clone = new DeconstructInstruction();
+ clone.Init.AddRange(this.Init.Select(inst => (StLoc)inst.Clone()));
+ clone.Pattern = (MatchInstruction)this.pattern.Clone();
+ clone.Conversions = (Block)this.conversions.Clone();
+ clone.Assignments = (Block)this.assignments.Clone();
+ return clone;
+ }
+
+ protected override InstructionFlags ComputeFlags()
+ {
+ var flags = InstructionFlags.None;
+ foreach (var inst in Init) {
+ flags |= inst.Flags;
+ }
+ flags |= pattern.Flags | conversions.Flags | assignments.Flags;
+ return flags;
+ }
+
+ public override InstructionFlags DirectFlags {
+ get {
+ return InstructionFlags.None;
+ }
+ }
+
+ protected internal override void InstructionCollectionUpdateComplete()
+ {
+ base.InstructionCollectionUpdateComplete();
+ if (pattern.Parent == this)
+ pattern.ChildIndex = Init.Count;
+ if (conversions.Parent == this)
+ conversions.ChildIndex = Init.Count + 1;
+ if (assignments.Parent == this)
+ assignments.ChildIndex = Init.Count + 2;
+ }
+
+ public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
+ {
+ WriteILRange(output, options);
+ output.Write("deconstruct ");
+ output.MarkFoldStart("{...}");
+ output.WriteLine("{");
+ output.Indent();
+ output.WriteLine("init:");
+ output.Indent();
+ foreach (var inst in this.Init) {
+ inst.WriteTo(output, options);
+ output.WriteLine();
+ }
+ output.Unindent();
+ output.WriteLine("pattern:");
+ output.Indent();
+ pattern.WriteTo(output, options);
+ output.Unindent();
+ output.WriteLine();
+ output.Write("conversions: ");
+ conversions.WriteTo(output, options);
+ output.WriteLine();
+ output.Write("assignments: ");
+ assignments.WriteTo(output, options);
+ output.Unindent();
+ output.WriteLine();
+ output.Write('}');
+ output.MarkFoldEnd();
+ }
+
+ internal static bool IsConversionStLoc(ILInstruction inst, out ILVariable variable, out ILVariable inputVariable)
+ {
+ inputVariable = null;
+ if (!inst.MatchStLoc(out variable, out var value))
+ return false;
+ ILInstruction input;
+ switch (value) {
+ case Conv conv:
+ input = conv.Argument;
+ break;
+ //case Call { Method: { IsOperator: true, Name: "op_Implicit" }, Arguments: { Count: 1 } } call:
+ // input = call.Arguments[0];
+ // break;
+ default:
+ return false;
+ }
+ return input.MatchLdLoc(out inputVariable) || input.MatchLdLoca(out inputVariable);
+ }
+
+ internal static bool IsAssignment(ILInstruction inst, ICompilation typeSystem, out IType expectedType, out ILInstruction value)
+ {
+ expectedType = null;
+ value = null;
+ switch (inst) {
+ case CallInstruction call:
+ if (call.Method.AccessorKind != System.Reflection.MethodSemanticsAttributes.Setter)
+ return false;
+ for (int i = 0; i < call.Arguments.Count - 1; i++) {
+ ILInstruction arg = call.Arguments[i];
+ if (arg.Flags == InstructionFlags.None) {
+ // OK - we accept integer literals, etc.
+ } else if (arg.MatchLdLoc(out var v)) {
+ } else {
+ return false;
+ }
+ }
+ expectedType = call.Method.Parameters.Last().Type;
+ value = call.Arguments.Last();
+ return true;
+ case StLoc stloc:
+ expectedType = stloc.Variable.Type;
+ value = stloc.Value;
+ return true;
+ case StObj stobj:
+ var target = stobj.Target;
+ while (target.MatchLdFlda(out var nestedTarget, out _))
+ target = nestedTarget;
+ if (target.Flags == InstructionFlags.None) {
+ // OK - we accept integer literals, etc.
+ } else if (target.MatchLdLoc(out var v)) {
+ } else {
+ return false;
+ }
+ if (stobj.Target.InferType(typeSystem) is ByReferenceType brt)
+ expectedType = brt.ElementType;
+ else
+ expectedType = SpecialType.UnknownType;
+ value = stobj.Value;
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ internal override void CheckInvariant(ILPhase phase)
+ {
+ base.CheckInvariant(phase);
+ var patternVariables = new HashSet();
+ var conversionVariables = new HashSet();
+
+ foreach (StLoc init in this.Init) {
+ Debug.Assert(init.Variable.IsSingleDefinition && init.Variable.LoadCount == 1);
+ Debug.Assert(init.Variable.LoadInstructions[0].IsDescendantOf(assignments));
+ }
+
+ ValidatePattern(pattern);
+
+ foreach (var inst in this.conversions.Instructions) {
+ if (!IsConversionStLoc(inst, out var variable, out var inputVariable))
+ Debug.Fail("inst is not a conversion stloc!");
+ Debug.Assert(variable.IsSingleDefinition && variable.LoadCount == 1);
+ Debug.Assert(variable.LoadInstructions[0].IsDescendantOf(assignments));
+ Debug.Assert(patternVariables.Contains(inputVariable));
+ conversionVariables.Add(variable);
+ }
+ Debug.Assert(this.conversions.FinalInstruction is Nop);
+
+ foreach (var inst in assignments.Instructions) {
+ if (!(IsAssignment(inst, typeSystem: null, out _, out var value) && value.MatchLdLoc(out var inputVariable)))
+ throw new InvalidOperationException("inst is not an assignment!");
+ Debug.Assert(patternVariables.Contains(inputVariable) || conversionVariables.Contains(inputVariable));
+ }
+ Debug.Assert(this.assignments.FinalInstruction is Nop);
+
+ void ValidatePattern(MatchInstruction inst)
+ {
+ Debug.Assert(inst.IsDeconstructCall || inst.IsDeconstructTuple);
+ Debug.Assert(!inst.CheckNotNull && !inst.CheckType);
+ Debug.Assert(!inst.HasDesignator);
+ foreach (var subPattern in inst.SubPatterns.Cast()) {
+ if (subPattern.IsVar) {
+ Debug.Assert(subPattern.Variable.IsSingleDefinition && subPattern.Variable.LoadCount <= 1);
+ if (subPattern.Variable.LoadCount == 1)
+ Debug.Assert(subPattern.Variable.LoadInstructions[0].IsDescendantOf(this));
+ patternVariables.Add(subPattern.Variable);
+ } else {
+ ValidatePattern(subPattern);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs
new file mode 100644
index 000000000..998a0e37c
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs
@@ -0,0 +1,67 @@
+// Copyright (c) 2020 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System.Diagnostics;
+using ICSharpCode.Decompiler.TypeSystem;
+
+namespace ICSharpCode.Decompiler.IL
+{
+ partial class DeconstructResultInstruction
+ {
+ public int Index { get; }
+
+ public override StackType ResultType { get; }
+
+ public DeconstructResultInstruction(int index, StackType resultType, ILInstruction argument)
+ : base(OpCode.DeconstructResultInstruction, argument)
+ {
+ Debug.Assert(index >= 0);
+ Index = index;
+ ResultType = resultType;
+ }
+
+ public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
+ {
+ WriteILRange(output, options);
+ output.Write(OpCode);
+ output.Write(' ');
+ output.Write(Index.ToString());
+ output.Write('(');
+ this.Argument.WriteTo(output, options);
+ output.Write(')');
+ }
+
+ MatchInstruction FindMatch()
+ {
+ for (ILInstruction inst = this; inst != null; inst = inst.Parent) {
+ if (inst.Parent is MatchInstruction match && inst != match.TestedOperand)
+ return match;
+ }
+ return null;
+ }
+
+ void AdditionalInvariants()
+ {
+ var matchInst = FindMatch();
+ Debug.Assert(matchInst != null && (matchInst.IsDeconstructCall || matchInst.IsDeconstructTuple));
+ Debug.Assert(Argument.MatchLdLoc(matchInst.Variable));
+ var outParamType = matchInst.GetDeconstructResultType(this.Index);
+ Debug.Assert(outParamType.GetStackType() == ResultType);
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
index 1bc534954..01a37daa2 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
@@ -98,6 +98,86 @@ namespace ICSharpCode.Decompiler.IL
}
return false;
}
+
+ public ILInstruction GetCommonParent(ILInstruction other)
+ {
+ if (other == null)
+ throw new ArgumentNullException(nameof(other));
+
+ ILInstruction a = this;
+ ILInstruction b = other;
+
+ int levelA = a.CountAncestors();
+ int levelB = b.CountAncestors();
+
+ while (levelA > levelB) {
+ a = a.Parent;
+ levelA--;
+ }
+
+ while (levelB > levelA) {
+ b = b.Parent;
+ levelB--;
+ }
+
+ while (a != b) {
+ a = a.Parent;
+ b = b.Parent;
+ }
+
+ return a;
+ }
+
+ ///
+ /// Returns whether this appears before other in a post-order walk of the whole tree.
+ ///
+ public bool IsBefore(ILInstruction other)
+ {
+ if (other == null)
+ throw new ArgumentNullException(nameof(other));
+
+ ILInstruction a = this;
+ ILInstruction b = other;
+
+ int levelA = a.CountAncestors();
+ int levelB = b.CountAncestors();
+
+ int originalLevelA = levelA;
+ int originalLevelB = levelB;
+
+ while (levelA > levelB) {
+ a = a.Parent;
+ levelA--;
+ }
+
+ while (levelB > levelA) {
+ b = b.Parent;
+ levelB--;
+ }
+
+ if (a == b) {
+ // a or b is a descendant of the other,
+ // whichever node has the higher level comes first in post-order walk.
+ return originalLevelA > originalLevelB;
+ }
+
+ while (a.Parent != b.Parent) {
+ a = a.Parent;
+ b = b.Parent;
+ }
+
+ // now a and b have the same parent or are both root nodes
+ return a.ChildIndex < b.ChildIndex;
+ }
+
+ private int CountAncestors()
+ {
+ int level = 0;
+ for (ILInstruction ancestor = this; ancestor != null; ancestor = ancestor.Parent) {
+ level++;
+ }
+ return level;
+ }
///
/// Gets the stack type of the value produced by this instruction.
@@ -632,9 +712,10 @@ namespace ICSharpCode.Decompiler.IL
/// Reference to the field holding the child
/// New child
/// Index of the field in the Children collection
- protected internal void SetChildInstruction(ref ILInstruction childPointer, ILInstruction newValue, int index)
+ protected internal void SetChildInstruction(ref T childPointer, T newValue, int index)
+ where T : ILInstruction
{
- ILInstruction oldValue = childPointer;
+ T oldValue = childPointer;
Debug.Assert(oldValue == GetChild(index));
if (oldValue == newValue && newValue?.parent == this && newValue.ChildIndex == index)
return;
diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
new file mode 100644
index 000000000..e29784ebe
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
@@ -0,0 +1,267 @@
+// Copyright (c) 2020 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using ICSharpCode.Decompiler.TypeSystem;
+
+namespace ICSharpCode.Decompiler.IL
+{
+ partial class MatchInstruction : ILInstruction
+ {
+ /* Pseudo-Code for interpreting a MatchInstruction:
+ bool Eval()
+ {
+ var value = this.TestedOperand.Eval();
+ if (this.CheckNotNull && value == null)
+ return false;
+ if (this.CheckType && !(value is this.Variable.Type))
+ return false;
+ if (this.IsDeconstructCall) {
+ deconstructResult = new[numArgs];
+ EvalCall(this.Method, value, out deconstructResult[0], .., out deconstructResult[numArgs-1]);
+ // any occurrences of 'deconstruct.result' in the subPatterns will refer
+ // to the values provided by evaluating the call.
+ }
+ Variable.Value = value;
+ foreach (var subPattern in this.SubPatterns) {
+ if (!subPattern.Eval())
+ return false;
+ }
+ return true;
+ }
+ */
+ /* Examples of MatchInstructions:
+ expr is var x:
+ match(x = expr)
+
+ expr is {} x:
+ match.notnull(x = expr)
+
+ expr is T x:
+ match.type[T](x = expr)
+
+ expr is C { A: var x } z:
+ match.type[C](z = expr) {
+ match(x = z.A)
+ }
+
+ expr is C { A: var x, B: 42, C: { A: 4 } } z:
+ match.type[C](z = expr) {
+ match(x = z.A),
+ comp (z.B == 42),
+ match.notnull(temp2 = z.C) {
+ comp (temp2.A == 4)
+ }
+ }
+
+ expr is C(var x, var y, <4):
+ match.type[C].deconstruct[C.Deconstruct](tmp1 = expr) {
+ match(x = deconstruct.result1(tmp1)),
+ match(y = deconstruct.result2(tmp1)),
+ comp(deconstruct.result3(tmp1) < 4),
+ }
+
+ expr is C(1, D(2, 3)):
+ match.type[C].deconstruct(c = expr) {
+ comp(deconstruct.result1(c) == 1),
+ match.type[D].deconstruct(d = deconstruct.result2(c)) {
+ comp(deconstruct.result1(d) == 2),
+ comp(deconstruct.result2(d) == 2),
+ }
+ }
+ */
+
+ public bool IsVar => !CheckType && !CheckNotNull && !IsDeconstructCall && !IsDeconstructTuple && SubPatterns.Count == 0;
+
+ public bool HasDesignator => Variable.LoadCount + Variable.AddressCount > SubPatterns.Count;
+
+ public int NumPositionalPatterns {
+ get {
+ if (IsDeconstructCall)
+ return method.Parameters.Count - (method.IsStatic ? 1 : 0);
+ else if (IsDeconstructTuple)
+ return TupleType.GetTupleElementTypes(variable.Type).Length;
+ else
+ return 0;
+ }
+ }
+
+ public MatchInstruction(ILVariable variable, ILInstruction testedOperand)
+ : this(variable, method: null, testedOperand)
+ {
+ }
+
+ ///
+ /// Checks whether the input instruction can represent a pattern matching operation.
+ ///
+ /// Any pattern matching instruction will first evaluate the `testedOperand` (a descendant of `inst`),
+ /// and then match the value of that operand against the pattern encoded in the instruction.
+ /// The matching may have side-effects on the newly-initialized pattern variables
+ /// (even if the pattern fails to match!).
+ /// The pattern matching instruction evaluates to 1 (as I4) if the pattern matches, or 0 otherwise.
+ ///
+ public static bool IsPatternMatch(ILInstruction inst, out ILInstruction testedOperand)
+ {
+ switch (inst) {
+ case MatchInstruction m:
+ testedOperand = m.testedOperand;
+ return true;
+ case Comp comp:
+ testedOperand = comp.Left;
+ return IsConstant(comp.Right);
+ case ILInstruction logicNot when logicNot.MatchLogicNot(out var operand):
+ return IsPatternMatch(operand, out testedOperand);
+ default:
+ testedOperand = null;
+ return false;
+ }
+ }
+
+ private static bool IsConstant(ILInstruction inst)
+ {
+ return inst.OpCode switch
+ {
+ OpCode.LdcDecimal => true,
+ OpCode.LdcF4 => true,
+ OpCode.LdcF8 => true,
+ OpCode.LdcI4 => true,
+ OpCode.LdcI8 => true,
+ OpCode.LdNull => true,
+ _ => false
+ };
+ }
+
+ internal IType GetDeconstructResultType(int index)
+ {
+ if (this.IsDeconstructCall) {
+ int firstOutParam = (method.IsStatic ? 1 : 0);
+ var outParamType = this.Method.Parameters[firstOutParam + index].Type;
+ if (!(outParamType is ByReferenceType brt))
+ throw new InvalidOperationException("deconstruct out param must be by reference");
+ return brt.ElementType;
+ }
+ if (this.IsDeconstructTuple) {
+ var elementTypes = TupleType.GetTupleElementTypes(this.variable.Type);
+ return elementTypes[index];
+ }
+ throw new InvalidOperationException("GetDeconstructResultType requires a deconstruct pattern");
+ }
+
+ void AdditionalInvariants()
+ {
+ Debug.Assert(variable.Kind == VariableKind.PatternLocal);
+ if (this.IsDeconstructCall) {
+ Debug.Assert(IsDeconstructMethod(method));
+ } else {
+ Debug.Assert(method == null);
+ }
+ if (this.IsDeconstructTuple) {
+ Debug.Assert(variable.Type.Kind == TypeKind.Tuple);
+ }
+ Debug.Assert(SubPatterns.Count >= NumPositionalPatterns);
+ foreach (var subPattern in SubPatterns) {
+ if (!IsPatternMatch(subPattern, out ILInstruction operand))
+ Debug.Fail("Sub-Pattern must be a valid pattern");
+ // the first child is TestedOperand
+ int subPatternIndex = subPattern.ChildIndex - 1;
+ if (subPatternIndex < NumPositionalPatterns) {
+ // positional pattern
+ Debug.Assert(operand is DeconstructResultInstruction result && result.Index == subPatternIndex);
+ } else if (operand.MatchLdFld(out var target, out _)) {
+ Debug.Assert(target.MatchLdLoc(variable));
+ } else if (operand is CallInstruction call) {
+ Debug.Assert(call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter);
+ Debug.Assert(call.Arguments[0].MatchLdLoc(variable));
+ } else {
+ Debug.Fail("Tested operand of sub-pattern is invalid.");
+ }
+ }
+ }
+
+ internal static bool IsDeconstructMethod(IMethod method)
+ {
+ if (method.Name != "Deconstruct")
+ return false;
+ if (method.ReturnType.Kind != TypeKind.Void)
+ return false;
+ int firstOutParam = (method.IsStatic ? 1 : 0);
+ if (method.IsStatic) {
+ if (!method.IsExtensionMethod)
+ return false;
+ // TODO : check whether all type arguments can be inferred from the first argument
+ } else {
+ if (method.TypeParameters.Count != 0)
+ return false;
+ }
+
+ // TODO : check whether the method is ambigious
+
+ if (method.Parameters.Count < firstOutParam)
+ return false;
+
+ for (int i = firstOutParam; i < method.Parameters.Count; i++) {
+ if (!method.Parameters[i].IsOut)
+ return false;
+ }
+
+ return true;
+ }
+
+ public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
+ {
+ WriteILRange(output, options);
+ output.Write(OpCode);
+ if (CheckNotNull) {
+ output.Write(".notnull");
+ }
+ if (CheckType) {
+ output.Write(".type[");
+ variable.Type.WriteTo(output);
+ output.Write(']');
+ }
+ if (IsDeconstructCall) {
+ output.Write(".deconstruct[");
+ method.WriteTo(output);
+ output.Write(']');
+ }
+ if (IsDeconstructTuple) {
+ output.Write(".tuple");
+ }
+ output.Write(' ');
+ output.Write('(');
+ Variable.WriteTo(output);
+ output.Write(" = ");
+ TestedOperand.WriteTo(output, options);
+ output.Write(')');
+ if (SubPatterns.Count > 0) {
+ output.MarkFoldStart("{...}");
+ output.WriteLine("{");
+ output.Indent();
+ foreach (var pattern in SubPatterns) {
+ pattern.WriteTo(output, options);
+ output.WriteLine();
+ }
+ output.Unindent();
+ output.Write('}');
+ output.MarkFoldEnd();
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs
new file mode 100644
index 000000000..409d3a907
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs
@@ -0,0 +1,435 @@
+// Copyright (c) 2020 Siegfried Pammer
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Resources;
+
+using ICSharpCode.Decompiler.CSharp.Resolver;
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.Util;
+
+namespace ICSharpCode.Decompiler.IL.Transforms
+{
+ ///
+ ///
+ ///
+ class DeconstructionTransform : IStatementTransform
+ {
+ StatementTransformContext context;
+ readonly Dictionary deconstructionResultsLookup = new Dictionary();
+ ILVariable[] deconstructionResults;
+ ILVariable tupleVariable;
+ TupleType tupleType;
+
+ /*
+ stloc tuple(call MakeIntIntTuple(ldloc this))
+ ----
+ stloc myInt(call op_Implicit(ldfld Item2(ldloca tuple)))
+ stloc a(ldfld Item1(ldloca tuple))
+ stloc b(ldloc myInt)
+ ==>
+ deconstruct {
+ init:
+
+ deconstruct:
+ match.deconstruct(temp = ldloca tuple) {
+ match(result0 = deconstruct.result 0(temp)),
+ match(result1 = deconstruct.result 1(temp))
+ }
+ conversions: {
+ stloc conv2(call op_Implicit(ldloc result1))
+ }
+ assignments: {
+ stloc a(ldloc result0)
+ stloc b(ldloc conv2)
+ }
+ }
+ * */
+ void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
+ {
+ if (!context.Settings.Deconstruction)
+ return;
+
+ try {
+ this.context = context;
+ Reset();
+
+ if (TransformDeconstruction(block, pos))
+ return;
+ if (InlineDeconstructionInitializer(block, pos))
+ return;
+ } finally {
+ this.context = null;
+ Reset();
+ }
+ }
+
+ private void Reset()
+ {
+ this.deconstructionResultsLookup.Clear();
+ this.tupleVariable = null;
+ this.tupleType = null;
+ this.deconstructionResults = null;
+ }
+
+ struct ConversionInfo
+ {
+ public IType inputType;
+ public Conv conv;
+ }
+
+ ///
+ /// Get index of deconstruction result or tuple element
+ /// Returns -1 on failure.
+ ///
+ int FindIndex(ILInstruction inst, out Action delayedActions)
+ {
+ delayedActions = null;
+ if (inst.MatchLdLoc(out var v)) {
+ if (!deconstructionResultsLookup.TryGetValue(v, out int index))
+ return -1;
+ return index;
+ }
+ if (inst.MatchLdFld(out _, out _)) {
+ if (!TupleTransform.MatchTupleFieldAccess((LdFlda)((LdObj)inst).Target, out var tupleType, out var target, out int index))
+ return -1;
+ // Item fields are one-based, we use zero-based indexing.
+ index--;
+ // normalize tuple type
+ tupleType = TupleType.FromUnderlyingType(context.TypeSystem, tupleType);
+ if (!target.MatchLdLoca(out v))
+ return -1;
+ if (this.tupleVariable == null) {
+ this.tupleVariable = v;
+ this.tupleType = (TupleType)tupleType;
+ this.deconstructionResults = new ILVariable[this.tupleType.Cardinality];
+ }
+ if (this.tupleType.Cardinality < 2)
+ return -1;
+ if (v != tupleVariable || !this.tupleType.Equals(tupleType))
+ return -1;
+ if (this.deconstructionResults[index] == null) {
+ var freshVar = new ILVariable(VariableKind.StackSlot, this.tupleType.ElementTypes[index]) { Name = "E_" + index };
+ delayedActions += _ => context.Function.Variables.Add(freshVar);
+ this.deconstructionResults[index] = freshVar;
+ }
+ delayedActions += _ => {
+ inst.ReplaceWith(new LdLoc(this.deconstructionResults[index]));
+ };
+ return index;
+ }
+ return -1;
+ }
+
+ ///
+ /// stloc v(value)
+ /// expr(..., deconstruct { ... }, ...)
+ /// =>
+ /// expr(..., deconstruct { init: stloc v(value) ... }, ...)
+ ///
+ bool InlineDeconstructionInitializer(Block block, int pos)
+ {
+ if (!block.Instructions[pos].MatchStLoc(out var v, out var value))
+ return false;
+ if (!(v.IsSingleDefinition && v.LoadCount == 1))
+ return false;
+ if (pos + 1 >= block.Instructions.Count)
+ return false;
+ var result = ILInlining.FindLoadInNext(block.Instructions[pos + 1], v, value, InliningOptions.FindDeconstruction);
+ if (result.Type != ILInlining.FindResultType.Deconstruction)
+ return false;
+ var deconstruction = (DeconstructInstruction)result.LoadInst;
+ LdLoc loadInst = v.LoadInstructions[0];
+ if (!loadInst.IsDescendantOf(deconstruction.Assignments))
+ return false;
+ if (loadInst.SlotInfo == StObj.TargetSlot) {
+ if (value.OpCode == OpCode.LdFlda || value.OpCode == OpCode.LdElema)
+ return false;
+ }
+ if (deconstruction.Init.Count > 0) {
+ var a = deconstruction.Init[0].Variable.LoadInstructions.Single();
+ var b = v.LoadInstructions.Single();
+ if (!b.IsBefore(a))
+ return false;
+ }
+ context.Step("InlineDeconstructionInitializer", block.Instructions[pos]);
+ deconstruction.Init.Insert(0, (StLoc)block.Instructions[pos]);
+ block.Instructions.RemoveAt(pos);
+ v.Kind = VariableKind.DeconstructionInitTemporary;
+ return true;
+ }
+
+ bool TransformDeconstruction(Block block, int pos)
+ {
+ int startPos = pos;
+ Action delayedActions = null;
+ if (MatchDeconstruction(block.Instructions[pos], out IMethod deconstructMethod,
+ out ILInstruction rootTestedOperand))
+ {
+ pos++;
+ }
+ if (!MatchConversions(block, ref pos, out var conversions, out var conversionStLocs, ref delayedActions))
+ return false;
+
+ if (!MatchAssignments(block, ref pos, conversions, conversionStLocs, ref delayedActions))
+ return false;
+ // first tuple element may not be discarded,
+ // otherwise we would run this transform on a suffix of the actual pattern.
+ if (deconstructionResults[0] == null)
+ return false;
+ context.Step("Deconstruction", block.Instructions[startPos]);
+ DeconstructInstruction replacement = new DeconstructInstruction();
+ IType deconstructedType;
+ if (deconstructMethod == null) {
+ deconstructedType = this.tupleType;
+ rootTestedOperand = new LdLoc(this.tupleVariable);
+ } else {
+ if (deconstructMethod.IsStatic) {
+ deconstructedType = deconstructMethod.Parameters[0].Type;
+ } else {
+ deconstructedType = deconstructMethod.DeclaringType;
+ }
+ }
+ var rootTempVariable = context.Function.RegisterVariable(VariableKind.PatternLocal, deconstructedType);
+ replacement.Pattern = new MatchInstruction(rootTempVariable, deconstructMethod, rootTestedOperand) {
+ IsDeconstructCall = deconstructMethod != null,
+ IsDeconstructTuple = this.tupleType != null
+ };
+ int index = 0;
+ foreach (ILVariable v in deconstructionResults) {
+ var result = v;
+ if (result == null) {
+ var freshVar = new ILVariable(VariableKind.PatternLocal, this.tupleType.ElementTypes[index]) { Name = "E_" + index };
+ context.Function.Variables.Add(freshVar);
+ result = freshVar;
+ } else {
+ result.Kind = VariableKind.PatternLocal;
+ }
+ replacement.Pattern.SubPatterns.Add(
+ new MatchInstruction(
+ result,
+ new DeconstructResultInstruction(index, result.StackType, new LdLoc(rootTempVariable))
+ )
+ );
+ index++;
+ }
+ replacement.Conversions = new Block(BlockKind.DeconstructionConversions);
+ foreach (var convInst in conversionStLocs) {
+ replacement.Conversions.Instructions.Add(convInst);
+ }
+ replacement.Assignments = new Block(BlockKind.DeconstructionAssignments);
+ delayedActions?.Invoke(replacement);
+ block.Instructions[startPos] = replacement;
+ block.Instructions.RemoveRange(startPos + 1, pos - startPos - 1);
+ return true;
+ }
+
+ bool MatchDeconstruction(ILInstruction inst, out IMethod deconstructMethod,
+ out ILInstruction testedOperand)
+ {
+ testedOperand = null;
+ deconstructMethod = null;
+ deconstructionResults = null;
+ if (!(inst is CallInstruction call))
+ return false;
+ if (!MatchInstruction.IsDeconstructMethod(call.Method))
+ return false;
+ if (call.Method.IsStatic == call is CallVirt)
+ return false;
+ if (call.Arguments.Count < 3)
+ return false;
+ deconstructionResults = new ILVariable[call.Arguments.Count - 1];
+ for (int i = 0; i < deconstructionResults.Length; i++) {
+ if (!call.Arguments[i + 1].MatchLdLoca(out var v))
+ return false;
+ // TODO v.LoadCount may be 2 if the deconstruction is assigned to a tuple variable
+ // or 0? because of discards
+ if (!(v.StoreCount == 0 && v.AddressCount == 1 && v.LoadCount <= 1))
+ return false;
+ deconstructionResultsLookup.Add(v, i);
+ deconstructionResults[i] = v;
+ }
+ testedOperand = call.Arguments[0];
+ deconstructMethod = call.Method;
+ return true;
+ }
+
+ bool MatchConversions(Block block, ref int pos,
+ out Dictionary conversions,
+ out List conversionStLocs,
+ ref Action delayedActions)
+ {
+ conversions = new Dictionary();
+ conversionStLocs = new List();
+ int previousIndex = -1;
+ while (MatchConversion(
+ block.Instructions.ElementAtOrDefault(pos), out var inputInstruction,
+ out var outputVariable, out var info)) {
+ int index = FindIndex(inputInstruction, out var tupleAccessAdjustment);
+ if (index <= previousIndex)
+ return false;
+ if (!(outputVariable.IsSingleDefinition && outputVariable.LoadCount == 1))
+ return false;
+ delayedActions += tupleAccessAdjustment;
+ deconstructionResultsLookup.Add(outputVariable, index);
+ conversions.Add(outputVariable, info);
+ conversionStLocs.Add((StLoc)block.Instructions[pos]);
+ pos++;
+ previousIndex = index;
+ }
+ return true;
+ }
+
+ bool MatchConversion(ILInstruction inst, out ILInstruction inputInstruction,
+ out ILVariable outputVariable, out ConversionInfo info)
+ {
+ info = default;
+ inputInstruction = null;
+ if (!inst.MatchStLoc(out outputVariable, out var value))
+ return false;
+ if (!(value is Conv conv))
+ return false;
+ info = new ConversionInfo {
+ inputType = conv.Argument.InferType(context.TypeSystem),
+ conv = conv
+ };
+ inputInstruction = conv.Argument;
+ return true;
+ }
+
+ bool MatchAssignments(Block block, ref int pos,
+ Dictionary conversions,
+ List conversionStLocs,
+ ref Action delayedActions)
+ {
+ int previousIndex = -1;
+ int conversionStLocIndex = 0;
+ int startPos = pos;
+ while (MatchAssignment(block.Instructions.ElementAtOrDefault(pos), out var targetType, out var valueInst, out var addAssignment)) {
+ int index = FindIndex(valueInst, out var tupleAccessAdjustment);
+ if (index <= previousIndex)
+ return false;
+ AddMissingAssignmentsForConversions(index, ref delayedActions);
+ if (!(valueInst.MatchLdLoc(out var resultVariable)
+ && conversions.TryGetValue(resultVariable, out var conversionInfo)))
+ {
+ conversionInfo = new ConversionInfo {
+ inputType = valueInst.InferType(context.TypeSystem)
+ };
+ }
+ if (block.Instructions[pos].MatchStLoc(out var assignmentTarget, out _)
+ && assignmentTarget.Kind == VariableKind.StackSlot
+ && assignmentTarget.IsSingleDefinition
+ && conversionInfo.conv == null) {
+ delayedActions += _ => {
+ assignmentTarget.Type = conversionInfo.inputType;
+ };
+ } else {
+ if (!IsCompatibleImplicitConversion(targetType, conversionInfo))
+ return false;
+ }
+ delayedActions += addAssignment;
+ delayedActions += tupleAccessAdjustment;
+ pos++;
+ previousIndex = index;
+ }
+ AddMissingAssignmentsForConversions(int.MaxValue, ref delayedActions);
+ return startPos != pos;
+
+ void AddMissingAssignmentsForConversions(int index, ref Action delayedActions)
+ {
+ while (conversionStLocIndex < conversionStLocs.Count) {
+ var stLoc = conversionStLocs[conversionStLocIndex];
+ int conversionResultIndex = deconstructionResultsLookup[stLoc.Variable];
+
+ if (conversionResultIndex >= index)
+ break;
+ if (conversionResultIndex > previousIndex) {
+ delayedActions += (DeconstructInstruction deconstructInst) => {
+ var freshVar = context.Function.RegisterVariable(VariableKind.StackSlot, stLoc.Variable.Type);
+ deconstructInst.Assignments.Instructions.Add(new StLoc(stLoc.Variable, new LdLoc(freshVar)));
+ stLoc.Variable = freshVar;
+ };
+ }
+ conversionStLocIndex++;
+ }
+ }
+ }
+
+ bool MatchAssignment(ILInstruction inst, out IType targetType, out ILInstruction valueInst, out Action addAssignment)
+ {
+ targetType = null;
+ valueInst = null;
+ addAssignment = null;
+ if (inst == null)
+ return false;
+ if (inst.MatchStLoc(out var v, out var value)
+ && value is Block block && block.MatchInlineAssignBlock(out var call, out valueInst)) {
+ if (!DeconstructInstruction.IsAssignment(call, context.TypeSystem, out targetType, out _))
+ return false;
+ if (!(v.IsSingleDefinition && v.LoadCount == 0))
+ return false;
+ var valueInstCopy = valueInst;
+ addAssignment = (DeconstructInstruction deconstructInst) => {
+ call.Arguments[call.Arguments.Count - 1] = valueInstCopy;
+ deconstructInst.Assignments.Instructions.Add(call);
+ };
+ return true;
+ } else if (DeconstructInstruction.IsAssignment(inst, context.TypeSystem, out targetType, out valueInst)) {
+ // OK - use the assignment as is
+ addAssignment = (DeconstructInstruction deconstructInst) => {
+ deconstructInst.Assignments.Instructions.Add(inst);
+ };
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool IsCompatibleImplicitConversion(IType targetType, ConversionInfo conversionInfo)
+ {
+ var c = CSharpConversions.Get(context.TypeSystem)
+ .ImplicitConversion(conversionInfo.inputType, targetType);
+ if (!c.IsValid)
+ return false;
+ var inputType = conversionInfo.inputType;
+ var conv = conversionInfo.conv;
+ if (c.IsIdentityConversion || c.IsReferenceConversion) {
+ return conv == null || conv.Kind == ConversionKind.Nop;
+ }
+ if (c.IsNumericConversion) {
+ switch (conv.Kind) {
+ case ConversionKind.IntToFloat:
+ return inputType.GetSign() == conv.InputSign;
+ case ConversionKind.FloatPrecisionChange:
+ return true;
+ case ConversionKind.SignExtend:
+ return inputType.GetSign() == Sign.Signed;
+ case ConversionKind.ZeroExtend:
+ return inputType.GetSign() == Sign.Unsigned;
+ default:
+ return false;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 5316249e2..82225949c 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -31,6 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
None = 0,
Aggressive = 1,
IntroduceNamedArguments = 2,
+ FindDeconstruction = 4,
}
///
@@ -503,6 +504,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
//case OpCode.BinaryNumericInstruction when parent.SlotInfo == SwitchInstruction.ValueSlot:
case OpCode.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot:
return true;
+ case OpCode.MatchInstruction when ((MatchInstruction)parent).IsDeconstructTuple:
+ return true;
}
// decide based on the top-level target instruction into which we are inlining:
switch (next.OpCode) {
@@ -543,8 +546,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// Found a load in call, but re-ordering not possible with regards to the
/// other call arguments.
/// Inlining is not possible, but we might convert the call to named arguments.
+ /// Only used with .
///
NamedArgument,
+ ///
+ /// Found a deconstruction.
+ /// Only used with .
+ ///
+ Deconstruction,
}
internal readonly struct FindResult
@@ -575,6 +584,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
Debug.Assert(callArg.Parent is CallInstruction);
return new FindResult(FindResultType.NamedArgument, loadInst, callArg);
}
+
+ public static FindResult Deconstruction(DeconstructInstruction deconstruction)
+ {
+ return new FindResult(FindResultType.Deconstruction, deconstruction, null);
+ }
}
///
@@ -611,6 +625,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
default:
return FindResult.Stop;
}
+ } else if (options.HasFlag(InliningOptions.FindDeconstruction) && expr is DeconstructInstruction di) {
+ return FindResult.Deconstruction(di);
}
foreach (var child in expr.Children) {
if (!expr.CanInlineIntoSlot(child.ChildIndex, expressionBeingMoved))
diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
index 82730d64f..ce2f7c49b 100644
--- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
+++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
@@ -164,7 +164,7 @@ namespace ICSharpCode.Decompiler
if (node is Identifier && node.Parent != null)
node = node.Parent;
- if (node is ParameterDeclaration || node is VariableInitializer || node is CatchClause || node is ForeachStatement) {
+ if (node is ParameterDeclaration || node is VariableInitializer || node is CatchClause || node is VariableDesignation) {
var variable = node.Annotation()?.Variable;
if (variable != null)
return variable;
diff --git a/ICSharpCode.Decompiler/TypeSystem/TupleType.cs b/ICSharpCode.Decompiler/TypeSystem/TupleType.cs
index 7833b84c5..906fa413a 100644
--- a/ICSharpCode.Decompiler/TypeSystem/TupleType.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/TupleType.cs
@@ -44,6 +44,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
///
public ImmutableArray ElementTypes { get; }
+ ///
+ /// Gets the cardinality of the tuple.
+ ///
+ public int Cardinality => ElementTypes.Length;
+
///
/// Gets the names of the tuple elements.
///
diff --git a/ILSpy.BamlDecompiler/Properties/launchSettings.json b/ILSpy.BamlDecompiler/Properties/launchSettings.json
new file mode 100644
index 000000000..253f16b70
--- /dev/null
+++ b/ILSpy.BamlDecompiler/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "profiles": {
+ "ILSpy.BamlDecompiler": {
+ "commandName": "Executable",
+ "executablePath": "C:\\Users\\sie_p\\Projects\\ILSpy\\ILSpy\\bin\\Debug\\net472\\ILSpy.exe",
+ "commandLineArgs": "/separate"
+ }
+ }
+}
\ No newline at end of file
diff --git a/ILSpy.ReadyToRun/Properties/Resources.Designer.cs b/ILSpy.ReadyToRun/Properties/Resources.Designer.cs
index 56cf7ac1f..f28df710f 100644
--- a/ILSpy.ReadyToRun/Properties/Resources.Designer.cs
+++ b/ILSpy.ReadyToRun/Properties/Resources.Designer.cs
@@ -1,10 +1,10 @@
//------------------------------------------------------------------------------
//
-// 此代码由工具生成。
-// 运行时版本:4.0.30319.42000
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
//
-// 对此文件的更改可能会导致不正确的行为,并且如果
-// 重新生成代码,这些更改将会丢失。
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
//
//------------------------------------------------------------------------------
@@ -13,12 +13,12 @@ namespace ILSpy.ReadyToRun.Properties {
///
- /// 一个强类型的资源类,用于查找本地化的字符串等。
+ /// A strongly-typed resource class, for looking up localized strings, etc.
///
- // 此类是由 StronglyTypedResourceBuilder
- // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
- // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
- // (以 /str 作为命令选项),或重新生成 VS 项目。
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
@@ -33,7 +33,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
///
- /// 返回此类使用的缓存的 ResourceManager 实例。
+ /// Returns the cached ResourceManager instance used by this class.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
@@ -47,8 +47,8 @@ namespace ILSpy.ReadyToRun.Properties {
}
///
- /// 重写当前线程的 CurrentUICulture 属性
- /// 重写当前线程的 CurrentUICulture 属性。
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
@@ -61,7 +61,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
///
- /// 查找类似 Disassembly Format 的本地化字符串。
+ /// Looks up a localized string similar to Disassembly Format.
///
public static string DisassemblyFormat {
get {
@@ -70,7 +70,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
///
- /// 查找类似 ReadyToRun 的本地化字符串。
+ /// Looks up a localized string similar to ReadyToRun.
///
public static string ReadyToRun {
get {
@@ -79,7 +79,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
///
- /// 查找类似 Show Debug Info 的本地化字符串。
+ /// Looks up a localized string similar to Show Debug Info.
///
public static string ShowDebugInfo {
get {
@@ -88,7 +88,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
///
- /// 查找类似 Show Unwind Info 的本地化字符串。
+ /// Looks up a localized string similar to Show Unwind Info.
///
public static string ShowUnwindInfo {
get {
diff --git a/ILSpy.ReadyToRun/Properties/launchSettings.json b/ILSpy.ReadyToRun/Properties/launchSettings.json
new file mode 100644
index 000000000..5df6a1d3d
--- /dev/null
+++ b/ILSpy.ReadyToRun/Properties/launchSettings.json
@@ -0,0 +1,9 @@
+{
+ "profiles": {
+ "ILSpy.ReadyToRun": {
+ "commandName": "Executable",
+ "executablePath": "C:\\Users\\sie_p\\Projects\\ILSpy\\ILSpy\\bin\\Debug\\net472\\ILSpy.exe",
+ "commandLineArgs": "/separate /language:ReadyToRun"
+ }
+ }
+}
\ No newline at end of file
diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj
index 197bd9ed7..56297314e 100644
--- a/ILSpy/ILSpy.csproj
+++ b/ILSpy/ILSpy.csproj
@@ -845,11 +845,19 @@
-
+
+
+
+
+
+
+
+
powershell -NoProfile -ExecutionPolicy Bypass -File BuildTools/sort-resx.ps1
+
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index dc324a9e4..72d584ce8 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -810,6 +810,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Detect deconstruction assignments.
+ ///
+ public static string DecompilerSettings_Deconstruction {
+ get {
+ return ResourceManager.GetString("DecompilerSettings.Deconstruction", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Detect awaited using and foreach statements.
///
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index e61f61bb9..735d599d4 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -300,6 +300,9 @@ Are you sure you want to continue?
Decompile use of the 'dynamic' type
+
+ Detect deconstruction assignments
+
Detect awaited using and foreach statements
diff --git a/ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs b/ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs
new file mode 100644
index 000000000..2009d3785
--- /dev/null
+++ b/ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Threading.Tasks;
+
+using ICSharpCode.AvalonEdit.Highlighting;
+using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.ILSpy.TextView;
+using ICSharpCode.ILSpy.TreeNodes;
+using ICSharpCode.ILSpy.ViewModels;
+
+namespace ICSharpCode.ILSpy.TreeNodes
+{
+ [Export(typeof(IResourceNodeFactory))]
+ sealed class JsonResourceNodeFactory : IResourceNodeFactory
+ {
+ private readonly static string[] jsonFileExtensions = { ".json" };
+
+ public ILSpyTreeNode CreateNode(Resource resource)
+ {
+ Stream stream = resource.TryOpenStream();
+ if (stream == null)
+ return null;
+ return CreateNode(resource.Name, stream);
+ }
+
+ public ILSpyTreeNode CreateNode(string key, object data)
+ {
+ if (!(data is Stream))
+ return null;
+ foreach (string fileExt in jsonFileExtensions)
+ {
+ if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase))
+ return new JsonResourceEntryNode(key, (Stream)data);
+ }
+ return null;
+ }
+ }
+
+ sealed class JsonResourceEntryNode : ResourceEntryNode
+ {
+ string json;
+
+ public JsonResourceEntryNode(string key, Stream data)
+ : base(key, data)
+ {
+ }
+
+ // TODO : add Json Icon
+ public override object Icon => Images.Resource;
+
+ public override bool View(TabPageModel tabPage)
+ {
+ AvalonEditTextOutput output = new AvalonEditTextOutput();
+ IHighlightingDefinition highlighting = null;
+
+ tabPage.ShowTextView(textView => textView.RunWithCancellation(
+ token => Task.Factory.StartNew(
+ () => {
+ try {
+ // cache read XAML because stream will be closed after first read
+ if (json == null) {
+ using (var reader = new StreamReader(Data)) {
+ json = reader.ReadToEnd();
+ }
+ }
+ output.Write(json);
+ highlighting = HighlightingManager.Instance.GetDefinitionByExtension(".json");
+ }
+ catch (Exception ex) {
+ output.Write(ex.ToString());
+ }
+ return output;
+ }, token)
+ ).Then(t => textView.ShowNode(t, this, highlighting))
+ .HandleExceptions());
+ tabPage.SupportsLanguageSwitching = false;
+ return true;
+ }
+ }
+}
diff --git a/ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs b/ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs
new file mode 100644
index 000000000..035b944e8
--- /dev/null
+++ b/ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs
@@ -0,0 +1,98 @@
+// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Threading.Tasks;
+
+using ICSharpCode.AvalonEdit.Highlighting;
+using ICSharpCode.Decompiler.Metadata;
+using ICSharpCode.ILSpy.TextView;
+using ICSharpCode.ILSpy.TreeNodes;
+using ICSharpCode.ILSpy.ViewModels;
+
+namespace ICSharpCode.ILSpy.TreeNodes
+{
+ [Export(typeof(IResourceNodeFactory))]
+ sealed class TextResourceNodeFactory : IResourceNodeFactory
+ {
+ private readonly static string[] txtFileExtensions = { ".txt", ".md" };
+
+ public ILSpyTreeNode CreateNode(Resource resource)
+ {
+ Stream stream = resource.TryOpenStream();
+ if (stream == null)
+ return null;
+ return CreateNode(resource.Name, stream);
+ }
+
+ public ILSpyTreeNode CreateNode(string key, object data)
+ {
+ if (!(data is Stream))
+ return null;
+ foreach (string fileExt in txtFileExtensions)
+ {
+ if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase))
+ return new TextResourceEntryNode(key, (Stream)data);
+ }
+ return null;
+ }
+ }
+
+ sealed class TextResourceEntryNode : ResourceEntryNode
+ {
+ string text;
+
+ public TextResourceEntryNode(string key, Stream data)
+ : base(key, data)
+ {
+ }
+
+ public override object Icon => Images.Resource;
+
+ public override bool View(TabPageModel tabPage)
+ {
+ AvalonEditTextOutput output = new AvalonEditTextOutput();
+ IHighlightingDefinition highlighting = null;
+
+ tabPage.ShowTextView(textView => textView.RunWithCancellation(
+ token => Task.Factory.StartNew(
+ () => {
+ try {
+ // cache read text because stream will be closed after first read
+ if (text == null) {
+ using (var reader = new StreamReader(Data)) {
+ text = reader.ReadToEnd();
+ }
+ }
+ output.Write(text);
+ highlighting = null;
+ }
+ catch (Exception ex) {
+ output.Write(ex.ToString());
+ }
+ return output;
+ }, token)
+ ).Then(t => textView.ShowNode(t, this, highlighting))
+ .HandleExceptions());
+ tabPage.SupportsLanguageSwitching = false;
+ return true;
+ }
+ }
+}
diff --git a/doc/ILAst Pattern Matching.md b/doc/ILAst Pattern Matching.md
new file mode 100644
index 000000000..fab710123
--- /dev/null
+++ b/doc/ILAst Pattern Matching.md
@@ -0,0 +1,152 @@
+ILAst Pattern Matching
+=================
+
+Some IL instructions are classified as "patterns".
+```c#
+abstract class PatternMatchILInstruction : IStoreInstruction {
+ public ILInstruction TestedOperand { get; }
+ public ILVariable Variable { get; } // variable that receives the match result; may also be a temporary
+}
+
+public bool IsPattern(this ILInstruction inst, out ILInstruction testedOperand) => inst switch {
+ PatternMatchILInstruction pm => testedOperand = pm.TestedOperand; return true;
+ LogicNot logicNot => IsPattern(logicNot.Operand, out testedOperand),
+ Comp comp => testedOperand = comp.Left; return IsConstant(comp.Right);
+}
+```
+Every `match.*` instruction has the following properties:
+ * The `TestedOperand` specifies what gets matched against the pattern.
+ * The `Variable` stores the value of `TestedOperand` (after converting to the matched type, if appropriate).
+ * If this variable is also used outside the `match.*` node, it corresponds to the C# `single_variable_designation`.
+ * Otherwise it's a temporary used for pattern matching.
+ * I think in both cases it should have VariableKind.PatternVar
+ * The match instruction evaluates to `StackType.I4`: 0 if the pattern was matched, 1 otherwise.
+
+Some `match` instructions have a body with `List nestedPatterns`. Here every nested pattern must be a pattern according to `IsPattern()`, and the `testedOperand` of each must be a member of the `Variable` of the parent pattern. (members are: field, property, or deconstruction.result).
+(exception: `match.and`/`match.or`, these instead require the `testedOperand` to be exactly the `Variable` of the parent pattern)
+
+
+Examples
+--------
+1) `expr is var x`
+ =>
+ `match.var(x = expr)`
+ =>
+ ```
+ Block (VarPattern) {
+ stloc x(expr) // single eval expr
+ final: ldc.i4 1 // match always
+ }
+ ```
+2) `expr is T x`
+ =>
+ `match.type T(x = expr) {}`
+ =>
+ ```
+ Block (TypePattern) {
+ stloc x(isinst T(expr))
+ final: x != null
+ }
+ ```
+3) `expr is C { A: var x } z`
+ =>
+ ```
+ match.type C(z = expr) {
+ match.var(x = z.A)
+ }
+ ```
+ =>
+ ```
+ Block (TypePattern) {
+ stloc z(isinst T(expr))
+ final: (z != null)
+ && Block(VarPattern) {
+ stloc x(z.A)
+ final: ldc.i4 1
+ }
+ }
+ ```
+4) `expr is C { A: var x, B: 42, C: { A: 4 } } z`
+ =>
+ ```
+ match.type C(z = expr) {
+ match.var (x = z.A),
+ comp (z.B == 42),
+ match.recursive (temp2 = z.C) {
+ comp (temp2.A == 4)
+ }
+ }
+ ```
+ =>
+ ```
+ Block (TypePattern) {
+ stloc z(isinst C(expr))
+ final: (z != null)
+ && Block(VarPattern) {
+ stloc x(z.A)
+ final: ldc.i4 1
+ }
+ && comp (z.B == 42)
+ && Block(RecursivePattern) {
+ stloc temp2(z.C)
+ final: (temp2 != null)
+ && comp (temp2.A == 4)
+ }
+ }
+ ```
+5) `expr is C(var x, var y, <4) { ... }`
+ =>
+ ```
+ match.recursive.type.deconstruct(C tmp1 = expr) {
+ match.var(x = deconstruct.result0(tmp1)),
+ match.var(y = deconstruct.result1(tmp1)),
+ comp(deconstruct.result2(tmp1) < 4),
+ }
+ ```
+
+6) `expr is C(1, D(2, 3))`
+ =>
+ ```
+ match.type.deconstruct(C c = expr) {
+ comp(deconstruct.result0(c) == 1),
+ match.type.deconstruct(D d = deconstruct.result1(c)) {
+ comp(deconstruct.result0(d) == 2),
+ comp(deconstruct.result1(d) == 2),
+ }
+ }
+ ```
+
+7) `x is >= 0 and var y and <= 100`
+ ```
+ match.and(tmp1 = x) {
+ comp(tmp1 >= 0),
+ match.var(y = tmp1),
+ comp(tmp1 <= 100)
+ }
+ ```
+
+8) `x is not C _`
+ =>
+ ```
+ logic.not(
+ match.type(C tmp1 = x) {}
+ )
+ ```
+
+9) `expr is (var a, var b)` (when expr is object)
+ =>
+ ```
+ match.type.deconstruct(ITuple tmp = expr) {
+ match.var(a = deconstruct.result0(tmp)),
+ match.var(b = deconstruct.result1(tmp)),
+ }
+ ```
+
+10) `expr is (var a, var b)` (when expr is ValueTuple)
+ =>
+ ```
+ match.recursive(tmp = expr) {
+ match.var(a = tmp.Item1),
+ match.var(b = tmp.Item2),
+ }
+ ```
\ No newline at end of file
diff --git a/msbuild.binlog b/msbuild.binlog
new file mode 100644
index 000000000..357791088
Binary files /dev/null and b/msbuild.binlog differ