mirror of https://github.com/icsharpcode/ILSpy.git
47 changed files with 3352 additions and 78 deletions
@ -0,0 +1,249 @@
@@ -0,0 +1,249 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness |
||||
{ |
||||
class DeconstructionTests |
||||
{ |
||||
public static void Main() |
||||
{ |
||||
new DeconstructionTests().Test(); |
||||
} |
||||
|
||||
public struct MyInt |
||||
{ |
||||
public static implicit operator int(MyInt x) |
||||
{ |
||||
Console.WriteLine("int op_Implicit(MyInt)"); |
||||
return 0; |
||||
} |
||||
|
||||
public static implicit operator MyInt(int x) |
||||
{ |
||||
Console.WriteLine("MyInt op_Implicit(int)"); |
||||
return default(MyInt); |
||||
} |
||||
} |
||||
|
||||
private class DeconstructionSource<T, T2> |
||||
{ |
||||
public int Dummy { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public void Deconstruct(out T a, out T2 b) |
||||
{ |
||||
Console.WriteLine("Deconstruct"); |
||||
a = default(T); |
||||
b = default(T2); |
||||
} |
||||
} |
||||
|
||||
private class AssignmentTargets |
||||
{ |
||||
int id; |
||||
|
||||
public AssignmentTargets(int id) |
||||
{ |
||||
this.id = id; |
||||
} |
||||
|
||||
public int IntField; |
||||
|
||||
public int? NullableIntField; |
||||
|
||||
public MyInt MyIntField; |
||||
|
||||
public MyInt? NullableMyIntField; |
||||
|
||||
public MyInt My { |
||||
get { |
||||
Console.WriteLine($"{id}.get_My()"); |
||||
return default; |
||||
} |
||||
set { |
||||
Console.WriteLine($"{id}.set_My({value})"); |
||||
} |
||||
} |
||||
|
||||
public MyInt? NMy { |
||||
get { |
||||
Console.WriteLine($"{id}.get_NMy()"); |
||||
return default; |
||||
} |
||||
set { |
||||
Console.WriteLine($"{id}.set_NMy({value})"); |
||||
} |
||||
} |
||||
|
||||
public int IntProperty { |
||||
get { |
||||
Console.WriteLine($"{id}.get_IntProperty()"); |
||||
return default; |
||||
} |
||||
set { |
||||
Console.WriteLine($"{id}.set_IntProperty({value})"); |
||||
} |
||||
} |
||||
|
||||
public uint UIntProperty { |
||||
get { |
||||
Console.WriteLine($"{id}.get_UIntProperty()"); |
||||
return default; |
||||
} |
||||
set { |
||||
Console.WriteLine($"{id}.set_UIntProperty({value})"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private DeconstructionSource<T, T2> GetSource<T, T2>() |
||||
{ |
||||
Console.WriteLine("GetSource()"); |
||||
return new DeconstructionSource<T, T2>(); |
||||
} |
||||
|
||||
private (T, T2) GetTuple<T, T2>() |
||||
{ |
||||
Console.WriteLine("GetTuple<T, T2>()"); |
||||
return default(ValueTuple<T, T2>); |
||||
} |
||||
|
||||
private (T, T2, T3) GetTuple<T, T2, T3>() |
||||
{ |
||||
Console.WriteLine("GetTuple<T, T2, T3>()"); |
||||
return default(ValueTuple<T, T2, T3>); |
||||
} |
||||
|
||||
private AssignmentTargets Get(int i) |
||||
{ |
||||
Console.WriteLine($"Get({i})"); |
||||
return new AssignmentTargets(i); |
||||
} |
||||
|
||||
public void Test() |
||||
{ |
||||
Property_NoDeconstruction_SwappedAssignments(); |
||||
Property_NoDeconstruction_SwappedInits(); |
||||
Property_IntToUIntConversion(); |
||||
NoDeconstruction_NotUsingConver(); |
||||
NoDeconstruction_NotUsingConver_Tuple(); |
||||
NullReferenceException_Field_Deconstruction(out _); |
||||
NullReferenceException_RefLocalReferencesField_Deconstruction(out _); |
||||
NullReferenceException_RefLocalReferencesArrayElement_Deconstruction(out _, null); |
||||
DeconstructTupleSameVar(("a", "b")); |
||||
DeconstructTupleListForEachSameVar(new List<(string, string)> { ("a", "b") }); |
||||
} |
||||
|
||||
public void Property_NoDeconstruction_SwappedAssignments() |
||||
{ |
||||
Console.WriteLine("Property_NoDeconstruction_SwappedAssignments:"); |
||||
AssignmentTargets customDeconstructionAndConversion = Get(0); |
||||
AssignmentTargets customDeconstructionAndConversion2 = Get(1); |
||||
GetSource<MyInt?, MyInt>().Deconstruct(out MyInt? x, out MyInt y); |
||||
MyInt myInt2 = customDeconstructionAndConversion2.My = y; |
||||
MyInt? myInt4 = customDeconstructionAndConversion.NMy = x; |
||||
} |
||||
|
||||
public void Property_NoDeconstruction_SwappedInits() |
||||
{ |
||||
Console.WriteLine("Property_NoDeconstruction_SwappedInits:"); |
||||
AssignmentTargets customDeconstructionAndConversion = Get(1); |
||||
(Get(0).NMy, customDeconstructionAndConversion.My) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void Property_IntToUIntConversion() |
||||
{ |
||||
Console.WriteLine("Property_IntToUIntConversion:"); |
||||
AssignmentTargets t0 = Get(0); |
||||
AssignmentTargets t1 = Get(1); |
||||
int a; |
||||
uint b; |
||||
GetSource<int, uint>().Deconstruct(out a, out b); |
||||
t0.UIntProperty = (uint)a; |
||||
t1.IntProperty = (int)b; |
||||
} |
||||
|
||||
public void NoDeconstruction_NotUsingConver() |
||||
{ |
||||
Console.WriteLine("NoDeconstruction_NotUsingConver:"); |
||||
AssignmentTargets t0 = Get(0); |
||||
int a; |
||||
uint b; |
||||
GetSource<int, uint>().Deconstruct(out a, out b); |
||||
long c = a; |
||||
t0.IntProperty = a; |
||||
t0.UIntProperty = b; |
||||
Console.WriteLine(c); |
||||
} |
||||
|
||||
public void NoDeconstruction_NotUsingConver_Tuple() |
||||
{ |
||||
Console.WriteLine("NoDeconstruction_NotUsingConver_Tuple:"); |
||||
AssignmentTargets t0 = Get(0); |
||||
var t = GetTuple<int, uint>(); |
||||
long c = t.Item1; |
||||
t0.IntProperty = t.Item1; |
||||
t0.UIntProperty = t.Item2; |
||||
Console.WriteLine(c); |
||||
} |
||||
|
||||
public void NullReferenceException_Field_Deconstruction(out int a) |
||||
{ |
||||
try { |
||||
AssignmentTargets t0 = null; |
||||
(t0.IntField, a) = GetSource<int, int>(); |
||||
} catch (Exception ex) { |
||||
a = 0; |
||||
Console.WriteLine(ex.GetType().FullName); |
||||
} |
||||
} |
||||
|
||||
public void NullReferenceException_RefLocalReferencesField_Deconstruction(out int a) |
||||
{ |
||||
try { |
||||
AssignmentTargets t0 = null; |
||||
ref int i = ref t0.IntField; |
||||
(i, a) = GetSource<int, int>(); |
||||
} catch (Exception ex) { |
||||
a = 0; |
||||
Console.WriteLine(ex.GetType().FullName); |
||||
} |
||||
} |
||||
|
||||
public void NullReferenceException_RefLocalReferencesArrayElement_Deconstruction(out int a, int[] arr) |
||||
{ |
||||
try { |
||||
ref int i = ref arr[0]; |
||||
(i, a) = GetSource<int, int>(); |
||||
} catch (Exception ex) { |
||||
a = 0; |
||||
Console.WriteLine(ex.GetType().FullName); |
||||
} |
||||
} |
||||
|
||||
public void DeconstructTupleSameVar((string, string) tuple) |
||||
{ |
||||
Console.WriteLine("DeconstructTupleSameVar:"); |
||||
string a; |
||||
a = tuple.Item1; |
||||
a = tuple.Item2; |
||||
Console.WriteLine(a); |
||||
} |
||||
|
||||
public void DeconstructTupleListForEachSameVar(List<(string, string)> tuples) |
||||
{ |
||||
Console.WriteLine("DeconstructTupleListForEachSameVar:"); |
||||
foreach (var tuple in tuples) { |
||||
string a; |
||||
a = tuple.Item1; |
||||
a = tuple.Item2; |
||||
Console.WriteLine(a); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,596 @@
@@ -0,0 +1,596 @@
|
||||
// Copyright (c) 2020 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty |
||||
{ |
||||
public static class DeconstructionExt |
||||
{ |
||||
public static void Deconstruct<K, V>(this KeyValuePair<K, V> pair, out K key, out V value) |
||||
{ |
||||
key = pair.Key; |
||||
value = pair.Value; |
||||
} |
||||
} |
||||
|
||||
internal class DeconstructionTests |
||||
{ |
||||
[StructLayout(LayoutKind.Sequential, Size = 1)] |
||||
public struct MyInt |
||||
{ |
||||
public static implicit operator int(MyInt x) |
||||
{ |
||||
return 0; |
||||
} |
||||
|
||||
public static implicit operator MyInt(int x) |
||||
{ |
||||
return default(MyInt); |
||||
} |
||||
} |
||||
|
||||
private class DeconstructionSource<T, T2> |
||||
{ |
||||
public int Dummy { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public void Deconstruct(out T a, out T2 b) |
||||
{ |
||||
a = default(T); |
||||
b = default(T2); |
||||
} |
||||
} |
||||
|
||||
private class DeconstructionSource<T, T2, T3> |
||||
{ |
||||
public int Dummy { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public void Deconstruct(out T a, out T2 b, out T3 c) |
||||
{ |
||||
a = default(T); |
||||
b = default(T2); |
||||
c = default(T3); |
||||
} |
||||
} |
||||
|
||||
private class AssignmentTargets |
||||
{ |
||||
public int IntField; |
||||
public long LongField; |
||||
public float FloatField; |
||||
public double DoubleField; |
||||
public decimal DecimalField; |
||||
|
||||
public MyInt MyField; |
||||
public MyInt? NMyField; |
||||
|
||||
public string StringField; |
||||
public object ObjectField; |
||||
public dynamic DynamicField; |
||||
|
||||
public int? NullableIntField; |
||||
|
||||
public MyInt MyIntField; |
||||
|
||||
public MyInt? NullableMyIntField; |
||||
|
||||
public int Int { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public long Long { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public float Float { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public double Double { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public decimal Decimal { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public string String { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public object Object { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public dynamic Dynamic { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public int? NInt { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public MyInt My { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public MyInt? NMy { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public static MyInt StaticMy { |
||||
get; |
||||
set; |
||||
} |
||||
|
||||
public static MyInt? StaticNMy { |
||||
get; |
||||
set; |
||||
} |
||||
} |
||||
|
||||
private DeconstructionSource<T, T2> GetSource<T, T2>() |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
private DeconstructionSource<T, T2, T3> GetSource<T, T2, T3>() |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
private ref T GetRef<T>() |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
private (T, T2) GetTuple<T, T2>() |
||||
{ |
||||
return default((T, T2)); |
||||
} |
||||
|
||||
private (T, T2, T3) GetTuple<T, T2, T3>() |
||||
{ |
||||
return default((T, T2, T3)); |
||||
} |
||||
|
||||
private AssignmentTargets Get(int i) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
public void LocalVariable_NoConversion_Custom() |
||||
{ |
||||
var (myInt3, x) = GetSource<MyInt?, MyInt>(); |
||||
Console.WriteLine(myInt3); |
||||
Console.WriteLine(x); |
||||
} |
||||
|
||||
public void LocalVariable_NoConversion_Tuple() |
||||
{ |
||||
var (myInt, x) = GetTuple<MyInt?, MyInt>(); |
||||
Console.WriteLine(myInt); |
||||
Console.WriteLine(x); |
||||
} |
||||
|
||||
public void LocalVariable_NoConversion_Custom_DiscardFirst() |
||||
{ |
||||
var (_, x, value) = GetSource<MyInt?, MyInt, int>(); |
||||
Console.WriteLine(x); |
||||
Console.WriteLine(value); |
||||
} |
||||
|
||||
// currently we detect deconstruction, iff the first element is not discarded
|
||||
//public void LocalVariable_NoConversion_Tuple_DiscardFirst()
|
||||
//{
|
||||
// var (_, x, value) = GetTuple<MyInt?, MyInt, int>();
|
||||
// Console.WriteLine(x);
|
||||
// Console.WriteLine(value);
|
||||
//}
|
||||
|
||||
public void LocalVariable_NoConversion_Custom_DiscardLast() |
||||
{ |
||||
var (myInt3, x, _) = GetSource<MyInt?, MyInt, int>(); |
||||
Console.WriteLine(myInt3); |
||||
Console.WriteLine(x); |
||||
} |
||||
|
||||
public void LocalVariable_NoConversion_Tuple_DiscardLast() |
||||
{ |
||||
var (myInt, x, _) = GetTuple<MyInt?, MyInt, int>(); |
||||
Console.WriteLine(myInt); |
||||
Console.WriteLine(x); |
||||
} |
||||
|
||||
public void LocalVariable_NoConversion_Custom_DiscardSecond() |
||||
{ |
||||
var (myInt3, _, value) = GetSource<MyInt?, MyInt, int>(); |
||||
Console.WriteLine(myInt3); |
||||
Console.WriteLine(value); |
||||
} |
||||
|
||||
public void LocalVariable_NoConversion_Tuple_DiscardSecond() |
||||
{ |
||||
var (myInt, _, value) = GetTuple<MyInt?, MyInt, int>(); |
||||
Console.WriteLine(myInt); |
||||
Console.WriteLine(value); |
||||
} |
||||
|
||||
public void LocalVariable_NoConversion_Custom_ReferenceTypes() |
||||
{ |
||||
var (value, value2) = GetSource<string, string>(); |
||||
Console.WriteLine(value); |
||||
Console.WriteLine(value2); |
||||
} |
||||
|
||||
public void LocalVariable_NoConversion_Tuple_ReferenceTypes() |
||||
{ |
||||
var (value, value2) = GetTuple<string, string>(); |
||||
Console.WriteLine(value); |
||||
Console.WriteLine(value2); |
||||
} |
||||
|
||||
public void LocalVariable_IntToLongConversion_Custom() |
||||
{ |
||||
int value; |
||||
long value2; |
||||
(value, value2) = GetSource<int, int>(); |
||||
Console.WriteLine(value); |
||||
Console.WriteLine(value2); |
||||
} |
||||
|
||||
public void LocalVariable_IntToLongConversion_Tuple() |
||||
{ |
||||
int value; |
||||
long value2; |
||||
(value, value2) = GetTuple<int, int>(); |
||||
Console.WriteLine(value); |
||||
Console.WriteLine(value2); |
||||
} |
||||
|
||||
public void LocalVariable_FloatToDoubleConversion_Custom() |
||||
{ |
||||
int value; |
||||
double value2; |
||||
(value, value2) = GetSource<int, float>(); |
||||
Console.WriteLine(value); |
||||
Console.WriteLine(value2); |
||||
} |
||||
|
||||
public void LocalVariable_FloatToDoubleConversion_Tuple() |
||||
{ |
||||
int value; |
||||
double value2; |
||||
(value, value2) = GetTuple<int, float>(); |
||||
Console.WriteLine(value); |
||||
Console.WriteLine(value2); |
||||
} |
||||
|
||||
// dynamic conversion is currently not supported
|
||||
//public void LocalVariable_ImplicitReferenceConversion_Custom()
|
||||
//{
|
||||
// object value;
|
||||
// dynamic value2;
|
||||
// (value, value2) = GetSource<string, string>();
|
||||
// Console.WriteLine(value);
|
||||
// value2.UseMe();
|
||||
//}
|
||||
|
||||
//public void LocalVariable_ImplicitReferenceConversion_Tuple()
|
||||
//{
|
||||
// object value;
|
||||
// dynamic value2;
|
||||
// (value, value2) = GetTuple<string, string>();
|
||||
// Console.WriteLine(value);
|
||||
// value2.UseMe();
|
||||
//}
|
||||
|
||||
public void LocalVariable_NoConversion_ComplexValue_Custom() |
||||
{ |
||||
var (myInt3, x) = new DeconstructionSource<MyInt?, MyInt> { |
||||
Dummy = 3 |
||||
}; |
||||
Console.WriteLine(myInt3); |
||||
Console.WriteLine(x); |
||||
} |
||||
|
||||
public void Property_NoConversion_Custom() |
||||
{ |
||||
(Get(0).NMy, Get(1).My) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void Property_IntToLongConversion_Custom() |
||||
{ |
||||
(Get(0).Int, Get(1).Long) = GetSource<int, int>(); |
||||
} |
||||
|
||||
public void Property_FloatToDoubleConversion_Custom() |
||||
{ |
||||
(Get(0).Int, Get(1).Double) = GetSource<int, float>(); |
||||
} |
||||
|
||||
// dynamic conversion is not supported
|
||||
//public void Property_ImplicitReferenceConversion_Custom()
|
||||
//{
|
||||
// (Get(0).Object, Get(1).Dynamic) = GetSource<string, string>();
|
||||
//}
|
||||
|
||||
public void Property_NoConversion_Custom_DiscardFirst() |
||||
{ |
||||
(_, Get(1).My) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void Property_NoConversion_Custom_DiscardLast() |
||||
{ |
||||
(Get(0).NMy, _) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void Property_NoConversion_Tuple() |
||||
{ |
||||
(Get(0).NMy, Get(1).My) = GetTuple<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void Property_NoConversion_Tuple_DiscardLast() |
||||
{ |
||||
(Get(0).NMy, Get(1).My, _) = GetTuple<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
// currently we detect deconstruction, iff the first element is not discarded
|
||||
//public void Property_NoConversion_Tuple_DiscardFirst()
|
||||
//{
|
||||
// (_, Get(1).My, Get(2).Int) = GetTuple<MyInt?, MyInt, int>();
|
||||
//}
|
||||
|
||||
public void Property_NoConversion_Custom_DiscardSecond() |
||||
{ |
||||
(Get(0).NMy, _, Get(2).Int) = GetSource<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
public void Property_NoConversion_Tuple_DiscardSecond() |
||||
{ |
||||
(Get(0).NMy, _, Get(2).Int) = GetTuple<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
public void Property_NoConversion_Custom_ReferenceTypes() |
||||
{ |
||||
(Get(0).String, Get(1).String) = GetSource<string, string>(); |
||||
} |
||||
|
||||
public void Property_NoConversion_Tuple_ReferenceTypes() |
||||
{ |
||||
(Get(0).String, Get(1).String) = GetTuple<string, string>(); |
||||
} |
||||
|
||||
public void Property_IntToLongConversion_Tuple() |
||||
{ |
||||
(Get(0).Int, Get(1).Long) = GetTuple<int, int>(); |
||||
} |
||||
|
||||
public void Property_FloatToDoubleConversion_Tuple() |
||||
{ |
||||
(Get(0).Int, Get(1).Double) = GetTuple<int, float>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Custom(out double a) |
||||
{ |
||||
(a, GetRef<float>()) = GetSource<double, float>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Tuple(out double a) |
||||
{ |
||||
(a, GetRef<float>()) = GetTuple<double, float>(); |
||||
} |
||||
|
||||
public void RefLocal_FloatToDoubleConversion_Custom(out double a) |
||||
{ |
||||
(a, GetRef<double>()) = GetSource<double, float>(); |
||||
} |
||||
|
||||
public void RefLocal_FloatToDoubleConversion_Custom2(out double a) |
||||
{ |
||||
(a, GetRef<double>()) = GetSource<float, float>(); |
||||
} |
||||
|
||||
public void RefLocal_FloatToDoubleConversion_Tuple(out double a) |
||||
{ |
||||
(a, GetRef<double>()) = GetTuple<double, float>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Custom(out MyInt? a) |
||||
{ |
||||
(a, GetRef<MyInt>()) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void RefLocal_IntToLongConversion_Custom(out long a) |
||||
{ |
||||
(a, GetRef<long>()) = GetSource<int, int>(); |
||||
} |
||||
|
||||
// dynamic conversion is not supported
|
||||
//public void RefLocal_ImplicitReferenceConversion_Custom(out object a)
|
||||
//{
|
||||
// (a, GetRef<dynamic>()) = GetSource<string, string>();
|
||||
//}
|
||||
|
||||
public void RefLocal_NoConversion_Custom_DiscardFirst() |
||||
{ |
||||
(_, GetRef<MyInt>()) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Custom_DiscardLast(out MyInt? a) |
||||
{ |
||||
(a, _) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Tuple(out MyInt? a) |
||||
{ |
||||
(a, GetRef<MyInt>()) = GetTuple<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Tuple_DiscardLast(out MyInt? a) |
||||
{ |
||||
(a, GetRef<MyInt>(), _) = GetTuple<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
// currently we detect deconstruction, iff the first element is not discarded
|
||||
//public void RefLocal_NoConversion_Tuple_DiscardFirst(out var a)
|
||||
//{
|
||||
// (_, GetRef<var>(), GetRef<var>()) = GetTuple<MyInt?, MyInt, int>();
|
||||
//}
|
||||
|
||||
public void RefLocal_NoConversion_Custom_DiscardSecond(out MyInt? a) |
||||
{ |
||||
(a, _, GetRef<int>()) = GetSource<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Tuple_DiscardSecond(out MyInt? a) |
||||
{ |
||||
(a, _, GetRef<int>()) = GetTuple<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Custom_ReferenceTypes(out string a) |
||||
{ |
||||
(a, GetRef<string>()) = GetSource<string, string>(); |
||||
} |
||||
|
||||
public void RefLocal_NoConversion_Tuple_ReferenceTypes(out string a) |
||||
{ |
||||
(a, GetRef<string>()) = GetTuple<string, string>(); |
||||
} |
||||
|
||||
public void RefLocal_IntToLongConversion_Tuple(out long a) |
||||
{ |
||||
(a, GetRef<long>()) = GetTuple<int, int>(); |
||||
} |
||||
|
||||
//public void ArrayAssign_FloatToDoubleConversion_Custom(double[] arr)
|
||||
//{
|
||||
// (arr[0], arr[1], arr[2]) = GetSource<double, float, double>();
|
||||
//}
|
||||
|
||||
public void Field_NoConversion_Custom() |
||||
{ |
||||
(Get(0).IntField, Get(1).IntField) = GetSource<int, int>(); |
||||
} |
||||
|
||||
public void Field_NoConversion_Tuple() |
||||
{ |
||||
(Get(0).IntField, Get(1).IntField) = GetTuple<int, int>(); |
||||
} |
||||
|
||||
public void Field_IntToLongConversion_Custom() |
||||
{ |
||||
(Get(0).IntField, Get(1).LongField) = GetSource<int, int>(); |
||||
} |
||||
|
||||
public void Field_IntToLongConversion_Tuple() |
||||
{ |
||||
(Get(0).IntField, Get(1).LongField) = GetTuple<int, int>(); |
||||
} |
||||
|
||||
public void Field_FloatToDoubleConversion_Custom() |
||||
{ |
||||
(Get(0).DoubleField, Get(1).DoubleField) = GetSource<double, float>(); |
||||
} |
||||
|
||||
public void Field_FloatToDoubleConversion_Tuple() |
||||
{ |
||||
(Get(0).DoubleField, Get(1).DoubleField) = GetTuple<double, float>(); |
||||
} |
||||
|
||||
// dynamic conversion is not supported
|
||||
//public void Field_ImplicitReferenceConversion_Custom()
|
||||
//{
|
||||
// (Get(0).ObjectField, Get(1).DynamicField) = GetSource<string, string>();
|
||||
//}
|
||||
|
||||
public void Field_NoConversion_Custom_DiscardFirst() |
||||
{ |
||||
(_, Get(1).MyField) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void Field_NoConversion_Custom_DiscardLast() |
||||
{ |
||||
(Get(0).NMyField, _) = GetSource<MyInt?, MyInt>(); |
||||
} |
||||
|
||||
public void Field_NoConversion_Tuple_DiscardLast() |
||||
{ |
||||
(Get(0).NMyField, Get(1).MyField, _) = GetTuple<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
// currently we detect deconstruction, iff the first element is not discarded
|
||||
//public void Field_NoConversion_Tuple_DiscardFirst()
|
||||
//{
|
||||
// (_, Get(1).MyField, Get(2).IntField) = GetTuple<MyInt?, MyInt, int>();
|
||||
//}
|
||||
|
||||
public void Field_NoConversion_Custom_DiscardSecond() |
||||
{ |
||||
(Get(0).NMyField, _, Get(2).IntField) = GetSource<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
public void Field_NoConversion_Tuple_DiscardSecond() |
||||
{ |
||||
(Get(0).NMyField, _, Get(2).IntField) = GetTuple<MyInt?, MyInt, int>(); |
||||
} |
||||
|
||||
public void Field_NoConversion_Custom_ReferenceTypes() |
||||
{ |
||||
(Get(0).StringField, Get(1).StringField) = GetSource<string, string>(); |
||||
} |
||||
|
||||
public void Field_NoConversion_Tuple_ReferenceTypes() |
||||
{ |
||||
(Get(0).StringField, Get(1).StringField) = GetTuple<string, string>(); |
||||
} |
||||
|
||||
public void DeconstructDictionaryForEach(Dictionary<string, int> dictionary) |
||||
{ |
||||
foreach (var (str, num2) in dictionary) { |
||||
Console.WriteLine(str + ": " + num2); |
||||
} |
||||
} |
||||
|
||||
public void DeconstructTupleListForEach(List<(string, int)> tuples) |
||||
{ |
||||
foreach (var (str, num) in tuples) { |
||||
Console.WriteLine(str + ": " + num); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2020 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; |
||||
|
||||
namespace ICSharpCode.Decompiler.CSharp.Syntax |
||||
{ |
||||
/// <summary>
|
||||
/// TypeName VariableDesignation
|
||||
/// </summary>
|
||||
public class DeclarationExpression : Expression |
||||
{ |
||||
public AstType Type { |
||||
get { return GetChildByRole(Roles.Type); } |
||||
set { SetChildByRole(Roles.Type, value); } |
||||
} |
||||
|
||||
public VariableDesignation Designation { |
||||
get { return GetChildByRole(Roles.VariableDesignationRole); } |
||||
set { SetChildByRole(Roles.VariableDesignationRole, value); } |
||||
} |
||||
|
||||
public override void AcceptVisitor(IAstVisitor visitor) |
||||
{ |
||||
visitor.VisitDeclarationExpression(this); |
||||
} |
||||
|
||||
public override T AcceptVisitor<T>(IAstVisitor<T> visitor) |
||||
{ |
||||
return visitor.VisitDeclarationExpression(this); |
||||
} |
||||
|
||||
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data) |
||||
{ |
||||
return visitor.VisitDeclarationExpression(this, data); |
||||
} |
||||
|
||||
protected internal override bool DoMatch(AstNode other, Match match) |
||||
{ |
||||
return other is DeclarationExpression o && Designation.DoMatch(o.Designation, match); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,137 @@
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) 2020 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; |
||||
|
||||
namespace ICSharpCode.Decompiler.CSharp.Syntax |
||||
{ |
||||
public abstract class VariableDesignation : AstNode |
||||
{ |
||||
public override NodeType NodeType => NodeType.Unknown; |
||||
|
||||
#region Null
|
||||
public new static readonly VariableDesignation Null = new NullVariableDesignation(); |
||||
|
||||
sealed class NullVariableDesignation : VariableDesignation |
||||
{ |
||||
public override bool IsNull { |
||||
get { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
public override void AcceptVisitor(IAstVisitor visitor) |
||||
{ |
||||
visitor.VisitNullNode(this); |
||||
} |
||||
|
||||
public override T AcceptVisitor<T>(IAstVisitor<T> visitor) |
||||
{ |
||||
return visitor.VisitNullNode(this); |
||||
} |
||||
|
||||
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data) |
||||
{ |
||||
return visitor.VisitNullNode(this, data); |
||||
} |
||||
|
||||
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) |
||||
{ |
||||
return other == null || other.IsNull; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Identifier
|
||||
/// </summary>
|
||||
public class SingleVariableDesignation : VariableDesignation |
||||
{ |
||||
|
||||
public string Identifier { |
||||
get { return GetChildByRole(Roles.Identifier).Name; } |
||||
set { SetChildByRole(Roles.Identifier, Syntax.Identifier.Create(value)); } |
||||
} |
||||
|
||||
public Identifier IdentifierToken { |
||||
get { return GetChildByRole(Roles.Identifier); } |
||||
set { SetChildByRole(Roles.Identifier, value); } |
||||
} |
||||
|
||||
public override void AcceptVisitor(IAstVisitor visitor) |
||||
{ |
||||
visitor.VisitSingleVariableDesignation(this); |
||||
} |
||||
|
||||
public override T AcceptVisitor<T>(IAstVisitor<T> visitor) |
||||
{ |
||||
return visitor.VisitSingleVariableDesignation(this); |
||||
} |
||||
|
||||
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data) |
||||
{ |
||||
return visitor.VisitSingleVariableDesignation(this, data); |
||||
} |
||||
|
||||
protected internal override bool DoMatch(AstNode other, Match match) |
||||
{ |
||||
return other is SingleVariableDesignation o && MatchString(this.Identifier, o.Identifier); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// ( VariableDesignation (, VariableDesignation)* )
|
||||
/// </summary>
|
||||
public class ParenthesizedVariableDesignation : VariableDesignation |
||||
{ |
||||
|
||||
public CSharpTokenNode LParToken { |
||||
get { return GetChildByRole(Roles.LPar); } |
||||
} |
||||
|
||||
public AstNodeCollection<VariableDesignation> VariableDesignations { |
||||
get { return GetChildrenByRole(Roles.VariableDesignationRole); } |
||||
} |
||||
|
||||
public CSharpTokenNode RParToken { |
||||
get { return GetChildByRole(Roles.RPar); } |
||||
} |
||||
|
||||
public override void AcceptVisitor(IAstVisitor visitor) |
||||
{ |
||||
visitor.VisitParenthesizedVariableDesignation(this); |
||||
} |
||||
|
||||
public override T AcceptVisitor<T>(IAstVisitor<T> visitor) |
||||
{ |
||||
return visitor.VisitParenthesizedVariableDesignation(this); |
||||
} |
||||
|
||||
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data) |
||||
{ |
||||
return visitor.VisitParenthesizedVariableDesignation(this, data); |
||||
} |
||||
|
||||
protected internal override bool DoMatch(AstNode other, Match match) |
||||
{ |
||||
return other is ParenthesizedVariableDesignation o && VariableDesignations.DoMatch(o.VariableDesignations, match); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,300 @@
@@ -0,0 +1,300 @@
|
||||
// Copyright (c) 2020 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL |
||||
{ |
||||
partial class DeconstructInstruction |
||||
{ |
||||
public static readonly SlotInfo InitSlot = new SlotInfo("Init", canInlineInto: true, isCollection: true); |
||||
public static readonly SlotInfo PatternSlot = new SlotInfo("Pattern", canInlineInto: true); |
||||
public static readonly SlotInfo ConversionsSlot = new SlotInfo("Conversions"); |
||||
public static readonly SlotInfo AssignmentsSlot = new SlotInfo("Assignments"); |
||||
|
||||
public DeconstructInstruction() |
||||
: base(OpCode.DeconstructInstruction) |
||||
{ |
||||
this.Init = new InstructionCollection<StLoc>(this, 0); |
||||
} |
||||
|
||||
public readonly InstructionCollection<StLoc> Init; |
||||
|
||||
MatchInstruction pattern; |
||||
public MatchInstruction Pattern { |
||||
get { return this.pattern; } |
||||
set { |
||||
ValidateChild(value); |
||||
SetChildInstruction(ref this.pattern, value, Init.Count); |
||||
} |
||||
} |
||||
|
||||
Block conversions; |
||||
public Block Conversions { |
||||
get { return this.conversions; } |
||||
set { |
||||
ValidateChild(value); |
||||
SetChildInstruction(ref this.conversions, value, Init.Count + 1); |
||||
} |
||||
} |
||||
|
||||
Block assignments; |
||||
public Block Assignments { |
||||
get { return this.assignments; } |
||||
set { |
||||
ValidateChild(value); |
||||
SetChildInstruction(ref this.assignments, value, Init.Count + 2); |
||||
} |
||||
} |
||||
|
||||
protected sealed override int GetChildCount() |
||||
{ |
||||
return Init.Count + 3; |
||||
} |
||||
|
||||
protected sealed override ILInstruction GetChild(int index) |
||||
{ |
||||
switch (index - Init.Count) { |
||||
case 0: |
||||
return this.pattern; |
||||
case 1: |
||||
return this.conversions; |
||||
case 2: |
||||
return this.assignments; |
||||
default: |
||||
return this.Init[index]; |
||||
} |
||||
} |
||||
|
||||
protected sealed override void SetChild(int index, ILInstruction value) |
||||
{ |
||||
switch (index - Init.Count) { |
||||
case 0: |
||||
this.Pattern = (MatchInstruction)value; |
||||
break; |
||||
case 1: |
||||
this.Conversions = (Block)value; |
||||
break; |
||||
case 2: |
||||
this.Assignments = (Block)value; |
||||
break; |
||||
default: |
||||
this.Init[index] = (StLoc)value; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
protected sealed override SlotInfo GetChildSlot(int index) |
||||
{ |
||||
switch (index - Init.Count) { |
||||
case 0: |
||||
return PatternSlot; |
||||
case 1: |
||||
return ConversionsSlot; |
||||
case 2: |
||||
return AssignmentsSlot; |
||||
default: |
||||
return InitSlot; |
||||
} |
||||
} |
||||
|
||||
public sealed override ILInstruction Clone() |
||||
{ |
||||
var clone = new DeconstructInstruction(); |
||||
clone.Init.AddRange(this.Init.Select(inst => (StLoc)inst.Clone())); |
||||
clone.Pattern = (MatchInstruction)this.pattern.Clone(); |
||||
clone.Conversions = (Block)this.conversions.Clone(); |
||||
clone.Assignments = (Block)this.assignments.Clone(); |
||||
return clone; |
||||
} |
||||
|
||||
protected override InstructionFlags ComputeFlags() |
||||
{ |
||||
var flags = InstructionFlags.None; |
||||
foreach (var inst in Init) { |
||||
flags |= inst.Flags; |
||||
} |
||||
flags |= pattern.Flags | conversions.Flags | assignments.Flags; |
||||
return flags; |
||||
} |
||||
|
||||
public override InstructionFlags DirectFlags { |
||||
get { |
||||
return InstructionFlags.None; |
||||
} |
||||
} |
||||
|
||||
protected internal override void InstructionCollectionUpdateComplete() |
||||
{ |
||||
base.InstructionCollectionUpdateComplete(); |
||||
if (pattern.Parent == this) |
||||
pattern.ChildIndex = Init.Count; |
||||
if (conversions.Parent == this) |
||||
conversions.ChildIndex = Init.Count + 1; |
||||
if (assignments.Parent == this) |
||||
assignments.ChildIndex = Init.Count + 2; |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output, ILAstWritingOptions options) |
||||
{ |
||||
WriteILRange(output, options); |
||||
output.Write("deconstruct "); |
||||
output.MarkFoldStart("{...}"); |
||||
output.WriteLine("{"); |
||||
output.Indent(); |
||||
output.WriteLine("init:"); |
||||
output.Indent(); |
||||
foreach (var inst in this.Init) { |
||||
inst.WriteTo(output, options); |
||||
output.WriteLine(); |
||||
} |
||||
output.Unindent(); |
||||
output.WriteLine("pattern:"); |
||||
output.Indent(); |
||||
pattern.WriteTo(output, options); |
||||
output.Unindent(); |
||||
output.WriteLine(); |
||||
output.Write("conversions: "); |
||||
conversions.WriteTo(output, options); |
||||
output.WriteLine(); |
||||
output.Write("assignments: "); |
||||
assignments.WriteTo(output, options); |
||||
output.Unindent(); |
||||
output.WriteLine(); |
||||
output.Write('}'); |
||||
output.MarkFoldEnd(); |
||||
} |
||||
|
||||
internal static bool IsConversionStLoc(ILInstruction inst, out ILVariable variable, out ILVariable inputVariable) |
||||
{ |
||||
inputVariable = null; |
||||
if (!inst.MatchStLoc(out variable, out var value)) |
||||
return false; |
||||
ILInstruction input; |
||||
switch (value) { |
||||
case Conv conv: |
||||
input = conv.Argument; |
||||
break; |
||||
//case Call { Method: { IsOperator: true, Name: "op_Implicit" }, Arguments: { Count: 1 } } call:
|
||||
// input = call.Arguments[0];
|
||||
// break;
|
||||
default: |
||||
return false; |
||||
} |
||||
return input.MatchLdLoc(out inputVariable) || input.MatchLdLoca(out inputVariable); |
||||
} |
||||
|
||||
internal static bool IsAssignment(ILInstruction inst, ICompilation typeSystem, out IType expectedType, out ILInstruction value) |
||||
{ |
||||
expectedType = null; |
||||
value = null; |
||||
switch (inst) { |
||||
case CallInstruction call: |
||||
if (call.Method.AccessorKind != System.Reflection.MethodSemanticsAttributes.Setter) |
||||
return false; |
||||
for (int i = 0; i < call.Arguments.Count - 1; i++) { |
||||
ILInstruction arg = call.Arguments[i]; |
||||
if (arg.Flags == InstructionFlags.None) { |
||||
// OK - we accept integer literals, etc.
|
||||
} else if (arg.MatchLdLoc(out var v)) { |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
expectedType = call.Method.Parameters.Last().Type; |
||||
value = call.Arguments.Last(); |
||||
return true; |
||||
case StLoc stloc: |
||||
expectedType = stloc.Variable.Type; |
||||
value = stloc.Value; |
||||
return true; |
||||
case StObj stobj: |
||||
var target = stobj.Target; |
||||
while (target.MatchLdFlda(out var nestedTarget, out _)) |
||||
target = nestedTarget; |
||||
if (target.Flags == InstructionFlags.None) { |
||||
// OK - we accept integer literals, etc.
|
||||
} else if (target.MatchLdLoc(out var v)) { |
||||
} else { |
||||
return false; |
||||
} |
||||
if (stobj.Target.InferType(typeSystem) is ByReferenceType brt) |
||||
expectedType = brt.ElementType; |
||||
else |
||||
expectedType = SpecialType.UnknownType; |
||||
value = stobj.Value; |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
internal override void CheckInvariant(ILPhase phase) |
||||
{ |
||||
base.CheckInvariant(phase); |
||||
var patternVariables = new HashSet<ILVariable>(); |
||||
var conversionVariables = new HashSet<ILVariable>(); |
||||
|
||||
foreach (StLoc init in this.Init) { |
||||
Debug.Assert(init.Variable.IsSingleDefinition && init.Variable.LoadCount == 1); |
||||
Debug.Assert(init.Variable.LoadInstructions[0].IsDescendantOf(assignments)); |
||||
} |
||||
|
||||
ValidatePattern(pattern); |
||||
|
||||
foreach (var inst in this.conversions.Instructions) { |
||||
if (!IsConversionStLoc(inst, out var variable, out var inputVariable)) |
||||
Debug.Fail("inst is not a conversion stloc!"); |
||||
Debug.Assert(variable.IsSingleDefinition && variable.LoadCount == 1); |
||||
Debug.Assert(variable.LoadInstructions[0].IsDescendantOf(assignments)); |
||||
Debug.Assert(patternVariables.Contains(inputVariable)); |
||||
conversionVariables.Add(variable); |
||||
} |
||||
Debug.Assert(this.conversions.FinalInstruction is Nop); |
||||
|
||||
foreach (var inst in assignments.Instructions) { |
||||
if (!(IsAssignment(inst, typeSystem: null, out _, out var value) && value.MatchLdLoc(out var inputVariable))) |
||||
throw new InvalidOperationException("inst is not an assignment!"); |
||||
Debug.Assert(patternVariables.Contains(inputVariable) || conversionVariables.Contains(inputVariable)); |
||||
} |
||||
Debug.Assert(this.assignments.FinalInstruction is Nop); |
||||
|
||||
void ValidatePattern(MatchInstruction inst) |
||||
{ |
||||
Debug.Assert(inst.IsDeconstructCall || inst.IsDeconstructTuple); |
||||
Debug.Assert(!inst.CheckNotNull && !inst.CheckType); |
||||
Debug.Assert(!inst.HasDesignator); |
||||
foreach (var subPattern in inst.SubPatterns.Cast<MatchInstruction>()) { |
||||
if (subPattern.IsVar) { |
||||
Debug.Assert(subPattern.Variable.IsSingleDefinition && subPattern.Variable.LoadCount <= 1); |
||||
if (subPattern.Variable.LoadCount == 1) |
||||
Debug.Assert(subPattern.Variable.LoadInstructions[0].IsDescendantOf(this)); |
||||
patternVariables.Add(subPattern.Variable); |
||||
} else { |
||||
ValidatePattern(subPattern); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2020 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System.Diagnostics; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL |
||||
{ |
||||
partial class DeconstructResultInstruction |
||||
{ |
||||
public int Index { get; } |
||||
|
||||
public override StackType ResultType { get; } |
||||
|
||||
public DeconstructResultInstruction(int index, StackType resultType, ILInstruction argument) |
||||
: base(OpCode.DeconstructResultInstruction, argument) |
||||
{ |
||||
Debug.Assert(index >= 0); |
||||
Index = index; |
||||
ResultType = resultType; |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output, ILAstWritingOptions options) |
||||
{ |
||||
WriteILRange(output, options); |
||||
output.Write(OpCode); |
||||
output.Write(' '); |
||||
output.Write(Index.ToString()); |
||||
output.Write('('); |
||||
this.Argument.WriteTo(output, options); |
||||
output.Write(')'); |
||||
} |
||||
|
||||
MatchInstruction FindMatch() |
||||
{ |
||||
for (ILInstruction inst = this; inst != null; inst = inst.Parent) { |
||||
if (inst.Parent is MatchInstruction match && inst != match.TestedOperand) |
||||
return match; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
void AdditionalInvariants() |
||||
{ |
||||
var matchInst = FindMatch(); |
||||
Debug.Assert(matchInst != null && (matchInst.IsDeconstructCall || matchInst.IsDeconstructTuple)); |
||||
Debug.Assert(Argument.MatchLdLoc(matchInst.Variable)); |
||||
var outParamType = matchInst.GetDeconstructResultType(this.Index); |
||||
Debug.Assert(outParamType.GetStackType() == ResultType); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,267 @@
@@ -0,0 +1,267 @@
|
||||
// Copyright (c) 2020 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL |
||||
{ |
||||
partial class MatchInstruction : ILInstruction |
||||
{ |
||||
/* Pseudo-Code for interpreting a MatchInstruction: |
||||
bool Eval() |
||||
{ |
||||
var value = this.TestedOperand.Eval(); |
||||
if (this.CheckNotNull && value == null) |
||||
return false; |
||||
if (this.CheckType && !(value is this.Variable.Type)) |
||||
return false; |
||||
if (this.IsDeconstructCall) { |
||||
deconstructResult = new[numArgs]; |
||||
EvalCall(this.Method, value, out deconstructResult[0], .., out deconstructResult[numArgs-1]); |
||||
// any occurrences of 'deconstruct.result' in the subPatterns will refer
|
||||
// to the values provided by evaluating the call.
|
||||
} |
||||
Variable.Value = value; |
||||
foreach (var subPattern in this.SubPatterns) { |
||||
if (!subPattern.Eval()) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
*/ |
||||
/* Examples of MatchInstructions: |
||||
expr is var x: |
||||
match(x = expr) |
||||
|
||||
expr is {} x: |
||||
match.notnull(x = expr) |
||||
|
||||
expr is T x: |
||||
match.type[T](x = expr) |
||||
|
||||
expr is C { A: var x } z: |
||||
match.type[C](z = expr) { |
||||
match(x = z.A) |
||||
} |
||||
|
||||
expr is C { A: var x, B: 42, C: { A: 4 } } z: |
||||
match.type[C](z = expr) { |
||||
match(x = z.A), |
||||
comp (z.B == 42), |
||||
match.notnull(temp2 = z.C) { |
||||
comp (temp2.A == 4) |
||||
} |
||||
} |
||||
|
||||
expr is C(var x, var y, <4): |
||||
match.type[C].deconstruct[C.Deconstruct](tmp1 = expr) { |
||||
match(x = deconstruct.result1(tmp1)), |
||||
match(y = deconstruct.result2(tmp1)), |
||||
comp(deconstruct.result3(tmp1) < 4), |
||||
} |
||||
|
||||
expr is C(1, D(2, 3)): |
||||
match.type[C].deconstruct(c = expr) { |
||||
comp(deconstruct.result1(c) == 1), |
||||
match.type[D].deconstruct(d = deconstruct.result2(c)) { |
||||
comp(deconstruct.result1(d) == 2), |
||||
comp(deconstruct.result2(d) == 2), |
||||
} |
||||
} |
||||
*/ |
||||
|
||||
public bool IsVar => !CheckType && !CheckNotNull && !IsDeconstructCall && !IsDeconstructTuple && SubPatterns.Count == 0; |
||||
|
||||
public bool HasDesignator => Variable.LoadCount + Variable.AddressCount > SubPatterns.Count; |
||||
|
||||
public int NumPositionalPatterns { |
||||
get { |
||||
if (IsDeconstructCall) |
||||
return method.Parameters.Count - (method.IsStatic ? 1 : 0); |
||||
else if (IsDeconstructTuple) |
||||
return TupleType.GetTupleElementTypes(variable.Type).Length; |
||||
else |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
public MatchInstruction(ILVariable variable, ILInstruction testedOperand) |
||||
: this(variable, method: null, testedOperand) |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Checks whether the input instruction can represent a pattern matching operation.
|
||||
///
|
||||
/// Any pattern matching instruction will first evaluate the `testedOperand` (a descendant of `inst`),
|
||||
/// and then match the value of that operand against the pattern encoded in the instruction.
|
||||
/// The matching may have side-effects on the newly-initialized pattern variables
|
||||
/// (even if the pattern fails to match!).
|
||||
/// The pattern matching instruction evaluates to 1 (as I4) if the pattern matches, or 0 otherwise.
|
||||
/// </summary>
|
||||
public static bool IsPatternMatch(ILInstruction inst, out ILInstruction testedOperand) |
||||
{ |
||||
switch (inst) { |
||||
case MatchInstruction m: |
||||
testedOperand = m.testedOperand; |
||||
return true; |
||||
case Comp comp: |
||||
testedOperand = comp.Left; |
||||
return IsConstant(comp.Right); |
||||
case ILInstruction logicNot when logicNot.MatchLogicNot(out var operand): |
||||
return IsPatternMatch(operand, out testedOperand); |
||||
default: |
||||
testedOperand = null; |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
private static bool IsConstant(ILInstruction inst) |
||||
{ |
||||
return inst.OpCode switch |
||||
{ |
||||
OpCode.LdcDecimal => true, |
||||
OpCode.LdcF4 => true, |
||||
OpCode.LdcF8 => true, |
||||
OpCode.LdcI4 => true, |
||||
OpCode.LdcI8 => true, |
||||
OpCode.LdNull => true, |
||||
_ => false |
||||
}; |
||||
} |
||||
|
||||
internal IType GetDeconstructResultType(int index) |
||||
{ |
||||
if (this.IsDeconstructCall) { |
||||
int firstOutParam = (method.IsStatic ? 1 : 0); |
||||
var outParamType = this.Method.Parameters[firstOutParam + index].Type; |
||||
if (!(outParamType is ByReferenceType brt)) |
||||
throw new InvalidOperationException("deconstruct out param must be by reference"); |
||||
return brt.ElementType; |
||||
} |
||||
if (this.IsDeconstructTuple) { |
||||
var elementTypes = TupleType.GetTupleElementTypes(this.variable.Type); |
||||
return elementTypes[index]; |
||||
} |
||||
throw new InvalidOperationException("GetDeconstructResultType requires a deconstruct pattern"); |
||||
} |
||||
|
||||
void AdditionalInvariants() |
||||
{ |
||||
Debug.Assert(variable.Kind == VariableKind.PatternLocal); |
||||
if (this.IsDeconstructCall) { |
||||
Debug.Assert(IsDeconstructMethod(method)); |
||||
} else { |
||||
Debug.Assert(method == null); |
||||
} |
||||
if (this.IsDeconstructTuple) { |
||||
Debug.Assert(variable.Type.Kind == TypeKind.Tuple); |
||||
} |
||||
Debug.Assert(SubPatterns.Count >= NumPositionalPatterns); |
||||
foreach (var subPattern in SubPatterns) { |
||||
if (!IsPatternMatch(subPattern, out ILInstruction operand)) |
||||
Debug.Fail("Sub-Pattern must be a valid pattern"); |
||||
// the first child is TestedOperand
|
||||
int subPatternIndex = subPattern.ChildIndex - 1; |
||||
if (subPatternIndex < NumPositionalPatterns) { |
||||
// positional pattern
|
||||
Debug.Assert(operand is DeconstructResultInstruction result && result.Index == subPatternIndex); |
||||
} else if (operand.MatchLdFld(out var target, out _)) { |
||||
Debug.Assert(target.MatchLdLoc(variable)); |
||||
} else if (operand is CallInstruction call) { |
||||
Debug.Assert(call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter); |
||||
Debug.Assert(call.Arguments[0].MatchLdLoc(variable)); |
||||
} else { |
||||
Debug.Fail("Tested operand of sub-pattern is invalid."); |
||||
} |
||||
} |
||||
} |
||||
|
||||
internal static bool IsDeconstructMethod(IMethod method) |
||||
{ |
||||
if (method.Name != "Deconstruct") |
||||
return false; |
||||
if (method.ReturnType.Kind != TypeKind.Void) |
||||
return false; |
||||
int firstOutParam = (method.IsStatic ? 1 : 0); |
||||
if (method.IsStatic) { |
||||
if (!method.IsExtensionMethod) |
||||
return false; |
||||
// TODO : check whether all type arguments can be inferred from the first argument
|
||||
} else { |
||||
if (method.TypeParameters.Count != 0) |
||||
return false; |
||||
} |
||||
|
||||
// TODO : check whether the method is ambigious
|
||||
|
||||
if (method.Parameters.Count < firstOutParam) |
||||
return false; |
||||
|
||||
for (int i = firstOutParam; i < method.Parameters.Count; i++) { |
||||
if (!method.Parameters[i].IsOut) |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output, ILAstWritingOptions options) |
||||
{ |
||||
WriteILRange(output, options); |
||||
output.Write(OpCode); |
||||
if (CheckNotNull) { |
||||
output.Write(".notnull"); |
||||
} |
||||
if (CheckType) { |
||||
output.Write(".type["); |
||||
variable.Type.WriteTo(output); |
||||
output.Write(']'); |
||||
} |
||||
if (IsDeconstructCall) { |
||||
output.Write(".deconstruct["); |
||||
method.WriteTo(output); |
||||
output.Write(']'); |
||||
} |
||||
if (IsDeconstructTuple) { |
||||
output.Write(".tuple"); |
||||
} |
||||
output.Write(' '); |
||||
output.Write('('); |
||||
Variable.WriteTo(output); |
||||
output.Write(" = "); |
||||
TestedOperand.WriteTo(output, options); |
||||
output.Write(')'); |
||||
if (SubPatterns.Count > 0) { |
||||
output.MarkFoldStart("{...}"); |
||||
output.WriteLine("{"); |
||||
output.Indent(); |
||||
foreach (var pattern in SubPatterns) { |
||||
pattern.WriteTo(output, options); |
||||
output.WriteLine(); |
||||
} |
||||
output.Unindent(); |
||||
output.Write('}'); |
||||
output.MarkFoldEnd(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,435 @@
@@ -0,0 +1,435 @@
|
||||
// Copyright (c) 2020 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.Immutable; |
||||
using System.Linq; |
||||
using System.Resources; |
||||
|
||||
using ICSharpCode.Decompiler.CSharp.Resolver; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
using ICSharpCode.Decompiler.Util; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL.Transforms |
||||
{ |
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class DeconstructionTransform : IStatementTransform |
||||
{ |
||||
StatementTransformContext context; |
||||
readonly Dictionary<ILVariable, int> deconstructionResultsLookup = new Dictionary<ILVariable, int>(); |
||||
ILVariable[] deconstructionResults; |
||||
ILVariable tupleVariable; |
||||
TupleType tupleType; |
||||
|
||||
/* |
||||
stloc tuple(call MakeIntIntTuple(ldloc this)) |
||||
---- |
||||
stloc myInt(call op_Implicit(ldfld Item2(ldloca tuple))) |
||||
stloc a(ldfld Item1(ldloca tuple)) |
||||
stloc b(ldloc myInt) |
||||
==> |
||||
deconstruct { |
||||
init: |
||||
<empty> |
||||
deconstruct: |
||||
match.deconstruct(temp = ldloca tuple) { |
||||
match(result0 = deconstruct.result 0(temp)), |
||||
match(result1 = deconstruct.result 1(temp)) |
||||
} |
||||
conversions: { |
||||
stloc conv2(call op_Implicit(ldloc result1)) |
||||
} |
||||
assignments: { |
||||
stloc a(ldloc result0) |
||||
stloc b(ldloc conv2) |
||||
} |
||||
} |
||||
* */ |
||||
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) |
||||
{ |
||||
if (!context.Settings.Deconstruction) |
||||
return; |
||||
|
||||
try { |
||||
this.context = context; |
||||
Reset(); |
||||
|
||||
if (TransformDeconstruction(block, pos)) |
||||
return; |
||||
if (InlineDeconstructionInitializer(block, pos)) |
||||
return; |
||||
} finally { |
||||
this.context = null; |
||||
Reset(); |
||||
} |
||||
} |
||||
|
||||
private void Reset() |
||||
{ |
||||
this.deconstructionResultsLookup.Clear(); |
||||
this.tupleVariable = null; |
||||
this.tupleType = null; |
||||
this.deconstructionResults = null; |
||||
} |
||||
|
||||
struct ConversionInfo |
||||
{ |
||||
public IType inputType; |
||||
public Conv conv; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Get index of deconstruction result or tuple element
|
||||
/// Returns -1 on failure.
|
||||
/// </summary>
|
||||
int FindIndex(ILInstruction inst, out Action<DeconstructInstruction> delayedActions) |
||||
{ |
||||
delayedActions = null; |
||||
if (inst.MatchLdLoc(out var v)) { |
||||
if (!deconstructionResultsLookup.TryGetValue(v, out int index)) |
||||
return -1; |
||||
return index; |
||||
} |
||||
if (inst.MatchLdFld(out _, out _)) { |
||||
if (!TupleTransform.MatchTupleFieldAccess((LdFlda)((LdObj)inst).Target, out var tupleType, out var target, out int index)) |
||||
return -1; |
||||
// Item fields are one-based, we use zero-based indexing.
|
||||
index--; |
||||
// normalize tuple type
|
||||
tupleType = TupleType.FromUnderlyingType(context.TypeSystem, tupleType); |
||||
if (!target.MatchLdLoca(out v)) |
||||
return -1; |
||||
if (this.tupleVariable == null) { |
||||
this.tupleVariable = v; |
||||
this.tupleType = (TupleType)tupleType; |
||||
this.deconstructionResults = new ILVariable[this.tupleType.Cardinality]; |
||||
} |
||||
if (this.tupleType.Cardinality < 2) |
||||
return -1; |
||||
if (v != tupleVariable || !this.tupleType.Equals(tupleType)) |
||||
return -1; |
||||
if (this.deconstructionResults[index] == null) { |
||||
var freshVar = new ILVariable(VariableKind.StackSlot, this.tupleType.ElementTypes[index]) { Name = "E_" + index }; |
||||
delayedActions += _ => context.Function.Variables.Add(freshVar); |
||||
this.deconstructionResults[index] = freshVar; |
||||
} |
||||
delayedActions += _ => { |
||||
inst.ReplaceWith(new LdLoc(this.deconstructionResults[index])); |
||||
}; |
||||
return index; |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// stloc v(value)
|
||||
/// expr(..., deconstruct { ... }, ...)
|
||||
/// =>
|
||||
/// expr(..., deconstruct { init: stloc v(value) ... }, ...)
|
||||
/// </summary>
|
||||
bool InlineDeconstructionInitializer(Block block, int pos) |
||||
{ |
||||
if (!block.Instructions[pos].MatchStLoc(out var v, out var value)) |
||||
return false; |
||||
if (!(v.IsSingleDefinition && v.LoadCount == 1)) |
||||
return false; |
||||
if (pos + 1 >= block.Instructions.Count) |
||||
return false; |
||||
var result = ILInlining.FindLoadInNext(block.Instructions[pos + 1], v, value, InliningOptions.FindDeconstruction); |
||||
if (result.Type != ILInlining.FindResultType.Deconstruction) |
||||
return false; |
||||
var deconstruction = (DeconstructInstruction)result.LoadInst; |
||||
LdLoc loadInst = v.LoadInstructions[0]; |
||||
if (!loadInst.IsDescendantOf(deconstruction.Assignments)) |
||||
return false; |
||||
if (loadInst.SlotInfo == StObj.TargetSlot) { |
||||
if (value.OpCode == OpCode.LdFlda || value.OpCode == OpCode.LdElema) |
||||
return false; |
||||
} |
||||
if (deconstruction.Init.Count > 0) { |
||||
var a = deconstruction.Init[0].Variable.LoadInstructions.Single(); |
||||
var b = v.LoadInstructions.Single(); |
||||
if (!b.IsBefore(a)) |
||||
return false; |
||||
} |
||||
context.Step("InlineDeconstructionInitializer", block.Instructions[pos]); |
||||
deconstruction.Init.Insert(0, (StLoc)block.Instructions[pos]); |
||||
block.Instructions.RemoveAt(pos); |
||||
v.Kind = VariableKind.DeconstructionInitTemporary; |
||||
return true; |
||||
} |
||||
|
||||
bool TransformDeconstruction(Block block, int pos) |
||||
{ |
||||
int startPos = pos; |
||||
Action<DeconstructInstruction> delayedActions = null; |
||||
if (MatchDeconstruction(block.Instructions[pos], out IMethod deconstructMethod, |
||||
out ILInstruction rootTestedOperand)) |
||||
{ |
||||
pos++; |
||||
} |
||||
if (!MatchConversions(block, ref pos, out var conversions, out var conversionStLocs, ref delayedActions)) |
||||
return false; |
||||
|
||||
if (!MatchAssignments(block, ref pos, conversions, conversionStLocs, ref delayedActions)) |
||||
return false; |
||||
// first tuple element may not be discarded,
|
||||
// otherwise we would run this transform on a suffix of the actual pattern.
|
||||
if (deconstructionResults[0] == null) |
||||
return false; |
||||
context.Step("Deconstruction", block.Instructions[startPos]); |
||||
DeconstructInstruction replacement = new DeconstructInstruction(); |
||||
IType deconstructedType; |
||||
if (deconstructMethod == null) { |
||||
deconstructedType = this.tupleType; |
||||
rootTestedOperand = new LdLoc(this.tupleVariable); |
||||
} else { |
||||
if (deconstructMethod.IsStatic) { |
||||
deconstructedType = deconstructMethod.Parameters[0].Type; |
||||
} else { |
||||
deconstructedType = deconstructMethod.DeclaringType; |
||||
} |
||||
} |
||||
var rootTempVariable = context.Function.RegisterVariable(VariableKind.PatternLocal, deconstructedType); |
||||
replacement.Pattern = new MatchInstruction(rootTempVariable, deconstructMethod, rootTestedOperand) { |
||||
IsDeconstructCall = deconstructMethod != null, |
||||
IsDeconstructTuple = this.tupleType != null |
||||
}; |
||||
int index = 0; |
||||
foreach (ILVariable v in deconstructionResults) { |
||||
var result = v; |
||||
if (result == null) { |
||||
var freshVar = new ILVariable(VariableKind.PatternLocal, this.tupleType.ElementTypes[index]) { Name = "E_" + index }; |
||||
context.Function.Variables.Add(freshVar); |
||||
result = freshVar; |
||||
} else { |
||||
result.Kind = VariableKind.PatternLocal; |
||||
} |
||||
replacement.Pattern.SubPatterns.Add( |
||||
new MatchInstruction( |
||||
result, |
||||
new DeconstructResultInstruction(index, result.StackType, new LdLoc(rootTempVariable)) |
||||
) |
||||
); |
||||
index++; |
||||
} |
||||
replacement.Conversions = new Block(BlockKind.DeconstructionConversions); |
||||
foreach (var convInst in conversionStLocs) { |
||||
replacement.Conversions.Instructions.Add(convInst); |
||||
} |
||||
replacement.Assignments = new Block(BlockKind.DeconstructionAssignments); |
||||
delayedActions?.Invoke(replacement); |
||||
block.Instructions[startPos] = replacement; |
||||
block.Instructions.RemoveRange(startPos + 1, pos - startPos - 1); |
||||
return true; |
||||
} |
||||
|
||||
bool MatchDeconstruction(ILInstruction inst, out IMethod deconstructMethod, |
||||
out ILInstruction testedOperand) |
||||
{ |
||||
testedOperand = null; |
||||
deconstructMethod = null; |
||||
deconstructionResults = null; |
||||
if (!(inst is CallInstruction call)) |
||||
return false; |
||||
if (!MatchInstruction.IsDeconstructMethod(call.Method)) |
||||
return false; |
||||
if (call.Method.IsStatic == call is CallVirt) |
||||
return false; |
||||
if (call.Arguments.Count < 3) |
||||
return false; |
||||
deconstructionResults = new ILVariable[call.Arguments.Count - 1]; |
||||
for (int i = 0; i < deconstructionResults.Length; i++) { |
||||
if (!call.Arguments[i + 1].MatchLdLoca(out var v)) |
||||
return false; |
||||
// TODO v.LoadCount may be 2 if the deconstruction is assigned to a tuple variable
|
||||
// or 0? because of discards
|
||||
if (!(v.StoreCount == 0 && v.AddressCount == 1 && v.LoadCount <= 1)) |
||||
return false; |
||||
deconstructionResultsLookup.Add(v, i); |
||||
deconstructionResults[i] = v; |
||||
} |
||||
testedOperand = call.Arguments[0]; |
||||
deconstructMethod = call.Method; |
||||
return true; |
||||
} |
||||
|
||||
bool MatchConversions(Block block, ref int pos, |
||||
out Dictionary<ILVariable, ConversionInfo> conversions, |
||||
out List<StLoc> conversionStLocs, |
||||
ref Action<DeconstructInstruction> delayedActions) |
||||
{ |
||||
conversions = new Dictionary<ILVariable, ConversionInfo>(); |
||||
conversionStLocs = new List<StLoc>(); |
||||
int previousIndex = -1; |
||||
while (MatchConversion( |
||||
block.Instructions.ElementAtOrDefault(pos), out var inputInstruction, |
||||
out var outputVariable, out var info)) { |
||||
int index = FindIndex(inputInstruction, out var tupleAccessAdjustment); |
||||
if (index <= previousIndex) |
||||
return false; |
||||
if (!(outputVariable.IsSingleDefinition && outputVariable.LoadCount == 1)) |
||||
return false; |
||||
delayedActions += tupleAccessAdjustment; |
||||
deconstructionResultsLookup.Add(outputVariable, index); |
||||
conversions.Add(outputVariable, info); |
||||
conversionStLocs.Add((StLoc)block.Instructions[pos]); |
||||
pos++; |
||||
previousIndex = index; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
bool MatchConversion(ILInstruction inst, out ILInstruction inputInstruction, |
||||
out ILVariable outputVariable, out ConversionInfo info) |
||||
{ |
||||
info = default; |
||||
inputInstruction = null; |
||||
if (!inst.MatchStLoc(out outputVariable, out var value)) |
||||
return false; |
||||
if (!(value is Conv conv)) |
||||
return false; |
||||
info = new ConversionInfo { |
||||
inputType = conv.Argument.InferType(context.TypeSystem), |
||||
conv = conv |
||||
}; |
||||
inputInstruction = conv.Argument; |
||||
return true; |
||||
} |
||||
|
||||
bool MatchAssignments(Block block, ref int pos, |
||||
Dictionary<ILVariable, ConversionInfo> conversions, |
||||
List<StLoc> conversionStLocs, |
||||
ref Action<DeconstructInstruction> delayedActions) |
||||
{ |
||||
int previousIndex = -1; |
||||
int conversionStLocIndex = 0; |
||||
int startPos = pos; |
||||
while (MatchAssignment(block.Instructions.ElementAtOrDefault(pos), out var targetType, out var valueInst, out var addAssignment)) { |
||||
int index = FindIndex(valueInst, out var tupleAccessAdjustment); |
||||
if (index <= previousIndex) |
||||
return false; |
||||
AddMissingAssignmentsForConversions(index, ref delayedActions); |
||||
if (!(valueInst.MatchLdLoc(out var resultVariable) |
||||
&& conversions.TryGetValue(resultVariable, out var conversionInfo))) |
||||
{ |
||||
conversionInfo = new ConversionInfo { |
||||
inputType = valueInst.InferType(context.TypeSystem) |
||||
}; |
||||
} |
||||
if (block.Instructions[pos].MatchStLoc(out var assignmentTarget, out _) |
||||
&& assignmentTarget.Kind == VariableKind.StackSlot |
||||
&& assignmentTarget.IsSingleDefinition |
||||
&& conversionInfo.conv == null) { |
||||
delayedActions += _ => { |
||||
assignmentTarget.Type = conversionInfo.inputType; |
||||
}; |
||||
} else { |
||||
if (!IsCompatibleImplicitConversion(targetType, conversionInfo)) |
||||
return false; |
||||
} |
||||
delayedActions += addAssignment; |
||||
delayedActions += tupleAccessAdjustment; |
||||
pos++; |
||||
previousIndex = index; |
||||
} |
||||
AddMissingAssignmentsForConversions(int.MaxValue, ref delayedActions); |
||||
return startPos != pos; |
||||
|
||||
void AddMissingAssignmentsForConversions(int index, ref Action<DeconstructInstruction> delayedActions) |
||||
{ |
||||
while (conversionStLocIndex < conversionStLocs.Count) { |
||||
var stLoc = conversionStLocs[conversionStLocIndex]; |
||||
int conversionResultIndex = deconstructionResultsLookup[stLoc.Variable]; |
||||
|
||||
if (conversionResultIndex >= index) |
||||
break; |
||||
if (conversionResultIndex > previousIndex) { |
||||
delayedActions += (DeconstructInstruction deconstructInst) => { |
||||
var freshVar = context.Function.RegisterVariable(VariableKind.StackSlot, stLoc.Variable.Type); |
||||
deconstructInst.Assignments.Instructions.Add(new StLoc(stLoc.Variable, new LdLoc(freshVar))); |
||||
stLoc.Variable = freshVar; |
||||
}; |
||||
} |
||||
conversionStLocIndex++; |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool MatchAssignment(ILInstruction inst, out IType targetType, out ILInstruction valueInst, out Action<DeconstructInstruction> addAssignment) |
||||
{ |
||||
targetType = null; |
||||
valueInst = null; |
||||
addAssignment = null; |
||||
if (inst == null) |
||||
return false; |
||||
if (inst.MatchStLoc(out var v, out var value) |
||||
&& value is Block block && block.MatchInlineAssignBlock(out var call, out valueInst)) { |
||||
if (!DeconstructInstruction.IsAssignment(call, context.TypeSystem, out targetType, out _)) |
||||
return false; |
||||
if (!(v.IsSingleDefinition && v.LoadCount == 0)) |
||||
return false; |
||||
var valueInstCopy = valueInst; |
||||
addAssignment = (DeconstructInstruction deconstructInst) => { |
||||
call.Arguments[call.Arguments.Count - 1] = valueInstCopy; |
||||
deconstructInst.Assignments.Instructions.Add(call); |
||||
}; |
||||
return true; |
||||
} else if (DeconstructInstruction.IsAssignment(inst, context.TypeSystem, out targetType, out valueInst)) { |
||||
// OK - use the assignment as is
|
||||
addAssignment = (DeconstructInstruction deconstructInst) => { |
||||
deconstructInst.Assignments.Instructions.Add(inst); |
||||
}; |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
bool IsCompatibleImplicitConversion(IType targetType, ConversionInfo conversionInfo) |
||||
{ |
||||
var c = CSharpConversions.Get(context.TypeSystem) |
||||
.ImplicitConversion(conversionInfo.inputType, targetType); |
||||
if (!c.IsValid) |
||||
return false; |
||||
var inputType = conversionInfo.inputType; |
||||
var conv = conversionInfo.conv; |
||||
if (c.IsIdentityConversion || c.IsReferenceConversion) { |
||||
return conv == null || conv.Kind == ConversionKind.Nop; |
||||
} |
||||
if (c.IsNumericConversion) { |
||||
switch (conv.Kind) { |
||||
case ConversionKind.IntToFloat: |
||||
return inputType.GetSign() == conv.InputSign; |
||||
case ConversionKind.FloatPrecisionChange: |
||||
return true; |
||||
case ConversionKind.SignExtend: |
||||
return inputType.GetSign() == Sign.Signed; |
||||
case ConversionKind.ZeroExtend: |
||||
return inputType.GetSign() == Sign.Unsigned; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
{ |
||||
"profiles": { |
||||
"ILSpy.BamlDecompiler": { |
||||
"commandName": "Executable", |
||||
"executablePath": "C:\\Users\\sie_p\\Projects\\ILSpy\\ILSpy\\bin\\Debug\\net472\\ILSpy.exe", |
||||
"commandLineArgs": "/separate" |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
{ |
||||
"profiles": { |
||||
"ILSpy.ReadyToRun": { |
||||
"commandName": "Executable", |
||||
"executablePath": "C:\\Users\\sie_p\\Projects\\ILSpy\\ILSpy\\bin\\Debug\\net472\\ILSpy.exe", |
||||
"commandLineArgs": "/separate /language:ReadyToRun" |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.ComponentModel.Composition; |
||||
using System.IO; |
||||
using System.Threading.Tasks; |
||||
|
||||
using ICSharpCode.AvalonEdit.Highlighting; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.ILSpy.TextView; |
||||
using ICSharpCode.ILSpy.TreeNodes; |
||||
using ICSharpCode.ILSpy.ViewModels; |
||||
|
||||
namespace ICSharpCode.ILSpy.TreeNodes |
||||
{ |
||||
[Export(typeof(IResourceNodeFactory))] |
||||
sealed class JsonResourceNodeFactory : IResourceNodeFactory |
||||
{ |
||||
private readonly static string[] jsonFileExtensions = { ".json" }; |
||||
|
||||
public ILSpyTreeNode CreateNode(Resource resource) |
||||
{ |
||||
Stream stream = resource.TryOpenStream(); |
||||
if (stream == null) |
||||
return null; |
||||
return CreateNode(resource.Name, stream); |
||||
} |
||||
|
||||
public ILSpyTreeNode CreateNode(string key, object data) |
||||
{ |
||||
if (!(data is Stream)) |
||||
return null; |
||||
foreach (string fileExt in jsonFileExtensions) |
||||
{ |
||||
if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase)) |
||||
return new JsonResourceEntryNode(key, (Stream)data); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
sealed class JsonResourceEntryNode : ResourceEntryNode |
||||
{ |
||||
string json; |
||||
|
||||
public JsonResourceEntryNode(string key, Stream data) |
||||
: base(key, data) |
||||
{ |
||||
} |
||||
|
||||
// TODO : add Json Icon
|
||||
public override object Icon => Images.Resource; |
||||
|
||||
public override bool View(TabPageModel tabPage) |
||||
{ |
||||
AvalonEditTextOutput output = new AvalonEditTextOutput(); |
||||
IHighlightingDefinition highlighting = null; |
||||
|
||||
tabPage.ShowTextView(textView => textView.RunWithCancellation( |
||||
token => Task.Factory.StartNew( |
||||
() => { |
||||
try { |
||||
// cache read XAML because stream will be closed after first read
|
||||
if (json == null) { |
||||
using (var reader = new StreamReader(Data)) { |
||||
json = reader.ReadToEnd(); |
||||
} |
||||
} |
||||
output.Write(json); |
||||
highlighting = HighlightingManager.Instance.GetDefinitionByExtension(".json"); |
||||
} |
||||
catch (Exception ex) { |
||||
output.Write(ex.ToString()); |
||||
} |
||||
return output; |
||||
}, token) |
||||
).Then(t => textView.ShowNode(t, this, highlighting)) |
||||
.HandleExceptions()); |
||||
tabPage.SupportsLanguageSwitching = false; |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.ComponentModel.Composition; |
||||
using System.IO; |
||||
using System.Threading.Tasks; |
||||
|
||||
using ICSharpCode.AvalonEdit.Highlighting; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.ILSpy.TextView; |
||||
using ICSharpCode.ILSpy.TreeNodes; |
||||
using ICSharpCode.ILSpy.ViewModels; |
||||
|
||||
namespace ICSharpCode.ILSpy.TreeNodes |
||||
{ |
||||
[Export(typeof(IResourceNodeFactory))] |
||||
sealed class TextResourceNodeFactory : IResourceNodeFactory |
||||
{ |
||||
private readonly static string[] txtFileExtensions = { ".txt", ".md" }; |
||||
|
||||
public ILSpyTreeNode CreateNode(Resource resource) |
||||
{ |
||||
Stream stream = resource.TryOpenStream(); |
||||
if (stream == null) |
||||
return null; |
||||
return CreateNode(resource.Name, stream); |
||||
} |
||||
|
||||
public ILSpyTreeNode CreateNode(string key, object data) |
||||
{ |
||||
if (!(data is Stream)) |
||||
return null; |
||||
foreach (string fileExt in txtFileExtensions) |
||||
{ |
||||
if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase)) |
||||
return new TextResourceEntryNode(key, (Stream)data); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
sealed class TextResourceEntryNode : ResourceEntryNode |
||||
{ |
||||
string text; |
||||
|
||||
public TextResourceEntryNode(string key, Stream data) |
||||
: base(key, data) |
||||
{ |
||||
} |
||||
|
||||
public override object Icon => Images.Resource; |
||||
|
||||
public override bool View(TabPageModel tabPage) |
||||
{ |
||||
AvalonEditTextOutput output = new AvalonEditTextOutput(); |
||||
IHighlightingDefinition highlighting = null; |
||||
|
||||
tabPage.ShowTextView(textView => textView.RunWithCancellation( |
||||
token => Task.Factory.StartNew( |
||||
() => { |
||||
try { |
||||
// cache read text because stream will be closed after first read
|
||||
if (text == null) { |
||||
using (var reader = new StreamReader(Data)) { |
||||
text = reader.ReadToEnd(); |
||||
} |
||||
} |
||||
output.Write(text); |
||||
highlighting = null; |
||||
} |
||||
catch (Exception ex) { |
||||
output.Write(ex.ToString()); |
||||
} |
||||
return output; |
||||
}, token) |
||||
).Then(t => textView.ShowNode(t, this, highlighting)) |
||||
.HandleExceptions()); |
||||
tabPage.SupportsLanguageSwitching = false; |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
ILAst Pattern Matching |
||||
================= |
||||
|
||||
Some IL instructions are classified as "patterns". |
||||
```c# |
||||
abstract class PatternMatchILInstruction : IStoreInstruction { |
||||
public ILInstruction TestedOperand { get; } |
||||
public ILVariable Variable { get; } // variable that receives the match result; may also be a temporary |
||||
} |
||||
|
||||
public bool IsPattern(this ILInstruction inst, out ILInstruction testedOperand) => inst switch { |
||||
PatternMatchILInstruction pm => testedOperand = pm.TestedOperand; return true; |
||||
LogicNot logicNot => IsPattern(logicNot.Operand, out testedOperand), |
||||
Comp comp => testedOperand = comp.Left; return IsConstant(comp.Right); |
||||
} |
||||
``` |
||||
Every `match.*` instruction has the following properties: |
||||
* The `TestedOperand` specifies what gets matched against the pattern. |
||||
* The `Variable` stores the value of `TestedOperand` (after converting to the matched type, if appropriate). |
||||
* If this variable is also used outside the `match.*` node, it corresponds to the C# `single_variable_designation`. |
||||
* Otherwise it's a temporary used for pattern matching. |
||||
* I think in both cases it should have VariableKind.PatternVar |
||||
* The match instruction evaluates to `StackType.I4`: 0 if the pattern was matched, 1 otherwise. |
||||
|
||||
Some `match` instructions have a body with `List<ILInstruction> nestedPatterns`. Here every nested pattern must be a pattern according to `IsPattern()`, and the `testedOperand` of each must be a member of the `Variable` of the parent pattern. (members are: field, property, or deconstruction.result). |
||||
(exception: `match.and`/`match.or`, these instead require the `testedOperand` to be exactly the `Variable` of the parent pattern) |
||||
|
||||
|
||||
Examples |
||||
-------- |
||||
1) `expr is var x` |
||||
=> |
||||
`match.var(x = expr)` |
||||
=> |
||||
``` |
||||
Block (VarPattern) { |
||||
stloc x(expr) // single eval expr |
||||
final: ldc.i4 1 // match always |
||||
} |
||||
``` |
||||
2) `expr is T x` |
||||
=> |
||||
`match.type T(x = expr) {}` |
||||
=> |
||||
``` |
||||
Block (TypePattern) { |
||||
stloc x(isinst T(expr)) |
||||
final: x != null |
||||
} |
||||
``` |
||||
3) `expr is C { A: var x } z` |
||||
=> |
||||
``` |
||||
match.type C(z = expr) { |
||||
match.var(x = z.A) |
||||
} |
||||
``` |
||||
=> |
||||
``` |
||||
Block (TypePattern) { |
||||
stloc z(isinst T(expr)) |
||||
final: (z != null) |
||||
&& Block(VarPattern) { |
||||
stloc x(z.A) |
||||
final: ldc.i4 1 |
||||
} |
||||
} |
||||
``` |
||||
4) `expr is C { A: var x, B: 42, C: { A: 4 } } z` |
||||
=> |
||||
``` |
||||
match.type C(z = expr) { |
||||
match.var (x = z.A), |
||||
comp (z.B == 42), |
||||
match.recursive (temp2 = z.C) { |
||||
comp (temp2.A == 4) |
||||
} |
||||
} |
||||
``` |
||||
=> |
||||
``` |
||||
Block (TypePattern) { |
||||
stloc z(isinst C(expr)) |
||||
final: (z != null) |
||||
&& Block(VarPattern) { |
||||
stloc x(z.A) |
||||
final: ldc.i4 1 |
||||
} |
||||
&& comp (z.B == 42) |
||||
&& Block(RecursivePattern) { |
||||
stloc temp2(z.C) |
||||
final: (temp2 != null) |
||||
&& comp (temp2.A == 4) |
||||
} |
||||
} |
||||
``` |
||||
5) `expr is C(var x, var y, <4) { ... }` |
||||
=> |
||||
``` |
||||
match.recursive.type.deconstruct(C tmp1 = expr) { |
||||
match.var(x = deconstruct.result0(tmp1)), |
||||
match.var(y = deconstruct.result1(tmp1)), |
||||
comp(deconstruct.result2(tmp1) < 4), |
||||
} |
||||
``` |
||||
|
||||
6) `expr is C(1, D(2, 3))` |
||||
=> |
||||
``` |
||||
match.type.deconstruct(C c = expr) { |
||||
comp(deconstruct.result0(c) == 1), |
||||
match.type.deconstruct(D d = deconstruct.result1(c)) { |
||||
comp(deconstruct.result0(d) == 2), |
||||
comp(deconstruct.result1(d) == 2), |
||||
} |
||||
} |
||||
``` |
||||
|
||||
7) `x is >= 0 and var y and <= 100` |
||||
``` |
||||
match.and(tmp1 = x) { |
||||
comp(tmp1 >= 0), |
||||
match.var(y = tmp1), |
||||
comp(tmp1 <= 100) |
||||
} |
||||
``` |
||||
|
||||
8) `x is not C _` |
||||
=> |
||||
``` |
||||
logic.not( |
||||
match.type(C tmp1 = x) {} |
||||
) |
||||
``` |
||||
|
||||
9) `expr is (var a, var b)` (when expr is object) |
||||
=> |
||||
``` |
||||
match.type.deconstruct(ITuple tmp = expr) { |
||||
match.var(a = deconstruct.result0(tmp)), |
||||
match.var(b = deconstruct.result1(tmp)), |
||||
} |
||||
``` |
||||
|
||||
10) `expr is (var a, var b)` (when expr is ValueTuple<int, int>) |
||||
=> |
||||
``` |
||||
match.recursive(tmp = expr) { |
||||
match.var(a = tmp.Item1), |
||||
match.var(b = tmp.Item2), |
||||
} |
||||
``` |
||||
Binary file not shown.
Loading…
Reference in new issue