Browse Source

Add support for tuple deconstruction

pull/2119/head
Siegfried Pammer 5 years ago
parent
commit
7f4653c274
  1. 38
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs
  2. 15
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs
  3. 3
      ICSharpCode.Decompiler/IL/Instructions.cs
  4. 2
      ICSharpCode.Decompiler/IL/Instructions.tt
  5. 2
      ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs
  6. 9
      ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs
  7. 30
      ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
  8. 134
      ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs
  9. 2
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  10. 5
      ICSharpCode.Decompiler/TypeSystem/TupleType.cs
  11. 9
      ILSpy.BamlDecompiler/Properties/launchSettings.json
  12. 9
      ILSpy.ReadyToRun/Properties/launchSettings.json
  13. 99
      ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs
  14. 98
      ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs
  15. 152
      doc/ILAst Pattern Matching.md
  16. BIN
      msbuild.binlog

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

@ -107,6 +107,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -107,6 +107,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
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})");
@ -118,6 +130,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -118,6 +130,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
Property_NoDeconstruction_SwappedAssignments();
Property_NoDeconstruction_SwappedInits();
Property_IntToUIntConversion();
NoDeconstruction_NotUsingConver();
NoDeconstruction_NotUsingConver_Tuple();
}
public void Property_NoDeconstruction_SwappedAssignments()
@ -148,5 +162,29 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -148,5 +162,29 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
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);
}
}
}

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

@ -117,6 +117,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -117,6 +117,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return null;
}
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;
@ -201,5 +211,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -201,5 +211,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
(Get(0).NMy, _) = GetSource<MyInt?, MyInt>();
}
public void Tuple_Property_NoConversion()
{
(Get(0).NMy, Get(1).My) = GetTuple<MyInt?, MyInt>();
}
}
}

3
ICSharpCode.Decompiler/IL/Instructions.cs

