Browse Source

Merge pull request #2119 from icsharpcode/deconstruction-pattern-matching-foundation

C# 7.0 Deconstruction
pull/2126/head
Christoph Wille 6 years ago committed by GitHub
parent
commit
6415e9d1d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      BuildTools/update-assemblyinfo.ps1
  2. 6
      ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
  3. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  4. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  5. 249
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs
  6. 596
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs
  7. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  8. 82
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  9. 30
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  10. 2
      ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs
  11. 114
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  12. 53
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
  13. 58
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs
  14. 4
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs
  15. 12
      ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
  16. 3
      ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
  17. 21
      ICSharpCode.Decompiler/CSharp/Syntax/Statements/ForeachStatement.cs
  18. 137
      ICSharpCode.Decompiler/CSharp/Syntax/VariableDesignation.cs
  19. 43
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
  20. 8
      ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
  21. 20
      ICSharpCode.Decompiler/DecompilerSettings.cs
  22. 41
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  23. 5
      ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs
  24. 5
      ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs
  25. 20
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  26. 14
      ICSharpCode.Decompiler/IL/ILVariable.cs
  27. 255
      ICSharpCode.Decompiler/IL/Instructions.cs
  28. 13
      ICSharpCode.Decompiler/IL/Instructions.tt
  29. 14
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  30. 300
      ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs
  31. 67
      ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs
  32. 85
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  33. 267
      ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
  34. 435
      ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs
  35. 16
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  36. 2
      ICSharpCode.Decompiler/Output/TextTokenWriter.cs
  37. 5
      ICSharpCode.Decompiler/TypeSystem/TupleType.cs
  38. 9
      ILSpy.BamlDecompiler/Properties/launchSettings.json
  39. 32
      ILSpy.ReadyToRun/Properties/Resources.Designer.cs
  40. 9
      ILSpy.ReadyToRun/Properties/launchSettings.json
  41. 10
      ILSpy/ILSpy.csproj
  42. 9
      ILSpy/Properties/Resources.Designer.cs
  43. 3
      ILSpy/Properties/Resources.resx
  44. 99
      ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs
  45. 98
      ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs
  46. 152
      doc/ILAst Pattern Matching.md
  47. BIN
      msbuild.binlog

6
BuildTools/update-assemblyinfo.ps1

@ -53,7 +53,7 @@ function gitCommitHash() { @@ -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 { @@ -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 { @@ -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;
}
}

6
ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs

@ -202,6 +202,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -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)
{

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -91,7 +91,9 @@ @@ -91,7 +91,9 @@
<Compile Include="DisassemblerPrettyTestRunner.cs" />
<Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" />
<Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\DeconstructionTests.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\Pretty\DeconstructionTests.cs" />
<Compile Include="TestCases\Pretty\SwitchExpressions.cs" />
<None Include="TestCases\Pretty\NativeInts.cs" />
<None Include="TestCases\ILPretty\CallIndirect.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -500,6 +500,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -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);

249
ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs

@ -0,0 +1,249 @@ @@ -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<T, T2>
{
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<T, T2> GetSource<T, T2>()
{
Console.WriteLine("GetSource()");
return new DeconstructionSource<T, T2>();
}
private (T, T2) GetTuple<T, T2>()
{
Console.WriteLine("GetTuple<T, T2>()");
return default(ValueTuple<T, T2>);
}
private (T, T2, T3) GetTuple<T, T2, T3>()
{
Console.WriteLine("GetTuple<T, T2, T3>()");
return default(ValueTuple<T, T2, T3>);
}
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<MyInt?, MyInt>().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<MyInt?, MyInt>();
}
public void Property_IntToUIntConversion()
{
Console.WriteLine("Property_IntToUIntConversion:");
AssignmentTargets t0 = Get(0);
AssignmentTargets t1 = Get(1);
int a;
uint b;
GetSource<int, uint>().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<int, uint>().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<int, uint>();
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<int, int>();
} 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<int, int>();
} 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<int, int>();
} 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);
}
}
}
}

596
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs

