mirror of https://github.com/icsharpcode/ILSpy.git
47 changed files with 3352 additions and 78 deletions
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"profiles": { |
||||||
|
"ILSpy.BamlDecompiler": { |
||||||
|
"commandName": "Executable", |
||||||
|
"executablePath": "C:\\Users\\sie_p\\Projects\\ILSpy\\ILSpy\\bin\\Debug\\net472\\ILSpy.exe", |
||||||
|
"commandLineArgs": "/separate" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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), |
||||||
|
} |
||||||
|
``` |
||||||
Binary file not shown.
Loading…
Reference in new issue