@ -6140,6 +6140,7 @@ namespace ICSharpCode.Decompiler.IL @@ -6140,6 +6140,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>Returns the method operand.</summary>
public IMethod Method { get { return method; } }
public bool IsDeconstructCall;
public bool IsDeconstructTuple;
public bool CheckType;
public bool CheckNotNull;
public static readonly SlotInfo TestedOperandSlot = new SlotInfo("TestedOperand", canInlineInto: true);
@ -6219,7 +6220,7 @@ namespace ICSharpCode.Decompiler.IL @@ -6219,7 +6220,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as MatchInstruction;
return o != null && variable == o.variable && object.Equals(method, o.method) && this.IsDeconstructCall == o.IsDeconstructCall && this.CheckType == o.CheckType && this.CheckNotNull == o.CheckNotNull && this.testedOperand.PerformMatch(o.testedOperand, ref match) && Patterns.ListMatch.DoMatch(this.SubPatterns, o.SubPatterns, ref match);
return o != null && variable == o.variable && object.Equals(method, o.method) && this.IsDeconstructCall == o.IsDeconstructCall && this.IsDeconstructTuple == o.IsDeconstructTuple && this.CheckType == o.CheckType && this.CheckNotNull == o.CheckNotNull && this.testedOperand.PerformMatch(o.testedOperand, ref match) && Patterns.ListMatch.DoMatch(this.SubPatterns, o.SubPatterns, ref match);
}
internal override void CheckInvariant(ILPhase phase)
{

2
ICSharpCode.Decompiler/IL/Instructions.tt

@ -337,7 +337,7 @@ @@ -337,7 +337,7 @@
new OpCode("match", "ILAst representation of C# patterns",
CustomClassName("MatchInstruction"), HasVariableOperand("Store"), HasMethodOperand,
BoolFlag("IsDeconstructCall"), BoolFlag("CheckType"), BoolFlag("CheckNotNull"),
BoolFlag("IsDeconstructCall"), BoolFlag("IsDeconstructTuple"), BoolFlag("CheckType"), BoolFlag("CheckNotNull"),
CustomChildren(new []{
new ChildInfo("testedOperand") { CanInlineInto = true },
new ChildInfo("subPatterns") { IsCollection = true }

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

@ -268,7 +268,7 @@ namespace ICSharpCode.Decompiler.IL @@ -268,7 +268,7 @@ namespace ICSharpCode.Decompiler.IL
void ValidatePattern(MatchInstruction inst)
{
Debug.Assert(inst.IsDeconstructCall);
Debug.Assert(inst.IsDeconstructCall || inst.IsDeconstructTuple);
Debug.Assert(!inst.CheckNotNull && !inst.CheckType);
Debug.Assert(!inst.HasDesignator);
foreach (var subPattern in inst.SubPatterns.Cast<MatchInstruction>()) {

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

@ -58,13 +58,10 @@ namespace ICSharpCode.Decompiler.IL @@ -58,13 +58,10 @@ namespace ICSharpCode.Decompiler.IL
void AdditionalInvariants()
{
var matchInst = FindMatch();
Debug.Assert(matchInst != null && matchInst.IsDeconstructCall);
Debug.Assert(matchInst != null && (matchInst.IsDeconstructCall || matchInst.IsDeconstructTuple));
Debug.Assert(Argument.MatchLdLoc(matchInst.Variable));
var outParamType = matchInst.GetDeconstructResult(this.Index).Type;
if (outParamType is ByReferenceType brt)
Debug.Assert(brt.ElementType.GetStackType() == ResultType);
else
Debug.Fail("deconstruct out param must be by reference");
var outParamType = matchInst.GetDeconstructResultType(this.Index);
Debug.Assert(outParamType.GetStackType() == ResultType);
}
}
}

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

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
// 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;
@ -87,7 +88,7 @@ namespace ICSharpCode.Decompiler.IL @@ -87,7 +88,7 @@ namespace ICSharpCode.Decompiler.IL
}
*/
public bool IsVar => !CheckType && !CheckNotNull && !IsDeconstructCall && SubPatterns.Count == 0;
public bool IsVar => !CheckType && !CheckNotNull && !IsDeconstructCall && !IsDeconstructTuple && SubPatterns.Count == 0;
public bool HasDesignator => Variable.LoadCount + Variable.AddressCount > SubPatterns.Count;
@ -95,6 +96,8 @@ namespace ICSharpCode.Decompiler.IL @@ -95,6 +96,8 @@ namespace ICSharpCode.Decompiler.IL
get {
if (IsDeconstructCall)
return method.Parameters.Count - (method.IsStatic ? 1 : 0);
else if (IsDeconstructTuple)
return TupleType.GetTupleElementTypes(variable.Type).Length;
else
return 0;
}
@ -145,11 +148,20 @@ namespace ICSharpCode.Decompiler.IL @@ -145,11 +148,20 @@ namespace ICSharpCode.Decompiler.IL
};
}
internal IParameter GetDeconstructResult(int index)
internal IType GetDeconstructResultType(int index)
{
Debug.Assert(this.IsDeconstructCall);
if (this.IsDeconstructCall) {
int firstOutParam = (method.IsStatic ? 1 : 0);
return this.Method.Parameters[firstOutParam + index];
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()
@ -157,8 +169,13 @@ namespace ICSharpCode.Decompiler.IL @@ -157,8 +169,13 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(variable.Kind == VariableKind.PatternLocal);
if (this.IsDeconstructCall) {
Debug.Assert(IsDeconstructMethod(method));
Debug.Assert(SubPatterns.Count >= NumPositionalPatterns);
} 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");
@ -224,6 +241,9 @@ namespace ICSharpCode.Decompiler.IL @@ -224,6 +241,9 @@ namespace ICSharpCode.Decompiler.IL
method.WriteTo(output);
output.Write(']');
}
if (IsDeconstructTuple) {
output.Write(".tuple");
}
output.Write(' ');
output.Write('(');
Variable.WriteTo(output);

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

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Resources;
@ -33,6 +34,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -33,6 +34,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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))
@ -65,6 +70,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -65,6 +70,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
try {
this.context = context;
Reset();
if (TransformDeconstruction(block, pos))
return;
@ -72,15 +78,67 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -72,15 +78,67 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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(lhs)
/// expr(..., deconstruct { ... }, ...)
@ -118,34 +176,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -118,34 +176,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
int startPos = pos;
Action<DeconstructInstruction> delayedActions = null;
if (MatchDeconstruction(block.Instructions[pos], out var deconstructMethod,
out var rootTestedOperand, out var deconstructionResults,
out var deconstructionResultsLookup))
if (MatchDeconstruction(block.Instructions[pos], out IMethod deconstructMethod,
out ILInstruction rootTestedOperand))
{
pos++;
}
else {
return false;
}
if (!MatchConversions(block, ref pos, deconstructionResultsLookup, out var conversions, out var conversionStLocs))
if (!MatchConversions(block, ref pos, out var conversions, out var conversionStLocs, ref delayedActions))
return false;
if (!MatchAssignments(block, ref pos, deconstructionResultsLookup, conversions, conversionStLocs, ref delayedActions))
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 = true
IsDeconstructCall = deconstructMethod != null,
IsDeconstructTuple = this.tupleType != null
};
int index = 0;
foreach (var result in deconstructionResults) {
foreach (ILVariable result in deconstructionResults) {
result.Kind = VariableKind.PatternLocal;
replacement.Pattern.SubPatterns.Add(
new MatchInstruction(
@ -167,13 +231,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -167,13 +231,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
bool MatchDeconstruction(ILInstruction inst, out IMethod deconstructMethod,
out ILInstruction testedOperand, out List<ILVariable> deconstructionResults,
out Dictionary<ILVariable, int> deconstructionResultsLookup)
out ILInstruction testedOperand)
{
testedOperand = null;
deconstructMethod = null;
deconstructionResults = null;
deconstructionResultsLookup = null;
if (!(inst is CallInstruction call))
return false;
if (!MatchInstruction.IsDeconstructMethod(call.Method))
@ -182,17 +244,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -182,17 +244,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (call.Arguments.Count < 3)
return false;
deconstructionResults = new List<ILVariable>();
deconstructionResultsLookup = new Dictionary<ILVariable, int>();
for (int i = 1; i < call.Arguments.Count; i++) {
if (!call.Arguments[i].MatchLdLoca(out var v))
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 - 1);
deconstructionResults.Add(v);
deconstructionResultsLookup.Add(v, i);
deconstructionResults[i] = v;
}
testedOperand = call.Arguments[0];
deconstructMethod = call.Method;
@ -200,26 +261,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -200,26 +261,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
bool MatchConversions(Block block, ref int pos,
Dictionary<ILVariable, int> deconstructionResultsLookup,
out Dictionary<ILVariable, ConversionInfo> conversions,
out List<StLoc> conversionStLocs)
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))
{
if (!inputInstruction.MatchLdLoc(out var inputVariable))
return false;
if (!deconstructionResultsLookup.TryGetValue(inputVariable, out int index))
return false;
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;
deconstructionResultsLookup.Remove(inputVariable);
delayedActions += tupleAccessAdjustment;
deconstructionResultsLookup.Add(outputVariable, index);
conversions.Add(outputVariable, info);
conversionStLocs.Add((StLoc)block.Instructions[pos]);
@ -247,7 +304,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -247,7 +304,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
bool MatchAssignments(Block block, ref int pos,
Dictionary<ILVariable, int> deconstructionResultsLookup,
Dictionary<ILVariable, ConversionInfo> conversions,
List<StLoc> conversionStLocs,
ref Action<DeconstructInstruction> delayedActions)
@ -256,33 +312,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -256,33 +312,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms
int conversionStLocIndex = 0;
int startPos = pos;
while (MatchAssignment(block.Instructions.ElementAtOrDefault(pos), out var targetType, out var valueInst, out var addAssignment)) {
if (!valueInst.MatchLdLoc(out var resultVariable))
return false;
if (!deconstructionResultsLookup.TryGetValue(resultVariable, out int index))
return false;
int index = FindIndex(valueInst, out var tupleAccessAdjustment);
if (index <= previousIndex)
return false;
AddMissingAssignmentsForConversions(index, ref delayedActions);
if (!conversions.TryGetValue(resultVariable, out var conversionInfo)) {
if (!(valueInst.MatchLdLoc(out var resultVariable)
&& conversions.TryGetValue(resultVariable, out var conversionInfo)))
{
conversionInfo = new ConversionInfo {
inputType = resultVariable.Type
inputType = valueInst.InferType(context.TypeSystem)
};
}
if (block.Instructions[pos].MatchStLoc(out var assignmentTarget, out _)
&& assignmentTarget.Kind == VariableKind.StackSlot
&& assignmentTarget.IsSingleDefinition
&& conversionInfo.conv == null)
{
&& conversionInfo.conv == null) {
delayedActions += _ => {
assignmentTarget.Type = conversionInfo.inputType;
};
}
else
{
} else {
if (!IsCompatibleImplicitConversion(targetType, conversionInfo))
return false;
}
delayedActions += addAssignment;
delayedActions += tupleAccessAdjustment;
pos++;
previousIndex = index;
}
@ -303,7 +356,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -303,7 +356,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
deconstructInst.Assignments.Instructions.Add(new StLoc(stLoc.Variable, new LdLoc(freshVar)));
stLoc.Variable = freshVar;
};
}
conversionStLocIndex++;
}

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

