diff --git a/Directory.Packages.props b/Directory.Packages.props index b77822de6b..42ac5d6aff 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,8 +15,8 @@ - - + + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs index 3fb1734daa..dc6bd309c9 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs @@ -16,12 +16,10 @@ // 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.CSharp.Transforms; using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; @@ -361,7 +359,7 @@ private bool UseCSharpSwitch(out KeyValuePair defaultSec private bool MatchRoslynSwitchOnString() { var insns = analysis.RootBlock.Instructions; - return insns.Count >= 3 && SwitchOnStringTransform.MatchComputeStringHashCall(insns[insns.Count - 3], analysis.SwitchVariable, out var switchLdLoc); + return insns.Count >= 3 && SwitchOnStringTransform.MatchComputeStringOrReadOnlySpanHashCall(insns[insns.Count - 3], analysis.SwitchVariable, out _); } /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 7f494cad6b..5385bbaae6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -274,6 +274,18 @@ void VisitLogicNot(Comp inst, ILInstruction arg) protected internal override void VisitCall(Call inst) { + if (NullableLiftingTransform.MatchGetValueOrDefault(inst, out var nullableValue, out var fallback) + && SemanticHelper.IsPure(fallback.Flags)) + { + context.Step("call Nullable{T}.GetValueOrDefault(a, b) -> a ?? b", inst); + var ldObj = new LdObj(nullableValue, inst.Method.DeclaringType); + var replacement = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, ldObj, fallback) { + UnderlyingResultType = fallback.ResultType + }; + inst.ReplaceWith(replacement.WithILRange(inst)); + replacement.AcceptVisitor(this); + return; + } base.VisitCall(inst); TransformAssignment.HandleCompoundAssign(inst, context); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 44f73e3e47..4e297b00d9 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -16,7 +16,6 @@ // 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; @@ -1047,6 +1046,24 @@ internal static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstructio return true; } + /// + /// Matches 'call nullableValue.GetValueOrDefault(fallback)' + /// + internal static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstruction nullableValue, out ILInstruction fallback) + { + nullableValue = null; + fallback = null; + if (!(inst is Call call)) + return false; + if (call.Method.Name != "GetValueOrDefault" || call.Arguments.Count != 2) + return false; + if (call.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT) + return false; + nullableValue = call.Arguments[0]; + fallback = call.Arguments[1]; + return true; + } + /// /// Matches 'call Nullable{T}.GetValueOrDefault(ldloca v)' /// diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index d59041381a..e254d243b6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -294,6 +294,16 @@ bool AddSwitchSection(string value, ILInstruction inst) keepAssignmentBefore = true; switchValue = new LdLoc(switchValueVar); } + if (!switchValueVar.Type.IsKnownType(KnownTypeCode.String)) + { + if (!context.Settings.SwitchOnReadOnlySpanChar) + return false; + if (!switchValueVar.Type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) + && !switchValueVar.Type.IsKnownType(KnownTypeCode.SpanOfT)) + { + return false; + } + } // if instruction must be followed by a branch to the next case if (!nextCaseJump.MatchBranch(out Block currentCaseBlock)) return false; @@ -335,7 +345,7 @@ bool AddSwitchSection(string value, ILInstruction inst) int offset = firstBlock == null ? 1 : 0; var sections = new List(values.Skip(offset).SelectWithIndex((index, s) => new SwitchSection { Labels = new LongSet(index), Body = s.Item2 is Block b ? new Branch(b) : s.Item2.Clone() })); sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = currentCaseBlock != null ? (ILInstruction)new Branch(currentCaseBlock) : new Leave((BlockContainer)nextCaseBlock) }); - var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray(), context.TypeSystem.FindType(KnownTypeCode.String)); + var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray(), switchValueVar.Type); var inst = new SwitchInstruction(stringToInt); inst.Sections.AddRange(sections); if (removeExtraLoad) @@ -1008,7 +1018,7 @@ bool MatchRoslynSwitchOnString(InstructionCollection instructions if (!(switchBlockInstructionsOffset + 1 < switchBlockInstructions.Count && switchBlockInstructions[switchBlockInstructionsOffset + 1] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(out var switchValueVar) - && MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset], + && MatchComputeStringOrReadOnlySpanHashCall(switchBlockInstructions[switchBlockInstructionsOffset], switchValueVar, out LdLoc switchValueLoad))) { return false; @@ -1643,19 +1653,28 @@ bool MatchStringLengthCall(ILInstruction inst, ILVariable switchValueVar) } /// - /// Matches 'stloc(targetVar, call ComputeStringHash(ldloc switchValue))' + /// Matches + /// 'stloc(targetVar, call ComputeStringHash(ldloc switchValue))' + /// - or - + /// 'stloc(targetVar, call ComputeSpanHash(ldloc switchValue))' + /// - or - + /// 'stloc(targetVar, call ComputeReadOnlySpanHash(ldloc switchValue))' /// - internal static bool MatchComputeStringHashCall(ILInstruction inst, ILVariable targetVar, out LdLoc switchValue) + internal static bool MatchComputeStringOrReadOnlySpanHashCall(ILInstruction inst, ILVariable targetVar, out LdLoc switchValue) { switchValue = null; if (!inst.MatchStLoc(targetVar, out var value)) return false; - if (!(value is Call c && c.Arguments.Count == 1 && c.Method.Name == "ComputeStringHash" && c.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())) - return false; - if (!(c.Arguments[0] is LdLoc)) - return false; - switchValue = (LdLoc)c.Arguments[0]; - return true; + if (value is Call c && c.Arguments.Count == 1 + && c.Method.Name is "ComputeStringHash" or "ComputeSpanHash" or "ComputeReadOnlySpanHash" + && c.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) + { + if (c.Arguments[0] is not LdLoc ldloc) + return false; + switchValue = ldloc; + return true; + } + return false; } ///