@ -0,0 +1,596 @@ @@ -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<K, V>(this KeyValuePair<K, V> 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<T, T2>
{
public int Dummy {
get;
set;
}
public void Deconstruct(out T a, out T2 b)
{
a = default(T);
b = default(T2);
}
}
private class DeconstructionSource<T, T2, T3>
{
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<T, T2> GetSource<T, T2>()
{
return null;
}
private DeconstructionSource<T, T2, T3> GetSource<T, T2, T3>()
{
return null;
}
private ref T GetRef<T>()
{
throw new NotImplementedException();
}
private (T, T2) GetTuple<T, T2>()
{
return default((T, T2));
}
private (T, T2, T3) GetTuple<T, T2, T3>()
{
return default((T, T2, T3));
}
private AssignmentTargets Get(int i)
{
return null;
}
public void LocalVariable_NoConversion_Custom()
{
var (myInt3, x) = GetSource<MyInt?, MyInt>();
Console.WriteLine(myInt3);
Console.WriteLine(x);
}
public void LocalVariable_NoConversion_Tuple()
{
var (myInt, x) = GetTuple<MyInt?, MyInt>();
Console.WriteLine(myInt);
Console.WriteLine(x);
}
public void LocalVariable_NoConversion_Custom_DiscardFirst()
{
var (_, x, value) = GetSource<MyInt?, MyInt, int>();
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<MyInt?, MyInt, int>();
// Console.WriteLine(x);
// Console.WriteLine(value);
//}
public void LocalVariable_NoConversion_Custom_DiscardLast()
{
var (myInt3, x, _) = GetSource<MyInt?, MyInt, int>();
Console.WriteLine(myInt3);
Console.WriteLine(x);
}
public void LocalVariable_NoConversion_Tuple_DiscardLast()
{
var (myInt, x, _) = GetTuple<MyInt?, MyInt, int>();
Console.WriteLine(myInt);
Console.WriteLine(x);
}
public void LocalVariable_NoConversion_Custom_DiscardSecond()
{
var (myInt3, _, value) = GetSource<MyInt?, MyInt, int>();
Console.WriteLine(myInt3);
Console.WriteLine(value);
}
public void LocalVariable_NoConversion_Tuple_DiscardSecond()
{
var (myInt, _, value) = GetTuple<MyInt?, MyInt, int>();
Console.WriteLine(myInt);
Console.WriteLine(value);
}
public void LocalVariable_NoConversion_Custom_ReferenceTypes()
{
var (value, value2) = GetSource<string, string>();
Console.WriteLine(value);
Console.WriteLine(value2);
}
public void LocalVariable_NoConversion_Tuple_ReferenceTypes()
{
var (value, value2) = GetTuple<string, string>();
Console.WriteLine(value);
Console.WriteLine(value2);
}
public void LocalVariable_IntToLongConversion_Custom()
{
int value;
long value2;
(value, value2) = GetSource<int, int>();
Console.WriteLine(value);
Console.WriteLine(value2);
}
public void LocalVariable_IntToLongConversion_Tuple()
{
int value;
long value2;
(value, value2) = GetTuple<int, int>();
Console.WriteLine(value);
Console.WriteLine(value2);
}
public void LocalVariable_FloatToDoubleConversion_Custom()
{
int value;
double value2;
(value, value2) = GetSource<int, float>();
Console.WriteLine(value);
Console.WriteLine(value2);
}
public void LocalVariable_FloatToDoubleConversion_Tuple()
{
int value;
double value2;
(value, value2) = GetTuple<int, float>();
Console.WriteLine(value);
Console.WriteLine(value2);
}
// dynamic conversion is currently not supported
//public void LocalVariable_ImplicitReferenceConversion_Custom()
//{
// object value;
// dynamic value2;
// (value, value2) = GetSource<string, string>();
// Console.WriteLine(value);
// value2.UseMe();
//}
//public void LocalVariable_ImplicitReferenceConversion_Tuple()
//{
// object value;
// dynamic value2;
// (value, value2) = GetTuple<string, string>();
// Console.WriteLine(value);
// value2.UseMe();
//}
public void LocalVariable_NoConversion_ComplexValue_Custom()
{
var (myInt3, x) = new DeconstructionSource<MyInt?, MyInt> {
Dummy = 3
};
Console.WriteLine(myInt3);
Console.WriteLine(x);
}
public void Property_NoConversion_Custom()
{
(Get(0).NMy, Get(1).My) = GetSource<MyInt?, MyInt>();
}
public void Property_IntToLongConversion_Custom()
{
(Get(0).Int, Get(1).Long) = GetSource<int, int>();
}
public void Property_FloatToDoubleConversion_Custom()
{
(Get(0).Int, Get(1).Double) = GetSource<int, float>();
}
// dynamic conversion is not supported
//public void Property_ImplicitReferenceConversion_Custom()
//{
// (Get(0).Object, Get(1).Dynamic) = GetSource<string, string>();
//}
public void Property_NoConversion_Custom_DiscardFirst()
{
(_, Get(1).My) = GetSource<MyInt?, MyInt>();
}
public void Property_NoConversion_Custom_DiscardLast()
{
(Get(0).NMy, _) = GetSource<MyInt?, MyInt>();
}
public void Property_NoConversion_Tuple()
{
(Get(0).NMy, Get(1).My) = GetTuple<MyInt?, MyInt>();
}
public void Property_NoConversion_Tuple_DiscardLast()
{
(Get(0).NMy, Get(1).My, _) = GetTuple<MyInt?, MyInt, int>();
}
// currently we detect deconstruction, iff the first element is not discarded
//public void Property_NoConversion_Tuple_DiscardFirst()
//{
// (_, Get(1).My, Get(2).Int) = GetTuple<MyInt?, MyInt, int>();
//}
public void Property_NoConversion_Custom_DiscardSecond()
{
(Get(0).NMy, _, Get(2).Int) = GetSource<MyInt?, MyInt, int>();
}
public void Property_NoConversion_Tuple_DiscardSecond()
{
(Get(0).NMy, _, Get(2).Int) = GetTuple<MyInt?, MyInt, int>();
}
public void Property_NoConversion_Custom_ReferenceTypes()
{
(Get(0).String, Get(1).String) = GetSource<string, string>();
}
public void Property_NoConversion_Tuple_ReferenceTypes()
{
(Get(0).String, Get(1).String) = GetTuple<string, string>();
}
public void Property_IntToLongConversion_Tuple()
{
(Get(0).Int, Get(1).Long) = GetTuple<int, int>();
}
public void Property_FloatToDoubleConversion_Tuple()
{
(Get(0).Int, Get(1).Double) = GetTuple<int, float>();
}
public void RefLocal_NoConversion_Custom(out double a)
{
(a, GetRef<float>()) = GetSource<double, float>();
}
public void RefLocal_NoConversion_Tuple(out double a)
{
(a, GetRef<float>()) = GetTuple<double, float>();
}
public void RefLocal_FloatToDoubleConversion_Custom(out double a)
{
(a, GetRef<double>()) = GetSource<double, float>();
}
public void RefLocal_FloatToDoubleConversion_Custom2(out double a)
{
(a, GetRef<double>()) = GetSource<float, float>();
}
public void RefLocal_FloatToDoubleConversion_Tuple(out double a)
{
(a, GetRef<double>()) = GetTuple<double, float>();
}
public void RefLocal_NoConversion_Custom(out MyInt? a)
{
(a, GetRef<MyInt>()) = GetSource<MyInt?, MyInt>();
}
public void RefLocal_IntToLongConversion_Custom(out long a)
{
(a, GetRef<long>()) = GetSource<int, int>();
}
// dynamic conversion is not supported
//public void RefLocal_ImplicitReferenceConversion_Custom(out object a)
//{
// (a, GetRef<dynamic>()) = GetSource<string, string>();
//}
public void RefLocal_NoConversion_Custom_DiscardFirst()
{
(_, GetRef<MyInt>()) = GetSource<MyInt?, MyInt>();
}
public void RefLocal_NoConversion_Custom_DiscardLast(out MyInt? a)
{
(a, _) = GetSource<MyInt?, MyInt>();
}
public void RefLocal_NoConversion_Tuple(out MyInt? a)
{
(a, GetRef<MyInt>()) = GetTuple<MyInt?, MyInt>();
}
public void RefLocal_NoConversion_Tuple_DiscardLast(out MyInt? a)
{
(a, GetRef<MyInt>(), _) = GetTuple<MyInt?, MyInt, int>();
}
// currently we detect deconstruction, iff the first element is not discarded
//public void RefLocal_NoConversion_Tuple_DiscardFirst(out var a)
//{
// (_, GetRef<var>(), GetRef<var>()) = GetTuple<MyInt?, MyInt, int>();
//}
public void RefLocal_NoConversion_Custom_DiscardSecond(out MyInt? a)
{
(a, _, GetRef<int>()) = GetSource<MyInt?, MyInt, int>();
}
public void RefLocal_NoConversion_Tuple_DiscardSecond(out MyInt? a)
{
(a, _, GetRef<int>()) = GetTuple<MyInt?, MyInt, int>();
}
public void RefLocal_NoConversion_Custom_ReferenceTypes(out string a)
{
(a, GetRef<string>()) = GetSource<string, string>();
}
public void RefLocal_NoConversion_Tuple_ReferenceTypes(out string a)
{
(a, GetRef<string>()) = GetTuple<string, string>();
}
public void RefLocal_IntToLongConversion_Tuple(out long a)
{
(a, GetRef<long>()) = GetTuple<int, int>();
}
//public void ArrayAssign_FloatToDoubleConversion_Custom(double[] arr)
//{
// (arr[0], arr[1], arr[2]) = GetSource<double, float, double>();
//}
public void Field_NoConversion_Custom()
{
(Get(0).IntField, Get(1).IntField) = GetSource<int, int>();
}
public void Field_NoConversion_Tuple()
{
(Get(0).IntField, Get(1).IntField) = GetTuple<int, int>();
}
public void Field_IntToLongConversion_Custom()
{
(Get(0).IntField, Get(1).LongField) = GetSource<int, int>();
}
public void Field_IntToLongConversion_Tuple()
{
(Get(0).IntField, Get(1).LongField) = GetTuple<int, int>();
}
public void Field_FloatToDoubleConversion_Custom()
{
(Get(0).DoubleField, Get(1).DoubleField) = GetSource<double, float>();
}
public void Field_FloatToDoubleConversion_Tuple()
{
(Get(0).DoubleField, Get(1).DoubleField) = GetTuple<double, float>();
}
// dynamic conversion is not supported
//public void Field_ImplicitReferenceConversion_Custom()
//{
// (Get(0).ObjectField, Get(1).DynamicField) = GetSource<string, string>();
//}
public void Field_NoConversion_Custom_DiscardFirst()
{
(_, Get(1).MyField) = GetSource<MyInt?, MyInt>();
}
public void Field_NoConversion_Custom_DiscardLast()
{
(Get(0).NMyField, _) = GetSource<MyInt?, MyInt>();
}
public void Field_NoConversion_Tuple_DiscardLast()
{
(Get(0).NMyField, Get(1).MyField, _) = GetTuple<MyInt?, MyInt, int>();
}
// currently we detect deconstruction, iff the first element is not discarded
//public void Field_NoConversion_Tuple_DiscardFirst()
//{
// (_, Get(1).MyField, Get(2).IntField) = GetTuple<MyInt?, MyInt, int>();
//}
public void Field_NoConversion_Custom_DiscardSecond()
{
(Get(0).NMyField, _, Get(2).IntField) = GetSource<MyInt?, MyInt, int>();
}
public void Field_NoConversion_Tuple_DiscardSecond()
{
(Get(0).NMyField, _, Get(2).IntField) = GetTuple<MyInt?, MyInt, int>();
}
public void Field_NoConversion_Custom_ReferenceTypes()
{
(Get(0).StringField, Get(1).StringField) = GetSource<string, string>();
}
public void Field_NoConversion_Tuple_ReferenceTypes()
{
(Get(0).StringField, Get(1).StringField) = GetTuple<string, string>();
}
public void DeconstructDictionaryForEach(Dictionary<string, int> 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);
}
}
}
}

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -144,6 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -144,6 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformCollectionAndObjectInitializers(),
new TransformExpressionTrees(),
new IndexRangeTransform(),
new DeconstructionTransform(),
new NamedArgumentTransform(),
new UserDefinedLogicTransform()
),