@ -504,6 +504,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -504,6 +504,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
//case OpCode.BinaryNumericInstruction when parent.SlotInfo == SwitchInstruction.ValueSlot:
case OpCode.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot:
return true;
case OpCode.MatchInstruction when ((MatchInstruction)parent).IsDeconstructTuple:
return true;
}
// decide based on the top-level target instruction into which we are inlining:
switch (next.OpCode) {

5
ICSharpCode.Decompiler/TypeSystem/TupleType.cs

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

9
ILSpy.BamlDecompiler/Properties/launchSettings.json

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

9
ILSpy.ReadyToRun/Properties/launchSettings.json

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

99
ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels;
namespace ICSharpCode.ILSpy.TreeNodes
{
[Export(typeof(IResourceNodeFactory))]
sealed class JsonResourceNodeFactory : IResourceNodeFactory
{
private readonly static string[] jsonFileExtensions = { ".json" };
public ILSpyTreeNode CreateNode(Resource resource)
{
Stream stream = resource.TryOpenStream();
if (stream == null)
return null;
return CreateNode(resource.Name, stream);
}
public ILSpyTreeNode CreateNode(string key, object data)
{
if (!(data is Stream))
return null;
foreach (string fileExt in jsonFileExtensions)
{
if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase))
return new JsonResourceEntryNode(key, (Stream)data);
}
return null;
}
}
sealed class JsonResourceEntryNode : ResourceEntryNode
{
string json;
public JsonResourceEntryNode(string key, Stream data)
: base(key, data)
{
}
// TODO : add Json Icon
public override object Icon => Images.Resource;
public override bool View(TabPageModel tabPage)
{
AvalonEditTextOutput output = new AvalonEditTextOutput();
IHighlightingDefinition highlighting = null;
tabPage.ShowTextView(textView => textView.RunWithCancellation(
token => Task.Factory.StartNew(
() => {
try {
// cache read XAML because stream will be closed after first read
if (json == null) {
using (var reader = new StreamReader(Data)) {
json = reader.ReadToEnd();
}
}
output.Write(json);
highlighting = HighlightingManager.Instance.GetDefinitionByExtension(".json");
}
catch (Exception ex) {
output.Write(ex.ToString());
}
return output;
}, token)
).Then(t => textView.ShowNode(t, this, highlighting))
.HandleExceptions());
tabPage.SupportsLanguageSwitching = false;
return true;
}
}
}

