diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs index 97ab0e0a7..0bc435a5c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/DeconstructionTests.cs @@ -107,6 +107,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness return new DeconstructionSource(); } + private (T, T2) GetTuple() + { + Console.WriteLine("GetTuple()"); + return default(ValueTuple); + } + + private (T, T2, T3) GetTuple() + { + Console.WriteLine("GetTuple()"); + return default(ValueTuple); + } + private AssignmentTargets Get(int i) { Console.WriteLine($"Get({i})"); @@ -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 t0.UIntProperty = (uint)a; t1.IntProperty = (int)b; } + + public void NoDeconstruction_NotUsingConver() + { + Console.WriteLine("NoDeconstruction_NotUsingConver:"); + AssignmentTargets t0 = Get(0); + int a; + uint b; + GetSource().Deconstruct(out a, out b); + long c = a; + t0.IntProperty = a; + t0.UIntProperty = b; + Console.WriteLine(c); + } + + public void NoDeconstruction_NotUsingConver_Tuple() + { + Console.WriteLine("NoDeconstruction_NotUsingConver_Tuple:"); + AssignmentTargets t0 = Get(0); + var t = GetTuple(); + long c = t.Item1; + t0.IntProperty = t.Item1; + t0.UIntProperty = t.Item2; + Console.WriteLine(c); + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs index e86156723..29a2c6e99 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs @@ -117,6 +117,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return null; } + private (T, T2) GetTuple() + { + return default((T, T2)); + } + + private (T, T2, T3) GetTuple() + { + return default((T, T2, T3)); + } + private AssignmentTargets Get(int i) { return null; @@ -201,5 +211,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { (Get(0).NMy, _) = GetSource(); } + + public void Tuple_Property_NoConversion() + { + (Get(0).NMy, Get(1).My) = GetTuple(); + } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index a483d5c75..66af1937e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -6140,6 +6140,7 @@ namespace ICSharpCode.Decompiler.IL /// Returns the method operand. public IMethod Method { get { return method; } } public bool IsDeconstructCall; + public bool IsDeconstructTuple; public bool CheckType; public bool CheckNotNull; public static readonly SlotInfo TestedOperandSlot = new SlotInfo("TestedOperand", canInlineInto: true); @@ -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) { diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index cb6cdb5e2..627733750 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -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 } diff --git a/ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs index 2a0cbd165..bca210787 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/DeconstructInstruction.cs @@ -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()) { diff --git a/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs index cb18a2bd0..998a0e37c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/DeconstructResultInstruction.cs @@ -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); } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs index 8f5b8fe0a..e29784ebe 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs @@ -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 } */ - 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 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 }; } - internal IParameter GetDeconstructResult(int index) + internal IType GetDeconstructResultType(int index) { - Debug.Assert(this.IsDeconstructCall); - int firstOutParam = (method.IsStatic ? 1 : 0); - return this.Method.Parameters[firstOutParam + 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() @@ -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 method.WriteTo(output); output.Write(']'); } + if (IsDeconstructTuple) { + output.Write(".tuple"); + } output.Write(' '); output.Write('('); Variable.WriteTo(output); diff --git a/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs index ce6c736d4..4bc725926 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs @@ -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 class DeconstructionTransform : IStatementTransform { StatementTransformContext context; + readonly Dictionary deconstructionResultsLookup = new Dictionary(); + ILVariable[] deconstructionResults; + ILVariable tupleVariable; + TupleType tupleType; /* stloc tuple(call MakeIntIntTuple(ldloc this)) @@ -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 return; } finally { this.context = null; + Reset(); } } + private void Reset() + { + this.deconstructionResultsLookup.Clear(); + this.tupleVariable = null; + this.tupleType = null; + this.deconstructionResults = null; + } + struct ConversionInfo { public IType inputType; public Conv conv; } + /// + /// Get index of deconstruction result or tuple element + /// Returns -1 on failure. + /// + int FindIndex(ILInstruction inst, out Action delayedActions) + { + delayedActions = null; + if (inst.MatchLdLoc(out var v)) { + if (!deconstructionResultsLookup.TryGetValue(v, out int index)) + return -1; + return index; + } + if (inst.MatchLdFld(out _, out _)) { + if (!TupleTransform.MatchTupleFieldAccess((LdFlda)((LdObj)inst).Target, out var tupleType, out var target, out int index)) + return -1; + // Item fields are one-based, we use zero-based indexing. + index--; + // normalize tuple type + tupleType = TupleType.FromUnderlyingType(context.TypeSystem, tupleType); + if (!target.MatchLdLoca(out v)) + return -1; + if (this.tupleVariable == null) { + this.tupleVariable = v; + this.tupleType = (TupleType)tupleType; + this.deconstructionResults = new ILVariable[this.tupleType.Cardinality]; + } + if (this.tupleType.Cardinality < 2) + return -1; + if (v != tupleVariable || !this.tupleType.Equals(tupleType)) + return -1; + if (this.deconstructionResults[index] == null) { + var freshVar = new ILVariable(VariableKind.StackSlot, this.tupleType.ElementTypes[index]) { Name = "E_" + index }; + delayedActions += _ => context.Function.Variables.Add(freshVar); + this.deconstructionResults[index] = freshVar; + } + delayedActions += _ => { + inst.ReplaceWith(new LdLoc(this.deconstructionResults[index])); + }; + return index; + } + return -1; + } + /// /// stloc v(lhs) /// expr(..., deconstruct { ... }, ...) @@ -118,34 +176,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms { int startPos = pos; Action 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.IsStatic) { - deconstructedType = deconstructMethod.Parameters[0].Type; + if (deconstructMethod == null) { + deconstructedType = this.tupleType; + rootTestedOperand = new LdLoc(this.tupleVariable); } else { - deconstructedType = deconstructMethod.DeclaringType; + 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 } bool MatchDeconstruction(ILInstruction inst, out IMethod deconstructMethod, - out ILInstruction testedOperand, out List deconstructionResults, - out Dictionary 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 return false; if (call.Arguments.Count < 3) return false; - deconstructionResults = new List(); - deconstructionResultsLookup = new Dictionary(); - 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 } bool MatchConversions(Block block, ref int pos, - Dictionary deconstructionResultsLookup, out Dictionary conversions, - out List conversionStLocs) + out List conversionStLocs, + ref Action delayedActions) { conversions = new Dictionary(); conversionStLocs = new List(); int previousIndex = -1; while (MatchConversion( block.Instructions.ElementAtOrDefault(pos), out var inputInstruction, - out var outputVariable, out var info)) - { - 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 } bool MatchAssignments(Block block, ref int pos, - Dictionary deconstructionResultsLookup, Dictionary conversions, List conversionStLocs, ref Action delayedActions) @@ -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 deconstructInst.Assignments.Instructions.Add(new StLoc(stLoc.Variable, new LdLoc(freshVar))); stLoc.Variable = freshVar; }; - } conversionStLocIndex++; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index d4963e909..82225949c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -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) { diff --git a/ICSharpCode.Decompiler/TypeSystem/TupleType.cs b/ICSharpCode.Decompiler/TypeSystem/TupleType.cs index 7833b84c5..906fa413a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TupleType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TupleType.cs @@ -44,6 +44,11 @@ namespace ICSharpCode.Decompiler.TypeSystem /// public ImmutableArray ElementTypes { get; } + /// + /// Gets the cardinality of the tuple. + /// + public int Cardinality => ElementTypes.Length; + /// /// Gets the names of the tuple elements. /// diff --git a/ILSpy.BamlDecompiler/Properties/launchSettings.json b/ILSpy.BamlDecompiler/Properties/launchSettings.json new file mode 100644 index 000000000..253f16b70 --- /dev/null +++ b/ILSpy.BamlDecompiler/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "ILSpy.BamlDecompiler": { + "commandName": "Executable", + "executablePath": "C:\\Users\\sie_p\\Projects\\ILSpy\\ILSpy\\bin\\Debug\\net472\\ILSpy.exe", + "commandLineArgs": "/separate" + } + } +} \ No newline at end of file diff --git a/ILSpy.ReadyToRun/Properties/launchSettings.json b/ILSpy.ReadyToRun/Properties/launchSettings.json new file mode 100644 index 000000000..5df6a1d3d --- /dev/null +++ b/ILSpy.ReadyToRun/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "ILSpy.ReadyToRun": { + "commandName": "Executable", + "executablePath": "C:\\Users\\sie_p\\Projects\\ILSpy\\ILSpy\\bin\\Debug\\net472\\ILSpy.exe", + "commandLineArgs": "/separate /language:ReadyToRun" + } + } +} \ No newline at end of file diff --git a/ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs b/ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs new file mode 100644 index 000000000..2009d3785 --- /dev/null +++ b/ILSpy/TreeNodes/ResourceNodes/JsonResourceNode.cs @@ -0,0 +1,99 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Threading.Tasks; + +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.TreeNodes +{ + [Export(typeof(IResourceNodeFactory))] + sealed class JsonResourceNodeFactory : IResourceNodeFactory + { + private readonly static string[] jsonFileExtensions = { ".json" }; + + public ILSpyTreeNode CreateNode(Resource resource) + { + Stream stream = resource.TryOpenStream(); + if (stream == null) + return null; + return CreateNode(resource.Name, stream); + } + + public ILSpyTreeNode CreateNode(string key, object data) + { + if (!(data is Stream)) + return null; + foreach (string fileExt in jsonFileExtensions) + { + if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase)) + return new JsonResourceEntryNode(key, (Stream)data); + } + return null; + } + } + + sealed class JsonResourceEntryNode : ResourceEntryNode + { + string json; + + public JsonResourceEntryNode(string key, Stream data) + : base(key, data) + { + } + + // TODO : add Json Icon + public override object Icon => Images.Resource; + + public override bool View(TabPageModel tabPage) + { + AvalonEditTextOutput output = new AvalonEditTextOutput(); + IHighlightingDefinition highlighting = null; + + tabPage.ShowTextView(textView => textView.RunWithCancellation( + token => Task.Factory.StartNew( + () => { + try { + // cache read XAML because stream will be closed after first read + if (json == null) { + using (var reader = new StreamReader(Data)) { + json = reader.ReadToEnd(); + } + } + output.Write(json); + highlighting = HighlightingManager.Instance.GetDefinitionByExtension(".json"); + } + catch (Exception ex) { + output.Write(ex.ToString()); + } + return output; + }, token) + ).Then(t => textView.ShowNode(t, this, highlighting)) + .HandleExceptions()); + tabPage.SupportsLanguageSwitching = false; + return true; + } + } +} diff --git a/ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs b/ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs new file mode 100644 index 000000000..035b944e8 --- /dev/null +++ b/ILSpy/TreeNodes/ResourceNodes/TextResourceNode.cs @@ -0,0 +1,98 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Threading.Tasks; + +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; + +namespace ICSharpCode.ILSpy.TreeNodes +{ + [Export(typeof(IResourceNodeFactory))] + sealed class TextResourceNodeFactory : IResourceNodeFactory + { + private readonly static string[] txtFileExtensions = { ".txt", ".md" }; + + public ILSpyTreeNode CreateNode(Resource resource) + { + Stream stream = resource.TryOpenStream(); + if (stream == null) + return null; + return CreateNode(resource.Name, stream); + } + + public ILSpyTreeNode CreateNode(string key, object data) + { + if (!(data is Stream)) + return null; + foreach (string fileExt in txtFileExtensions) + { + if (key.EndsWith(fileExt, StringComparison.OrdinalIgnoreCase)) + return new TextResourceEntryNode(key, (Stream)data); + } + return null; + } + } + + sealed class TextResourceEntryNode : ResourceEntryNode + { + string text; + + public TextResourceEntryNode(string key, Stream data) + : base(key, data) + { + } + + public override object Icon => Images.Resource; + + public override bool View(TabPageModel tabPage) + { + AvalonEditTextOutput output = new AvalonEditTextOutput(); + IHighlightingDefinition highlighting = null; + + tabPage.ShowTextView(textView => textView.RunWithCancellation( + token => Task.Factory.StartNew( + () => { + try { + // cache read text because stream will be closed after first read + if (text == null) { + using (var reader = new StreamReader(Data)) { + text = reader.ReadToEnd(); + } + } + output.Write(text); + highlighting = null; + } + catch (Exception ex) { + output.Write(ex.ToString()); + } + return output; + }, token) + ).Then(t => textView.ShowNode(t, this, highlighting)) + .HandleExceptions()); + tabPage.SupportsLanguageSwitching = false; + return true; + } + } +} diff --git a/doc/ILAst Pattern Matching.md b/doc/ILAst Pattern Matching.md new file mode 100644 index 000000000..fab710123 --- /dev/null +++ b/doc/ILAst Pattern Matching.md @@ -0,0 +1,152 @@ +ILAst Pattern Matching +================= + +Some IL instructions are classified as "patterns". +```c# +abstract class PatternMatchILInstruction : IStoreInstruction { + public ILInstruction TestedOperand { get; } + public ILVariable Variable { get; } // variable that receives the match result; may also be a temporary +} + +public bool IsPattern(this ILInstruction inst, out ILInstruction testedOperand) => inst switch { + PatternMatchILInstruction pm => testedOperand = pm.TestedOperand; return true; + LogicNot logicNot => IsPattern(logicNot.Operand, out testedOperand), + Comp comp => testedOperand = comp.Left; return IsConstant(comp.Right); +} +``` +Every `match.*` instruction has the following properties: + * The `TestedOperand` specifies what gets matched against the pattern. + * The `Variable` stores the value of `TestedOperand` (after converting to the matched type, if appropriate). + * If this variable is also used outside the `match.*` node, it corresponds to the C# `single_variable_designation`. + * Otherwise it's a temporary used for pattern matching. + * I think in both cases it should have VariableKind.PatternVar + * The match instruction evaluates to `StackType.I4`: 0 if the pattern was matched, 1 otherwise. + +Some `match` instructions have a body with `List nestedPatterns`. Here every nested pattern must be a pattern according to `IsPattern()`, and the `testedOperand` of each must be a member of the `Variable` of the parent pattern. (members are: field, property, or deconstruction.result). +(exception: `match.and`/`match.or`, these instead require the `testedOperand` to be exactly the `Variable` of the parent pattern) + + +Examples +-------- +1) `expr is var x` + => + `match.var(x = expr)` + => + ``` + Block (VarPattern) { + stloc x(expr) // single eval expr + final: ldc.i4 1 // match always + } + ``` +2) `expr is T x` + => + `match.type T(x = expr) {}` + => + ``` + Block (TypePattern) { + stloc x(isinst T(expr)) + final: x != null + } + ``` +3) `expr is C { A: var x } z` + => + ``` + match.type C(z = expr) { + match.var(x = z.A) + } + ``` + => + ``` + Block (TypePattern) { + stloc z(isinst T(expr)) + final: (z != null) + && Block(VarPattern) { + stloc x(z.A) + final: ldc.i4 1 + } + } + ``` +4) `expr is C { A: var x, B: 42, C: { A: 4 } } z` + => + ``` + match.type C(z = expr) { + match.var (x = z.A), + comp (z.B == 42), + match.recursive (temp2 = z.C) { + comp (temp2.A == 4) + } + } + ``` + => + ``` + Block (TypePattern) { + stloc z(isinst C(expr)) + final: (z != null) + && Block(VarPattern) { + stloc x(z.A) + final: ldc.i4 1 + } + && comp (z.B == 42) + && Block(RecursivePattern) { + stloc temp2(z.C) + final: (temp2 != null) + && comp (temp2.A == 4) + } + } + ``` +5) `expr is C(var x, var y, <4) { ... }` + => + ``` + match.recursive.type.deconstruct(C tmp1 = expr) { + match.var(x = deconstruct.result0(tmp1)), + match.var(y = deconstruct.result1(tmp1)), + comp(deconstruct.result2(tmp1) < 4), + } + ``` + +6) `expr is C(1, D(2, 3))` + => + ``` + match.type.deconstruct(C c = expr) { + comp(deconstruct.result0(c) == 1), + match.type.deconstruct(D d = deconstruct.result1(c)) { + comp(deconstruct.result0(d) == 2), + comp(deconstruct.result1(d) == 2), + } + } + ``` + +7) `x is >= 0 and var y and <= 100` + ``` + match.and(tmp1 = x) { + comp(tmp1 >= 0), + match.var(y = tmp1), + comp(tmp1 <= 100) + } + ``` + +8) `x is not C _` + => + ``` + logic.not( + match.type(C tmp1 = x) {} + ) + ``` + +9) `expr is (var a, var b)` (when expr is object) + => + ``` + match.type.deconstruct(ITuple tmp = expr) { + match.var(a = deconstruct.result0(tmp)), + match.var(b = deconstruct.result1(tmp)), + } + ``` + +10) `expr is (var a, var b)` (when expr is ValueTuple) + => + ``` + match.recursive(tmp = expr) { + match.var(a = tmp.Item1), + match.var(b = tmp.Item2), + } + ``` \ No newline at end of file diff --git a/msbuild.binlog b/msbuild.binlog new file mode 100644 index 000000000..357791088 Binary files /dev/null and b/msbuild.binlog differ