82
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -3386,6 +3386,88 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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<ILVariable, ILVariable> conversionMapping = new Dictionary<ILVariable, ILVariable>();
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<MatchInstruction>()) {
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";

30
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -791,6 +791,17 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -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 @@ -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 @@ -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);

2
ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs

@ -269,7 +269,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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);
}

114
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -504,12 +504,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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<Expression>("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 @@ -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 @@ -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 @@ -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 @@ -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<MatchInstruction>()) {
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 @@ -703,7 +743,12 @@ namespace ICSharpCode.Decompiler.CSharp
/// ... (ldloca copy) ...
/// </code>
/// </summary>
IntroduceNewVariableAndLocalCopy
IntroduceNewVariableAndLocalCopy,
/// <summary>
/// call get_Current() is the tested operand of a deconstruct instruction.
/// and the deconstruct instruction is the first statement in the loop body.
/// </summary>
Deconstruction,
}
/// <summary>
@ -716,11 +761,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -716,11 +761,14 @@ namespace ICSharpCode.Decompiler.CSharp
/// <param name="singleGetter">Returns the call instruction invoking Current's getter.</param>
/// <param name="foreachVariable">Returns the the foreach variable, if a suitable was found. This variable is only assigned once and its assignment is the first statement in <paramref name="loopBody"/>.</param>
/// <returns><see cref="RequiredGetCurrentTransformation"/> for details.</returns>
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<ILInstruction>().Concat(enumerator.AddressInstructions.OfType<ILInstruction>())).Where(ld => !ld.IsDescendantOf(moveNextUsage)).ToArray();
var loads = enumerator.LoadInstructions.OfType<ILInstruction>()
.Concat(enumerator.AddressInstructions.OfType<ILInstruction>())
.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 @@ -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 @@ -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 @@ -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<ILVariable>(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;
}
/// <summary>
/// Determines whether storeInst.Variable is only assigned once and used only inside <paramref name="usingContainer"/>.
/// 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.)
/// </summary>
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 @@ -766,6 +852,8 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (storeInst.Variable.StoreInstructions.OfType<ILInstruction>().Any(st => st != storeInst))
return false;
if (!(storeInst.Variable.CaptureScope == null || storeInst.Variable.CaptureScope == loopContainer))
return false;
return true;
bool AddressUseAllowed(LdLoca la)