98
ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs

@ -0,0 +1,98 @@ @@ -0,0 +1,98 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Threading.Tasks;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels;
namespace ICSharpCode.ILSpy.TreeNodes
{
[Export(typeof(IResourceNodeFactory))]
sealed class TextResourceNodeFactory : IResourceNodeFactory
{
private readonly static string[] txtFileExtensions = { ".txt", ".md" };
public ILSpyTreeNode CreateNode(Resource resource)
{
Stream stream = resource.TryOpenStream();
if (stream == null)
return null;
return CreateNode(resource.Name, stream);
}
public ILSpyTreeNode CreateNode(string key, object data)
{
if (!(data is Stream))
return null;
foreach (string fileExt in txtFileExtensions)
{
if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase))
return new TextResourceEntryNode(key, (Stream)data);
}
return null;
}
}
sealed class TextResourceEntryNode : ResourceEntryNode
{
string text;
public TextResourceEntryNode(string key, Stream data)
: base(key, data)
{
}
public override object Icon => Images.Resource;
public override bool View(TabPageModel tabPage)
{
AvalonEditTextOutput output = new AvalonEditTextOutput();
IHighlightingDefinition highlighting = null;
tabPage.ShowTextView(textView => textView.RunWithCancellation(
token => Task.Factory.StartNew(
() => {
try {
// cache read text because stream will be closed after first read
if (text == null) {
using (var reader = new StreamReader(Data)) {
text = reader.ReadToEnd();
}
}
output.Write(text);
highlighting = null;
}
catch (Exception ex) {
output.Write(ex.ToString());
}
return output;
}, token)
).Then(t => textView.ShowNode(t, this, highlighting))
.HandleExceptions());
tabPage.SupportsLanguageSwitching = false;
return true;
}
}
}

152
doc/ILAst Pattern Matching.md

