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;
}
///