53
ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs

@ -501,9 +501,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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);

58
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/DeclarationExpression.cs

@ -0,0 +1,58 @@ @@ -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
{
/// <summary>
/// TypeName VariableDesignation
/// </summary>
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<T>(IAstVisitor<T> visitor)
{
return visitor.VisitDeclarationExpression(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> 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);
}
}
}

4
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs

@ -42,10 +42,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -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;

12
ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs

@ -35,6 +35,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -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);
@ -156,6 +157,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -156,6 +157,9 @@ 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 @@ -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 @@ -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 @@ -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 @@ -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);

3
ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs

@ -47,9 +47,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -47,9 +47,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public static readonly Role<Statement> EmbeddedStatement = new Role<Statement> ("EmbeddedStatement", Statement.Null);
public readonly static Role<EntityDeclaration> TypeMemberRole = new Role<EntityDeclaration> ("TypeMember");
public static readonly Role<VariableDesignation> VariableDesignationRole = new Role<VariableDesignation>("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 ("(");

21
ICSharpCode.Decompiler/CSharp/Syntax/Statements/ForeachStatement.cs

@ -48,22 +48,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -48,22 +48,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
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 @@ -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);
}
}

137
ICSharpCode.Decompiler/CSharp/Syntax/VariableDesignation.cs

@ -0,0 +1,137 @@ @@ -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<T>(IAstVisitor<T> visitor)
{
return visitor.VisitNullNode(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{
return visitor.VisitNullNode(this, data);
}
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
return other == null || other.IsNull;
}
}
#endregion
}
/// <summary>
/// Identifier
/// </summary>
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<T>(IAstVisitor<T> visitor)
{
return visitor.VisitSingleVariableDesignation(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> 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);
}
}
/// <summary>
/// ( VariableDesignation (, VariableDesignation)* )
/// </summary>
public class ParenthesizedVariableDesignation : VariableDesignation
{
public CSharpTokenNode LParToken {
get { return GetChildByRole(Roles.LPar); }
}
public AstNodeCollection<VariableDesignation> 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<T>(IAstVisitor<T> visitor)
{
return visitor.VisitParenthesizedVariableDesignation(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> 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);
}
}
}