@ -0,0 +1,152 @@ @@ -0,0 +1,152 @@
ILAst Pattern Matching
=================
Some IL instructions are classified as "patterns".
```c#
abstract class PatternMatchILInstruction : IStoreInstruction {
public ILInstruction TestedOperand { get; }
public ILVariable Variable { get; } // variable that receives the match result; may also be a temporary
}
public bool IsPattern(this ILInstruction inst, out ILInstruction testedOperand) => inst switch {
PatternMatchILInstruction pm => testedOperand = pm.TestedOperand; return true;
LogicNot logicNot => IsPattern(logicNot.Operand, out testedOperand),
Comp comp => testedOperand = comp.Left; return IsConstant(comp.Right);
}
```
Every `match.*` instruction has the following properties:
* The `TestedOperand` specifies what gets matched against the pattern.
* The `Variable` stores the value of `TestedOperand` (after converting to the matched type, if appropriate).
* If this variable is also used outside the `match.*` node, it corresponds to the C# `single_variable_designation`.
* Otherwise it's a temporary used for pattern matching.
* I think in both cases it should have VariableKind.PatternVar
* The match instruction evaluates to `StackType.I4`: 0 if the pattern was matched, 1 otherwise.
Some `match` instructions have a body with `List<ILInstruction> nestedPatterns`. Here every nested pattern must be a pattern according to `IsPattern()`, and the `testedOperand` of each must be a member of the `Variable` of the parent pattern. (members are: field, property, or deconstruction.result).
(exception: `match.and`/`match.or`, these instead require the `testedOperand` to be exactly the `Variable` of the parent pattern)
Examples
--------
1) `expr is var x`
=>
`match.var(x = expr)`
=>
```
Block (VarPattern) {
stloc x(expr) // single eval expr
final: ldc.i4 1 // match always
}
```
2) `expr is T x`
=>
`match.type T(x = expr) {}`
=>
```
Block (TypePattern) {
stloc x(isinst T(expr))
final: x != null
}
```
3) `expr is C { A: var x } z`
=>
```
match.type C(z = expr) {
match.var(x = z.A)
}
```
=>
```
Block (TypePattern) {
stloc z(isinst T(expr))
final: (z != null)
&& Block(VarPattern) {
stloc x(z.A)
final: ldc.i4 1
}
}
```
4) `expr is C { A: var x, B: 42, C: { A: 4 } } z`
=>
```
match.type C(z = expr) {
match.var (x = z.A),
comp (z.B == 42),
match.recursive (temp2 = z.C) {
comp (temp2.A == 4)
}
}
```
=>
```
Block (TypePattern) {
stloc z(isinst C(expr))
final: (z != null)
&& Block(VarPattern) {
stloc x(z.A)
final: ldc.i4 1
}
&& comp (z.B == 42)
&& Block(RecursivePattern) {
stloc temp2(z.C)
final: (temp2 != null)
&& comp (temp2.A == 4)
}
}
```
5) `expr is C(var x, var y, <4) { ... }`
=>
```
match.recursive.type.deconstruct(C tmp1 = expr) {
match.var(x = deconstruct.result0(tmp1)),
match.var(y = deconstruct.result1(tmp1)),
comp(deconstruct.result2(tmp1) < 4),
}
```
6) `expr is C(1, D(2, 3))`
=>
```
match.type.deconstruct(C c = expr) {
comp(deconstruct.result0(c) == 1),
match.type.deconstruct(D d = deconstruct.result1(c)) {
comp(deconstruct.result0(d) == 2),
comp(deconstruct.result1(d) == 2),
}
}
```
7) `x is >= 0 and var y and <= 100`
```
match.and(tmp1 = x) {
comp(tmp1 >= 0),
match.var(y = tmp1),
comp(tmp1 <= 100)
}
```
8) `x is not C _`
=>
```
logic.not(
match.type(C tmp1 = x) {}
)
```
9) `expr is (var a, var b)` (when expr is object)
=>
```
match.type.deconstruct(ITuple tmp = expr) {
match.var(a = deconstruct.result0(tmp)),
match.var(b = deconstruct.result1(tmp)),
}
```
10) `expr is (var a, var b)` (when expr is ValueTuple<int, int>)
=>
```
match.recursive(tmp = expr) {
match.var(a = tmp.Item1),
match.var(b = tmp.Item2),
}
```

BIN
msbuild.binlog

Binary file not shown.
Loading…
Cancel
Save