43
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -102,6 +102,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -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 @@ -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 @@ -133,7 +135,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
variableDict.Clear();
}
}
/// <summary>
/// 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 @@ -425,6 +426,44 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
}
private void InsertDeconstructionVariableDeclarations()
{
var usedVariables = new HashSet<ILVariable>();
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<DeconstructInstruction>();
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 @@ -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)) {

8
ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs

@ -337,14 +337,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -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<IdentifierExpression>("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 @@ -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<IdentifierExpression>("collection").Single().Detach(),
EmbeddedStatement = body
};
@ -504,7 +504,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -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;

20
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -93,6 +93,7 @@ namespace ICSharpCode.Decompiler @@ -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 @@ -1162,6 +1163,23 @@ namespace ICSharpCode.Decompiler
}
}
bool deconstruction = true;
/// <summary>
/// Gets/Sets whether C# 7.0 deconstruction should be detected.
/// </summary>
[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;
/// <summary>
@ -1182,7 +1200,7 @@ namespace ICSharpCode.Decompiler @@ -1182,7 +1200,7 @@ namespace ICSharpCode.Decompiler
bool ranges = true;
/// <summary>
/// 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.
/// </summary>
[Category("C# 8.0 / VS 2019")]
[Description("DecompilerSettings.Ranges")]

41
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -646,6 +646,8 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -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 @@ -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);
}
/// <summary>
/// Evaluates a match instruction.
/// </summary>
/// <returns>
/// A pair of:
/// * The state after the pattern matches
/// * The state after the pattern fails to match
/// </returns>
/// <remarks>
/// <c>this.state</c> is invalid after this function was called, and must be overwritten
/// with one of the return values.
/// </remarks>
(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);

5
ICSharpCode.Decompiler/FlowAnalysis/DefiniteAssignmentVisitor.cs

@ -169,6 +169,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -169,6 +169,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
HandleStore(inst.Variable);
}
protected override void HandleMatchStore(MatchInstruction inst)
{
HandleStore(inst.Variable);
}
protected override void BeginTryCatchHandler(TryCatchHandler inst)
{
HandleStore(inst.Variable);

5
ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitionsVisitor.cs

@ -381,6 +381,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -381,6 +381,11 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
HandleStore(inst, inst.Variable);
}
protected override void HandleMatchStore(MatchInstruction inst)
{
HandleStore(inst, inst.Variable);
}
protected override void BeginTryCatchHandler(TryCatchHandler inst)
{
base.BeginTryCatchHandler(inst);

20
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -70,9 +70,15 @@ @@ -70,9 +70,15 @@
<Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="CSharp\ProjectDecompiler\TargetFramework.cs" />
<Compile Include="CSharp\ProjectDecompiler\TargetServices.cs" />
<Compile Include="CSharp\Syntax\Expressions\DeclarationExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\SwitchExpression.cs" />
<Compile Include="CSharp\Syntax\FunctionPointerType.cs" />
<Compile Include="CSharp\Syntax\VariableDesignation.cs" />
<Compile Include="IL\Transforms\DeconstructionTransform.cs" />
<Compile Include="IL\Transforms\FixLoneIsInst.cs" />
<Compile Include="IL\Instructions\DeconstructInstruction.cs" />
<Compile Include="IL\Instructions\DeconstructResultInstruction.cs" />
<Compile Include="IL\Instructions\MatchInstruction.cs" />
<Compile Include="IL\Transforms\IndexRangeTransform.cs" />
<Compile Include="CSharp\TranslatedStatement.cs" />
<Compile Include="DebugInfo\KnownGuids.cs" />
@ -634,13 +640,25 @@ @@ -634,13 +640,25 @@
</ItemGroup>
<Target Name="ILSpyUpdateAssemblyInfo" BeforeTargets="BeforeBuild">
<ItemGroup>
<UpdateAssemblyInfoStamp Include="obj\update-assemblyinfo-last-commit-hash.txt" />
</ItemGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
<UpdateAssemblyInfo>powershell -NoProfile -ExecutionPolicy Bypass -File BuildTools/update-assemblyinfo.ps1 $(Configuration)</UpdateAssemblyInfo>
<GitRevParse>git rev-parse HEAD^^{commit}</GitRevParse>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT' ">
<UpdateAssemblyInfo>pwsh -NoProfile -ExecutionPolicy Bypass -File BuildTools/update-assemblyinfo.ps1 $(Configuration)</UpdateAssemblyInfo>
<GitRevParse>git rev-parse HEAD^{commit}</GitRevParse>
</PropertyGroup>
<Exec WorkingDirectory=".." Command="$(UpdateAssemblyInfo)" Timeout="60000" />
<ReadLinesFromFile ContinueOnError="true" File="@(UpdateAssemblyInfoStamp)">
<Output TaskParameter="Lines" PropertyName="LastCommitHash" />
</ReadLinesFromFile>
<Exec ContinueOnError="true" WorkingDirectory=".." Command="$(GitRevParse)" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="CommitHash" />
</Exec>
<Exec WorkingDirectory=".." Command="$(UpdateAssemblyInfo)" Timeout="60000" Condition="'$(CommitHash)'!='$(LastCommitHash)'" />
<WriteLinesToFile Lines="$(CommitHash)" File="@(UpdateAssemblyInfoStamp)" Overwrite="true" Condition="'$(CommitHash)'!='$(LastCommitHash)'" />
</Target>
</Project>

14
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -75,6 +75,14 @@ namespace ICSharpCode.Decompiler.IL @@ -75,6 +75,14 @@ namespace ICSharpCode.Decompiler.IL
/// Local variable that holds the display class used for lambdas within this function.
/// </summary>
DisplayClassLocal,
/// <summary>
/// Local variable declared within a pattern match.
/// </summary>
PatternLocal,
/// <summary>
/// Temporary variable declared in a deconstruction init section.
/// </summary>
DeconstructionInitTemporary,
}
static class VariableKindExtensions
@ -433,6 +441,12 @@ namespace ICSharpCode.Decompiler.IL @@ -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();
}

255
ICSharpCode.Decompiler/IL/Instructions.cs

@ -220,6 +220,8 @@ namespace ICSharpCode.Decompiler.IL @@ -220,6 +220,8 @@ namespace ICSharpCode.Decompiler.IL
DynamicInvokeInstruction,
/// <summary>ILAst representation of a call to the Binder.IsEvent method inside a dynamic expression.</summary>
DynamicIsEventInstruction,
/// <summary>ILAst representation of C# patterns</summary>
MatchInstruction,
/// <summary>Push a typed reference of type class onto the stack.</summary>
MakeRefAny,
/// <summary>Push the type token stored in a typed reference.</summary>
@ -230,6 +232,10 @@ namespace ICSharpCode.Decompiler.IL @@ -230,6 +232,10 @@ namespace ICSharpCode.Decompiler.IL
YieldReturn,
/// <summary>C# await operator.</summary>
Await,
/// <summary>Deconstruction statement</summary>
DeconstructInstruction,
/// <summary>Represents a deconstructed value</summary>
DeconstructResultInstruction,
/// <summary>Matches any node</summary>
AnyNode,
}
@ -6085,6 +6091,147 @@ namespace ICSharpCode.Decompiler.IL @@ -6085,6 +6091,147 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>ILAst representation of C# patterns</summary>
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<ILInstruction>(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;
/// <summary>Returns the method operand.</summary>
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<ILInstruction> 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<ILInstruction>(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<T>(ILVisitor<T> visitor)
{
return visitor.VisitMatchInstruction(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> 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
{
/// <summary>Push a typed reference of type class onto the stack.</summary>
public sealed partial class MakeRefAny : UnaryInstruction
@ -6397,6 +6544,61 @@ namespace ICSharpCode.Decompiler.IL @@ -6397,6 +6544,61 @@ namespace ICSharpCode.Decompiler.IL
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Deconstruction statement</summary>
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<T>(ILVisitor<T> visitor)
{
return visitor.VisitDeconstructInstruction(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> 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
{
/// <summary>Represents a deconstructed value</summary>
public sealed partial class DeconstructResultInstruction : UnaryInstruction
{
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitDeconstructResultInstruction(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitDeconstructResultInstruction(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> 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
{
/// <summary>Matches any node</summary>
@ -6808,6 +7010,10 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -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);
}
}
/// <summary>
@ -7194,6 +7408,10 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -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);
}
}
/// <summary>
@ -7580,6 +7806,10 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -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 @@ -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 @@ -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;

13
ICSharpCode.Decompiler/IL/Instructions.tt

@ -335,6 +335,13 @@ @@ -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 @@ @@ -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),
};

14
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -171,6 +171,12 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -420,5 +426,13 @@ namespace ICSharpCode.Decompiler.IL
/// }
/// </example>
CallWithNamedArgs,
/// <summary>
/// <see cref="DeconstructInstruction"/>
/// </summary>
DeconstructionConversions,
/// <summary>
/// <see cref="DeconstructInstruction"/>
/// </summary>
DeconstructionAssignments,
}
}

300
ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs

@ -0,0 +1,300 @@ @@ -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<StLoc>(this, 0);
}
public readonly InstructionCollection<StLoc> 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<ILVariable>();
var conversionVariables = new HashSet<ILVariable>();
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<MatchInstruction>()) {
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);
}
}
}
}
}
}

67
ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs

@ -0,0 +1,67 @@ @@ -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);
}
}
}

85
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -99,6 +99,86 @@ namespace ICSharpCode.Decompiler.IL @@ -99,6 +99,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;
}
/// <summary>
/// Returns whether this appears before other in a post-order walk of the whole tree.
/// </summary>
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;
}
/// <summary>
/// Gets the stack type of the value produced by this instruction.
/// </summary>
@ -632,9 +712,10 @@ namespace ICSharpCode.Decompiler.IL @@ -632,9 +712,10 @@ namespace ICSharpCode.Decompiler.IL
/// <param name="childPointer">Reference to the field holding the child</param>
/// <param name="newValue">New child</param>
/// <param name="index">Index of the field in the Children collection</param>
protected internal void SetChildInstruction(ref ILInstruction childPointer, ILInstruction newValue, int index)
protected internal void SetChildInstruction<T>(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;

267
ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs

@ -0,0 +1,267 @@ @@ -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)
{
}
/// <summary>
/// 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.
/// </summary>
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();
}
}
}
}

435
ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs

@ -0,0 +1,435 @@ @@ -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
{
/// <summary>
///
/// </summary>
class DeconstructionTransform : IStatementTransform
{
StatementTransformContext context;
readonly Dictionary<ILVariable, int> deconstructionResultsLookup = new Dictionary<ILVariable, int>();
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:
<empty>
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;
}
/// <summary>
/// Get index of deconstruction result or tuple element
/// Returns -1 on failure.
/// </summary>
int FindIndex(ILInstruction inst, out Action<DeconstructInstruction> 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;
}
/// <summary>
/// stloc v(value)
/// expr(..., deconstruct { ... }, ...)
/// =>
/// expr(..., deconstruct { init: stloc v(value) ... }, ...)
/// </summary>
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<DeconstructInstruction> 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<ILVariable, ConversionInfo> conversions,
out List<StLoc> conversionStLocs,
ref Action<DeconstructInstruction> delayedActions)
{
conversions = new Dictionary<ILVariable, ConversionInfo>();
conversionStLocs = new List<StLoc>();
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<ILVariable, ConversionInfo> conversions,
List<StLoc> conversionStLocs,
ref Action<DeconstructInstruction> 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<DeconstructInstruction> 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<DeconstructInstruction> 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;
}
}
}

16
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -31,6 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -31,6 +31,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
None = 0,
Aggressive = 1,
IntroduceNamedArguments = 2,
FindDeconstruction = 4,
}
/// <summary>
@ -503,6 +504,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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 <see cref="InliningOptions.IntroduceNamedArguments"/>.
/// </summary>
NamedArgument,
/// <summary>
/// Found a deconstruction.
/// Only used with <see cref="InliningOptions.FindDeconstruction"/>.
/// </summary>
Deconstruction,
}
internal readonly struct FindResult
@ -575,6 +584,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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);
}
}
/// <summary>
@ -611,6 +625,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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))

2
ICSharpCode.Decompiler/Output/TextTokenWriter.cs

@ -164,7 +164,7 @@ namespace ICSharpCode.Decompiler @@ -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<ILVariableResolveResult>()?.Variable;
if (variable != null)
return variable;

5
ICSharpCode.Decompiler/TypeSystem/TupleType.cs

@ -44,6 +44,11 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -44,6 +44,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
public ImmutableArray<IType> ElementTypes { get; }
/// <summary>
/// Gets the cardinality of the tuple.
/// </summary>
public int Cardinality => ElementTypes.Length;
/// <summary>
/// Gets the names of the tuple elements.
/// </summary>

9
ILSpy.BamlDecompiler/Properties/launchSettings.json

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
{
"profiles": {
"ILSpy.BamlDecompiler": {
"commandName": "Executable",
"executablePath": "C:\\Users\\sie_p\\Projects\\ILSpy\\ILSpy\\bin\\Debug\\net472\\ILSpy.exe",
"commandLineArgs": "/separate"
}
}
}

32
ILSpy.ReadyToRun/Properties/Resources.Designer.cs generated

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本: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.
// </auto-generated>
//------------------------------------------------------------------------------
@ -13,12 +13,12 @@ namespace ILSpy.ReadyToRun.Properties { @@ -13,12 +13,12 @@ namespace ILSpy.ReadyToRun.Properties {
/// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 此类是由 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 { @@ -33,7 +33,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
/// <summary>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
@ -47,8 +47,8 @@ namespace ILSpy.ReadyToRun.Properties { @@ -47,8 +47,8 @@ namespace ILSpy.ReadyToRun.Properties {
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性
/// 重写当前线程的 CurrentUICulture 属性。
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
@ -61,7 +61,7 @@ namespace ILSpy.ReadyToRun.Properties { @@ -61,7 +61,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
/// <summary>
/// 查找类似 Disassembly Format 的本地化字符串。
/// Looks up a localized string similar to Disassembly Format.
/// </summary>
public static string DisassemblyFormat {
get {
@ -70,7 +70,7 @@ namespace ILSpy.ReadyToRun.Properties { @@ -70,7 +70,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
/// <summary>
/// 查找类似 ReadyToRun 的本地化字符串。
/// Looks up a localized string similar to ReadyToRun.
/// </summary>
public static string ReadyToRun {
get {
@ -79,7 +79,7 @@ namespace ILSpy.ReadyToRun.Properties { @@ -79,7 +79,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
/// <summary>
/// 查找类似 Show Debug Info 的本地化字符串。
/// Looks up a localized string similar to Show Debug Info.
/// </summary>
public static string ShowDebugInfo {
get {
@ -88,7 +88,7 @@ namespace ILSpy.ReadyToRun.Properties { @@ -88,7 +88,7 @@ namespace ILSpy.ReadyToRun.Properties {
}
/// <summary>
/// 查找类似 Show Unwind Info 的本地化字符串。
/// Looks up a localized string similar to Show Unwind Info.
/// </summary>
public static string ShowUnwindInfo {
get {

9
ILSpy.ReadyToRun/Properties/launchSettings.json

@ -0,0 +1,9 @@ @@ -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"
}
}
}

10
ILSpy/ILSpy.csproj

@ -845,11 +845,19 @@ @@ -845,11 +845,19 @@
<Import Project="$(VCToolsVersionPropsFile)" Condition="Exists('$(VCToolsVersionPropsFile)')" />
<Target Name="SortResX" BeforeTargets="BeforeBuild">
<ItemGroup>
<SortResXInput Include="Properties\*.resx" />
<SortResXInput Include="..\ILSpy.AddIn\*.resx" />
<SortResXInput Include="..\ILSpy.ReadyToRun\Properties\*.resx" />
<SortResXStamp Include="obj\sort-resx.stamp" />
</ItemGroup>
<Target Name="SortResX" BeforeTargets="BeforeBuild" Inputs="@(SortResXInput)" Outputs="@(SortResXStamp)">
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
<SortResX>powershell -NoProfile -ExecutionPolicy Bypass -File BuildTools/sort-resx.ps1</SortResX>
</PropertyGroup>
<Exec WorkingDirectory=".." Command="$(SortResX)" Timeout="60000" />
<Touch Files="@(SortResXStamp)" AlwaysCreate="true" />
</Target>
<Target Name="ApplyStackExtension" AfterTargets="PostBuildEvent">

9
ILSpy/Properties/Resources.Designer.cs generated

@ -810,6 +810,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -810,6 +810,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Detect deconstruction assignments.
/// </summary>
public static string DecompilerSettings_Deconstruction {
get {
return ResourceManager.GetString("DecompilerSettings.Deconstruction", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Detect awaited using and foreach statements.
/// </summary>

3
ILSpy/Properties/Resources.resx

@ -300,6 +300,9 @@ Are you sure you want to continue?</value> @@ -300,6 +300,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.DecompileUseOfTheDynamicType" xml:space="preserve">
<value>Decompile use of the 'dynamic' type</value>
</data>
<data name="DecompilerSettings.Deconstruction" xml:space="preserve">
<value>Detect deconstruction assignments</value>
</data>
<data name="DecompilerSettings.DetectAsyncUsingAndForeachStatements" xml:space="preserve">
<value>Detect awaited using and foreach statements</value>
</data>

99
ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs

@ -0,0 +1,99 @@ @@ -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;
}
}
}

98
ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs

@ -0,0 +1,98 @@ @@ -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;
}
}
}

152
doc/ILAst Pattern Matching.md

@ -0,0 +1,152 @@ @@ -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<ILInstruction> 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<int, int>)
=>
```
match.recursive(tmp = expr) {
match.var(a = tmp.Item1),
match.var(b = tmp.Item2),
}
```

BIN
msbuild.binlog

Binary file not shown.
Loading…
Cancel
Save